diff --git a/beacon-chain/db/kv/BUILD.bazel b/beacon-chain/db/kv/BUILD.bazel index 41560e0e44bf..a1995e31e47f 100644 --- a/beacon-chain/db/kv/BUILD.bazel +++ b/beacon-chain/db/kv/BUILD.bazel @@ -20,6 +20,7 @@ go_library( "migration.go", "migration_archived_index.go", "migration_block_slot_index.go", + "migration_finalized_parent.go", "migration_state_validators.go", "schema.go", "state.go", diff --git a/beacon-chain/db/kv/migration.go b/beacon-chain/db/kv/migration.go index 70c485d2c9de..4bf2ad92299c 100644 --- a/beacon-chain/db/kv/migration.go +++ b/beacon-chain/db/kv/migration.go @@ -14,6 +14,7 @@ var migrations = []migration{ migrateArchivedIndex, migrateBlockSlotIndex, migrateStateValidators, + migrateFinalizedParent, } // RunMigrations defined in the migrations array. diff --git a/beacon-chain/db/kv/migration_finalized_parent.go b/beacon-chain/db/kv/migration_finalized_parent.go new file mode 100644 index 000000000000..dc58dde40b16 --- /dev/null +++ b/beacon-chain/db/kv/migration_finalized_parent.go @@ -0,0 +1,88 @@ +package kv + +import ( + "bytes" + "context" + "fmt" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + bolt "go.etcd.io/bbolt" +) + +var migrationFinalizedParent = []byte("parent_bug_32fb183") + +func migrateFinalizedParent(ctx context.Context, db *bolt.DB) error { + if updateErr := db.Update(func(tx *bolt.Tx) error { + mb := tx.Bucket(migrationsBucket) + if b := mb.Get(migrationFinalizedParent); bytes.Equal(b, migrationCompleted) { + return nil // Migration already completed. + } + + bkt := tx.Bucket(finalizedBlockRootsIndexBucket) + if bkt == nil { + return fmt.Errorf("unable to read %s bucket for migration", finalizedBlockRootsIndexBucket) + } + bb := tx.Bucket(blocksBucket) + if bb == nil { + return fmt.Errorf("unable to read %s bucket for migration", blocksBucket) + } + + c := bkt.Cursor() + var skipped primitives.Slot + foundBuggedIdx := false + maxBugSearch := params.BeaconConfig().SlotsPerEpoch * 100 + for k, v := c.Last(); k != nil; k, v = c.Prev() { + // check if context is cancelled in between + if ctx.Err() != nil { + return ctx.Err() + } + + idxEntry := ðpb.FinalizedBlockRootContainer{} + if err := decode(ctx, v, idxEntry); err != nil { + return errors.Wrapf(err, "unable to decode finalized block root container for root=%#x", k) + } + // Not one of the corrupt values + if !bytes.Equal(idxEntry.ParentRoot, k) { + skipped += 1 + if !foundBuggedIdx && skipped > maxBugSearch { + break + } + continue + } + foundBuggedIdx = true + log.WithField("root", fmt.Sprintf("%#x", k)).Debug("found index entry with incorrect parent root") + + // Look up full block to get the correct parent root. + encBlk := bb.Get(k) + if encBlk == nil { + return errors.Wrapf(ErrNotFound, "could not find block for corrupt finalized index entry %#x", k) + } + blk, err := unmarshalBlock(ctx, encBlk) + if err != nil { + return errors.Wrapf(err, "unable to decode block for root=%#x", k) + } + // Replace parent root in the index with the correct value and write it back. + pr := blk.Block().ParentRoot() + idxEntry.ParentRoot = pr[:] + idxEnc, err := encode(ctx, idxEntry) + if err != nil { + return errors.Wrapf(err, "failed to encode finalized index entry for root=%#x", k) + } + if err := bkt.Put(k, idxEnc); err != nil { + return errors.Wrapf(err, "failed to update finalized index entry for root=%#x", k) + } + log.WithField("root", fmt.Sprintf("%#x", k)). + WithField("parentRoot", fmt.Sprintf("%#x", idxEntry.ParentRoot)). + Debug("updated corrupt index entry with correct parent") + } + // Mark migration complete. + return mb.Put(migrationFinalizedParent, migrationCompleted) + }); updateErr != nil { + log.WithError(updateErr).Errorf("could not run finalized parent root index repair migration") + return updateErr + } + return nil +}