Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dot/state): create Range to traverse the blocktree and the blocks in the disk #2990

Merged
merged 28 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
151071a
feat: introduce Range method
EclesioMeloJunior Dec 5, 2022
3c7c465
chore: improve `fmt.Errorf` context message
EclesioMeloJunior Dec 5, 2022
907274a
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 5, 2022
957264b
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 7, 2022
97beb26
Merge branch 'eclesio/fix/subchain-method' of github.com:ChainSafe/go…
EclesioMeloJunior Dec 7, 2022
84bf6ce
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 7, 2022
f9949a5
chore: add tests to `blockstate.Range` method
EclesioMeloJunior Dec 8, 2022
621007c
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 8, 2022
d4e9911
chore: resolving an lint warn
EclesioMeloJunior Dec 8, 2022
0b0e8a0
Merge branch 'eclesio/fix/subchain-method' of github.com:ChainSafe/go…
EclesioMeloJunior Dec 8, 2022
4fcb51b
chore: add more tests
EclesioMeloJunior Dec 10, 2022
2e50d10
chore: solving a lint problem
EclesioMeloJunior Dec 12, 2022
df182df
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 12, 2022
bd10b2d
chore: doing benchamarks and changed the `retrieveRangeFromDisk` func…
EclesioMeloJunior Dec 12, 2022
e79c5d6
chore: replacing `SubBlockchain` with improved version
EclesioMeloJunior Dec 12, 2022
70b3a74
chore: solving lint warns
EclesioMeloJunior Dec 12, 2022
0f244c5
chore: improve tests
EclesioMeloJunior Dec 12, 2022
4e0e85c
chore: wrapping `errNilBlockTree` error
EclesioMeloJunior Dec 12, 2022
e7c496a
chore: rename to `inDiskHashes`
EclesioMeloJunior Dec 12, 2022
2be1952
chore: remove unneeded paren
EclesioMeloJunior Dec 12, 2022
b75101f
chore: addressing comments
EclesioMeloJunior Dec 12, 2022
dc7b667
chore: addressing comments
EclesioMeloJunior Dec 13, 2022
10a14b9
chore: replacing all `disk` occurrences for `database`
EclesioMeloJunior Dec 13, 2022
fa9fcb2
chore: addressing comments
EclesioMeloJunior Dec 13, 2022
3cb4c75
chore: addressing ci warns
EclesioMeloJunior Dec 13, 2022
0645a95
Merge branch 'development' into eclesio/fix/subchain-method
EclesioMeloJunior Dec 13, 2022
4b632d8
chore: solving tests
EclesioMeloJunior Dec 13, 2022
8cbd2f5
Merge branch 'eclesio/fix/subchain-method' of github.com:ChainSafe/go…
EclesioMeloJunior Dec 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 134 additions & 2 deletions dot/state/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var (
messageQueuePrefix = []byte("mqp") // messageQueuePrefix + hash -> message queue
justificationPrefix = []byte("jcp") // justificationPrefix + hash -> justification

errNilBlockTree = errors.New("blocktree is nil")
errNilBlockBody = errors.New("block body is nil")

syncedBlocksGauge = promauto.NewGauge(prometheus.GaugeOpts{
Expand Down Expand Up @@ -542,10 +543,141 @@ func (bs *BlockState) GetSlotForBlock(hash common.Hash) (uint64, error) {
return types.GetSlotFromHeader(header)
}

var ErrEmptyHeader = errors.New("empty header")

func (bs *BlockState) loadHeaderFromDatabase(hash common.Hash) (header *types.Header, err error) {
startHeaderData, err := bs.db.Get(headerKey(hash))
if err != nil {
return nil, fmt.Errorf("querying database: %w", err)
}

header = types.NewEmptyHeader()
err = scale.Unmarshal(startHeaderData, header)
if err != nil {
return nil, fmt.Errorf("unmarshaling start header: %w", err)
}

if header.Empty() {
return nil, fmt.Errorf("%w: %s", ErrEmptyHeader, hash)
}

return header, nil
}

// Range returns the sub-blockchain between the starting hash and the
// ending hash using both block tree and database
func (bs *BlockState) Range(startHash, endHash common.Hash) (hashes []common.Hash, err error) {
if startHash == endHash {
hashes = []common.Hash{startHash}
return hashes, nil
}

endHeader, err := bs.loadHeaderFromDatabase(endHash)
if errors.Is(err, chaindb.ErrKeyNotFound) ||
errors.Is(err, ErrEmptyHeader) {
// end hash is not in the database so we should lookup the
// block that could be in memory and in the database as well
return bs.retrieveRange(startHash, endHash)
} else if err != nil {
return nil, fmt.Errorf("retrieving end hash from database: %w", err)
}

// end hash was found in the database, that means all the blocks
// between start and end can be found in the database
return bs.retrieveRangeFromDatabase(startHash, endHeader)
}

func (bs *BlockState) retrieveRange(startHash, endHash common.Hash) (hashes []common.Hash, err error) {
inMemoryHashes, err := bs.bt.Range(startHash, endHash)
if err != nil {
return nil, fmt.Errorf("retrieving range from in-memory blocktree: %w", err)
}

firstItem := inMemoryHashes[0]

// if the first item is equal to the startHash that means we got the range
// from the in-memory blocktree
if firstItem == startHash {
return inMemoryHashes, nil
}

// since we got as many blocks as we could from
// the block tree but still missing blocks to
// fulfil the range we should lookup in the
// database for the remaining ones, the first item in the hashes array
// must be the block tree root that is also placed in the database
// so we will start from its parent since it is already in the array
blockTreeRootHeader, err := bs.loadHeaderFromDatabase(firstItem)
if err != nil {
return nil, fmt.Errorf("loading block tree root from database: %w", err)
}

startingAtParentHeader, err := bs.loadHeaderFromDatabase(blockTreeRootHeader.ParentHash)
if err != nil {
return nil, fmt.Errorf("loading header of parent of the root from database: %w", err)
}

inDatabaseHashes, err := bs.retrieveRangeFromDatabase(startHash, startingAtParentHeader)
if err != nil {
return nil, fmt.Errorf("retrieving range from database: %w", err)
}

hashes = append(inDatabaseHashes, inMemoryHashes...)
return hashes, nil
}

var ErrStartHashMismatch = errors.New("start hash mismatch")
var ErrStartGreaterThanEnd = errors.New("start greater than end")

// retrieveRangeFromDatabase takes the start and the end and will retrieve all block in between
// where all blocks (start and end inclusive) are supposed to be placed at database
func (bs *BlockState) retrieveRangeFromDatabase(startHash common.Hash,
endHeader *types.Header) (hashes []common.Hash, err error) {
startHeader, err := bs.loadHeaderFromDatabase(startHash)
if err != nil {
return nil, fmt.Errorf("range start should be in database: %w", err)
}

if startHeader.Number > endHeader.Number {
return nil, fmt.Errorf("%w", ErrStartGreaterThanEnd)
}

// blocksInRange is the difference between the end number to start number
// but the difference doesn't include the start item so we add 1
blocksInRange := endHeader.Number - startHeader.Number + 1

hashes = make([]common.Hash, blocksInRange)

lastPosition := blocksInRange - 1

hashes[0] = startHash
hashes[lastPosition] = endHeader.Hash()

inLoopHash := endHeader.ParentHash
for currentPosition := lastPosition - 1; currentPosition > 0; currentPosition-- {
hashes[currentPosition] = inLoopHash

inLoopHeader, err := bs.loadHeaderFromDatabase(inLoopHash)
if err != nil {
return nil, fmt.Errorf("retrieving hash %s from database: %w", inLoopHash.Short(), err)
}

inLoopHash = inLoopHeader.ParentHash
}

// here we ensure that we finished up the loop
// with the same hash as the startHash
if inLoopHash != startHash {
return nil, fmt.Errorf("%w: expecting %s, found: %s", ErrStartHashMismatch, startHash.Short(), inLoopHash.Short())
}

return hashes, nil
}

// SubChain returns the sub-blockchain between the starting hash and the ending hash using the block tree
func (bs *BlockState) SubChain(start, end common.Hash) ([]common.Hash, error) {
if bs.bt == nil {
return nil, fmt.Errorf("blocktree is nil")
return nil, fmt.Errorf("%w", errNilBlockTree)
}

return bs.bt.SubBlockchain(start, end)
Expand All @@ -555,7 +687,7 @@ func (bs *BlockState) SubChain(start, end common.Hash) ([]common.Hash, error) {
// it returns an error if parent or child are not in the blocktree.
func (bs *BlockState) IsDescendantOf(parent, child common.Hash) (bool, error) {
if bs.bt == nil {
return false, fmt.Errorf("blocktree is nil")
return false, fmt.Errorf("%w", errNilBlockTree)
}

return bs.bt.IsDescendantOf(parent, child)
Expand Down
Loading