Skip to content

Commit 6442544

Browse files
fix(blockstate): if blocktree fails to search a hash in memory, load it from disk (#3059)
If blocktree fails to get highest finalised hash (which is stored in memory), load it from disk.
1 parent b819559 commit 6442544

5 files changed

Lines changed: 255 additions & 125 deletions

File tree

dot/state/block.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,68 @@ func (bs *BlockState) GetHashesByNumber(blockNumber uint) ([]common.Hash, error)
268268
return blockHashes, nil
269269
}
270270

271+
// GetAllDescendants gets all the descendants for a given block hash (including itself), by first checking in memory
272+
// and, if not found, reading from the block state database.
273+
func (bs *BlockState) GetAllDescendants(hash common.Hash) ([]common.Hash, error) {
274+
allDescendants, err := bs.bt.GetAllDescendants(hash)
275+
if err != nil && !errors.Is(err, blocktree.ErrNodeNotFound) {
276+
return nil, err
277+
}
278+
279+
if err == nil {
280+
return allDescendants, nil
281+
}
282+
283+
allDescendants = []common.Hash{hash}
284+
285+
header, err := bs.GetHeader(hash)
286+
if err != nil {
287+
return nil, fmt.Errorf("getting header: %w", err)
288+
}
289+
290+
nextBlockHashes, err := bs.GetHashesByNumber(header.Number + 1)
291+
if err != nil {
292+
return nil, fmt.Errorf("getting hashes by number: %w", err)
293+
}
294+
295+
for _, nextBlockHash := range nextBlockHashes {
296+
nextHeader, err := bs.GetHeader(nextBlockHash)
297+
if err != nil {
298+
return nil, fmt.Errorf("getting header from block hash %s: %w", nextBlockHash, err)
299+
}
300+
// next block is not a descendant of the block for the given hash
301+
if nextHeader.ParentHash != hash {
302+
return []common.Hash{hash}, nil
303+
}
304+
305+
nextDescendants, err := bs.bt.GetAllDescendants(nextBlockHash)
306+
if err != nil && !errors.Is(err, blocktree.ErrNodeNotFound) {
307+
return nil, fmt.Errorf("getting all descendants: %w", err)
308+
}
309+
if err == nil {
310+
allDescendants = append(allDescendants, nextDescendants...)
311+
return allDescendants, nil
312+
}
313+
314+
nextDescendants, err = bs.GetAllDescendants(nextBlockHash)
315+
if err != nil {
316+
return nil, err
317+
}
318+
319+
allDescendants = append(allDescendants, nextDescendants...)
320+
}
321+
322+
return allDescendants, nil
323+
}
324+
271325
// GetBlockHashesBySlot gets all block hashes that were produced in the given slot.
272326
func (bs *BlockState) GetBlockHashesBySlot(slotNum uint64) ([]common.Hash, error) {
273327
highestFinalisedHash, err := bs.GetHighestFinalisedHash()
274328
if err != nil {
275329
return nil, fmt.Errorf("failed to get highest finalised hash: %w", err)
276330
}
277331

278-
descendants, err := bs.bt.GetAllDescendants(highestFinalisedHash)
332+
descendants, err := bs.GetAllDescendants(highestFinalisedHash)
279333
if err != nil {
280334
return nil, fmt.Errorf("failed to get descendants: %w", err)
281335
}

dot/state/block_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,69 @@ func TestGetHashesByNumber(t *testing.T) {
254254
require.ElementsMatch(t, blocks, []common.Hash{block.Header.Hash(), block2.Header.Hash()})
255255
}
256256

257+
func TestGetAllDescendants(t *testing.T) {
258+
t.Parallel()
259+
260+
bs := newTestBlockState(t, newTriesEmpty())
261+
slot := uint64(77)
262+
263+
babeHeader := types.NewBabeDigest()
264+
err := babeHeader.Set(*types.NewBabePrimaryPreDigest(0, slot, [32]byte{}, [64]byte{}))
265+
require.NoError(t, err)
266+
data, err := scale.Marshal(babeHeader)
267+
require.NoError(t, err)
268+
preDigest := types.NewBABEPreRuntimeDigest(data)
269+
270+
digest := types.NewDigest()
271+
err = digest.Add(*preDigest)
272+
require.NoError(t, err)
273+
block := &types.Block{
274+
Header: types.Header{
275+
ParentHash: testGenesisHeader.Hash(),
276+
Number: 1,
277+
Digest: digest,
278+
},
279+
Body: sampleBlockBody,
280+
}
281+
282+
err = bs.AddBlockWithArrivalTime(block, time.Now())
283+
require.NoError(t, err)
284+
285+
babeHeader2 := types.NewBabeDigest()
286+
err = babeHeader2.Set(*types.NewBabePrimaryPreDigest(1, slot+1, [32]byte{}, [64]byte{}))
287+
require.NoError(t, err)
288+
data2, err := scale.Marshal(babeHeader2)
289+
require.NoError(t, err)
290+
preDigest2 := types.NewBABEPreRuntimeDigest(data2)
291+
292+
digest2 := types.NewDigest()
293+
err = digest2.Add(*preDigest2)
294+
require.NoError(t, err)
295+
block2 := &types.Block{
296+
Header: types.Header{
297+
ParentHash: block.Header.Hash(),
298+
Number: 2,
299+
Digest: digest2,
300+
},
301+
Body: sampleBlockBody,
302+
}
303+
err = bs.AddBlockWithArrivalTime(block2, time.Now())
304+
require.NoError(t, err)
305+
306+
err = bs.SetFinalisedHash(block2.Header.Hash(), 1, 1)
307+
require.NoError(t, err)
308+
309+
// can't fetch given block's descendants since the given block get removed from memory after
310+
// being finalised, using blocktree.GetAllDescendants
311+
_, err = bs.bt.GetAllDescendants(block.Header.Hash())
312+
require.ErrorIs(t, err, blocktree.ErrNodeNotFound)
313+
314+
// can fetch given finalised block's descendants using disk, using using blockstate.GetAllDescendants
315+
blockHashes, err := bs.GetAllDescendants(block.Header.Hash())
316+
require.NoError(t, err)
317+
require.ElementsMatch(t, blockHashes, []common.Hash{block.Header.Hash(), block2.Header.Hash()})
318+
}
319+
257320
func TestGetBlockHashesBySlot(t *testing.T) {
258321
t.Parallel()
259322

lib/blocktree/blocktree.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ func (bt *BlockTree) GetAllBlocks() []Hash {
422422
return bt.root.getAllDescendants(nil)
423423
}
424424

425-
// GetAllDescendants returns all block hashes that are descendants of the given block hash.
425+
// GetAllDescendants returns all block hashes that are descendants of the given block hash (including itself).
426426
func (bt *BlockTree) GetAllDescendants(hash common.Hash) ([]Hash, error) {
427427
bt.RLock()
428428
defer bt.RUnlock()

lib/blocktree/blocktree_test.go

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -6,145 +6,22 @@ package blocktree
66
import (
77
"bytes"
88
"fmt"
9-
"math/rand"
109
"testing"
1110
"time"
1211

1312
"github.com/ChainSafe/gossamer/dot/types"
1413
"github.com/ChainSafe/gossamer/lib/common"
15-
"github.com/ChainSafe/gossamer/pkg/scale"
1614
"github.com/stretchr/testify/assert"
1715
"github.com/stretchr/testify/require"
1816
)
1917

20-
var zeroHash, _ = common.HexToHash("0x00")
21-
var testHeader = &types.Header{
22-
ParentHash: zeroHash,
23-
Number: 0,
24-
Digest: types.NewDigest(),
25-
}
26-
27-
type testBranch struct {
28-
hash Hash
29-
number uint
30-
arrivalTime int64
31-
}
32-
3318
func newBlockTreeFromNode(root *node) *BlockTree {
3419
return &BlockTree{
3520
root: root,
3621
leaves: newLeafMap(root),
3722
}
3823
}
3924

40-
func createPrimaryBABEDigest(t testing.TB) scale.VaryingDataTypeSlice {
41-
babeDigest := types.NewBabeDigest()
42-
err := babeDigest.Set(types.BabePrimaryPreDigest{AuthorityIndex: 0})
43-
require.NoError(t, err)
44-
45-
bdEnc, err := scale.Marshal(babeDigest)
46-
require.NoError(t, err)
47-
48-
digest := types.NewDigest()
49-
err = digest.Add(types.PreRuntimeDigest{
50-
ConsensusEngineID: types.BabeEngineID,
51-
Data: bdEnc,
52-
})
53-
require.NoError(t, err)
54-
return digest
55-
}
56-
57-
func createTestBlockTree(t *testing.T, header *types.Header, number uint) (*BlockTree, []testBranch) {
58-
bt := NewBlockTreeFromRoot(header)
59-
previousHash := header.Hash()
60-
61-
// branch tree randomly
62-
var branches []testBranch
63-
r := rand.New(rand.NewSource(time.Now().UnixNano())) //skipcq: GSC-G404
64-
65-
at := int64(0)
66-
67-
// create base tree
68-
for i := uint(1); i <= number; i++ {
69-
header := &types.Header{
70-
ParentHash: previousHash,
71-
Number: i,
72-
Digest: createPrimaryBABEDigest(t),
73-
}
74-
75-
hash := header.Hash()
76-
err := bt.AddBlock(header, time.Unix(0, at))
77-
require.NoError(t, err)
78-
79-
previousHash = hash
80-
81-
isBranch := r.Intn(2)
82-
if isBranch == 1 {
83-
branches = append(branches, testBranch{
84-
hash: hash,
85-
number: bt.getNode(hash).number,
86-
arrivalTime: at,
87-
})
88-
}
89-
90-
at += int64(r.Intn(8))
91-
}
92-
93-
// create tree branches
94-
for _, branch := range branches {
95-
at := branch.arrivalTime
96-
previousHash = branch.hash
97-
98-
for i := branch.number; i <= number; i++ {
99-
header := &types.Header{
100-
ParentHash: previousHash,
101-
Number: i + 1,
102-
StateRoot: common.Hash{0x1},
103-
Digest: createPrimaryBABEDigest(t),
104-
}
105-
106-
hash := header.Hash()
107-
err := bt.AddBlock(header, time.Unix(0, at))
108-
require.NoError(t, err)
109-
110-
previousHash = hash
111-
at += int64(r.Intn(8))
112-
113-
}
114-
}
115-
116-
return bt, branches
117-
}
118-
119-
func createFlatTree(t testing.TB, number uint) (*BlockTree, []common.Hash) {
120-
rootHeader := &types.Header{
121-
ParentHash: zeroHash,
122-
Digest: createPrimaryBABEDigest(t),
123-
}
124-
125-
bt := NewBlockTreeFromRoot(rootHeader)
126-
require.NotNil(t, bt)
127-
previousHash := bt.root.hash
128-
129-
hashes := []common.Hash{bt.root.hash}
130-
for i := uint(1); i <= number; i++ {
131-
header := &types.Header{
132-
ParentHash: previousHash,
133-
Number: i,
134-
Digest: createPrimaryBABEDigest(t),
135-
}
136-
137-
hash := header.Hash()
138-
hashes = append(hashes, hash)
139-
140-
err := bt.AddBlock(header, time.Unix(0, 0))
141-
require.NoError(t, err)
142-
previousHash = hash
143-
}
144-
145-
return bt, hashes
146-
}
147-
14825
func Test_NewBlockTreeFromNode(t *testing.T) {
14926
var bt *BlockTree
15027
var branches []testBranch

0 commit comments

Comments
 (0)