-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Modify the algorithm of updateFinalizedBlockRoots
#13486
Changes from 5 commits
7e2520f
6b36e66
77b311f
38579c4
52a38b8
f96c5a3
34755c5
8d4fcd7
64cde45
b119648
b4d4e6d
dabf5b8
f4880c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ import ( | |
"context" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db/filters" | ||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" | ||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" | ||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" | ||
|
@@ -29,139 +28,77 @@ var containerFinalizedButNotCanonical = []byte("recent block needs reindexing to | |
// beacon block chain using the finalized root alone as this would exclude all other blocks in the | ||
// finalized epoch from being indexed as "final and canonical". | ||
// | ||
// The algorithm for building the index works as follows: | ||
// - De-index all finalized beacon block roots from previous_finalized_epoch to | ||
// new_finalized_epoch. (I.e. delete these roots from the index, to be re-indexed.) | ||
// - Build the canonical finalized chain by walking up the ancestry chain from the finalized block | ||
// root until a parent is found in the index, or the parent is genesis or the origin checkpoint. | ||
// - Add all block roots in the database where epoch(block.slot) == checkpoint.epoch. | ||
// | ||
// This method ensures that all blocks from the current finalized epoch are considered "final" while | ||
// maintaining only canonical and finalized blocks older than the current finalized epoch. | ||
// The main part of the algorithm traverses parent->child block relationships in the | ||
// `blockParentRootIndicesBucket` bucket to find the path between the last finalized checkpoint | ||
// and the current finalized checkpoint. It relies on the invariant that there is a unique path | ||
// between two finalized checkpoints. | ||
func (s *Store) updateFinalizedBlockRoots(ctx context.Context, tx *bolt.Tx, checkpoint *ethpb.Checkpoint) error { | ||
ctx, span := trace.StartSpan(ctx, "BeaconDB.updateFinalizedBlockRoots") | ||
defer span.End() | ||
|
||
bkt := tx.Bucket(finalizedBlockRootsIndexBucket) | ||
|
||
root := checkpoint.Root | ||
var previousRoot []byte | ||
genesisRoot := tx.Bucket(blocksBucket).Get(genesisBlockRootKey) | ||
initCheckpointRoot := tx.Bucket(blocksBucket).Get(originCheckpointBlockRootKey) | ||
|
||
// De-index recent finalized block roots, to be re-indexed. | ||
finalizedBkt := tx.Bucket(finalizedBlockRootsIndexBucket) | ||
previousFinalizedCheckpoint := ðpb.Checkpoint{} | ||
if b := bkt.Get(previousFinalizedCheckpointKey); b != nil { | ||
if b := finalizedBkt.Get(previousFinalizedCheckpointKey); b != nil { | ||
if err := decode(ctx, b, previousFinalizedCheckpoint); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
} | ||
|
||
blockRoots, err := s.BlockRoots(ctx, filters.NewFilter(). | ||
SetStartEpoch(previousFinalizedCheckpoint.Epoch). | ||
SetEndEpoch(checkpoint.Epoch+1), | ||
) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
for _, root := range blockRoots { | ||
if err := bkt.Delete(root[:]); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
} | ||
|
||
// Walk up the ancestry chain until we reach a block root present in the finalized block roots | ||
// index bucket or genesis block root. | ||
for { | ||
if bytes.Equal(root, genesisRoot) { | ||
break | ||
} | ||
|
||
signedBlock, err := s.Block(ctx, bytesutil.ToBytes32(root)) | ||
// Handle the case of checkpoint sync. | ||
if previousFinalizedCheckpoint.Root == nil && bytes.Equal(checkpoint.Root, tx.Bucket(blocksBucket).Get(originCheckpointBlockRootKey)) { | ||
container := ðpb.FinalizedBlockRootContainer{} | ||
enc, err := encode(ctx, container) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
if err := blocks.BeaconBlockIsNil(signedBlock); err != nil { | ||
if err = finalizedBkt.Put(checkpoint.Root, enc); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
block := signedBlock.Block() | ||
return updatePrevFinalizedCheckpoint(ctx, span, finalizedBkt, checkpoint) | ||
} | ||
|
||
parentRoot := block.ParentRoot() | ||
container := ðpb.FinalizedBlockRootContainer{ | ||
ParentRoot: parentRoot[:], | ||
ChildRoot: previousRoot, | ||
var finalized [][]byte | ||
if previousFinalizedCheckpoint.Root == nil { | ||
genesisRoot := tx.Bucket(blocksBucket).Get(genesisBlockRootKey) | ||
_, finalized = pathToFinalizedCheckpoint(ctx, [][]byte{genesisRoot}, checkpoint.Root, tx) | ||
} else { | ||
if err := updateChildOfPrevFinalizedCheckpoint( | ||
ctx, | ||
span, | ||
finalizedBkt, | ||
tx.Bucket(blockParentRootIndicesBucket), previousFinalizedCheckpoint.Root, | ||
); err != nil { | ||
return err | ||
} | ||
_, finalized = pathToFinalizedCheckpoint(ctx, [][]byte{previousFinalizedCheckpoint.Root}, checkpoint.Root, tx) | ||
} | ||
|
||
for i, r := range finalized { | ||
var container *ethpb.FinalizedBlockRootContainer | ||
if i == len(finalized)-1 { | ||
// We don't know the finalized child of the new finalized checkpoint. | ||
// It will be filled out in the next function call. | ||
container = ðpb.FinalizedBlockRootContainer{} | ||
} else { | ||
container = ðpb.FinalizedBlockRootContainer{ | ||
ChildRoot: finalized[i+1], | ||
} | ||
} | ||
enc, err := encode(ctx, container) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
if err := bkt.Put(root, enc); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
|
||
// breaking here allows the initial checkpoint root to be correctly inserted, | ||
// but stops the loop from trying to search for its parent. | ||
if bytes.Equal(root, initCheckpointRoot) { | ||
break | ||
} | ||
|
||
// Found parent, loop exit condition. | ||
pr := block.ParentRoot() | ||
if parentBytes := bkt.Get(pr[:]); parentBytes != nil { | ||
parent := ðpb.FinalizedBlockRootContainer{} | ||
if err := decode(ctx, parentBytes, parent); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
parent.ChildRoot = root | ||
enc, err := encode(ctx, parent) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
if err := bkt.Put(pr[:], enc); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
break | ||
} | ||
previousRoot = root | ||
root = pr[:] | ||
} | ||
|
||
// Upsert blocks from the current finalized epoch. | ||
roots, err := s.BlockRoots(ctx, filters.NewFilter().SetStartEpoch(checkpoint.Epoch).SetEndEpoch(checkpoint.Epoch+1)) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
for _, root := range roots { | ||
root := root[:] | ||
if bytes.Equal(root, checkpoint.Root) || bkt.Get(root) != nil { | ||
continue | ||
} | ||
if err := bkt.Put(root, containerFinalizedButNotCanonical); err != nil { | ||
if err = finalizedBkt.Put(r, enc); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
} | ||
|
||
// Update previous checkpoint | ||
enc, err := encode(ctx, checkpoint) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
|
||
return bkt.Put(previousFinalizedCheckpointKey, enc) | ||
return updatePrevFinalizedCheckpoint(ctx, span, finalizedBkt, checkpoint) | ||
} | ||
|
||
// BackfillFinalizedIndex updates the finalized index for a contiguous chain of blocks that are the ancestors of the | ||
|
@@ -243,8 +180,6 @@ func (s *Store) BackfillFinalizedIndex(ctx context.Context, blocks []blocks.ROBl | |
|
||
// IsFinalizedBlock returns true if the block root is present in the finalized block root index. | ||
// A beacon block root contained exists in this index if it is considered finalized and canonical. | ||
// Note: beacon blocks from the latest finalized epoch return true, whether or not they are | ||
// considered canonical in the "head view" of the beacon node. | ||
func (s *Store) IsFinalizedBlock(ctx context.Context, blockRoot [32]byte) bool { | ||
_, span := trace.StartSpan(ctx, "BeaconDB.IsFinalizedBlock") | ||
defer span.End() | ||
|
@@ -297,3 +232,50 @@ func (s *Store) FinalizedChildBlock(ctx context.Context, blockRoot [32]byte) (in | |
tracing.AnnotateError(span, err) | ||
return blk, err | ||
} | ||
|
||
func pathToFinalizedCheckpoint(ctx context.Context, roots [][]byte, checkpointRoot []byte, tx *bolt.Tx) (bool, [][]byte) { | ||
if len(roots) == 1 && roots[0] == nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if an empty slice is passed for |
||
return false, nil | ||
} | ||
|
||
for _, r := range roots { | ||
if bytes.Equal(r, checkpointRoot) { | ||
return true, [][]byte{r} | ||
} | ||
children := lookupValuesForIndices(ctx, map[string][]byte{string(blockParentRootIndicesBucket): r}, tx)[0] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will panic if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a condition for it although it is not strictly necessary. Since the passed in map contains one key, the resulting slice will have one item. |
||
isPath, path := pathToFinalizedCheckpoint(ctx, children, checkpointRoot, tx) | ||
if isPath { | ||
return true, append([][]byte{r}, path...) | ||
} | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
func updatePrevFinalizedCheckpoint(ctx context.Context, span *trace.Span, finalizedBkt *bolt.Bucket, checkpoint *ethpb.Checkpoint) error { | ||
enc, err := encode(ctx, checkpoint) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
return finalizedBkt.Put(previousFinalizedCheckpointKey, enc) | ||
} | ||
|
||
func updateChildOfPrevFinalizedCheckpoint(ctx context.Context, span *trace.Span, finalizedBkt, parentBkt *bolt.Bucket, checkpointRoot []byte) error { | ||
container := ðpb.FinalizedBlockRootContainer{} | ||
if err := decode(ctx, finalizedBkt.Get(checkpointRoot), container); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
container.ChildRoot = parentBkt.Get(checkpointRoot) | ||
enc, err := encode(ctx, container) | ||
if err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
if err = finalizedBkt.Put(checkpointRoot, enc); err != nil { | ||
tracing.AnnotateError(span, err) | ||
return err | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This completely misses adding in
ParentRoot
for the container. In fact it appears this PR has completely removed it.