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:
Nishant Das
2021-05-10 07:34:10 +08:00
committed by GitHub
parent 050b244fa6
commit 255354f27e
2 changed files with 247 additions and 4 deletions

View File

@@ -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

View File

@@ -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, &ethpb.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: &ethpb.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: &ethpb.Deposit{
Data: &ethpb.Deposit_Data{
PublicKey: bytesutil.PadTo([]byte("a"), 48),
Signature: mockSig[:],
WithdrawalCredentials: mockCreds[:],
}},
},
{
Index: 1,
Eth1BlockHeight: 10,
Deposit: &ethpb.Deposit{
Data: &ethpb.Deposit_Data{
PublicKey: bytesutil.PadTo([]byte("b"), 48),
Signature: mockSig[:],
WithdrawalCredentials: mockCreds[:],
}},
},
}
recentDeposits := []*dbpb.DepositContainer{
{
Index: 2,
Eth1BlockHeight: 11,
Deposit: &ethpb.Deposit{
Data: &ethpb.Deposit_Data{
PublicKey: bytesutil.PadTo([]byte("c"), 48),
Signature: mockSig[:],
WithdrawalCredentials: mockCreds[:],
}},
},
{
Index: 3,
Eth1BlockHeight: 11,
Deposit: &ethpb.Deposit{
Data: &ethpb.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, &ethpb.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 &ethpb.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 &ethpb.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 &ethpb.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()