package depositcache import ( "bytes" "context" "fmt" "math/big" "testing" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" dbpb "github.com/prysmaticlabs/prysm/proto/beacon/db" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil/assert" "github.com/prysmaticlabs/prysm/shared/testutil/require" "github.com/prysmaticlabs/prysm/shared/trieutil" logTest "github.com/sirupsen/logrus/hooks/test" ) const nilDepositErr = "Ignoring nil deposit insertion" var _ = DepositFetcher(&DepositCache{}) func TestInsertDeposit_LogsOnNilDepositInsertion(t *testing.T) { hook := logTest.NewGlobal() dc, err := NewDepositCache() require.NoError(t, err) dc.InsertDeposit(context.Background(), nil, 1, 0, [32]byte{}) require.Equal(t, 0, len(dc.deposits), "Number of deposits changed") assert.Equal(t, nilDepositErr, hook.LastEntry().Message) } func TestInsertDeposit_MaintainsSortedOrderByIndex(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) insertions := []struct { blkNum uint64 deposit *ethpb.Deposit index int64 }{ { blkNum: 0, deposit: ðpb.Deposit{}, index: 0, }, { blkNum: 0, deposit: ðpb.Deposit{}, index: 3, }, { blkNum: 0, deposit: ðpb.Deposit{}, index: 1, }, { blkNum: 0, deposit: ðpb.Deposit{}, index: 4, }, } for _, ins := range insertions { dc.InsertDeposit(context.Background(), ins.deposit, ins.blkNum, ins.index, [32]byte{}) } expectedIndices := []int64{0, 1, 3, 4} for i, ei := range expectedIndices { assert.Equal(t, ei, dc.deposits[i].Index, fmt.Sprintf("dc.deposits[%d].Index = %d, wanted %d", i, dc.deposits[i].Index, ei)) } } func TestAllDeposits_ReturnsAllDeposits(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) deposits := []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{}, }, } dc.deposits = deposits d := dc.AllDeposits(context.Background(), nil) assert.Equal(t, len(deposits), len(d)) } func TestAllDeposits_FiltersDepositUpToAndIncludingBlockNumber(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) deposits := []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{}, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{}, }, } dc.deposits = deposits d := dc.AllDeposits(context.Background(), big.NewInt(11)) assert.Equal(t, 5, len(d)) } func TestDepositsNumberAndRootAtHeight_ReturnsAppropriateCountAndRoot(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) dc.deposits = []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, DepositRoot: bytesutil.PadTo([]byte("root"), 32), }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, } n, root := dc.DepositsNumberAndRootAtHeight(context.Background(), big.NewInt(11)) assert.Equal(t, 5, int(n)) assert.Equal(t, bytesutil.ToBytes32([]byte("root")), root) } func TestDepositsNumberAndRootAtHeight_ReturnsEmptyTrieIfBlockHeightLessThanOldestDeposit(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) dc.deposits = []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, DepositRoot: bytesutil.PadTo([]byte("root"), 32), }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: make([]byte, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, DepositRoot: bytesutil.PadTo([]byte("root"), 32), }, } n, root := dc.DepositsNumberAndRootAtHeight(context.Background(), big.NewInt(2)) assert.Equal(t, 0, int(n)) assert.Equal(t, [32]byte{}, root) } func TestDepositByPubkey_ReturnsFirstMatchingDeposit(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) dc.deposits = []*dbpb.DepositContainer{ { Eth1BlockHeight: 9, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("pk0"), 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("pk1"), 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("pk1"), 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, { Eth1BlockHeight: 12, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte("pk2"), 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, }, } pk1 := bytesutil.PadTo([]byte("pk1"), 48) dep, blkNum := dc.DepositByPubkey(context.Background(), pk1) if dep == nil || !bytes.Equal(dep.Data.PublicKey, pk1) { t.Error("Returned wrong deposit") } assert.Equal(t, 0, blkNum.Cmp(big.NewInt(10)), fmt.Sprintf("Returned wrong block number %v", blkNum)) } func TestFinalizedDeposits_DepositsCachedCorrectly(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) finalizedDeposits := []*dbpb.DepositContainer{ { Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{0}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 0, }, { Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{1}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 1, }, { Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{2}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 2, }, } dc.deposits = append(finalizedDeposits, &dbpb.DepositContainer{ Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{3}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 3, }) dc.InsertFinalizedDeposits(context.Background(), 2) cachedDeposits := dc.FinalizedDeposits(context.Background()) require.NotNil(t, cachedDeposits, "Deposits not cached") assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex) var deps [][]byte for _, d := range finalizedDeposits { hash, err := d.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Could not hash deposit data") deps = append(deps, hash[:]) } trie, err := trieutil.GenerateTrieFromItems(deps, int(params.BeaconConfig().DepositContractTreeDepth)) require.NoError(t, err, "Could not generate deposit trie") assert.Equal(t, trie.HashTreeRoot(), cachedDeposits.Deposits.HashTreeRoot()) } func TestFinalizedDeposits_UtilizesPreviouslyCachedDeposits(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) oldFinalizedDeposits := []*dbpb.DepositContainer{ { Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{0}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 0, }, { Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{1}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 1, }, } newFinalizedDeposit := dbpb.DepositContainer{ Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{2}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 2, } dc.deposits = oldFinalizedDeposits dc.InsertFinalizedDeposits(context.Background(), 1) // Artificially exclude old deposits so that they can only be retrieved from previously finalized deposits. dc.deposits = []*dbpb.DepositContainer{&newFinalizedDeposit} dc.InsertFinalizedDeposits(context.Background(), 2) cachedDeposits := dc.FinalizedDeposits(context.Background()) require.NotNil(t, cachedDeposits, "Deposits not cached") assert.Equal(t, int64(2), cachedDeposits.MerkleTrieIndex) var deps [][]byte for _, d := range append(oldFinalizedDeposits, &newFinalizedDeposit) { hash, err := d.Deposit.Data.HashTreeRoot() require.NoError(t, err, "Could not hash deposit data") deps = append(deps, hash[:]) } trie, err := trieutil.GenerateTrieFromItems(deps, int(params.BeaconConfig().DepositContractTreeDepth)) require.NoError(t, err, "Could not generate deposit trie") assert.Equal(t, trie.HashTreeRoot(), cachedDeposits.Deposits.HashTreeRoot()) } func TestFinalizedDeposits_InitializedCorrectly(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) finalizedDeposits := dc.finalizedDeposits assert.NotNil(t, finalizedDeposits) assert.NotNil(t, finalizedDeposits.Deposits) assert.Equal(t, int64(-1), finalizedDeposits.MerkleTrieIndex) } func TestNonFinalizedDeposits_ReturnsAllNonFinalizedDeposits(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) finalizedDeposits := []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{0}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 0, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{1}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 1, }, } dc.deposits = append(finalizedDeposits, &dbpb.DepositContainer{ Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{2}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 2, }, &dbpb.DepositContainer{ Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{3}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 3, }) dc.InsertFinalizedDeposits(context.Background(), 1) deps := dc.NonFinalizedDeposits(context.Background(), nil) assert.Equal(t, 2, len(deps)) } func TestNonFinalizedDeposits_ReturnsNonFinalizedDepositsUpToBlockNumber(t *testing.T) { dc, err := NewDepositCache() require.NoError(t, err) finalizedDeposits := []*dbpb.DepositContainer{ { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{0}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 0, }, { Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{1}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 1, }, } dc.deposits = append(finalizedDeposits, &dbpb.DepositContainer{ Eth1BlockHeight: 10, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{2}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 2, }, &dbpb.DepositContainer{ Eth1BlockHeight: 11, Deposit: ðpb.Deposit{ Data: ðpb.Deposit_Data{ PublicKey: bytesutil.PadTo([]byte{3}, 48), WithdrawalCredentials: make([]byte, 32), Signature: make([]byte, 96), }, }, Index: 3, }) dc.InsertFinalizedDeposits(context.Background(), 1) deps := dc.NonFinalizedDeposits(context.Background(), big.NewInt(10)) assert.Equal(t, 1, len(deps)) }