diff --git a/CHANGELOG.md b/CHANGELOG.md index e9535921b5..cc2c7651bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Added SubmitAggregateAndProofsRequestV2 endpoint. - Updated the `beacon-chain/monitor` package to Electra. [PR](https://github.com/prysmaticlabs/prysm/pull/14562) - Added ListAttestationsV2 endpoint. +- Add ability to rollback node's internal state during processing. ### Changed diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 6688ca8039..7a55c3f83f 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -76,6 +76,7 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { err := s.cfg.ForkChoiceStore.InsertNode(ctx, cfg.postState, cfg.roblock) if err != nil { + s.rollbackBlock(ctx, cfg.roblock.Root()) return errors.Wrapf(err, "could not insert block %d to fork choice store", cfg.roblock.Block().Slot()) } if err := s.handleBlockAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil { @@ -687,3 +688,15 @@ func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, bl } return err } + +// In the event of an issue processing a block we rollback changes done to the db and our caches +// to always ensure that the node's internal state is consistent. +func (s *Service) rollbackBlock(ctx context.Context, blockRoot [32]byte) { + log.Warnf("Rolling back insertion of block with root %#x due to processing error", blockRoot) + if err := s.cfg.StateGen.DeleteStateFromCaches(ctx, blockRoot); err != nil { + log.WithError(err).Errorf("Could not delete state from caches with block root %#x", blockRoot) + } + if err := s.cfg.BeaconDB.DeleteBlock(ctx, blockRoot); err != nil { + log.WithError(err).Errorf("Could not delete block with block root %#x", blockRoot) + } +} diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index 9f326dbf8a..7ed483861f 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -2293,6 +2293,59 @@ func Test_getFCUArgs(t *testing.T) { require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot) } +func TestRollbackBlock(t *testing.T) { + service, tr := minimalTestService(t) + ctx := tr.ctx + + st, keys := util.DeterministicGenesisState(t, 64) + stateRoot, err := st.HashTreeRoot(ctx) + require.NoError(t, err, "Could not hash genesis state") + + require.NoError(t, service.saveGenesisData(ctx, st)) + + genesis := blocks.NewGenesisBlock(stateRoot[:]) + wsb, err := consensusblocks.NewSignedBeaconBlock(genesis) + require.NoError(t, err) + require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb), "Could not save genesis block") + parentRoot, err := genesis.Block.HashTreeRoot() + require.NoError(t, err, "Could not get signing root") + require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st, parentRoot), "Could not save genesis state") + require.NoError(t, service.cfg.BeaconDB.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") + + st, err = service.HeadState(ctx) + require.NoError(t, err) + b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 1) + require.NoError(t, err) + wsb, err = consensusblocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + root, err := b.Block.HashTreeRoot() + require.NoError(t, err) + preState, err := service.getBlockPreState(ctx, wsb.Block()) + require.NoError(t, err) + postState, err := service.validateStateTransition(ctx, preState, wsb) + require.NoError(t, err) + require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState)) + + require.Equal(t, true, service.cfg.BeaconDB.HasBlock(ctx, root)) + hasState, err := service.cfg.StateGen.HasState(ctx, root) + require.NoError(t, err) + require.Equal(t, true, hasState) + + // Set invalid parent root to trigger forkchoice error. + wsb.SetParentRoot([]byte("bad")) + roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + + // Rollback block insertion into db and caches. + require.ErrorContains(t, fmt.Sprintf("could not insert block %d to fork choice store", roblock.Block().Slot()), service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})) + + // The block should no longer exist. + require.Equal(t, false, service.cfg.BeaconDB.HasBlock(ctx, root)) + hasState, err = service.cfg.StateGen.HasState(ctx, root) + require.NoError(t, err) + require.Equal(t, false, hasState) +} + func fakeCommitments(n int) [][]byte { f := make([][]byte, n) for i := range f {