diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3fb2f37a..f6394a9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -145,3 +145,18 @@ jobs: - name: Test run: make unit-race + + test-rpctest: + name: Unit rpctest + runs-on: ubuntu-latest + steps: + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Check out source + uses: actions/checkout@v4 + + - name: Test + run: make unit diff --git a/btcjson/chainsvrresults.go b/btcjson/chainsvrresults.go index 3ee2b8b6..5cd1e907 100644 --- a/btcjson/chainsvrresults.go +++ b/btcjson/chainsvrresults.go @@ -385,6 +385,9 @@ func (h *StringOrArray) UnmarshalJSON(data []byte) error { } switch v := unmarshalled.(type) { + case nil: + *h = nil + case string: *h = []string{v} diff --git a/btcjson/chainsvrresults_test.go b/btcjson/chainsvrresults_test.go index fb681a9c..f37adb4b 100644 --- a/btcjson/chainsvrresults_test.go +++ b/btcjson/chainsvrresults_test.go @@ -350,6 +350,16 @@ func TestGetBlockChainInfoWarnings(t *testing.T) { result: `{"warnings": []}`, expected: btcjson.StringOrArray{}, }, + { + name: "blockchain info with null warnings", + result: `{"warnings": null}`, + expected: nil, + }, + { + name: "blockchain info with warnings field omitted", + result: `{}`, + expected: nil, + }, } for _, test := range tests { diff --git a/integration/p2a_test.go b/integration/p2a_test.go index 059e56b7..e986db23 100644 --- a/integration/p2a_test.go +++ b/integration/p2a_test.go @@ -6,6 +6,7 @@ package integration import ( "testing" + "github.com/btcsuite/btcd/address/v2" "github.com/btcsuite/btcd/btcutil/v2" "github.com/btcsuite/btcd/chaincfg/v2" "github.com/btcsuite/btcd/integration/rpctest" @@ -46,7 +47,7 @@ func TestPayToAnchorSimple(t *testing.T) { // Create a P2A output using the helper to get a P2A address. This // ensures we're using the same P2A script generation logic. - p2aAddr, err := btcutil.NewAddressPayToAnchor(&chaincfg.SimNetParams) + p2aAddr, err := address.NewAddressPayToAnchor(&chaincfg.SimNetParams) if err != nil { t.Fatalf("unable to create P2A address: %v", err) } diff --git a/integration/rpctest/btcd.go b/integration/rpctest/btcd.go index 29642c84..22717a5c 100644 --- a/integration/rpctest/btcd.go +++ b/integration/rpctest/btcd.go @@ -6,6 +6,7 @@ package rpctest import ( "fmt" + "math/rand/v2" "os/exec" "path/filepath" "runtime" @@ -43,8 +44,12 @@ func btcdExecutablePath() (string, error) { return "", err } - // Build btcd and output an executable in a static temp path. - outputPath := filepath.Join(testDir, "btcd") + // Build btcd to a random path so concurrent `go test` processes + // (e.g. when test packages run in parallel under `make unit`) do + // not race on the same output file. Each test process pays a + // one-time compile cost; within a process the compileMtx-guarded + // cache keeps it to one build. + outputPath := filepath.Join(testDir, fmt.Sprintf("btcd-%d", rand.Uint32())) if runtime.GOOS == "windows" { outputPath += ".exe" } diff --git a/integration/rpctest/rpc_harness.go b/integration/rpctest/rpc_harness.go index 1d3d42da..9c9cb852 100644 --- a/integration/rpctest/rpc_harness.go +++ b/integration/rpctest/rpc_harness.go @@ -6,6 +6,7 @@ package rpctest import ( "fmt" + "math/rand/v2" "net" "os" "path/filepath" @@ -76,7 +77,15 @@ var ( // lastPort is the last port determined to be free for use by a new // node. It should be used atomically. - lastPort uint32 = defaultNodePort + // + // Seed with a random offset so concurrent `go test` processes + // (e.g. when integration/ and integration/rpctest/ run in parallel + // under `make unit`) do not race on the same port range. The + // bind-test in NextAvailablePort closes the listener before + // returning, leaving a window where another process could grab the + // same port; staggering each process's starting point avoids the + // collision. The 50k-port window leaves headroom below 65535. + lastPort uint32 = defaultNodePort + rand.Uint32N(50000) ) // HarnessTestCase represents a test-case which utilizes an instance of the diff --git a/netsync/manager.go b/netsync/manager.go index cf9c898b..9addc5ca 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -1124,9 +1124,10 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) { peer.UpdateLastAnnouncedBlock(&invVects[lastBlock].Hash) } - // Ignore invs from peers that aren't the sync if we are not current. - // Helps prevent fetching a mass of orphans. - if peer != sm.syncPeer && !sm.current() { + // Ignore invs from peers that aren't the sync peer if we are not + // current. Helps prevent fetching a mass of orphans. When syncPeer + // is nil, accept invs from any peer. + if sm.syncPeer != nil && peer != sm.syncPeer && !sm.current() { return }