package validator import ( "context" "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" "github.com/prysmaticlabs/go-bitfield" mock "github.com/prysmaticlabs/prysm/v3/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v3/beacon-chain/builder" builderTest "github.com/prysmaticlabs/prysm/v3/beacon-chain/builder/testing" "github.com/prysmaticlabs/prysm/v3/beacon-chain/cache" "github.com/prysmaticlabs/prysm/v3/beacon-chain/cache/depositcache" b "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/signing" coretime "github.com/prysmaticlabs/prysm/v3/beacon-chain/core/time" 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/operations/attestations" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/slashings" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/synccommittee" "github.com/prysmaticlabs/prysm/v3/beacon-chain/operations/voluntaryexits" mockp2p "github.com/prysmaticlabs/prysm/v3/beacon-chain/p2p/testing" "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/stategen" v1 "github.com/prysmaticlabs/prysm/v3/beacon-chain/state/v1" mockSync "github.com/prysmaticlabs/prysm/v3/beacon-chain/sync/initial-sync/testing" fieldparams "github.com/prysmaticlabs/prysm/v3/config/fieldparams" "github.com/prysmaticlabs/prysm/v3/config/params" "github.com/prysmaticlabs/prysm/v3/consensus-types/blocks" types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v3/container/trie" "github.com/prysmaticlabs/prysm/v3/crypto/bls" "github.com/prysmaticlabs/prysm/v3/encoding/bytesutil" enginev1 "github.com/prysmaticlabs/prysm/v3/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/attestation" attaggregation "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/attestation/aggregation/attestations" "github.com/prysmaticlabs/prysm/v3/testing/assert" "github.com/prysmaticlabs/prysm/v3/testing/require" "github.com/prysmaticlabs/prysm/v3/testing/util" "github.com/prysmaticlabs/prysm/v3/time/slots" logTest "github.com/sirupsen/logrus/hooks/test" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) func TestProposer_ProposeBlock_OK(t *testing.T) { tests := []struct { name string block func([32]byte) *ethpb.GenericSignedBeaconBlock }{ { name: "phase0", block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock { blockToPropose := util.NewBeaconBlock() blockToPropose.Block.Slot = 5 blockToPropose.Block.ParentRoot = parent[:] blk := ðpb.GenericSignedBeaconBlock_Phase0{Phase0: blockToPropose} return ðpb.GenericSignedBeaconBlock{Block: blk} }, }, { name: "altair", block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock { blockToPropose := util.NewBeaconBlockAltair() blockToPropose.Block.Slot = 5 blockToPropose.Block.ParentRoot = parent[:] blk := ðpb.GenericSignedBeaconBlock_Altair{Altair: blockToPropose} return ðpb.GenericSignedBeaconBlock{Block: blk} }, }, { name: "bellatrix", block: func(parent [32]byte) *ethpb.GenericSignedBeaconBlock { blockToPropose := util.NewBeaconBlockBellatrix() blockToPropose.Block.Slot = 5 blockToPropose.Block.ParentRoot = parent[:] blk := ðpb.GenericSignedBeaconBlock_Bellatrix{Bellatrix: blockToPropose} return ðpb.GenericSignedBeaconBlock{Block: blk} }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() params.SetupTestConfigCleanup(t) params.OverrideBeaconConfig(params.MainnetConfig()) genesis := util.NewBeaconBlock() util.SaveBlock(t, ctx, db, genesis) numDeposits := uint64(64) beaconState, _ := util.DeterministicGenesisState(t, numDeposits) bsRoot, err := beaconState.HashTreeRoot(ctx) require.NoError(t, err) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err) require.NoError(t, db.SaveState(ctx, beaconState, genesisRoot), "Could not save genesis state") c := &mock.ChainService{Root: bsRoot[:], State: beaconState} proposerServer := &Server{ ChainStartFetcher: &mockExecution.Chain{}, Eth1InfoFetcher: &mockExecution.Chain{}, Eth1BlockFetcher: &mockExecution.Chain{}, BlockReceiver: c, HeadFetcher: c, BlockNotifier: c.BlockNotifier(), P2P: mockp2p.NewTestP2P(t), } blockToPropose := tt.block(bsRoot) res, err := proposerServer.ProposeBeaconBlock(context.Background(), blockToPropose) assert.NoError(t, err, "Could not propose block correctly") if res == nil || len(res.BlockRoot) == 0 { t.Error("No block root was returned") } }) } } func TestProposer_ComputeStateRoot_OK(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() params.SetupTestConfigCleanup(t) params.OverrideBeaconConfig(params.MainnetConfig()) beaconState, parentRoot, privKeys := util.DeterministicGenesisStateWithGenesisBlock(t, ctx, db, 100) proposerServer := &Server{ ChainStartFetcher: &mockExecution.Chain{}, Eth1InfoFetcher: &mockExecution.Chain{}, Eth1BlockFetcher: &mockExecution.Chain{}, StateGen: stategen.New(db), } req := util.NewBeaconBlock() req.Block.ProposerIndex = 21 req.Block.ParentRoot = parentRoot[:] req.Block.Slot = 1 require.NoError(t, beaconState.SetSlot(beaconState.Slot()+1)) randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) proposerIdx, err := helpers.BeaconProposerIndex(ctx, beaconState) require.NoError(t, err) require.NoError(t, beaconState.SetSlot(beaconState.Slot()-1)) req.Block.Body.RandaoReveal = randaoReveal currentEpoch := coretime.CurrentEpoch(beaconState) req.Signature, err = signing.ComputeDomainAndSign(beaconState, currentEpoch, req.Block, params.BeaconConfig().DomainBeaconProposer, privKeys[proposerIdx]) require.NoError(t, err) wsb, err := blocks.NewSignedBeaconBlock(req) require.NoError(t, err) _, err = proposerServer.computeStateRoot(context.Background(), wsb) require.NoError(t, err) } func TestProposer_PendingDeposits_Eth1DataVoteOK(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) newHeight := big.NewInt(height.Int64() + 11000) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), int(newHeight.Int64()): []byte("0x1"), }, } var votes []*ethpb.Eth1Data blockHash := make([]byte, 32) copy(blockHash, "0x1") vote := ðpb.Eth1Data{ DepositRoot: make([]byte, 32), BlockHash: blockHash, DepositCount: 3, } period := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod))) for i := 0; i <= int(period/2); i++ { votes = append(votes, vote) } blockHash = make([]byte, 32) copy(blockHash, "0x0") beaconState, err := util.NewBeaconState() require.NoError(t, err) require.NoError(t, beaconState.SetEth1DepositIndex(2)) require.NoError(t, beaconState.SetEth1Data(ðpb.Eth1Data{ DepositRoot: make([]byte, 32), BlockHash: blockHash, DepositCount: 2, })) require.NoError(t, beaconState.SetEth1DataVotes(votes)) blk := util.NewBeaconBlock() blkRoot, err := blk.Block.HashTreeRoot() require.NoError(t, err) bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) _, eth1Height, err := bs.canonicalEth1Data(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 0, eth1Height.Cmp(height)) newState, err := b.ProcessEth1DataInBlock(ctx, beaconState, blk.Block.Body.Eth1Data) require.NoError(t, err) if proto.Equal(newState.Eth1Data(), vote) { t.Errorf("eth1data in the state equal to vote, when not expected to"+ "have majority: Got %v", vote) } blk.Block.Body.Eth1Data = vote _, eth1Height, err = bs.canonicalEth1Data(ctx, beaconState, vote) require.NoError(t, err) assert.Equal(t, 0, eth1Height.Cmp(newHeight)) newState, err = b.ProcessEth1DataInBlock(ctx, beaconState, blk.Block.Body.Eth1Data) require.NoError(t, err) if !proto.Equal(newState.Eth1Data(), vote) { t.Errorf("eth1data in the state not of the expected kind: Got %v but wanted %v", newState.Eth1Data(), vote) } } func TestProposer_PendingDeposits_OutsideEth1FollowWindow(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), }, Eth1DepositIndex: 2, }) require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte // Using the merkleTreeIndex as the block number for this test... readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Eth1BlockHeight: 2, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Eth1BlockHeight: 8, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } recentDeposits := []*ethpb.DepositContainer{ { Index: 2, Eth1BlockHeight: 400, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("c"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 3, Eth1BlockHeight: 600, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("d"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } depositCache, err := depositcache.New() require.NoError(t, err) depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.HashTreeRoot() require.NoError(t, err) bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 0, len(deposits), "Received unexpected list of deposits") // It should not return the recent deposits after their follow window. // as latest block number makes no difference in retrieval of deposits p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) deposits, err = bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 0, len(deposits), "Received unexpected number of pending deposits") } func TestProposer_PendingDeposits_FollowsCorrectEth1Block(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) newHeight := big.NewInt(height.Int64() + 11000) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), int(newHeight.Int64()): []byte("0x1"), }, } var votes []*ethpb.Eth1Data vote := ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x1"), 32), DepositRoot: make([]byte, 32), DepositCount: 7, } period := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod))) for i := 0; i <= int(period/2); i++ { votes = append(votes, vote) } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: []byte("0x0"), DepositRoot: make([]byte, 32), DepositCount: 5, }, Eth1DepositIndex: 1, Eth1DataVotes: votes, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte // Using the merkleTreeIndex as the block number for this test... readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Eth1BlockHeight: 8, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Eth1BlockHeight: 14, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } recentDeposits := []*ethpb.DepositContainer{ { Index: 2, Eth1BlockHeight: 5000, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("c"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 3, Eth1BlockHeight: 6000, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("d"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } depositCache, err := depositcache.New() require.NoError(t, err) depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 0, len(deposits), "Received unexpected list of deposits") // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) // we should get our pending deposits once this vote pushes the vote tally to include // the updated eth1 data. deposits, err = bs.deposits(ctx, beaconState, vote) require.NoError(t, err) assert.Equal(t, len(recentDeposits), len(deposits), "Received unexpected number of pending deposits") } func TestProposer_PendingDeposits_CantReturnBelowStateEth1DepositIndex(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := util.NewBeaconState() require.NoError(t, err) require.NoError(t, beaconState.SetEth1Data(ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), DepositCount: 100, })) require.NoError(t, beaconState.SetEth1DepositIndex(10)) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } var recentDeposits []*ethpb.DepositContainer for i := int64(2); i < 16; i++ { recentDeposits = append(recentDeposits, ðpb.DepositContainer{ Index: i, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{byte(i)}, 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }) } depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") depositCache, err := depositcache.New() require.NoError(t, err) for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) expectedDeposits := 6 assert.Equal(t, expectedDeposits, len(deposits), "Received unexpected number of pending deposits") } func TestProposer_PendingDeposits_CantReturnMoreThanMax(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), DepositCount: 100, }, Eth1DepositIndex: 2, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } var recentDeposits []*ethpb.DepositContainer for i := int64(2); i < 22; i++ { recentDeposits = append(recentDeposits, ðpb.DepositContainer{ Index: i, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{byte(i)}, 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }) } depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") depositCache, err := depositcache.New() require.NoError(t, err) for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, height.Uint64(), dp.Index, root) } bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, params.BeaconConfig().MaxDeposits, uint64(len(deposits)), "Received unexpected number of pending deposits") } func TestProposer_PendingDeposits_CantReturnMoreThanDepositCount(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), DepositCount: 5, }, Eth1DepositIndex: 2, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } var recentDeposits []*ethpb.DepositContainer for i := int64(2); i < 22; i++ { recentDeposits = append(recentDeposits, ðpb.DepositContainer{ Index: i, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{byte(i)}, 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }) } depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") depositCache, err := depositcache.New() require.NoError(t, err) for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, } // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 3, len(deposits), "Received unexpected number of pending deposits") } func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), DepositCount: 4, }, Eth1DepositIndex: 1, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.Block.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte // Using the merkleTreeIndex as the block number for this test... finalizedDeposits := []*ethpb.DepositContainer{ { Index: 0, Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } recentDeposits := []*ethpb.DepositContainer{ { Index: 2, Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("c"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 3, Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("d"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } depositCache, err := depositcache.New() require.NoError(t, err) depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") for _, dp := range append(finalizedDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } dt, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance))) require.NoError(t, err) actualRoot, err := dt.HashTreeRoot() require.NoError(t, err) expectedRoot, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.Equal(t, expectedRoot, actualRoot, "Incorrect deposit trie root") } func TestProposer_DepositTrie_RebuildTrie(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), DepositCount: 4, }, Eth1DepositIndex: 1, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.Block.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte // Using the merkleTreeIndex as the block number for this test... finalizedDeposits := []*ethpb.DepositContainer{ { Index: 0, Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } recentDeposits := []*ethpb.DepositContainer{ { Index: 2, Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("c"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 3, Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("d"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } depositCache, err := depositcache.New() require.NoError(t, err) depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") for _, dp := range append(finalizedDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, root) } d := depositCache.AllDepositContainers(ctx) origDeposit, ok := proto.Clone(d[0].Deposit).(*ethpb.Deposit) assert.Equal(t, true, ok) junkCreds := mockCreds copy(junkCreds[:1], []byte{'A'}) // Mutate it since its a pointer d[0].Deposit.Data.WithdrawalCredentials = junkCreds[:] // Insert junk to corrupt trie. depositCache.InsertFinalizedDeposits(ctx, 2) // Add original back d[0].Deposit = origDeposit bs := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, } dt, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance))) require.NoError(t, err) expectedRoot, err := depositTrie.HashTreeRoot() require.NoError(t, err) actualRoot, err := dt.HashTreeRoot() require.NoError(t, err) assert.Equal(t, expectedRoot, actualRoot, "Incorrect deposit trie root") } func TestProposer_ValidateDepositTrie(t *testing.T) { tt := []struct { name string eth1dataCreator func() *ethpb.Eth1Data trieCreator func() *trie.SparseMerkleTrie success bool }{ { name: "invalid trie items", eth1dataCreator: func() *ethpb.Eth1Data { return ðpb.Eth1Data{DepositRoot: []byte{}, DepositCount: 10, BlockHash: []byte{}} }, trieCreator: func() *trie.SparseMerkleTrie { newTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) assert.NoError(t, err) return newTrie }, success: false, }, { name: "invalid deposit root", eth1dataCreator: func() *ethpb.Eth1Data { newTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) assert.NoError(t, err) assert.NoError(t, newTrie.Insert([]byte{'a'}, 0)) assert.NoError(t, newTrie.Insert([]byte{'b'}, 1)) assert.NoError(t, newTrie.Insert([]byte{'c'}, 2)) return ðpb.Eth1Data{DepositRoot: []byte{'B'}, DepositCount: 3, BlockHash: []byte{}} }, trieCreator: func() *trie.SparseMerkleTrie { newTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) assert.NoError(t, err) assert.NoError(t, newTrie.Insert([]byte{'a'}, 0)) assert.NoError(t, newTrie.Insert([]byte{'b'}, 1)) assert.NoError(t, newTrie.Insert([]byte{'c'}, 2)) return newTrie }, success: false, }, { name: "valid deposit trie", eth1dataCreator: func() *ethpb.Eth1Data { newTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) assert.NoError(t, err) assert.NoError(t, newTrie.Insert([]byte{'a'}, 0)) assert.NoError(t, newTrie.Insert([]byte{'b'}, 1)) assert.NoError(t, newTrie.Insert([]byte{'c'}, 2)) rt, err := newTrie.HashTreeRoot() require.NoError(t, err) return ðpb.Eth1Data{DepositRoot: rt[:], DepositCount: 3, BlockHash: []byte{}} }, trieCreator: func() *trie.SparseMerkleTrie { newTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) assert.NoError(t, err) assert.NoError(t, newTrie.Insert([]byte{'a'}, 0)) assert.NoError(t, newTrie.Insert([]byte{'b'}, 1)) assert.NoError(t, newTrie.Insert([]byte{'c'}, 2)) return newTrie }, success: true, }, } for _, test := range tt { t.Run(test.name, func(t *testing.T) { valid, err := validateDepositTrie(test.trieCreator(), test.eth1dataCreator()) assert.Equal(t, test.success, valid) if valid { assert.NoError(t, err) } }) } } func TestProposer_Eth1Data_MajorityVote(t *testing.T) { slot := types.Slot(64) earliestValidTime, latestValidTime := majorityVoteBoundaryTime(slot) dc := ethpb.DepositContainer{ Index: 0, Eth1BlockHeight: 0, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: make([]byte, 96), WithdrawalCredentials: make([]byte, 32), }}, } depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err) depositCache, err := depositcache.New() require.NoError(t, err) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(context.Background(), dc.Deposit, dc.Eth1BlockHeight, dc.Index, root)) t.Run("choose highest count", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("first"), DepositCount: 1}, {BlockHash: []byte("first"), DepositCount: 1}, {BlockHash: []byte("second"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("first") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count at earliest valid time - choose highest count", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("earliest"), DepositCount: 1}, {BlockHash: []byte("earliest"), DepositCount: 1}, {BlockHash: []byte("second"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("earliest") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count at latest valid time - choose highest count", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("first"), DepositCount: 1}, {BlockHash: []byte("latest"), DepositCount: 1}, {BlockHash: []byte("latest"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("latest") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count before range - choose highest count within range", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(49, earliestValidTime-1, []byte("before_range")). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("before_range"), DepositCount: 1}, {BlockHash: []byte("before_range"), DepositCount: 1}, {BlockHash: []byte("first"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("first") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count after range - choose highest count within range", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(100, latestValidTime, []byte("latest")). InsertBlock(101, latestValidTime+1, []byte("after_range")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("first"), DepositCount: 1}, {BlockHash: []byte("after_range"), DepositCount: 1}, {BlockHash: []byte("after_range"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("first") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count on unknown block - choose known block with highest count", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("unknown"), DepositCount: 1}, {BlockHash: []byte("unknown"), DepositCount: 1}, {BlockHash: []byte("first"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("first") assert.DeepEqual(t, expectedHash, hash) }) t.Run("no blocks in range - choose current eth1data", func(t *testing.T) { p := mockExecution.New(). InsertBlock(49, earliestValidTime-1, []byte("before_range")). InsertBlock(101, latestValidTime+1, []byte("after_range")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, }) require.NoError(t, err) currentEth1Data := ðpb.Eth1Data{DepositCount: 1, BlockHash: []byte("current")} ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: currentEth1Data}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("current") assert.DeepEqual(t, expectedHash, hash) }) t.Run("no votes in range - choose most recent block", func(t *testing.T) { p := mockExecution.New(). InsertBlock(49, earliestValidTime-1, []byte("before_range")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(101, latestValidTime+1, []byte("after_range")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("before_range"), DepositCount: 1}, {BlockHash: []byte("after_range"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := make([]byte, 32) copy(expectedHash, "second") assert.DeepEqual(t, expectedHash, hash) }) t.Run("no votes - choose more recent block", func(t *testing.T) { p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{}}) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := make([]byte, 32) copy(expectedHash, "latest") assert.DeepEqual(t, expectedHash, hash) }) t.Run("no votes and more recent block has less deposits - choose current eth1data", func(t *testing.T) { p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, }) require.NoError(t, err) // Set the deposit count in current eth1data to exceed the latest most recent block's deposit count. currentEth1Data := ðpb.Eth1Data{DepositCount: 2, BlockHash: []byte("current")} ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: currentEth1Data}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("current") assert.DeepEqual(t, expectedHash, hash) }) t.Run("same count - choose more recent block", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("first"), DepositCount: 1}, {BlockHash: []byte("second"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("second") assert.DeepEqual(t, expectedHash, hash) }) t.Run("highest count on block with less deposits - choose another block", func(t *testing.T) { t.Skip() p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(51, earliestValidTime+1, []byte("first")). InsertBlock(52, earliestValidTime+2, []byte("second")). InsertBlock(100, latestValidTime, []byte("latest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("no_new_deposits"), DepositCount: 0}, {BlockHash: []byte("no_new_deposits"), DepositCount: 0}, {BlockHash: []byte("second"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("second") assert.DeepEqual(t, expectedHash, hash) }) t.Run("only one block at earliest valid time - choose this block", func(t *testing.T) { t.Skip() p := mockExecution.New().InsertBlock(50, earliestValidTime, []byte("earliest")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("earliest"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("earliest") assert.DeepEqual(t, expectedHash, hash) }) t.Run("vote on last block before range - choose next block", func(t *testing.T) { p := mockExecution.New(). InsertBlock(49, earliestValidTime-1, []byte("before_range")). // It is important to have height `50` with time `earliestValidTime+1` and not `earliestValidTime` // because of earliest block increment in the algorithm. InsertBlock(50, earliestValidTime+1, []byte("first")) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("before_range"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 1}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := make([]byte, 32) copy(expectedHash, "first") assert.DeepEqual(t, expectedHash, hash) }) t.Run("no deposits - choose chain start eth1data", func(t *testing.T) { p := mockExecution.New(). InsertBlock(50, earliestValidTime, []byte("earliest")). InsertBlock(100, latestValidTime, []byte("latest")) p.Eth1Data = ðpb.Eth1Data{ BlockHash: []byte("eth1data"), } depositCache, err := depositcache.New() require.NoError(t, err) beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Slot: slot, Eth1DataVotes: []*ethpb.Eth1Data{ {BlockHash: []byte("earliest"), DepositCount: 1}, }, }) require.NoError(t, err) ps := &Server{ ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, BlockFetcher: p, DepositFetcher: depositCache, HeadFetcher: &mock.ChainService{ETH1Data: ðpb.Eth1Data{DepositCount: 0}}, } ctx := context.Background() majorityVoteEth1Data, err := ps.eth1DataMajorityVote(ctx, beaconState) require.NoError(t, err) hash := majorityVoteEth1Data.BlockHash expectedHash := []byte("eth1data") assert.DeepEqual(t, expectedHash, hash) }) } func TestProposer_FilterAttestation(t *testing.T) { params.SetupTestConfigCleanup(t) params.OverrideBeaconConfig(params.MainnetConfig()) genesis := util.NewBeaconBlock() numValidators := uint64(64) st, privKeys := util.DeterministicGenesisState(t, numValidators) require.NoError(t, st.SetGenesisValidatorsRoot(params.BeaconConfig().ZeroHash[:])) assert.NoError(t, st.SetSlot(1)) genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err) tests := []struct { name string wantedErr string inputAtts func() []*ethpb.Attestation expectedAtts func(inputAtts []*ethpb.Attestation) []*ethpb.Attestation }{ { name: "nil attestations", inputAtts: func() []*ethpb.Attestation { return nil }, expectedAtts: func(inputAtts []*ethpb.Attestation) []*ethpb.Attestation { return []*ethpb.Attestation{} }, }, { name: "invalid attestations", inputAtts: func() []*ethpb.Attestation { atts := make([]*ethpb.Attestation, 10) for i := 0; i < len(atts); i++ { atts[i] = util.HydrateAttestation(ðpb.Attestation{ Data: ðpb.AttestationData{ CommitteeIndex: types.CommitteeIndex(i), }, }) } return atts }, expectedAtts: func(inputAtts []*ethpb.Attestation) []*ethpb.Attestation { return []*ethpb.Attestation{} }, }, { name: "filter aggregates ok", inputAtts: func() []*ethpb.Attestation { atts := make([]*ethpb.Attestation, 10) for i := 0; i < len(atts); i++ { atts[i] = util.HydrateAttestation(ðpb.Attestation{ Data: ðpb.AttestationData{ CommitteeIndex: types.CommitteeIndex(i), Source: ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}, }, AggregationBits: bitfield.Bitlist{0b00000110}, }) committee, err := helpers.BeaconCommitteeFromState(context.Background(), st, atts[i].Data.Slot, atts[i].Data.CommitteeIndex) assert.NoError(t, err) attestingIndices, err := attestation.AttestingIndices(atts[i].AggregationBits, committee) require.NoError(t, err) assert.NoError(t, err) domain, err := signing.Domain(st.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, params.BeaconConfig().ZeroHash[:]) require.NoError(t, err) sigs := make([]bls.Signature, len(attestingIndices)) zeroSig := [96]byte{} atts[i].Signature = zeroSig[:] for i, indice := range attestingIndices { hashTreeRoot, err := signing.ComputeSigningRoot(atts[i].Data, domain) require.NoError(t, err) sig := privKeys[indice].Sign(hashTreeRoot[:]) sigs[i] = sig } atts[i].Signature = bls.AggregateSignatures(sigs).Marshal() } return atts }, expectedAtts: func(inputAtts []*ethpb.Attestation) []*ethpb.Attestation { return []*ethpb.Attestation{inputAtts[0]} }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { proposerServer := &Server{ AttPool: attestations.NewPool(), HeadFetcher: &mock.ChainService{State: st, Root: genesisRoot[:]}, } atts := tt.inputAtts() received, err := proposerServer.validateAndDeleteAttsInPool(context.Background(), st, atts) if tt.wantedErr != "" { assert.ErrorContains(t, tt.wantedErr, err) assert.Equal(t, nil, received) } else { assert.NoError(t, err) assert.DeepEqual(t, tt.expectedAtts(atts), received) } }) } } func TestProposer_Deposits_ReturnsEmptyList_IfLatestEth1DataEqGenesisEth1Block(t *testing.T) { ctx := context.Background() height := big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)) p := &mockExecution.Chain{ LatestBlockNumber: height, HashesByHeight: map[int][]byte{ int(height.Int64()): []byte("0x0"), }, GenesisEth1Block: height, } beaconState, err := v1.InitializeFromProto(ðpb.BeaconState{ Eth1Data: ðpb.Eth1Data{ BlockHash: bytesutil.PadTo([]byte("0x0"), 32), DepositRoot: make([]byte, 32), }, Eth1DepositIndex: 2, }) require.NoError(t, err) blk := util.NewBeaconBlock() blk.Block.Slot = beaconState.Slot() blkRoot, err := blk.Block.HashTreeRoot() require.NoError(t, err) var mockSig [96]byte var mockCreds [32]byte readyDeposits := []*ethpb.DepositContainer{ { Index: 0, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("a"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, { Index: 1, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("b"), 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }, } var recentDeposits []*ethpb.DepositContainer for i := int64(2); i < 22; i++ { recentDeposits = append(recentDeposits, ðpb.DepositContainer{ Index: i, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{byte(i)}, 48), Signature: mockSig[:], WithdrawalCredentials: mockCreds[:], }}, }) } depositTrie, err := trie.NewTrie(params.BeaconConfig().DepositContractTreeDepth) require.NoError(t, err, "Could not setup deposit trie") depositCache, err := depositcache.New() require.NoError(t, err) for _, dp := range append(readyDeposits, recentDeposits...) { depositHash, err := dp.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Unable to determine hashed value of deposit") assert.NoError(t, depositTrie.Insert(depositHash[:], int(dp.Index))) root, err := depositTrie.HashTreeRoot() require.NoError(t, err) assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root)) } for _, dp := range recentDeposits { root, err := depositTrie.HashTreeRoot() require.NoError(t, err) depositCache.InsertPendingDeposit(ctx, dp.Deposit, uint64(dp.Index), dp.Index, root) } bs := &Server{ BlockReceiver: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]}, ChainStartFetcher: p, Eth1InfoFetcher: p, Eth1BlockFetcher: p, DepositFetcher: depositCache, PendingDepositsFetcher: depositCache, } // It should also return the recent deposits after their follow window. p.LatestBlockNumber = big.NewInt(0).Add(p.LatestBlockNumber, big.NewInt(10000)) deposits, err := bs.deposits(ctx, beaconState, ðpb.Eth1Data{}) require.NoError(t, err) assert.Equal(t, 0, len(deposits), "Received unexpected number of pending deposits") } func TestProposer_DeleteAttsInPool_Aggregated(t *testing.T) { s := &Server{ AttPool: attestations.NewPool(), } priv, err := bls.RandKey() require.NoError(t, err) sig := priv.Sign([]byte("foo")).Marshal() aggregatedAtts := []*ethpb.Attestation{ util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b10101}, Signature: sig}), util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b11010}, Signature: sig})} unaggregatedAtts := []*ethpb.Attestation{ util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b10010}, Signature: sig}), util.HydrateAttestation(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1}, AggregationBits: bitfield.Bitlist{0b10100}, Signature: sig})} require.NoError(t, s.AttPool.SaveAggregatedAttestations(aggregatedAtts)) require.NoError(t, s.AttPool.SaveUnaggregatedAttestations(unaggregatedAtts)) aa, err := attaggregation.Aggregate(aggregatedAtts) require.NoError(t, err) require.NoError(t, s.deleteAttsInPool(context.Background(), append(aa, unaggregatedAtts...))) assert.Equal(t, 0, len(s.AttPool.AggregatedAttestations()), "Did not delete aggregated attestation") atts, err := s.AttPool.UnaggregatedAttestations() require.NoError(t, err) assert.Equal(t, 0, len(atts), "Did not delete unaggregated attestation") } func TestProposer_GetBeaconBlock_PreForkEpoch(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() params.SetupTestConfigCleanup(t) params.OverrideBeaconConfig(params.MainnetConfig()) beaconState, privKeys := util.DeterministicGenesisState(t, 64) stateRoot, err := beaconState.HashTreeRoot(ctx) require.NoError(t, err, "Could not hash genesis state") genesis := b.NewGenesisBlock(stateRoot[:]) genBlk := ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ Slot: genesis.Block.Slot, ParentRoot: genesis.Block.ParentRoot, StateRoot: genesis.Block.StateRoot, Body: ðpb.BeaconBlockBody{ RandaoReveal: genesis.Block.Body.RandaoReveal, Graffiti: genesis.Block.Body.Graffiti, Eth1Data: genesis.Block.Body.Eth1Data, }, }, Signature: genesis.Signature, } util.SaveBlock(t, ctx, db, genBlk) parentRoot, err := genBlk.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") proposerServer := &Server{ HeadFetcher: &mock.ChainService{State: beaconState, Root: parentRoot[:]}, SyncChecker: &mockSync.Sync{IsSyncing: false}, BlockReceiver: &mock.ChainService{}, HeadUpdater: &mock.ChainService{}, ChainStartFetcher: &mockExecution.Chain{}, Eth1InfoFetcher: &mockExecution.Chain{}, Eth1BlockFetcher: &mockExecution.Chain{}, MockEth1Votes: true, AttPool: attestations.NewPool(), SlashingsPool: slashings.NewPool(), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db), SyncCommitteePool: synccommittee.NewStore(), } randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) graffiti := bytesutil.ToBytes32([]byte("eth2")) req := ðpb.BlockRequest{ Slot: 1, RandaoReveal: randaoReveal, Graffiti: graffiti[:], } proposerSlashings := make([]*ethpb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings) for i := types.ValidatorIndex(0); uint64(i) < params.BeaconConfig().MaxProposerSlashings; i++ { proposerSlashing, err := util.GenerateProposerSlashingForValidator( beaconState, privKeys[i], i, /* validator index */ ) require.NoError(t, err) proposerSlashings[i] = proposerSlashing err = proposerServer.SlashingsPool.InsertProposerSlashing(context.Background(), beaconState, proposerSlashing) require.NoError(t, err) } attSlashings := make([]*ethpb.AttesterSlashing, params.BeaconConfig().MaxAttesterSlashings) for i := uint64(0); i < params.BeaconConfig().MaxAttesterSlashings; i++ { attesterSlashing, err := util.GenerateAttesterSlashingForValidator( beaconState, privKeys[i+params.BeaconConfig().MaxProposerSlashings], types.ValidatorIndex(i+params.BeaconConfig().MaxProposerSlashings), /* validator index */ ) require.NoError(t, err) attSlashings[i] = attesterSlashing err = proposerServer.SlashingsPool.InsertAttesterSlashing(context.Background(), beaconState, attesterSlashing) require.NoError(t, err) } block, err := proposerServer.GetBeaconBlock(ctx, req) require.NoError(t, err) phase0Blk, ok := block.GetBlock().(*ethpb.GenericBeaconBlock_Phase0) require.Equal(t, true, ok) assert.Equal(t, req.Slot, phase0Blk.Phase0.Slot) assert.DeepEqual(t, parentRoot[:], phase0Blk.Phase0.ParentRoot, "Expected block to have correct parent root") assert.DeepEqual(t, randaoReveal, phase0Blk.Phase0.Body.RandaoReveal, "Expected block to have correct randao reveal") assert.DeepEqual(t, req.Graffiti, phase0Blk.Phase0.Body.Graffiti, "Expected block to have correct Graffiti") assert.Equal(t, params.BeaconConfig().MaxProposerSlashings, uint64(len(phase0Blk.Phase0.Body.ProposerSlashings))) assert.DeepEqual(t, proposerSlashings, phase0Blk.Phase0.Body.ProposerSlashings) assert.Equal(t, params.BeaconConfig().MaxAttesterSlashings, uint64(len(phase0Blk.Phase0.Body.AttesterSlashings))) assert.DeepEqual(t, attSlashings, phase0Blk.Phase0.Body.AttesterSlashings) } func TestProposer_GetBeaconBlock_PostForkEpoch(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() params.SetupTestConfigCleanup(t) cfg := params.MainnetConfig().Copy() cfg.AltairForkEpoch = 1 params.OverrideBeaconConfig(cfg) beaconState, privKeys := util.DeterministicGenesisState(t, 64) stateRoot, err := beaconState.HashTreeRoot(ctx) require.NoError(t, err, "Could not hash genesis state") genesis := b.NewGenesisBlock(stateRoot[:]) util.SaveBlock(t, ctx, db, genesis) parentRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") altairSlot, err := slots.EpochStart(params.BeaconConfig().AltairForkEpoch) require.NoError(t, err) genAltair := ðpb.SignedBeaconBlockAltair{ Block: ðpb.BeaconBlockAltair{ Slot: altairSlot + 1, ParentRoot: parentRoot[:], StateRoot: genesis.Block.StateRoot, Body: ðpb.BeaconBlockBodyAltair{ RandaoReveal: genesis.Block.Body.RandaoReveal, Graffiti: genesis.Block.Body.Graffiti, Eth1Data: genesis.Block.Body.Eth1Data, SyncAggregate: ðpb.SyncAggregate{SyncCommitteeBits: bitfield.NewBitvector512(), SyncCommitteeSignature: make([]byte, 96)}, }, }, Signature: genesis.Signature, } blkRoot, err := genAltair.Block.HashTreeRoot() require.NoError(t, err) require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state") proposerServer := &Server{ HeadFetcher: &mock.ChainService{State: beaconState, Root: parentRoot[:]}, SyncChecker: &mockSync.Sync{IsSyncing: false}, BlockReceiver: &mock.ChainService{}, HeadUpdater: &mock.ChainService{}, ChainStartFetcher: &mockExecution.Chain{}, Eth1InfoFetcher: &mockExecution.Chain{}, Eth1BlockFetcher: &mockExecution.Chain{}, MockEth1Votes: true, AttPool: attestations.NewPool(), SlashingsPool: slashings.NewPool(), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db), SyncCommitteePool: synccommittee.NewStore(), } randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) graffiti := bytesutil.ToBytes32([]byte("eth2")) require.NoError(t, err) req := ðpb.BlockRequest{ Slot: altairSlot + 1, RandaoReveal: randaoReveal, Graffiti: graffiti[:], } proposerSlashings := make([]*ethpb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings) for i := types.ValidatorIndex(0); uint64(i) < params.BeaconConfig().MaxProposerSlashings; i++ { proposerSlashing, err := util.GenerateProposerSlashingForValidator( beaconState, privKeys[i], i, /* validator index */ ) require.NoError(t, err) proposerSlashings[i] = proposerSlashing err = proposerServer.SlashingsPool.InsertProposerSlashing(context.Background(), beaconState, proposerSlashing) require.NoError(t, err) } attSlashings := make([]*ethpb.AttesterSlashing, params.BeaconConfig().MaxAttesterSlashings) for i := uint64(0); i < params.BeaconConfig().MaxAttesterSlashings; i++ { attesterSlashing, err := util.GenerateAttesterSlashingForValidator( beaconState, privKeys[i+params.BeaconConfig().MaxProposerSlashings], types.ValidatorIndex(i+params.BeaconConfig().MaxProposerSlashings), /* validator index */ ) require.NoError(t, err) attSlashings[i] = attesterSlashing err = proposerServer.SlashingsPool.InsertAttesterSlashing(context.Background(), beaconState, attesterSlashing) require.NoError(t, err) } block, err := proposerServer.GetBeaconBlock(ctx, req) require.NoError(t, err) altairBlk, ok := block.GetBlock().(*ethpb.GenericBeaconBlock_Altair) require.Equal(t, true, ok) assert.Equal(t, req.Slot, altairBlk.Altair.Slot) assert.DeepEqual(t, parentRoot[:], altairBlk.Altair.ParentRoot, "Expected block to have correct parent root") assert.DeepEqual(t, randaoReveal, altairBlk.Altair.Body.RandaoReveal, "Expected block to have correct randao reveal") assert.DeepEqual(t, req.Graffiti, altairBlk.Altair.Body.Graffiti, "Expected block to have correct Graffiti") assert.Equal(t, params.BeaconConfig().MaxProposerSlashings, uint64(len(altairBlk.Altair.Body.ProposerSlashings))) assert.DeepEqual(t, proposerSlashings, altairBlk.Altair.Body.ProposerSlashings) assert.Equal(t, params.BeaconConfig().MaxAttesterSlashings, uint64(len(altairBlk.Altair.Body.AttesterSlashings))) assert.DeepEqual(t, attSlashings, altairBlk.Altair.Body.AttesterSlashings) } func TestProposer_GetBeaconBlock_BellatrixEpoch(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() hook := logTest.NewGlobal() terminalBlockHash := bytesutil.PadTo([]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 32) params.SetupTestConfigCleanup(t) cfg := params.MainnetConfig().Copy() cfg.BellatrixForkEpoch = 2 cfg.AltairForkEpoch = 1 cfg.TerminalBlockHash = common.BytesToHash(terminalBlockHash) cfg.TerminalBlockHashActivationEpoch = 2 params.OverrideBeaconConfig(cfg) beaconState, privKeys := util.DeterministicGenesisState(t, 64) stateRoot, err := beaconState.HashTreeRoot(ctx) require.NoError(t, err, "Could not hash genesis state") genesis := b.NewGenesisBlock(stateRoot[:]) util.SaveBlock(t, ctx, db, genesis) parentRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") bellatrixSlot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch) require.NoError(t, err) blk := ðpb.SignedBeaconBlockBellatrix{ Block: ðpb.BeaconBlockBellatrix{ Slot: bellatrixSlot + 1, ParentRoot: parentRoot[:], StateRoot: genesis.Block.StateRoot, Body: ðpb.BeaconBlockBodyBellatrix{ RandaoReveal: genesis.Block.Body.RandaoReveal, Graffiti: genesis.Block.Body.Graffiti, Eth1Data: genesis.Block.Body.Eth1Data, SyncAggregate: ðpb.SyncAggregate{SyncCommitteeBits: bitfield.NewBitvector512(), SyncCommitteeSignature: make([]byte, 96)}, ExecutionPayload: &enginev1.ExecutionPayload{ ParentHash: make([]byte, fieldparams.RootLength), FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), StateRoot: make([]byte, fieldparams.RootLength), ReceiptsRoot: make([]byte, fieldparams.RootLength), LogsBloom: make([]byte, fieldparams.LogsBloomLength), PrevRandao: make([]byte, fieldparams.RootLength), BaseFeePerGas: make([]byte, fieldparams.RootLength), BlockHash: make([]byte, fieldparams.RootLength), }, }, }, Signature: genesis.Signature, } blkRoot, err := blk.Block.HashTreeRoot() require.NoError(t, err) require.NoError(t, err, "Could not get signing root") require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state") c := mockExecution.New() c.HashesByHeight[0] = terminalBlockHash random, err := helpers.RandaoMix(beaconState, slots.ToEpoch(beaconState.Slot())) require.NoError(t, err) timeStamp, err := slots.ToTime(beaconState.GenesisTime(), bellatrixSlot+1) require.NoError(t, err) payload := &enginev1.ExecutionPayload{ ParentHash: make([]byte, fieldparams.RootLength), FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), StateRoot: make([]byte, fieldparams.RootLength), ReceiptsRoot: make([]byte, fieldparams.RootLength), LogsBloom: make([]byte, fieldparams.LogsBloomLength), PrevRandao: random, BaseFeePerGas: make([]byte, fieldparams.RootLength), BlockHash: make([]byte, fieldparams.RootLength), Transactions: make([][]byte, 0), ExtraData: make([]byte, 0), BlockNumber: 1, GasLimit: 2, GasUsed: 3, Timestamp: uint64(timeStamp.Unix()), } proposerServer := &Server{ HeadFetcher: &mock.ChainService{State: beaconState, Root: parentRoot[:], Optimistic: false}, TimeFetcher: &mock.ChainService{Genesis: time.Now()}, SyncChecker: &mockSync.Sync{IsSyncing: false}, BlockReceiver: &mock.ChainService{}, HeadUpdater: &mock.ChainService{}, ChainStartFetcher: &mockExecution.Chain{}, Eth1InfoFetcher: &mockExecution.Chain{}, Eth1BlockFetcher: c, MockEth1Votes: true, AttPool: attestations.NewPool(), SlashingsPool: slashings.NewPool(), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db), SyncCommitteePool: synccommittee.NewStore(), ExecutionEngineCaller: &mockExecution.EngineClient{ PayloadIDBytes: &enginev1.PayloadIDBytes{1}, ExecutionPayload: payload, }, BeaconDB: db, ProposerSlotIndexCache: cache.NewProposerPayloadIDsCache(), } randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) graffiti := bytesutil.ToBytes32([]byte("eth2")) require.NoError(t, err) req := ðpb.BlockRequest{ Slot: bellatrixSlot + 1, RandaoReveal: randaoReveal, Graffiti: graffiti[:], } block, err := proposerServer.GetBeaconBlock(ctx, req) require.NoError(t, err) bellatrixBlk, ok := block.GetBlock().(*ethpb.GenericBeaconBlock_Bellatrix) require.Equal(t, true, ok) assert.Equal(t, req.Slot, bellatrixBlk.Bellatrix.Slot) assert.DeepEqual(t, parentRoot[:], bellatrixBlk.Bellatrix.ParentRoot, "Expected block to have correct parent root") assert.DeepEqual(t, randaoReveal, bellatrixBlk.Bellatrix.Body.RandaoReveal, "Expected block to have correct randao reveal") assert.DeepEqual(t, req.Graffiti, bellatrixBlk.Bellatrix.Body.Graffiti, "Expected block to have correct Graffiti") require.LogsContain(t, hook, "Fee recipient is currently using the burn address") require.DeepEqual(t, payload, bellatrixBlk.Bellatrix.Body.ExecutionPayload) // Payload should equal. // Operator sets default fee recipient to not be burned through beacon node cli. newHook := logTest.NewGlobal() params.SetupTestConfigCleanup(t) cfg = params.MainnetConfig().Copy() cfg.DefaultFeeRecipient = common.Address{'b'} params.OverrideBeaconConfig(cfg) _, err = proposerServer.GetBeaconBlock(ctx, req) require.NoError(t, err) require.LogsDoNotContain(t, newHook, "Fee recipient is currently using the burn address") } func TestProposer_GetBeaconBlock_Optimistic(t *testing.T) { params.SetupTestConfigCleanup(t) cfg := params.MainnetConfig().Copy() cfg.BellatrixForkEpoch = 2 cfg.AltairForkEpoch = 1 params.OverrideBeaconConfig(cfg) bellatrixSlot, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch) require.NoError(t, err) proposerServer := &Server{OptimisticModeFetcher: &mock.ChainService{Optimistic: true}, TimeFetcher: &mock.ChainService{}} req := ðpb.BlockRequest{ Slot: bellatrixSlot + 1, } _, err = proposerServer.GetBeaconBlock(context.Background(), req) s, ok := status.FromError(err) require.Equal(t, true, ok) require.DeepEqual(t, codes.Unavailable, s.Code()) require.ErrorContains(t, errOptimisticMode.Error(), err) } func TestProposer_GetSyncAggregate_OK(t *testing.T) { proposerServer := &Server{ SyncChecker: &mockSync.Sync{IsSyncing: false}, SyncCommitteePool: synccommittee.NewStore(), } r := params.BeaconConfig().ZeroHash conts := []*ethpb.SyncCommitteeContribution{ {Slot: 1, SubcommitteeIndex: 0, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b0001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 0, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 0, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1110}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 1, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b0001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 1, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 1, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1110}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 2, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b0001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 2, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 2, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1110}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 3, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b0001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 3, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1001}, BlockRoot: r[:]}, {Slot: 1, SubcommitteeIndex: 3, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b1110}, BlockRoot: r[:]}, {Slot: 2, SubcommitteeIndex: 0, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b10101010}, BlockRoot: r[:]}, {Slot: 2, SubcommitteeIndex: 1, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b10101010}, BlockRoot: r[:]}, {Slot: 2, SubcommitteeIndex: 2, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b10101010}, BlockRoot: r[:]}, {Slot: 2, SubcommitteeIndex: 3, Signature: bls.NewAggregateSignature().Marshal(), AggregationBits: []byte{0b10101010}, BlockRoot: r[:]}, } for _, cont := range conts { require.NoError(t, proposerServer.SyncCommitteePool.SaveSyncCommitteeContribution(cont)) } aggregate, err := proposerServer.getSyncAggregate(context.Background(), 1, bytesutil.ToBytes32(conts[0].BlockRoot)) require.NoError(t, err) require.DeepEqual(t, bitfield.Bitvector512{0xf, 0xf, 0xf, 0xf}, aggregate.SyncCommitteeBits) aggregate, err = proposerServer.getSyncAggregate(context.Background(), 2, bytesutil.ToBytes32(conts[0].BlockRoot)) require.NoError(t, err) require.DeepEqual(t, bitfield.Bitvector512{0xaa, 0xaa, 0xaa, 0xaa}, aggregate.SyncCommitteeBits) aggregate, err = proposerServer.getSyncAggregate(context.Background(), 3, bytesutil.ToBytes32(conts[0].BlockRoot)) require.NoError(t, err) require.DeepEqual(t, bitfield.NewBitvector512(), aggregate.SyncCommitteeBits) } func TestProposer_PrepareBeaconProposer(t *testing.T) { type args struct { request *ethpb.PrepareBeaconProposerRequest } tests := []struct { name string args args wantErr string }{ { name: "Happy Path", args: args{ request: ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ { FeeRecipient: make([]byte, fieldparams.FeeRecipientLength), ValidatorIndex: 1, }, }, }, }, wantErr: "", }, { name: "invalid fee recipient length", args: args{ request: ðpb.PrepareBeaconProposerRequest{ Recipients: []*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer{ { FeeRecipient: make([]byte, fieldparams.BLSPubkeyLength), ValidatorIndex: 1, }, }, }, }, wantErr: "Invalid fee recipient address", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { db := dbutil.SetupDB(t) ctx := context.Background() proposerServer := &Server{BeaconDB: db} _, err := proposerServer.PrepareBeaconProposer(ctx, tt.args.request) if tt.wantErr != "" { require.ErrorContains(t, tt.wantErr, err) return } require.NoError(t, err) address, err := proposerServer.BeaconDB.FeeRecipientByValidatorID(ctx, 1) require.NoError(t, err) require.Equal(t, common.BytesToAddress(tt.args.request.Recipients[0].FeeRecipient), address) }) } } func TestProposer_SubmitValidatorRegistrations(t *testing.T) { ctx := context.Background() proposerServer := &Server{} reg := ðpb.SignedValidatorRegistrationsV1{} _, err := proposerServer.SubmitValidatorRegistrations(ctx, reg) require.ErrorContains(t, builder.ErrNoBuilder.Error(), err) proposerServer = &Server{BlockBuilder: &builderTest.MockBuilderService{}} _, err = proposerServer.SubmitValidatorRegistrations(ctx, reg) require.ErrorContains(t, builder.ErrNoBuilder.Error(), err) proposerServer = &Server{BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true}} _, err = proposerServer.SubmitValidatorRegistrations(ctx, reg) require.NoError(t, err) proposerServer = &Server{BlockBuilder: &builderTest.MockBuilderService{HasConfigured: true, ErrRegisterValidator: errors.New("bad")}} _, err = proposerServer.SubmitValidatorRegistrations(ctx, reg) require.ErrorContains(t, "bad", err) } func majorityVoteBoundaryTime(slot types.Slot) (uint64, uint64) { s := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod)) slotStartTime := uint64(mockExecution.GenesisTime) + uint64((slot - (slot % (s))).Mul(params.BeaconConfig().SecondsPerSlot)) earliestValidTime := slotStartTime - 2*params.BeaconConfig().SecondsPerETH1Block*params.BeaconConfig().Eth1FollowDistance latestValidTime := slotStartTime - params.BeaconConfig().SecondsPerETH1Block*params.BeaconConfig().Eth1FollowDistance return earliestValidTime, latestValidTime }