mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-29 06:37:17 +00:00
c925632cc9
* Add a span to block-by-slot * /goroutinez
734 lines
30 KiB
Go
734 lines
30 KiB
Go
// Package blocks contains block processing libraries. These libraries
|
|
// process and verify block specific messages such as PoW receipt root,
|
|
// RANDAO, validator deposits, exits and slashing proofs.
|
|
package blocks
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/state/stateutils"
|
|
v "github.com/prysmaticlabs/prysm/beacon-chain/core/validators"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/db"
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/bls"
|
|
"github.com/prysmaticlabs/prysm/shared/forkutil"
|
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
|
"github.com/prysmaticlabs/prysm/shared/trieutil"
|
|
"github.com/sirupsen/logrus"
|
|
"go.opencensus.io/trace"
|
|
)
|
|
|
|
// VerifyProposerSignature uses BLS signature verification to ensure
|
|
// the correct proposer created an incoming beacon block during state
|
|
// transition processing.
|
|
//
|
|
// WIP - this is stubbed out until BLS is integrated into Prysm.
|
|
func VerifyProposerSignature(
|
|
ctx context.Context,
|
|
_ *pb.BeaconBlock,
|
|
) error {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.VerifyProposerSignature")
|
|
defer span.End()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcessEth1DataInBlock is an operation performed on each
|
|
// beacon block to ensure the ETH1 data votes are processed
|
|
// into the beacon state.
|
|
//
|
|
// Official spec definition of ProcessEth1Data
|
|
// If block.eth1_data equals eth1_data_vote.eth1_data for some eth1_data_vote
|
|
// in state.eth1_data_votes, set eth1_data_vote.vote_count += 1.
|
|
// Otherwise, append to state.eth1_data_votes a new Eth1DataVote(eth1_data=block.eth1_data, vote_count=1).
|
|
func ProcessEth1DataInBlock(ctx context.Context, beaconState *pb.BeaconState, block *pb.BeaconBlock) *pb.BeaconState {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessEth1DataInBlock")
|
|
defer span.End()
|
|
|
|
var eth1DataVoteAdded bool
|
|
|
|
for idx := range beaconState.Eth1DataVotes {
|
|
if proto.Equal(beaconState.Eth1DataVotes[idx].Eth1Data, block.Eth1Data) {
|
|
beaconState.Eth1DataVotes[idx].VoteCount++
|
|
eth1DataVoteAdded = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !eth1DataVoteAdded {
|
|
beaconState.Eth1DataVotes = append(
|
|
beaconState.Eth1DataVotes,
|
|
&pb.Eth1DataVote{
|
|
Eth1Data: block.Eth1Data,
|
|
VoteCount: 1,
|
|
},
|
|
)
|
|
}
|
|
|
|
return beaconState
|
|
}
|
|
|
|
// ProcessBlockRandao checks the block proposer's
|
|
// randao commitment and generates a new randao mix to update
|
|
// in the beacon state's latest randao mixes slice.
|
|
//
|
|
// Official spec definition for block randao verification:
|
|
// Let proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)].
|
|
// Verify that bls_verify(pubkey=proposer.pubkey, message_hash=int_to_bytes32(get_current_epoch(state)),
|
|
// signature=block.randao_reveal, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO)).
|
|
// Set state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] =
|
|
// xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))
|
|
func ProcessBlockRandao(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
verifySignatures bool,
|
|
enableLogging bool,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessBlockRandao")
|
|
defer span.End()
|
|
|
|
proposerIdx, err := helpers.BeaconProposerIndex(beaconState, beaconState.Slot)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not get beacon proposer index: %v", err)
|
|
}
|
|
if verifySignatures {
|
|
if err := verifyBlockRandao(beaconState, block, proposerIdx, enableLogging); err != nil {
|
|
return nil, fmt.Errorf("could not verify block randao: %v", err)
|
|
}
|
|
}
|
|
// If block randao passed verification, we XOR the state's latest randao mix with the block's
|
|
// randao and update the state's corresponding latest randao mix value.
|
|
latestMixesLength := params.BeaconConfig().LatestRandaoMixesLength
|
|
currentEpoch := helpers.CurrentEpoch(beaconState)
|
|
latestMixSlice := beaconState.LatestRandaoMixes[currentEpoch%latestMixesLength]
|
|
blockRandaoReveal := hashutil.Hash(block.RandaoReveal)
|
|
for i, x := range blockRandaoReveal {
|
|
latestMixSlice[i] ^= x
|
|
}
|
|
beaconState.LatestRandaoMixes[currentEpoch%latestMixesLength] = latestMixSlice
|
|
return beaconState, nil
|
|
}
|
|
|
|
// Verify that bls_verify(pubkey=proposer.pubkey, message_hash=hash_tree_root(get_current_epoch(state)),
|
|
// signature=block.randao_reveal, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))
|
|
func verifyBlockRandao(beaconState *pb.BeaconState, block *pb.BeaconBlock, proposerIdx uint64, enableLogging bool) error {
|
|
proposer := beaconState.ValidatorRegistry[proposerIdx]
|
|
pub, err := bls.PublicKeyFromBytes(proposer.Pubkey)
|
|
if err != nil {
|
|
return fmt.Errorf("could not deserialize proposer public key: %v", err)
|
|
}
|
|
currentEpoch := helpers.CurrentEpoch(beaconState)
|
|
buf := make([]byte, 32)
|
|
binary.LittleEndian.PutUint64(buf, currentEpoch)
|
|
domain := forkutil.DomainVersion(beaconState.Fork, currentEpoch, params.BeaconConfig().DomainRandao)
|
|
sig, err := bls.SignatureFromBytes(block.RandaoReveal)
|
|
if err != nil {
|
|
return fmt.Errorf("could not deserialize block randao reveal: %v", err)
|
|
}
|
|
if enableLogging {
|
|
log.WithFields(logrus.Fields{
|
|
"epoch": helpers.CurrentEpoch(beaconState) - params.BeaconConfig().GenesisEpoch,
|
|
"proposerIndex": proposerIdx,
|
|
}).Info("Verifying randao")
|
|
}
|
|
if !sig.Verify(buf, pub, domain) {
|
|
return fmt.Errorf("block randao reveal signature did not verify")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProcessProposerSlashings is one of the operations performed
|
|
// on each processed beacon block to slash proposers based on
|
|
// slashing conditions if any slashable events occurred.
|
|
//
|
|
// Official spec definition for proposer slashings:
|
|
// Verify that len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS.
|
|
//
|
|
// For each proposer_slashing in block.body.proposer_slashings:
|
|
// Let proposer = state.validator_registry[proposer_slashing.proposer_index].
|
|
// Verify that proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot.
|
|
// Verify that proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard.
|
|
// Verify that proposer_slashing.proposal_data_1.block_root != proposer_slashing.proposal_data_2.block_root.
|
|
// Verify that proposer.slashed_epoch > get_current_epoch(state).
|
|
// Verify that bls_verify(pubkey=proposer.pubkey, message_hash=hash_tree_root(proposer_slashing.proposal_data_1),
|
|
// signature=proposer_slashing.proposal_signature_1,
|
|
// domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_1.slot), DOMAIN_PROPOSAL)).
|
|
// Verify that bls_verify(pubkey=proposer.pubkey, message_hash=hash_tree_root(proposer_slashing.proposal_data_2),
|
|
// signature=proposer_slashing.proposal_signature_2,
|
|
// domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_2.slot), DOMAIN_PROPOSAL)).
|
|
// Run slash_validator(state, proposer_slashing.proposer_index).
|
|
func ProcessProposerSlashings(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
verifySignatures bool,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessProposerSlashings")
|
|
defer span.End()
|
|
|
|
body := block.Body
|
|
registry := beaconState.ValidatorRegistry
|
|
if uint64(len(body.ProposerSlashings)) > params.BeaconConfig().MaxProposerSlashings {
|
|
return nil, fmt.Errorf(
|
|
"number of proposer slashings (%d) exceeds allowed threshold of %d",
|
|
len(body.ProposerSlashings),
|
|
params.BeaconConfig().MaxProposerSlashings,
|
|
)
|
|
}
|
|
var err error
|
|
for idx, slashing := range body.ProposerSlashings {
|
|
if err = verifyProposerSlashing(slashing, verifySignatures); err != nil {
|
|
return nil, fmt.Errorf("could not verify proposer slashing #%d: %v", idx, err)
|
|
}
|
|
proposer := registry[slashing.ProposerIndex]
|
|
if proposer.SlashedEpoch > helpers.CurrentEpoch(beaconState) {
|
|
beaconState, err = v.SlashValidator(beaconState, slashing.ProposerIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not slash proposer index %d: %v",
|
|
slashing.ProposerIndex, err)
|
|
}
|
|
}
|
|
}
|
|
return beaconState, nil
|
|
}
|
|
|
|
func verifyProposerSlashing(
|
|
slashing *pb.ProposerSlashing,
|
|
verifySignatures bool,
|
|
) error {
|
|
// section of block operations.
|
|
slot1 := slashing.ProposalData_1.Slot
|
|
slot2 := slashing.ProposalData_2.Slot
|
|
shard1 := slashing.ProposalData_1.Shard
|
|
shard2 := slashing.ProposalData_2.Shard
|
|
root1 := slashing.ProposalData_1.BlockRootHash32
|
|
root2 := slashing.ProposalData_2.BlockRootHash32
|
|
if slot1 != slot2 {
|
|
if slot1 > params.BeaconConfig().GenesisSlot {
|
|
slot1 -= params.BeaconConfig().GenesisSlot
|
|
}
|
|
if slot2 > params.BeaconConfig().GenesisSlot {
|
|
slot2 -= params.BeaconConfig().GenesisSlot
|
|
}
|
|
return fmt.Errorf("slashing proposal data slots do not match: %d, %d",
|
|
slot1, slot2)
|
|
}
|
|
if shard1 != shard2 {
|
|
return fmt.Errorf("slashing proposal data shards do not match: %d, %d", shard1, shard2)
|
|
}
|
|
if !bytes.Equal(root1, root2) {
|
|
return fmt.Errorf("slashing proposal data block roots do not match: %#x, %#x", root1, root2)
|
|
}
|
|
if verifySignatures {
|
|
// TODO(#258): Verify BLS according to the specification in the "Proposer Slashings"
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProcessAttesterSlashings is one of the operations performed
|
|
// on each processed beacon block to slash attesters based on
|
|
// Casper FFG slashing conditions if any slashable events occurred.
|
|
//
|
|
// Official spec definition for attester slashings:
|
|
//
|
|
// Verify that len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS.
|
|
//
|
|
// For each attester_slashing in block.body.attester_slashings:
|
|
// Let slashable_attestation_1 = attester_slashing.slashable_attestation_1.
|
|
// Let slashable_attestation_2 = attester_slashing.slashable_attestation_2.
|
|
// Verify that slashable_attestation_1.data != slashable_attestation_2.data.
|
|
// Verify that is_double_vote(slashable_attestation_1.data, slashable_attestation_2.data)
|
|
// or is_surround_vote(slashable_attestation_1.data, slashable_attestation_2.data).
|
|
// Verify that verify_slashable_attestation(state, slashable_attestation_1).
|
|
// Verify that verify_slashable_attestation(state, slashable_attestation_2).
|
|
// Let slashable_indices = [index for index in slashable_attestation_1.validator_indices if
|
|
// index in slashable_attestation_2.validator_indices and
|
|
// state.validator_registry[index].slashed_epoch > get_current_epoch(state)].
|
|
// Verify that len(slashable_indices) >= 1.
|
|
// Run slash_validator(state, index) for each index in slashable_indices.
|
|
func ProcessAttesterSlashings(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
verifySignatures bool,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessAttesterSlashings")
|
|
defer span.End()
|
|
|
|
body := block.Body
|
|
if uint64(len(body.AttesterSlashings)) > params.BeaconConfig().MaxAttesterSlashings {
|
|
return nil, fmt.Errorf(
|
|
"number of attester slashings (%d) exceeds allowed threshold of %d",
|
|
len(body.AttesterSlashings),
|
|
params.BeaconConfig().MaxAttesterSlashings,
|
|
)
|
|
}
|
|
for idx, slashing := range body.AttesterSlashings {
|
|
if err := verifyAttesterSlashing(slashing, verifySignatures); err != nil {
|
|
return nil, fmt.Errorf("could not verify attester slashing #%d: %v", idx, err)
|
|
}
|
|
slashableIndices, err := attesterSlashableIndices(beaconState, slashing)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not determine validator indices to slash: %v", err)
|
|
}
|
|
for _, validatorIndex := range slashableIndices {
|
|
beaconState, err = v.SlashValidator(beaconState, validatorIndex)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not slash validator index %d: %v",
|
|
validatorIndex, err)
|
|
}
|
|
}
|
|
}
|
|
return beaconState, nil
|
|
}
|
|
|
|
func verifyAttesterSlashing(slashing *pb.AttesterSlashing, verifySignatures bool) error {
|
|
slashableAttestation1 := slashing.SlashableAttestation_1
|
|
slashableAttestation2 := slashing.SlashableAttestation_2
|
|
data1 := slashableAttestation1.Data
|
|
data2 := slashableAttestation2.Data
|
|
// Inner attestation data structures for the votes should not be equal,
|
|
// as that would mean both votes are the same and therefore no slashing
|
|
// should occur.
|
|
if reflect.DeepEqual(data1, data2) {
|
|
return fmt.Errorf(
|
|
"attester slashing inner slashable vote data attestation should not match: %v, %v",
|
|
data1,
|
|
data2,
|
|
)
|
|
}
|
|
// Two attestations having the same epoch target are considered to be a "double vote" in Casper
|
|
// Proof of Stake literature and the Ethereum 2.0 specification. Below, we verify that either this is the case
|
|
// or that the two attestations are a "surround vote" instead.
|
|
isSameTarget := helpers.SlotToEpoch(data1.Slot) == helpers.SlotToEpoch(data2.Slot)
|
|
if !(isSameTarget || isSurroundVote(data1, data2)) {
|
|
return errors.New("attester slashing is not a double vote nor surround vote")
|
|
}
|
|
if err := verifySlashableAttestation(slashableAttestation1, verifySignatures); err != nil {
|
|
return fmt.Errorf("could not verify attester slashable attestation data 1: %v", err)
|
|
}
|
|
if err := verifySlashableAttestation(slashableAttestation2, verifySignatures); err != nil {
|
|
return fmt.Errorf("could not verify attester slashable attestation data 2: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func attesterSlashableIndices(beaconState *pb.BeaconState, slashing *pb.AttesterSlashing) ([]uint64, error) {
|
|
slashableAttestation1 := slashing.SlashableAttestation_1
|
|
slashableAttestation2 := slashing.SlashableAttestation_2
|
|
// Let slashable_indices = [index for index in slashable_attestation_1.validator_indices if
|
|
// index in slashable_attestation_2.validator_indices and
|
|
// state.validator_registry[index].slashed_epoch > get_current_epoch(state)].
|
|
var slashableIndices []uint64
|
|
for _, idx1 := range slashableAttestation1.ValidatorIndices {
|
|
for _, idx2 := range slashableAttestation2.ValidatorIndices {
|
|
if idx1 == idx2 {
|
|
if beaconState.ValidatorRegistry[idx1].SlashedEpoch > helpers.CurrentEpoch(beaconState) {
|
|
slashableIndices = append(slashableIndices, idx1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Verify that len(slashable_indices) >= 1.
|
|
if len(slashableIndices) < 1 {
|
|
return nil, errors.New("expected a non-empty list of slashable indices")
|
|
}
|
|
return slashableIndices, nil
|
|
}
|
|
|
|
func verifySlashableAttestation(att *pb.SlashableAttestation, verifySignatures bool) error {
|
|
emptyCustody := make([]byte, len(att.CustodyBitfield))
|
|
if bytes.Equal(att.CustodyBitfield, emptyCustody) {
|
|
return errors.New("custody bit field can't all be 0s")
|
|
}
|
|
if len(att.ValidatorIndices) == 0 {
|
|
return errors.New("empty validator indices")
|
|
}
|
|
for i := 0; i < len(att.ValidatorIndices)-1; i++ {
|
|
if att.ValidatorIndices[i] >= att.ValidatorIndices[i+1] {
|
|
return fmt.Errorf("validator indices not in descending order: %v",
|
|
att.ValidatorIndices)
|
|
}
|
|
}
|
|
|
|
if isValidated, err := helpers.VerifyBitfield(att.CustodyBitfield, len(att.ValidatorIndices)); !isValidated || err != nil {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return errors.New("bitfield is unable to be verified")
|
|
}
|
|
|
|
if uint64(len(att.ValidatorIndices)) > params.BeaconConfig().MaxIndicesPerSlashableVote {
|
|
return fmt.Errorf("validator indices length (%d) exceeded max indices per slashable vote(%d)",
|
|
len(att.ValidatorIndices), params.BeaconConfig().MaxIndicesPerSlashableVote)
|
|
}
|
|
|
|
if verifySignatures {
|
|
// TODO(#258): Implement BLS verify multiple.
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// isSurroundVote checks if attestation 1's source epoch is smaller than attestation 2
|
|
// while simultaneously checking if its target epoch is greater than that of attestation 2.
|
|
// This is a Casper FFG slashing condition. This is known as "surrounding" a vote
|
|
// in Casper Proof of Stake literature.
|
|
func isSurroundVote(data1 *pb.AttestationData, data2 *pb.AttestationData) bool {
|
|
sourceEpoch1 := data1.JustifiedEpoch
|
|
sourceEpoch2 := data2.JustifiedEpoch
|
|
targetEpoch1 := helpers.SlotToEpoch(data1.Slot)
|
|
targetEpoch2 := helpers.SlotToEpoch(data2.Slot)
|
|
return sourceEpoch1 < sourceEpoch2 && targetEpoch2 < targetEpoch1
|
|
}
|
|
|
|
// ProcessBlockAttestations applies processing operations to a block's inner attestation
|
|
// records. This function returns a list of pending attestations which can then be
|
|
// appended to the BeaconState's latest attestations.
|
|
//
|
|
// Official spec definition for block attestation processing:
|
|
// Verify that len(block.body.attestations) <= MAX_ATTESTATIONS.
|
|
//
|
|
// For each attestation in block.body.attestations:
|
|
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
|
|
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
|
|
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH.
|
|
// Verify that attestation.data.justified_epoch is equal to state.justified_epoch
|
|
// if attestation.data.slot >= get_epoch_start_slot(get_current_epoch(state)) else state.previous_justified_epoch.
|
|
// Verify that attestation.data.justified_block_root is equal to
|
|
// get_block_root(state, get_epoch_start_slot(attestation.data.justified_epoch)).
|
|
// Verify that either attestation.data.latest_crosslink_root or
|
|
// attestation.data.shard_block_root equals state.latest_crosslinks[shard].shard_block_root.
|
|
// Verify bitfields and aggregate signature using BLS.
|
|
// [TO BE REMOVED IN PHASE 1] Verify that attestation.data.shard_block_root == ZERO_HASH.
|
|
// Append PendingAttestation(data=attestation.data, aggregation_bitfield=attestation.aggregation_bitfield,
|
|
// custody_bitfield=attestation.custody_bitfield, inclusion_slot=state.slot) to state.latest_attestations
|
|
func ProcessBlockAttestations(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
verifySignatures bool,
|
|
beaconDB *db.BeaconDB,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessBlockAttestations")
|
|
defer span.End()
|
|
|
|
atts := block.Body.Attestations
|
|
if uint64(len(atts)) > params.BeaconConfig().MaxAttestations {
|
|
return nil, fmt.Errorf(
|
|
"number of attestations in block (%d) exceeds allowed threshold of %d",
|
|
len(atts),
|
|
params.BeaconConfig().MaxAttestations,
|
|
)
|
|
}
|
|
|
|
for idx, attestation := range atts {
|
|
if err := verifyAttestation(ctx, beaconState, attestation, verifySignatures, beaconDB); err != nil {
|
|
return nil, fmt.Errorf("could not verify attestation at index %d in block: %v", idx, err)
|
|
}
|
|
beaconState.LatestAttestations = append(beaconState.LatestAttestations, &pb.PendingAttestation{
|
|
Data: attestation.Data,
|
|
AggregationBitfield: attestation.AggregationBitfield,
|
|
CustodyBitfield: attestation.CustodyBitfield,
|
|
InclusionSlot: beaconState.Slot,
|
|
})
|
|
}
|
|
|
|
return beaconState, nil
|
|
}
|
|
|
|
func verifyAttestation(ctx context.Context, beaconState *pb.BeaconState, att *pb.Attestation, verifySignatures bool, beaconDB *db.BeaconDB) error {
|
|
if att.Data.Slot < params.BeaconConfig().GenesisSlot {
|
|
return fmt.Errorf(
|
|
"attestation slot (slot %d) less than genesis slot (%d)",
|
|
att.Data.Slot,
|
|
params.BeaconConfig().GenesisSlot,
|
|
)
|
|
}
|
|
inclusionDelay := params.BeaconConfig().MinAttestationInclusionDelay
|
|
if att.Data.Slot+inclusionDelay > beaconState.Slot {
|
|
return fmt.Errorf(
|
|
"attestation slot (slot %d) + inclusion delay (%d) beyond current beacon state slot (%d)",
|
|
att.Data.Slot-params.BeaconConfig().GenesisSlot,
|
|
inclusionDelay,
|
|
beaconState.Slot-params.BeaconConfig().GenesisSlot,
|
|
)
|
|
}
|
|
if att.Data.Slot+params.BeaconConfig().SlotsPerEpoch <= beaconState.Slot {
|
|
return fmt.Errorf(
|
|
"attestation slot (slot %d) + epoch length (%d) less than current beacon state slot (%d)",
|
|
att.Data.Slot-params.BeaconConfig().GenesisSlot,
|
|
params.BeaconConfig().SlotsPerEpoch,
|
|
beaconState.Slot-params.BeaconConfig().GenesisSlot,
|
|
)
|
|
}
|
|
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch
|
|
// if slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state)
|
|
// else state.previous_justified_epoch`.
|
|
if helpers.SlotToEpoch(att.Data.Slot+1) >= helpers.CurrentEpoch(beaconState) {
|
|
if att.Data.JustifiedEpoch != beaconState.JustifiedEpoch {
|
|
return fmt.Errorf(
|
|
"expected attestation.JustifiedEpoch == state.JustifiedEpoch, received %d == %d",
|
|
att.Data.JustifiedEpoch-params.BeaconConfig().GenesisEpoch,
|
|
beaconState.JustifiedEpoch-params.BeaconConfig().GenesisEpoch,
|
|
)
|
|
}
|
|
} else {
|
|
if att.Data.JustifiedEpoch != beaconState.PreviousJustifiedEpoch {
|
|
return fmt.Errorf(
|
|
"expected attestation.JustifiedEpoch == state.PreviousJustifiedEpoch, received %d == %d",
|
|
att.Data.JustifiedEpoch-params.BeaconConfig().GenesisEpoch,
|
|
beaconState.PreviousJustifiedEpoch-params.BeaconConfig().GenesisEpoch,
|
|
)
|
|
}
|
|
}
|
|
|
|
// Verify that attestation.data.justified_block_root is equal to
|
|
// get_block_root(state, get_epoch_start_slot(attestation.data.justified_epoch)).
|
|
justifiedSlot := helpers.StartSlot(att.Data.JustifiedEpoch)
|
|
var justifiedBlock *pb.BeaconBlock
|
|
var err error
|
|
for i := uint64(0); justifiedBlock == nil && i < params.BeaconConfig().SlotsPerEpoch; i++ {
|
|
justifiedBlock, err = beaconDB.BlockBySlot(ctx, justifiedSlot-i)
|
|
if err != nil {
|
|
return fmt.Errorf("could not get justified block: %v", err)
|
|
}
|
|
}
|
|
blockRoot, err := hashutil.HashBeaconBlock(justifiedBlock)
|
|
if err != nil {
|
|
return fmt.Errorf("could not get justified block: %v", err)
|
|
}
|
|
|
|
justifiedBlockRoot := att.Data.JustifiedBlockRootHash32
|
|
if !bytes.Equal(justifiedBlockRoot, blockRoot[:]) {
|
|
return fmt.Errorf(
|
|
"expected JustifiedBlockRoot == getBlockRoot(state, JustifiedEpoch): got %#x = %#x",
|
|
justifiedBlockRoot,
|
|
blockRoot,
|
|
)
|
|
}
|
|
|
|
// Verify that either:
|
|
// 1.) Crosslink(shard_block_root=attestation.data.shard_block_root,
|
|
// epoch=slot_to_epoch(attestation.data.slot)) equals
|
|
// state.latest_crosslinks[attestation.data.shard]
|
|
// 2.) attestation.data.latest_crosslink
|
|
// equals state.latest_crosslinks[attestation.data.shard]
|
|
shard := att.Data.Shard
|
|
crosslink := &pb.Crosslink{
|
|
CrosslinkDataRootHash32: att.Data.CrosslinkDataRootHash32,
|
|
Epoch: helpers.SlotToEpoch(att.Data.Slot),
|
|
}
|
|
crosslinkFromAttestation := att.Data.LatestCrosslink
|
|
crosslinkFromState := beaconState.LatestCrosslinks[shard]
|
|
if !(reflect.DeepEqual(crosslinkFromState, crosslink) ||
|
|
reflect.DeepEqual(crosslinkFromState, crosslinkFromAttestation)) {
|
|
return fmt.Errorf(
|
|
"incoming attestation does not match crosslink in state for shard %d",
|
|
shard,
|
|
)
|
|
}
|
|
|
|
// Verify attestation.shard_block_root == ZERO_HASH [TO BE REMOVED IN PHASE 1].
|
|
if !bytes.Equal(att.Data.CrosslinkDataRootHash32, params.BeaconConfig().ZeroHash[:]) {
|
|
return fmt.Errorf(
|
|
"expected attestation.ShardBlockRoot == %#x, received %#x instead",
|
|
params.BeaconConfig().ZeroHash[:],
|
|
att.Data.CrosslinkDataRootHash32,
|
|
)
|
|
}
|
|
if verifySignatures {
|
|
// TODO(#258): Integrate BLS signature verification for attestation.
|
|
// assert bls_verify_multiple(
|
|
// pubkeys=[
|
|
// bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_participants]),
|
|
// bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_participants]),
|
|
// ],
|
|
// message_hash=[
|
|
// hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b0)),
|
|
// hash_tree_root(AttestationDataAndCustodyBit(data=attestation.data, custody_bit=0b1)),
|
|
// ],
|
|
// signature=attestation.aggregate_signature,
|
|
// domain=get_domain(state.fork, slot_to_epoch(attestation.data.slot), DOMAIN_ATTESTATION),
|
|
// )
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ProcessValidatorDeposits is one of the operations performed on each processed
|
|
// beacon block to verify queued validators from the Ethereum 1.0 Deposit Contract
|
|
// into the beacon chain.
|
|
//
|
|
// Official spec definition for processing validator deposits:
|
|
// Verify that len(block.body.deposits) <= MAX_DEPOSITS.
|
|
// For each deposit in block.body.deposits:
|
|
// Let serialized_deposit_data be the serialized form of deposit.deposit_data.
|
|
// It should be the DepositInput followed by 8 bytes for deposit_data.value
|
|
// and 8 bytes for deposit_data.timestamp. That is, it should match
|
|
// deposit_data in the Ethereum 1.0 deposit contract of which the hash
|
|
// was placed into the Merkle tree.
|
|
//
|
|
// Verify deposit merkle_branch, setting leaf=hash(serialized_deposit_data), branch=deposit.branch,
|
|
// depth=DEPOSIT_CONTRACT_TREE_DEPTH and root=state.latest_eth1_data.deposit_root, index = deposit.index:
|
|
//
|
|
// Run the following:
|
|
// process_deposit(
|
|
// state=state,
|
|
// pubkey=deposit.deposit_data.deposit_input.pubkey,
|
|
// deposit=deposit.deposit_data.value,
|
|
// proof_of_possession=deposit.deposit_data.deposit_input.proof_of_possession,
|
|
// withdrawal_credentials=deposit.deposit_data.deposit_input.withdrawal_credentials,
|
|
// )
|
|
func ProcessValidatorDeposits(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessValidatorDeposits")
|
|
defer span.End()
|
|
|
|
deposits := block.Body.Deposits
|
|
if uint64(len(deposits)) > params.BeaconConfig().MaxDeposits {
|
|
return nil, fmt.Errorf(
|
|
"number of deposits (%d) exceeds allowed threshold of %d",
|
|
len(deposits),
|
|
params.BeaconConfig().MaxDeposits,
|
|
)
|
|
}
|
|
var err error
|
|
var depositInput *pb.DepositInput
|
|
validatorIndexMap := stateutils.ValidatorIndexMap(beaconState)
|
|
for idx, deposit := range deposits {
|
|
depositData := deposit.DepositData
|
|
depositInput, err = helpers.DecodeDepositInput(depositData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not decode deposit input: %v", err)
|
|
}
|
|
if err = verifyDeposit(beaconState, deposit); err != nil {
|
|
return nil, fmt.Errorf("could not verify deposit #%d: %v", idx, err)
|
|
}
|
|
// depositData consists of depositValue [8]byte +
|
|
// depositTimestamp [8]byte + depositInput []byte .
|
|
depositValue := depositData[:8]
|
|
// We then mutate the beacon state with the verified validator deposit.
|
|
beaconState, err = v.ProcessDeposit(
|
|
beaconState,
|
|
validatorIndexMap,
|
|
depositInput.Pubkey,
|
|
binary.LittleEndian.Uint64(depositValue),
|
|
depositInput.ProofOfPossession,
|
|
depositInput.WithdrawalCredentialsHash32,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not process deposit into beacon state: %v", err)
|
|
}
|
|
}
|
|
return beaconState, nil
|
|
}
|
|
|
|
func verifyDeposit(beaconState *pb.BeaconState, deposit *pb.Deposit) error {
|
|
// Verify Merkle proof of deposit and deposit trie root.
|
|
receiptRoot := beaconState.LatestEth1Data.DepositRootHash32
|
|
if ok := trieutil.VerifyMerkleProof(
|
|
receiptRoot,
|
|
deposit.DepositData,
|
|
int(deposit.MerkleTreeIndex),
|
|
deposit.MerkleBranchHash32S,
|
|
); !ok {
|
|
return fmt.Errorf(
|
|
"deposit merkle branch of deposit root did not verify for root: %#x",
|
|
receiptRoot,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProcessValidatorExits is one of the operations performed
|
|
// on each processed beacon block to determine which validators
|
|
// should exit the state's validator registry.
|
|
//
|
|
// Official spec definition for processing exits:
|
|
//
|
|
// Verify that len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS.
|
|
//
|
|
// For each exit in block.body.voluntary_exits:
|
|
// Let validator = state.validator_registry[exit.validator_index].
|
|
// Verify that validator.exit_epoch > get_entry_exit_effect_epoch(get_current_epoch(state)).
|
|
// Verify that get_current_epoch(state) >= exit.epoch.
|
|
// Let exit_message = hash_tree_root(
|
|
// Exit(epoch=exit.epoch, validator_index=exit.validator_index, signature=EMPTY_SIGNATURE)
|
|
// )
|
|
// Verify that bls_verify(pubkey=validator.pubkey, message_hash=exit_message,
|
|
// signature=exit.signature, domain=get_domain(state.fork, exit.epoch, DOMAIN_EXIT)).
|
|
// Run initiate_validator_exit(state, exit.validator_index).
|
|
func ProcessValidatorExits(
|
|
ctx context.Context,
|
|
beaconState *pb.BeaconState,
|
|
block *pb.BeaconBlock,
|
|
verifySignatures bool,
|
|
) (*pb.BeaconState, error) {
|
|
ctx, span := trace.StartSpan(ctx, "beacon-chain.ChainService.state.ProcessBlock.ProcessValidatorExits")
|
|
defer span.End()
|
|
|
|
exits := block.Body.VoluntaryExits
|
|
if uint64(len(exits)) > params.BeaconConfig().MaxVoluntaryExits {
|
|
return nil, fmt.Errorf(
|
|
"number of exits (%d) exceeds allowed threshold of %d",
|
|
len(exits),
|
|
params.BeaconConfig().MaxVoluntaryExits,
|
|
)
|
|
}
|
|
|
|
validatorRegistry := beaconState.ValidatorRegistry
|
|
for idx, exit := range exits {
|
|
if err := verifyExit(beaconState, exit, verifySignatures); err != nil {
|
|
return nil, fmt.Errorf("could not verify exit #%d: %v", idx, err)
|
|
}
|
|
beaconState = v.InitiateValidatorExit(beaconState, exit.ValidatorIndex)
|
|
}
|
|
beaconState.ValidatorRegistry = validatorRegistry
|
|
return beaconState, nil
|
|
}
|
|
|
|
func verifyExit(beaconState *pb.BeaconState, exit *pb.VoluntaryExit, verifySignatures bool) error {
|
|
validator := beaconState.ValidatorRegistry[exit.ValidatorIndex]
|
|
currentEpoch := helpers.CurrentEpoch(beaconState)
|
|
entryExitEffectEpoch := helpers.EntryExitEffectEpoch(currentEpoch)
|
|
if validator.ExitEpoch <= entryExitEffectEpoch {
|
|
return fmt.Errorf(
|
|
"validator exit epoch should be > entry_exit_effect_epoch, received %d <= %d",
|
|
currentEpoch,
|
|
entryExitEffectEpoch,
|
|
)
|
|
}
|
|
if currentEpoch < exit.Epoch {
|
|
return fmt.Errorf(
|
|
"expected current epoch >= exit.epoch, received %d < %d",
|
|
currentEpoch,
|
|
exit.Epoch,
|
|
)
|
|
}
|
|
if verifySignatures {
|
|
// TODO(#258): Verify using BLS signature verification below:
|
|
// Let exit_message = hash_tree_root(
|
|
// Exit(epoch=exit.epoch, validator_index=exit.validator_index, signature=EMPTY_SIGNATURE)
|
|
// )
|
|
// Verify that bls_verify(pubkey=validator.pubkey, message_hash=exit_message,
|
|
// signature=exit.signature, domain=get_domain(state.fork, exit.epoch, DOMAIN_EXIT)).
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|