Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f9645f0

Browse files
committed
blockchain: reuse existing header node in maybeAcceptBlock
maybeAcceptBlock unconditionally created a new blockNode, overwriting the index entry. If maybeAcceptBlockHeader had already processed the header, the pointer held by bestHeader's chainView became orphaned, breaking bestHeader.Contains and downstream checks like IsValidHeader. Check for an existing node first and upgrade its status to statusDataStored rather than replacing it.
1 parent c1a4612 commit f9645f0

2 files changed

Lines changed: 89 additions & 5 deletions

File tree

blockchain/accept.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,20 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
6464
// Create a new block node for the block and add it to the node index. Even
6565
// if the block ultimately gets connected to the main chain, it starts out
6666
// on a side chain.
67-
blockHeader := &block.MsgBlock().Header
68-
newNode := newBlockNode(blockHeader, prevNode)
69-
newNode.status = statusDataStored
70-
71-
b.index.AddNode(newNode)
67+
//
68+
// If a header-only node already exists (from maybeAcceptBlockHeader),
69+
// upgrade its status rather than creating a new node. Creating a new
70+
// node would overwrite the index entry, orphaning the pointer held by
71+
// bestHeader's chainView and breaking Contains checks.
72+
newNode := b.index.LookupNode(block.Hash())
73+
if newNode != nil {
74+
b.index.SetStatusFlags(newNode, statusDataStored)
75+
} else {
76+
blockHeader := &block.MsgBlock().Header
77+
newNode = newBlockNode(blockHeader, prevNode)
78+
newNode.status = statusDataStored | statusHeaderStored
79+
b.index.AddNode(newNode)
80+
}
7281
err = b.index.flushToDB()
7382
if err != nil {
7483
return false, err

blockchain/accept_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) 2013-2026 The btcsuite developers
2+
// Use of this source code is governed by an ISC
3+
// license that can be found in the LICENSE file.
4+
5+
package blockchain
6+
7+
import (
8+
"testing"
9+
10+
"github.com/btcsuite/btcd/blockchain/internal/testhelper"
11+
"github.com/btcsuite/btcd/btcutil"
12+
)
13+
14+
// TestMaybeAcceptBlockReusesHeaderNode ensures that when a block header is
15+
// processed first via ProcessBlockHeader and later the full block arrives via
16+
// ProcessBlock, the existing blockNode pointer is reused rather than replaced.
17+
// Replacing the pointer would orphan the entry held by bestHeader's chainView,
18+
// causing bestHeader.Contains(index.LookupNode(hash)) to return false and
19+
// breaking IsValidHeader and downstream netsync checks.
20+
func TestMaybeAcceptBlockReusesHeaderNode(t *testing.T) {
21+
chain, params, tearDown := utxoCacheTestChain(
22+
"TestMaybeAcceptBlockReusesHeaderNode")
23+
defer tearDown()
24+
25+
// Build a base chain of 3 blocks.
26+
//
27+
// genesis -> 1 -> 2 -> 3
28+
tip := btcutil.NewBlock(params.GenesisBlock)
29+
_, _, err := addBlocks(3, chain, tip, []*testhelper.SpendableOut{})
30+
if err != nil {
31+
t.Fatalf("failed to build base chain: %v", err)
32+
}
33+
34+
// Create block 4 without processing it.
35+
prevBlock, err := chain.BlockByHeight(3)
36+
if err != nil {
37+
t.Fatalf("failed to get block at height 3: %v", err)
38+
}
39+
block4, _, err := newBlock(chain, prevBlock, nil)
40+
if err != nil {
41+
t.Fatalf("failed to create block 4: %v", err)
42+
}
43+
44+
// Process block 4's header first.
45+
block4Hash := block4.Hash()
46+
_, err = chain.ProcessBlockHeader(
47+
&block4.MsgBlock().Header, BFNone, false)
48+
if err != nil {
49+
t.Fatalf("ProcessBlockHeader fail: %v", err)
50+
}
51+
52+
// Capture the header-only node pointer from the index.
53+
headerNode := chain.index.LookupNode(block4Hash)
54+
if headerNode == nil {
55+
t.Fatal("header node not found in block index")
56+
}
57+
58+
// Now process the full block.
59+
_, _, err = chain.ProcessBlock(block4, BFNone)
60+
if err != nil {
61+
t.Fatalf("ProcessBlock fail: %v", err)
62+
}
63+
64+
// The index must still hold the same pointer that bestHeader has.
65+
// Before the fix, maybeAcceptBlock would create a fresh node and
66+
// overwrite the index entry, orphaning the pointer in bestHeader.
67+
fullBlockNode := chain.index.LookupNode(block4Hash)
68+
if fullBlockNode != headerNode {
69+
t.Fatal("ProcessBlock replaced the header node pointer " +
70+
"instead of reusing it")
71+
}
72+
if !chain.bestHeader.Contains(fullBlockNode) {
73+
t.Fatal("node no longer in bestHeader after ProcessBlock")
74+
}
75+
}

0 commit comments

Comments
 (0)