From aca38e3f24b6db2d959b283c76b39effabf98669 Mon Sep 17 00:00:00 2001 From: Potuz Date: Sun, 18 Sep 2022 07:41:13 -0300 Subject: [PATCH 1/6] Remove protoarray forkchoice --- beacon-chain/blockchain/BUILD.bazel | 2 +- beacon-chain/blockchain/chain_info_test.go | 109 +- .../blockchain/execution_engine_test.go | 146 -- beacon-chain/blockchain/head_test.go | 59 - beacon-chain/blockchain/mock_test.go | 4 +- beacon-chain/blockchain/pow_block_test.go | 6 +- .../blockchain/process_attestation_test.go | 62 - beacon-chain/blockchain/process_block_test.go | 994 +---------- beacon-chain/blockchain/receive_block_test.go | 8 +- beacon-chain/blockchain/service_test.go | 41 - .../weak_subjectivity_checks_test.go | 4 +- .../forkchoice/protoarray/BUILD.bazel | 79 - beacon-chain/forkchoice/protoarray/doc.go | 7 - beacon-chain/forkchoice/protoarray/errors.go | 19 - .../forkchoice/protoarray/ffg_update_test.go | 280 ---- beacon-chain/forkchoice/protoarray/helpers.go | 109 -- .../forkchoice/protoarray/helpers_test.go | 258 --- beacon-chain/forkchoice/protoarray/metrics.go | 54 - .../forkchoice/protoarray/no_vote_test.go | 103 -- beacon-chain/forkchoice/protoarray/node.go | 50 - .../forkchoice/protoarray/node_test.go | 38 - beacon-chain/forkchoice/protoarray/on_tick.go | 74 - .../forkchoice/protoarray/on_tick_test.go | 105 -- .../forkchoice/protoarray/optimistic_sync.go | 180 -- .../protoarray/optimistic_sync_test.go | 554 ------- .../forkchoice/protoarray/proposer_boost.go | 43 - .../protoarray/proposer_boost_test.go | 476 ------ beacon-chain/forkchoice/protoarray/store.go | 1120 ------------- .../forkchoice/protoarray/store_test.go | 1469 ----------------- beacon-chain/forkchoice/protoarray/types.go | 81 - .../protoarray/unrealized_justification.go | 135 -- .../unrealized_justification_test.go | 333 ---- .../forkchoice/protoarray/vote_test.go | 321 ---- beacon-chain/node/BUILD.bazel | 1 - beacon-chain/node/node.go | 8 +- beacon-chain/rpc/eth/validator/BUILD.bazel | 2 +- .../rpc/eth/validator/validator_test.go | 4 +- .../validator/proposer_bellatrix_test.go | 10 +- 38 files changed, 72 insertions(+), 7276 deletions(-) delete mode 100644 beacon-chain/forkchoice/protoarray/BUILD.bazel delete mode 100644 beacon-chain/forkchoice/protoarray/doc.go delete mode 100644 beacon-chain/forkchoice/protoarray/errors.go delete mode 100644 beacon-chain/forkchoice/protoarray/ffg_update_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/helpers.go delete mode 100644 beacon-chain/forkchoice/protoarray/helpers_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/metrics.go delete mode 100644 beacon-chain/forkchoice/protoarray/no_vote_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/node.go delete mode 100644 beacon-chain/forkchoice/protoarray/node_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/on_tick.go delete mode 100644 beacon-chain/forkchoice/protoarray/on_tick_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/optimistic_sync.go delete mode 100644 beacon-chain/forkchoice/protoarray/optimistic_sync_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/proposer_boost.go delete mode 100644 beacon-chain/forkchoice/protoarray/proposer_boost_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/store.go delete mode 100644 beacon-chain/forkchoice/protoarray/store_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/types.go delete mode 100644 beacon-chain/forkchoice/protoarray/unrealized_justification.go delete mode 100644 beacon-chain/forkchoice/protoarray/unrealized_justification_test.go delete mode 100644 beacon-chain/forkchoice/protoarray/vote_test.go diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index 7ba787222593..e1b38bce7ae7 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -51,7 +51,6 @@ go_library( "//beacon-chain/execution:go_default_library", "//beacon-chain/forkchoice:go_default_library", "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", - "//beacon-chain/forkchoice/protoarray:go_default_library", "//beacon-chain/forkchoice/types:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/slashings:go_default_library", @@ -82,6 +81,7 @@ go_library( "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", + "@com_github_prysmaticlabs_prysm_v3//beacon-chain/forkchoice/protoarray:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@io_opencensus_go//trace:go_default_library", ], diff --git a/beacon-chain/blockchain/chain_info_test.go b/beacon-chain/blockchain/chain_info_test.go index 454e706852ba..9e6a1b929edb 100644 --- a/beacon-chain/blockchain/chain_info_test.go +++ b/beacon-chain/blockchain/chain_info_test.go @@ -7,7 +7,6 @@ import ( testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" state_native "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native" @@ -307,7 +306,7 @@ func TestService_HeadGenesisValidatorsRoot(t *testing.T) { } func TestService_ChainHeads_ProtoArray(t *testing.T) { ctx := context.Background() - c := &Service{cfg: &config{ForkChoiceStore: protoarray.New()}} + c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}} ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} st, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc) @@ -337,7 +336,7 @@ func TestService_ChainHeads_ProtoArray(t *testing.T) { // \ ---------- E // ---------- D -func TestService_ChainHeads_DoublyLinkedTree(t *testing.T) { +func TestService_ChainHeads(t *testing.T) { ctx := context.Background() c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}} ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} @@ -429,29 +428,7 @@ func TestService_HeadValidatorIndexToPublicKeyNil(t *testing.T) { require.Equal(t, [fieldparams.BLSPubkeyLength]byte{}, p) } -func TestService_IsOptimistic_ProtoArray(t *testing.T) { - params.SetupTestConfigCleanup(t) - cfg := params.BeaconConfig() - cfg.BellatrixForkEpoch = 0 - params.OverrideBeaconConfig(cfg) - - ctx := context.Background() - c := &Service{cfg: &config{ForkChoiceStore: protoarray.New()}, head: &head{slot: 101, root: [32]byte{'b'}}} - ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - st, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc) - require.NoError(t, err) - require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc) - require.NoError(t, err) - require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot)) - - opt, err := c.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, true, opt) -} - -func TestService_IsOptimistic_DoublyLinkedTree(t *testing.T) { +func TestService_IsOptimistic(t *testing.T) { params.SetupTestConfigCleanup(t) cfg := params.BeaconConfig() cfg.BellatrixForkEpoch = 0 @@ -481,24 +458,7 @@ func TestService_IsOptimisticBeforeBellatrix(t *testing.T) { require.Equal(t, false, opt) } -func TestService_IsOptimisticForRoot_ProtoArray(t *testing.T) { - ctx := context.Background() - c := &Service{cfg: &config{ForkChoiceStore: protoarray.New()}, head: &head{slot: 101, root: [32]byte{'b'}}} - ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - st, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc) - require.NoError(t, err) - require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc) - require.NoError(t, err) - require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot)) - - opt, err := c.IsOptimisticForRoot(ctx, [32]byte{'a'}) - require.NoError(t, err) - require.Equal(t, true, opt) -} - -func TestService_IsOptimisticForRoot_DoublyLinkedTree(t *testing.T) { +func TestService_IsOptimisticForRoot(t *testing.T) { ctx := context.Background() c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}, head: &head{slot: 101, root: [32]byte{'b'}}} ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} @@ -515,66 +475,7 @@ func TestService_IsOptimisticForRoot_DoublyLinkedTree(t *testing.T) { require.Equal(t, true, opt) } -func TestService_IsOptimisticForRoot_DB_ProtoArray(t *testing.T) { - beaconDB := testDB.SetupDB(t) - ctx := context.Background() - c := &Service{cfg: &config{BeaconDB: beaconDB, ForkChoiceStore: protoarray.New()}, head: &head{slot: 101, root: [32]byte{'b'}}} - c.head = &head{root: params.BeaconConfig().ZeroHash} - b := util.NewBeaconBlock() - b.Block.Slot = 10 - br, err := b.Block.HashTreeRoot() - require.NoError(t, err) - util.SaveBlock(t, context.Background(), beaconDB, b) - require.NoError(t, beaconDB.SaveStateSummary(context.Background(), ðpb.StateSummary{Root: br[:], Slot: 10})) - - optimisticBlock := util.NewBeaconBlock() - optimisticBlock.Block.Slot = 97 - optimisticRoot, err := optimisticBlock.Block.HashTreeRoot() - require.NoError(t, err) - util.SaveBlock(t, context.Background(), beaconDB, optimisticBlock) - - validatedBlock := util.NewBeaconBlock() - validatedBlock.Block.Slot = 9 - validatedRoot, err := validatedBlock.Block.HashTreeRoot() - require.NoError(t, err) - util.SaveBlock(t, context.Background(), beaconDB, validatedBlock) - - validatedCheckpoint := ðpb.Checkpoint{Root: br[:]} - require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, validatedCheckpoint)) - - _, err = c.IsOptimisticForRoot(ctx, optimisticRoot) - require.ErrorContains(t, "nil summary returned from the DB", err) - - require.NoError(t, beaconDB.SaveStateSummary(context.Background(), ðpb.StateSummary{Root: optimisticRoot[:], Slot: 11})) - optimistic, err := c.IsOptimisticForRoot(ctx, optimisticRoot) - require.NoError(t, err) - require.Equal(t, true, optimistic) - - require.NoError(t, beaconDB.SaveStateSummary(context.Background(), ðpb.StateSummary{Root: validatedRoot[:], Slot: 9})) - cp := ðpb.Checkpoint{ - Epoch: 1, - Root: validatedRoot[:], - } - require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, validatedRoot)) - require.NoError(t, beaconDB.SaveFinalizedCheckpoint(ctx, cp)) - - validated, err := c.IsOptimisticForRoot(ctx, validatedRoot) - require.NoError(t, err) - require.Equal(t, false, validated) - - // Before the first finalized epoch, finalized root could be zeros. - validatedCheckpoint = ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, br)) - require.NoError(t, beaconDB.SaveStateSummary(context.Background(), ðpb.StateSummary{Root: params.BeaconConfig().ZeroHash[:], Slot: 10})) - require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, validatedCheckpoint)) - - require.NoError(t, beaconDB.SaveStateSummary(context.Background(), ðpb.StateSummary{Root: optimisticRoot[:], Slot: 11})) - optimistic, err = c.IsOptimisticForRoot(ctx, optimisticRoot) - require.NoError(t, err) - require.Equal(t, true, optimistic) -} - -func TestService_IsOptimisticForRoot_DB_DoublyLinkedTree(t *testing.T) { +func TestService_IsOptimisticForRoot_DB(t *testing.T) { beaconDB := testDB.SetupDB(t) ctx := context.Background() c := &Service{cfg: &config{BeaconDB: beaconDB, ForkChoiceStore: doublylinkedtree.New()}, head: &head{slot: 101, root: [32]byte{'b'}}} diff --git a/beacon-chain/blockchain/execution_engine_test.go b/beacon-chain/blockchain/execution_engine_test.go index 9ffd50d9eb6b..73803bea6f6a 100644 --- a/beacon-chain/blockchain/execution_engine_test.go +++ b/beacon-chain/blockchain/execution_engine_test.go @@ -13,7 +13,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution" mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" bstate "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" @@ -198,151 +197,6 @@ func Test_NotifyForkchoiceUpdate(t *testing.T) { } } -// -// -// A <- B <- C <- D -// \ -// ---------- E <- F -// \ -// ------ G -// D is the current head, attestations for F and G come late, both are invalid. -// We switch recursively to F then G and finally to D. -// -// We test: -// 1. forkchoice removes blocks F and G from the forkchoice implementation -// 2. forkchoice removes the weights of these blocks -// 3. the blockchain package calls fcu to obtain heads G -> F -> D. - -func Test_NotifyForkchoiceUpdateRecursive_Protoarray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - // Prepare blocks - ba := util.NewBeaconBlockBellatrix() - ba.Block.Body.ExecutionPayload.BlockNumber = 1 - wba := util.SaveBlock(t, ctx, beaconDB, ba) - bra, err := wba.Block().HashTreeRoot() - require.NoError(t, err) - - bb := util.NewBeaconBlockBellatrix() - bb.Block.Body.ExecutionPayload.BlockNumber = 2 - wbb := util.SaveBlock(t, ctx, beaconDB, bb) - brb, err := wbb.Block().HashTreeRoot() - require.NoError(t, err) - - bc := util.NewBeaconBlockBellatrix() - bc.Block.Body.ExecutionPayload.BlockNumber = 3 - wbc := util.SaveBlock(t, ctx, beaconDB, bc) - brc, err := wbc.Block().HashTreeRoot() - require.NoError(t, err) - - bd := util.NewBeaconBlockBellatrix() - pd := [32]byte{'D'} - bd.Block.Body.ExecutionPayload.BlockHash = pd[:] - bd.Block.Body.ExecutionPayload.BlockNumber = 4 - wbd := util.SaveBlock(t, ctx, beaconDB, bd) - brd, err := wbd.Block().HashTreeRoot() - require.NoError(t, err) - - be := util.NewBeaconBlockBellatrix() - pe := [32]byte{'E'} - be.Block.Body.ExecutionPayload.BlockHash = pe[:] - be.Block.Body.ExecutionPayload.BlockNumber = 5 - wbe := util.SaveBlock(t, ctx, beaconDB, be) - bre, err := wbe.Block().HashTreeRoot() - require.NoError(t, err) - - bf := util.NewBeaconBlockBellatrix() - pf := [32]byte{'F'} - bf.Block.Body.ExecutionPayload.BlockHash = pf[:] - bf.Block.Body.ExecutionPayload.BlockNumber = 6 - bf.Block.ParentRoot = bre[:] - wbf := util.SaveBlock(t, ctx, beaconDB, bf) - brf, err := wbf.Block().HashTreeRoot() - require.NoError(t, err) - - bg := util.NewBeaconBlockBellatrix() - bg.Block.Body.ExecutionPayload.BlockNumber = 7 - pg := [32]byte{'G'} - bg.Block.Body.ExecutionPayload.BlockHash = pg[:] - bg.Block.ParentRoot = bre[:] - wbg := util.SaveBlock(t, ctx, beaconDB, bg) - brg, err := wbg.Block().HashTreeRoot() - require.NoError(t, err) - - // Insert blocks into forkchoice - service := setupBeaconChain(t, beaconDB) - fcs := protoarray.New() - service.cfg.ForkChoiceStore = fcs - service.cfg.ProposerSlotIndexCache = cache.NewProposerPayloadIDsCache() - - service.justifiedBalances.balances = []uint64{50, 100, 200} - require.NoError(t, err) - ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 5, bre, brb, [32]byte{'E'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 6, brf, bre, [32]byte{'F'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 7, brg, bre, [32]byte{'G'}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - - // Insert Attestations to D, F and G so that they have higher weight than D - // Ensure G is head - fcs.ProcessAttestation(ctx, []uint64{0}, brd, 1) - fcs.ProcessAttestation(ctx, []uint64{1}, brf, 1) - fcs.ProcessAttestation(ctx, []uint64{2}, brg, 1) - jc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: bra} - require.NoError(t, fcs.UpdateJustifiedCheckpoint(jc)) - headRoot, err := fcs.Head(ctx, []uint64{50, 100, 200}) - require.NoError(t, err) - require.Equal(t, brg, headRoot) - - // Prepare Engine Mock to return invalid unless head is D, LVH = E - service.cfg.ExecutionEngineCaller = &mockExecution.EngineClient{ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, ForkChoiceUpdatedResp: pe[:], OverrideValidHash: [32]byte{'D'}} - st, _ := util.DeterministicGenesisState(t, 1) - service.head = &head{ - state: st, - block: wba, - } - - require.NoError(t, beaconDB.SaveState(ctx, st, bra)) - require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra)) - a := ¬ifyForkchoiceUpdateArg{ - headState: st, - headBlock: wbg.Block(), - headRoot: brg, - } - _, err = service.notifyForkchoiceUpdate(ctx, a) - require.Equal(t, true, IsInvalidBlock(err)) - require.Equal(t, brf, InvalidBlockRoot(err)) - - // Ensure Head is D - headRoot, err = fcs.Head(ctx, service.justifiedBalances.balances) - require.NoError(t, err) - require.Equal(t, brd, headRoot) - - // Ensure F and G where removed but their parent E wasn't - require.Equal(t, false, fcs.HasNode(brf)) - require.Equal(t, false, fcs.HasNode(brg)) - require.Equal(t, true, fcs.HasNode(bre)) -} - func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) diff --git a/beacon-chain/blockchain/head_test.go b/beacon-chain/blockchain/head_test.go index 638331729b54..34875fc3239e 100644 --- a/beacon-chain/blockchain/head_test.go +++ b/beacon-chain/blockchain/head_test.go @@ -10,7 +10,6 @@ import ( mock "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/v3/config/features" "github.com/prysmaticlabs/prysm/v3/config/params" @@ -234,64 +233,6 @@ func Test_notifyNewHeadEvent(t *testing.T) { }) } -func TestSaveOrphanedAtts_NoCommonAncestor_Protoarray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - service := setupBeaconChain(t, beaconDB) - // this test does not make sense in doubly linked tree since it enforces - // that the finalized node is a common ancestor - service.cfg.ForkChoiceStore = protoarray.New() - - service.genesisTime = time.Now().Add(time.Duration(-10*int64(1)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second) - - // Chain setup - // 0 -- 1 -- 2 -- 3 - // -4 - st, keys := util.DeterministicGenesisState(t, 64) - blkG, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 0) - assert.NoError(t, err) - util.SaveBlock(t, ctx, service.cfg.BeaconDB, blkG) - rG, err := blkG.Block.HashTreeRoot() - require.NoError(t, err) - - blk1, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 1) - assert.NoError(t, err) - blk1.Block.ParentRoot = rG[:] - r1, err := blk1.Block.HashTreeRoot() - require.NoError(t, err) - - blk2, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 2) - assert.NoError(t, err) - blk2.Block.ParentRoot = r1[:] - r2, err := blk2.Block.HashTreeRoot() - require.NoError(t, err) - - blk3, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 3) - assert.NoError(t, err) - blk3.Block.ParentRoot = r2[:] - r3, err := blk3.Block.HashTreeRoot() - require.NoError(t, err) - - blk4 := util.NewBeaconBlock() - blk4.Block.Slot = 4 - r4, err := blk4.Block.HashTreeRoot() - require.NoError(t, err) - - for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk3, blk4} { - r, err := blk.Block.HashTreeRoot() - require.NoError(t, err) - ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]} - state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc) - require.NoError(t, err) - require.NoError(t, service.ForkChoicer().InsertNode(ctx, state, blkRoot)) - util.SaveBlock(t, ctx, beaconDB, blk) - } - - require.NoError(t, service.saveOrphanedAtts(ctx, r3, r4)) - require.Equal(t, 0, service.cfg.AttPool.AggregatedAttestationCount()) -} - func TestSaveOrphanedAtts(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) diff --git a/beacon-chain/blockchain/mock_test.go b/beacon-chain/blockchain/mock_test.go index 4f8289b7f15a..e2bb69ff47e2 100644 --- a/beacon-chain/blockchain/mock_test.go +++ b/beacon-chain/blockchain/mock_test.go @@ -6,14 +6,14 @@ import ( "testing" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" ) func testServiceOptsWithDB(t *testing.T) []Option { beaconDB := testDB.SetupDB(t) - fcs := protoarray.New() + fcs := doublylinkedtree.New() return []Option{ WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), diff --git a/beacon-chain/blockchain/pow_block_test.go b/beacon-chain/blockchain/pow_block_test.go index 23ecf8496392..e8ecc56d9199 100644 --- a/beacon-chain/blockchain/pow_block_test.go +++ b/beacon-chain/blockchain/pow_block_test.go @@ -10,7 +10,7 @@ import ( "github.com/holiman/uint256" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" mocks "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" @@ -109,7 +109,7 @@ func Test_validateMergeBlock(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) - fcs := protoarray.New() + fcs := doublylinkedtree.New() opts := []Option{ WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), @@ -159,7 +159,7 @@ func Test_validateMergeBlock(t *testing.T) { func Test_getBlkParentHashAndTD(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) - fcs := protoarray.New() + fcs := doublylinkedtree.New() opts := []Option{ WithDatabase(beaconDB), WithStateGen(stategen.New(beaconDB)), diff --git a/beacon-chain/blockchain/process_attestation_test.go b/beacon-chain/blockchain/process_attestation_test.go index 86b3bba67011..dc905d76e8b9 100644 --- a/beacon-chain/blockchain/process_attestation_test.go +++ b/beacon-chain/blockchain/process_attestation_test.go @@ -8,7 +8,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" @@ -234,36 +233,6 @@ func TestStore_OnAttestation_ErrorConditions_DoublyLinkedTree(t *testing.T) { } } -func TestStore_OnAttestation_Ok_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - fcs := protoarray.New() - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(fcs), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - genesisState, pks := util.DeterministicGenesisState(t, 64) - service.SetGenesisTime(time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0)) - require.NoError(t, service.saveGenesisData(ctx, genesisState)) - att, err := util.GenerateAttestations(genesisState, pks, 1, 0, false) - require.NoError(t, err) - tRoot := bytesutil.ToBytes32(att[0].Data.Target.Root) - copied := genesisState.Copy() - copied, err = transition.ProcessSlots(ctx, copied, 1) - require.NoError(t, err) - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, copied, tRoot)) - ojc := ðpb.Checkpoint{Epoch: 1, Root: tRoot[:]} - ofc := ðpb.Checkpoint{Epoch: 1, Root: tRoot[:]} - state, blkRoot, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, ojc, ofc) - require.NoError(t, err) - require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot)) - require.NoError(t, service.OnAttestation(ctx, att[0])) -} - func TestStore_OnAttestation_Ok_DoublyLinkedTree(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -461,37 +430,6 @@ func TestVerifyBeaconBlock_OK(t *testing.T) { assert.NoError(t, service.verifyBeaconBlock(ctx, d), "Did not receive the wanted error") } -func TestVerifyFinalizedConsistency_InconsistentRoot_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - fcs := protoarray.New() - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(fcs), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - b32 := util.NewBeaconBlock() - b32.Block.Slot = 32 - util.SaveBlock(t, ctx, service.cfg.BeaconDB, b32) - r32, err := b32.Block.HashTreeRoot() - require.NoError(t, err) - - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 1})) - b33 := util.NewBeaconBlock() - b33.Block.Slot = 33 - b33.Block.ParentRoot = r32[:] - util.SaveBlock(t, ctx, service.cfg.BeaconDB, b33) - r33, err := b33.Block.HashTreeRoot() - require.NoError(t, err) - - err = service.VerifyFinalizedConsistency(context.Background(), r33[:]) - require.ErrorContains(t, "Root and finalized store are not consistent", err) -} - func TestVerifyFinalizedConsistency_InconsistentRoot_DoublyLinkedTree(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index 250b3cb6c0b6..2aec50ff8fba 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -24,7 +24,6 @@ import ( mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" @@ -46,103 +45,7 @@ import ( logTest "github.com/sirupsen/logrus/hooks/test" ) -func TestStore_OnBlock_ProtoArray(t *testing.T) { - ctx := context.Background() - - beaconDB := testDB.SetupDB(t) - fcs := protoarray.New() - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(fcs), - } - - service, err := NewService(ctx, opts...) - require.NoError(t, err) - genesisStateRoot := [32]byte{} - genesis := blocks.NewGenesisBlock(genesisStateRoot[:]) - util.SaveBlock(t, ctx, beaconDB, genesis) - validGenesisRoot, err := genesis.Block.HashTreeRoot() - require.NoError(t, err) - st, err := util.NewBeaconState() - require.NoError(t, err) - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), validGenesisRoot)) - roots, err := blockTree1(t, beaconDB, validGenesisRoot[:]) - require.NoError(t, err) - random := util.NewBeaconBlock() - random.Block.Slot = 1 - random.Block.ParentRoot = validGenesisRoot[:] - util.SaveBlock(t, ctx, beaconDB, random) - randomParentRoot, err := random.Block.HashTreeRoot() - assert.NoError(t, err) - require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: st.Slot(), Root: randomParentRoot[:]})) - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), randomParentRoot)) - randomParentRoot2 := roots[1] - require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: st.Slot(), Root: randomParentRoot2})) - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), bytesutil.ToBytes32(randomParentRoot2))) - - tests := []struct { - name string - blk *ethpb.SignedBeaconBlock - s state.BeaconState - time uint64 - wantErrString string - }{ - { - name: "parent block root does not have a state", - blk: util.NewBeaconBlock(), - s: st.Copy(), - wantErrString: "could not reconstruct parent state", - }, - { - name: "block is from the future", - blk: func() *ethpb.SignedBeaconBlock { - b := util.NewBeaconBlock() - b.Block.ParentRoot = randomParentRoot2 - b.Block.Slot = params.BeaconConfig().FarFutureSlot - return b - }(), - s: st.Copy(), - wantErrString: "is in the far distant future", - }, - { - name: "could not get finalized block", - blk: func() *ethpb.SignedBeaconBlock { - b := util.NewBeaconBlock() - b.Block.ParentRoot = randomParentRoot[:] - return b - }(), - s: st.Copy(), - wantErrString: "is not a descendant of the current finalized block", - }, - { - name: "same slot as finalized block", - blk: func() *ethpb.SignedBeaconBlock { - b := util.NewBeaconBlock() - b.Block.Slot = 0 - b.Block.ParentRoot = randomParentRoot2 - return b - }(), - s: st.Copy(), - wantErrString: "block is equal or earlier than finalized block, slot 0 < slot 0", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fRoot := bytesutil.ToBytes32(roots[0]) - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: fRoot})) - root, err := tt.blk.Block.HashTreeRoot() - assert.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(tt.blk) - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - assert.ErrorContains(t, tt.wantErrString, err) - }) - } -} - -func TestStore_OnBlock_DoublyLinkedTree(t *testing.T) { +func TestStore_OnBlock(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -238,84 +141,7 @@ func TestStore_OnBlock_DoublyLinkedTree(t *testing.T) { } } -func TestStore_OnBlockBatch_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - st, keys := util.DeterministicGenesisState(t, 64) - require.NoError(t, service.saveGenesisData(ctx, st)) - bState := st.Copy() - - var blks []interfaces.SignedBeaconBlock - var blkRoots [][32]byte - for i := 0; i < 97; i++ { - b, err := util.GenerateFullBlock(bState, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - bState, err = transition.ExecuteStateTransition(ctx, bState, wsb) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.saveInitSyncBlock(ctx, root, wsb)) - blks = append(blks, wsb) - blkRoots = append(blkRoots, root) - } - err = service.onBlockBatch(ctx, blks, blkRoots[1:]) - require.ErrorIs(t, errWrongBlockCount, err) - err = service.onBlockBatch(ctx, blks, blkRoots) - require.NoError(t, err) - jcp := service.CurrentJustifiedCheckpt() - jroot := bytesutil.ToBytes32(jcp.Root) - require.Equal(t, blkRoots[63], jroot) - require.Equal(t, types.Epoch(2), service.cfg.ForkChoiceStore.JustifiedCheckpoint().Epoch) -} - -func TestStore_OnBlockBatch_PruneOK_Protoarray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - st, keys := util.DeterministicGenesisState(t, 64) - require.NoError(t, service.saveGenesisData(ctx, st)) - bState := st.Copy() - - var blks []interfaces.SignedBeaconBlock - var blkRoots [][32]byte - for i := 0; i < 320; i++ { - b, err := util.GenerateFullBlock(bState, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - bState, err = transition.ExecuteStateTransition(ctx, bState, wsb) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.saveInitSyncBlock(ctx, root, wsb)) - blks = append(blks, wsb) - blkRoots = append(blkRoots, root) - } - err = service.onBlockBatch(ctx, blks, blkRoots) - require.NoError(t, err) - require.Equal(t, 65, service.ForkChoicer().NodeCount()) -} - -func TestStore_OnBlockBatch_DoublyLinkedTree(t *testing.T) { +func TestStore_OnBlockBatch(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -393,34 +219,7 @@ func TestStore_OnBlockBatch_NotifyNewPayload(t *testing.T) { require.NoError(t, err) } -func TestCachedPreState_CanGetFromStateSummary_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - st, keys := util.DeterministicGenesisState(t, 64) - require.NoError(t, service.saveGenesisData(ctx, st)) - b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), types.Slot(1)) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - require.NoError(t, beaconDB.SaveBlock(ctx, wsb)) - - require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Slot: 1, Root: root[:]})) - require.NoError(t, service.cfg.StateGen.SaveState(ctx, root, st)) - require.NoError(t, service.verifyBlkPreState(ctx, wsb.Block())) -} - -func TestCachedPreState_CanGetFromStateSummary_DoublyLinkedTree(t *testing.T) { +func TestCachedPreState_CanGetFromStateSummary(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -447,48 +246,7 @@ func TestCachedPreState_CanGetFromStateSummary_DoublyLinkedTree(t *testing.T) { require.NoError(t, service.verifyBlkPreState(ctx, wsb.Block())) } -func TestFillForkChoiceMissingBlocks_CanSave_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - service.cfg.ForkChoiceStore = protoarray.New() - - st, _ := util.DeterministicGenesisState(t, 64) - require.NoError(t, service.saveGenesisData(ctx, st)) - - roots, err := blockTree1(t, beaconDB, service.originBlockRoot[:]) - require.NoError(t, err) - beaconState, _ := util.DeterministicGenesisState(t, 32) - blk := util.NewBeaconBlock() - blk.Block.Slot = 9 - blk.Block.ParentRoot = roots[8] - wsb, err := consensusblocks.NewSignedBeaconBlock(blk) - require.NoError(t, err) - - r0 := bytesutil.ToBytes32(roots[0]) - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 0, Root: r0})) - err = service.fillInForkChoiceMissingBlocks( - context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint()) - require.NoError(t, err) - - // 4 nodes from the block tree 1. B3 - B4 - B6 - B8 - // plus 1 node for genesis block root. - // block 0 is not inserted because its slot is 0 which is invalid - assert.Equal(t, 5, service.cfg.ForkChoiceStore.NodeCount(), "Miss match nodes") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(service.originBlockRoot), "Didn't save node") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(roots[3])), "Didn't save node") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(roots[4])), "Didn't save node") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(roots[6])), "Didn't save node") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(roots[8])), "Didn't save node") -} - -func TestFillForkChoiceMissingBlocks_CanSave_DoublyLinkedTree(t *testing.T) { +func TestFillForkChoiceMissingBlocks_CanSave(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -537,7 +295,7 @@ func TestFillForkChoiceMissingBlocks_CanSave_DoublyLinkedTree(t *testing.T) { assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(roots[8])), "Didn't save node") } -func TestFillForkChoiceMissingBlocks_RootsMatch_ProtoArray(t *testing.T) { +func TestFillForkChoiceMissingBlocks_RootsMatch(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -547,10 +305,11 @@ func TestFillForkChoiceMissingBlocks_RootsMatch_ProtoArray(t *testing.T) { } service, err := NewService(ctx, opts...) require.NoError(t, err) - service.cfg.ForkChoiceStore = protoarray.New() + service.cfg.ForkChoiceStore = doublylinkedtree.New() st, _ := util.DeterministicGenesisState(t, 64) require.NoError(t, service.saveGenesisData(ctx, st)) + roots, err := blockTree1(t, beaconDB, service.originBlockRoot[:]) require.NoError(t, err) @@ -562,25 +321,32 @@ func TestFillForkChoiceMissingBlocks_RootsMatch_ProtoArray(t *testing.T) { wsb, err := consensusblocks.NewSignedBeaconBlock(blk) require.NoError(t, err) + // save invalid block at slot 0 because doubly linked tree enforces that + // the parent of the last block inserted is the tree node. + fcp := ðpb.Checkpoint{Epoch: 0, Root: service.originBlockRoot[:]} r0 := bytesutil.ToBytes32(roots[0]) - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 0, Root: r0})) + state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, fcp, fcp) + require.NoError(t, err) + require.NoError(t, service.ForkChoicer().InsertNode(ctx, state, blkRoot)) + fcp2 := &forkchoicetypes.Checkpoint{Epoch: 0, Root: r0} + require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(fcp2)) err = service.fillInForkChoiceMissingBlocks( context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint()) require.NoError(t, err) - // 4 nodes from the block tree 1. B3 - B4 - B6 - B8 + // 5 nodes from the block tree 1. B0 - B3 - B4 - B6 - B8 // plus the origin block root - assert.Equal(t, 5, service.cfg.ForkChoiceStore.NodeCount(), "Miss match nodes") + assert.Equal(t, 6, service.cfg.ForkChoiceStore.NodeCount(), "Miss match nodes") // Ensure all roots and their respective blocks exist. - wantedRoots := [][]byte{roots[3], roots[4], roots[6], roots[8]} + wantedRoots := [][]byte{roots[0], roots[3], roots[4], roots[6], roots[8]} for i, rt := range wantedRoots { assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(rt)), fmt.Sprintf("Didn't save node: %d", i)) assert.Equal(t, true, service.cfg.BeaconDB.HasBlock(context.Background(), bytesutil.ToBytes32(rt))) } } -func TestFillForkChoiceMissingBlocks_RootsMatch_DoublyLinkedTree(t *testing.T) { +func TestFillForkChoiceMissingBlocks_FilterFinalized(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -592,123 +358,15 @@ func TestFillForkChoiceMissingBlocks_RootsMatch_DoublyLinkedTree(t *testing.T) { require.NoError(t, err) service.cfg.ForkChoiceStore = doublylinkedtree.New() - st, _ := util.DeterministicGenesisState(t, 64) - require.NoError(t, service.saveGenesisData(ctx, st)) - - roots, err := blockTree1(t, beaconDB, service.originBlockRoot[:]) + genesisStateRoot := [32]byte{} + genesis := blocks.NewGenesisBlock(genesisStateRoot[:]) + util.SaveBlock(t, ctx, beaconDB, genesis) + validGenesisRoot, err := genesis.Block.HashTreeRoot() + assert.NoError(t, err) + st, err := util.NewBeaconState() require.NoError(t, err) - beaconState, _ := util.DeterministicGenesisState(t, 32) - blk := util.NewBeaconBlock() - blk.Block.Slot = 9 - blk.Block.ParentRoot = roots[8] - - wsb, err := consensusblocks.NewSignedBeaconBlock(blk) - require.NoError(t, err) - - // save invalid block at slot 0 because doubly linked tree enforces that - // the parent of the last block inserted is the tree node. - fcp := ðpb.Checkpoint{Epoch: 0, Root: service.originBlockRoot[:]} - r0 := bytesutil.ToBytes32(roots[0]) - state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, fcp, fcp) - require.NoError(t, err) - require.NoError(t, service.ForkChoicer().InsertNode(ctx, state, blkRoot)) - fcp2 := &forkchoicetypes.Checkpoint{Epoch: 0, Root: r0} - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(fcp2)) - - err = service.fillInForkChoiceMissingBlocks( - context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint()) - require.NoError(t, err) - - // 5 nodes from the block tree 1. B0 - B3 - B4 - B6 - B8 - // plus the origin block root - assert.Equal(t, 6, service.cfg.ForkChoiceStore.NodeCount(), "Miss match nodes") - // Ensure all roots and their respective blocks exist. - wantedRoots := [][]byte{roots[0], roots[3], roots[4], roots[6], roots[8]} - for i, rt := range wantedRoots { - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(bytesutil.ToBytes32(rt)), fmt.Sprintf("Didn't save node: %d", i)) - assert.Equal(t, true, service.cfg.BeaconDB.HasBlock(context.Background(), bytesutil.ToBytes32(rt))) - } -} - -func TestFillForkChoiceMissingBlocks_FilterFinalized_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - service.cfg.ForkChoiceStore = protoarray.New() - - genesisStateRoot := [32]byte{} - genesis := blocks.NewGenesisBlock(genesisStateRoot[:]) - util.SaveBlock(t, ctx, beaconDB, genesis) - validGenesisRoot, err := genesis.Block.HashTreeRoot() - assert.NoError(t, err) - st, err := util.NewBeaconState() - require.NoError(t, err) - - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), validGenesisRoot)) - - // Define a tree branch, slot 63 <- 64 <- 65 <- 66 - b63 := util.NewBeaconBlock() - b63.Block.Slot = 63 - util.SaveBlock(t, ctx, service.cfg.BeaconDB, b63) - r63, err := b63.Block.HashTreeRoot() - require.NoError(t, err) - b64 := util.NewBeaconBlock() - b64.Block.Slot = 64 - b64.Block.ParentRoot = r63[:] - util.SaveBlock(t, ctx, service.cfg.BeaconDB, b64) - r64, err := b64.Block.HashTreeRoot() - require.NoError(t, err) - b65 := util.NewBeaconBlock() - b65.Block.Slot = 65 - b65.Block.ParentRoot = r64[:] - r65, err := b65.Block.HashTreeRoot() - require.NoError(t, err) - util.SaveBlock(t, ctx, service.cfg.BeaconDB, b65) - b66 := util.NewBeaconBlock() - b66.Block.Slot = 66 - b66.Block.ParentRoot = r65[:] - wsb := util.SaveBlock(t, ctx, service.cfg.BeaconDB, b66) - - beaconState, _ := util.DeterministicGenesisState(t, 32) - // Set finalized epoch to 2. - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 2, Root: r64})) - err = service.fillInForkChoiceMissingBlocks( - context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint()) - require.NoError(t, err) - - // We should have saved 1 node: block 65 - assert.Equal(t, 1, service.cfg.ForkChoiceStore.NodeCount(), "Miss match nodes") - assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(r65), "Didn't save node") -} - -func TestFillForkChoiceMissingBlocks_FilterFinalized_DoublyLinkedTree(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - opts := []Option{ - WithDatabase(beaconDB), - WithStateGen(stategen.New(beaconDB)), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - service.cfg.ForkChoiceStore = doublylinkedtree.New() - - genesisStateRoot := [32]byte{} - genesis := blocks.NewGenesisBlock(genesisStateRoot[:]) - util.SaveBlock(t, ctx, beaconDB, genesis) - validGenesisRoot, err := genesis.Block.HashTreeRoot() - assert.NoError(t, err) - st, err := util.NewBeaconState() - require.NoError(t, err) - - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), validGenesisRoot)) + require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st.Copy(), validGenesisRoot)) // Define a tree branch, slot 63 <- 64 <- 65 b63 := util.NewBeaconBlock() @@ -746,7 +404,7 @@ func TestFillForkChoiceMissingBlocks_FilterFinalized_DoublyLinkedTree(t *testing assert.Equal(t, true, service.cfg.ForkChoiceStore.HasNode(r65), "Didn't save node") } -func TestFillForkChoiceMissingBlocks_FinalizedSibling_DoublyLinkedTree(t *testing.T) { +func TestFillForkChoiceMissingBlocks_FinalizedSibling(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -1785,339 +1443,21 @@ func Test_verifyBlkFinalizedSlot_invalidBlock(t *testing.T) { } service, err := NewService(ctx, opts...) require.NoError(t, err) - require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 1})) - blk := util.HydrateBeaconBlock(ðpb.BeaconBlock{Slot: 1}) - wb, err := consensusblocks.NewBeaconBlock(blk) - require.NoError(t, err) - err = service.verifyBlkFinalizedSlot(wb) - require.Equal(t, true, IsInvalidBlock(err)) -} - -// See the description in #10777 and #10782 for the full setup -// We sync optimistically a chain of blocks. Block 17 is the last block in Epoch -// 2. Block 18 justifies block 12 (the first in Epoch 2) and Block 19 returns -// INVALID from FCU, with LVH block 17. No head is viable. We check -// that the node is optimistic and that we can actually import a block on top of -// 17 and recover. -func TestStore_NoViableHead_FCU_Protoarray(t *testing.T) { - params.SetupTestConfigCleanup(t) - config := params.BeaconConfig() - config.SlotsPerEpoch = 6 - config.AltairForkEpoch = 1 - config.BellatrixForkEpoch = 2 - params.OverrideBeaconConfig(config) - - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - mockEngine := &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus} - opts := []Option{ - WithDatabase(beaconDB), - WithAttestationPool(attestations.NewPool()), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), - WithStateNotifier(&mock.MockStateNotifier{}), - WithExecutionEngineCaller(mockEngine), - WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - 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") - - for i := 1; i < 6; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.onBlock(ctx, wsb, root)) - } - - for i := 6; i < 12; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockAltair(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - } - - for i := 12; i < 18; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - } - // Check that we haven't justified the second epoch yet - jc := service.ForkChoicer().JustifiedCheckpoint() - require.Equal(t, types.Epoch(0), jc.Epoch) - - // import a block that justifies the second epoch - driftGenesisTime(service, 18, 0) - validHeadState, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(validHeadState, keys, util.DefaultBlockGenConfig(), 18) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - firstInvalidRoot, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, firstInvalidRoot) - require.NoError(t, err) - jc = service.ForkChoicer().JustifiedCheckpoint() - require.Equal(t, types.Epoch(2), jc.Epoch) - - sjc := validHeadState.CurrentJustifiedCheckpoint() - require.Equal(t, types.Epoch(0), sjc.Epoch) - lvh := b.Block.Body.ExecutionPayload.ParentHash - // check our head - require.Equal(t, firstInvalidRoot, service.ForkChoicer().CachedHeadRoot()) - - // import another block to find out that it was invalid - mockEngine = &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, ForkChoiceUpdatedResp: lvh} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 19, 0) - st, err = service.HeadState(ctx) - require.NoError(t, err) - b, err = util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), 19) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.ErrorContains(t, "received an INVALID payload from execution engine", err) - // Check that forkchoice's head is the last invalid block imported. The - // store's headroot is the previous head (since the invalid block did - // not finish importing) one and that the node is optimistic - require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) - headRoot, err := service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, firstInvalidRoot, bytesutil.ToBytes32(headRoot)) - optimistic, err := service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, true, optimistic) - - // import another block based on the last valid head state - mockEngine = &mockExecution.EngineClient{} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 20, 0) - b, err = util.GenerateFullBlockBellatrix(validHeadState, keys, &util.BlockGenConfig{}, 20) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err = b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - // Check the newly imported block is head, it justified the right - // checkpoint and the node is no longer optimistic - require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) - sjc = service.CurrentJustifiedCheckpt() - require.Equal(t, jc.Epoch, sjc.Epoch) - require.Equal(t, jc.Root, bytesutil.ToBytes32(sjc.Root)) - optimistic, err = service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, false, optimistic) -} - -// See the description in #10777 and #10782 for the full setup -// We sync optimistically a chain of blocks. Block 17 is the last block in Epoch -// 2. Block 18 justifies block 12 (the first in Epoch 2) and Block 19 returns -// INVALID from FCU, with LVH block 17. No head is viable. We check -// that the node is optimistic and that we can actually import a block on top of -// 17 and recover. -func TestStore_NoViableHead_FCU_DoublyLinkedTree(t *testing.T) { - params.SetupTestConfigCleanup(t) - config := params.BeaconConfig() - config.SlotsPerEpoch = 6 - config.AltairForkEpoch = 1 - config.BellatrixForkEpoch = 2 - params.OverrideBeaconConfig(config) - - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - mockEngine := &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus} - opts := []Option{ - WithDatabase(beaconDB), - WithAttestationPool(attestations.NewPool()), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(doublylinkedtree.New()), - WithStateNotifier(&mock.MockStateNotifier{}), - WithExecutionEngineCaller(mockEngine), - WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - 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") - - for i := 1; i < 6; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.onBlock(ctx, wsb, root)) - } - - for i := 6; i < 12; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockAltair(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - } - - for i := 12; i < 18; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - } - // Check that we haven't justified the second epoch yet - jc := service.ForkChoicer().JustifiedCheckpoint() - require.Equal(t, types.Epoch(0), jc.Epoch) - - // import a block that justifies the second epoch - driftGenesisTime(service, 18, 0) - validHeadState, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(validHeadState, keys, util.DefaultBlockGenConfig(), 18) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - firstInvalidRoot, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, firstInvalidRoot) - require.NoError(t, err) - jc = service.ForkChoicer().JustifiedCheckpoint() - require.Equal(t, types.Epoch(2), jc.Epoch) - - sjc := validHeadState.CurrentJustifiedCheckpoint() - require.Equal(t, types.Epoch(0), sjc.Epoch) - lvh := b.Block.Body.ExecutionPayload.ParentHash - // check our head - require.Equal(t, firstInvalidRoot, service.ForkChoicer().CachedHeadRoot()) - - // import another block to find out that it was invalid - mockEngine = &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, ForkChoiceUpdatedResp: lvh} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 19, 0) - st, err = service.HeadState(ctx) - require.NoError(t, err) - b, err = util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), 19) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.ErrorContains(t, "received an INVALID payload from execution engine", err) - // Check that forkchoice's head is the last invalid block imported. The - // store's headroot is the previous head (since the invalid block did - // not finish importing) one and that the node is optimistic - require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) - headRoot, err := service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, firstInvalidRoot, bytesutil.ToBytes32(headRoot)) - optimistic, err := service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, true, optimistic) - - // import another block based on the last valid head state - mockEngine = &mockExecution.EngineClient{} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 20, 0) - b, err = util.GenerateFullBlockBellatrix(validHeadState, keys, &util.BlockGenConfig{}, 20) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err = b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - // Check the newly imported block is head, it justified the right - // checkpoint and the node is no longer optimistic - require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) - sjc = service.CurrentJustifiedCheckpt() - require.Equal(t, jc.Epoch, sjc.Epoch) - require.Equal(t, jc.Root, bytesutil.ToBytes32(sjc.Root)) - optimistic, err = service.IsOptimistic(ctx) + require.NoError(t, service.ForkChoicer().UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 1})) + blk := util.HydrateBeaconBlock(ðpb.BeaconBlock{Slot: 1}) + wb, err := consensusblocks.NewBeaconBlock(blk) require.NoError(t, err) - require.Equal(t, false, optimistic) + err = service.verifyBlkFinalizedSlot(wb) + require.Equal(t, true, IsInvalidBlock(err)) } // See the description in #10777 and #10782 for the full setup // We sync optimistically a chain of blocks. Block 17 is the last block in Epoch // 2. Block 18 justifies block 12 (the first in Epoch 2) and Block 19 returns -// INVALID from NewPayload, with LVH block 17. No head is viable. We check +// INVALID from FCU, with LVH block 17. No head is viable. We check // that the node is optimistic and that we can actually import a block on top of // 17 and recover. -func TestStore_NoViableHead_NewPayload_DoublyLinkedTree(t *testing.T) { +func TestStore_NoViableHead_FCU(t *testing.T) { params.SetupTestConfigCleanup(t) config := params.BeaconConfig() config.SlotsPerEpoch = 6 @@ -2223,7 +1563,7 @@ func TestStore_NoViableHead_NewPayload_DoublyLinkedTree(t *testing.T) { require.Equal(t, firstInvalidRoot, service.ForkChoicer().CachedHeadRoot()) // import another block to find out that it was invalid - mockEngine = &mockExecution.EngineClient{ErrNewPayload: execution.ErrInvalidPayloadStatus, NewPayloadResp: lvh} + mockEngine = &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus, ForkChoiceUpdatedResp: lvh} service.cfg.ExecutionEngineCaller = mockEngine driftGenesisTime(service, 19, 0) st, err = service.HeadState(ctx) @@ -2236,10 +1576,10 @@ func TestStore_NoViableHead_NewPayload_DoublyLinkedTree(t *testing.T) { require.NoError(t, err) err = service.onBlock(ctx, wsb, root) require.ErrorContains(t, "received an INVALID payload from execution engine", err) - // Check that forkchoice's head and store's headroot are the previous head (since the invalid block did - // not finish importing and it was never imported to forkchoice). Check - // also that the node is optimistic - require.Equal(t, firstInvalidRoot, service.ForkChoicer().CachedHeadRoot()) + // Check that forkchoice's head is the last invalid block imported. The + // store's headroot is the previous head (since the invalid block did + // not finish importing) one and that the node is optimistic + require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) headRoot, err := service.HeadRoot(ctx) require.NoError(t, err) require.Equal(t, firstInvalidRoot, bytesutil.ToBytes32(headRoot)) @@ -2276,7 +1616,7 @@ func TestStore_NoViableHead_NewPayload_DoublyLinkedTree(t *testing.T) { // INVALID from NewPayload, with LVH block 17. No head is viable. We check // that the node is optimistic and that we can actually import a block on top of // 17 and recover. -func TestStore_NoViableHead_NewPayload_Protoarray(t *testing.T) { +func TestStore_NoViableHead_NewPayload(t *testing.T) { params.SetupTestConfigCleanup(t) config := params.BeaconConfig() config.SlotsPerEpoch = 6 @@ -2292,7 +1632,7 @@ func TestStore_NoViableHead_NewPayload_Protoarray(t *testing.T) { WithDatabase(beaconDB), WithAttestationPool(attestations.NewPool()), WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), + WithForkChoiceStore(doublylinkedtree.New()), WithStateNotifier(&mock.MockStateNotifier{}), WithExecutionEngineCaller(mockEngine), WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), @@ -2436,7 +1776,7 @@ func TestStore_NoViableHead_NewPayload_Protoarray(t *testing.T) { // that the node is optimistic and that we can actually import a chain of blocks on top of // 12 and recover. Notice that it takes two epochs to fully recover, and we stay // optimistic for the whole time. -func TestStore_NoViableHead_Liveness_DoublyLinkedTree(t *testing.T) { +func TestStore_NoViableHead_Liveness(t *testing.T) { params.SetupTestConfigCleanup(t) config := params.BeaconConfig() config.SlotsPerEpoch = 6 @@ -2849,33 +2189,12 @@ func TestStore_NoViableHead_Liveness_Protoarray(t *testing.T) { type newForkChoicer func() forkchoice.ForkChoicer -func TestStore_NoViableHead_Reboot(t *testing.T) { - cases := []struct { - new newForkChoicer - name string - }{ - { - new: func() forkchoice.ForkChoicer { return doublylinkedtree.New() }, - name: "doublylinkedtree", - }, - { - new: func() forkchoice.ForkChoicer { return protoarray.New() }, - name: "protoarray", - }, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - noViableHead_Reboot(t, c.new) - }) - } -} - // See the description in #10777 and #10782 for the full setup // We sync optimistically a chain of blocks. Block 12 is the first block in Epoch // 2 (and the merge block in this sequence). Block 18 justifies it and Block 19 returns // INVALID from NewPayload, with LVH block 12. No head is viable. We check that // the node can reboot from this state -func noViableHead_Reboot(t *testing.T, newfc newForkChoicer) { +func noViableHead_Reboot(t *testing.T) { params.SetupTestConfigCleanup(t) config := params.BeaconConfig() config.SlotsPerEpoch = 6 @@ -2889,11 +2208,12 @@ func noViableHead_Reboot(t *testing.T, newfc newForkChoicer) { mockEngine := &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus} attSrv, err := attestations.NewService(ctx, &attestations.Config{}) require.NoError(t, err) + newfc := doublylinkedtree.New() opts := []Option{ WithDatabase(beaconDB), WithAttestationPool(attestations.NewPool()), WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(newfc()), + WithForkChoiceStore(newfc), WithStateNotifier(&mock.MockStateNotifier{}), WithExecutionEngineCaller(mockEngine), WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), @@ -3001,7 +2321,7 @@ func noViableHead_Reboot(t *testing.T, newfc newForkChoicer) { require.NoError(t, err) // HeadBlock returns no error when headroot == nil require.Equal(t, blk, nil) - service.cfg.ForkChoiceStore = newfc() + service.cfg.ForkChoiceStore = doublylinkedtree.New() require.NoError(t, service.StartFromSavedState(genesisState)) // Forkchoice has the genesisRoot loaded at startup @@ -3094,228 +2414,6 @@ func noViableHead_Reboot(t *testing.T, newfc newForkChoicer) { require.Equal(t, false, service.ForkChoicer().AllTipsAreInvalid()) } -// See the description in #10777 and #10782 for the full setup -// We sync optimistically a chain of blocks. Block 12 is the first block in Epoch -// 2 (and the merge block in this sequence). Block 18 justifies it and Block 19 returns -// INVALID from NewPayload, with LVH block 12. No head is viable. We check that -// the node can reboot from this state -func TestStore_NoViableHead_Reboot_Protoarray(t *testing.T) { - params.SetupTestConfigCleanup(t) - config := params.BeaconConfig() - config.SlotsPerEpoch = 6 - config.AltairForkEpoch = 1 - config.BellatrixForkEpoch = 2 - params.OverrideBeaconConfig(config) - - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - - mockEngine := &mockExecution.EngineClient{ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus, ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus} - attSrv, err := attestations.NewService(ctx, &attestations.Config{}) - require.NoError(t, err) - opts := []Option{ - WithDatabase(beaconDB), - WithAttestationPool(attestations.NewPool()), - WithStateGen(stategen.New(beaconDB)), - WithForkChoiceStore(protoarray.New()), - WithStateNotifier(&mock.MockStateNotifier{}), - WithExecutionEngineCaller(mockEngine), - WithProposerIdsCache(cache.NewProposerPayloadIDsCache()), - WithAttestationService(attSrv), - } - service, err := NewService(ctx, opts...) - require.NoError(t, err) - - genesisState, keys := util.DeterministicGenesisState(t, 64) - stateRoot, err := genesisState.HashTreeRoot(ctx) - require.NoError(t, err, "Could not hash genesis state") - genesis := blocks.NewGenesisBlock(stateRoot[:]) - wsb, err := consensusblocks.NewSignedBeaconBlock(genesis) - require.NoError(t, err) - genesisRoot, err := genesis.Block.HashTreeRoot() - require.NoError(t, err, "Could not get signing root") - require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb), "Could not save genesis block") - require.NoError(t, service.saveGenesisData(ctx, genesisState)) - - require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, genesisState, genesisRoot), "Could not save genesis state") - require.NoError(t, service.cfg.BeaconDB.SaveHeadBlockRoot(ctx, genesisRoot), "Could not save genesis state") - - for i := 1; i < 6; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.onBlock(ctx, wsb, root)) - } - - for i := 6; i < 12; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockAltair(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - } - - // import the merge block - driftGenesisTime(service, 12, 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), 12) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - lastValidRoot, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, lastValidRoot) - require.NoError(t, err) - // save the post state and the payload Hash of this block since it will - // be the LVH - validHeadState, err := service.HeadState(ctx) - require.NoError(t, err) - lvh := b.Block.Body.ExecutionPayload.BlockHash - validjc := validHeadState.CurrentJustifiedCheckpoint() - require.Equal(t, types.Epoch(0), validjc.Epoch) - - // import blocks 13 through 18 to justify 12 - for i := 13; i < 19; i++ { - driftGenesisTime(service, int64(i), 0) - st, err := service.HeadState(ctx) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.onBlock(ctx, wsb, root)) - } - // Check that we have justified the second epoch - jc := service.ForkChoicer().JustifiedCheckpoint() - require.Equal(t, types.Epoch(2), jc.Epoch) - - // import block 19 to find out that the whole chain 13--18 was in fact - // invalid - mockEngine = &mockExecution.EngineClient{ErrNewPayload: execution.ErrInvalidPayloadStatus, NewPayloadResp: lvh} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 19, 0) - st, err = service.HeadState(ctx) - require.NoError(t, err) - b, err = util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), 19) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.ErrorContains(t, "received an INVALID payload from execution engine", err) - - // Check that the headroot/state are not in DB and restart the node - blk, err := service.cfg.BeaconDB.HeadBlock(ctx) - require.NoError(t, err) // HeadBlock returns no error when headroot == nil - require.Equal(t, blk, nil) - - service.cfg.ForkChoiceStore = protoarray.New() - require.NoError(t, service.StartFromSavedState(genesisState)) - - require.Equal(t, genesisRoot, service.ensureRootNotZeros(service.ForkChoicer().CachedHeadRoot())) - // Service's store has the finalized state as headRoot - headRoot, err := service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, genesisRoot, bytesutil.ToBytes32(headRoot)) - optimistic, err := service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, false, optimistic) - require.Equal(t, false, service.ForkChoicer().AllTipsAreInvalid()) - - // Check that the node's justified checkpoint does not agree with the - // last valid state's justified checkpoint - sjc := service.CurrentJustifiedCheckpt() - require.Equal(t, types.Epoch(2), sjc.Epoch) - - // import another block based on the last valid head state - mockEngine = &mockExecution.EngineClient{} - service.cfg.ExecutionEngineCaller = mockEngine - driftGenesisTime(service, 20, 0) - b, err = util.GenerateFullBlockBellatrix(validHeadState, keys, &util.BlockGenConfig{}, 20) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err = b.Block.HashTreeRoot() - require.NoError(t, err) - require.NoError(t, service.onBlock(ctx, wsb, root)) - // Check that the head is still INVALID and the node is optimistic - require.Equal(t, genesisRoot, service.ensureRootNotZeros(service.ForkChoicer().CachedHeadRoot())) - headRoot, err = service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, genesisRoot, bytesutil.ToBytes32(headRoot)) - - optimistic, err = service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, true, optimistic) - require.Equal(t, true, service.ForkChoicer().AllTipsAreInvalid()) - st, err = service.cfg.StateGen.StateByRoot(ctx, root) - require.NoError(t, err) - // Import blocks 21--23 - for i := 21; i < 24; i++ { - driftGenesisTime(service, int64(i), 0) - require.NoError(t, err) - b, err := util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), types.Slot(i)) - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err := b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - st, err = service.cfg.StateGen.StateByRoot(ctx, root) - require.NoError(t, err) - } - // Head should still be INVALID and the node is optimistic - require.Equal(t, genesisRoot, service.ensureRootNotZeros(service.ForkChoicer().CachedHeadRoot())) - headRoot, err = service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, genesisRoot, bytesutil.ToBytes32(headRoot)) - - optimistic, err = service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, true, optimistic) - require.Equal(t, true, service.ForkChoicer().AllTipsAreInvalid()) - - // Import block 24, it should justify Epoch 3 and become HEAD, the node - // recovers - driftGenesisTime(service, 24, 0) - b, err = util.GenerateFullBlockBellatrix(st, keys, util.DefaultBlockGenConfig(), 24) - require.NoError(t, err) - wsb, err = consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - root, err = b.Block.HashTreeRoot() - require.NoError(t, err) - err = service.onBlock(ctx, wsb, root) - require.NoError(t, err) - require.Equal(t, root, service.ForkChoicer().CachedHeadRoot()) - headRoot, err = service.HeadRoot(ctx) - require.NoError(t, err) - require.Equal(t, root, bytesutil.ToBytes32(headRoot)) - - sjc = service.CurrentJustifiedCheckpt() - require.Equal(t, types.Epoch(3), sjc.Epoch) - optimistic, err = service.IsOptimistic(ctx) - require.NoError(t, err) - require.Equal(t, false, optimistic) - require.Equal(t, false, service.ForkChoicer().AllTipsAreInvalid()) -} - func TestOnBlock_HandleBlockAttestations(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) diff --git a/beacon-chain/blockchain/receive_block_test.go b/beacon-chain/blockchain/receive_block_test.go index d4ca920a7748..45b9c3f8509e 100644 --- a/beacon-chain/blockchain/receive_block_test.go +++ b/beacon-chain/blockchain/receive_block_test.go @@ -8,7 +8,7 @@ import ( blockchainTesting "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/voluntaryexits" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" @@ -129,7 +129,7 @@ func TestService_ReceiveBlock(t *testing.T) { opts := []Option{ WithDatabase(beaconDB), - WithForkChoiceStore(protoarray.New()), + WithForkChoiceStore(doublylinkedtree.New()), WithAttestationPool(attestations.NewPool()), WithExitPool(voluntaryexits.NewPool()), WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true}), @@ -168,7 +168,7 @@ func TestService_ReceiveBlockUpdateHead(t *testing.T) { require.NoError(t, beaconDB.SaveState(ctx, genesis, genesisBlockRoot)) opts := []Option{ WithDatabase(beaconDB), - WithForkChoiceStore(protoarray.New()), + WithForkChoiceStore(doublylinkedtree.New()), WithAttestationPool(attestations.NewPool()), WithExitPool(voluntaryexits.NewPool()), WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true}), @@ -245,7 +245,7 @@ func TestService_ReceiveBlockBatch(t *testing.T) { beaconDB := testDB.SetupDB(t) opts := []Option{ WithDatabase(beaconDB), - WithForkChoiceStore(protoarray.New()), + WithForkChoiceStore(doublylinkedtree.New()), WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true}), WithStateGen(stategen.New(beaconDB)), } diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index a2c595a31ed2..da9afb252c92 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -21,7 +21,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution" mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/p2p" state_native "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native" @@ -409,25 +408,6 @@ func TestChainService_SaveHeadNoDB(t *testing.T) { } } -func TestHasBlock_ForkChoiceAndDB_ProtoArray(t *testing.T) { - ctx := context.Background() - beaconDB := testDB.SetupDB(t) - s := &Service{ - cfg: &config{ForkChoiceStore: protoarray.New(), BeaconDB: beaconDB}, - } - b := util.NewBeaconBlock() - r, err := b.Block.HashTreeRoot() - require.NoError(t, err) - beaconState, err := util.NewBeaconState() - require.NoError(t, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(b) - require.NoError(t, err) - require.NoError(t, s.insertBlockToForkchoiceStore(ctx, wsb.Block(), r, beaconState)) - - assert.Equal(t, false, s.hasBlock(ctx, [32]byte{}), "Should not have block") - assert.Equal(t, true, s.hasBlock(ctx, r), "Should have block") -} - func TestHasBlock_ForkChoiceAndDB_DoublyLinkedTree(t *testing.T) { ctx := context.Background() beaconDB := testDB.SetupDB(t) @@ -499,27 +479,6 @@ func BenchmarkHasBlockDB(b *testing.B) { } } -func BenchmarkHasBlockForkChoiceStore_ProtoArray(b *testing.B) { - ctx := context.Background() - beaconDB := testDB.SetupDB(b) - s := &Service{ - cfg: &config{ForkChoiceStore: protoarray.New(), BeaconDB: beaconDB}, - } - blk := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{Body: ðpb.BeaconBlockBody{}}} - r, err := blk.Block.HashTreeRoot() - require.NoError(b, err) - bs := ðpb.BeaconState{FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}, CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)}} - beaconState, err := state_native.InitializeFromProtoPhase0(bs) - require.NoError(b, err) - wsb, err := consensusblocks.NewSignedBeaconBlock(blk) - require.NoError(b, err) - require.NoError(b, s.insertBlockToForkchoiceStore(ctx, wsb.Block(), r, beaconState)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - require.Equal(b, true, s.cfg.ForkChoiceStore.HasNode(r), "Block is not in fork choice store") - } -} func BenchmarkHasBlockForkChoiceStore_DoublyLinkedTree(b *testing.B) { ctx := context.Background() beaconDB := testDB.SetupDB(b) diff --git a/beacon-chain/blockchain/weak_subjectivity_checks_test.go b/beacon-chain/blockchain/weak_subjectivity_checks_test.go index ab0d51fcb560..52771002f76c 100644 --- a/beacon-chain/blockchain/weak_subjectivity_checks_test.go +++ b/beacon-chain/blockchain/weak_subjectivity_checks_test.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" testDB "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" @@ -72,7 +72,7 @@ func TestService_VerifyWeakSubjectivityRoot(t *testing.T) { wv, err := NewWeakSubjectivityVerifier(tt.checkpt, beaconDB) require.Equal(t, !tt.disabled, wv.enabled) require.NoError(t, err) - fcs := protoarray.New() + fcs := doublylinkedtree.New() s := &Service{ cfg: &config{BeaconDB: beaconDB, WeakSubjectivityCheckpt: tt.checkpt, ForkChoiceStore: fcs}, wsVerifier: wv, diff --git a/beacon-chain/forkchoice/protoarray/BUILD.bazel b/beacon-chain/forkchoice/protoarray/BUILD.bazel deleted file mode 100644 index 0c8752349ee4..000000000000 --- a/beacon-chain/forkchoice/protoarray/BUILD.bazel +++ /dev/null @@ -1,79 +0,0 @@ -load("@prysm//tools/go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "doc.go", - "errors.go", - "helpers.go", - "metrics.go", - "node.go", - "on_tick.go", - "optimistic_sync.go", - "proposer_boost.go", - "store.go", - "types.go", - "unrealized_justification.go", - ], - importpath = "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray", - visibility = [ - "//beacon-chain:__subpackages__", - "//testing/spectest:__subpackages__", - ], - deps = [ - "//beacon-chain/core/blocks:go_default_library", - "//beacon-chain/core/epoch/precompute:go_default_library", - "//beacon-chain/forkchoice:go_default_library", - "//beacon-chain/forkchoice/types:go_default_library", - "//beacon-chain/state:go_default_library", - "//config/features:go_default_library", - "//config/fieldparams:go_default_library", - "//config/params:go_default_library", - "//consensus-types/primitives:go_default_library", - "//encoding/bytesutil:go_default_library", - "//math:go_default_library", - "//proto/eth/v1:go_default_library", - "//proto/prysm/v1alpha1:go_default_library", - "//runtime/version:go_default_library", - "//time/slots:go_default_library", - "@com_github_pkg_errors//:go_default_library", - "@com_github_prometheus_client_golang//prometheus:go_default_library", - "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", - "@com_github_sirupsen_logrus//:go_default_library", - "@io_opencensus_go//trace:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "ffg_update_test.go", - "helpers_test.go", - "no_vote_test.go", - "node_test.go", - "on_tick_test.go", - "optimistic_sync_test.go", - "proposer_boost_test.go", - "store_test.go", - "unrealized_justification_test.go", - "vote_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//beacon-chain/forkchoice:go_default_library", - "//beacon-chain/forkchoice/types:go_default_library", - "//beacon-chain/state:go_default_library", - "//beacon-chain/state/state-native:go_default_library", - "//config/features:go_default_library", - "//config/params:go_default_library", - "//consensus-types/blocks:go_default_library", - "//consensus-types/primitives:go_default_library", - "//crypto/hash:go_default_library", - "//encoding/bytesutil:go_default_library", - "//proto/engine/v1:go_default_library", - "//proto/prysm/v1alpha1:go_default_library", - "//testing/assert:go_default_library", - "//testing/require:go_default_library", - "//testing/util:go_default_library", - ], -) diff --git a/beacon-chain/forkchoice/protoarray/doc.go b/beacon-chain/forkchoice/protoarray/doc.go deleted file mode 100644 index eb091f31b3fe..000000000000 --- a/beacon-chain/forkchoice/protoarray/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -/* -Package protoarray implements proto array fork choice as outlined: -https://github.com/protolambda/lmd-ghost#array-based-stateful-dag-proto_array -This was motivated by the original implementation by Sigma Prime here: -https://github.com/sigp/lighthouse/pull/804 -*/ -package protoarray diff --git a/beacon-chain/forkchoice/protoarray/errors.go b/beacon-chain/forkchoice/protoarray/errors.go deleted file mode 100644 index 5bfceaf984ba..000000000000 --- a/beacon-chain/forkchoice/protoarray/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package protoarray - -import "errors" - -var errUnknownFinalizedRoot = errors.New("unknown finalized root") -var errUnknownJustifiedRoot = errors.New("unknown justified root") -var errInvalidNodeIndex = errors.New("node index is invalid") -var errInvalidFinalizedNode = errors.New("invalid finalized block on chain") -var ErrUnknownNodeRoot = errors.New("unknown block root") -var errInvalidJustifiedIndex = errors.New("justified index is invalid") -var errInvalidBestDescendantIndex = errors.New("best descendant index is invalid") -var errInvalidParentDelta = errors.New("parent delta is invalid") -var errInvalidNodeDelta = errors.New("node delta is invalid") -var errInvalidDeltaLength = errors.New("delta length is invalid") -var errInvalidOptimisticStatus = errors.New("invalid optimistic status") -var errInvalidNilCheckpoint = errors.New("invalid nil checkpoint") -var errInvalidUnrealizedJustifiedEpoch = errors.New("invalid unrealized justified epoch") -var errInvalidUnrealizedFinalizedEpoch = errors.New("invalid unrealized finalized epoch") -var errNilBlockHeader = errors.New("invalid nil block header") diff --git a/beacon-chain/forkchoice/protoarray/ffg_update_test.go b/beacon-chain/forkchoice/protoarray/ffg_update_test.go deleted file mode 100644 index f5499de2f584..000000000000 --- a/beacon-chain/forkchoice/protoarray/ffg_update_test.go +++ /dev/null @@ -1,280 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" - state_native "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/state-native" - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -// prepareForkchoiceState prepares a beacon State with the given data to mock -// insert into forkchoice -func prepareForkchoiceState( - _ context.Context, - slot types.Slot, - blockRoot [32]byte, - parentRoot [32]byte, - payloadHash [32]byte, - justifiedEpoch types.Epoch, - finalizedEpoch types.Epoch, -) (state.BeaconState, [32]byte, error) { - blockHeader := ðpb.BeaconBlockHeader{ - ParentRoot: parentRoot[:], - } - - executionHeader := &enginev1.ExecutionPayloadHeader{ - BlockHash: payloadHash[:], - } - - justifiedCheckpoint := ðpb.Checkpoint{ - Epoch: justifiedEpoch, - } - - finalizedCheckpoint := ðpb.Checkpoint{ - Epoch: finalizedEpoch, - } - - base := ðpb.BeaconStateBellatrix{ - Slot: slot, - RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector), - BlockRoots: make([][]byte, 1), - CurrentJustifiedCheckpoint: justifiedCheckpoint, - FinalizedCheckpoint: finalizedCheckpoint, - LatestExecutionPayloadHeader: executionHeader, - LatestBlockHeader: blockHeader, - } - - base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...) - st, err := state_native.InitializeFromProtoBellatrix(base) - return st, blockRoot, err -} -func TestFFGUpdates_OneBranch(t *testing.T) { - balances := []uint64{1, 1} - f := setup(0, 0) - ctx := context.Background() - - // The head should always start at the finalized block. - r, err := f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, params.BeaconConfig().ZeroHash, r, "Incorrect head with genesis") - - // Define the following tree: - // 0 <- justified: 0, finalized: 0 - // | - // 1 <- justified: 0, finalized: 0 - // | - // 2 <- justified: 1, finalized: 0 - // | - // 3 <- justified: 2, finalized: 1 - st, blkRoot, err := prepareForkchoiceState(context.Background(), 1, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(2), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(3), indexToHash(2), params.BeaconConfig().ZeroHash, 2, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - - // With starting justified epoch at 0, the head should be 3: - // 0 <- start - // | - // 1 - // | - // 2 - // | - // 3 <- head - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(3), r, "Incorrect head for with justified epoch at 0") - - // With starting justified epoch at 1, the head should be 2: - // 0 - // | - // 1 <- start - // | - // 2 <- head - // | - // 3 - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Root: indexToHash(1), Epoch: 1} - f.store.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Root: indexToHash(0), Epoch: 0} - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head with justified epoch at 1") - - // With starting justified epoch at 2, the head should be 3: - // 0 - // | - // 1 - // | - // 2 <- start - // | - // 3 <- head - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Root: indexToHash(3), Epoch: 2} - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(3), r, "Incorrect head with justified epoch at 2") -} - -func TestFFGUpdates_TwoBranches(t *testing.T) { - balances := []uint64{1, 1} - f := setup(0, 0) - ctx := context.Background() - - r, err := f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, params.BeaconConfig().ZeroHash, r, "Incorrect head with genesis") - - // Define the following tree: - // 0 - // / \ - // justified: 0, finalized: 0 -> 1 2 <- justified: 0, finalized: 0 - // | | - // justified: 1, finalized: 0 -> 3 4 <- justified: 0, finalized: 0 - // | | - // justified: 1, finalized: 0 -> 5 6 <- justified: 0, finalized: 0 - // | | - // justified: 1, finalized: 0 -> 7 8 <- justified: 1, finalized: 0 - // | | - // justified: 2, finalized: 0 -> 9 10 <- justified: 2, finalized: 0 - // Left branch. - st, blkRoot, err := prepareForkchoiceState(context.Background(), 1, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(3), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(5), indexToHash(3), params.BeaconConfig().ZeroHash, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(7), indexToHash(5), params.BeaconConfig().ZeroHash, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(9), indexToHash(7), params.BeaconConfig().ZeroHash, 2, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - // Right branch. - st, blkRoot, err = prepareForkchoiceState(context.Background(), 1, indexToHash(2), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(4), indexToHash(2), params.BeaconConfig().ZeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(6), indexToHash(4), params.BeaconConfig().ZeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(8), indexToHash(6), params.BeaconConfig().ZeroHash, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - st, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(10), indexToHash(8), params.BeaconConfig().ZeroHash, 2, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, blkRoot)) - - // With start at 0, the head should be 10: - // 0 <-- start - // / \ - // 1 2 - // | | - // 3 4 - // | | - // 5 6 - // | | - // 7 8 - // | | - // 9 10 <-- head - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head with justified epoch at 0") - - // Add a vote to 1: - // 0 - // / \ - // +1 vote -> 1 2 - // | | - // 3 4 - // | | - // 5 6 - // | | - // 7 8 - // | | - // 9 10 - f.ProcessAttestation(context.Background(), []uint64{0}, indexToHash(1), 0) - - // With the additional vote to the left branch, the head should be 9: - // 0 <-- start - // / \ - // 1 2 - // | | - // 3 4 - // | | - // 5 6 - // | | - // 7 8 - // | | - // head -> 9 10 - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(9), r, "Incorrect head with justified epoch at 0") - - // Add a vote to 2: - // 0 - // / \ - // 1 2 <- +1 vote - // | | - // 3 4 - // | | - // 5 6 - // | | - // 7 8 - // | | - // 9 10 - f.ProcessAttestation(context.Background(), []uint64{1}, indexToHash(2), 0) - - // With the additional vote to the right branch, the head should be 10: - // 0 <-- start - // / \ - // 1 2 - // | | - // 3 4 - // | | - // 5 6 - // | | - // 7 8 - // | | - // 9 10 <-- head - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head with justified epoch at 0") - - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 1, Root: indexToHash(1)} - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(7), r, "Incorrect head with justified epoch at 0") -} - -func setup(justifiedEpoch, finalizedEpoch types.Epoch) *ForkChoice { - f := New() - f.store.nodesIndices[params.BeaconConfig().ZeroHash] = 0 - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: justifiedEpoch, Root: params.BeaconConfig().ZeroHash} - f.store.bestJustifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: justifiedEpoch, Root: params.BeaconConfig().ZeroHash} - f.store.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: finalizedEpoch, Root: params.BeaconConfig().ZeroHash} - f.store.nodes = append(f.store.nodes, &Node{ - slot: 0, - root: params.BeaconConfig().ZeroHash, - parent: NonExistentNode, - justifiedEpoch: justifiedEpoch, - finalizedEpoch: finalizedEpoch, - bestChild: NonExistentNode, - bestDescendant: NonExistentNode, - weight: 0, - }) - return f -} diff --git a/beacon-chain/forkchoice/protoarray/helpers.go b/beacon-chain/forkchoice/protoarray/helpers.go deleted file mode 100644 index 7ed5535e8bea..000000000000 --- a/beacon-chain/forkchoice/protoarray/helpers.go +++ /dev/null @@ -1,109 +0,0 @@ -package protoarray - -import ( - "context" - - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - pmath "github.com/prysmaticlabs/prysm/v3/math" - "go.opencensus.io/trace" -) - -// This computes validator balance delta from validator votes. -// It returns a list of deltas that represents the difference between old -// balances and new balances. This function assumes the caller holds a lock in -// Store.nodesLock and Store.votesLock -func computeDeltas( - ctx context.Context, - count int, - blockIndices map[[32]byte]uint64, - votes []Vote, - oldBalances, newBalances []uint64, - slashedIndices map[types.ValidatorIndex]bool, -) ([]int, []Vote, error) { - ctx, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.computeDeltas") - defer span.End() - - deltas := make([]int, count) - - for validatorIndex, vote := range votes { - // Skip if validator has been slashed - if slashedIndices[types.ValidatorIndex(validatorIndex)] { - continue - } - oldBalance := uint64(0) - newBalance := uint64(0) - - // Skip if validator has never voted for current root and next root (i.e. if the - // votes are zero hash aka genesis block), there's nothing to compute. - if vote.currentRoot == params.BeaconConfig().ZeroHash && vote.nextRoot == params.BeaconConfig().ZeroHash { - continue - } - - // If the validator index did not exist in `oldBalance` or `newBalance` list above, the balance is just 0. - if validatorIndex < len(oldBalances) { - oldBalance = oldBalances[validatorIndex] - } - if validatorIndex < len(newBalances) { - newBalance = newBalances[validatorIndex] - } - - // Perform delta only if the validator's balance or vote has changed. - if vote.currentRoot != vote.nextRoot || oldBalance != newBalance { - // Ignore the vote if it's not known in `blockIndices`, - // that means we have not seen the block before. - nextDeltaIndex, ok := blockIndices[vote.nextRoot] - if ok { - // Protection against out of bound, the `nextDeltaIndex` which defines - // the block location in the dag can not exceed the total `delta` length. - if nextDeltaIndex >= uint64(len(deltas)) { - return nil, nil, errInvalidNodeDelta - } - delta, err := pmath.Int(newBalance) - if err != nil { - return nil, nil, err - } - deltas[nextDeltaIndex] += delta - } - - currentDeltaIndex, ok := blockIndices[vote.currentRoot] - if ok { - // Protection against out of bound (same as above) - if currentDeltaIndex >= uint64(len(deltas)) { - return nil, nil, errInvalidNodeDelta - } - delta, err := pmath.Int(oldBalance) - if err != nil { - return nil, nil, err - } - deltas[currentDeltaIndex] -= delta - } - } - - // Rotate the validator vote. - vote.currentRoot = vote.nextRoot - votes[validatorIndex] = vote - } - - return deltas, votes, nil -} - -// This return a copy of the proto array node object. -func copyNode(node *Node) *Node { - if node == nil { - return &Node{} - } - - return &Node{ - slot: node.slot, - root: node.root, - parent: node.parent, - payloadHash: node.payloadHash, - justifiedEpoch: node.justifiedEpoch, - finalizedEpoch: node.finalizedEpoch, - weight: node.weight, - bestChild: node.bestChild, - bestDescendant: node.bestDescendant, - status: node.status, - } -} diff --git a/beacon-chain/forkchoice/protoarray/helpers_test.go b/beacon-chain/forkchoice/protoarray/helpers_test.go deleted file mode 100644 index c17ca488ee24..000000000000 --- a/beacon-chain/forkchoice/protoarray/helpers_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package protoarray - -import ( - "context" - "encoding/binary" - "testing" - - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/crypto/hash" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestComputeDelta_ZeroHash(t *testing.T) { - validatorCount := uint64(16) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := make([]uint64, 0) - newBalances := make([]uint64, 0) - - for i := uint64(0); i < validatorCount; i++ { - indices[indexToHash(i)] = i - votes = append(votes, Vote{params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0}) - oldBalances = append(oldBalances, 0) - newBalances = append(newBalances, 0) - } - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, int(validatorCount), len(delta)) - - for _, d := range delta { - assert.Equal(t, 0, d) - } - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_AllVoteTheSame(t *testing.T) { - validatorCount := uint64(16) - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := make([]uint64, 0) - newBalances := make([]uint64, 0) - - for i := uint64(0); i < validatorCount; i++ { - indices[indexToHash(i)] = i - votes = append(votes, Vote{params.BeaconConfig().ZeroHash, indexToHash(0), 0}) - oldBalances = append(oldBalances, balance) - newBalances = append(newBalances, balance) - } - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, int(validatorCount), len(delta)) - - for i, d := range delta { - if i == 0 { - assert.Equal(t, balance*validatorCount, uint64(d)) - } else { - assert.Equal(t, 0, d) - } - } - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_DifferentVotes(t *testing.T) { - validatorCount := uint64(16) - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := make([]uint64, 0) - newBalances := make([]uint64, 0) - - for i := uint64(0); i < validatorCount; i++ { - indices[indexToHash(i)] = i - votes = append(votes, Vote{params.BeaconConfig().ZeroHash, indexToHash(i), 0}) - oldBalances = append(oldBalances, balance) - newBalances = append(newBalances, balance) - } - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, int(validatorCount), len(delta)) - - for _, d := range delta { - assert.Equal(t, balance, uint64(d)) - } - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_MovingVotes(t *testing.T) { - validatorCount := uint64(16) - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := make([]uint64, 0) - newBalances := make([]uint64, 0) - - lastIndex := uint64(len(indices) - 1) - for i := uint64(0); i < validatorCount; i++ { - indices[indexToHash(i)] = i - votes = append(votes, Vote{indexToHash(0), indexToHash(lastIndex), 0}) - oldBalances = append(oldBalances, balance) - newBalances = append(newBalances, balance) - } - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, int(validatorCount), len(delta)) - - for i, d := range delta { - if i == 0 { - assert.Equal(t, -int(balance*validatorCount), d, "First root should have negative delta") - } else if i == int(lastIndex) { - assert.Equal(t, int(balance*validatorCount), d, "Last root should have positive delta") - } else { - assert.Equal(t, 0, d) - } - } - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_MoveOutOfTree(t *testing.T) { - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := []uint64{balance, balance} - newBalances := []uint64{balance, balance} - - indices[indexToHash(1)] = 0 - - votes = append(votes, - Vote{indexToHash(1), params.BeaconConfig().ZeroHash, 0}, - Vote{indexToHash(1), [32]byte{'A'}, 0}) - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, 1, len(delta)) - assert.Equal(t, 0-2*int(balance), delta[0]) - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_ChangingBalances(t *testing.T) { - oldBalance := uint64(32) - newBalance := oldBalance * 2 - validatorCount := uint64(16) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := make([]uint64, 0) - newBalances := make([]uint64, 0) - - indices[indexToHash(1)] = 0 - - for i := uint64(0); i < validatorCount; i++ { - indices[indexToHash(i)] = i - votes = append(votes, Vote{indexToHash(0), indexToHash(1), 0}) - oldBalances = append(oldBalances, oldBalance) - newBalances = append(newBalances, newBalance) - } - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, 16, len(delta)) - - for i, d := range delta { - if i == 0 { - assert.Equal(t, -int(oldBalance*validatorCount), d, "First root should have negative delta") - } else if i == 1 { - assert.Equal(t, int(newBalance*validatorCount), d, "Last root should have positive delta") - } else { - assert.Equal(t, 0, d) - } - } - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_ValidatorAppear(t *testing.T) { - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := []uint64{balance} - newBalances := []uint64{balance, balance} - - indices[indexToHash(1)] = 0 - indices[indexToHash(2)] = 1 - - votes = append(votes, - Vote{indexToHash(1), indexToHash(2), 0}, - Vote{indexToHash(1), indexToHash(2), 0}) - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, 2, len(delta)) - assert.Equal(t, 0-int(balance), delta[0]) - assert.Equal(t, 2*int(balance), delta[1]) - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func TestComputeDelta_ValidatorDisappears(t *testing.T) { - balance := uint64(32) - indices := make(map[[32]byte]uint64) - votes := make([]Vote, 0) - oldBalances := []uint64{balance, balance} - newBalances := []uint64{balance} - - indices[indexToHash(1)] = 0 - indices[indexToHash(2)] = 1 - - votes = append(votes, - Vote{indexToHash(1), indexToHash(2), 0}, - Vote{indexToHash(1), indexToHash(2), 0}) - - slashedIndices := make(map[types.ValidatorIndex]bool) - delta, _, err := computeDeltas(context.Background(), len(indices), indices, votes, oldBalances, newBalances, slashedIndices) - require.NoError(t, err) - assert.Equal(t, 2, len(delta)) - assert.Equal(t, 0-2*int(balance), delta[0]) - assert.Equal(t, int(balance), delta[1]) - - for _, vote := range votes { - assert.Equal(t, vote.currentRoot, vote.nextRoot, "The vote should not have changed") - } -} - -func indexToHash(i uint64) [32]byte { - var b [8]byte - binary.LittleEndian.PutUint64(b[:], i) - return hash.Hash(b[:]) -} diff --git a/beacon-chain/forkchoice/protoarray/metrics.go b/beacon-chain/forkchoice/protoarray/metrics.go deleted file mode 100644 index 94ea168e1be1..000000000000 --- a/beacon-chain/forkchoice/protoarray/metrics.go +++ /dev/null @@ -1,54 +0,0 @@ -package protoarray - -import ( - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" - "github.com/sirupsen/logrus" -) - -var ( - log = logrus.WithField("prefix", "forkchoice-protoarray") - - headSlotNumber = promauto.NewGauge( - prometheus.GaugeOpts{ - Name: "proto_array_head_slot", - Help: "The slot number of the current head.", - }, - ) - nodeCount = promauto.NewGauge( - prometheus.GaugeOpts{ - Name: "proto_array_node_count", - Help: "The number of nodes in the DAG array based store structure.", - }, - ) - headChangesCount = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "proto_array_head_changed_count", - Help: "The number of times head changes.", - }, - ) - calledHeadCount = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "proto_array_head_requested_count", - Help: "The number of times someone called head.", - }, - ) - processedBlockCount = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "proto_array_block_processed_count", - Help: "The number of times a block is processed for fork choice.", - }, - ) - processedAttestationCount = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "proto_array_attestation_processed_count", - Help: "The number of times an attestation is processed for fork choice.", - }, - ) - prunedCount = promauto.NewCounter( - prometheus.CounterOpts{ - Name: "proto_array_pruned_count", - Help: "The number of times pruning happened.", - }, - ) -) diff --git a/beacon-chain/forkchoice/protoarray/no_vote_test.go b/beacon-chain/forkchoice/protoarray/no_vote_test.go deleted file mode 100644 index 404c23dfd4c2..000000000000 --- a/beacon-chain/forkchoice/protoarray/no_vote_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - "github.com/prysmaticlabs/prysm/v3/config/params" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestNoVote_CanFindHead(t *testing.T) { - balances := make([]uint64, 16) - f := setup(1, 1) - ctx := context.Background() - - // The head should always start at the finalized block. - r, err := f.Head(context.Background(), balances) - require.NoError(t, err) - if r != params.BeaconConfig().ZeroHash { - t.Errorf("Incorrect head with genesis") - } - - // Insert block 2 into the tree and verify head is at 2: - // 0 - // / - // 2 <- head - state, blkRoot, err := prepareForkchoiceState(context.Background(), 0, indexToHash(2), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Insert block 1 into the tree and verify head is still at 2: - // 0 - // / \ - // head -> 2 1 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Insert block 3 into the tree and verify head is still at 2: - // 0 - // / \ - // head -> 2 1 - // | - // 3 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(3), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Insert block 4 into the tree and verify head is at 4: - // 0 - // / \ - // 2 1 - // | | - // head -> 4 3 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(4), indexToHash(2), params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(4), r, "Incorrect head for with justified epoch at 1") - - // Insert block 5 with justified epoch of 2, verify head is 5 - // 0 - // / \ - // 2 1 - // | | - // head -> 4 3 - // | - // 5 <- justified epoch = 2 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(5), indexToHash(4), params.BeaconConfig().ZeroHash, 2, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(5), r, "Incorrect head for with justified epoch at 2") - - // Insert block 6 with justified epoch of 2, verify head is at 6. - // 0 - // / \ - // 2 1 - // | | - // 4 3 - // | - // 5 - // | - // 6 <- head - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(6), indexToHash(5), params.BeaconConfig().ZeroHash, 2, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(6), r, "Incorrect head for with justified epoch at 2") -} diff --git a/beacon-chain/forkchoice/protoarray/node.go b/beacon-chain/forkchoice/protoarray/node.go deleted file mode 100644 index 1424e6fa50f1..000000000000 --- a/beacon-chain/forkchoice/protoarray/node.go +++ /dev/null @@ -1,50 +0,0 @@ -package protoarray - -import ( - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" -) - -// Slot of the fork choice node. -func (n *Node) Slot() types.Slot { - return n.slot -} - -// Root of the fork choice node. -func (n *Node) Root() [32]byte { - return n.root -} - -// Parent of the fork choice node. -func (n *Node) Parent() uint64 { - return n.parent -} - -// JustifiedEpoch of the fork choice node. -func (n *Node) JustifiedEpoch() types.Epoch { - return n.justifiedEpoch -} - -// FinalizedEpoch of the fork choice node. -func (n *Node) FinalizedEpoch() types.Epoch { - return n.finalizedEpoch -} - -// Weight of the fork choice node. -func (n *Node) Weight() uint64 { - return n.weight -} - -// BestChild of the fork choice node. -func (n *Node) BestChild() uint64 { - return n.bestChild -} - -// BestDescendant of the fork choice node. -func (n *Node) BestDescendant() uint64 { - return n.bestDescendant -} - -// VotedFraction is not implemented for protoarray -func (*ForkChoice) VotedFraction(_ [32]byte) (uint64, error) { - return 0, nil -} diff --git a/beacon-chain/forkchoice/protoarray/node_test.go b/beacon-chain/forkchoice/protoarray/node_test.go deleted file mode 100644 index f62046d7f367..000000000000 --- a/beacon-chain/forkchoice/protoarray/node_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package protoarray - -import ( - "testing" - - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestNode_Getters(t *testing.T) { - slot := types.Slot(100) - root := [32]byte{'a'} - parent := uint64(10) - jEpoch := types.Epoch(20) - fEpoch := types.Epoch(30) - weight := uint64(10000) - bestChild := uint64(5) - bestDescendant := uint64(4) - n := &Node{ - slot: slot, - root: root, - parent: parent, - justifiedEpoch: jEpoch, - finalizedEpoch: fEpoch, - weight: weight, - bestChild: bestChild, - bestDescendant: bestDescendant, - } - - require.Equal(t, slot, n.Slot()) - require.Equal(t, root, n.Root()) - require.Equal(t, parent, n.Parent()) - require.Equal(t, jEpoch, n.JustifiedEpoch()) - require.Equal(t, fEpoch, n.FinalizedEpoch()) - require.Equal(t, weight, n.Weight()) - require.Equal(t, bestChild, n.BestChild()) - require.Equal(t, bestDescendant, n.BestDescendant()) -} diff --git a/beacon-chain/forkchoice/protoarray/on_tick.go b/beacon-chain/forkchoice/protoarray/on_tick.go deleted file mode 100644 index 7604afc07b47..000000000000 --- a/beacon-chain/forkchoice/protoarray/on_tick.go +++ /dev/null @@ -1,74 +0,0 @@ -package protoarray - -import ( - "context" - - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v3/config/features" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/time/slots" -) - -// NewSlot mimics the implementation of `on_tick` in fork choice consensus spec. -// It resets the proposer boost root in fork choice, and it updates store's justified checkpoint -// if a better checkpoint on the store's finalized checkpoint chain. -// This should only be called at the start of every slot interval. -// -// Spec pseudocode definition: -// # Reset store.proposer_boost_root if this is a new slot -// if current_slot > previous_slot: -// store.proposer_boost_root = Root() -// -// # Not a new epoch, return -// if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): -// return -// -// # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain -// if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: -// finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) -// ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) -// if ancestor_at_finalized_slot == store.finalized_checkpoint.root: -// store.justified_checkpoint = store.best_justified_checkpoint -func (f *ForkChoice) NewSlot(ctx context.Context, slot types.Slot) error { - // Reset proposer boost root - if err := f.ResetBoostedProposerRoot(ctx); err != nil { - return errors.Wrap(err, "could not reset boosted proposer root in fork choice") - } - - // Return if it's not a new epoch. - if !slots.IsEpochStart(slot) { - return nil - } - - // Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - f.store.checkpointsLock.RLock() - bjcp := f.store.bestJustifiedCheckpoint - jcp := f.store.justifiedCheckpoint - fcp := f.store.finalizedCheckpoint - f.store.checkpointsLock.RUnlock() - if bjcp.Epoch > jcp.Epoch { - finalizedSlot, err := slots.EpochStart(fcp.Epoch) - if err != nil { - return err - } - - // We check that the best justified checkpoint is a descendant of the finalized checkpoint. - // This should always happen as forkchoice enforces that every node is a descendant of the - // finalized checkpoint. This check is here for additional security, consider removing the extra - // loop call here. - r, err := f.AncestorRoot(ctx, bjcp.Root, finalizedSlot) - if err != nil { - return err - } - if r == fcp.Root { - f.store.checkpointsLock.Lock() - f.store.prevJustifiedCheckpoint = jcp - f.store.justifiedCheckpoint = bjcp - f.store.checkpointsLock.Unlock() - } - } - if !features.Get().DisablePullTips { - f.updateUnrealizedCheckpoints() - } - return nil -} diff --git a/beacon-chain/forkchoice/protoarray/on_tick_test.go b/beacon-chain/forkchoice/protoarray/on_tick_test.go deleted file mode 100644 index b1aa0b1fb228..000000000000 --- a/beacon-chain/forkchoice/protoarray/on_tick_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestStore_NewSlot(t *testing.T) { - ctx := context.Background() - bj := [32]byte{'z'} - - type args struct { - slot types.Slot - finalized *forkchoicetypes.Checkpoint - justified *forkchoicetypes.Checkpoint - bestJustified *forkchoicetypes.Checkpoint - shouldEqual bool - } - tests := []struct { - name string - args args - }{ - { - name: "Not epoch boundary. No change", - args: args{ - slot: params.BeaconConfig().SlotsPerEpoch + 1, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}}, - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'b'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: bj}, - shouldEqual: false, - }, - }, - { - name: "Justified higher than best justified. No change", - args: args{ - slot: params.BeaconConfig().SlotsPerEpoch, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}}, - justified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: bj}, - shouldEqual: false, - }, - }, - { - name: "Best justified not on the same chain as finalized. No change", - args: args{ - slot: params.BeaconConfig().SlotsPerEpoch, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}}, - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'b'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'d'}}, - shouldEqual: false, - }, - }, - { - name: "Best justified on the same chain as finalized. Yes change", - args: args{ - slot: params.BeaconConfig().SlotsPerEpoch, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}}, - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'b'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: bj}, - shouldEqual: true, - }, - }, - } - for _, test := range tests { - f := setup(test.args.justified.Epoch, test.args.finalized.Epoch) - state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, [32]byte{}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) // genesis - state, blkRoot, err = prepareForkchoiceState(ctx, 32, [32]byte{'a'}, [32]byte{}, [32]byte{}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) // finalized - state, blkRoot, err = prepareForkchoiceState(ctx, 64, [32]byte{'b'}, [32]byte{'a'}, [32]byte{}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) // justified - state, blkRoot, err = prepareForkchoiceState(ctx, 96, bj, [32]byte{'a'}, [32]byte{}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) // best justified - state, blkRoot, err = prepareForkchoiceState(ctx, 97, [32]byte{'d'}, [32]byte{}, [32]byte{}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) // bad - - require.NoError(t, f.UpdateFinalizedCheckpoint(test.args.finalized)) - require.NoError(t, f.UpdateJustifiedCheckpoint(test.args.justified)) - f.store.bestJustifiedCheckpoint = test.args.bestJustified - - require.NoError(t, f.NewSlot(ctx, test.args.slot)) - if test.args.shouldEqual { - bcp := f.BestJustifiedCheckpoint() - cp := f.JustifiedCheckpoint() - require.Equal(t, bcp.Epoch, cp.Epoch) - require.Equal(t, bcp.Root, cp.Root) - } else { - bcp := f.BestJustifiedCheckpoint() - cp := f.JustifiedCheckpoint() - epochsEqual := bcp.Epoch == cp.Epoch - rootsEqual := bcp.Root == cp.Root - require.Equal(t, false, epochsEqual && rootsEqual) - } - } -} diff --git a/beacon-chain/forkchoice/protoarray/optimistic_sync.go b/beacon-chain/forkchoice/protoarray/optimistic_sync.go deleted file mode 100644 index 094ac811b44c..000000000000 --- a/beacon-chain/forkchoice/protoarray/optimistic_sync.go +++ /dev/null @@ -1,180 +0,0 @@ -package protoarray - -import ( - "context" - - "github.com/prysmaticlabs/prysm/v3/config/params" -) - -// IsOptimistic returns true if this node is optimistically synced -// A optimistically synced block is synced as usual, but its -// execution payload is not validated, while the EL is still syncing. -// This function returns an error if the block is not found in the fork choice -// store -func (f *ForkChoice) IsOptimistic(root [32]byte) (bool, error) { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - index, ok := f.store.nodesIndices[root] - if !ok { - return true, ErrUnknownNodeRoot - } - node := f.store.nodes[index] - return node.status == syncing, nil -} - -// SetOptimisticToValid is called with the root of a block that was returned as -// VALID by the EL. -// -// WARNING: This method returns an error if the root is not found in forkchoice -func (f *ForkChoice) SetOptimisticToValid(ctx context.Context, root [32]byte) error { - f.store.nodesLock.Lock() - defer f.store.nodesLock.Unlock() - // We can only update if given root is in Fork Choice - index, ok := f.store.nodesIndices[root] - if !ok { - return ErrUnknownNodeRoot - } - - for node := f.store.nodes[index]; node.status == syncing; node = f.store.nodes[index] { - if ctx.Err() != nil { - return ctx.Err() - } - node.status = valid - index = node.parent - if index == NonExistentNode { - break - } - } - return nil -} - -// SetOptimisticToInvalid updates the synced_tips map when the block with the given root becomes INVALID. -// It takes three parameters: the root of the INVALID block, its parent root and the payload Hash -// of the last valid block -func (f *ForkChoice) SetOptimisticToInvalid(ctx context.Context, root, parentRoot, payloadHash [32]byte) ([][32]byte, error) { - f.store.nodesLock.Lock() - defer f.store.nodesLock.Unlock() - invalidRoots := make([][32]byte, 0) - lastValidIndex, ok := f.store.payloadIndices[payloadHash] - if !ok { - lastValidIndex = uint64(len(f.store.nodes)) - } - - invalidIndex, ok := f.store.nodesIndices[root] - if !ok { - invalidIndex, ok = f.store.nodesIndices[parentRoot] - if !ok { - return invalidRoots, ErrUnknownNodeRoot - } - // return early if parent is LVH - if invalidIndex == lastValidIndex { - return invalidRoots, nil - } - } - node := f.store.nodes[invalidIndex] - - // Check if last valid hash is an ancestor of the passed node - firstInvalidIndex := node.parent - for ; firstInvalidIndex != NonExistentNode && firstInvalidIndex != lastValidIndex; firstInvalidIndex = node.parent { - node = f.store.nodes[firstInvalidIndex] - } - - // Deal with the case that the last valid payload is in a different fork - // This means we are dealing with an EE that does not follow the spec - if node.parent != lastValidIndex { - node = f.store.nodes[invalidIndex] - // return early if invalid node was not imported - if node.root == parentRoot { - return invalidRoots, nil - } - - firstInvalidIndex = invalidIndex - lastValidIndex = node.parent - if lastValidIndex == NonExistentNode { - return invalidRoots, errInvalidFinalizedNode - } - } else { - firstInvalidIndex = f.store.nodesIndices[node.root] - } - - // Update the weights of the nodes subtracting the first INVALID node's weight - weight := node.weight - var validNode *Node - for index := lastValidIndex; index != NonExistentNode; index = validNode.parent { - validNode = f.store.nodes[index] - validNode.weight -= weight - } - - // Find the current proposer boost (it should be set to zero if an - // INVALID block was boosted) - f.store.proposerBoostLock.RLock() - boostRoot := f.store.proposerBoostRoot - previousBoostRoot := f.store.previousProposerBoostRoot - f.store.proposerBoostLock.RUnlock() - - // Remove the invalid roots from our store maps and adjust their weight - // to zero - boosted := node.root == boostRoot - previouslyBoosted := node.root == previousBoostRoot - - invalidIndices := map[uint64]bool{firstInvalidIndex: true} - node.status = invalid - node.weight = 0 - delete(f.store.nodesIndices, node.root) - delete(f.store.canonicalNodes, node.root) - delete(f.store.payloadIndices, node.payloadHash) - for index := firstInvalidIndex + 1; index < uint64(len(f.store.nodes)); index++ { - invalidNode := f.store.nodes[index] - if _, ok := invalidIndices[invalidNode.parent]; !ok { - continue - } - if invalidNode.status == valid { - return invalidRoots, errInvalidOptimisticStatus - } - if !boosted && invalidNode.root == boostRoot { - boosted = true - } - if !previouslyBoosted && invalidNode.root == previousBoostRoot { - previouslyBoosted = true - } - invalidNode.status = invalid - invalidIndices[index] = true - invalidNode.weight = 0 - delete(f.store.nodesIndices, invalidNode.root) - delete(f.store.canonicalNodes, invalidNode.root) - delete(f.store.payloadIndices, invalidNode.payloadHash) - } - if boosted { - if err := f.ResetBoostedProposerRoot(ctx); err != nil { - return invalidRoots, err - } - } - if previouslyBoosted { - f.store.proposerBoostLock.Lock() - f.store.previousProposerBoostRoot = params.BeaconConfig().ZeroHash - f.store.previousProposerBoostScore = 0 - f.store.proposerBoostLock.Unlock() - } - - for index := range invalidIndices { - invalidRoots = append(invalidRoots, f.store.nodes[index].root) - } - - // Update the best child and descendant - for i := len(f.store.nodes) - 1; i >= 0; i-- { - n := f.store.nodes[i] - if n.parent != NonExistentNode { - if err := f.store.updateBestChildAndDescendant(n.parent, uint64(i)); err != nil { - return invalidRoots, err - } - } - } - return invalidRoots, nil -} - -// AllTipsAreInvalid returns true if no forkchoice tip is viable for head -func (f *ForkChoice) AllTipsAreInvalid() bool { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - return f.store.allTipsAreInvalid -} diff --git a/beacon-chain/forkchoice/protoarray/optimistic_sync_test.go b/beacon-chain/forkchoice/protoarray/optimistic_sync_test.go deleted file mode 100644 index 9db72a3ac55a..000000000000 --- a/beacon-chain/forkchoice/protoarray/optimistic_sync_test.go +++ /dev/null @@ -1,554 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func slicesEqual(a, b [][32]byte) bool { - if len(a) != len(b) { - return false - } - - mapA := make(map[[32]byte]bool, len(a)) - for _, root := range a { - mapA[root] = true - } - for _, root := range b { - _, ok := mapA[root] - if !ok { - return false - } - } - return true -} - -func TestOptimistic_Outside_ForkChoice(t *testing.T) { - root0 := bytesutil.ToBytes32([]byte("hello0")) - - nodeA := &Node{ - slot: types.Slot(100), - root: bytesutil.ToBytes32([]byte("helloA")), - bestChild: 1, - status: valid, - } - nodes := []*Node{ - nodeA, - } - ni := map[[32]byte]uint64{ - nodeA.root: 0, - } - - s := &Store{ - nodes: nodes, - nodesIndices: ni, - } - - f := &ForkChoice{ - store: s, - } - _, err := f.IsOptimistic(root0) - require.ErrorIs(t, ErrUnknownNodeRoot, err) -} - -// This tests the algorithm to update optimistic Status -// We start with the following diagram -// -// E -- F -// / -// C -- D -// / \ -// A -- B G -- H -- I -// \ \ -// J -- K -- L -// -// The Chain A -- B -- C -- D -- E is VALID. -// -func TestSetOptimisticToValid(t *testing.T) { - ctx := context.Background() - tests := []struct { - root [32]byte // the root of the new VALID block - testRoot [32]byte // root of the node we will test optimistic status - wantedOptimistic bool // wanted optimistic status for tested node - wantedErr error // wanted error message - }{ - { - [32]byte{'i'}, - [32]byte{'i'}, - false, - nil, - }, - { - [32]byte{'i'}, - [32]byte{'f'}, - true, - nil, - }, - { - [32]byte{'i'}, - [32]byte{'b'}, - false, - nil, - }, - { - [32]byte{'i'}, - [32]byte{'h'}, - false, - nil, - }, - { - [32]byte{'b'}, - [32]byte{'b'}, - false, - nil, - }, - { - [32]byte{'b'}, - [32]byte{'h'}, - true, - nil, - }, - { - [32]byte{'b'}, - [32]byte{'a'}, - false, - nil, - }, - { - [32]byte{'k'}, - [32]byte{'k'}, - false, - nil, - }, - { - [32]byte{'k'}, - [32]byte{'l'}, - true, - nil, - }, - { - [32]byte{'p'}, - [32]byte{}, - false, - ErrUnknownNodeRoot, - }, - } - for _, tc := range tests { - f := setup(1, 1) - - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.SetOptimisticToValid(context.Background(), [32]byte{'e'})) - optimistic, err := f.IsOptimistic([32]byte{'b'}) - require.NoError(t, err) - require.Equal(t, false, optimistic) - - err = f.SetOptimisticToValid(context.Background(), tc.root) - if tc.wantedErr != nil { - require.ErrorIs(t, err, tc.wantedErr) - } else { - require.NoError(t, err) - optimistic, err := f.IsOptimistic(tc.testRoot) - require.NoError(t, err) - require.Equal(t, tc.wantedOptimistic, optimistic) - } - } -} - -// We test the algorithm to update a node from SYNCING to INVALID -// We start with the same diagram as above: -// -// E(2) -- F(1) -// / -// C(7) -- D(6) -// / \ -// A(10) -- B(9) G(3) -- H(1) -- I(0) -// \ \ -// J(1) -- K(1) -- L(0) -// -// And the chain A -- B -- C -- D -- E has been fully validated. The numbers in parentheses are -// the weights of the nodes. -// -func TestSetOptimisticToInvalid(t *testing.T) { - tests := []struct { - name string // test description - root [32]byte // the root of the new INVALID block - parentRoot [32]byte // the root of the parent block - payload [32]byte // the payload of the last valid hash - newBestChild uint64 - newBestDescendant uint64 - newParentWeight uint64 - returnedRoots [][32]byte - }{ - { - "Remove tip, parent was valid", - [32]byte{'j'}, - [32]byte{'b'}, - [32]byte{'B'}, - 3, - 12, - 8, - [][32]byte{{'j'}}, - }, - { - "Remove tip, parent was optimistic", - [32]byte{'i'}, - [32]byte{'h'}, - [32]byte{'H'}, - NonExistentNode, - NonExistentNode, - 1, - [][32]byte{{'i'}}, - }, - { - "Remove tip, lvh is inner and valid", - [32]byte{'i'}, - [32]byte{'h'}, - [32]byte{'D'}, - 6, - 8, - 3, - [][32]byte{{'g'}, {'h'}, {'k'}, {'i'}, {'l'}}, - }, - { - "Remove inner, lvh is inner and optimistic", - [32]byte{'h'}, - [32]byte{'g'}, - [32]byte{'G'}, - 10, - 12, - 2, - [][32]byte{{'h'}, {'i'}}, - }, - { - "Remove tip, lvh is inner and optimistic", - [32]byte{'l'}, - [32]byte{'k'}, - [32]byte{'G'}, - 9, - 11, - 2, - [][32]byte{{'k'}, {'l'}}, - }, - { - "Remove tip, lvh is not an ancestor", - [32]byte{'j'}, - [32]byte{'b'}, - [32]byte{'C'}, - 5, - 12, - 7, - [][32]byte{{'j'}}, - }, - { - "Remove inner, lvh is not an ancestor", - [32]byte{'g'}, - [32]byte{'d'}, - [32]byte{'J'}, - NonExistentNode, - NonExistentNode, - 1, - [][32]byte{{'g'}, {'h'}, {'k'}, {'i'}, {'l'}}, - }, - { - "Remove not inserted, parent was invalid", - [32]byte{'z'}, - [32]byte{'j'}, - [32]byte{'B'}, - 3, - 12, - 8, - [][32]byte{{'j'}}, - }, - { - "Remove not inserted, parent was valid", - [32]byte{'z'}, - [32]byte{'j'}, - [32]byte{'J'}, - NonExistentNode, - NonExistentNode, - 1, - [][32]byte{}, - }, - } - for _, tc := range tests { - ctx := context.Background() - f := setup(1, 1) - - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - weights := []uint64{10, 10, 9, 7, 1, 6, 2, 3, 1, 1, 1, 0, 0} - f.store.nodesLock.Lock() - for i, node := range f.store.nodes { - node.weight = weights[i] - } - f.store.nodesLock.Unlock() - require.NoError(t, f.SetOptimisticToValid(ctx, [32]byte{'e'})) - roots, err := f.SetOptimisticToInvalid(ctx, tc.root, tc.parentRoot, tc.payload) - require.NoError(t, err) - f.store.nodesLock.RLock() - _, ok := f.store.nodesIndices[tc.root] - require.Equal(t, false, ok) - lvh := f.store.nodes[f.store.payloadIndices[tc.payload]] - require.Equal(t, true, slicesEqual(tc.returnedRoots, roots)) - require.Equal(t, tc.newBestChild, lvh.bestChild) - require.Equal(t, tc.newBestDescendant, lvh.bestDescendant) - require.Equal(t, tc.newParentWeight, lvh.weight) - require.Equal(t, syncing, f.store.nodes[8].status /* F */) - require.Equal(t, valid, f.store.nodes[5].status /* E */) - f.store.nodesLock.RUnlock() - } -} - -func TestSetOptimisticToInvalid_InvalidRoots(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - _, err = f.SetOptimisticToInvalid(ctx, [32]byte{'p'}, [32]byte{'p'}, [32]byte{'B'}) - require.ErrorIs(t, ErrUnknownNodeRoot, err) -} - -// This is a regression test (10445) -func TestSetOptimisticToInvalid_ProposerBoost(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.store.proposerBoostLock.Lock() - f.store.proposerBoostRoot = [32]byte{'c'} - f.store.previousProposerBoostScore = 10 - f.store.previousProposerBoostRoot = [32]byte{'b'} - f.store.proposerBoostLock.Unlock() - - _, err = f.SetOptimisticToInvalid(ctx, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'A'}) - require.NoError(t, err) - f.store.proposerBoostLock.RLock() - require.Equal(t, uint64(0), f.store.previousProposerBoostScore) - require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot) - require.DeepEqual(t, params.BeaconConfig().ZeroHash, f.store.previousProposerBoostRoot) - f.store.proposerBoostLock.RUnlock() -} - -// This is a regression test (10996) -func TestSetOptimisticToInvalid_BogusLVH(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - state, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, root)) - - state, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, root)) - - invalidRoots, err := f.SetOptimisticToInvalid(ctx, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'R'}) - require.NoError(t, err) - require.Equal(t, 1, len(invalidRoots)) - require.Equal(t, [32]byte{'b'}, invalidRoots[0]) -} - -// This is a regression test (10996) -func TestSetOptimisticToInvalid_BogusLVH_RotNotImported(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - state, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, root)) - - state, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, root)) - - invalidRoots, err := f.SetOptimisticToInvalid(ctx, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'R'}) - require.NoError(t, err) - require.Equal(t, 0, len(invalidRoots)) -} - -// Pow | Pos -// -// CA -- A -- B -- C-----D -// \ \--------------E -// \ -// ----------------------F -- G -// B is INVALID -// -func TestSetOptimisticToInvalid_ForkAtMerge(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - st, root, err := prepareForkchoiceState(ctx, 100, [32]byte{'r'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 101, [32]byte{'a'}, [32]byte{'r'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 102, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 103, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 104, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 105, [32]byte{'e'}, [32]byte{'b'}, [32]byte{'E'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 106, [32]byte{'f'}, [32]byte{'r'}, [32]byte{'F'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 107, [32]byte{'g'}, [32]byte{'f'}, [32]byte{'G'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - roots, err := f.SetOptimisticToInvalid(ctx, [32]byte{'x'}, [32]byte{'d'}, [32]byte{}) - require.NoError(t, err) - require.Equal(t, 4, len(roots)) - require.Equal(t, true, slicesEqual(roots, [][32]byte{{'b'}, {'c'}, {'d'}, {'e'}})) -} - -// Pow | Pos -// -// CA -------- B -- C-----D -// \ \--------------E -// \ -// --A -------------------------F -- G -// B is INVALID -// -func TestSetOptimisticToInvalid_ForkAtMerge_bis(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - - st, root, err := prepareForkchoiceState(ctx, 100, [32]byte{'r'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 101, [32]byte{'a'}, [32]byte{'r'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 102, [32]byte{'b'}, [32]byte{}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 103, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 104, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 105, [32]byte{'e'}, [32]byte{'b'}, [32]byte{'E'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 106, [32]byte{'f'}, [32]byte{'a'}, [32]byte{'F'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - st, root, err = prepareForkchoiceState(ctx, 107, [32]byte{'g'}, [32]byte{'f'}, [32]byte{'G'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, st, root)) - - roots, err := f.SetOptimisticToInvalid(ctx, [32]byte{'d'}, [32]byte{'c'}, [32]byte{}) - require.NoError(t, err) - require.Equal(t, 1, len(roots)) - require.Equal(t, [32]byte{'d'}, roots[0]) -} diff --git a/beacon-chain/forkchoice/protoarray/proposer_boost.go b/beacon-chain/forkchoice/protoarray/proposer_boost.go deleted file mode 100644 index a9ba2ef2d6c6..000000000000 --- a/beacon-chain/forkchoice/protoarray/proposer_boost.go +++ /dev/null @@ -1,43 +0,0 @@ -package protoarray - -import ( - "context" - - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v3/config/params" -) - -// ResetBoostedProposerRoot sets the value of the proposer boosted root to zeros. -func (f *ForkChoice) ResetBoostedProposerRoot(_ context.Context) error { - f.store.proposerBoostLock.Lock() - f.store.proposerBoostRoot = [32]byte{} - f.store.proposerBoostLock.Unlock() - return nil -} - -// Given a list of validator balances, we compute the proposer boost score -// that should be given to a proposer based on their committee weight, derived from -// the total active balances, the size of a committee, and a boost score constant. -// IMPORTANT: The caller MUST pass in a list of validator balances where balances > 0 refer to active -// validators while balances == 0 are for inactive validators. -func computeProposerBoostScore(validatorBalances []uint64) (score uint64, err error) { - totalActiveBalance := uint64(0) - numActive := uint64(0) - for _, balance := range validatorBalances { - // We only consider balances > 0. The input slice should be constructed - // as balance > 0 for all active validators and 0 for inactive ones. - if balance == 0 { - continue - } - totalActiveBalance += balance - numActive += 1 - } - if numActive == 0 { - // Should never happen. - err = errors.New("no active validators") - return - } - committeeWeight := totalActiveBalance / uint64(params.BeaconConfig().SlotsPerEpoch) - score = (committeeWeight * params.BeaconConfig().ProposerScoreBoost) / 100 - return -} diff --git a/beacon-chain/forkchoice/protoarray/proposer_boost_test.go b/beacon-chain/forkchoice/protoarray/proposer_boost_test.go deleted file mode 100644 index 0f2f71eab31e..000000000000 --- a/beacon-chain/forkchoice/protoarray/proposer_boost_test.go +++ /dev/null @@ -1,476 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - "time" - - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -// Helper function to simulate the block being on time or delayed for proposer -// boost. It alters the genesisTime tracked by the store. -func driftGenesisTime(f *ForkChoice, slot types.Slot, delay uint64) { - f.SetGenesisTime(uint64(time.Now().Unix()) - uint64(slot)*params.BeaconConfig().SecondsPerSlot - delay) -} - -// Simple, ex-ante attack mitigation using proposer boost. -// In a nutshell, an adversarial block proposer in slot n+1 keeps its proposal hidden. -// The honest block proposer in slot n+2 will then propose an honest block. The -// adversary can now use its committee members’ votes from both slots n+1 and n+2. -// and release their withheld block of slot n+2 in an attempt to win fork choice. -// If the honest proposal is boosted at slot n+2, it will win against this attacker. -func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) { - ctx := context.Background() - jEpoch, fEpoch := types.Epoch(0), types.Epoch(0) - zeroHash := params.BeaconConfig().ZeroHash - balances := make([]uint64, 64) // 64 active validators. - for i := 0; i < len(balances); i++ { - balances[i] = 10 - } - t.Run("back-propagates boost score to ancestors after proposer boosting", func(t *testing.T) { - f := setup(jEpoch, fEpoch) - - // The head should always start at the finalized block. - headRoot, err := f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, zeroHash, headRoot, "Incorrect head with genesis") - - // Insert block at slot 1 into the tree and verify head is at that block: - // 0 - // | - // 1 <- HEAD - slot := types.Slot(1) - driftGenesisTime(f, slot, 0) - newRoot := indexToHash(1) - state, blkRoot, err := prepareForkchoiceState( - ctx, - slot, - newRoot, - headRoot, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.ProcessAttestation(ctx, []uint64{0}, newRoot, fEpoch) - headRoot, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 1") - - // Insert block at slot 2 into the tree and verify head is at that block: - // 0 - // | - // 1 - // | - // 2 <- HEAD - slot = types.Slot(2) - driftGenesisTime(f, slot, 0) - newRoot = indexToHash(2) - state, blkRoot, err = prepareForkchoiceState( - ctx, - slot, - newRoot, - headRoot, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.ProcessAttestation(ctx, []uint64{1}, newRoot, fEpoch) - headRoot, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 2") - - // Insert block at slot 3 into the tree and verify head is at that block: - // 0 - // | - // 1 - // | - // 2 - // | - // 3 <- HEAD - slot = types.Slot(3) - driftGenesisTime(f, slot, 0) - newRoot = indexToHash(3) - state, blkRoot, err = prepareForkchoiceState( - ctx, - slot, - newRoot, - headRoot, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.ProcessAttestation(ctx, []uint64{2}, newRoot, fEpoch) - headRoot, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3") - - // Insert a second block at slot 4 into the tree and boost its score. - // 0 - // | - // 1 - // | - // 2 - // / \ - // 3 | - // 4 <- HEAD - slot = types.Slot(4) - driftGenesisTime(f, slot, 0) - newRoot = indexToHash(4) - state, blkRoot, err = prepareForkchoiceState( - ctx, - slot, - newRoot, - indexToHash(2), - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch) - headRoot, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3") - - // Check the ancestor scores from the store. - require.Equal(t, 5, len(f.store.nodes)) - - // Expect nodes to have a boosted, back-propagated score. - // Ancestors have the added weights of their children. Genesis is a special exception at 0 weight, - require.Equal(t, f.store.nodes[0].weight, uint64(0)) - - // Proposer boost score with this tests parameters is 8 - // Each of the nodes received one attestation accounting for 10. - // Node D is the only one with a proposer boost still applied: - // - // (A: 48) -> (B: 38) -> (C: 10) - // \--------------->(D: 18) - // - require.Equal(t, f.store.nodes[4].weight, uint64(18)) - require.Equal(t, f.store.nodes[1].weight, uint64(48)) - require.Equal(t, f.store.nodes[2].weight, uint64(38)) - require.Equal(t, f.store.nodes[3].weight, uint64(10)) - - // Regression: process attestations for C, check that it - // becomes head, we need two attestations to have C.weight = 30 > 24 = D.weight - f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), fEpoch) - headRoot, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(3), headRoot, "Incorrect head for justified epoch at slot 4") - }) - t.Run("vanilla ex ante attack", func(t *testing.T) { - f := setup(jEpoch, fEpoch) - - // The head should always start at the finalized block. - r, err := f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, zeroHash, r, "Incorrect head with genesis") - - // Proposer from slot 1 does not reveal their block, B, at slot 1. - // Proposer at slot 2 does reveal their block, C, and it becomes the head. - // C builds on A, as proposer at slot 1 did not reveal B. - // A - // / \ - // (B?) \ - // \ - // C <- Slot 2 HEAD - honestBlockSlot := types.Slot(2) - driftGenesisTime(f, honestBlockSlot, 0) - honestBlock := indexToHash(2) - state, blkRoot, err := prepareForkchoiceState( - ctx, - honestBlockSlot, - honestBlock, - zeroHash, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2") - - maliciouslyWithheldBlockSlot := types.Slot(1) - maliciouslyWithheldBlock := indexToHash(1) - state, blkRoot, err = prepareForkchoiceState( - ctx, - maliciouslyWithheldBlockSlot, - maliciouslyWithheldBlock, - zeroHash, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Ensure the head is C, the honest block. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2") - - // The maliciously withheld block has one vote. - votes := []uint64{1} - f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch) - // The honest block has one vote. - votes = []uint64{2} - f.ProcessAttestation(ctx, votes, honestBlock, fEpoch) - - // Ensure the head is STILL C, the honest block, as the honest block had proposer boost. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2") - }) - t.Run("adversarial attestations > proposer boosting", func(t *testing.T) { - f := setup(jEpoch, fEpoch) - - // The head should always start at the finalized block. - r, err := f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, zeroHash, r, "Incorrect head with genesis") - - // Proposer from slot 1 does not reveal their block, B, at slot 1. - // Proposer at slot 2 does reveal their block, C, and it becomes the head. - // C builds on A, as proposer at slot 1 did not reveal B. - // A - // / \ - // (B?) \ - // \ - // C <- Slot 2 HEAD - honestBlockSlot := types.Slot(2) - driftGenesisTime(f, honestBlockSlot, 0) - honestBlock := indexToHash(2) - state, blkRoot, err := prepareForkchoiceState( - ctx, - honestBlockSlot, - honestBlock, - zeroHash, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Ensure C is the head. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2") - - maliciouslyWithheldBlockSlot := types.Slot(1) - maliciouslyWithheldBlock := indexToHash(1) - state, blkRoot, err = prepareForkchoiceState( - ctx, - maliciouslyWithheldBlockSlot, - maliciouslyWithheldBlock, - zeroHash, - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Ensure C is still the head after the malicious proposer reveals their block. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2") - - // An attestation is received for B that has more voting power than C with the proposer boost, - // allowing B to then become the head if their attestation has enough adversarial votes. - votes := []uint64{1, 2} - f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch) - - // Expect the head to have switched to B. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, maliciouslyWithheldBlock, r, "Expected B to become the head") - }) - t.Run("boosting necessary to sandwich attack", func(t *testing.T) { - // Boosting necessary to sandwich attack. - // Objects: - // Block A - slot N - // Block B (parent A) - slot N+1 - // Block C (parent A) - slot N+2 - // Block D (parent B) - slot N+3 - // Attestation_1 (Block C); size 1 - slot N+2 (honest) - // Steps: - // Block A received at N — A is head - // Block C received at N+2 — C is head - // Block B received at N+2 — C is head - // Attestation_1 received at N+3 — C is head - // Block D received at N+3 — D is head - f := setup(jEpoch, fEpoch) - a := zeroHash - - // The head should always start at the finalized block. - r, err := f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, zeroHash, r, "Incorrect head with genesis") - - cSlot := types.Slot(2) - driftGenesisTime(f, cSlot, 0) - c := indexToHash(2) - state, blkRoot, err := prepareForkchoiceState( - ctx, - cSlot, - c, - a, // parent - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Ensure C is the head. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2") - - bSlot := types.Slot(1) - b := indexToHash(1) - state, blkRoot, err = prepareForkchoiceState( - ctx, - bSlot, - b, - a, // parent - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Ensure C is still the head. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2") - - // An attestation for C is received at slot N+3. - votes := []uint64{1} - f.ProcessAttestation(ctx, votes, c, fEpoch) - - // A block D, building on B, is received at slot N+3. It should not be able to win without boosting. - dSlot := types.Slot(3) - d := indexToHash(3) - state, blkRoot, err = prepareForkchoiceState( - ctx, - dSlot, - d, - b, // parent - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // D cannot win without a boost. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, c, r, "Expected C to remain the head") - - // If the same block arrives with boosting then it becomes head: - driftGenesisTime(f, dSlot, 0) - d2 := indexToHash(30) - state, blkRoot, err = prepareForkchoiceState( - ctx, - dSlot, - d2, - b, // parent - zeroHash, - jEpoch, - fEpoch, - ) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - votes = []uint64{2} - f.ProcessAttestation(ctx, votes, d2, fEpoch) - // Ensure D becomes the head thanks to boosting. - r, err = f.Head(ctx, balances) - require.NoError(t, err) - assert.Equal(t, d2, r, "Expected D to become the head") - }) -} - -func TestForkChoice_BoostProposerRoot(t *testing.T) { - ctx := context.Background() - root := [32]byte{'A'} - zeroHash := [32]byte{} - - t.Run("does not boost block from different slot", func(t *testing.T) { - f := setup(0, 0) - slot := types.Slot(0) - currentSlot := types.Slot(1) - driftGenesisTime(f, currentSlot, 0) - state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.Equal(t, [32]byte{}, f.store.proposerBoostRoot) - }) - t.Run("does not boost untimely block from same slot", func(t *testing.T) { - f := setup(0, 0) - slot := types.Slot(1) - currentSlot := types.Slot(1) - driftGenesisTime(f, currentSlot, params.BeaconConfig().SecondsPerSlot-1) - state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.Equal(t, [32]byte{}, f.store.proposerBoostRoot) - }) - t.Run("boosts perfectly timely block from same slot", func(t *testing.T) { - f := setup(0, 0) - slot := types.Slot(1) - currentSlot := types.Slot(1) - driftGenesisTime(f, currentSlot, 0) - state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.Equal(t, root, f.store.proposerBoostRoot) - }) - t.Run("boosts timely block from same slot", func(t *testing.T) { - f := setup(0, 0) - slot := types.Slot(1) - currentSlot := types.Slot(1) - driftGenesisTime(f, currentSlot, 1) - state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.Equal(t, root, f.store.proposerBoostRoot) - }) -} - -func TestForkChoice_computeProposerBoostScore(t *testing.T) { - t.Run("nil justified balances throws error", func(t *testing.T) { - _, err := computeProposerBoostScore(nil) - require.ErrorContains(t, "no active validators", err) - }) - t.Run("normal active balances computes score", func(t *testing.T) { - validatorBalances := make([]uint64, 64) // Num validators - for i := 0; i < len(validatorBalances); i++ { - validatorBalances[i] = 10 - } - // Avg balance is 10, and the number of validators is 64. - // With a committee size of num validators (64) / slots per epoch (32) == 2. - // we then have a committee weight of avg balance * committee size = 10 * 2 = 20. - // The score then becomes 10 * PROPOSER_SCORE_BOOST // 100, which is - // 20 * 40 / 100 = 8. - score, err := computeProposerBoostScore(validatorBalances) - require.NoError(t, err) - require.Equal(t, uint64(8), score) - }) -} diff --git a/beacon-chain/forkchoice/protoarray/store.go b/beacon-chain/forkchoice/protoarray/store.go deleted file mode 100644 index b4bb4cfc9835..000000000000 --- a/beacon-chain/forkchoice/protoarray/store.go +++ /dev/null @@ -1,1120 +0,0 @@ -package protoarray - -import ( - "bytes" - "context" - "fmt" - "time" - - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" - "github.com/prysmaticlabs/prysm/v3/config/features" - fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - pmath "github.com/prysmaticlabs/prysm/v3/math" - v1 "github.com/prysmaticlabs/prysm/v3/proto/eth/v1" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v3/runtime/version" - "github.com/prysmaticlabs/prysm/v3/time/slots" - "github.com/sirupsen/logrus" - "go.opencensus.io/trace" -) - -// This defines the minimal number of block nodes that can be in the tree -// before getting pruned upon new finalization. -const defaultPruneThreshold = 256 - -// New initializes a new fork choice store. -func New() *ForkChoice { - s := &Store{ - justifiedCheckpoint: &forkchoicetypes.Checkpoint{}, - bestJustifiedCheckpoint: &forkchoicetypes.Checkpoint{}, - unrealizedJustifiedCheckpoint: &forkchoicetypes.Checkpoint{}, - prevJustifiedCheckpoint: &forkchoicetypes.Checkpoint{}, - finalizedCheckpoint: &forkchoicetypes.Checkpoint{}, - unrealizedFinalizedCheckpoint: &forkchoicetypes.Checkpoint{}, - proposerBoostRoot: [32]byte{}, - nodes: make([]*Node, 0), - nodesIndices: make(map[[32]byte]uint64), - payloadIndices: make(map[[32]byte]uint64), - canonicalNodes: make(map[[32]byte]bool), - slashedIndices: make(map[types.ValidatorIndex]bool), - pruneThreshold: defaultPruneThreshold, - receivedBlocksLastEpoch: [fieldparams.SlotsPerEpoch]types.Slot{}, - } - - b := make([]uint64, 0) - v := make([]Vote, 0) - return &ForkChoice{store: s, balances: b, votes: v} -} - -// Head returns the head root from fork choice store. -// It firsts computes validator's balance changes then recalculates block tree from leaves to root. -func (f *ForkChoice) Head(ctx context.Context, justifiedStateBalances []uint64) ([32]byte, error) { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.Head") - defer span.End() - f.votesLock.Lock() - defer f.votesLock.Unlock() - - calledHeadCount.Inc() - newBalances := justifiedStateBalances - - // Using the write lock here because `updateCanonicalNodes` that gets called subsequently requires a write operation. - f.store.nodesLock.Lock() - defer f.store.nodesLock.Unlock() - deltas, newVotes, err := computeDeltas(ctx, len(f.store.nodes), f.store.nodesIndices, f.votes, f.balances, newBalances, f.store.slashedIndices) - if err != nil { - return [32]byte{}, errors.Wrap(err, "Could not compute deltas") - } - f.votes = newVotes - - if err := f.store.applyWeightChanges(ctx, newBalances, deltas); err != nil { - return [32]byte{}, errors.Wrap(err, "Could not apply score changes") - } - f.balances = newBalances - - return f.store.head(ctx) -} - -// ProcessAttestation processes attestation for vote accounting, it iterates around validator indices -// and update their votes accordingly. -func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, targetEpoch types.Epoch) { - _, span := trace.StartSpan(ctx, "protoArrayForkChoice.ProcessAttestation") - defer span.End() - f.votesLock.Lock() - defer f.votesLock.Unlock() - - for _, index := range validatorIndices { - // Validator indices will grow the vote cache. - for index >= uint64(len(f.votes)) { - f.votes = append(f.votes, Vote{currentRoot: params.BeaconConfig().ZeroHash, nextRoot: params.BeaconConfig().ZeroHash}) - } - - // Newly allocated vote if the root fields are untouched. - newVote := f.votes[index].nextRoot == params.BeaconConfig().ZeroHash && - f.votes[index].currentRoot == params.BeaconConfig().ZeroHash - - // Vote gets updated if it's newly allocated or high target epoch. - if newVote || targetEpoch > f.votes[index].nextEpoch { - f.votes[index].nextEpoch = targetEpoch - f.votes[index].nextRoot = blockRoot - } - } - - processedAttestationCount.Inc() -} - -// NodeCount returns the current number of nodes in the Store -func (f *ForkChoice) NodeCount() int { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - return len(f.store.nodes) -} - -// ProposerBoost returns the proposerBoost of the store -func (f *ForkChoice) ProposerBoost() [fieldparams.RootLength]byte { - return f.store.proposerBoost() -} - -// InsertNode processes a new block by inserting it to the fork choice store. -func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, root [32]byte) error { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.InsertNode") - defer span.End() - - slot := state.Slot() - bh := state.LatestBlockHeader() - if bh == nil { - return errNilBlockHeader - } - parentRoot := bytesutil.ToBytes32(bh.ParentRoot) - payloadHash := [32]byte{} - if state.Version() >= version.Bellatrix { - ph, err := state.LatestExecutionPayloadHeader() - if err != nil { - return err - } - if ph != nil { - copy(payloadHash[:], ph.BlockHash) - } - } - jc := state.CurrentJustifiedCheckpoint() - if jc == nil { - return errInvalidNilCheckpoint - } - justifiedEpoch := jc.Epoch - fc := state.FinalizedCheckpoint() - if fc == nil { - return errInvalidNilCheckpoint - } - finalizedEpoch := fc.Epoch - node, err := f.store.insert(ctx, slot, root, parentRoot, payloadHash, justifiedEpoch, finalizedEpoch) - if err != nil { - return err - } - - if !features.Get().DisablePullTips { - jc, fc = f.store.pullTips(state, node, jc, fc) - } - return f.updateCheckpoints(ctx, jc, fc) -} - -// updateCheckpoints update the checkpoints when inserting a new node. -func (f *ForkChoice) updateCheckpoints(ctx context.Context, jc, fc *ethpb.Checkpoint) error { - f.store.checkpointsLock.Lock() - if jc.Epoch > f.store.justifiedCheckpoint.Epoch { - bj := f.store.bestJustifiedCheckpoint - if bj == nil || jc.Epoch > bj.Epoch { - f.store.bestJustifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch, - Root: bytesutil.ToBytes32(jc.Root)} - } - currentSlot := slots.CurrentSlot(f.store.genesisTime) - if slots.SinceEpochStarts(currentSlot) < params.BeaconConfig().SafeSlotsToUpdateJustified { - f.store.prevJustifiedCheckpoint = f.store.justifiedCheckpoint - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch, - Root: bytesutil.ToBytes32(jc.Root)} - } else { - currentJcp := f.store.justifiedCheckpoint - currentRoot := currentJcp.Root - if currentRoot == params.BeaconConfig().ZeroHash { - currentRoot = f.store.originRoot - } - jSlot, err := slots.EpochStart(currentJcp.Epoch) - if err != nil { - f.store.checkpointsLock.Unlock() - return err - } - jcRoot := bytesutil.ToBytes32(jc.Root) - // release the checkpoints lock here because - // AncestorRoot takes a lock on nodes and that can lead - // to double locks - f.store.checkpointsLock.Unlock() - root, err := f.AncestorRoot(ctx, jcRoot, jSlot) - if err != nil { - return err - } - f.store.checkpointsLock.Lock() - if root == currentRoot { - f.store.prevJustifiedCheckpoint = f.store.justifiedCheckpoint - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch, - Root: jcRoot} - } - } - } - // Update finalization - if fc.Epoch <= f.store.finalizedCheckpoint.Epoch { - f.store.checkpointsLock.Unlock() - return nil - } - f.store.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: fc.Epoch, - Root: bytesutil.ToBytes32(fc.Root)} - f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch, - Root: bytesutil.ToBytes32(jc.Root)} - f.store.checkpointsLock.Unlock() - return f.store.prune(ctx) -} - -// HasNode returns true if the node exists in fork choice store, -// false else wise. -func (f *ForkChoice) HasNode(root [32]byte) bool { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - - _, ok := f.store.nodesIndices[root] - return ok -} - -// HasParent returns true if the node parent exists in fork choice store, -// false else wise. -func (f *ForkChoice) HasParent(root [32]byte) bool { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - - i, ok := f.store.nodesIndices[root] - if !ok || i >= uint64(len(f.store.nodes)) { - return false - } - - return f.store.nodes[i].parent != NonExistentNode -} - -// IsCanonical returns true if the given root is part of the canonical chain. -func (f *ForkChoice) IsCanonical(root [32]byte) bool { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - - return f.store.canonicalNodes[root] -} - -// AncestorRoot returns the ancestor root of input block root at a given slot. -func (f *ForkChoice) AncestorRoot(ctx context.Context, root [32]byte, slot types.Slot) ([32]byte, error) { - ctx, span := trace.StartSpan(ctx, "protoArray.AncestorRoot") - defer span.End() - - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - - i, ok := f.store.nodesIndices[root] - if !ok { - return [32]byte{}, errors.New("node does not exist") - } - if i >= uint64(len(f.store.nodes)) { - return [32]byte{}, errors.New("node index out of range") - } - - for f.store.nodes[i].slot > slot { - if ctx.Err() != nil { - return [32]byte{}, ctx.Err() - } - - i = f.store.nodes[i].parent - - if i >= uint64(len(f.store.nodes)) { - return [32]byte{}, errors.New("node index out of range") - } - } - - return f.store.nodes[i].root, nil -} - -// CommonAncestor returns the common ancestor root and slot between the two block roots r1 and r2. -func (f *ForkChoice) CommonAncestor(ctx context.Context, r1 [32]byte, r2 [32]byte) ([32]byte, types.Slot, error) { - ctx, span := trace.StartSpan(ctx, "protoArray.CommonAncestorRoot") - defer span.End() - - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - - i1, ok := f.store.nodesIndices[r1] - if !ok || i1 >= uint64(len(f.store.nodes)) { - return [32]byte{}, 0, forkchoice.ErrUnknownCommonAncestor - } - - // Do nothing if the two input roots are the same. - if r1 == r2 { - n1 := f.store.nodes[i1] - return r1, n1.slot, nil - } - - i2, ok := f.store.nodesIndices[r2] - if !ok || i2 >= uint64(len(f.store.nodes)) { - return [32]byte{}, 0, forkchoice.ErrUnknownCommonAncestor - } - - for { - if ctx.Err() != nil { - return [32]byte{}, 0, ctx.Err() - } - if i1 > i2 { - n1 := f.store.nodes[i1] - i1 = n1.parent - // Reaches the end of the tree and unable to find common ancestor. - if i1 >= uint64(len(f.store.nodes)) { - return [32]byte{}, 0, forkchoice.ErrUnknownCommonAncestor - } - } else { - n2 := f.store.nodes[i2] - i2 = n2.parent - // Reaches the end of the tree and unable to find common ancestor. - if i2 >= uint64(len(f.store.nodes)) { - return [32]byte{}, 0, forkchoice.ErrUnknownCommonAncestor - } - } - if i1 == i2 { - n1 := f.store.nodes[i1] - return n1.root, n1.slot, nil - } - } -} - -// PruneThreshold of fork choice store. -func (s *Store) PruneThreshold() uint64 { - return s.pruneThreshold -} - -// BestJustifiedCheckpoint of fork choice store. -func (f *ForkChoice) BestJustifiedCheckpoint() *forkchoicetypes.Checkpoint { - f.store.checkpointsLock.RLock() - defer f.store.checkpointsLock.RUnlock() - return f.store.bestJustifiedCheckpoint -} - -// PreviousJustifiedCheckpoint of fork choice store. -func (f *ForkChoice) PreviousJustifiedCheckpoint() *forkchoicetypes.Checkpoint { - f.store.checkpointsLock.RLock() - defer f.store.checkpointsLock.RUnlock() - return f.store.prevJustifiedCheckpoint -} - -// JustifiedCheckpoint of fork choice store. -func (f *ForkChoice) JustifiedCheckpoint() *forkchoicetypes.Checkpoint { - f.store.checkpointsLock.RLock() - defer f.store.checkpointsLock.RUnlock() - return f.store.justifiedCheckpoint -} - -// FinalizedCheckpoint of fork choice store. -func (f *ForkChoice) FinalizedCheckpoint() *forkchoicetypes.Checkpoint { - f.store.checkpointsLock.RLock() - defer f.store.checkpointsLock.RUnlock() - return f.store.finalizedCheckpoint -} - -// proposerBoost of fork choice store. -func (s *Store) proposerBoost() [fieldparams.RootLength]byte { - s.proposerBoostLock.RLock() - defer s.proposerBoostLock.RUnlock() - return s.proposerBoostRoot -} - -// head starts from justified root and then follows the best descendant links -// to find the best block for head. It assumes the caller has a lock on nodes. -func (s *Store) head(ctx context.Context) ([32]byte, error) { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.head") - defer span.End() - - s.checkpointsLock.RLock() - - // Justified index has to be valid in node indices map, and can not be out of bound. - if s.justifiedCheckpoint == nil { - s.checkpointsLock.RUnlock() - return [32]byte{}, errInvalidNilCheckpoint - } - - justifiedIndex, ok := s.nodesIndices[s.justifiedCheckpoint.Root] - if !ok { - // If the justifiedCheckpoint is from genesis, then the root is - // zeroHash. In this case it should be the root of forkchoice - // tree. - if s.justifiedCheckpoint.Epoch == params.BeaconConfig().GenesisEpoch { - justifiedIndex = uint64(0) - } else { - s.checkpointsLock.RUnlock() - return [32]byte{}, errUnknownJustifiedRoot - } - } - s.checkpointsLock.RUnlock() - if justifiedIndex >= uint64(len(s.nodes)) { - return [32]byte{}, errInvalidJustifiedIndex - } - justifiedNode := s.nodes[justifiedIndex] - bestDescendantIndex := justifiedNode.bestDescendant - // If the justified node doesn't have a best descendant, - // the best node is itself. - if bestDescendantIndex == NonExistentNode { - bestDescendantIndex = justifiedIndex - } - if bestDescendantIndex >= uint64(len(s.nodes)) { - return [32]byte{}, errInvalidBestDescendantIndex - } - bestNode := s.nodes[bestDescendantIndex] - - if !s.viableForHead(bestNode) { - s.allTipsAreInvalid = true - s.checkpointsLock.RLock() - jEpoch := s.justifiedCheckpoint.Epoch - fEpoch := s.finalizedCheckpoint.Epoch - s.checkpointsLock.RUnlock() - return [32]byte{}, fmt.Errorf("head at slot %d with weight %d is not eligible, finalizedEpoch %d != %d, justifiedEpoch %d != %d", - bestNode.slot, bestNode.weight/10e9, bestNode.finalizedEpoch, fEpoch, bestNode.justifiedEpoch, jEpoch) - } - s.allTipsAreInvalid = false - - // Update metrics and tracked head Root - if bestNode.root != s.lastHeadRoot { - headChangesCount.Inc() - headSlotNumber.Set(float64(bestNode.slot)) - s.lastHeadRoot = bestNode.root - } - - // Update canonical mapping given the head root. - if err := s.updateCanonicalNodes(ctx, bestNode.root); err != nil { - return [32]byte{}, err - } - - return bestNode.root, nil -} - -// updateCanonicalNodes updates the canonical nodes mapping given the input -// block root. This function assumes the caller holds a lock in Store.nodesLock -func (s *Store) updateCanonicalNodes(ctx context.Context, root [32]byte) error { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.updateCanonicalNodes") - defer span.End() - - // Set the input node to canonical. - i := s.nodesIndices[root] - var newCanonicalRoots [][32]byte - var n *Node - for i != NonExistentNode { - if ctx.Err() != nil { - return ctx.Err() - } - - // Get the parent node, if the node is already in canonical mapping, - // we can be sure rest of the ancestors are canonical. Exit early. - n = s.nodes[i] - if s.canonicalNodes[n.root] { - break - } - - // Set parent node to canonical. Repeat until parent node index is undefined. - newCanonicalRoots = append(newCanonicalRoots, n.root) - i = n.parent - } - - // i is either NonExistentNode or has the index of the last canonical - // node before the last head update. - if i == NonExistentNode { - s.canonicalNodes = make(map[[fieldparams.RootLength]byte]bool) - } else { - for j := i + 1; j < uint64(len(s.nodes)); j++ { - delete(s.canonicalNodes, s.nodes[j].root) - } - } - - for _, canonicalRoot := range newCanonicalRoots { - s.canonicalNodes[canonicalRoot] = true - } - - return nil -} - -// insert registers a new block node to the fork choice store's node list. -// It then updates the new node's parent with best child and descendant node. -func (s *Store) insert(ctx context.Context, - slot types.Slot, - root, parent, payloadHash [32]byte, - justifiedEpoch, finalizedEpoch types.Epoch) (*Node, error) { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.insert") - defer span.End() - - s.nodesLock.Lock() - defer s.nodesLock.Unlock() - - if err := ctx.Err(); err != nil { - return nil, err - } - - // Return if the block has been inserted into Store before. - if idx, ok := s.nodesIndices[root]; ok { - return s.nodes[idx], nil - } - - index := uint64(len(s.nodes)) - parentIndex, ok := s.nodesIndices[parent] - // Mark genesis block's parent as non-existent. - if !ok { - parentIndex = NonExistentNode - } - - n := &Node{ - slot: slot, - root: root, - parent: parentIndex, - justifiedEpoch: justifiedEpoch, - unrealizedJustifiedEpoch: justifiedEpoch, - finalizedEpoch: finalizedEpoch, - unrealizedFinalizedEpoch: finalizedEpoch, - bestChild: NonExistentNode, - bestDescendant: NonExistentNode, - weight: 0, - payloadHash: payloadHash, - } - - s.nodesIndices[root] = index - s.payloadIndices[payloadHash] = index - s.nodes = append(s.nodes, n) - - // Apply proposer boost - timeNow := uint64(time.Now().Unix()) - if timeNow < s.genesisTime { - return n, nil - } - secondsIntoSlot := (timeNow - s.genesisTime) % params.BeaconConfig().SecondsPerSlot - currentSlot := slots.CurrentSlot(s.genesisTime) - boostThreshold := params.BeaconConfig().SecondsPerSlot / params.BeaconConfig().IntervalsPerSlot - if currentSlot == slot && secondsIntoSlot < boostThreshold { - s.proposerBoostLock.Lock() - s.proposerBoostRoot = root - s.proposerBoostLock.Unlock() - } - - // Update parent with the best child and descendant only if it's available. - if n.parent != NonExistentNode { - if err := s.updateBestChildAndDescendant(parentIndex, index); err != nil { - return n, err - } - } - - // Update metrics. - processedBlockCount.Inc() - nodeCount.Set(float64(len(s.nodes))) - - // Only update received block slot if it's within epoch from current time. - if slot+params.BeaconConfig().SlotsPerEpoch > slots.CurrentSlot(s.genesisTime) { - s.receivedBlocksLastEpoch[slot%params.BeaconConfig().SlotsPerEpoch] = slot - } - // Update highest slot tracking. - highestSlot := s.nodes[s.highestReceivedIndex].slot - if slot > highestSlot { - s.highestReceivedIndex = uint64(len(s.nodes) - 1) - } - return n, nil -} - -// applyWeightChanges iterates backwards through the nodes in store. It checks all nodes parent -// and its best child. For each node, it updates the weight with input delta and -// back propagate the nodes' delta to its parents' delta. After scoring changes, -// the best child is then updated along with the best descendant. This function -// assumes the caller holds a lock in Store.nodesLock -func (s *Store) applyWeightChanges( - ctx context.Context, newBalances []uint64, delta []int, -) error { - _, span := trace.StartSpan(ctx, "protoArrayForkChoice.applyWeightChanges") - defer span.End() - - // The length of the nodes can not be different than length of the delta. - if len(s.nodes) != len(delta) { - return errInvalidDeltaLength - } - - // Proposer score defaults to 0. - proposerScore := uint64(0) - - // Iterate backwards through all index to node in store. - var err error - for i := len(s.nodes) - 1; i >= 0; i-- { - if ctx.Err() != nil { - return ctx.Err() - } - n := s.nodes[i] - - // There is no need to adjust the balances or manage parent of the zero hash, it - // is an alias to the genesis block. - if n.root == params.BeaconConfig().ZeroHash { - continue - } - - nodeDelta := delta[i] - - // If we have a node where the proposer boost was previously applied, - // we then decrease the delta by the required score amount. - s.proposerBoostLock.Lock() - if s.previousProposerBoostRoot != params.BeaconConfig().ZeroHash && s.previousProposerBoostRoot == n.root { - nodeDelta -= int(s.previousProposerBoostScore) - } - - if s.proposerBoostRoot != params.BeaconConfig().ZeroHash && s.proposerBoostRoot == n.root { - proposerScore, err = computeProposerBoostScore(newBalances) - if err != nil { - s.proposerBoostLock.Unlock() - return err - } - iProposerScore, err := pmath.Int(proposerScore) - if err != nil { - s.proposerBoostLock.Unlock() - return err - } - nodeDelta = nodeDelta + iProposerScore - } - s.proposerBoostLock.Unlock() - - // A node's weight can not be negative but the delta can be negative. - if nodeDelta < 0 { - d := uint64(-nodeDelta) - if n.weight < d { - s.proposerBoostLock.RLock() - log.WithFields(logrus.Fields{ - "nodeDelta": d, - "nodeRoot": fmt.Sprintf("%#x", bytesutil.Trunc(n.root[:])), - "nodeWeight": n.weight, - "proposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(s.proposerBoostRoot[:])), - "previousProposerBoostRoot": fmt.Sprintf("%#x", bytesutil.Trunc(s.previousProposerBoostRoot[:])), - "previousProposerBoostScore": s.previousProposerBoostScore, - }).Warning("node with invalid weight, setting it to zero") - s.proposerBoostLock.RUnlock() - n.weight = 0 - } else { - n.weight -= d - } - } else { - n.weight += uint64(nodeDelta) - } - - // Update parent's best child and descendant if the node has a known parent. - if n.parent != NonExistentNode { - // Protection against node parent index out of bound. This should not happen. - if int(n.parent) >= len(delta) { - return errInvalidParentDelta - } - // Back propagate the nodes' delta to its parent. - delta[n.parent] += nodeDelta - } - } - - // Set the previous boosted root and score. - s.proposerBoostLock.Lock() - s.previousProposerBoostRoot = s.proposerBoostRoot - s.previousProposerBoostScore = proposerScore - s.proposerBoostLock.Unlock() - - for i := len(s.nodes) - 1; i >= 0; i-- { - n := s.nodes[i] - if n.parent != NonExistentNode { - if int(n.parent) >= len(delta) { - return errInvalidParentDelta - } - if err := s.updateBestChildAndDescendant(n.parent, uint64(i)); err != nil { - return err - } - } - } - - return nil -} - -// updateBestChildAndDescendant updates parent node's best child and descendant. -// It looks at input parent node and input child node and potentially modifies parent's best -// child and best descendant indices. -// There are four outcomes: -// 1.) The child is already the best child, but it's now invalid due to a FFG change and should be removed. -// 2.) The child is already the best child and the parent is updated with the new best descendant. -// 3.) The child is not the best child but becomes the best child. -// 4.) The child is not the best child and does not become the best child. -func (s *Store) updateBestChildAndDescendant(parentIndex, childIndex uint64) error { - - // Protection against parent index out of bound, this should not happen. - if parentIndex >= uint64(len(s.nodes)) { - return errInvalidNodeIndex - } - parent := s.nodes[parentIndex] - - // Protection against child index out of bound, again this should not happen. - if childIndex >= uint64(len(s.nodes)) { - return errInvalidNodeIndex - } - child := s.nodes[childIndex] - - // Is the child viable to become head? Based on justification and finalization rules. - childLeadsToViableHead, err := s.leadsToViableHead(child) - if err != nil { - return err - } - - // Define 3 variables for the 3 outcomes mentioned above. This is to - // set `parent.bestChild` and `parent.bestDescendant` to. These - // aliases are to assist readability. - changeToNone := []uint64{NonExistentNode, NonExistentNode} - bestDescendant := child.bestDescendant - if bestDescendant == NonExistentNode { - bestDescendant = childIndex - } - changeToChild := []uint64{childIndex, bestDescendant} - noChange := []uint64{parent.bestChild, parent.bestDescendant} - var newParentChild []uint64 - - if parent.bestChild != NonExistentNode { - if parent.bestChild == childIndex && !childLeadsToViableHead { - // If the child is already the best child of the parent, but it's not viable for head, - // we should remove it. (Outcome 1) - newParentChild = changeToNone - } else if parent.bestChild == childIndex { - // If the child is already the best child of the parent, set it again to ensure the best - // descendant of the parent is updated. (Outcome 2) - newParentChild = changeToChild - } else { - // Protection against parent's best child going out of bound. - if parent.bestChild > uint64(len(s.nodes)) { - return errInvalidBestDescendantIndex - } - bestChild := s.nodes[parent.bestChild] - // Is current parent's best child viable to be head? Based on justification and finalization rules. - bestChildLeadsToViableHead, err := s.leadsToViableHead(bestChild) - if err != nil { - return err - } - - if childLeadsToViableHead && !bestChildLeadsToViableHead { - // The child leads to a viable head, but the current parent's best child doesn't. - newParentChild = changeToChild - } else if !childLeadsToViableHead && bestChildLeadsToViableHead { - // The child doesn't lead to a viable head, the current parent's best child does. - newParentChild = noChange - } else if child.weight == bestChild.weight { - // If both are viable, compare their weights. - // Tie-breaker of equal weights by root. - if bytes.Compare(child.root[:], bestChild.root[:]) > 0 { - newParentChild = changeToChild - } else { - newParentChild = noChange - } - } else { - // Choose winner by weight. - if child.weight > bestChild.weight { - newParentChild = changeToChild - } else { - newParentChild = noChange - } - } - } - } else { - if childLeadsToViableHead { - // If parent doesn't have a best child and the child is viable. - newParentChild = changeToChild - } else { - // If parent doesn't have a best child and the child is not viable. - newParentChild = noChange - } - } - - // Update parent with the outcome. - parent.bestChild = newParentChild[0] - parent.bestDescendant = newParentChild[1] - s.nodes[parentIndex] = parent - - return nil -} - -// prune prunes the store with the new finalized root. The tree is only -// pruned if the number of the nodes in store has met prune threshold. -func (s *Store) prune(ctx context.Context) error { - ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.prune") - defer span.End() - - s.nodesLock.Lock() - defer s.nodesLock.Unlock() - s.checkpointsLock.RLock() - finalizedRoot := s.finalizedCheckpoint.Root - s.checkpointsLock.RUnlock() - - // Protection against invalid checkpoint - finalizedIndex, ok := s.nodesIndices[finalizedRoot] - if !ok { - return errUnknownFinalizedRoot - } - - // The number of the nodes has not met the prune threshold. - // Pruning at small numbers incurs more cost than benefit. - if finalizedIndex < s.pruneThreshold { - return nil - } - - canonicalNodesMap := make(map[uint64]uint64, uint64(len(s.nodes))-finalizedIndex) - canonicalNodes := make([]*Node, 1, uint64(len(s.nodes))-finalizedIndex) - finalizedNode := s.nodes[finalizedIndex] - finalizedNode.parent = NonExistentNode - canonicalNodes[0] = finalizedNode - canonicalNodesMap[finalizedIndex] = uint64(0) - - for idx := uint64(0); idx < uint64(len(s.nodes)); idx++ { - if ctx.Err() != nil { - return ctx.Err() - } - node := copyNode(s.nodes[idx]) - parentIdx, ok := canonicalNodesMap[node.parent] - if ok { - currentIndex := uint64(len(canonicalNodes)) - s.nodesIndices[node.root] = currentIndex - s.payloadIndices[node.payloadHash] = currentIndex - canonicalNodesMap[idx] = currentIndex - node.parent = parentIdx - canonicalNodes = append(canonicalNodes, node) - } else { - // Remove node that is not part of finalized branch. - delete(s.nodesIndices, node.root) - delete(s.canonicalNodes, node.root) - delete(s.payloadIndices, node.payloadHash) - } - } - s.nodesIndices[finalizedRoot] = uint64(0) - s.canonicalNodes[finalizedRoot] = true - s.payloadIndices[finalizedNode.payloadHash] = uint64(0) - - // Recompute the best child and descendant for each canonical nodes. - for _, node := range canonicalNodes { - if node.bestChild != NonExistentNode { - node.bestChild = canonicalNodesMap[node.bestChild] - } - if node.bestDescendant != NonExistentNode { - node.bestDescendant = canonicalNodesMap[node.bestDescendant] - } - } - - s.nodes = canonicalNodes - prunedCount.Inc() - return nil -} - -// leadsToViableHead returns true if the node or the best descendant of the node is viable for head. -// Any node with diff finalized or justified epoch than the ones in fork choice store -// should not be viable to head. -func (s *Store) leadsToViableHead(node *Node) (bool, error) { - if node.status == invalid { - return false, nil - } - - var bestDescendantViable bool - bestDescendantIndex := node.bestDescendant - - // If the best descendant is not part of the leaves. - if bestDescendantIndex != NonExistentNode { - // Protection against out of bound, the best descendant index can not be - // exceeds length of nodes list. - if bestDescendantIndex >= uint64(len(s.nodes)) { - return false, errInvalidBestDescendantIndex - } - - bestDescendantNode := s.nodes[bestDescendantIndex] - bestDescendantViable = s.viableForHead(bestDescendantNode) - } - - // The node is viable as long as the best descendant is viable. - return bestDescendantViable || s.viableForHead(node), nil -} - -// viableForHead returns true if the node is viable to head. -// Any node with diff finalized or justified epoch than the ones in fork choice store -// should not be viable to head. -func (s *Store) viableForHead(node *Node) bool { - s.checkpointsLock.RLock() - defer s.checkpointsLock.RUnlock() - // `node` is viable if its justified epoch and finalized epoch are the same as the one in `Store`. - // It's also viable if we are in genesis epoch. - justified := s.justifiedCheckpoint.Epoch == node.justifiedEpoch || s.justifiedCheckpoint.Epoch == 0 - finalized := s.finalizedCheckpoint.Epoch == node.finalizedEpoch || s.finalizedCheckpoint.Epoch == 0 - if features.Get().EnableDefensivePull { - currentEpoch := slots.EpochsSinceGenesis(time.Unix(int64(s.genesisTime), 0)) - if !justified && s.justifiedCheckpoint.Epoch+1 == currentEpoch { - if node.unrealizedJustifiedEpoch+1 >= currentEpoch { - justified = true - } - if node.unrealizedFinalizedEpoch >= s.finalizedCheckpoint.Epoch { - finalized = true - } - } - } - return justified && finalized -} - -// Tips returns all possible chain heads (leaves of fork choice tree). -// Heads roots and heads slots are returned. -func (f *ForkChoice) Tips() ([][32]byte, []types.Slot) { - - // Deliberate choice to not preallocate space for below. - // Heads cant be more than 2-3 in the worst case where pre-allocation will be 64 to begin with. - headsRoots := make([][32]byte, 0) - headsSlots := make([]types.Slot, 0) - - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - for _, node := range f.store.nodes { - // Possible heads have no children. - if node.BestDescendant() == NonExistentNode && node.BestChild() == NonExistentNode { - headsRoots = append(headsRoots, node.Root()) - headsSlots = append(headsSlots, node.Slot()) - } - } - return headsRoots, headsSlots -} - -// InsertSlashedIndex adds the given slashed validator index to the -// store-tracked list. Votes from these validators are not accounted for -// in forkchoice. -func (f *ForkChoice) InsertSlashedIndex(ctx context.Context, index types.ValidatorIndex) { - f.votesLock.RLock() - defer f.votesLock.RUnlock() - f.store.nodesLock.Lock() - defer f.store.nodesLock.Unlock() - // return early if the index was already included: - if f.store.slashedIndices[index] { - return - } - f.store.slashedIndices[index] = true - - // Subtract last vote from this equivocating validator - if index >= types.ValidatorIndex(len(f.balances)) { - return - } - - if index >= types.ValidatorIndex(len(f.votes)) { - return - } - - nodeIndex, ok := f.store.nodesIndices[f.votes[index].currentRoot] - if !ok { - return - } - - var node *Node - for nodeIndex != NonExistentNode { - if ctx.Err() != nil { - return - } - - node = f.store.nodes[nodeIndex] - if node == nil { - return - } - - if node.weight < f.balances[index] { - node.weight = 0 - } else { - node.weight -= f.balances[index] - } - nodeIndex = node.parent - } -} - -// UpdateJustifiedCheckpoint sets the justified checkpoint to the given one -func (f *ForkChoice) UpdateJustifiedCheckpoint(jc *forkchoicetypes.Checkpoint) error { - if jc == nil { - return errInvalidNilCheckpoint - } - f.store.checkpointsLock.Lock() - defer f.store.checkpointsLock.Unlock() - f.store.prevJustifiedCheckpoint = f.store.justifiedCheckpoint - f.store.justifiedCheckpoint = jc - bj := f.store.bestJustifiedCheckpoint - if bj == nil || bj.Root == params.BeaconConfig().ZeroHash || jc.Epoch > bj.Epoch { - f.store.bestJustifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: jc.Epoch, Root: jc.Root} - } - return nil -} - -// UpdateFinalizedCheckpoint sets the finalized checkpoint to the given one -func (f *ForkChoice) UpdateFinalizedCheckpoint(fc *forkchoicetypes.Checkpoint) error { - if fc == nil { - return errInvalidNilCheckpoint - } - f.store.checkpointsLock.Lock() - defer f.store.checkpointsLock.Unlock() - f.store.finalizedCheckpoint = fc - return nil -} - -// InsertOptimisticChain inserts all nodes corresponding to blocks in the slice -// `blocks`. It includes all blocks **except** the first one. -func (f *ForkChoice) InsertOptimisticChain(ctx context.Context, chain []*forkchoicetypes.BlockAndCheckpoints) error { - if len(chain) == 0 { - return nil - } - for i := len(chain) - 1; i > 0; i-- { - b := chain[i].Block - r := chain[i-1].Block.ParentRoot() - parentRoot := b.ParentRoot() - payloadHash, err := blocks.GetBlockPayloadHash(b) - if err != nil { - return err - } - if _, err := f.store.insert(ctx, - b.Slot(), r, parentRoot, payloadHash, - chain[i].JustifiedCheckpoint.Epoch, chain[i].FinalizedCheckpoint.Epoch); err != nil { - return err - } - if err := f.updateCheckpoints(ctx, chain[i].JustifiedCheckpoint, chain[i].FinalizedCheckpoint); err != nil { - return err - } - } - return nil -} - -// SetGenesisTime sets the genesisTime tracked by forkchoice -func (f *ForkChoice) SetGenesisTime(genesisTime uint64) { - f.store.genesisTime = genesisTime -} - -// SetOriginRoot sets the genesis block root -func (f *ForkChoice) SetOriginRoot(root [32]byte) { - f.store.originRoot = root -} - -// CachedHeadRoot returns the last cached head root -func (f *ForkChoice) CachedHeadRoot() [32]byte { - return f.store.lastHeadRoot -} - -// FinalizedPayloadBlockHash returns the hash of the payload at the finalized checkpoint -func (f *ForkChoice) FinalizedPayloadBlockHash() [32]byte { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - root := f.FinalizedCheckpoint().Root - idx := f.store.nodesIndices[root] - if idx >= uint64(len(f.store.nodes)) { - // This should not happen - return [32]byte{} - } - node := f.store.nodes[idx] - return node.payloadHash -} - -// JustifiedPayloadBlockHash returns the hash of the payload at the justified checkpoint -func (f *ForkChoice) JustifiedPayloadBlockHash() [32]byte { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - root := f.JustifiedCheckpoint().Root - idx := f.store.nodesIndices[root] - if idx >= uint64(len(f.store.nodes)) { - // This should not happen - return [32]byte{} - } - node := f.store.nodes[idx] - return node.payloadHash -} - -// ReceivedBlocksLastEpoch returns the number of blocks received in the last epoch -func (f *ForkChoice) ReceivedBlocksLastEpoch() (uint64, error) { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - count := uint64(0) - lowerBound := slots.CurrentSlot(f.store.genesisTime) - var err error - if lowerBound > fieldparams.SlotsPerEpoch { - lowerBound, err = lowerBound.SafeSub(fieldparams.SlotsPerEpoch) - if err != nil { - return 0, err - } - } - - for _, s := range f.store.receivedBlocksLastEpoch { - if s != 0 && lowerBound <= s { - count++ - } - } - return count, nil -} - -func (*ForkChoice) ForkChoiceDump(_ context.Context) (*v1.ForkChoiceResponse, error) { - return nil, errors.New("ForkChoiceDump is not supported by protoarray") -} - -// HighestReceivedBlockSlot returns the highest slot received by the forkchoice -func (f *ForkChoice) HighestReceivedBlockSlot() types.Slot { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - if len(f.store.nodes) == 0 { - return 0 - } - idx := uint64(len(f.store.nodes) - 1) - if f.store.highestReceivedIndex < idx { - idx = f.store.highestReceivedIndex - } - return f.store.nodes[idx].slot -} - -// HighestReceivedBlockRoot returns the highest slot root received by the forkchoice -func (f *ForkChoice) HighestReceivedBlockRoot() [32]byte { - f.store.nodesLock.RLock() - defer f.store.nodesLock.RUnlock() - if len(f.store.nodes) == 0 { - return [32]byte{} - } - idx := uint64(len(f.store.nodes) - 1) - if f.store.highestReceivedIndex < idx { - idx = f.store.highestReceivedIndex - } - return f.store.nodes[idx].root -} diff --git a/beacon-chain/forkchoice/protoarray/store_test.go b/beacon-chain/forkchoice/protoarray/store_test.go deleted file mode 100644 index efbb42d344d6..000000000000 --- a/beacon-chain/forkchoice/protoarray/store_test.go +++ /dev/null @@ -1,1469 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - "time" - - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/config/features" - "github.com/prysmaticlabs/prysm/v3/config/params" - "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" - "github.com/prysmaticlabs/prysm/v3/testing/util" -) - -func TestStore_PruneThreshold(t *testing.T) { - s := &Store{ - pruneThreshold: defaultPruneThreshold, - } - if got := s.PruneThreshold(); got != defaultPruneThreshold { - t.Errorf("PruneThreshold() = %v, want %v", got, defaultPruneThreshold) - } -} - -func TestStore_JustifiedEpoch(t *testing.T) { - j := types.Epoch(100) - f := setup(j, j) - require.Equal(t, j, f.JustifiedCheckpoint().Epoch) -} - -func TestStore_FinalizedEpoch(t *testing.T) { - j := types.Epoch(50) - f := setup(j, j) - require.Equal(t, j, f.FinalizedCheckpoint().Epoch) -} - -func TestForkChoice_HasNode(t *testing.T) { - nodeIndices := map[[32]byte]uint64{ - {'a'}: 1, - {'b'}: 2, - } - s := &Store{ - nodesIndices: nodeIndices, - } - f := &ForkChoice{store: s} - require.Equal(t, true, f.HasNode([32]byte{'a'})) -} - -func TestStore_Head_UnknownJustifiedRoot(t *testing.T) { - s := &Store{nodesIndices: make(map[[32]byte]uint64)} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'a'}} - - _, err := s.head(context.Background()) - assert.ErrorContains(t, errUnknownJustifiedRoot.Error(), err) -} - -func TestStore_Head_UnknownJustifiedIndex(t *testing.T) { - r := [32]byte{'A'} - indices := make(map[[32]byte]uint64) - indices[r] = 1 - s := &Store{nodesIndices: indices} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - - _, err := s.head(context.Background()) - assert.ErrorContains(t, errInvalidJustifiedIndex.Error(), err) -} - -func TestStore_Head_Itself(t *testing.T) { - r := [32]byte{'A'} - indices := map[[32]byte]uint64{r: 0} - - // Since the justified node does not have a best descendant so the best node - // is itself. - s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, parent: NonExistentNode, bestDescendant: NonExistentNode}}, canonicalNodes: make(map[[32]byte]bool)} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - h, err := s.head(context.Background()) - require.NoError(t, err) - assert.Equal(t, r, h) -} - -func TestStore_Head_BestDescendant(t *testing.T) { - r := [32]byte{'A'} - best := [32]byte{'B'} - indices := map[[32]byte]uint64{r: 0, best: 1} - - // Since the justified node's best descendant is at index 1, and its root is `best`, - // the head should be `best`. - s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, bestDescendant: 1, parent: NonExistentNode}, {root: best, parent: 0}}, canonicalNodes: make(map[[32]byte]bool)} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - h, err := s.head(context.Background()) - require.NoError(t, err) - assert.Equal(t, best, h) -} - -func TestStore_Head_ContextCancelled(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - r := [32]byte{'A'} - best := [32]byte{'B'} - indices := map[[32]byte]uint64{r: 0, best: 1} - - s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, parent: NonExistentNode, bestDescendant: 1}, {root: best, parent: 0}}, canonicalNodes: make(map[[32]byte]bool)} - cancel() - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 0, Root: r} - _, err := s.head(ctx) - require.ErrorContains(t, "context canceled", err) -} - -func TestStore_Insert_UnknownParent(t *testing.T) { - // The new node does not have a parent. - s := &Store{nodesIndices: make(map[[32]byte]uint64), payloadIndices: make(map[[32]byte]uint64)} - _, err := s.insert(context.Background(), 100, [32]byte{'A'}, [32]byte{'B'}, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - assert.Equal(t, 1, len(s.nodes), "Did not insert block") - assert.Equal(t, 1, len(s.nodesIndices), "Did not insert block") - assert.Equal(t, NonExistentNode, s.nodes[0].parent, "Incorrect parent") - assert.Equal(t, types.Epoch(1), s.nodes[0].justifiedEpoch, "Incorrect justification") - assert.Equal(t, types.Epoch(1), s.nodes[0].finalizedEpoch, "Incorrect finalization") - assert.Equal(t, [32]byte{'A'}, s.nodes[0].root, "Incorrect root") -} - -func TestStore_Insert_KnownParent(t *testing.T) { - // Similar to UnknownParent test, but this time the new node has a valid parent already in store. - // The new node builds on top of the parent. - s := &Store{nodesIndices: make(map[[32]byte]uint64), payloadIndices: make(map[[32]byte]uint64)} - s.nodes = []*Node{{}} - p := [32]byte{'B'} - s.nodesIndices[p] = 0 - payloadHash := [32]byte{'c'} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{} - _, err := s.insert(context.Background(), 100, [32]byte{'A'}, p, payloadHash, 1, 1) - require.NoError(t, err) - assert.Equal(t, 2, len(s.nodes), "Did not insert block") - assert.Equal(t, 2, len(s.nodesIndices), "Did not insert block") - assert.Equal(t, uint64(0), s.nodes[1].parent, "Incorrect parent") - assert.Equal(t, types.Epoch(1), s.nodes[1].justifiedEpoch, "Incorrect justification") - assert.Equal(t, types.Epoch(1), s.nodes[1].finalizedEpoch, "Incorrect finalization") - assert.Equal(t, [32]byte{'A'}, s.nodes[1].root, "Incorrect root") - assert.Equal(t, payloadHash, s.nodes[1].payloadHash) -} - -func TestStore_ApplyScoreChanges_InvalidDeltaLength(t *testing.T) { - s := &Store{} - - // This will fail because node indices has length of 0, and delta list has a length of 1. - err := s.applyWeightChanges(context.Background(), []uint64{}, []int{1}) - assert.ErrorContains(t, errInvalidDeltaLength.Error(), err) -} - -func TestStore_ApplyScoreChanges_UpdateWeightsPositiveDelta(t *testing.T) { - // Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other. - s := &Store{nodes: []*Node{ - {root: [32]byte{'A'}, weight: 100}, - {root: [32]byte{'A'}, weight: 100}, - {parent: 1, root: [32]byte{'A'}, weight: 100}}} - - // Each node gets one unique vote. The weight should look like 103 <- 102 <- 101 because - // they get propagated back. - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{} - require.NoError(t, s.applyWeightChanges(context.Background(), []uint64{}, []int{1, 1, 1})) - assert.Equal(t, uint64(103), s.nodes[0].weight) - assert.Equal(t, uint64(102), s.nodes[1].weight) - assert.Equal(t, uint64(101), s.nodes[2].weight) -} - -func TestStore_ApplyScoreChanges_UpdateWeightsNegativeDelta(t *testing.T) { - // Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other. - s := &Store{nodes: []*Node{ - {root: [32]byte{'A'}, weight: 100}, - {root: [32]byte{'A'}, weight: 100}, - {parent: 1, root: [32]byte{'A'}, weight: 100}}} - - // Each node gets one unique vote which contributes to negative delta. - // The weight should look like 97 <- 98 <- 99 because they get propagated back. - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{} - require.NoError(t, s.applyWeightChanges(context.Background(), []uint64{}, []int{-1, -1, -1})) - assert.Equal(t, uint64(97), s.nodes[0].weight) - assert.Equal(t, uint64(98), s.nodes[1].weight) - assert.Equal(t, uint64(99), s.nodes[2].weight) -} - -func TestStore_ApplyScoreChanges_UpdateWeightsMixedDelta(t *testing.T) { - // Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other. - s := &Store{nodes: []*Node{ - {root: [32]byte{'A'}, weight: 100}, - {root: [32]byte{'A'}, weight: 100}, - {parent: 1, root: [32]byte{'A'}, weight: 100}}} - - // Each node gets one mixed vote. The weight should look like 100 <- 200 <- 250. - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{} - require.NoError(t, s.applyWeightChanges(context.Background(), []uint64{}, []int{-100, -50, 150})) - assert.Equal(t, uint64(100), s.nodes[0].weight) - assert.Equal(t, uint64(200), s.nodes[1].weight) - assert.Equal(t, uint64(250), s.nodes[2].weight) -} - -func TestStore_UpdateBestChildAndDescendant_RemoveChild(t *testing.T) { - // Make parent's best child equal's to input child index and child is not viable. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{nodes: []*Node{{bestChild: 1}, {}}, justifiedCheckpoint: jc, finalizedCheckpoint: fc} - require.NoError(t, s.updateBestChildAndDescendant(0, 1)) - - // Verify parent's best child and best descendant are `none`. - assert.Equal(t, NonExistentNode, s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, NonExistentNode, s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_UpdateDescendant(t *testing.T) { - // Make parent's best child equal to child index and child is viable. - s := &Store{nodes: []*Node{{bestChild: 1}, {bestDescendant: NonExistentNode}}} - s.justifiedCheckpoint = &forkchoicetypes.Checkpoint{} - s.finalizedCheckpoint = &forkchoicetypes.Checkpoint{} - require.NoError(t, s.updateBestChildAndDescendant(0, 1)) - - // Verify parent's best child is the same and best descendant is not set to child index. - assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(1), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_ChangeChildByViability(t *testing.T) { - // Make parent's best child not equal to child index, child leads to viable index and - // parent's best child doesn't lead to viable index. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are set to child index. - assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_ChangeChildByWeight(t *testing.T) { - // Make parent's best child not equal to child index, child leads to viable index and - // parents best child leads to viable index but child has more weight than parent's best child. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1, weight: 1}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are set to child index. - assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_ChangeChildAtLeaf(t *testing.T) { - // Make parent's best child to none and input child leads to viable index. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are set to child index. - assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_NoChangeByViability(t *testing.T) { - // Make parent's best child not equal to child index, child leads to not viable index and - // parents best child leads to viable index. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are not changed. - assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_NoChangeByWeight(t *testing.T) { - // Make parent's best child not equal to child index, child leads to viable index and - // parents best child leads to viable index but parent's best child has more weight. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1, weight: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are not changed. - assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_UpdateBestChildAndDescendant_NoChangeAtLeaf(t *testing.T) { - // Make parent's best child to none and input child does not lead to viable index. - jc := &forkchoicetypes.Checkpoint{Epoch: 1} - fc := &forkchoicetypes.Checkpoint{Epoch: 1} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{{bestChild: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}, - {bestDescendant: NonExistentNode}}} - require.NoError(t, s.updateBestChildAndDescendant(0, 2)) - - // Verify parent's best child and best descendant are not changed. - assert.Equal(t, NonExistentNode, s.nodes[0].bestChild, "Did not get correct best child index") - assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index") -} - -func TestStore_Prune_LessThanThreshold(t *testing.T) { - // Define 100 nodes in store. - numOfNodes := 100 - indices := make(map[[32]byte]uint64) - nodes := make([]*Node, 0) - indices[indexToHash(uint64(0))] = uint64(0) - nodes = append(nodes, &Node{ - slot: types.Slot(0), - root: indexToHash(uint64(0)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(1), - parent: NonExistentNode, - }) - for i := 1; i < numOfNodes-1; i++ { - indices[indexToHash(uint64(i))] = uint64(i) - nodes = append(nodes, &Node{ - slot: types.Slot(i), - root: indexToHash(uint64(i)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(i + 1), - parent: uint64(i) - 1, - }) - } - indices[indexToHash(uint64(numOfNodes-1))] = uint64(numOfNodes - 1) - nodes = append(nodes, &Node{ - slot: types.Slot(numOfNodes - 1), - root: indexToHash(uint64(numOfNodes - 1)), - bestDescendant: NonExistentNode, - bestChild: NonExistentNode, - parent: uint64(numOfNodes - 2), - }) - - s := &Store{nodes: nodes, nodesIndices: indices, pruneThreshold: 100} - - // Finalized root is at index 99 so everything before 99 should be pruned, - // but PruneThreshold is at 100 so nothing will be pruned. - fc := &forkchoicetypes.Checkpoint{Epoch: 3, Root: indexToHash(99)} - s.finalizedCheckpoint = fc - require.NoError(t, s.prune(context.Background())) - assert.Equal(t, 100, len(s.nodes), "Incorrect nodes count") - assert.Equal(t, 100, len(s.nodesIndices), "Incorrect node indices count") -} - -func TestStore_Prune_MoreThanThreshold(t *testing.T) { - // Define 100 nodes in store. - numOfNodes := 100 - indices := make(map[[32]byte]uint64) - nodes := make([]*Node, 0) - indices[indexToHash(uint64(0))] = uint64(0) - nodes = append(nodes, &Node{ - slot: types.Slot(0), - root: indexToHash(uint64(0)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(1), - parent: NonExistentNode, - }) - for i := 1; i < numOfNodes-1; i++ { - indices[indexToHash(uint64(i))] = uint64(i) - nodes = append(nodes, &Node{ - slot: types.Slot(i), - root: indexToHash(uint64(i)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(i + 1), - parent: uint64(i) - 1, - }) - } - nodes = append(nodes, &Node{ - slot: types.Slot(numOfNodes - 1), - root: indexToHash(uint64(numOfNodes - 1)), - bestDescendant: NonExistentNode, - bestChild: NonExistentNode, - parent: uint64(numOfNodes - 2), - }) - indices[indexToHash(uint64(numOfNodes-1))] = uint64(numOfNodes - 1) - s := &Store{nodes: nodes, nodesIndices: indices, canonicalNodes: map[[32]byte]bool{}, payloadIndices: map[[32]byte]uint64{}} - - // Finalized root is at index 99 so everything before 99 should be pruned. - fc := &forkchoicetypes.Checkpoint{Epoch: 3, Root: indexToHash(99)} - s.finalizedCheckpoint = fc - require.NoError(t, s.prune(context.Background())) - assert.Equal(t, 1, len(s.nodes), "Incorrect nodes count") - assert.Equal(t, 1, len(s.nodesIndices), "Incorrect node indices count") -} - -func TestStore_Prune_MoreThanOnce(t *testing.T) { - // Define 100 nodes in store. - numOfNodes := 100 - indices := make(map[[32]byte]uint64) - nodes := make([]*Node, 0) - nodes = append(nodes, &Node{ - slot: types.Slot(0), - root: indexToHash(uint64(0)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(1), - parent: NonExistentNode, - }) - for i := 1; i < numOfNodes-1; i++ { - indices[indexToHash(uint64(i))] = uint64(i) - nodes = append(nodes, &Node{ - slot: types.Slot(i), - root: indexToHash(uint64(i)), - bestDescendant: uint64(numOfNodes - 1), - bestChild: uint64(i + 1), - parent: uint64(i) - 1, - }) - } - nodes = append(nodes, &Node{ - slot: types.Slot(numOfNodes - 1), - root: indexToHash(uint64(numOfNodes - 1)), - bestDescendant: NonExistentNode, - bestChild: NonExistentNode, - parent: uint64(numOfNodes - 2), - }) - - s := &Store{nodes: nodes, nodesIndices: indices, canonicalNodes: map[[32]byte]bool{}, payloadIndices: map[[32]byte]uint64{}} - - // Finalized root is at index 11 so everything before 11 should be pruned. - fc := &forkchoicetypes.Checkpoint{Epoch: 1, Root: indexToHash(10)} - s.finalizedCheckpoint = fc - require.NoError(t, s.prune(context.Background())) - assert.Equal(t, 90, len(s.nodes), "Incorrect nodes count") - assert.Equal(t, 90, len(s.nodesIndices), "Incorrect node indices count") - - // One more time. - s.finalizedCheckpoint.Root = indexToHash(20) - require.NoError(t, s.prune(context.Background())) - assert.Equal(t, 80, len(s.nodes), "Incorrect nodes count") - assert.Equal(t, 80, len(s.nodesIndices), "Incorrect node indices count") -} - -// This unit tests starts with a simple branch like this -// -// - 1 -// / -// -- 0 -- 2 -// -// And we finalize 1. As a result only 1 should survive -func TestStore_Prune_NoDanglingBranch(t *testing.T) { - nodes := []*Node{ - { - slot: 100, - bestChild: 1, - bestDescendant: 1, - root: indexToHash(uint64(0)), - parent: NonExistentNode, - payloadHash: [32]byte{'A'}, - }, - { - slot: 101, - root: indexToHash(uint64(1)), - bestChild: NonExistentNode, - bestDescendant: NonExistentNode, - parent: 0, - payloadHash: [32]byte{'B'}, - }, - { - slot: 101, - root: indexToHash(uint64(2)), - parent: 0, - bestChild: NonExistentNode, - bestDescendant: NonExistentNode, - payloadHash: [32]byte{'C'}, - }, - } - s := &Store{ - pruneThreshold: 0, - nodes: nodes, - nodesIndices: map[[32]byte]uint64{ - indexToHash(uint64(0)): 0, - indexToHash(uint64(1)): 1, - indexToHash(uint64(2)): 2, - }, - canonicalNodes: map[[32]byte]bool{ - indexToHash(uint64(0)): true, - indexToHash(uint64(1)): true, - indexToHash(uint64(2)): true, - }, - payloadIndices: map[[32]byte]uint64{ - {'A'}: 0, - {'B'}: 1, - {'C'}: 2, - }, - } - fc := &forkchoicetypes.Checkpoint{Epoch: 1, Root: indexToHash(1)} - s.finalizedCheckpoint = fc - require.NoError(t, s.prune(context.Background())) - require.Equal(t, 1, len(s.nodes)) - require.Equal(t, 1, len(s.nodesIndices)) - require.Equal(t, 1, len(s.canonicalNodes)) - require.Equal(t, 1, len(s.payloadIndices)) -} - -// This test starts with the following branching diagram -/// We start with the following diagram -// -// E -- F -// / -// C -- D -// / \ -// A -- B G -- H -- I -// \ \ -// J -- K -- L -// -// -func TestStore_PruneBranched(t *testing.T) { - ctx := context.Background() - - tests := []struct { - finalizedRoot [32]byte - wantedCanonical [32]byte - wantedNonCanonical [32]byte - canonicalCount int - payloadHash [32]byte - payloadIndex uint64 - nonExistentPayload [32]byte - }{ - { - [32]byte{'f'}, - [32]byte{'f'}, - [32]byte{'a'}, - 1, - [32]byte{'F'}, - 0, - [32]byte{'H'}, - }, - { - [32]byte{'d'}, - [32]byte{'e'}, - [32]byte{'i'}, - 3, - [32]byte{'E'}, - 1, - [32]byte{'C'}, - }, - { - [32]byte{'b'}, - [32]byte{'f'}, - [32]byte{'h'}, - 5, - [32]byte{'D'}, - 3, - [32]byte{'A'}, - }, - } - - for _, tc := range tests { - f := setup(1, 1) - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.store.pruneThreshold = 0 - require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'f'})) - require.Equal(t, true, f.IsCanonical([32]byte{'a'})) - require.Equal(t, true, f.IsCanonical([32]byte{'f'})) - - f.store.finalizedCheckpoint.Root = tc.finalizedRoot - require.NoError(t, f.store.prune(ctx)) - require.Equal(t, tc.canonicalCount, len(f.store.canonicalNodes)) - require.Equal(t, true, f.IsCanonical(tc.wantedCanonical)) - require.Equal(t, false, f.IsCanonical(tc.wantedNonCanonical)) - require.Equal(t, tc.payloadIndex, f.store.payloadIndices[tc.payloadHash]) - _, ok := f.store.payloadIndices[tc.nonExistentPayload] - require.Equal(t, false, ok) - } -} - -func TestStore_CommonAncestor(t *testing.T) { - ctx := context.Background() - f := setup(0, 0) - - // /-- b -- d -- e - // a - // \-- c -- f - // \-- g - // \ -- h -- i -- j - state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 1, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 2, [32]byte{'c'}, [32]byte{'a'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 3, [32]byte{'d'}, [32]byte{'b'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 4, [32]byte{'e'}, [32]byte{'d'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 5, [32]byte{'f'}, [32]byte{'c'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 6, [32]byte{'g'}, [32]byte{'c'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 7, [32]byte{'h'}, [32]byte{'c'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 8, [32]byte{'i'}, [32]byte{'h'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 9, [32]byte{'j'}, [32]byte{'i'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - tests := []struct { - name string - r1 [32]byte - r2 [32]byte - wantRoot [32]byte - wantSlot types.Slot - }{ - { - name: "Common ancestor between c and b is a", - r1: [32]byte{'c'}, - r2: [32]byte{'b'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between c and d is a", - r1: [32]byte{'c'}, - r2: [32]byte{'d'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between c and e is a", - r1: [32]byte{'c'}, - r2: [32]byte{'e'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between g and f is c", - r1: [32]byte{'g'}, - r2: [32]byte{'f'}, - wantRoot: [32]byte{'c'}, - wantSlot: 2, - }, - { - name: "Common ancestor between f and h is c", - r1: [32]byte{'f'}, - r2: [32]byte{'h'}, - wantRoot: [32]byte{'c'}, - wantSlot: 2, - }, - { - name: "Common ancestor between g and h is c", - r1: [32]byte{'g'}, - r2: [32]byte{'h'}, - wantRoot: [32]byte{'c'}, - wantSlot: 2, - }, - { - name: "Common ancestor between b and h is a", - r1: [32]byte{'b'}, - r2: [32]byte{'h'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between e and h is a", - r1: [32]byte{'e'}, - r2: [32]byte{'h'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between i and f is c", - r1: [32]byte{'i'}, - r2: [32]byte{'f'}, - wantRoot: [32]byte{'c'}, - wantSlot: 2, - }, - { - name: "Common ancestor between e and h is a", - r1: [32]byte{'j'}, - r2: [32]byte{'g'}, - wantRoot: [32]byte{'c'}, - wantSlot: 2, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - gotRoot, gotSlot, err := f.CommonAncestor(ctx, tc.r1, tc.r2) - require.NoError(t, err) - require.Equal(t, tc.wantRoot, gotRoot) - require.Equal(t, tc.wantSlot, gotSlot) - }) - } - - // a -- b -- c -- d - f = setup(0, 0) - state, blkRoot, err = prepareForkchoiceState(ctx, 0, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 1, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 2, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 3, [32]byte{'d'}, [32]byte{'c'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - tests = []struct { - name string - r1 [32]byte - r2 [32]byte - wantRoot [32]byte - wantSlot types.Slot - }{ - { - name: "Common ancestor between a and b is a", - r1: [32]byte{'a'}, - r2: [32]byte{'b'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - { - name: "Common ancestor between b and d is b", - r1: [32]byte{'d'}, - r2: [32]byte{'b'}, - wantRoot: [32]byte{'b'}, - wantSlot: 1, - }, - { - name: "Common ancestor between d and a is a", - r1: [32]byte{'d'}, - r2: [32]byte{'a'}, - wantRoot: [32]byte{'a'}, - wantSlot: 0, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - gotRoot, gotSlot, err := f.CommonAncestor(ctx, tc.r1, tc.r2) - require.NoError(t, err) - require.Equal(t, tc.wantRoot, gotRoot) - require.Equal(t, tc.wantSlot, gotSlot) - }) - } - - // Equal inputs should return the same root. - r, s, err := f.CommonAncestor(ctx, [32]byte{'b'}, [32]byte{'b'}) - require.NoError(t, err) - require.Equal(t, [32]byte{'b'}, r) - require.Equal(t, types.Slot(1), s) - // Requesting finalized root (last node) should return the same root. - r, s, err = f.CommonAncestor(ctx, [32]byte{'a'}, [32]byte{'a'}) - require.NoError(t, err) - require.Equal(t, [32]byte{'a'}, r) - require.Equal(t, types.Slot(0), s) - // Requesting unknown root - _, _, err = f.CommonAncestor(ctx, [32]byte{'a'}, [32]byte{'z'}) - require.ErrorIs(t, err, forkchoice.ErrUnknownCommonAncestor) - _, _, err = f.CommonAncestor(ctx, [32]byte{'z'}, [32]byte{'a'}) - require.ErrorIs(t, err, forkchoice.ErrUnknownCommonAncestor) - state, blkRoot, err = prepareForkchoiceState(ctx, 100, [32]byte{'y'}, [32]byte{'z'}, [32]byte{}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - // broken link - _, _, err = f.CommonAncestor(ctx, [32]byte{'y'}, [32]byte{'a'}) - require.ErrorIs(t, err, forkchoice.ErrUnknownCommonAncestor) -} - -func TestStore_LeadsToViableHead(t *testing.T) { - tests := []struct { - n *Node - justifiedEpoch types.Epoch - finalizedEpoch types.Epoch - want bool - }{ - {&Node{}, 0, 0, true}, - {&Node{}, 1, 0, false}, - {&Node{}, 0, 1, false}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false}, - {&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true}, - } - for _, tc := range tests { - jc := &forkchoicetypes.Checkpoint{Epoch: tc.justifiedEpoch} - fc := &forkchoicetypes.Checkpoint{Epoch: tc.finalizedEpoch} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - nodes: []*Node{tc.n}, - } - got, err := s.leadsToViableHead(tc.n) - require.NoError(t, err) - assert.Equal(t, tc.want, got) - } -} - -func TestStore_ViableForHead(t *testing.T) { - tests := []struct { - n *Node - justifiedEpoch types.Epoch - finalizedEpoch types.Epoch - want bool - }{ - {&Node{}, 0, 0, true}, - {&Node{}, 1, 0, false}, - {&Node{}, 0, 1, false}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false}, - {&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true}, - } - for _, tc := range tests { - jc := &forkchoicetypes.Checkpoint{Epoch: tc.justifiedEpoch} - fc := &forkchoicetypes.Checkpoint{Epoch: tc.finalizedEpoch} - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - } - assert.Equal(t, tc.want, s.viableForHead(tc.n)) - } -} - -func TestStore_ViableForHead_DefensivePull(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: true, - }) - defer resetCfg() - - tests := []struct { - n *Node - justifiedEpoch types.Epoch - finalizedEpoch types.Epoch - currentEpoch types.Epoch - want bool - }{ - {&Node{}, 0, 0, 0, true}, - {&Node{}, 1, 0, 1, false}, - {&Node{}, 0, 1, 1, false}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, 1, true}, - {&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, 2, false}, - {&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, 3, true}, - {&Node{unrealizedFinalizedEpoch: 3, unrealizedJustifiedEpoch: 4}, 3, 2, 4, true}, - {&Node{unrealizedFinalizedEpoch: 2, unrealizedJustifiedEpoch: 3}, 3, 2, 4, true}, - {&Node{unrealizedFinalizedEpoch: 1, unrealizedJustifiedEpoch: 2}, 3, 2, 4, false}, - } - for _, tc := range tests { - jc := &forkchoicetypes.Checkpoint{Epoch: tc.justifiedEpoch} - fc := &forkchoicetypes.Checkpoint{Epoch: tc.finalizedEpoch} - currentTime := uint64(time.Now().Unix()) - driftSeconds := uint64(params.BeaconConfig().SlotsPerEpoch) * params.BeaconConfig().SecondsPerSlot - s := &Store{ - justifiedCheckpoint: jc, - finalizedCheckpoint: fc, - genesisTime: currentTime - driftSeconds*uint64(tc.currentEpoch), - } - assert.Equal(t, tc.want, s.viableForHead(tc.n)) - } -} - -func TestStore_HasParent(t *testing.T) { - tests := []struct { - m map[[32]byte]uint64 - n []*Node - r [32]byte - want bool - }{ - {r: [32]byte{'a'}, want: false}, - {m: map[[32]byte]uint64{{'a'}: 0}, r: [32]byte{'a'}, want: false}, - {m: map[[32]byte]uint64{{'a'}: 0}, r: [32]byte{'a'}, - n: []*Node{{parent: NonExistentNode}}, want: false}, - {m: map[[32]byte]uint64{{'a'}: 0}, - n: []*Node{{parent: 0}}, r: [32]byte{'a'}, - want: true}, - } - for _, tc := range tests { - f := &ForkChoice{store: &Store{ - nodesIndices: tc.m, - nodes: tc.n, - }} - assert.Equal(t, tc.want, f.HasParent(tc.r)) - } -} - -func TestStore_AncestorRoot(t *testing.T) { - ctx := context.Background() - f := &ForkChoice{store: &Store{}} - f.store.nodesIndices = map[[32]byte]uint64{} - _, err := f.AncestorRoot(ctx, [32]byte{'a'}, 0) - assert.ErrorContains(t, "node does not exist", err) - f.store.nodesIndices[[32]byte{'a'}] = 0 - _, err = f.AncestorRoot(ctx, [32]byte{'a'}, 0) - assert.ErrorContains(t, "node index out of range", err) - f.store.nodesIndices[[32]byte{'b'}] = 1 - f.store.nodesIndices[[32]byte{'c'}] = 2 - f.store.nodes = []*Node{ - {slot: 1, root: [32]byte{'a'}, parent: NonExistentNode}, - {slot: 2, root: [32]byte{'b'}, parent: 0}, - {slot: 3, root: [32]byte{'c'}, parent: 1}, - } - - r, err := f.AncestorRoot(ctx, [32]byte{'c'}, 1) - require.NoError(t, err) - assert.Equal(t, r, [32]byte{'a'}) - r, err = f.AncestorRoot(ctx, [32]byte{'c'}, 2) - require.NoError(t, err) - assert.Equal(t, r, [32]byte{'b'}) -} - -func TestStore_AncestorRootOutOfBound(t *testing.T) { - ctx := context.Background() - f := &ForkChoice{store: &Store{}} - f.store.nodesIndices = map[[32]byte]uint64{} - _, err := f.AncestorRoot(ctx, [32]byte{'a'}, 0) - assert.ErrorContains(t, "node does not exist", err) - f.store.nodesIndices[[32]byte{'a'}] = 0 - _, err = f.AncestorRoot(ctx, [32]byte{'a'}, 0) - assert.ErrorContains(t, "node index out of range", err) - f.store.nodesIndices[[32]byte{'b'}] = 1 - f.store.nodesIndices[[32]byte{'c'}] = 2 - f.store.nodes = []*Node{ - {slot: 1, root: [32]byte{'a'}, parent: NonExistentNode}, - {slot: 2, root: [32]byte{'b'}, parent: 100}, // Out of bound parent. - {slot: 3, root: [32]byte{'c'}, parent: 1}, - } - - _, err = f.AncestorRoot(ctx, [32]byte{'c'}, 1) - require.ErrorContains(t, "node index out of range", err) -} - -func TestStore_UpdateCanonicalNodes_WholeList(t *testing.T) { - ctx := context.Background() - f := &ForkChoice{store: &Store{}} - f.store.canonicalNodes = map[[32]byte]bool{} - f.store.nodesIndices = map[[32]byte]uint64{} - f.store.nodes = []*Node{ - {slot: 1, root: [32]byte{'a'}, parent: NonExistentNode}, - {slot: 2, root: [32]byte{'b'}, parent: 0}, - {slot: 3, root: [32]byte{'c'}, parent: 1}, - } - f.store.nodesIndices[[32]byte{'c'}] = 2 - require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'c'})) - require.Equal(t, len(f.store.nodes), len(f.store.canonicalNodes)) - require.Equal(t, true, f.IsCanonical([32]byte{'a'})) - require.Equal(t, true, f.IsCanonical([32]byte{'b'})) - require.Equal(t, true, f.IsCanonical([32]byte{'c'})) - idxc := f.store.nodesIndices[[32]byte{'c'}] - _, ok := f.store.nodesIndices[[32]byte{'d'}] - require.Equal(t, idxc, uint64(2)) - require.Equal(t, false, ok) -} - -func TestStore_UpdateCanonicalNodes_ParentAlreadyIn(t *testing.T) { - ctx := context.Background() - f := &ForkChoice{store: &Store{}} - f.store.canonicalNodes = map[[32]byte]bool{} - f.store.nodesIndices = map[[32]byte]uint64{} - f.store.nodes = []*Node{ - {}, - {slot: 2, root: [32]byte{'b'}, parent: 0}, - {slot: 3, root: [32]byte{'c'}, parent: 1}, - } - f.store.nodesIndices[[32]byte{'c'}] = 2 - f.store.canonicalNodes[[32]byte{'b'}] = true - require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'c'})) - require.Equal(t, len(f.store.nodes)-1, len(f.store.canonicalNodes)) - - require.Equal(t, true, f.IsCanonical([32]byte{'c'})) - require.Equal(t, true, f.IsCanonical([32]byte{'b'})) -} - -func TestStore_UpdateCanonicalNodes_ContextCancelled(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - f := &ForkChoice{store: &Store{}} - f.store.canonicalNodes = map[[32]byte]bool{} - f.store.nodesIndices = map[[32]byte]uint64{} - f.store.nodes = []*Node{ - {slot: 1, root: [32]byte{'a'}, parent: NonExistentNode}, - {slot: 2, root: [32]byte{'b'}, parent: 0}, - {slot: 3, root: [32]byte{'c'}, parent: 1}, - } - f.store.nodesIndices[[32]byte{'c'}] = 2 - cancel() - require.ErrorContains(t, "context canceled", f.store.updateCanonicalNodes(ctx, [32]byte{'c'})) -} - -func TestStore_UpdateCanonicalNodes_RemoveOldCanonical(t *testing.T) { - ctx := context.Background() - f := &ForkChoice{store: &Store{}} - f.store.canonicalNodes = map[[32]byte]bool{} - f.store.nodesIndices = map[[32]byte]uint64{ - {'a'}: 0, - {'b'}: 1, - {'c'}: 2, - {'d'}: 3, - {'e'}: 4, - } - - f.store.nodes = []*Node{ - {slot: 1, root: [32]byte{'a'}, parent: NonExistentNode}, - {slot: 2, root: [32]byte{'b'}, parent: 0}, - {slot: 3, root: [32]byte{'c'}, parent: 1}, - {slot: 4, root: [32]byte{'d'}, parent: 1}, - {slot: 5, root: [32]byte{'e'}, parent: 3}, - } - require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'c'})) - require.Equal(t, 3, len(f.store.canonicalNodes)) - require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'e'})) - require.Equal(t, 4, len(f.store.canonicalNodes)) - require.Equal(t, true, f.IsCanonical([32]byte{'a'})) - require.Equal(t, true, f.IsCanonical([32]byte{'b'})) - require.Equal(t, true, f.IsCanonical([32]byte{'d'})) - require.Equal(t, true, f.IsCanonical([32]byte{'e'})) - _, ok := f.store.canonicalNodes[[32]byte{'c'}] - require.Equal(t, false, ok) -} - -func TestStore_RemoveEquivocating(t *testing.T) { - ctx := context.Background() - f := setup(1, 1) - // Insert a block it will be head - state, blkRoot, err := prepareForkchoiceState(ctx, 1, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - head, err := f.Head(ctx, []uint64{}) - require.NoError(t, err) - require.Equal(t, [32]byte{'a'}, head) - - // Insert two extra blocks - state, blkRoot, err = prepareForkchoiceState(ctx, 2, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 3, [32]byte{'c'}, [32]byte{'a'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - head, err = f.Head(ctx, []uint64{}) - require.NoError(t, err) - require.Equal(t, [32]byte{'c'}, head) - - // Insert two attestations for block b, it becomes head - f.ProcessAttestation(ctx, []uint64{1, 2}, [32]byte{'b'}, 1) - f.ProcessAttestation(ctx, []uint64{3}, [32]byte{'c'}, 1) - head, err = f.Head(ctx, []uint64{100, 200, 200, 300}) - require.NoError(t, err) - require.Equal(t, [32]byte{'b'}, head) - - // Process b's slashing, c is now head - f.InsertSlashedIndex(ctx, 1) - head, err = f.Head(ctx, []uint64{100, 200, 200, 300}) - require.NoError(t, err) - require.Equal(t, [32]byte{'c'}, head) - require.Equal(t, uint64(200), f.store.nodes[2].weight) - require.Equal(t, uint64(300), f.store.nodes[3].weight) - - // Process the same slashing again, should be a noop - f.InsertSlashedIndex(ctx, 1) - head, err = f.Head(ctx, []uint64{100, 200, 200, 300}) - require.NoError(t, err) - require.Equal(t, [32]byte{'c'}, head) - require.Equal(t, uint64(200), f.store.nodes[2].weight) - require.Equal(t, uint64(300), f.store.nodes[3].weight) - - // Process index where index == vote length. Should not panic. - f.InsertSlashedIndex(ctx, types.ValidatorIndex(len(f.balances))) - f.InsertSlashedIndex(ctx, types.ValidatorIndex(len(f.votes))) - require.Equal(t, true, len(f.store.slashedIndices) > 0) -} - -func TestStore_UpdateCheckpoints(t *testing.T) { - f := setup(1, 1) - jr := [32]byte{'j'} - fr := [32]byte{'f'} - jc := &forkchoicetypes.Checkpoint{Root: jr, Epoch: 3} - fc := &forkchoicetypes.Checkpoint{Root: fr, Epoch: 2} - require.NoError(t, f.UpdateJustifiedCheckpoint(jc)) - require.NoError(t, f.UpdateFinalizedCheckpoint(fc)) - require.Equal(t, f.store.justifiedCheckpoint, jc) - require.Equal(t, f.store.finalizedCheckpoint, fc) -} - -func TestStore_InsertOptimisticChain(t *testing.T) { - f := setup(1, 1) - blks := make([]*forkchoicetypes.BlockAndCheckpoints, 0) - blk := util.NewBeaconBlock() - blk.Block.Slot = 1 - pr := [32]byte{} - blk.Block.ParentRoot = pr[:] - root, err := blk.Block.HashTreeRoot() - require.NoError(t, err) - wsb, err := blocks.NewSignedBeaconBlock(blk) - require.NoError(t, err) - blks = append(blks, &forkchoicetypes.BlockAndCheckpoints{Block: wsb.Block(), - JustifiedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: params.BeaconConfig().ZeroHash[:]}, - FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: params.BeaconConfig().ZeroHash[:]}, - }) - for i := uint64(2); i < 11; i++ { - blk := util.NewBeaconBlock() - blk.Block.Slot = types.Slot(i) - copiedRoot := root - blk.Block.ParentRoot = copiedRoot[:] - wsb, err = blocks.NewSignedBeaconBlock(blk) - require.NoError(t, err) - blks = append(blks, &forkchoicetypes.BlockAndCheckpoints{Block: wsb.Block(), - JustifiedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: params.BeaconConfig().ZeroHash[:]}, - FinalizedCheckpoint: ðpb.Checkpoint{Epoch: 1, Root: params.BeaconConfig().ZeroHash[:]}, - }) - root, err = blk.Block.HashTreeRoot() - require.NoError(t, err) - } - args := make([]*forkchoicetypes.BlockAndCheckpoints, 10) - for i := 0; i < len(blks); i++ { - args[i] = blks[10-i-1] - } - require.NoError(t, f.InsertOptimisticChain(context.Background(), args)) - - f = setup(1, 1) - require.NoError(t, f.InsertOptimisticChain(context.Background(), args[2:])) -} - -func TestForkChoice_UpdateCheckpoints(t *testing.T) { - ctx := context.Background() - tests := []struct { - name string - justified *forkchoicetypes.Checkpoint - bestJustified *forkchoicetypes.Checkpoint - finalized *forkchoicetypes.Checkpoint - newJustified *forkchoicetypes.Checkpoint - newFinalized *forkchoicetypes.Checkpoint - wantedJustified *forkchoicetypes.Checkpoint - wantedBestJustified *forkchoicetypes.Checkpoint - wantedFinalized *forkchoicetypes.Checkpoint - currentSlot types.Slot - wantedErr string - }{ - { - name: "lower than store justified and finalized", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 1}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 0}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - }, - { - name: "higher than store justified, early slot, direct descendant", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'g'}}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - }, - { - name: "higher than store justified, early slot, not a descendant", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'g'}}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - }, - { - name: "higher than store justified, late slot, descendant", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'g'}}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'b'}}, - currentSlot: params.BeaconConfig().SafeSlotsToUpdateJustified.Add(1), - }, - { - name: "higher than store justified, late slot, not descendant", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'g'}}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - currentSlot: params.BeaconConfig().SafeSlotsToUpdateJustified.Add(1), - }, - { - name: "higher than store finalized, late slot, not descendant", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'h'}}, - wantedJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - wantedFinalized: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'h'}}, - wantedBestJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'c'}}, - currentSlot: params.BeaconConfig().SafeSlotsToUpdateJustified.Add(1), - }, - { - name: "Unknown checkpoint root, late slot", - justified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - bestJustified: &forkchoicetypes.Checkpoint{Epoch: 2, Root: [32]byte{'j'}}, - finalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'f'}}, - newJustified: &forkchoicetypes.Checkpoint{Epoch: 3, Root: [32]byte{'d'}}, - newFinalized: &forkchoicetypes.Checkpoint{Epoch: 1, Root: [32]byte{'h'}}, - currentSlot: params.BeaconConfig().SafeSlotsToUpdateJustified.Add(1), - wantedErr: "node does not exist", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fcs := setup(tt.justified.Epoch, tt.finalized.Epoch) - fcs.store.justifiedCheckpoint = tt.justified - fcs.store.finalizedCheckpoint = tt.finalized - fcs.store.bestJustifiedCheckpoint = tt.bestJustified - fcs.store.genesisTime = uint64(time.Now().Unix()) - uint64(tt.currentSlot)*params.BeaconConfig().SecondsPerSlot - - state, blkRoot, err := prepareForkchoiceState(ctx, 32, [32]byte{'f'}, - [32]byte{}, [32]byte{}, tt.finalized.Epoch, tt.finalized.Epoch) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 64, [32]byte{'j'}, - [32]byte{'f'}, [32]byte{}, tt.justified.Epoch, tt.finalized.Epoch) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 96, [32]byte{'b'}, - [32]byte{'j'}, [32]byte{}, tt.newJustified.Epoch, tt.newFinalized.Epoch) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 96, [32]byte{'c'}, - [32]byte{'f'}, [32]byte{}, tt.newJustified.Epoch, tt.newFinalized.Epoch) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 65, [32]byte{'h'}, - [32]byte{'f'}, [32]byte{}, tt.newFinalized.Epoch, tt.newFinalized.Epoch) - require.NoError(t, err) - require.NoError(t, fcs.InsertNode(ctx, state, blkRoot)) - // restart justifications cause insertion messed it up - // restart justifications cause insertion messed it up - fcs.store.justifiedCheckpoint = tt.justified - fcs.store.finalizedCheckpoint = tt.finalized - fcs.store.bestJustifiedCheckpoint = tt.bestJustified - - jc := ðpb.Checkpoint{Epoch: tt.newJustified.Epoch, Root: tt.newJustified.Root[:]} - fc := ðpb.Checkpoint{Epoch: tt.newFinalized.Epoch, Root: tt.newFinalized.Root[:]} - err = fcs.updateCheckpoints(ctx, jc, fc) - if len(tt.wantedErr) > 0 { - require.ErrorContains(t, tt.wantedErr, err) - } else { - require.NoError(t, err) - require.Equal(t, tt.wantedJustified.Epoch, fcs.store.justifiedCheckpoint.Epoch) - require.Equal(t, tt.wantedFinalized.Epoch, fcs.store.finalizedCheckpoint.Epoch) - require.Equal(t, tt.wantedJustified.Root, fcs.store.justifiedCheckpoint.Root) - require.Equal(t, tt.wantedFinalized.Root, fcs.store.finalizedCheckpoint.Root) - require.Equal(t, tt.wantedBestJustified.Epoch, fcs.store.bestJustifiedCheckpoint.Epoch) - require.Equal(t, tt.wantedBestJustified.Root, fcs.store.bestJustifiedCheckpoint.Root) - } - }) - } -} - -func TestForkChoice_HighestReceivedBlockSlotRoot(t *testing.T) { - f := setup(1, 1) - s := f.store - _, err := s.insert(context.Background(), 100, [32]byte{'A'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - - require.Equal(t, uint64(len(s.nodes)-1), s.highestReceivedIndex) - require.Equal(t, types.Slot(100), f.HighestReceivedBlockSlot()) - require.Equal(t, [32]byte{'A'}, f.HighestReceivedBlockRoot()) - _, err = s.insert(context.Background(), 1000, [32]byte{'B'}, [32]byte{'A'}, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.Equal(t, uint64(len(s.nodes)-1), s.highestReceivedIndex) - require.Equal(t, types.Slot(1000), f.HighestReceivedBlockSlot()) - require.Equal(t, [32]byte{'B'}, f.HighestReceivedBlockRoot()) - _, err = s.insert(context.Background(), 500, [32]byte{'C'}, [32]byte{'A'}, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.Equal(t, uint64(len(s.nodes)-2), s.highestReceivedIndex) - require.Equal(t, types.Slot(1000), f.HighestReceivedBlockSlot()) - require.Equal(t, [32]byte{'B'}, f.HighestReceivedBlockRoot()) -} - -func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) { - f := setup(1, 1) - s := f.store - b := [32]byte{} - - // Make sure it doesn't underflow - s.genesisTime = uint64(time.Now().Add(time.Duration(-1*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - _, err := s.insert(context.Background(), 1, [32]byte{'a'}, b, b, 1, 1) - require.NoError(t, err) - count, err := f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(1), f.HighestReceivedBlockSlot()) - - // 64 - // Received block last epoch is 1 - _, err = s.insert(context.Background(), 64, [32]byte{'A'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-64*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(64), f.HighestReceivedBlockSlot()) - - // 64 65 - // Received block last epoch is 2 - _, err = s.insert(context.Background(), 65, [32]byte{'B'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-65*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(2), count) - require.Equal(t, types.Slot(65), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // Received block last epoch is 3 - _, err = s.insert(context.Background(), 66, [32]byte{'C'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-66*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(3), count) - require.Equal(t, types.Slot(66), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // 98 - // Received block last epoch is 1 - _, err = s.insert(context.Background(), 98, [32]byte{'D'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-98*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(98), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // 98 - // 132 - // Received block last epoch is 1 - _, err = s.insert(context.Background(), 132, [32]byte{'E'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(132), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // 98 - // 132 - // 99 - // Received block last epoch is still 1. 99 is outside the window - _, err = s.insert(context.Background(), 99, [32]byte{'F'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(132), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // 98 - // 132 - // 99 100 - // Received block last epoch is still 1. 100 is at the same position as 132 - _, err = s.insert(context.Background(), 100, [32]byte{'G'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - require.Equal(t, types.Slot(132), f.HighestReceivedBlockSlot()) - - // 64 65 66 - // 98 - // 132 - // 99 100 101 - // Received block last epoch is 2. 101 is within the window - _, err = s.insert(context.Background(), 101, [32]byte{'H'}, b, b, 1, 1) - require.NoError(t, err) - s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(2), count) - require.Equal(t, types.Slot(132), f.HighestReceivedBlockSlot()) - - s.genesisTime = uint64(time.Now().Add(time.Duration(-134*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(1), count) - s.genesisTime = uint64(time.Now().Add(time.Duration(-165*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix()) - count, err = f.ReceivedBlocksLastEpoch() - require.NoError(t, err) - require.Equal(t, uint64(0), count) -} diff --git a/beacon-chain/forkchoice/protoarray/types.go b/beacon-chain/forkchoice/protoarray/types.go deleted file mode 100644 index f898cfde0a6c..000000000000 --- a/beacon-chain/forkchoice/protoarray/types.go +++ /dev/null @@ -1,81 +0,0 @@ -package protoarray - -import ( - "sync" - - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" -) - -// ForkChoice defines the overall fork choice store which includes all block nodes, validator's latest votes and balances. -type ForkChoice struct { - store *Store - votes []Vote // tracks individual validator's last vote. - votesLock sync.RWMutex - balances []uint64 // tracks individual validator's last justified balances. -} - -// Store defines the fork choice store which includes block nodes and the last view of checkpoint information. -type Store struct { - pruneThreshold uint64 // do not prune tree unless threshold is reached. - justifiedCheckpoint *forkchoicetypes.Checkpoint // latest justified checkpoint in store. - bestJustifiedCheckpoint *forkchoicetypes.Checkpoint // best justified checkpoint in store. - unrealizedJustifiedCheckpoint *forkchoicetypes.Checkpoint // best justified checkpoint in store. - unrealizedFinalizedCheckpoint *forkchoicetypes.Checkpoint // best justified checkpoint in store. - prevJustifiedCheckpoint *forkchoicetypes.Checkpoint // previous justified checkpoint in store. - finalizedCheckpoint *forkchoicetypes.Checkpoint // latest finalized checkpoint in store. - proposerBoostRoot [fieldparams.RootLength]byte // latest block root that was boosted after being received in a timely manner. - previousProposerBoostRoot [fieldparams.RootLength]byte // previous block root that was boosted after being received in a timely manner. - previousProposerBoostScore uint64 // previous proposer boosted root score. - nodes []*Node // list of block nodes, each node is a representation of one block. - nodesIndices map[[fieldparams.RootLength]byte]uint64 // the root of block node and the nodes index in the list. - canonicalNodes map[[fieldparams.RootLength]byte]bool // the canonical block nodes. - payloadIndices map[[fieldparams.RootLength]byte]uint64 // the payload hash of block node and the index in the list - slashedIndices map[types.ValidatorIndex]bool // The list of equivocating validators - originRoot [fieldparams.RootLength]byte // The genesis block root - lastHeadRoot [fieldparams.RootLength]byte // The last cached head block root - nodesLock sync.RWMutex - proposerBoostLock sync.RWMutex - checkpointsLock sync.RWMutex - genesisTime uint64 - highestReceivedIndex uint64 // The highest slot node's index. - receivedBlocksLastEpoch [fieldparams.SlotsPerEpoch]types.Slot // Using `highestReceivedSlot`. The slot of blocks received in the last epoch. - allTipsAreInvalid bool // tracks if all tips are not viable for head -} - -// Node defines the individual block which includes its block parent, ancestor and how much weight accounted for it. -// This is used as an array based stateful DAG for efficient fork choice look up. -type Node struct { - slot types.Slot // slot of the block converted to the node. - root [fieldparams.RootLength]byte // root of the block converted to the node. - payloadHash [fieldparams.RootLength]byte // payloadHash of the block converted to the node. - parent uint64 // parent index of this node. - justifiedEpoch types.Epoch // justifiedEpoch of this node. - unrealizedJustifiedEpoch types.Epoch // the epoch that would be justified if the block would be advanced to the next epoch. - finalizedEpoch types.Epoch // finalizedEpoch of this node. - unrealizedFinalizedEpoch types.Epoch // the epoch that would be finalized if the block would be advanced to the next epoch. - weight uint64 // weight of this node. - bestChild uint64 // bestChild index of this node. - bestDescendant uint64 // bestDescendant of this node. - status status // optimistic status of this node -} - -// enum used as optimistic status of a node -type status uint8 - -const ( - syncing status = iota // the node is optimistic - valid //fully validated node - invalid // invalid execution payload -) - -// Vote defines an individual validator's vote. -type Vote struct { - currentRoot [fieldparams.RootLength]byte // current voting root. - nextRoot [fieldparams.RootLength]byte // next voting root. - nextEpoch types.Epoch // epoch of next voting period. -} - -// NonExistentNode defines an unknown node which is used for the array based stateful DAG. -const NonExistentNode = ^uint64(0) diff --git a/beacon-chain/forkchoice/protoarray/unrealized_justification.go b/beacon-chain/forkchoice/protoarray/unrealized_justification.go deleted file mode 100644 index 43df1fce4488..000000000000 --- a/beacon-chain/forkchoice/protoarray/unrealized_justification.go +++ /dev/null @@ -1,135 +0,0 @@ -package protoarray - -import ( - "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/epoch/precompute" - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" - ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" - "github.com/prysmaticlabs/prysm/v3/time/slots" -) - -func (s *Store) setUnrealizedJustifiedEpoch(root [32]byte, epoch types.Epoch) error { - s.nodesLock.Lock() - defer s.nodesLock.Unlock() - index, ok := s.nodesIndices[root] - if !ok { - return ErrUnknownNodeRoot - } - if index >= uint64(len(s.nodes)) { - return errInvalidNodeIndex - } - node := s.nodes[index] - if node == nil { - return errInvalidNodeIndex - } - if epoch < node.unrealizedJustifiedEpoch { - return errInvalidUnrealizedJustifiedEpoch - } - node.unrealizedJustifiedEpoch = epoch - return nil -} - -func (s *Store) setUnrealizedFinalizedEpoch(root [32]byte, epoch types.Epoch) error { - s.nodesLock.Lock() - defer s.nodesLock.Unlock() - index, ok := s.nodesIndices[root] - if !ok { - return ErrUnknownNodeRoot - } - if index >= uint64(len(s.nodes)) { - return errInvalidNodeIndex - } - node := s.nodes[index] - if node == nil { - return errInvalidNodeIndex - } - if epoch < node.unrealizedFinalizedEpoch { - return errInvalidUnrealizedFinalizedEpoch - } - node.unrealizedFinalizedEpoch = epoch - return nil -} - -// UpdateUnrealizedCheckpoints "realizes" the unrealized justified and finalized -// epochs stored within nodes. It should be called at the beginning of each -// epoch -func (f *ForkChoice) updateUnrealizedCheckpoints() { - f.store.nodesLock.Lock() - defer f.store.nodesLock.Unlock() - f.store.checkpointsLock.Lock() - defer f.store.checkpointsLock.Unlock() - for _, node := range f.store.nodes { - node.justifiedEpoch = node.unrealizedJustifiedEpoch - node.finalizedEpoch = node.unrealizedFinalizedEpoch - if node.justifiedEpoch > f.store.justifiedCheckpoint.Epoch { - if node.justifiedEpoch > f.store.bestJustifiedCheckpoint.Epoch { - f.store.bestJustifiedCheckpoint = f.store.unrealizedJustifiedCheckpoint - } - f.store.prevJustifiedCheckpoint = f.store.justifiedCheckpoint - f.store.justifiedCheckpoint = f.store.unrealizedJustifiedCheckpoint - } - if node.finalizedEpoch > f.store.finalizedCheckpoint.Epoch { - f.store.justifiedCheckpoint = f.store.unrealizedJustifiedCheckpoint - f.store.finalizedCheckpoint = f.store.unrealizedFinalizedCheckpoint - } - } -} - -func (s *Store) pullTips(state state.BeaconState, node *Node, jc, fc *ethpb.Checkpoint) (*ethpb.Checkpoint, *ethpb.Checkpoint) { - s.nodesLock.Lock() - defer s.nodesLock.Unlock() - - if node.parent == NonExistentNode { // Nothing to do if the parent is nil. - return jc, fc - } - - currentEpoch := slots.ToEpoch(slots.CurrentSlot(s.genesisTime)) - stateSlot := state.Slot() - stateEpoch := slots.ToEpoch(stateSlot) - - parent := s.nodes[node.parent] - currJustified := parent.unrealizedJustifiedEpoch == currentEpoch - prevJustified := parent.unrealizedJustifiedEpoch+1 == currentEpoch - tooEarlyForCurr := slots.SinceEpochStarts(stateSlot)*3 < params.BeaconConfig().SlotsPerEpoch*2 - if currJustified || (stateEpoch == currentEpoch && prevJustified && tooEarlyForCurr) { - node.unrealizedJustifiedEpoch = parent.unrealizedJustifiedEpoch - node.unrealizedFinalizedEpoch = parent.unrealizedFinalizedEpoch - return jc, fc - } - - _, uj, uf, err := precompute.UnrealizedCheckpoints(state) - if err != nil { - log.WithError(err).Debug("could not compute unrealized checkpoints") - uj, uf = jc, fc - } - - // Update store's unrealized checkpoints. - s.checkpointsLock.Lock() - if uj.Epoch > s.unrealizedJustifiedCheckpoint.Epoch { - s.unrealizedJustifiedCheckpoint = &forkchoicetypes.Checkpoint{ - Epoch: uj.Epoch, Root: bytesutil.ToBytes32(uj.Root), - } - } - if uf.Epoch > s.unrealizedFinalizedCheckpoint.Epoch { - s.unrealizedJustifiedCheckpoint = &forkchoicetypes.Checkpoint{ - Epoch: uj.Epoch, Root: bytesutil.ToBytes32(uj.Root), - } - s.unrealizedFinalizedCheckpoint = &forkchoicetypes.Checkpoint{ - Epoch: uf.Epoch, Root: bytesutil.ToBytes32(uf.Root), - } - } - s.checkpointsLock.Unlock() - - // Update node's checkpoints. - node.unrealizedJustifiedEpoch, node.unrealizedFinalizedEpoch = uj.Epoch, uf.Epoch - if stateEpoch < currentEpoch { - jc, fc = uj, uf - node.justifiedEpoch = uj.Epoch - node.finalizedEpoch = uf.Epoch - } - - return jc, fc -} diff --git a/beacon-chain/forkchoice/protoarray/unrealized_justification_test.go b/beacon-chain/forkchoice/protoarray/unrealized_justification_test.go deleted file mode 100644 index b845fdcafe3f..000000000000 --- a/beacon-chain/forkchoice/protoarray/unrealized_justification_test.go +++ /dev/null @@ -1,333 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" - "github.com/prysmaticlabs/prysm/v3/config/params" - types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestStore_SetUnrealizedEpochs(t *testing.T) { - f := setup(1, 1) - ctx := context.Background() - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - f.store.nodesLock.RLock() - require.Equal(t, types.Epoch(1), f.store.nodes[2].unrealizedJustifiedEpoch) - require.Equal(t, types.Epoch(1), f.store.nodes[2].unrealizedFinalizedEpoch) - f.store.nodesLock.RUnlock() - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'b'}, 2)) - require.NoError(t, f.store.setUnrealizedFinalizedEpoch([32]byte{'b'}, 2)) - f.store.nodesLock.RLock() - require.Equal(t, types.Epoch(2), f.store.nodes[2].unrealizedJustifiedEpoch) - require.Equal(t, types.Epoch(2), f.store.nodes[2].unrealizedFinalizedEpoch) - f.store.nodesLock.RUnlock() - - require.ErrorIs(t, errInvalidUnrealizedJustifiedEpoch, f.store.setUnrealizedJustifiedEpoch([32]byte{'b'}, 0)) - require.ErrorIs(t, errInvalidUnrealizedFinalizedEpoch, f.store.setUnrealizedFinalizedEpoch([32]byte{'b'}, 0)) -} - -func TestStore_UpdateUnrealizedCheckpoints(t *testing.T) { - f := setup(1, 1) - ctx := context.Background() - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - -} - -// -// Epoch 2 | Epoch 3 -// | -// C | -// / | -// A <-- B | -// \ | -// ---- D -// -// B is the first block that justifies A. -// -func TestStore_LongFork(t *testing.T) { - f := setup(1, 1) - ctx := context.Background() - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'b'}, 2)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'c'}, 2)) - - // Add an attestation to c, it is head - f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'c'}, 1) - headRoot, err := f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'c'}, headRoot) - - // D is head even though its weight is lower. - ha := [32]byte{'a'} - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'b'}, [32]byte{'D'}, 2, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.UpdateJustifiedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 2, Root: ha})) - headRoot, err = f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'d'}, headRoot) - require.Equal(t, uint64(0), f.store.nodes[4].weight) - require.Equal(t, uint64(100), f.store.nodes[3].weight) - - // Update unrealized justification, c becomes head - f.updateUnrealizedCheckpoints() - headRoot, err = f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'c'}, headRoot) -} - -// -// -// Epoch 1 Epoch 2 Epoch 3 -// | | -// | | -// A <-- B <-- C <-- D <-- E <-- F <-- G <-- H | -// | \ | -// | --------------- I -// | | -// -// E justifies A. G justifies E. -// -func TestStore_NoDeadLock(t *testing.T) { - f := setup(0, 0) - ctx := context.Background() - - // Epoch 1 blocks - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Epoch 2 Blocks - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'e'}, 1)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'f'}, 1)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'g'}, [32]byte{'f'}, [32]byte{'G'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'g'}, 2)) - require.NoError(t, f.store.setUnrealizedFinalizedEpoch([32]byte{'g'}, 1)) - f.store.unrealizedJustifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 2} - f.store.unrealizedFinalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 1} - state, blkRoot, err = prepareForkchoiceState(ctx, 107, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'h'}, 2)) - require.NoError(t, f.store.setUnrealizedFinalizedEpoch([32]byte{'h'}, 1)) - // Add an attestation for h - f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, 1) - - // Epoch 3 - // Current Head is H - headRoot, err := f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'h'}, headRoot) - require.Equal(t, types.Epoch(0), f.JustifiedCheckpoint().Epoch) - - // Insert Block I, it becomes Head - hr := [32]byte{'i'} - state, blkRoot, err = prepareForkchoiceState(ctx, 108, hr, [32]byte{'f'}, [32]byte{'I'}, 1, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - ha := [32]byte{'a'} - require.NoError(t, f.UpdateJustifiedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 1, Root: ha})) - headRoot, err = f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, hr, headRoot) - require.Equal(t, types.Epoch(1), f.JustifiedCheckpoint().Epoch) - require.Equal(t, types.Epoch(0), f.FinalizedCheckpoint().Epoch) - - // Realized Justified checkpoints, H becomes head - f.updateUnrealizedCheckpoints() - headRoot, err = f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'h'}, headRoot) - require.Equal(t, types.Epoch(2), f.JustifiedCheckpoint().Epoch) - require.Equal(t, types.Epoch(1), f.FinalizedCheckpoint().Epoch) -} - -// Epoch 1 | Epoch 2 -// | -// -- D (late) -- -// / | -// A <- B <- C | -// \ | -// -- -- -- E <- F <- G <- H -// | -// -// D justifies and comes late. -// -func TestStore_ForkNextEpoch(t *testing.T) { - f := setup(0, 0) - ctx := context.Background() - - // Epoch 1 blocks (D does not arrive) - state, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Epoch 2 blocks - state, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'c'}, [32]byte{'E'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 106, [32]byte{'g'}, [32]byte{'f'}, [32]byte{'G'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(ctx, 107, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - // Insert an attestation to H, H is head - f.ProcessAttestation(ctx, []uint64{0}, [32]byte{'h'}, 1) - headRoot, err := f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'h'}, headRoot) - require.Equal(t, types.Epoch(0), f.JustifiedCheckpoint().Epoch) - - // D arrives late, D is head - state, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 0, 0) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - require.NoError(t, f.store.setUnrealizedJustifiedEpoch([32]byte{'d'}, 1)) - f.store.unrealizedJustifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: 1} - f.updateUnrealizedCheckpoints() - headRoot, err = f.Head(ctx, []uint64{100}) - require.NoError(t, err) - require.Equal(t, [32]byte{'d'}, headRoot) - require.Equal(t, types.Epoch(1), f.JustifiedCheckpoint().Epoch) - // nodes[8] = D since it's late! - require.Equal(t, uint64(0), f.store.nodes[8].weight) - require.Equal(t, uint64(100), f.store.nodes[7].weight) -} - -func TestStore_PullTips_Heuristics(t *testing.T) { - ctx := context.Background() - t.Run("Current epoch is justified", func(tt *testing.T) { - f := setup(1, 1) - st, root, err := prepareForkchoiceState(ctx, 65, [32]byte{'p'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - f.store.nodes[1].unrealizedJustifiedEpoch = types.Epoch(2) - driftGenesisTime(f, 66, 0) - - st, root, err = prepareForkchoiceState(ctx, 66, [32]byte{'h'}, [32]byte{'p'}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - require.Equal(tt, types.Epoch(2), f.store.nodes[2].unrealizedJustifiedEpoch) - require.Equal(tt, types.Epoch(1), f.store.nodes[2].unrealizedFinalizedEpoch) - }) - - t.Run("Previous Epoch is justified and too early for current", func(tt *testing.T) { - f := setup(1, 1) - st, root, err := prepareForkchoiceState(ctx, 95, [32]byte{'p'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - f.store.nodes[1].unrealizedJustifiedEpoch = types.Epoch(2) - driftGenesisTime(f, 96, 0) - - st, root, err = prepareForkchoiceState(ctx, 96, [32]byte{'h'}, [32]byte{'p'}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - require.Equal(tt, types.Epoch(2), f.store.nodes[2].unrealizedJustifiedEpoch) - require.Equal(tt, types.Epoch(1), f.store.nodes[2].unrealizedFinalizedEpoch) - }) - t.Run("Previous Epoch is justified and not too early for current", func(tt *testing.T) { - f := setup(1, 1) - st, root, err := prepareForkchoiceState(ctx, 95, [32]byte{'p'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - f.store.nodes[1].unrealizedJustifiedEpoch = types.Epoch(2) - driftGenesisTime(f, 127, 0) - - st, root, err = prepareForkchoiceState(ctx, 127, [32]byte{'h'}, [32]byte{'p'}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - // Check that the justification point is not the parent's. - // This tests that the heuristics in pullTips did not apply and - // the test continues to compute a bogus unrealized - // justification - require.Equal(tt, types.Epoch(1), f.store.nodes[2].unrealizedJustifiedEpoch) - }) - t.Run("Block from previous Epoch", func(tt *testing.T) { - f := setup(1, 1) - st, root, err := prepareForkchoiceState(ctx, 94, [32]byte{'p'}, [32]byte{}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - f.store.nodes[1].unrealizedJustifiedEpoch = types.Epoch(2) - driftGenesisTime(f, 96, 0) - - st, root, err = prepareForkchoiceState(ctx, 95, [32]byte{'h'}, [32]byte{'p'}, [32]byte{}, 1, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - // Check that the justification point is not the parent's. - // This tests that the heuristics in pullTips did not apply and - // the test continues to compute a bogus unrealized - // justification - require.Equal(tt, types.Epoch(1), f.store.nodes[2].unrealizedJustifiedEpoch) - }) - t.Run("Previous Epoch is not justified", func(tt *testing.T) { - f := setup(1, 1) - st, root, err := prepareForkchoiceState(ctx, 128, [32]byte{'p'}, [32]byte{}, [32]byte{}, 2, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - driftGenesisTime(f, 129, 0) - - st, root, err = prepareForkchoiceState(ctx, 129, [32]byte{'h'}, [32]byte{'p'}, [32]byte{}, 2, 1) - require.NoError(tt, err) - require.NoError(tt, f.InsertNode(ctx, st, root)) - // Check that the justification point is not the parent's. - // This tests that the heuristics in pullTips did not apply and - // the test continues to compute a bogus unrealized - // justification - require.Equal(tt, types.Epoch(2), f.store.nodes[2].unrealizedJustifiedEpoch) - }) -} diff --git a/beacon-chain/forkchoice/protoarray/vote_test.go b/beacon-chain/forkchoice/protoarray/vote_test.go deleted file mode 100644 index cec66e53a7a1..000000000000 --- a/beacon-chain/forkchoice/protoarray/vote_test.go +++ /dev/null @@ -1,321 +0,0 @@ -package protoarray - -import ( - "context" - "testing" - - "github.com/prysmaticlabs/prysm/v3/config/params" - "github.com/prysmaticlabs/prysm/v3/testing/assert" - "github.com/prysmaticlabs/prysm/v3/testing/require" -) - -func TestVotes_CanFindHead(t *testing.T) { - balances := []uint64{1, 1} - f := setup(1, 1) - ctx := context.Background() - - // The head should always start at the finalized block. - r, err := f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, params.BeaconConfig().ZeroHash, r, "Incorrect head with genesis") - - // Insert block 2 into the tree and verify head is at 2: - // 0 - // / - // 2 <- head - state, blkRoot, err := prepareForkchoiceState(context.Background(), 0, indexToHash(2), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Insert block 1 into the tree and verify head is still at 2: - // 0 - // / \ - // head -> 2 1 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Add a vote to block 1 of the tree and verify head is switched to 1: - // 0 - // / \ - // 2 1 <- +vote, new head - f.ProcessAttestation(context.Background(), []uint64{0}, indexToHash(1), 2) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(1), r, "Incorrect head for with justified epoch at 1") - - // Add a vote to block 2 of the tree and verify head is switched to 2: - // 0 - // / \ - // vote, new head -> 2 1 - f.ProcessAttestation(context.Background(), []uint64{1}, indexToHash(2), 2) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Insert block 3 into the tree and verify head is still at 2: - // 0 - // / \ - // head -> 2 1 - // | - // 3 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(3), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Move validator 0's vote from 1 to 3 and verify head is still at 2: - // 0 - // / \ - // head -> 2 1 <- old vote - // | - // 3 <- new vote - f.ProcessAttestation(context.Background(), []uint64{0}, indexToHash(3), 3) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(2), r, "Incorrect head for with justified epoch at 1") - - // Move validator 1's vote from 2 to 1 and verify head is switched to 3: - // 0 - // / \ - // old vote -> 2 1 <- new vote - // | - // 3 <- head - f.ProcessAttestation(context.Background(), []uint64{1}, indexToHash(1), 3) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(3), r, "Incorrect head for with justified epoch at 1") - - // Insert block 4 into the tree and verify head is at 4: - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 <- head - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(4), indexToHash(3), params.BeaconConfig().ZeroHash, 1, 1) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(4), r, "Incorrect head for with justified epoch at 1") - - // Insert block 5 with justified epoch 2, it becomes head - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 <- head - // / - // 5 <- justified epoch = 2 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(5), indexToHash(4), params.BeaconConfig().ZeroHash, 2, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(5), r, "Incorrect head for with justified epoch at 1") - - // Insert block 6 with justified epoch 3: verify it's head - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 <- head - // / \ - // 5 6 <- justified epoch = 3 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(6), indexToHash(4), params.BeaconConfig().ZeroHash, 3, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(6), r, "Incorrect head for with justified epoch at 1") - - // Moved 2 votes to block 5: - f.ProcessAttestation(context.Background(), []uint64{0, 1}, indexToHash(5), 4) - - // Inset blocks 7 and 8 - // 6 should still be the head, even though 5 has all the votes. - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 - // / \ - // 5 6 <- head - // | - // 7 - // | - // 8 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(7), indexToHash(5), params.BeaconConfig().ZeroHash, 2, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(8), indexToHash(7), params.BeaconConfig().ZeroHash, 2, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(6), r, "Incorrect head for with justified epoch at 1") - - // Insert block 9 with justified epoch 3, it becomes head - // Verify 9 is the head: - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 - // / \ - // 5 6 - // | - // 7 - // | - // 8 - // | - // 10 <- head - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(10), indexToHash(8), params.BeaconConfig().ZeroHash, 3, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3") - - // Insert block 9 forking 10 verify it's head (lexicographic order) - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 - // / \ - // 5 6 - // | - // 7 - // | - // 8 - // / \ - // 9 10 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(9), indexToHash(8), params.BeaconConfig().ZeroHash, 3, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(9), r, "Incorrect head for with justified epoch at 3") - - // Move two votes for 10, verify it's head - - f.ProcessAttestation(context.Background(), []uint64{0, 1}, indexToHash(10), 5) - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3") - - // Add 3 more validators to the system. - balances = []uint64{1, 1, 1, 1, 1} - // The new validators voted for 9 - f.ProcessAttestation(context.Background(), []uint64{2, 3, 4}, indexToHash(9), 5) - // The new head should be 9. - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(9), r, "Incorrect head for with justified epoch at 3") - - // Set the balances of the last 2 validators to 0. - balances = []uint64{1, 1, 1, 0, 0} - // The head should be back to 10. - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3") - - // Set the balances back to normal. - balances = []uint64{1, 1, 1, 1, 1} - // The head should be back to 9. - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(9), r, "Incorrect head for with justified epoch at 3") - - // Remove the last 2 validators. - balances = []uint64{1, 1, 1} - // The head should be back to 10. - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3") - - // Verify pruning below the prune threshold does not affect head. - f.store.pruneThreshold = 1000 - prevRoot := f.store.finalizedCheckpoint.Root - f.store.finalizedCheckpoint.Root = indexToHash(5) - require.NoError(t, f.store.prune(context.Background())) - assert.Equal(t, 11, len(f.store.nodes), "Incorrect nodes length after prune") - - f.store.finalizedCheckpoint.Root = prevRoot - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 3") - - // Verify pruning above the prune threshold does prune: - // 0 - // / \ - // 2 1 - // | - // 3 - // | - // 4 - // -------pruned here ------ - // 5 6 - // | - // 7 - // | - // 8 - // / \ - // 9 10 - f.store.pruneThreshold = 1 - f.store.finalizedCheckpoint.Root = indexToHash(5) - require.NoError(t, f.store.prune(context.Background())) - assert.Equal(t, 5, len(f.store.nodes), "Incorrect nodes length after prune") - // we pruned artificially the justified root. - f.store.justifiedCheckpoint.Root = indexToHash(5) - f.store.finalizedCheckpoint.Root = prevRoot - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(10), r, "Incorrect head for with justified epoch at 2") - - // Insert new block 11 and verify head is at 11. - // 5 6 - // | - // 7 - // | - // 8 - // / \ - // 10 9 - // | - // head-> 11 - state, blkRoot, err = prepareForkchoiceState(context.Background(), 0, indexToHash(11), indexToHash(10), params.BeaconConfig().ZeroHash, 3, 2) - require.NoError(t, err) - require.NoError(t, f.InsertNode(ctx, state, blkRoot)) - - r, err = f.Head(context.Background(), balances) - require.NoError(t, err) - assert.Equal(t, indexToHash(11), r, "Incorrect head for with justified epoch at 3") -} diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index a0bc408a0835..9591d698783b 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -28,7 +28,6 @@ go_library( "//beacon-chain/execution:go_default_library", "//beacon-chain/forkchoice:go_default_library", "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", - "//beacon-chain/forkchoice/protoarray:go_default_library", "//beacon-chain/gateway:go_default_library", "//beacon-chain/monitor:go_default_library", "//beacon-chain/node/registration:go_default_library", diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index dc8c10d8e03b..3496c6d038b8 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -30,7 +30,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution" "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" "github.com/prysmaticlabs/prysm/v3/beacon-chain/gateway" "github.com/prysmaticlabs/prysm/v3/beacon-chain/monitor" "github.com/prysmaticlabs/prysm/v3/beacon-chain/node/registration" @@ -181,12 +180,7 @@ func New(cliCtx *cli.Context, opts ...Option) (*BeaconNode, error) { } } - if features.Get().DisableForkchoiceDoublyLinkedTree { - beacon.forkChoicer = protoarray.New() - } else { - beacon.forkChoicer = doublylinkedtree.New() - } - + beacon.forkChoicer = doublylinkedtree.New() depositAddress, err := execution.DepositContractAddress() if err != nil { return nil, err diff --git a/beacon-chain/rpc/eth/validator/BUILD.bazel b/beacon-chain/rpc/eth/validator/BUILD.bazel index 28b2eccb8319..a6c98ef4cfe3 100644 --- a/beacon-chain/rpc/eth/validator/BUILD.bazel +++ b/beacon-chain/rpc/eth/validator/BUILD.bazel @@ -60,7 +60,7 @@ go_test( "//beacon-chain/core/transition:go_default_library", "//beacon-chain/db/testing:go_default_library", "//beacon-chain/execution/testing:go_default_library", - "//beacon-chain/forkchoice/protoarray:go_default_library", + "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/attestations/mock:go_default_library", "//beacon-chain/operations/slashings:go_default_library", diff --git a/beacon-chain/rpc/eth/validator/validator_test.go b/beacon-chain/rpc/eth/validator/validator_test.go index 0cfa1c048a03..e88d68d9968c 100644 --- a/beacon-chain/rpc/eth/validator/validator_test.go +++ b/beacon-chain/rpc/eth/validator/validator_test.go @@ -19,7 +19,7 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/transition" dbutil "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations/mock" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/slashings" @@ -1921,7 +1921,7 @@ func TestProduceBlindedBlock(t *testing.T) { v1Alpha1Server := &v1alpha1validator.Server{ BeaconDB: db, - ForkFetcher: &mockChain.ChainService{ForkChoiceStore: protoarray.New()}, + ForkFetcher: &mockChain.ChainService{ForkChoiceStore: doublylinkedtree.New()}, TimeFetcher: &mockChain.ChainService{ Genesis: ti, }, diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go index 6bf75597f18c..da43448de894 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_bellatrix_test.go @@ -17,7 +17,7 @@ import ( prysmtime "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/time" dbTest "github.com/prysmaticlabs/prysm/v3/beacon-chain/db/testing" mockExecution "github.com/prysmaticlabs/prysm/v3/beacon-chain/execution/testing" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" + doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/slashings" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/synccommittee" @@ -446,7 +446,7 @@ func TestServer_getAndBuildHeaderBlock(t *testing.T) { vs.FinalizationFetcher = &blockchainTest.ChainService{FinalizedCheckPoint: ðpb.Checkpoint{Root: wbr1[:]}} vs.HeadFetcher = &blockchainTest.ChainService{Block: wb1} vs.BlockBuilder = &builderTest.MockBuilderService{HasConfigured: true, ErrGetHeader: errors.New("could not get payload")} - vs.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: protoarray.New()} + vs.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New()} ready, _, err = vs.GetAndBuildBlindBlock(ctx, ðpb.BeaconBlockAltair{}) require.ErrorContains(t, "could not get payload", err) require.Equal(t, false, ready) @@ -520,7 +520,7 @@ func TestServer_getAndBuildHeaderBlock(t *testing.T) { } vs.BlockBuilder = &builderTest.MockBuilderService{HasConfigured: true, Bid: sBid} vs.TimeFetcher = &blockchainTest.ChainService{Genesis: time.Now()} - vs.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: protoarray.New()} + vs.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New()} ready, builtBlk, err := vs.GetAndBuildBlindBlock(ctx, altairBlk.Block) require.NoError(t, err) require.Equal(t, true, ready) @@ -760,7 +760,7 @@ func TestServer_GetBellatrixBeaconBlock_BuilderCase(t *testing.T) { Signature: sk.Sign(sr[:]).Marshal(), } proposerServer.BlockBuilder = &builderTest.MockBuilderService{HasConfigured: true, Bid: sBid} - proposerServer.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: protoarray.New()} + proposerServer.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New()} randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) @@ -850,7 +850,7 @@ func TestServer_circuitBreakBuilder(t *testing.T) { _, err := s.circuitBreakBuilder(0) require.ErrorContains(t, "no fork choicer configured", err) - s.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: protoarray.New()} + s.ForkFetcher = &blockchainTest.ChainService{ForkChoiceStore: doublylinkedtree.New()} s.ForkFetcher.ForkChoicer().SetGenesisTime(uint64(time.Now().Unix())) b, err := s.circuitBreakBuilder(params.BeaconConfig().MaxBuilderConsecutiveMissedSlots + 1) require.NoError(t, err) From 676c5d60692c12fddb9cfa745605fdee4d0226a9 Mon Sep 17 00:00:00 2001 From: Potuz Date: Thu, 15 Sep 2022 10:32:37 -0300 Subject: [PATCH 2/6] exported errors --- beacon-chain/blockchain/BUILD.bazel | 1 - beacon-chain/blockchain/chain_info.go | 5 ++--- beacon-chain/blockchain/process_block_helpers.go | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/beacon-chain/blockchain/BUILD.bazel b/beacon-chain/blockchain/BUILD.bazel index e1b38bce7ae7..479db1187fda 100644 --- a/beacon-chain/blockchain/BUILD.bazel +++ b/beacon-chain/blockchain/BUILD.bazel @@ -81,7 +81,6 @@ go_library( "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", - "@com_github_prysmaticlabs_prysm_v3//beacon-chain/forkchoice/protoarray:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@io_opencensus_go//trace:go_default_library", ], diff --git a/beacon-chain/blockchain/chain_info.go b/beacon-chain/blockchain/chain_info.go index 693933eb6a55..b887b1c6dcdc 100644 --- a/beacon-chain/blockchain/chain_info.go +++ b/beacon-chain/blockchain/chain_info.go @@ -8,7 +8,6 @@ import ( "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" "github.com/prysmaticlabs/prysm/v3/config/params" @@ -314,7 +313,7 @@ func (s *Service) IsOptimistic(ctx context.Context) (bool, error) { if err == nil { return optimistic, nil } - if err != protoarray.ErrUnknownNodeRoot && err != doublylinkedtree.ErrNilNode { + if err != doublylinkedtree.ErrNilNode { return true, err } // If fockchoice does not have the headroot, then the node is considered @@ -338,7 +337,7 @@ func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, if err == nil { return optimistic, nil } - if err != protoarray.ErrUnknownNodeRoot && err != doublylinkedtree.ErrNilNode { + if err != doublylinkedtree.ErrNilNode { return false, err } // if the requested root is the headroot and the root is not found in diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index 980bb3eaedc2..5ee2b4d95cfb 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -8,7 +8,6 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" - "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/protoarray" forkchoicetypes "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/types" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state" "github.com/prysmaticlabs/prysm/v3/config/params" @@ -163,7 +162,7 @@ func (s *Service) updateFinalized(ctx context.Context, cp *ethpb.Checkpoint) err fRoot := bytesutil.ToBytes32(cp.Root) optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(fRoot) - if err != nil && err != protoarray.ErrUnknownNodeRoot && err != doublylinkedtree.ErrNilNode { + if err != nil && err != doublylinkedtree.ErrNilNode { return err } if !optimistic { From f3048f7068957c136a1f6fa04ed68a0cbf7a645f Mon Sep 17 00:00:00 2001 From: Potuz Date: Thu, 15 Sep 2022 12:06:03 -0300 Subject: [PATCH 3/6] fix spectests --- .../mainnet/altair/forkchoice/forkchoice_test.go | 10 ---------- .../mainnet/bellatrix/forkchoice/forkchoice_test.go | 10 ---------- .../mainnet/phase0/forkchoice/forkchoice_test.go | 10 ---------- .../minimal/altair/forkchoice/forkchoice_test.go | 10 ---------- .../minimal/bellatrix/forkchoice/forkchoice_test.go | 10 ---------- .../minimal/phase0/forkchoice/forkchoice_test.go | 10 ---------- 6 files changed, 60 deletions(-) diff --git a/testing/spectest/mainnet/altair/forkchoice/forkchoice_test.go b/testing/spectest/mainnet/altair/forkchoice/forkchoice_test.go index f8a9be336e6b..3608391d038e 100644 --- a/testing/spectest/mainnet/altair/forkchoice/forkchoice_test.go +++ b/testing/spectest/mainnet/altair/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMainnet_Altair_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "mainnet", version.Altair) } - -func TestMainnet_Altair_Forkchoice_DoublyLinkTree(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: false, - DisablePullTips: true, - DisableForkchoiceDoublyLinkedTree: false, - }) - defer resetCfg() - forkchoice.Run(t, "mainnet", version.Altair) -} diff --git a/testing/spectest/mainnet/bellatrix/forkchoice/forkchoice_test.go b/testing/spectest/mainnet/bellatrix/forkchoice/forkchoice_test.go index 4db88c96ed1c..8d69a2756814 100644 --- a/testing/spectest/mainnet/bellatrix/forkchoice/forkchoice_test.go +++ b/testing/spectest/mainnet/bellatrix/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMainnet_Bellatrix_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "mainnet", version.Bellatrix) } - -func TestMainnet_Bellatrix_Forkchoice_DoublyLinkTree(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: false, - DisablePullTips: true, - DisableForkchoiceDoublyLinkedTree: false, - }) - defer resetCfg() - forkchoice.Run(t, "mainnet", version.Bellatrix) -} diff --git a/testing/spectest/mainnet/phase0/forkchoice/forkchoice_test.go b/testing/spectest/mainnet/phase0/forkchoice/forkchoice_test.go index 84f1e56b186b..2cc0c62ed565 100644 --- a/testing/spectest/mainnet/phase0/forkchoice/forkchoice_test.go +++ b/testing/spectest/mainnet/phase0/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMainnet_Altair_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "mainnet", version.Phase0) } - -func TestMainnet_Altair_Forkchoice_DoublyLinkTree(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: false, - DisablePullTips: true, - DisableForkchoiceDoublyLinkedTree: false, - }) - defer resetCfg() - forkchoice.Run(t, "mainnet", version.Phase0) -} diff --git a/testing/spectest/minimal/altair/forkchoice/forkchoice_test.go b/testing/spectest/minimal/altair/forkchoice/forkchoice_test.go index d5431591cfbb..c42ab2afe78d 100644 --- a/testing/spectest/minimal/altair/forkchoice/forkchoice_test.go +++ b/testing/spectest/minimal/altair/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMinimal_Altair_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "minimal", version.Altair) } - -func TestMinimal_Altair_Forkchoice_DoublyLinkTre(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - DisableForkchoiceDoublyLinkedTree: false, - EnableDefensivePull: false, - DisablePullTips: true, - }) - defer resetCfg() - forkchoice.Run(t, "minimal", version.Altair) -} diff --git a/testing/spectest/minimal/bellatrix/forkchoice/forkchoice_test.go b/testing/spectest/minimal/bellatrix/forkchoice/forkchoice_test.go index f2ec37156d78..a60b37aed633 100644 --- a/testing/spectest/minimal/bellatrix/forkchoice/forkchoice_test.go +++ b/testing/spectest/minimal/bellatrix/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMinimal_Bellatrix_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "minimal", version.Bellatrix) } - -func TestMinimal_Bellatrix_Forkchoice_DoublyLinkTree(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: false, - DisablePullTips: true, - DisableForkchoiceDoublyLinkedTree: false, - }) - defer resetCfg() - forkchoice.Run(t, "minimal", version.Bellatrix) -} diff --git a/testing/spectest/minimal/phase0/forkchoice/forkchoice_test.go b/testing/spectest/minimal/phase0/forkchoice/forkchoice_test.go index 09b51a373e26..c2b269933fd9 100644 --- a/testing/spectest/minimal/phase0/forkchoice/forkchoice_test.go +++ b/testing/spectest/minimal/phase0/forkchoice/forkchoice_test.go @@ -16,13 +16,3 @@ func TestMinimal_Altair_Forkchoice(t *testing.T) { defer resetCfg() forkchoice.Run(t, "minimal", version.Phase0) } - -func TestMinimal_Altair_Forkchoice_DoublyLinkTre(t *testing.T) { - resetCfg := features.InitWithReset(&features.Flags{ - EnableDefensivePull: false, - DisablePullTips: true, - DisableForkchoiceDoublyLinkedTree: false, - }) - defer resetCfg() - forkchoice.Run(t, "minimal", version.Phase0) -} From 981de70fef06024b8d637c7ef5a3016acfe3c704 Mon Sep 17 00:00:00 2001 From: Potuz Date: Thu, 15 Sep 2022 15:04:28 -0300 Subject: [PATCH 4/6] fix tests --- beacon-chain/forkchoice/doubly-linked-tree/store.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/beacon-chain/forkchoice/doubly-linked-tree/store.go b/beacon-chain/forkchoice/doubly-linked-tree/store.go index 6199f7770f55..927991d170e1 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/store.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/store.go @@ -261,6 +261,9 @@ func (s *Store) tips() ([][32]byte, []types.Slot) { func (f *ForkChoice) HighestReceivedBlockSlot() types.Slot { f.store.nodesLock.RLock() defer f.store.nodesLock.RUnlock() + if f.store.highestReceivedNode == nil { + return 0 + } return f.store.highestReceivedNode.slot } @@ -268,6 +271,9 @@ func (f *ForkChoice) HighestReceivedBlockSlot() types.Slot { func (f *ForkChoice) HighestReceivedBlockRoot() [32]byte { f.store.nodesLock.RLock() defer f.store.nodesLock.RUnlock() + if f.store.highestReceivedNode == nil { + return [32]byte{} + } return f.store.highestReceivedNode.root } From 04f2cc5848276fd349da44d59f3bc5298d6a1303 Mon Sep 17 00:00:00 2001 From: Potuz Date: Thu, 15 Sep 2022 18:25:18 -0300 Subject: [PATCH 5/6] conflict 1 --- beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel index 02a565d3785e..446b019faeb1 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel @@ -114,7 +114,7 @@ common_deps = [ "//beacon-chain/core/transition:go_default_library", "//beacon-chain/db/testing:go_default_library", "//beacon-chain/execution/testing:go_default_library", - "//beacon-chain/forkchoice/protoarray:go_default_library", + "//beacon-chain/forkchoice/doubly-linked-tree:go_default_library", "//beacon-chain/operations/attestations:go_default_library", "//beacon-chain/operations/slashings:go_default_library", "//beacon-chain/operations/synccommittee:go_default_library", From 843881fb149c7c7be5e6c27953c4cae5dbe44a95 Mon Sep 17 00:00:00 2001 From: Potuz Date: Sun, 25 Sep 2022 21:18:04 -0300 Subject: [PATCH 6/6] Preston's review --- beacon-chain/blockchain/chain_info.go | 5 +++-- beacon-chain/blockchain/process_block_helpers.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beacon-chain/blockchain/chain_info.go b/beacon-chain/blockchain/chain_info.go index b887b1c6dcdc..c3ff15a176a2 100644 --- a/beacon-chain/blockchain/chain_info.go +++ b/beacon-chain/blockchain/chain_info.go @@ -5,6 +5,7 @@ import ( "context" "time" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice" doublylinkedtree "github.com/prysmaticlabs/prysm/v3/beacon-chain/forkchoice/doubly-linked-tree" @@ -313,7 +314,7 @@ func (s *Service) IsOptimistic(ctx context.Context) (bool, error) { if err == nil { return optimistic, nil } - if err != doublylinkedtree.ErrNilNode { + if !errors.Is(err, doublylinkedtree.ErrNilNode) { return true, err } // If fockchoice does not have the headroot, then the node is considered @@ -337,7 +338,7 @@ func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, if err == nil { return optimistic, nil } - if err != doublylinkedtree.ErrNilNode { + if !errors.Is(err, doublylinkedtree.ErrNilNode) { return false, err } // if the requested root is the headroot and the root is not found in diff --git a/beacon-chain/blockchain/process_block_helpers.go b/beacon-chain/blockchain/process_block_helpers.go index 5ee2b4d95cfb..cfb902895e79 100644 --- a/beacon-chain/blockchain/process_block_helpers.go +++ b/beacon-chain/blockchain/process_block_helpers.go @@ -162,7 +162,7 @@ func (s *Service) updateFinalized(ctx context.Context, cp *ethpb.Checkpoint) err fRoot := bytesutil.ToBytes32(cp.Root) optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(fRoot) - if err != nil && err != doublylinkedtree.ErrNilNode { + if err != nil && !errors.Is(err, doublylinkedtree.ErrNilNode) { return err } if !optimistic {