wire: assert block arena reuse across transactions
What changed, and why it matters
This commit only adds a new test to btcd. It does not change any production code. The test checks that when decoding a Bitcoin block containing many small transactions, the internal memory arena reuses a single chunk rather than allocating many chunks. It is a regression test for memory efficiency, not a fix for a security vulnerability.
No action required. This is a test-only commit. If reviewing a related series, verify that the production-code patch preceding this test actually implements the arena-reuse behavior the test asserts.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces TestBlockArenaTransactionReuse in wire/arena_test.go. The test builds a MsgBlock with 1,000 copies of a small transaction, serializes it, then deserializes it via both Deserialize and DeserializeTxLoc. It instruments the scriptChunkPools to count allocations and asserts that only one chunk of the transaction-script class is allocated. This guards against a regression where per-transaction rewinds would cause O(n) chunk allocation per block instead of reuse bounded by the largest transaction.
Changed components
wire/arena_test.goInspect captured patch +65 / −0
### wire/arena_test.go
@@ -128,6 +128,71 @@ func TestScriptArenaRewind(t *testing.T) {
require.Equal(t, &largest[0], &s[0])
}
+// TestBlockArenaTransactionReuse ensures block staging depends on the largest
+// transaction rather than the aggregate scripts across the block.
+func TestBlockArenaTransactionReuse(t *testing.T) {
+ for _, locations := range []bool{false, true} {
+ name := "deserialize"
+ if locations {
+ name = "transaction_locations"
+ }
+
+ t.Run(name, func(t *testing.T) {
+ original := scriptChunkPools
+ t.Cleanup(func() {
+ scriptChunkPools = original
+ })
+
+ var allocated [len(scriptChunkClasses)]int
+ for i, size := range scriptChunkClasses {
+ scriptChunkPools[i] = &chunkClassPool{
+ fixed: make(
+ chan *[]byte, scriptChunkFixedCaps[i],
+ ),
+ pool: sync.Pool{New: func() interface{} {
+ allocated[i]++
+ chunk := make([]byte, size)
+ return &chunk
+ }},
+ }
+ }
+
+ block := MsgBlock{Header: blockOne.Header}
+ for i := 0; i < 1000; i++ {
+ block.AddTransaction(blockOne.Transactions[0])
+ }
+
+ var encoded bytes.Buffer
+ require.NoError(t, block.Serialize(&encoded))
+
+ var decoded MsgBlock
+ if locations {
+ locs, err := decoded.DeserializeTxLoc(
+ bytes.NewBuffer(encoded.Bytes()),
+ )
+ require.NoError(t, err)
+ require.Len(t, locs, len(block.Transactions))
+ } else {
+ require.NoError(t, decoded.Deserialize(
+ bytes.NewReader(encoded.Bytes()),
+ ))
+ }
+
+ var roundTrip bytes.Buffer
+ require.NoError(t, decoded.Serialize(&roundTrip))
+ require.Equal(t, encoded.Bytes(), roundTrip.Bytes())
+
+ for i, count := range allocated {
+ want := 0
+ if i == txScriptChunkClass {
+ want = 1
+ }
+ require.Equal(t, want, count, "class %d", i)
+ }
+ })
+ }
+}
+
// TestScriptArenaDecodeGrowth decodes a transaction whose script data
// overflows the starting chunk class for standalone transactions, ensuring
// the decode path grows the arena transparently.Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.