Skip to content

Commit a9a8405

Browse files
committed
consensus/XDPoS, core, consensus/tests: fix verifyheaders parent lookups for epoch switches
Problem: VerifyHeaders could fail on epoch-switch batches when consensus hooks looked up parent headers through ChainReader before the batch had been written to disk. Cause: the import paths passed the raw chain reader into VerifyHeaders, so helper lookups could only see DB-backed state and missed in-flight headers during block and header-only imports. Solution: wrap the VerifyHeaders entry points with a batch-aware verifyChainReader so current-batch headers and blocks are visible during validation, add focused regression tests for epoch-switch parent resolution, and keep wrapper reuse as a no-op instead of mutating an existing cache.
1 parent 146252a commit a9a8405

6 files changed

Lines changed: 477 additions & 3 deletions

File tree

consensus/XDPoS/XDPoS.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Head
216216

217217
abort := make(chan struct{})
218218
results := make(chan error, len(headers))
219+
chain = NewVerifyHeadersChainReader(chain, headers, nil)
219220

220221
// Split the headers list into v1 and v2 buckets
221222
var v1headers []*types.Header
@@ -234,7 +235,6 @@ func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Head
234235
}
235236
}
236237

237-
238238
if v1headers != nil {
239239
x.EngineV1.VerifyHeaders(chain, v1headers, v1fullVerifies, abort, results)
240240
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package XDPoS
2+
3+
import (
4+
"github.com/XinFinOrg/XDPoSChain/common"
5+
"github.com/XinFinOrg/XDPoSChain/consensus"
6+
"github.com/XinFinOrg/XDPoSChain/core/types"
7+
"github.com/XinFinOrg/XDPoSChain/params"
8+
)
9+
10+
type verifyChainReader struct {
11+
chain consensus.ChainReader
12+
headersByHash map[common.Hash]*types.Header
13+
headersByNumber map[uint64]*types.Header
14+
blocksByHashNo map[hashAndNumber]*types.Block
15+
}
16+
17+
type hashAndNumber struct {
18+
hash common.Hash
19+
number uint64
20+
}
21+
22+
var _ consensus.ChainReader = (*verifyChainReader)(nil)
23+
24+
func NewVerifyHeadersChainReader(chain consensus.ChainReader, headers []*types.Header, blocks []*types.Block) consensus.ChainReader {
25+
if reader, ok := chain.(*verifyChainReader); ok {
26+
return reader
27+
}
28+
reader := &verifyChainReader{
29+
chain: chain,
30+
headersByHash: make(map[common.Hash]*types.Header, len(headers)),
31+
headersByNumber: make(map[uint64]*types.Header, len(headers)),
32+
blocksByHashNo: make(map[hashAndNumber]*types.Block, len(blocks)),
33+
}
34+
for _, header := range headers {
35+
if header == nil || header.Number == nil {
36+
continue
37+
}
38+
hash := header.Hash()
39+
number := header.Number.Uint64()
40+
reader.headersByHash[hash] = header
41+
if _, exists := reader.headersByNumber[number]; !exists {
42+
reader.headersByNumber[number] = header
43+
}
44+
}
45+
for _, block := range blocks {
46+
if block == nil {
47+
continue
48+
}
49+
reader.blocksByHashNo[hashAndNumber{hash: block.Hash(), number: block.NumberU64()}] = block
50+
}
51+
return reader
52+
}
53+
54+
func (r *verifyChainReader) Config() *params.ChainConfig {
55+
if r.chain == nil {
56+
return nil
57+
}
58+
return r.chain.Config()
59+
}
60+
61+
func (r *verifyChainReader) CurrentHeader() *types.Header {
62+
if r.chain == nil {
63+
return nil
64+
}
65+
return r.chain.CurrentHeader()
66+
}
67+
68+
func (r *verifyChainReader) GetHeader(hash common.Hash, number uint64) *types.Header {
69+
if header, ok := r.headersByHash[hash]; ok && header.Number != nil && header.Number.Uint64() == number {
70+
return header
71+
}
72+
if r.chain == nil {
73+
return nil
74+
}
75+
return r.chain.GetHeader(hash, number)
76+
}
77+
78+
func (r *verifyChainReader) GetHeaderByNumber(number uint64) *types.Header {
79+
if header, ok := r.headersByNumber[number]; ok {
80+
return header
81+
}
82+
if r.chain == nil {
83+
return nil
84+
}
85+
return r.chain.GetHeaderByNumber(number)
86+
}
87+
88+
func (r *verifyChainReader) GetHeaderByHash(hash common.Hash) *types.Header {
89+
if header, ok := r.headersByHash[hash]; ok {
90+
return header
91+
}
92+
if r.chain == nil {
93+
return nil
94+
}
95+
return r.chain.GetHeaderByHash(hash)
96+
}
97+
98+
func (r *verifyChainReader) GetBlock(hash common.Hash, number uint64) *types.Block {
99+
if block, ok := r.blocksByHashNo[hashAndNumber{hash: hash, number: number}]; ok {
100+
return block
101+
}
102+
if r.chain == nil {
103+
return nil
104+
}
105+
return r.chain.GetBlock(hash, number)
106+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package XDPoS
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
7+
"github.com/XinFinOrg/XDPoSChain/common"
8+
"github.com/XinFinOrg/XDPoSChain/core/types"
9+
"github.com/XinFinOrg/XDPoSChain/params"
10+
"github.com/XinFinOrg/XDPoSChain/trie"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func makeTestBlock(number int64, time int64, txs ...*types.Transaction) *types.Block {
15+
header := &types.Header{
16+
Number: big.NewInt(number),
17+
Time: big.NewInt(time),
18+
Difficulty: big.NewInt(1),
19+
GasLimit: 1_000_000,
20+
}
21+
return types.NewBlock(header, txs, nil, nil, trie.NewStackTrie(nil))
22+
}
23+
24+
type stubVerifyChainReader struct {
25+
config *params.ChainConfig
26+
currentHeader *types.Header
27+
headersByHash map[common.Hash]*types.Header
28+
headersByNumber map[uint64]*types.Header
29+
blocksByHashNo map[hashAndNumber]*types.Block
30+
}
31+
32+
func (s *stubVerifyChainReader) Config() *params.ChainConfig { return s.config }
33+
34+
func (s *stubVerifyChainReader) CurrentHeader() *types.Header { return s.currentHeader }
35+
36+
func (s *stubVerifyChainReader) GetHeader(hash common.Hash, number uint64) *types.Header {
37+
header := s.GetHeaderByHash(hash)
38+
if header == nil || header.Number == nil || header.Number.Uint64() != number {
39+
return nil
40+
}
41+
return header
42+
}
43+
44+
func (s *stubVerifyChainReader) GetHeaderByNumber(number uint64) *types.Header {
45+
if s.headersByNumber == nil {
46+
return nil
47+
}
48+
return s.headersByNumber[number]
49+
}
50+
51+
func (s *stubVerifyChainReader) GetHeaderByHash(hash common.Hash) *types.Header {
52+
if s.headersByHash == nil {
53+
return nil
54+
}
55+
return s.headersByHash[hash]
56+
}
57+
58+
func (s *stubVerifyChainReader) GetBlock(hash common.Hash, number uint64) *types.Block {
59+
if s.blocksByHashNo == nil {
60+
return nil
61+
}
62+
return s.blocksByHashNo[hashAndNumber{hash: hash, number: number}]
63+
}
64+
65+
func TestVerifyChainReaderWithNilChainIsNilSafe(t *testing.T) {
66+
batchHeader := &types.Header{Number: big.NewInt(1)}
67+
reader := NewVerifyHeadersChainReader(nil, []*types.Header{batchHeader}, nil).(*verifyChainReader)
68+
69+
assert.NotNil(t, reader)
70+
assert.Nil(t, reader.Config())
71+
assert.Nil(t, reader.CurrentHeader())
72+
assert.Equal(t, batchHeader.Hash(), reader.GetHeaderByNumber(1).Hash())
73+
assert.Equal(t, batchHeader.Hash(), reader.GetHeaderByHash(batchHeader.Hash()).Hash())
74+
assert.Equal(t, batchHeader.Hash(), reader.GetHeader(batchHeader.Hash(), 1).Hash())
75+
assert.Nil(t, reader.GetBlock(batchHeader.Hash(), 1))
76+
assert.Nil(t, reader.GetHeader(common.Hash{}, 2))
77+
assert.Nil(t, reader.GetBlock(common.Hash{}, 2))
78+
}
79+
80+
func TestVerifyChainReaderShadowsBatchEntries(t *testing.T) {
81+
baseHeader := &types.Header{Number: big.NewInt(100), Time: big.NewInt(1)}
82+
batchHeader := &types.Header{Number: big.NewInt(100), Time: big.NewInt(2)}
83+
batchTx := types.NewTransaction(1, common.Address{0x1}, big.NewInt(1), 21000, big.NewInt(1), []byte{0x1})
84+
batchBlock := makeTestBlock(100, 2, batchTx)
85+
batchHeader = batchBlock.Header()
86+
currentHeader := &types.Header{Number: big.NewInt(99), Time: big.NewInt(3)}
87+
baseBlock := types.NewBlockWithHeader(baseHeader)
88+
89+
base := &stubVerifyChainReader{
90+
config: params.TestXDPoSMockChainConfig,
91+
currentHeader: currentHeader,
92+
headersByHash: map[common.Hash]*types.Header{baseHeader.Hash(): baseHeader},
93+
headersByNumber: map[uint64]*types.Header{
94+
100: baseHeader,
95+
},
96+
blocksByHashNo: map[hashAndNumber]*types.Block{
97+
{hash: baseHeader.Hash(), number: 100}: baseBlock,
98+
},
99+
}
100+
101+
reader := NewVerifyHeadersChainReader(base, []*types.Header{batchHeader}, []*types.Block{batchBlock}).(*verifyChainReader)
102+
103+
assert.Equal(t, params.TestXDPoSMockChainConfig, reader.Config())
104+
assert.Equal(t, currentHeader.Hash(), reader.CurrentHeader().Hash())
105+
assert.Equal(t, batchHeader.Hash(), reader.GetHeaderByNumber(100).Hash())
106+
assert.Equal(t, batchHeader.Hash(), reader.GetHeaderByHash(batchHeader.Hash()).Hash())
107+
assert.Equal(t, batchHeader.Hash(), reader.GetHeader(batchHeader.Hash(), 100).Hash())
108+
assert.Equal(t, batchHeader.Hash(), reader.GetBlock(batchHeader.Hash(), 100).Hash())
109+
assert.Len(t, reader.GetBlock(batchHeader.Hash(), 100).Transactions(), 1)
110+
assert.Equal(t, baseHeader.Hash(), reader.GetBlock(baseHeader.Hash(), 100).Hash())
111+
}
112+
113+
func TestVerifyChainReaderReusesExistingWrapper(t *testing.T) {
114+
firstTx := types.NewTransaction(1, common.Address{0x1}, big.NewInt(1), 21000, big.NewInt(1), []byte{0x1})
115+
secondTx := types.NewTransaction(2, common.Address{0x2}, big.NewInt(2), 21000, big.NewInt(1), []byte{0x2})
116+
firstBlock := makeTestBlock(100, 1, firstTx)
117+
secondBlock := makeTestBlock(101, 2, secondTx)
118+
119+
reader := NewVerifyHeadersChainReader(nil, []*types.Header{firstBlock.Header()}, []*types.Block{firstBlock})
120+
reused := NewVerifyHeadersChainReader(reader, []*types.Header{secondBlock.Header()}, nil)
121+
122+
assert.Same(t, reader, reused)
123+
wrapped := reused.(*verifyChainReader)
124+
assert.Len(t, wrapped.GetBlock(firstBlock.Hash(), firstBlock.NumberU64()).Transactions(), 1)
125+
assert.Nil(t, wrapped.GetHeaderByNumber(secondBlock.NumberU64()))
126+
assert.Nil(t, wrapped.GetBlock(secondBlock.Hash(), secondBlock.NumberU64()))
127+
128+
reused = NewVerifyHeadersChainReader(reused, nil, []*types.Block{secondBlock})
129+
wrapped = reused.(*verifyChainReader)
130+
assert.Nil(t, wrapped.GetBlock(secondBlock.Hash(), secondBlock.NumberU64()))
131+
}

0 commit comments

Comments
 (0)