mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-10 07:58:22 -05:00
Fallback For Deposit Trie Creation (#8869)
* fallback * make it an error * comment * Update beacon-chain/rpc/validator/proposer.go Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: Radosław Kapka <rkapka@wp.pl>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -415,7 +416,7 @@ func (vs *Server) deposits(
|
||||
return []*ethpb.Deposit{}, nil
|
||||
}
|
||||
|
||||
depositTrie, err := vs.depositTrie(ctx, canonicalEth1DataHeight)
|
||||
depositTrie, err := vs.depositTrie(ctx, canonicalEth1Data, canonicalEth1DataHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve deposit trie")
|
||||
}
|
||||
@@ -478,7 +479,7 @@ func (vs *Server) canonicalEth1Data(
|
||||
return canonicalEth1Data, canonicalEth1DataHeight, nil
|
||||
}
|
||||
|
||||
func (vs *Server) depositTrie(ctx context.Context, canonicalEth1DataHeight *big.Int) (*trieutil.SparseMerkleTrie, error) {
|
||||
func (vs *Server) depositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (*trieutil.SparseMerkleTrie, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.depositTrie")
|
||||
defer span.End()
|
||||
|
||||
@@ -497,10 +498,56 @@ func (vs *Server) depositTrie(ctx context.Context, canonicalEth1DataHeight *big.
|
||||
depositTrie.Insert(depHash[:], int(insertIndex))
|
||||
insertIndex++
|
||||
}
|
||||
valid, err := vs.validateDepositTrie(depositTrie, canonicalEth1Data)
|
||||
// Log a warning here, as the cached trie is invalid.
|
||||
if !valid {
|
||||
log.Warnf("Cached deposit trie is invalid, rebuilding it now: %v", err)
|
||||
return vs.rebuildDepositTrie(ctx, canonicalEth1Data, canonicalEth1DataHeight)
|
||||
}
|
||||
|
||||
return depositTrie, nil
|
||||
}
|
||||
|
||||
// rebuilds our deposit trie by recreating it from all processed deposits till
|
||||
// specified eth1 block height.
|
||||
func (vs *Server) rebuildDepositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1Data, canonicalEth1DataHeight *big.Int) (*trieutil.SparseMerkleTrie, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "ProposerServer.rebuildDepositTrie")
|
||||
defer span.End()
|
||||
|
||||
deposits := vs.DepositFetcher.AllDeposits(ctx, canonicalEth1DataHeight)
|
||||
trieItems := make([][]byte, 0, len(deposits))
|
||||
for _, dep := range deposits {
|
||||
depHash, err := dep.Data.HashTreeRoot()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not hash deposit data")
|
||||
}
|
||||
trieItems = append(trieItems, depHash[:])
|
||||
}
|
||||
depositTrie, err := trieutil.GenerateTrieFromItems(trieItems, params.BeaconConfig().DepositContractTreeDepth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valid, err := vs.validateDepositTrie(depositTrie, canonicalEth1Data)
|
||||
// Log an error here, as even with rebuilding the trie, it is still invalid.
|
||||
if !valid {
|
||||
log.Errorf("Rebuilt deposit trie is invalid: %v", err)
|
||||
}
|
||||
return depositTrie, nil
|
||||
}
|
||||
|
||||
// validate that the provided deposit trie matches up with the canonical eth1 data provided.
|
||||
func (vs *Server) validateDepositTrie(trie *trieutil.SparseMerkleTrie, canonicalEth1Data *ethpb.Eth1Data) (bool, error) {
|
||||
if trie.NumOfItems() != int(canonicalEth1Data.DepositCount) {
|
||||
return false, errors.Errorf("wanted the canonical count of %d but received %d", canonicalEth1Data.DepositCount, trie.NumOfItems())
|
||||
}
|
||||
rt := trie.HashTreeRoot()
|
||||
if !bytes.Equal(rt[:], canonicalEth1Data.DepositRoot) {
|
||||
return false, errors.Errorf("wanted the canonical deposit root of %#x but received %#x", canonicalEth1Data.DepositRoot, rt)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// in case no vote for new eth1data vote considered best vote we
|
||||
// default into returning the latest deposit root and the block
|
||||
// hash of eth1 block hash that is FOLLOW_DISTANCE back from its
|
||||
|
||||
@@ -981,7 +981,6 @@ func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) {
|
||||
depositTrie.Insert(depositHash[:], int(dp.Index))
|
||||
assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.Root()))
|
||||
}
|
||||
depositCache.InsertFinalizedDeposits(ctx, 2)
|
||||
for _, dp := range recentDeposits {
|
||||
depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.Root())
|
||||
}
|
||||
@@ -996,7 +995,7 @@ func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) {
|
||||
HeadFetcher: &mock.ChainService{State: beaconState, Root: blkRoot[:]},
|
||||
}
|
||||
|
||||
trie, err := bs.depositTrie(ctx, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)))
|
||||
trie, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)))
|
||||
require.NoError(t, err)
|
||||
|
||||
actualRoot := trie.HashTreeRoot()
|
||||
@@ -1004,6 +1003,203 @@ func TestProposer_DepositTrie_UtilizesCachedFinalizedDeposits(t *testing.T) {
|
||||
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 := &mockPOW.POWChain{
|
||||
LatestBlockNumber: height,
|
||||
HashesByHeight: map[int][]byte{
|
||||
int(height.Int64()): []byte("0x0"),
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := stateV0.InitializeFromProto(&pbp2p.BeaconState{
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
BlockHash: bytesutil.PadTo([]byte("0x0"), 32),
|
||||
DepositRoot: make([]byte, 32),
|
||||
DepositCount: 4,
|
||||
},
|
||||
Eth1DepositIndex: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
blk := testutil.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 := []*dbpb.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 := []*dbpb.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 := trieutil.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")
|
||||
|
||||
depositTrie.Insert(depositHash[:], int(dp.Index))
|
||||
assert.NoError(t, depositCache.InsertDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.Root()))
|
||||
}
|
||||
for _, dp := range recentDeposits {
|
||||
depositCache.InsertPendingDeposit(ctx, dp.Deposit, dp.Eth1BlockHeight, dp.Index, depositTrie.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[:]},
|
||||
}
|
||||
|
||||
trie, err := bs.depositTrie(ctx, ðpb.Eth1Data{}, big.NewInt(int64(params.BeaconConfig().Eth1FollowDistance)))
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedRoot := depositTrie.HashTreeRoot()
|
||||
actualRoot := trie.HashTreeRoot()
|
||||
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() *trieutil.SparseMerkleTrie
|
||||
success bool
|
||||
}{
|
||||
{
|
||||
name: "invalid trie items",
|
||||
eth1dataCreator: func() *ethpb.Eth1Data {
|
||||
return ðpb.Eth1Data{DepositRoot: []byte{}, DepositCount: 10, BlockHash: []byte{}}
|
||||
},
|
||||
trieCreator: func() *trieutil.SparseMerkleTrie {
|
||||
trie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
assert.NoError(t, err)
|
||||
return trie
|
||||
},
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
name: "invalid deposit root",
|
||||
eth1dataCreator: func() *ethpb.Eth1Data {
|
||||
trie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
assert.NoError(t, err)
|
||||
trie.Insert([]byte{'a'}, 0)
|
||||
trie.Insert([]byte{'b'}, 1)
|
||||
trie.Insert([]byte{'c'}, 2)
|
||||
return ðpb.Eth1Data{DepositRoot: []byte{'B'}, DepositCount: 3, BlockHash: []byte{}}
|
||||
},
|
||||
trieCreator: func() *trieutil.SparseMerkleTrie {
|
||||
trie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
assert.NoError(t, err)
|
||||
trie.Insert([]byte{'a'}, 0)
|
||||
trie.Insert([]byte{'b'}, 1)
|
||||
trie.Insert([]byte{'c'}, 2)
|
||||
return trie
|
||||
},
|
||||
success: false,
|
||||
},
|
||||
{
|
||||
name: "valid deposit trie",
|
||||
eth1dataCreator: func() *ethpb.Eth1Data {
|
||||
trie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
assert.NoError(t, err)
|
||||
trie.Insert([]byte{'a'}, 0)
|
||||
trie.Insert([]byte{'b'}, 1)
|
||||
trie.Insert([]byte{'c'}, 2)
|
||||
rt := trie.HashTreeRoot()
|
||||
return ðpb.Eth1Data{DepositRoot: rt[:], DepositCount: 3, BlockHash: []byte{}}
|
||||
},
|
||||
trieCreator: func() *trieutil.SparseMerkleTrie {
|
||||
trie, err := trieutil.NewTrie(params.BeaconConfig().DepositContractTreeDepth)
|
||||
assert.NoError(t, err)
|
||||
trie.Insert([]byte{'a'}, 0)
|
||||
trie.Insert([]byte{'b'}, 1)
|
||||
trie.Insert([]byte{'c'}, 2)
|
||||
return trie
|
||||
},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tt {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
server := &Server{}
|
||||
valid, err := server.validateDepositTrie(test.trieCreator(), test.eth1dataCreator())
|
||||
assert.Equal(t, test.success, valid)
|
||||
if valid {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProposer_Eth1Data_NoBlockExists(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user