In this commit, we fix two go vet errors that go 1.26 now treats as
hard failures during test compilation.
In btcjson/help.go, the final result row of a complex help description
was emitted via fmt.Fprintf with a non-constant format string (the
result text could contain '%' chars). Switch to fmt.Fprint, since
there are no format args here anyway.
In btcec/schnorr/musig2/musig2_test.go, the nonce-registration loop
ran inside a goroutine and called t.Fatalf on failure. Fatalf only
exits the calling goroutine, so the test goroutine would keep running
with stale state. Use t.Errorf + return so the failure is recorded
correctly and the goroutine exits cleanly.
In this commit, we update every in-tree go.mod to declare go 1.25 and
have CI build with 1.26.3 (Dockerfile uses the golang:1.26-alpine base).
Docs that mention the minimum required Go version are aligned with the
new floor as well.
Fixes#2527, which flagged the README.md / go.mod version mismatch (the
README claimed 1.22 while the root + v2transport go.mod files already
required 1.23.2).
In this commit, we pin every in-tree go.mod to the freshly cut submodule
tags ahead of a btcd point release: btcec/v2.4.0, btcutil/v1.2.0,
btcutil/psbt/v1.2.0, and chaincfg/chainhash/v1.2.0. The btcec bump also
drags secp256k1 up to v4.4.0 (and blake256 to v1.1.0 transitively) for
every module that imports btcec.
v2transport stays at v1.0.1 since no v2transport code changed since the
last tag, but its go.mod is bumped here so the workspace resolves to a
consistent set of internal deps.
Add a build step to the existing Build job that cross-compiles both the
top-level module and btcutil for GOOS=android GOARCH=arm64.
This catches regressions in the android-specific build path (e.g. the
net_android.go wrapper around anet.InterfaceAddrs) without needing an
emulator or NDK toolchain - android/arm64 builds without cgo.
The previous commit exported the interfaceAddrs helper in net.go but
missed the parallel definition in net_noop.go (build tag: appengine),
which left builds with -tags appengine broken with:
./certgen.go:79:16: undefined: InterfaceAddrs
Rename the appengine no-op variant to match, restoring the appengine
build.
Golang's net package has been broken for android since android 11
and the node will not be able to call net.InterfaceAddr() without
root.
For android builds, we use anet so that the btcd node can be used
without root.
httpURL was being recomputed on every JSON-RPC POST. It went through
ParseAddressString to discriminate Unix sockets from TCP, which
called net.ResolveTCPAddr and triggered a DNS lookup whose result
was thrown away.
Both inputs (config.Host and config.DisableTLS) are immutable after
New, so the URL is constant for the life of the Client. Compute it
once and store it on the Client, mirroring the parsedDialAddr cache
already in newHTTPClient.
The httpURL method now uses HasPrefix and runs exactly once per
Client. Drop its (string, error) signature — with the resolve gone
nothing can fail. Add table coverage for the URL strings, plus a
wiring test that catches refactors of New silently dropping the
assignment to Client.httpURL.
The peerLifecycle channel is sized cfg.MaxPeers*2 because each
peer can emit two lifecycle events (peerAdd followed by peerDone).
Without an inline comment a future reader could "simplify" the
factor to cfg.MaxPeers and tighten back-pressure under normal
connect/disconnect churn, causing peerLifecycleHandler goroutines
to block.
peer.processRemoteVerAckMsg sets verAckReceived=true *before* it
invokes the OnVerAck listener that closes verAckCh. If the peer
disconnects in that window, the peerLifecycleHandler select may
pick Peer.Done() over verAckCh and skip the peerAdd send -- yet
VerAckReceived() still returns true. handleDonePeerMsg would then
call syncManager.DonePeer for a peer that was never NewPeer'd
(sync manager logs a warning and discards).
Add an explicit peerAdded atomic.Bool on serverPeer. Set it in
peerLifecycleHandler after peerAdd has been enqueued. Gate the
syncManager.DonePeer + orphan eviction in handleDonePeerMsg on
peerAdded.Load() so those side effects only fire for peers that
were actually registered.
This tightens the "peerAdd precedes peerDone, or peerAdd is
skipped entirely" invariant from this PR into something explicit
rather than something derived from a flag that races OnVerAck.
The prior select/default+close() guard on verAckCh is correct only
under the invariant that OnVerAck is invoked from a single goroutine
(peer.processRemoteVerAckMsg on the input handler). The two steps
are not atomic: any future change that invokes listeners off the
input goroutine would let two concurrent callers both observe
default and panic on double-close.
Replace the guard with sync.Once. This makes the close-once
contract obviously correct rather than correct-by-distant-invariant
and drops the dead "called more than once" log path.
In this commit, we modify the execution logic, such that the P2A witness
program doesn't appear as a unknown witness program version. For
execution, we just make sure the script passes.
Add a regression test for the StringOrArray.MarshalJSON infinite-recursion
fix that exercises both the direct method call and the path through
json.Marshal. Without the fix, the test triggers a goroutine stack
overflow.
Also add a round-trip test and tests for the new Warnings field on
GetBlockChainInfoResult, covering both the legacy single-string form and
the post-bitcoin#29845 array form.
Bitcoin Core added a warnings field to getblockchaininfo in PR #10858,
and recently changed it from a string to an array in PR #29845. Add
the field using StringOrArray type to handle both formats.
This allows rpcclient to properly unmarshal getblockchaininfo responses
from all Bitcoin Core versions.
Closes#2444
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The MarshalJSON method was calling json.Marshal(h) where h is of type
StringOrArray. Since StringOrArray implements json.Marshaler, this
caused json.Marshal to call MarshalJSON again, resulting in infinite
recursion and a stack overflow.
Fix by converting to the underlying []string type before marshaling,
which breaks the interface lookup cycle.
Closes#2369
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PR #2467 changed regtest to match Core's BIP34/65/66 activation rules, and
the merged stack carries that (commit cd4e5426 "regtest: align activations
with Bitcoin Core"). Height-1 regtest blocks now need a BIP34-compliant
coinbase height and a post-BIP66 block version.
The failing tests came from commits added after PR #2467 was opened on
December 25, 2025 but before it merged on April 30, 2026:
- c1a46122 ("blockchain: add ProcessBlockHeader")
- f9645f07 ("blockchain: reuse existing header node in maybeAcceptBlock")
- dc6e096c ("netsync: add TestSyncStateMachine for end-to-end IBD sync flow")
- ce094262 ("netsync: add TestStartSyncBlockFallback for block-only sync path")
- 2aae8a6d ("netsync: add TestStartSyncChainCurrent for chain-current noop path")
Because those tests landed later, they kept the old regtest assumptions even
though #2467 had already been authored and tested against the older tree.
Once #2467 finally merged, these newer tests started building invalid regtest
blocks and headers.
Fix them by setting the genesis tip height to 0 before generating descendants,
using Version 4 in the regtest block/header helpers, and encoding the test
coinbase height with a minimal BIP34 push plus padding for the generic coinbase
script-length rule.
NewBatch called New() and then called start() again. In HTTP POST mode that
created a second sendPostHandler and another shutdown-cancel goroutine, which
broke the expected single-flight serialization of POST sends.
Keep NewBatch as a semantic toggle only: rely on New() to start handlers
once, then set batch=true. A regression test now checks that batch POST
requests stay serialized through one active transport call.
Batch requests were only clearing batchList on Send() errors. The
per-request futures remained unresolved, so callers waiting on Receive
could block forever after a failed batch round trip.
Add failBatchRequests to fan out the Send() error to every queued batch
request and clear tracking state in one place. A regression test now
verifies queued futures complete with the same error returned by Send().
When shutdown races with sendPostRequest, a request could be marked
as ErrClientShutdown and still be enqueued. The sendPostHandler cleanup
loop would then try to send a second terminal response and could block
forever on a full response channel.
Fix this by prioritizing the shutdown path. First check shutdown with a
non-blocking select and return immediately when it is already closed.
Then use a second select to choose between enqueue and shutdown for the
remaining race window.
A regression test verifies a shutdown request is failed immediately and
never enqueued.
Use a shutdown-aware context for HTTP POST handling so shutdown can
interrupt in-flight requests.
Centralize shutdown error remapping in sendPostRequestAndRespond so all
error exits consistently return ErrClientShutdown when shutdown causes a
context cancellation. Move the retrying HTTP POST path into
sendPostRequestWithRetry and cover it with shutdown regression tests.
Extract the nested P2SH analysis from CalcScriptInfo into a dedicated
calcP2SHScriptInfo helper, and switch it to use finalOpcodeData for
redeem-script extraction (matching the consolidation in NewEngine and
GetWitnessSigOpCount). The helper also threads the inner witness-script
expected-input contribution for nested P2WSH spends, which the previous
inline code did not account for.
Both NewEngine and GetWitnessSigOpCount used sigScript[1:] (a raw byte
suffix of the scriptSig) to identify the candidate redeem script for
nested P2SH witness detection. GetPreciseSigOpCount and the existing
P2SH execution path already get the redeem script as the final pushed
element of the push-only scriptSig (via finalOpcodeData and
savedFirstStack[len-1] respectively). This commit consolidates the
nested-witness detection sites to use finalOpcodeData(0, scriptSig) so
all three places agree on what the redeem script is.
As a side effect of using the actual redeem script for detection, the
len(witness) != 0 precondition in NewEngine becomes redundant — the
redeem script's shape alone determines whether the spend is nested
witness, and verifyWitnessProgram already enforces the required
witness-stack shape downstream.
The refreshed Bitcoin Core script reference tests cover cases where an
empty CHECKSIG input must still fail pubkey encoding checks before the
interpreter falls back to a generic false result.