fix(validator): bump gobdk to hot-fix with per-CheckSig signature cache#842
Conversation
Pins github.com/bitcoin-sv/bdk/module/gobdk to a pseudo-version of the hot-fix on top of v1.2.3 (bitcoin-sv/bdk PR bsv-blockchain#40) that adds a per-instance CachingScriptChecker overriding CheckSig to short-circuit identical signature verifications within one EvalScript run. Without the hot-fix the validator stalls for hours on testnet block 1,451,505 / tx 7bc9a3408dd0c87b835c887a0bce22c20788fc3c4b953929d4367656d80acab5. The tx spends a 218-sat output whose locking script is 490,001 bytes of (OP_2DUP OP_CHECKSIGVERIFY) * 245,000 + OP_CHECKSIG. Each (OP_2DUP, OP_CHECKSIGVERIFY) pair leaves the stack at [sig, pubkey] so the script performs 245,001 identical ECDSA verifications, each preceded by a full SignatureHash SHA256 stream over the 490 KB scriptCode. Without the cache that is hours of CPU at 100% on a single core; the cgo entry point is uninterruptible from Go so it presents to operators as a hang in _Cfunc_ScriptEngine_VerifyScript. Block 1,451,505 is in testnet's hardcoded checkpoint set, so the script must verify as valid — the fix preserves consensus semantics and only collapses redundant work. With the hot-fix the verifier completes in ~70 s on macOS arm64 / similar on Linux x86_64 and returns nil (accepted as valid). Test_ScriptVerificationGoBDK_StuckTx_ProductionPolicy in services/validator/ScriptVerifierGoBDK_test.go regresses the fix end-to-end: - Loads the real testnet tx hex from services/validator/testdata/stuck-tx-7bc9a340.hex - Wires the real 490 KB previous-output locking script from services/validator/testdata/stuck-tx-7bc9a340-prev-locking-script-0.hex onto the input (WhatsOnChain truncates the script in its API/UI, so the fixture was extracted from the raw parent tx supplied by an operator with access to the production utxo external store) - Drives the verifier against production-grade PolicySettings - Asserts VerifyScript returns nil within a 3 minute deadline The go.mod entry carries a hard-coded warning: do not bump gobdk to v1.2.4 (or later) until bitcoin-sv/bdk PR bsv-blockchain#41 — the master/v1.2.4 port of the same hot-fix — has been merged. Without bsv-blockchain#41 the upstream v1.2.4 release ships without the cache and re-introduces the multi-hour stall.
|
🤖 Claude Code Review Status: Complete Critical Finding[Critical] Dependency pinned to unmerged code: The go.mod pins github.com/bitcoin-sv/bdk/module/gobdk to pseudo-version v1.2.4-0.20260511121643-5ab3fd5b627d, which references commit 5ab3fd5b627d from bitcoin-sv/bdk PR #40. However, PR #40 is closed but not merged (merged: false). This means:
Recommendation: Before merging this PR, verify that the BDK fix has been properly merged (either PR #40 or #41) and pin to the official merged commit or tagged release instead of an unmerged branch commit. Additional Observations
|
Benchmark Comparison ReportBaseline: Current: Summary
All benchmark results (sec/op)
Threshold: >10% with p < 0.05 | Generated: 2026-05-11 13:48 UTC |
The original commit added a Test_ScriptVerificationGoBDK_StuckTx_ProductionPolicy regression test that loaded the real testnet block 1,451,505 fixture and asserted the verifier returns within a deadline. The test is a faithful end-to-end reproducer but takes ~70 s on macOS arm64 and ~3 min on Linux x86_64 even with the gobdk hot-fix wired in — too much wall-clock to spend on every CI run. Synthetic alternatives that fit a < 1 s with-cache / > 60 s without-cache budget are not feasible with the current per-CheckSig memcmp identity check in the gobdk cache: the per-iter memcmp cost scales linearly with scriptCode size, capping the speedup ratio against the without-cache SignatureHash at ~15 x. No (N, scriptCode) combination fits both budgets. The fixture file naming also did not follow the rest of the codebase's hash-based <txid>.hex / <hash>.bin convention. Drop the test and the three testdata files from this PR so the change is just the go.mod bump. The previous commit on this branch retains the test in git history; revisit later either with a cheaper-identity gobdk cache (would enable a smaller synthetic regression) or as an opt-in benchmark target outside the default CI test set.
Drop the dangling reference to the regression test (removed in the previous commit) and the overspecified production-tx details. Keep the two pieces of information a future bumper actually needs: which BDK PR landed the hot-fix, and the constraint that v1.2.4 should not be adopted until bitcoin-sv/bdk PR bsv-blockchain#41 has merged.
…he (#842) Pins github.com/bitcoin-sv/bdk/module/gobdk to a pseudo-version of the hot-fix on top of v1.2.3 (bitcoin-sv/bdk PR #40) that adds a per-instance CachingScriptChecker overriding CheckSig to short-circuit identical signature verifications within one EvalScript run. Without the hot-fix the validator stalls for hours on testnet block 1,451,505 / tx 7bc9a3408dd0c87b835c887a0bce22c20788fc3c4b953929d4367656d80acab5, whose 490 KB locking script performs 245,001 identical ECDSA verifications. The cgo entry point is uninterruptible from Go so it presents to operators as a hang in _Cfunc_ScriptEngine_VerifyScript. With the hot-fix the verifier completes in ~70 s. Do not bump gobdk to v1.2.4 (or later) until bitcoin-sv/bdk PR #41 — the master/v1.2.4 port of the same hot-fix — has been merged.
|



Summary
Pins `github.com/bitcoin-sv/bdk/module/gobdk` to a pseudo-version of the hot-fix on top of `v1.2.3` (bitcoin-sv/bdk PR #40) that adds a per-instance `CachingScriptChecker` overriding `CheckSig` to short-circuit identical signature verifications within one `EvalScript` run.
Adds a regression test that drives a known-stuck testnet tx through the validator end-to-end against production policy settings and asserts the verifier returns in bounded time with `nil` (accepted as valid).
Background
Validators stalled for hours on testnet block 1,451,505 trying to verify tx `7bc9a3408dd0c87b835c887a0bce22c20788fc3c4b953929d4367656d80acab5`. The tx spends a 218-sat output whose locking script is 490,001 bytes of:
```
(OP_2DUP OP_CHECKSIGVERIFY) * 245,000 + OP_CHECKSIG
```
Each `(OP_2DUP, OP_CHECKSIGVERIFY)` pair leaves the stack at `[sig, pubkey]`, so the script performs 245,001 identical ECDSA verifications, each preceded by a full `SignatureHash` SHA256 stream over the 490 KB scriptCode. Without a cache that is hours of single-core CPU. The cgo entry point `_Cfunc_ScriptEngine_VerifyScript` is uninterruptible from Go so it presents to operators as a hang.
Block 1,451,505 is part of testnet's hardcoded checkpoint set (`go-chaincfg/params.go`) — the script is consensus-valid and the verifier must return `SCRIPT_ERR_OK`. Confirmed reproduced against `gobdk` v1.2.2 / v1.2.3 / v1.2.4 and against the pure-Go `gobt` verifier — not a library version regression.
The fix
`bitcoin-sv/bdk` PR #40 adds `bsv::CachingScriptChecker`:
First `CheckSig` does one full sighash plus one ECDSA verify; remaining 245,000 calls hit the in-memory map and return in O(ns) each. Verify time collapses from hours to ~70 s on macOS arm64 / similar on Linux x86_64.
The hot-fix branched off the BDK `release/1.2.3` tag rather than `master` so it can ship as `v1.2.3.1` (or merge forward) without entangling with the in-flight v1.2.4 upgrade in #839. PR #41 in the BDK repo is the master / v1.2.4 port of the same fix.
Why not wire the existing `CachingTransactionSignatureChecker` from `script/sigcache.cpp`?
Investigated; rejected. Three reasons (full discussion in BDK PR #40):
Changes
Test plan
Follow-up
When BDK PR #41 merges and a tagged v1.2.4 release ships with the same hot-fix, a separate PR can bump to that and drop the warning comment.