diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel index 7d6a5a201..0bfe5eabe 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/BUILD.bazel @@ -102,6 +102,7 @@ go_test( "blocks_test.go", "exit_test.go", "proposer_attestations_test.go", + "proposer_deposits_test.go", "proposer_execution_payload_test.go", "proposer_sync_aggregate_test.go", "proposer_test.go", diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go index 60ebd3349..05fd3e4ca 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits.go @@ -10,6 +10,7 @@ import ( "github.com/prysmaticlabs/prysm/config/params" "github.com/prysmaticlabs/prysm/container/trie" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" + "github.com/sirupsen/logrus" "go.opencensus.io/trace" "golang.org/x/sync/errgroup" "google.golang.org/grpc/codes" @@ -139,6 +140,13 @@ func (vs *Server) depositTrie(ctx context.Context, canonicalEth1Data *ethpb.Eth1 upToEth1DataDeposits := vs.DepositFetcher.NonFinalizedDeposits(ctx, finalizedDeposits.MerkleTrieIndex, canonicalEth1DataHeight) insertIndex := finalizedDeposits.MerkleTrieIndex + 1 + if shouldRebuildTrie(canonicalEth1Data.DepositCount, uint64(len(upToEth1DataDeposits))) { + log.WithFields(logrus.Fields{ + "unfinalized deposits": len(upToEth1DataDeposits), + "total deposit count": canonicalEth1Data.DepositCount, + }).Warn("Too many unfinalized deposits, building a deposit trie from scratch.") + return vs.rebuildDepositTrie(ctx, canonicalEth1Data, canonicalEth1DataHeight) + } for _, dep := range upToEth1DataDeposits { depHash, err := dep.Data.HashTreeRoot() if err != nil { @@ -210,3 +218,21 @@ func constructMerkleProof(trie *trie.SparseMerkleTrie, index int, deposit *ethpb deposit.Proof = proof return deposit, nil } + +// This checks whether we should fallback to rebuild the whole deposit trie. +func shouldRebuildTrie(totalDepCount, unFinalizedDeps uint64) bool { + if totalDepCount == 0 || unFinalizedDeps == 0 { + return false + } + // The total number interior nodes hashed in a binary trie would be + // x - 1, where x is the total number of leaves of the trie. For simplicity's + // sake we assume it as x here as this function is meant as a heuristic rather than + // and exact calculation. + // + // Since the effective_depth = log(x) , the total depth can be represented as + // depth = log(x) + k. We can then find the total number of nodes to be hashed by + // calculating y (log(x) + k) , where y is the number of unfinalized deposits. For + // the deposit trie, the value of log(x) + k is fixed at 32. + unFinalizedCompute := unFinalizedDeps * params.BeaconConfig().DepositContractTreeDepth + return unFinalizedCompute > totalDepCount +} diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits_test.go new file mode 100644 index 000000000..00f96db41 --- /dev/null +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deposits_test.go @@ -0,0 +1,56 @@ +package validator + +import "testing" + +func TestShouldFallback(t *testing.T) { + tests := []struct { + name string + totalDepCount uint64 + unFinalizedDeps uint64 + want bool + }{ + { + name: "0 dep count", + totalDepCount: 0, + unFinalizedDeps: 100, + want: false, + }, + { + name: "0 unfinalized count", + totalDepCount: 100, + unFinalizedDeps: 0, + want: false, + }, + { + name: "equal number of deposits and non finalized deposits", + totalDepCount: 1000, + unFinalizedDeps: 1000, + want: true, + }, + { + name: "large number of non finalized deposits", + totalDepCount: 300000, + unFinalizedDeps: 100000, + want: true, + }, + { + name: "small number of non finalized deposits", + totalDepCount: 300000, + unFinalizedDeps: 2000, + want: false, + }, + { + name: "unfinalized deposits beyond threshold", + totalDepCount: 300000, + unFinalizedDeps: 10000, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := shouldRebuildTrie(tt.totalDepCount, tt.unFinalizedDeps); got != tt.want { + t.Errorf("shouldRebuildTrie() = %v, want %v", got, tt.want) + } + }) + } +}