mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 05:38:55 +00:00
b7175b3482
* Revert "Revert "Update fastssz" (#7100)"
This reverts commit b954db9704
.
* Preston's patch
* Merge branch 'master' of github.com:prysmaticlabs/prysm into revert-7100-revert-6760-update-fssz
* Update fssz, add regression test case
* more HTR with fssz
* fix some tests
* only one test left
* Make it so that HTR will work
* gofmt, imports
* gofmt, imports
* fix
* Merge branch 'master' of github.com:prysmaticlabs/prysm into revert-7100-revert-6760-update-fssz
* fix
* Merge branch 'master' into revert-7100-revert-6760-update-fssz
* Merge refs/heads/master into revert-7100-revert-6760-update-fssz
* gaz
* Merge branch 'revert-7100-revert-6760-update-fssz' of github.com:prysmaticlabs/prysm into revert-7100-revert-6760-update-fssz
* Merge refs/heads/master into revert-7100-revert-6760-update-fssz
* fix test
* Merge branch 'revert-7100-revert-6760-update-fssz' of github.com:prysmaticlabs/prysm into revert-7100-revert-6760-update-fssz
* Merge refs/heads/master into revert-7100-revert-6760-update-fssz
722 lines
27 KiB
Go
722 lines
27 KiB
Go
package validator
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"reflect"
|
|
"sort"
|
|
"time"
|
|
|
|
fastssz "github.com/ferranbt/fastssz"
|
|
"github.com/pkg/errors"
|
|
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/cache/depositcache"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
|
|
blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/state/interop"
|
|
stateTrie "github.com/prysmaticlabs/prysm/beacon-chain/state"
|
|
dbpb "github.com/prysmaticlabs/prysm/proto/beacon/db"
|
|
attaggregation "github.com/prysmaticlabs/prysm/shared/aggregation/attestations"
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/shared/featureconfig"
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"github.com/prysmaticlabs/prysm/shared/rand"
|
|
"github.com/prysmaticlabs/prysm/shared/trieutil"
|
|
"github.com/sirupsen/logrus"
|
|
"go.opencensus.io/trace"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
// eth1DataNotification is a latch to stop flooding logs with the same warning.
|
|
var eth1DataNotification bool
|
|
|
|
const eth1dataTimeout = 2 * time.Second
|
|
|
|
type eth1DataSingleVote struct {
|
|
eth1Data ethpb.Eth1Data
|
|
blockHeight *big.Int
|
|
}
|
|
|
|
type eth1DataAggregatedVote struct {
|
|
data eth1DataSingleVote
|
|
votes int
|
|
}
|
|
|
|
// profitableAtts implements the Sort interface to sort attestations
|
|
// by highest slot and by highest aggregation bit count.
|
|
type profitableAtts struct {
|
|
atts []*ethpb.Attestation
|
|
}
|
|
|
|
func (p profitableAtts) Len() int { return len(p.atts) }
|
|
func (p profitableAtts) Swap(i, j int) { p.atts[i], p.atts[j] = p.atts[j], p.atts[i] }
|
|
func (p profitableAtts) Less(i, j int) bool {
|
|
if p.atts[i].Data.Slot == p.atts[j].Data.Slot {
|
|
return p.atts[i].AggregationBits.Count() > p.atts[j].AggregationBits.Count()
|
|
}
|
|
return p.atts[i].Data.Slot > p.atts[j].Data.Slot
|
|
}
|
|
|
|
// GetBlock is called by a proposer during its assigned slot to request a block to sign
|
|
// by passing in the slot and the signed randao reveal of the slot.
|
|
func (vs *Server) GetBlock(ctx context.Context, req *ethpb.BlockRequest) (*ethpb.BeaconBlock, error) {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.GetBlock")
|
|
defer span.End()
|
|
span.AddAttributes(trace.Int64Attribute("slot", int64(req.Slot)))
|
|
|
|
if vs.SyncChecker.Syncing() {
|
|
return nil, status.Errorf(codes.Unavailable, "Syncing to latest head, not ready to respond")
|
|
}
|
|
|
|
// Retrieve the parent block as the current head of the canonical chain.
|
|
parentRoot, err := vs.HeadFetcher.HeadRoot(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not retrieve head root: %v", err)
|
|
}
|
|
|
|
head, err := vs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get head state %v", err)
|
|
}
|
|
head, err = state.ProcessSlots(ctx, head, req.Slot)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not advance slot to calculate proposer index: %v", err)
|
|
}
|
|
|
|
var eth1Data *ethpb.Eth1Data
|
|
if featureconfig.Get().EnableEth1DataMajorityVote {
|
|
eth1Data, err = vs.eth1DataMajorityVote(ctx, head)
|
|
} else {
|
|
eth1Data, err = vs.eth1Data(ctx, req.Slot)
|
|
}
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get ETH1 data: %v", err)
|
|
}
|
|
|
|
// Pack ETH1 deposits which have not been included in the beacon chain.
|
|
deposits, err := vs.deposits(ctx, head, eth1Data)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get ETH1 deposits: %v", err)
|
|
}
|
|
|
|
// Pack aggregated attestations which have not been included in the beacon chain.
|
|
atts, err := vs.packAttestations(ctx, head)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not get attestations to pack into block: %v", err)
|
|
}
|
|
|
|
// Use zero hash as stub for state root to compute later.
|
|
stateRoot := params.BeaconConfig().ZeroHash[:]
|
|
|
|
graffiti := bytesutil.ToBytes32(req.Graffiti)
|
|
|
|
// Calculate new proposer index.
|
|
idx, err := helpers.BeaconProposerIndex(head)
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not calculate proposer index %v", err)
|
|
}
|
|
|
|
blk := ðpb.BeaconBlock{
|
|
Slot: req.Slot,
|
|
ParentRoot: parentRoot[:],
|
|
StateRoot: stateRoot,
|
|
ProposerIndex: idx,
|
|
Body: ðpb.BeaconBlockBody{
|
|
Eth1Data: eth1Data,
|
|
Deposits: deposits,
|
|
Attestations: atts,
|
|
RandaoReveal: req.RandaoReveal,
|
|
ProposerSlashings: vs.SlashingsPool.PendingProposerSlashings(ctx, head),
|
|
AttesterSlashings: vs.SlashingsPool.PendingAttesterSlashings(ctx, head),
|
|
VoluntaryExits: vs.ExitPool.PendingExits(head, req.Slot),
|
|
Graffiti: graffiti[:],
|
|
},
|
|
}
|
|
|
|
// Compute state root with the newly constructed block.
|
|
stateRoot, err = vs.computeStateRoot(ctx, ðpb.SignedBeaconBlock{Block: blk, Signature: make([]byte, 96)})
|
|
if err != nil {
|
|
interop.WriteBlockToDisk(ðpb.SignedBeaconBlock{Block: blk}, true /*failed*/)
|
|
return nil, status.Errorf(codes.Internal, "Could not compute state root: %v", err)
|
|
}
|
|
blk.StateRoot = stateRoot
|
|
|
|
return blk, nil
|
|
}
|
|
|
|
// ProposeBlock is called by a proposer during its assigned slot to create a block in an attempt
|
|
// to get it processed by the beacon node as the canonical head.
|
|
func (vs *Server) ProposeBlock(ctx context.Context, blk *ethpb.SignedBeaconBlock) (*ethpb.ProposeResponse, error) {
|
|
root, err := blk.Block.HashTreeRoot()
|
|
if err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not tree hash block: %v", err)
|
|
}
|
|
|
|
// Do not block proposal critical path with debug logging or block feed updates.
|
|
defer func() {
|
|
log.WithField("blockRoot", fmt.Sprintf("%#x", bytesutil.Trunc(root[:]))).Debugf(
|
|
"Block proposal received via RPC")
|
|
vs.BlockNotifier.BlockFeed().Send(&feed.Event{
|
|
Type: blockfeed.ReceivedBlock,
|
|
Data: &blockfeed.ReceivedBlockData{SignedBlock: blk},
|
|
})
|
|
}()
|
|
|
|
// Broadcast the new block to the network.
|
|
if err := vs.P2P.Broadcast(ctx, blk); err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not broadcast block: %v", err)
|
|
}
|
|
log.WithFields(logrus.Fields{
|
|
"blockRoot": hex.EncodeToString(root[:]),
|
|
}).Debug("Broadcasting block")
|
|
|
|
if err := vs.BlockReceiver.ReceiveBlock(ctx, blk, root); err != nil {
|
|
return nil, status.Errorf(codes.Internal, "Could not process beacon block: %v", err)
|
|
}
|
|
|
|
return ðpb.ProposeResponse{
|
|
BlockRoot: root[:],
|
|
}, nil
|
|
}
|
|
|
|
// eth1Data determines the appropriate eth1data for a block proposal. The algorithm for this method
|
|
// is as follows:
|
|
// - Determine the timestamp for the start slot for the eth1 voting period.
|
|
// - Determine the most recent eth1 block before that timestamp.
|
|
// - Subtract that eth1block.number by ETH1_FOLLOW_DISTANCE.
|
|
// - This is the eth1block to use for the block proposal.
|
|
func (vs *Server) eth1Data(ctx context.Context, slot uint64) (*ethpb.Eth1Data, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, eth1dataTimeout)
|
|
defer cancel()
|
|
|
|
if vs.MockEth1Votes {
|
|
return vs.mockETH1DataVote(ctx, slot)
|
|
}
|
|
if !vs.Eth1InfoFetcher.IsConnectedToETH1() {
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1DataNotification = false
|
|
|
|
eth1VotingPeriodStartTime := vs.slotStartTime(slot)
|
|
|
|
// Look up most recent block up to timestamp
|
|
blockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, eth1VotingPeriodStartTime)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get block number from timestamp")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1Data, err := vs.defaultEth1DataResponse(ctx, blockNumber)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get eth1 data from block number")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
|
|
return eth1Data, nil
|
|
}
|
|
|
|
// eth1DataMajorityVote determines the appropriate eth1data for a block proposal using an extended
|
|
// simple voting algorithm - voting with the majority. The algorithm for this method is as follows:
|
|
// - Determine the timestamp for the start slot for the current eth1 voting period.
|
|
// - Determine the timestamp for the start slot for the previous eth1 voting period.
|
|
// - Determine the most recent eth1 block before each timestamp.
|
|
// - Subtract the current period's eth1block.number by ETH1_FOLLOW_DISTANCE to determine the voting upper bound.
|
|
// - Subtract the previous period's eth1block.number by ETH1_FOLLOW_DISTANCE to determine the voting lower bound.
|
|
// - Filter out votes on unknown blocks and blocks which are outside of the range determined by the lower and upper bounds.
|
|
// - If no blocks are left after filtering, use the current period's most recent eth1 block for proposal.
|
|
// - Determine the vote with the highest count. Prefer the vote with the highest eth1 block height in the event of a tie.
|
|
// - This vote's block is the eth1block to use for the block proposal.
|
|
func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTrie.BeaconState) (*ethpb.Eth1Data, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, eth1dataTimeout)
|
|
defer cancel()
|
|
|
|
slot := beaconState.Slot()
|
|
|
|
if vs.MockEth1Votes {
|
|
return vs.mockETH1DataVote(ctx, slot)
|
|
}
|
|
if !vs.Eth1InfoFetcher.IsConnectedToETH1() {
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1DataNotification = false
|
|
|
|
slotsPerVotingPeriod := params.BeaconConfig().EpochsPerEth1VotingPeriod * params.BeaconConfig().SlotsPerEpoch
|
|
currentPeriodVotingStartTime := vs.slotStartTime(slot)
|
|
// Can't use slotStartTime function because slot would be negative in the initial voting period.
|
|
previousPeriodVotingStartTime := currentPeriodVotingStartTime -
|
|
slotsPerVotingPeriod*params.BeaconConfig().SecondsPerSlot
|
|
|
|
currentPeriodBlockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, currentPeriodVotingStartTime)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get block number for current voting period")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
previousPeriodBlockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, previousPeriodVotingStartTime)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get block number for previous voting period")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1FollowDistance := int64(params.BeaconConfig().Eth1FollowDistance)
|
|
currentPeriodInitialBlock := big.NewInt(0).Sub(currentPeriodBlockNumber, big.NewInt(eth1FollowDistance))
|
|
previousPeriodInitialBlock := big.NewInt(0).Sub(previousPeriodBlockNumber, big.NewInt(eth1FollowDistance))
|
|
|
|
currentDepositCount, _ := vs.DepositFetcher.DepositsNumberAndRootAtHeight(ctx, currentPeriodInitialBlock)
|
|
if currentDepositCount == 0 {
|
|
return vs.ChainStartFetcher.ChainStartEth1Data(), nil
|
|
}
|
|
|
|
if len(beaconState.Eth1DataVotes()) == 0 {
|
|
eth1Data, err := vs.defaultEth1DataResponse(ctx, currentPeriodBlockNumber)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get eth1 data from current period block number")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
return eth1Data, nil
|
|
}
|
|
|
|
inRangeVotes, err := vs.inRangeVotes(ctx, beaconState, currentPeriodInitialBlock, previousPeriodInitialBlock)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(inRangeVotes) == 0 {
|
|
eth1Data, err := vs.defaultEth1DataResponse(ctx, currentPeriodBlockNumber)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get eth1 data from current period block number")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
return eth1Data, nil
|
|
}
|
|
|
|
chosenVote := chosenEth1DataMajorityVote(inRangeVotes)
|
|
|
|
return &chosenVote.data.eth1Data, nil
|
|
}
|
|
|
|
func (vs *Server) slotStartTime(slot uint64) uint64 {
|
|
startTime, _ := vs.Eth1InfoFetcher.Eth2GenesisPowchainInfo()
|
|
startTime +=
|
|
(slot - (slot % (params.BeaconConfig().EpochsPerEth1VotingPeriod * params.BeaconConfig().SlotsPerEpoch))) *
|
|
params.BeaconConfig().SecondsPerSlot
|
|
return startTime
|
|
}
|
|
|
|
func (vs *Server) inRangeVotes(ctx context.Context,
|
|
beaconState *stateTrie.BeaconState,
|
|
currentPeriodInitialBlock *big.Int,
|
|
previousPeriodInitialBlock *big.Int) ([]eth1DataSingleVote, error) {
|
|
|
|
currentETH1Data := vs.HeadFetcher.HeadETH1Data()
|
|
|
|
var inRangeVotes []eth1DataSingleVote
|
|
for _, eth1Data := range beaconState.Eth1DataVotes() {
|
|
ok, height, err := vs.BlockFetcher.BlockExists(ctx, bytesutil.ToBytes32(eth1Data.BlockHash))
|
|
if err != nil {
|
|
log.WithError(err).Warning("Could not fetch eth1data height for received eth1data vote")
|
|
}
|
|
// Make sure we don't "undo deposit progress". See https://github.com/ethereum/eth2.0-specs/pull/1836
|
|
if eth1Data.DepositCount < currentETH1Data.DepositCount {
|
|
continue
|
|
}
|
|
// previousPeriodInitialBlock.Cmp(height) < 1 filters out all blocks before previousPeriodInitialBlock
|
|
// currentPeriodInitialBlock.Cmp(height) > -1 filters out all blocks after currentPeriodInitialBlock
|
|
// These filters result in the range [previousPeriodInitialBlock, currentPeriodInitialBlock]
|
|
if ok && previousPeriodInitialBlock.Cmp(height) < 1 && currentPeriodInitialBlock.Cmp(height) > -1 {
|
|
inRangeVotes = append(inRangeVotes, eth1DataSingleVote{eth1Data: *eth1Data, blockHeight: height})
|
|
}
|
|
}
|
|
|
|
return inRangeVotes, nil
|
|
}
|
|
|
|
func chosenEth1DataMajorityVote(votes []eth1DataSingleVote) eth1DataAggregatedVote {
|
|
var voteCount []eth1DataAggregatedVote
|
|
for _, singleVote := range votes {
|
|
newVote := true
|
|
for i, aggregatedVote := range voteCount {
|
|
aggregatedData := aggregatedVote.data
|
|
if reflect.DeepEqual(singleVote.eth1Data, aggregatedData.eth1Data) {
|
|
voteCount[i].votes++
|
|
newVote = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if newVote {
|
|
voteCount = append(voteCount, eth1DataAggregatedVote{data: singleVote, votes: 1})
|
|
}
|
|
}
|
|
|
|
currentVote := voteCount[0]
|
|
for _, aggregatedVote := range voteCount[1:] {
|
|
// Choose new eth1data if it has more votes or the same number of votes with a bigger block height.
|
|
if aggregatedVote.votes > currentVote.votes ||
|
|
(aggregatedVote.votes == currentVote.votes &&
|
|
aggregatedVote.data.blockHeight.Cmp(currentVote.data.blockHeight) == 1) {
|
|
currentVote = aggregatedVote
|
|
}
|
|
}
|
|
|
|
return currentVote
|
|
}
|
|
|
|
func (vs *Server) mockETH1DataVote(ctx context.Context, slot uint64) (*ethpb.Eth1Data, error) {
|
|
if !eth1DataNotification {
|
|
log.Warn("Beacon Node is no longer connected to an ETH1 chain, so ETH1 data votes are now mocked.")
|
|
eth1DataNotification = true
|
|
}
|
|
// If a mock eth1 data votes is specified, we use the following for the
|
|
// eth1data we provide to every proposer based on https://github.com/ethereum/eth2.0-pm/issues/62:
|
|
//
|
|
// slot_in_voting_period = current_slot % SLOTS_PER_ETH1_VOTING_PERIOD
|
|
// Eth1Data(
|
|
// DepositRoot = hash(current_epoch + slot_in_voting_period),
|
|
// DepositCount = state.eth1_deposit_index,
|
|
// BlockHash = hash(hash(current_epoch + slot_in_voting_period)),
|
|
// )
|
|
slotInVotingPeriod := slot % (params.BeaconConfig().EpochsPerEth1VotingPeriod * params.BeaconConfig().SlotsPerEpoch)
|
|
headState, err := vs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var enc []byte
|
|
enc = fastssz.MarshalUint64(enc, helpers.SlotToEpoch(slot)+slotInVotingPeriod)
|
|
depRoot := hashutil.Hash(enc)
|
|
blockHash := hashutil.Hash(depRoot[:])
|
|
return ðpb.Eth1Data{
|
|
DepositRoot: depRoot[:],
|
|
DepositCount: headState.Eth1DepositIndex(),
|
|
BlockHash: blockHash[:],
|
|
}, nil
|
|
}
|
|
|
|
func (vs *Server) randomETH1DataVote(ctx context.Context) (*ethpb.Eth1Data, error) {
|
|
if !eth1DataNotification {
|
|
log.Warn("Beacon Node is no longer connected to an ETH1 chain, so ETH1 data votes are now random.")
|
|
eth1DataNotification = true
|
|
}
|
|
headState, err := vs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// set random roots and block hashes to prevent a majority from being
|
|
// built if the eth1 node is offline
|
|
randGen := rand.NewGenerator()
|
|
depRoot := hashutil.Hash(bytesutil.Bytes32(randGen.Uint64()))
|
|
blockHash := hashutil.Hash(bytesutil.Bytes32(randGen.Uint64()))
|
|
return ðpb.Eth1Data{
|
|
DepositRoot: depRoot[:],
|
|
DepositCount: headState.Eth1DepositIndex(),
|
|
BlockHash: blockHash[:],
|
|
}, nil
|
|
}
|
|
|
|
// computeStateRoot computes the state root after a block has been processed through a state transition and
|
|
// returns it to the validator client.
|
|
func (vs *Server) computeStateRoot(ctx context.Context, block *ethpb.SignedBeaconBlock) ([]byte, error) {
|
|
beaconState, err := vs.StateGen.StateByRoot(ctx, bytesutil.ToBytes32(block.Block.ParentRoot))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not retrieve beacon state")
|
|
}
|
|
root, err := state.CalculateStateRoot(
|
|
ctx,
|
|
beaconState,
|
|
block,
|
|
)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not calculate state root at slot %d", beaconState.Slot())
|
|
}
|
|
|
|
log.WithField("beaconStateRoot", fmt.Sprintf("%#x", root)).Debugf("Computed state root")
|
|
return root[:], nil
|
|
}
|
|
|
|
// deposits returns a list of pending deposits that are ready for inclusion in the next beacon
|
|
// block. Determining deposits depends on the current eth1data vote for the block and whether or not
|
|
// this eth1data has enough support to be considered for deposits inclusion. If current vote has
|
|
// enough support, then use that vote for basis of determining deposits, otherwise use current state
|
|
// eth1data.
|
|
func (vs *Server) deposits(
|
|
ctx context.Context,
|
|
beaconState *stateTrie.BeaconState,
|
|
currentVote *ethpb.Eth1Data,
|
|
) ([]*ethpb.Deposit, error) {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.deposits")
|
|
defer span.End()
|
|
|
|
if vs.MockEth1Votes || !vs.Eth1InfoFetcher.IsConnectedToETH1() {
|
|
return []*ethpb.Deposit{}, nil
|
|
}
|
|
// Need to fetch if the deposits up to the state's latest eth 1 data matches
|
|
// the number of all deposits in this RPC call. If not, then we return nil.
|
|
canonicalEth1Data, canonicalEth1DataHeight, err := vs.canonicalEth1Data(ctx, beaconState, currentVote)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, genesisEth1Block := vs.Eth1InfoFetcher.Eth2GenesisPowchainInfo()
|
|
if genesisEth1Block.Cmp(canonicalEth1DataHeight) == 0 {
|
|
return []*ethpb.Deposit{}, nil
|
|
}
|
|
|
|
// If there are no pending deposits, exit early.
|
|
allPendingContainers := vs.PendingDepositsFetcher.PendingContainers(ctx, canonicalEth1DataHeight)
|
|
if len(allPendingContainers) == 0 {
|
|
return []*ethpb.Deposit{}, nil
|
|
}
|
|
|
|
depositTrie, err := vs.depositTrie(ctx, canonicalEth1DataHeight)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not retrieve deposit trie")
|
|
}
|
|
|
|
// Deposits need to be received in order of merkle index root, so this has to make sure
|
|
// deposits are sorted from lowest to highest.
|
|
var pendingDeps []*dbpb.DepositContainer
|
|
for _, dep := range allPendingContainers {
|
|
if uint64(dep.Index) >= beaconState.Eth1DepositIndex() && uint64(dep.Index) < canonicalEth1Data.DepositCount {
|
|
pendingDeps = append(pendingDeps, dep)
|
|
}
|
|
}
|
|
|
|
for i := range pendingDeps {
|
|
// Don't construct merkle proof if the number of deposits is more than max allowed in block.
|
|
if uint64(i) == params.BeaconConfig().MaxDeposits {
|
|
break
|
|
}
|
|
pendingDeps[i].Deposit, err = constructMerkleProof(depositTrie, int(pendingDeps[i].Index), pendingDeps[i].Deposit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Limit the return of pending deposits to not be more than max deposits allowed in block.
|
|
var pendingDeposits []*ethpb.Deposit
|
|
for i := uint64(0); i < uint64(len(pendingDeps)) && i < params.BeaconConfig().MaxDeposits; i++ {
|
|
pendingDeposits = append(pendingDeposits, pendingDeps[i].Deposit)
|
|
}
|
|
return pendingDeposits, nil
|
|
}
|
|
|
|
// canonicalEth1Data determines the canonical eth1data and eth1 block height to use for determining deposits.
|
|
func (vs *Server) canonicalEth1Data(
|
|
ctx context.Context,
|
|
beaconState *stateTrie.BeaconState,
|
|
currentVote *ethpb.Eth1Data) (*ethpb.Eth1Data, *big.Int, error) {
|
|
|
|
var eth1BlockHash [32]byte
|
|
|
|
// Add in current vote, to get accurate vote tally
|
|
if err := beaconState.AppendEth1DataVotes(currentVote); err != nil {
|
|
return nil, nil, errors.Wrap(err, "failed to append eth1 data votes to state")
|
|
}
|
|
hasSupport, err := blocks.Eth1DataHasEnoughSupport(beaconState, currentVote)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "could not determine if current eth1data vote has enough support")
|
|
}
|
|
var canonicalEth1Data *ethpb.Eth1Data
|
|
if hasSupport {
|
|
canonicalEth1Data = currentVote
|
|
eth1BlockHash = bytesutil.ToBytes32(currentVote.BlockHash)
|
|
} else {
|
|
canonicalEth1Data = beaconState.Eth1Data()
|
|
eth1BlockHash = bytesutil.ToBytes32(beaconState.Eth1Data().BlockHash)
|
|
}
|
|
_, canonicalEth1DataHeight, err := vs.Eth1BlockFetcher.BlockExists(ctx, eth1BlockHash)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "could not fetch eth1data height")
|
|
}
|
|
return canonicalEth1Data, canonicalEth1DataHeight, nil
|
|
}
|
|
|
|
func (vs *Server) depositTrie(ctx context.Context, canonicalEth1DataHeight *big.Int) (*trieutil.SparseMerkleTrie, error) {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.depositTrie")
|
|
defer span.End()
|
|
|
|
var depositTrie *trieutil.SparseMerkleTrie
|
|
|
|
var finalizedDeposits *depositcache.FinalizedDeposits
|
|
if featureconfig.Get().EnableFinalizedDepositsCache {
|
|
finalizedDeposits = vs.DepositFetcher.FinalizedDeposits(ctx)
|
|
depositTrie = finalizedDeposits.Deposits
|
|
upToEth1DataDeposits := vs.DepositFetcher.NonFinalizedDeposits(ctx, canonicalEth1DataHeight)
|
|
insertIndex := finalizedDeposits.MerkleTrieIndex + 1
|
|
|
|
for _, dep := range upToEth1DataDeposits {
|
|
depHash, err := dep.Data.HashTreeRoot()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not hash deposit data")
|
|
}
|
|
depositTrie.Insert(depHash[:], int(insertIndex))
|
|
insertIndex++
|
|
}
|
|
|
|
return depositTrie, nil
|
|
}
|
|
|
|
upToEth1DataDeposits := vs.DepositFetcher.AllDeposits(ctx, canonicalEth1DataHeight)
|
|
depositData := [][]byte{}
|
|
for _, dep := range upToEth1DataDeposits {
|
|
depHash, err := dep.Data.HashTreeRoot()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not hash deposit data")
|
|
}
|
|
depositData = append(depositData, depHash[:])
|
|
}
|
|
|
|
var err error
|
|
depositTrie, err = trieutil.GenerateTrieFromItems(depositData, int(params.BeaconConfig().DepositContractTreeDepth))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not generate historical deposit trie from deposits")
|
|
}
|
|
|
|
return depositTrie, 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
|
|
// latest block.
|
|
func (vs *Server) defaultEth1DataResponse(ctx context.Context, currentHeight *big.Int) (*ethpb.Eth1Data, error) {
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
eth1FollowDistance := int64(params.BeaconConfig().Eth1FollowDistance)
|
|
ancestorHeight := big.NewInt(0).Sub(currentHeight, big.NewInt(eth1FollowDistance))
|
|
blockHash, err := vs.Eth1BlockFetcher.BlockHashByHeight(ctx, ancestorHeight)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not fetch ETH1_FOLLOW_DISTANCE ancestor")
|
|
}
|
|
// Fetch all historical deposits up to an ancestor height.
|
|
depositsTillHeight, depositRoot := vs.DepositFetcher.DepositsNumberAndRootAtHeight(ctx, ancestorHeight)
|
|
if depositsTillHeight == 0 {
|
|
return vs.ChainStartFetcher.ChainStartEth1Data(), nil
|
|
}
|
|
// // Make sure we don't "undo deposit progress". See https://github.com/ethereum/eth2.0-specs/pull/1836
|
|
currentETH1Data := vs.HeadFetcher.HeadETH1Data()
|
|
if depositsTillHeight < currentETH1Data.DepositCount {
|
|
return currentETH1Data, nil
|
|
}
|
|
return ðpb.Eth1Data{
|
|
DepositRoot: depositRoot[:],
|
|
BlockHash: blockHash[:],
|
|
DepositCount: depositsTillHeight,
|
|
}, nil
|
|
}
|
|
|
|
// This filters the input attestations to return a list of valid attestations to be packaged inside a beacon block.
|
|
func (vs *Server) filterAttestationsForBlockInclusion(ctx context.Context, state *stateTrie.BeaconState, atts []*ethpb.Attestation) ([]*ethpb.Attestation, error) {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.filterAttestationsForBlockInclusion")
|
|
defer span.End()
|
|
|
|
validAtts := make([]*ethpb.Attestation, 0, len(atts))
|
|
inValidAtts := make([]*ethpb.Attestation, 0, len(atts))
|
|
|
|
for i, att := range atts {
|
|
if uint64(i) == params.BeaconConfig().MaxAttestations {
|
|
break
|
|
}
|
|
|
|
if _, err := blocks.ProcessAttestation(ctx, state, att); err != nil {
|
|
inValidAtts = append(inValidAtts, att)
|
|
continue
|
|
|
|
}
|
|
validAtts = append(validAtts, att)
|
|
}
|
|
|
|
if err := vs.deleteAttsInPool(ctx, inValidAtts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return validAtts, nil
|
|
}
|
|
|
|
// The input attestations are processed and seen by the node, this deletes them from pool
|
|
// so proposers don't include them in a block for the future.
|
|
func (vs *Server) deleteAttsInPool(ctx context.Context, atts []*ethpb.Attestation) error {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.deleteAttsInPool")
|
|
defer span.End()
|
|
|
|
for _, att := range atts {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
if helpers.IsAggregated(att) {
|
|
if err := vs.AttPool.DeleteAggregatedAttestation(att); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := vs.AttPool.DeleteUnaggregatedAttestation(att); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func constructMerkleProof(trie *trieutil.SparseMerkleTrie, index int, deposit *ethpb.Deposit) (*ethpb.Deposit, error) {
|
|
proof, err := trie.MerkleProof(index)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "could not generate merkle proof for deposit at index %d", index)
|
|
}
|
|
// For every deposit, we construct a Merkle proof using the powchain service's
|
|
// in-memory deposits trie, which is updated only once the state's LatestETH1Data
|
|
// property changes during a state transition after a voting period.
|
|
deposit.Proof = proof
|
|
return deposit, nil
|
|
}
|
|
|
|
func (vs *Server) packAttestations(ctx context.Context, latestState *stateTrie.BeaconState) ([]*ethpb.Attestation, error) {
|
|
ctx, span := trace.StartSpan(ctx, "ProposerServer.packAttestations")
|
|
defer span.End()
|
|
|
|
atts := vs.AttPool.AggregatedAttestations()
|
|
atts, err := vs.filterAttestationsForBlockInclusion(ctx, latestState, atts)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not filter attestations")
|
|
}
|
|
|
|
// If there is any room left in the block, consider unaggregated attestations as well.
|
|
numAtts := uint64(len(atts))
|
|
if numAtts < params.BeaconConfig().MaxAttestations {
|
|
uAtts, err := vs.AttPool.UnaggregatedAttestations()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not get unaggregated attestations")
|
|
}
|
|
uAtts, err = vs.filterAttestationsForBlockInclusion(ctx, latestState, uAtts)
|
|
atts = append(atts, uAtts...)
|
|
|
|
attsByDataRoot := make(map[[32]byte][]*ethpb.Attestation, len(atts))
|
|
for _, att := range atts {
|
|
attDataRoot, err := att.Data.HashTreeRoot()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attsByDataRoot[attDataRoot] = append(attsByDataRoot[attDataRoot], att)
|
|
}
|
|
|
|
attsForInclusion := make([]*ethpb.Attestation, 0)
|
|
for _, as := range attsByDataRoot {
|
|
as, err := attaggregation.Aggregate(as)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attsForInclusion = append(attsForInclusion, as...)
|
|
}
|
|
|
|
if uint64(len(attsForInclusion)) > params.BeaconConfig().MaxAttestations {
|
|
sort.Sort(profitableAtts{atts: attsForInclusion})
|
|
attsForInclusion = attsForInclusion[:params.BeaconConfig().MaxAttestations]
|
|
}
|
|
|
|
atts = attsForInclusion
|
|
}
|
|
return atts, nil
|
|
}
|