mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-25 21:07:18 +00:00
2e49fdb3d2
* WIP trying to start from bellatrix state * env var to control log path with unique paths due to flaky test re-run behavior, logs from a failed test run are overwritten by subsequent retries. This makes it difficult to retrieve logs after the first failed run. It also takes some squinting through output to find the location of the log file in the first place. This flag enables logs to be placed in an arbitrary path. Note that bazel sandboxing generally will force this path to be in the /tmp tree. * WIP - grabbing changes from rm-pre-genesis branch * combine bellatrix state w/ rm-pre-genesis branch * WIP * use encoding/detect for genesis state bytes * WIP more fixes towards start from bellatrix * remove debug wrapping * WIP * multiple bugfixes * fix fork ordering bug and bellatrix genesis blocks * send deposits, spam tx to advance, fix miner alloc * WIP * WIP mess * WIP * Print process ID information for purposes of attaching a debugger * bugs: genesis body_root and deposit index mismatch * fix voting period start, skip altair check * add changes * make it better * rm startup FCU, rm logs * cleanup import grouping&ordering * restore FCU log, get rid of tmp var * rm newline * restore newline * restore wrapped error * rm newline * removing boot node version override this doesn't seem to matter? * add issue number to todo comment * rm commented code * rm vmdebug geth flag * unexport values only used with genesis test pkg and add comments where missing from exported values. * adding comments to special cases for testnets * migrate comments from PR to actual code :) * rm unused test param * mark e2e spawns exempt from gosec warning * Fix DeepSource errors in `proposer_bellatrix.go` (#11739) * Fix DeepSource errors in * Omit receiver name * Address PR comments * Remove unused variable * Fix more DeepSource errors Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Remove `Test_IsExecutionEnabledCapella` (#11752) Co-authored-by: Radosław Kapka <rkapka@wp.pl> * Add REST implementation for Validator's `ProposeBeaconBlock` (#11731) * WIP * WIP * WIP * Add tests * WIP * Add more tests * Address DeepSource errors * Remove unused param * Add more tests * Address PR comments * Address PR comments * Fix formatting * Remove unused parameter * Fix TestLittleEndianBytesToBigInt Co-authored-by: Radosław Kapka <rkapka@wp.pl> * fix validator client (#11755) * fix validator client (cherry picked from commit deb138959a2ffcb89cd2e3eb8304477526f4a168) * Use signed changes in middleware block Co-authored-by: Potuz <potuz@prysmaticlabs.com> * API `finalized` metadata field - update protos (#11749) * API `finalized` metadata field - update protos * change nums Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> * log breaks unit tests that don't do full arg setup easiest to just remove it for now * restore prior behavior of phase0 block for altair * update unit tests to account for special case * loosen condition for fork version to match config we don't know which fork version genesis will start from, so we shouldn't force it to be a phase0 genesis. * skip until we can mod configs at runtime * NewGenesisBlockForState computes state root itself * rm noisy log * this log would be noisy in mainnet * fix format specifier, []byte -> string * core.Genesis UnmarshalJson has a value receiver :) * no longer needs to be exported Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com> Co-authored-by: prestonvanloon <preston@prysmaticlabs.com> Co-authored-by: nisdas <nishdas93@gmail.com> Co-authored-by: Patrice Vignola <vignola.patrice@gmail.com> Co-authored-by: Radosław Kapka <rkapka@wp.pl> Co-authored-by: terencechain <terence@prysmaticlabs.com> Co-authored-by: Potuz <potuz@prysmaticlabs.com> Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
180 lines
7.5 KiB
Go
180 lines
7.5 KiB
Go
package validator
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
|
|
"github.com/pkg/errors"
|
|
fastssz "github.com/prysmaticlabs/fastssz"
|
|
"github.com/prysmaticlabs/prysm/v3/beacon-chain/core/blocks"
|
|
"github.com/prysmaticlabs/prysm/v3/beacon-chain/state"
|
|
"github.com/prysmaticlabs/prysm/v3/config/features"
|
|
"github.com/prysmaticlabs/prysm/v3/config/params"
|
|
types "github.com/prysmaticlabs/prysm/v3/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/v3/crypto/hash"
|
|
"github.com/prysmaticlabs/prysm/v3/crypto/rand"
|
|
"github.com/prysmaticlabs/prysm/v3/encoding/bytesutil"
|
|
ethpb "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1"
|
|
"github.com/prysmaticlabs/prysm/v3/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.ExecutionClientConnected() {
|
|
return vs.randomETH1DataVote(ctx)
|
|
}
|
|
eth1DataNotification = false
|
|
|
|
genesisTime, _ := vs.Eth1InfoFetcher.GenesisExecutionChainInfo()
|
|
followDistanceSeconds := params.BeaconConfig().Eth1FollowDistance * params.BeaconConfig().SecondsPerETH1Block
|
|
latestValidTime := votingPeriodStartTime - followDistanceSeconds
|
|
earliestValidTime := votingPeriodStartTime - 2*followDistanceSeconds
|
|
|
|
// Special case for starting from a pre-mined genesis: the eth1 vote should be genesis until the chain has advanced
|
|
// by ETH1_FOLLOW_DISTANCE. The head state should maintain the same ETH1Data until this condition has passed, so
|
|
// trust the existing head for the right eth1 vote until we can get a meaningful value from the deposit contract.
|
|
if latestValidTime < genesisTime+followDistanceSeconds {
|
|
log.WithField("genesisTime", genesisTime).WithField("latestValidTime", latestValidTime).Warn("voting period before genesis + follow distance, using eth1data from head")
|
|
return vs.HeadFetcher.HeadETH1Data(), nil
|
|
}
|
|
|
|
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 {
|
|
h, 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: h.Bytes(),
|
|
DepositCount: lastBlockDepositCount,
|
|
DepositRoot: lastBlockDepositRoot[:],
|
|
}, nil
|
|
}
|
|
return vs.HeadFetcher.HeadETH1Data(), nil
|
|
}
|
|
|
|
func (vs *Server) slotStartTime(slot types.Slot) uint64 {
|
|
startTime, _ := vs.Eth1InfoFetcher.GenesisExecutionChainInfo()
|
|
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)
|
|
}
|
|
if features.Get().DisableStakinContractCheck && eth1BlockHash == [32]byte{} {
|
|
return canonicalEth1Data, new(big.Int).SetInt64(0), nil
|
|
}
|
|
_, 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
|
|
}
|