mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-25 04:47:18 +00:00
a80b1c252a
* Refactor rpc proposer into smaller files * Update BUILD.bazel * Fix test * Update BUILD.bazel
176 lines
6.9 KiB
Go
176 lines
6.9 KiB
Go
package validator
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
|
|
fastssz "github.com/ferranbt/fastssz"
|
|
"github.com/pkg/errors"
|
|
types "github.com/prysmaticlabs/eth2-types"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/blocks"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/state"
|
|
"github.com/prysmaticlabs/prysm/config/features"
|
|
"github.com/prysmaticlabs/prysm/config/params"
|
|
"github.com/prysmaticlabs/prysm/crypto/hash"
|
|
"github.com/prysmaticlabs/prysm/crypto/rand"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/time/slots"
|
|
)
|
|
|
|
// eth1DataMajorityVote determines the appropriate eth1data for a block proposal using
|
|
// an algorithm called Voting with the Majority. The algorithm works as follows:
|
|
// - Determine the timestamp for the start slot for the eth1 voting period.
|
|
// - Determine the earliest and latest timestamps that a valid block can have.
|
|
// - Determine the first block not before the earliest timestamp. This block is the lower bound.
|
|
// - Determine the last block not after the latest timestamp. This block is the upper bound.
|
|
// - If the last block is too early, use current eth1data from the beacon state.
|
|
// - 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 votes, use eth1data from the latest valid block.
|
|
// - Otherwise:
|
|
// - 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 eth1 block to use for the block proposal.
|
|
func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState state.BeaconState) (*ethpb.Eth1Data, error) {
|
|
ctx, cancel := context.WithTimeout(ctx, eth1dataTimeout)
|
|
defer cancel()
|
|
|
|
slot := beaconState.Slot()
|
|
votingPeriodStartTime := vs.slotStartTime(slot)
|
|
|
|
if vs.MockEth1Votes {
|
|
return vs.mockETH1DataVote(ctx, slot)
|
|
}
|
|
if !vs.Eth1InfoFetcher.IsConnectedToETH1() {
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1DataNotification = false
|
|
|
|
eth1FollowDistance := params.BeaconConfig().Eth1FollowDistance
|
|
earliestValidTime := votingPeriodStartTime - 2*params.BeaconConfig().SecondsPerETH1Block*eth1FollowDistance
|
|
latestValidTime := votingPeriodStartTime - params.BeaconConfig().SecondsPerETH1Block*eth1FollowDistance
|
|
|
|
if !features.Get().EnableGetBlockOptimizations {
|
|
_, err := vs.Eth1BlockFetcher.BlockByTimestamp(ctx, earliestValidTime)
|
|
if err != nil {
|
|
log.WithError(err).Error("Could not get last block by earliest valid time")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
}
|
|
|
|
lastBlockByLatestValidTime, err := vs.Eth1BlockFetcher.BlockByTimestamp(ctx, latestValidTime)
|
|
if err != nil {
|
|
log.WithError(err).Error("Could not get last block by latest valid time")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
if lastBlockByLatestValidTime.Time < earliestValidTime {
|
|
return vs.HeadFetcher.HeadETH1Data(), nil
|
|
}
|
|
|
|
lastBlockDepositCount, lastBlockDepositRoot := vs.DepositFetcher.DepositsNumberAndRootAtHeight(ctx, lastBlockByLatestValidTime.Number)
|
|
if lastBlockDepositCount == 0 {
|
|
return vs.ChainStartFetcher.ChainStartEth1Data(), nil
|
|
}
|
|
|
|
if lastBlockDepositCount >= vs.HeadFetcher.HeadETH1Data().DepositCount {
|
|
hash, err := vs.Eth1BlockFetcher.BlockHashByHeight(ctx, lastBlockByLatestValidTime.Number)
|
|
if err != nil {
|
|
log.WithError(err).Error("Could not get hash of last block by latest valid time")
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
return ðpb.Eth1Data{
|
|
BlockHash: hash.Bytes(),
|
|
DepositCount: lastBlockDepositCount,
|
|
DepositRoot: lastBlockDepositRoot[:],
|
|
}, nil
|
|
}
|
|
return vs.HeadFetcher.HeadETH1Data(), nil
|
|
}
|
|
|
|
func (vs *Server) slotStartTime(slot types.Slot) uint64 {
|
|
startTime, _ := vs.Eth1InfoFetcher.Eth2GenesisPowchainInfo()
|
|
return slots.VotingPeriodStartTime(startTime, slot)
|
|
}
|
|
|
|
// canonicalEth1Data determines the canonical eth1data and eth1 block height to use for determining deposits.
|
|
func (vs *Server) canonicalEth1Data(
|
|
ctx context.Context,
|
|
beaconState state.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, "could not 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) mockETH1DataVote(ctx context.Context, slot types.Slot) (*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.ModSlot(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod)))
|
|
headState, err := vs.HeadFetcher.HeadState(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var enc []byte
|
|
enc = fastssz.MarshalUint64(enc, uint64(slots.ToEpoch(slot))+uint64(slotInVotingPeriod))
|
|
depRoot := hash.Hash(enc)
|
|
blockHash := hash.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 := hash.Hash(bytesutil.Bytes32(randGen.Uint64()))
|
|
blockHash := hash.Hash(bytesutil.Bytes32(randGen.Uint64()))
|
|
return ðpb.Eth1Data{
|
|
DepositRoot: depRoot[:],
|
|
DepositCount: headState.Eth1DepositIndex(),
|
|
BlockHash: blockHash[:],
|
|
}, nil
|
|
}
|