diff --git a/CHANGELOG.md b/CHANGELOG.md index ab18633ec67a..3d0b52ae038f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -181,6 +181,7 @@ Updating to this release is recommended at your convenience. - Light client support: fix light client attested header execution fields' wrong version bug. - Testing: added custom matcher for better push settings testing. - Registered `GetDepositSnapshot` Beacon API endpoint. +- Fix rolling back of a block due to a context deadline. ### Security diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 7a55c3f83fba..e2889507b801 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -76,6 +76,8 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { err := s.cfg.ForkChoiceStore.InsertNode(ctx, cfg.postState, cfg.roblock) if err != nil { + // Do not use parent context in the event it deadlined + ctx = trace.NewContext(context.Background(), span) s.rollbackBlock(ctx, cfg.roblock.Root()) return errors.Wrapf(err, "could not insert block %d to fork choice store", cfg.roblock.Block().Slot()) } diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index 053eb5fc4780..53fb11e8055f 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -2352,6 +2352,85 @@ func TestRollbackBlock(t *testing.T) { require.Equal(t, false, hasState) } +func TestRollbackBlock_ContextDeadline(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") + require.NoError(t, service.cfg.BeaconDB.SaveJustifiedCheckpoint(ctx, ðpb.Checkpoint{Root: parentRoot[:]})) + require.NoError(t, service.cfg.BeaconDB.SaveFinalizedCheckpoint(ctx, ðpb.Checkpoint{Root: parentRoot[:]})) + + st, err = service.HeadState(ctx) + require.NoError(t, err) + b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 33) + 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)) + roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})) + + b, err = util.GenerateFullBlock(postState, keys, util.DefaultBlockGenConfig(), 34) + 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 deadlined context when processing the block + cancCtx, canc := context.WithCancel(context.Background()) + canc() + roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + + parentRoot = roblock.Block().ParentRoot() + + cj := ðpb.Checkpoint{} + cj.Epoch = 1 + cj.Root = parentRoot[:] + require.NoError(t, postState.SetCurrentJustifiedCheckpoint(cj)) + require.NoError(t, postState.SetFinalizedCheckpoint(cj)) + + // Rollback block insertion into db and caches. + require.ErrorContains(t, "context canceled", service.postBlockProcess(&postBlockProcessConfig{cancCtx, 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 {