Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 7 additions & 2 deletions cl/persistence/beacon_indicies/indicies.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,15 @@ func WriteParentBlockRoot(ctx context.Context, tx kv.RwTx, blockRoot, parentRoot
return tx.Put(kv.BlockRootToParentRoot, blockRoot[:], parentRoot[:])
}

func TruncateCanonicalChain(ctx context.Context, tx kv.RwTx, slot uint64) error {
return tx.ForEach(kv.CanonicalBlockRoots, base_encoding.Encode64ToBytes4(slot), func(k, _ []byte) error {
func TruncateCanonicalChain(ctx context.Context, tx kv.RwTx, slot uint64) (uint64, common.Hash, error) {
var highestSlot uint64
var highestRoot common.Hash
err := tx.ForEach(kv.CanonicalBlockRoots, base_encoding.Encode64ToBytes4(slot), func(k, v []byte) error {
highestSlot = base_encoding.Decode64FromBytes4(k)
copy(highestRoot[:], v)
return tx.Delete(kv.CanonicalBlockRoots, k)
})
return highestSlot, highestRoot, err
}

func PruneSignedHeaders(tx kv.RwTx, from uint64) error {
Expand Down
46 changes: 45 additions & 1 deletion cl/persistence/beacon_indicies/indicies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,57 @@ func TestTruncateCanonicalChain(t *testing.T) {
require.NoError(t, err)
require.Equal(t, common.Hash(blockRoot), canonicalRoot)

require.NoError(t, TruncateCanonicalChain(context.Background(), tx, 0))
_, _, err = TruncateCanonicalChain(context.Background(), tx, 0)
require.NoError(t, err)

canonicalRoot, err = ReadCanonicalBlockRoot(tx, block.Block.Slot)
require.NoError(t, err)
require.Equal(t, common.Hash{}, canonicalRoot)
}

// TestTruncateCanonicalChainReturnsHighest verifies that TruncateCanonicalChain
// returns the slot and root of the highest entry it deleted, and preserves
// entries below the truncation point.
func TestTruncateCanonicalChainReturnsHighest(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
tx, err := db.BeginRw(context.Background())
require.NoError(t, err)
defer tx.Rollback()

slots := []uint64{100, 101, 102, 103, 104}
roots := make([]common.Hash, len(slots))
for i, slot := range slots {
block := cltypes.NewSignedBeaconBlock(&clparams.MainnetBeaconConfig, clparams.Phase0Version)
block.Block.Slot = slot
block.Block.ParentRoot = common.Hash{byte(i + 1)}
require.NoError(t, WriteBeaconBlockHeaderAndIndicies(context.Background(), tx, block.SignedBeaconBlockHeader(), true))
root, err := block.Block.HashSSZ()
require.NoError(t, err)
roots[i] = common.Hash(root)
}

// Truncate from slot 102: deletes 102/103/104, preserves 100/101.
highestSlot, highestRoot, err := TruncateCanonicalChain(context.Background(), tx, 102)
require.NoError(t, err)
require.Equal(t, uint64(104), highestSlot)
require.Equal(t, roots[4], highestRoot)

// Slots 100, 101 must still be canonical.
for i, slot := range []uint64{100, 101} {
preserved, err := ReadCanonicalBlockRoot(tx, slot)
require.NoError(t, err)
require.Equal(t, roots[i], preserved)
}

// Slots 102, 103, 104 must be gone.
for _, slot := range []uint64{102, 103, 104} {
gone, err := ReadCanonicalBlockRoot(tx, slot)
require.NoError(t, err)
require.Equal(t, common.Hash{}, gone)
}
}

func TestReadBeaconBlockHeader(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
Expand Down
29 changes: 8 additions & 21 deletions cl/phase1/stages/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,6 @@ func updateCanonicalChainInTheDatabase(ctx context.Context, tx kv.RwTx, headSlot
return fmt.Errorf("failed to read canonical block root: %w", err)
}

oldCanonical := common.Hash{}
// Guard against uint64 underflow: currentSlot=0 → currentSlot-1 = MaxUint64 → infinite loop.
for i := currentSlot; i > 1; i-- {
oldCanonical, err = beacon_indicies.ReadCanonicalBlockRoot(tx, i-1)
if err != nil {
return fmt.Errorf("failed to read canonical block root: %w", err)
}
if oldCanonical != (common.Hash{}) {
break
}
}

// List of new canonical chain entries
reconnectionRoots := []canonicalEntry{{currentSlot, currentRoot}}

Expand Down Expand Up @@ -151,8 +139,10 @@ func updateCanonicalChainInTheDatabase(ctx context.Context, tx kv.RwTx, headSlot
reconnectionRoots = append(reconnectionRoots, canonicalEntry{currentSlot, currentRoot})
}

// Truncate the canonical chain at the current slot
if err := beacon_indicies.TruncateCanonicalChain(ctx, tx, currentSlot); err != nil {
// Truncate the canonical chain at the common ancestor and capture the
// previous canonical tip (highest slot/root deleted) for the chain_reorg event.
oldCanonicalSlot, oldCanonical, err := beacon_indicies.TruncateCanonicalChain(ctx, tx, currentSlot)
if err != nil {
return fmt.Errorf("failed to truncate canonical chain: %w", err)
}

Expand All @@ -168,12 +158,9 @@ func updateCanonicalChainInTheDatabase(ctx context.Context, tx kv.RwTx, headSlot
return fmt.Errorf("failed to mark root canonical: %w", err)
}

// check reorg
parentRoot, err := beacon_indicies.ReadParentBlockRoot(ctx, tx, headRoot)
if err != nil {
return fmt.Errorf("failed to read parent block root: %w", err)
}
if parentRoot != oldCanonical {
// A reorg occurred if the previous canonical chain extended past the
// common ancestor. currentSlot is the common ancestor's slot here.
if oldCanonical != (common.Hash{}) && currentSlot < oldCanonicalSlot {
log.Debug("cl reorg", "new_head_slot", headSlot, "fork_slot", currentSlot, "old_canonical", oldCanonical, "new_canonical", headRoot)
oldStateRoot, err := beacon_indicies.ReadStateRootByBlockRoot(ctx, tx, oldCanonical)
if err != nil {
Expand All @@ -187,7 +174,7 @@ func updateCanonicalChainInTheDatabase(ctx context.Context, tx kv.RwTx, headSlot
}
reorgEvent := &beaconevents.ChainReorgData{
Slot: headSlot,
Depth: currentSlot - headSlot,
Depth: oldCanonicalSlot - currentSlot,
OldHeadBlock: oldCanonical,
NewHeadBlock: headRoot,
OldHeadState: oldStateRoot,
Expand Down
Loading