First compiling version of per-block-proc refactor
This commit is contained in:
parent
a15ed0acd3
commit
40f74c9b26
@ -10,7 +10,6 @@ harness = false
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.2"
|
criterion = "0.2"
|
||||||
test_harness = { path = "../../beacon_node/beacon_chain/test_harness" }
|
|
||||||
env_logger = "0.6.0"
|
env_logger = "0.6.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -1,344 +0,0 @@
|
|||||||
use self::verify_slashable_attestation::verify_slashable_attestation;
|
|
||||||
use crate::SlotProcessingError;
|
|
||||||
use hashing::hash;
|
|
||||||
use int_to_bytes::int_to_bytes32;
|
|
||||||
use log::{debug, trace};
|
|
||||||
use ssz::{ssz_encode, TreeHash};
|
|
||||||
use types::*;
|
|
||||||
use validate_attestation::validate_attestations;
|
|
||||||
|
|
||||||
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
|
|
||||||
|
|
||||||
mod validate_attestation;
|
|
||||||
mod verify_slashable_attestation;
|
|
||||||
|
|
||||||
const PHASE_0_CUSTODY_BIT: bool = false;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Error {
|
|
||||||
DBError(String),
|
|
||||||
StateAlreadyTransitioned,
|
|
||||||
PresentSlotIsNone,
|
|
||||||
UnableToDecodeBlock,
|
|
||||||
MissingParentState(Hash256),
|
|
||||||
InvalidParentState(Hash256),
|
|
||||||
MissingBeaconBlock(Hash256),
|
|
||||||
InvalidBeaconBlock(Hash256),
|
|
||||||
MissingParentBlock(Hash256),
|
|
||||||
StateSlotMismatch,
|
|
||||||
BadBlockSignature,
|
|
||||||
BadRandaoSignature,
|
|
||||||
MaxProposerSlashingsExceeded,
|
|
||||||
BadProposerSlashing,
|
|
||||||
MaxAttesterSlashingsExceed,
|
|
||||||
MaxAttestationsExceeded,
|
|
||||||
BadAttesterSlashing,
|
|
||||||
InvalidAttestation(AttestationValidationError),
|
|
||||||
NoBlockRoot,
|
|
||||||
MaxDepositsExceeded,
|
|
||||||
BadDeposit,
|
|
||||||
MaxExitsExceeded,
|
|
||||||
BadExit,
|
|
||||||
BadCustodyReseeds,
|
|
||||||
BadCustodyChallenges,
|
|
||||||
BadCustodyResponses,
|
|
||||||
BeaconStateError(BeaconStateError),
|
|
||||||
SlotProcessingError(SlotProcessingError),
|
|
||||||
}
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum AttestationValidationError {
|
|
||||||
IncludedTooEarly,
|
|
||||||
IncludedTooLate,
|
|
||||||
WrongJustifiedSlot,
|
|
||||||
WrongJustifiedRoot,
|
|
||||||
BadLatestCrosslinkRoot,
|
|
||||||
BadSignature,
|
|
||||||
ShardBlockRootNotZero,
|
|
||||||
NoBlockRoot,
|
|
||||||
BeaconStateError(BeaconStateError),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! ensure {
|
|
||||||
($condition: expr, $result: expr) => {
|
|
||||||
if !$condition {
|
|
||||||
return Err($result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BlockProcessable {
|
|
||||||
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error>;
|
|
||||||
fn per_block_processing_without_verifying_block_signature(
|
|
||||||
&mut self,
|
|
||||||
block: &BeaconBlock,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockProcessable for BeaconState {
|
|
||||||
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error> {
|
|
||||||
per_block_processing_signature_optional(self, block, true, spec)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn per_block_processing_without_verifying_block_signature(
|
|
||||||
&mut self,
|
|
||||||
block: &BeaconBlock,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
per_block_processing_signature_optional(self, block, false, spec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn per_block_processing_signature_optional(
|
|
||||||
mut state: &mut BeaconState,
|
|
||||||
block: &BeaconBlock,
|
|
||||||
verify_block_signature: bool,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
ensure!(block.slot == state.slot, Error::StateSlotMismatch);
|
|
||||||
|
|
||||||
// Building the previous epoch could be delayed until an attestation from a previous epoch is
|
|
||||||
// included. This is left for future optimisation.
|
|
||||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
|
||||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Proposer Signature
|
|
||||||
*/
|
|
||||||
let block_proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
|
|
||||||
let block_proposer = &state.validator_registry[block_proposer_index];
|
|
||||||
|
|
||||||
if verify_block_signature {
|
|
||||||
ensure!(
|
|
||||||
bls_verify(
|
|
||||||
&block_proposer.pubkey,
|
|
||||||
&block.proposal_root(spec)[..],
|
|
||||||
&block.signature,
|
|
||||||
get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal)
|
|
||||||
),
|
|
||||||
Error::BadBlockSignature
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RANDAO
|
|
||||||
*/
|
|
||||||
ensure!(
|
|
||||||
bls_verify(
|
|
||||||
&block_proposer.pubkey,
|
|
||||||
&int_to_bytes32(state.current_epoch(spec).as_u64()),
|
|
||||||
&block.randao_reveal,
|
|
||||||
get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao)
|
|
||||||
),
|
|
||||||
Error::BadRandaoSignature
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: check this is correct.
|
|
||||||
let new_mix = {
|
|
||||||
let mut mix = state.latest_randao_mixes
|
|
||||||
[state.slot.as_usize() % spec.latest_randao_mixes_length]
|
|
||||||
.to_vec();
|
|
||||||
mix.append(&mut ssz_encode(&block.randao_reveal));
|
|
||||||
Hash256::from(&hash(&mix)[..])
|
|
||||||
};
|
|
||||||
|
|
||||||
state.latest_randao_mixes[state.slot.as_usize() % spec.latest_randao_mixes_length] = new_mix;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Eth1 data
|
|
||||||
*/
|
|
||||||
// TODO: Eth1 data processing.
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Proposer slashings
|
|
||||||
*/
|
|
||||||
ensure!(
|
|
||||||
block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings,
|
|
||||||
Error::MaxProposerSlashingsExceeded
|
|
||||||
);
|
|
||||||
for proposer_slashing in &block.body.proposer_slashings {
|
|
||||||
let proposer = state
|
|
||||||
.validator_registry
|
|
||||||
.get(proposer_slashing.proposer_index as usize)
|
|
||||||
.ok_or(Error::BadProposerSlashing)?;
|
|
||||||
ensure!(
|
|
||||||
proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot,
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard,
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
proposer_slashing.proposal_data_1.block_root
|
|
||||||
!= proposer_slashing.proposal_data_2.block_root,
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
proposer.penalized_epoch > state.current_epoch(spec),
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
bls_verify(
|
|
||||||
&proposer.pubkey,
|
|
||||||
&proposer_slashing.proposal_data_1.hash_tree_root(),
|
|
||||||
&proposer_slashing.proposal_signature_1,
|
|
||||||
get_domain(
|
|
||||||
&state.fork,
|
|
||||||
proposer_slashing
|
|
||||||
.proposal_data_1
|
|
||||||
.slot
|
|
||||||
.epoch(spec.slots_per_epoch),
|
|
||||||
spec.domain_proposal
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
bls_verify(
|
|
||||||
&proposer.pubkey,
|
|
||||||
&proposer_slashing.proposal_data_2.hash_tree_root(),
|
|
||||||
&proposer_slashing.proposal_signature_2,
|
|
||||||
get_domain(
|
|
||||||
&state.fork,
|
|
||||||
proposer_slashing
|
|
||||||
.proposal_data_2
|
|
||||||
.slot
|
|
||||||
.epoch(spec.slots_per_epoch),
|
|
||||||
spec.domain_proposal
|
|
||||||
)
|
|
||||||
),
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Attester slashings
|
|
||||||
*/
|
|
||||||
ensure!(
|
|
||||||
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
|
|
||||||
Error::MaxAttesterSlashingsExceed
|
|
||||||
);
|
|
||||||
for attester_slashing in &block.body.attester_slashings {
|
|
||||||
verify_slashable_attestation(&mut state, &attester_slashing, spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_attestations(&mut state, &block, spec);
|
|
||||||
|
|
||||||
// Convert each attestation into a `PendingAttestation` and insert into the state.
|
|
||||||
for attestation in &block.body.attestations {
|
|
||||||
let pending_attestation = PendingAttestation {
|
|
||||||
data: attestation.data.clone(),
|
|
||||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
|
||||||
custody_bitfield: attestation.custody_bitfield.clone(),
|
|
||||||
inclusion_slot: state.slot,
|
|
||||||
};
|
|
||||||
state.latest_attestations.push(pending_attestation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Deposits
|
|
||||||
*/
|
|
||||||
ensure!(
|
|
||||||
block.body.deposits.len() as u64 <= spec.max_deposits,
|
|
||||||
Error::MaxDepositsExceeded
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: verify deposit merkle branches.
|
|
||||||
for deposit in &block.body.deposits {
|
|
||||||
debug!(
|
|
||||||
"Processing deposit for pubkey {:?}",
|
|
||||||
deposit.deposit_data.deposit_input.pubkey
|
|
||||||
);
|
|
||||||
state
|
|
||||||
.process_deposit(
|
|
||||||
deposit.deposit_data.deposit_input.pubkey.clone(),
|
|
||||||
deposit.deposit_data.amount,
|
|
||||||
deposit
|
|
||||||
.deposit_data
|
|
||||||
.deposit_input
|
|
||||||
.proof_of_possession
|
|
||||||
.clone(),
|
|
||||||
deposit.deposit_data.deposit_input.withdrawal_credentials,
|
|
||||||
None,
|
|
||||||
spec,
|
|
||||||
)
|
|
||||||
.map_err(|_| Error::BadDeposit)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Exits
|
|
||||||
*/
|
|
||||||
ensure!(
|
|
||||||
block.body.exits.len() as u64 <= spec.max_exits,
|
|
||||||
Error::MaxExitsExceeded
|
|
||||||
);
|
|
||||||
|
|
||||||
for exit in &block.body.exits {
|
|
||||||
let validator = state
|
|
||||||
.validator_registry
|
|
||||||
.get(exit.validator_index as usize)
|
|
||||||
.ok_or(Error::BadExit)?;
|
|
||||||
ensure!(
|
|
||||||
validator.exit_epoch
|
|
||||||
> state.get_entry_exit_effect_epoch(state.current_epoch(spec), spec),
|
|
||||||
Error::BadExit
|
|
||||||
);
|
|
||||||
ensure!(state.current_epoch(spec) >= exit.epoch, Error::BadExit);
|
|
||||||
let exit_message = {
|
|
||||||
let exit_struct = VoluntaryExit {
|
|
||||||
epoch: exit.epoch,
|
|
||||||
validator_index: exit.validator_index,
|
|
||||||
signature: spec.empty_signature.clone(),
|
|
||||||
};
|
|
||||||
exit_struct.hash_tree_root()
|
|
||||||
};
|
|
||||||
ensure!(
|
|
||||||
bls_verify(
|
|
||||||
&validator.pubkey,
|
|
||||||
&exit_message,
|
|
||||||
&exit.signature,
|
|
||||||
get_domain(&state.fork, exit.epoch, spec.domain_exit)
|
|
||||||
),
|
|
||||||
Error::BadProposerSlashing
|
|
||||||
);
|
|
||||||
state.initiate_validator_exit(exit.validator_index as usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("State transition complete.");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_domain(fork: &Fork, epoch: Epoch, domain_type: u64) -> u64 {
|
|
||||||
fork.get_domain(epoch, domain_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, domain: u64) -> bool {
|
|
||||||
signature.verify(message, domain, pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<AttestationValidationError> for Error {
|
|
||||||
fn from(e: AttestationValidationError) -> Error {
|
|
||||||
Error::InvalidAttestation(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BeaconStateError> for Error {
|
|
||||||
fn from(e: BeaconStateError) -> Error {
|
|
||||||
Error::BeaconStateError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SlotProcessingError> for Error {
|
|
||||||
fn from(e: SlotProcessingError) -> Error {
|
|
||||||
Error::SlotProcessingError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<BeaconStateError> for AttestationValidationError {
|
|
||||||
fn from(e: BeaconStateError) -> AttestationValidationError {
|
|
||||||
AttestationValidationError::BeaconStateError(e)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
use super::Error;
|
|
||||||
use types::*;
|
|
||||||
|
|
||||||
macro_rules! ensure {
|
|
||||||
($condition: expr, $result: expr) => {
|
|
||||||
if !$condition {
|
|
||||||
return Err($result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
|
|
||||||
/// otherwise returns an `Err`.
|
|
||||||
pub fn verify_slashable_attestation(
|
|
||||||
state: &mut BeaconState,
|
|
||||||
attester_slashing: &AttesterSlashing,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
|
||||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
slashable_attestation_1.data != slashable_attestation_2.data,
|
|
||||||
Error::BadAttesterSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
|
||||||
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
|
||||||
Error::BadAttesterSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
state.verify_slashable_attestation(&slashable_attestation_1, spec),
|
|
||||||
Error::BadAttesterSlashing
|
|
||||||
);
|
|
||||||
ensure!(
|
|
||||||
state.verify_slashable_attestation(&slashable_attestation_2, spec),
|
|
||||||
Error::BadAttesterSlashing
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut slashable_indices = vec![];
|
|
||||||
for i in &slashable_attestation_1.validator_indices {
|
|
||||||
let validator = state
|
|
||||||
.validator_registry
|
|
||||||
.get(*i as usize)
|
|
||||||
.ok_or_else(|| Error::BadAttesterSlashing)?;
|
|
||||||
|
|
||||||
if slashable_attestation_1.validator_indices.contains(&i)
|
|
||||||
& !validator.is_penalized_at(state.current_epoch(spec))
|
|
||||||
{
|
|
||||||
slashable_indices.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing);
|
|
||||||
|
|
||||||
for i in slashable_indices {
|
|
||||||
state.penalize_validator(*i as usize, spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,9 +1,98 @@
|
|||||||
use types::BeaconStateError;
|
use types::BeaconStateError;
|
||||||
|
|
||||||
|
macro_rules! impl_from_beacon_state_error {
|
||||||
|
($type: ident) => {
|
||||||
|
impl From<BeaconStateError> for $type {
|
||||||
|
fn from(e: BeaconStateError) -> $type {
|
||||||
|
$type::BeaconStateError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_into_with_index_with_beacon_error {
|
||||||
|
($error_type: ident, $invalid_type: ident) => {
|
||||||
|
impl IntoWithIndex<BlockProcessingError> for $error_type {
|
||||||
|
fn into_with_index(self, i: usize) -> BlockProcessingError {
|
||||||
|
match self {
|
||||||
|
$error_type::Invalid(e) => {
|
||||||
|
BlockProcessingError::Invalid(BlockInvalid::$invalid_type(i, e))
|
||||||
|
}
|
||||||
|
$error_type::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_into_with_index_without_beacon_error {
|
||||||
|
($error_type: ident, $invalid_type: ident) => {
|
||||||
|
impl IntoWithIndex<BlockProcessingError> for $error_type {
|
||||||
|
fn into_with_index(self, i: usize) -> BlockProcessingError {
|
||||||
|
match self {
|
||||||
|
$error_type::Invalid(e) => {
|
||||||
|
BlockProcessingError::Invalid(BlockInvalid::$invalid_type(i, e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoWithIndex<T>: Sized {
|
||||||
|
fn into_with_index(self, i: usize) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Block Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum BlockProcessingError {
|
||||||
|
/// The `BeaconBlock` is invalid.
|
||||||
|
Invalid(BlockInvalid),
|
||||||
|
BeaconStateError(BeaconStateError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from_beacon_state_error!(BlockProcessingError);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum BlockInvalid {
|
||||||
|
StateSlotMismatch,
|
||||||
|
BadSignature,
|
||||||
|
BadRandaoSignature,
|
||||||
|
MaxAttestationsExceeded,
|
||||||
|
MaxAttesterSlashingsExceed,
|
||||||
|
MaxProposerSlashingsExceeded,
|
||||||
|
MaxDepositsExceeded,
|
||||||
|
MaxExitsExceeded,
|
||||||
|
MaxTransfersExceed,
|
||||||
|
AttestationInvalid(usize, AttestationInvalid),
|
||||||
|
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
|
||||||
|
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
|
||||||
|
DepositInvalid(usize, DepositInvalid),
|
||||||
|
// TODO: merge this into the `DepositInvalid` error.
|
||||||
|
DepositProcessingFailed(usize),
|
||||||
|
ExitInvalid(usize, ExitInvalid),
|
||||||
|
TransferInvalid(usize, TransferInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<BlockProcessingError> for BlockInvalid {
|
||||||
|
fn into(self) -> BlockProcessingError {
|
||||||
|
BlockProcessingError::Invalid(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attestation Validation
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum AttestationValidationError {
|
pub enum AttestationValidationError {
|
||||||
|
/// The `Attestation` is invalid.
|
||||||
Invalid(AttestationInvalid),
|
Invalid(AttestationInvalid),
|
||||||
ProcessingError(BeaconStateError),
|
/// Encountered a `BeaconStateError` whilst attempting to determine validity.
|
||||||
|
BeaconStateError(BeaconStateError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -23,8 +112,132 @@ pub enum AttestationInvalid {
|
|||||||
ShardBlockRootNotZero,
|
ShardBlockRootNotZero,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BeaconStateError> for AttestationValidationError {
|
impl_from_beacon_state_error!(AttestationValidationError);
|
||||||
fn from(e: BeaconStateError) -> AttestationValidationError {
|
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
||||||
AttestationValidationError::ProcessingError(e)
|
|
||||||
|
/*
|
||||||
|
* `AttesterSlashing` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum AttesterSlashingValidationError {
|
||||||
|
/// The `SlashableAttestation` is invalid.
|
||||||
|
Invalid(AttesterSlashingInvalid),
|
||||||
|
/// Encountered a `BeaconStateError` whilst attempting to determine validity.
|
||||||
|
BeaconStateError(BeaconStateError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum AttesterSlashingInvalid {
|
||||||
|
AttestationDataIdentical,
|
||||||
|
NotSlashable,
|
||||||
|
SlashableAttestation1Invalid(SlashableAttestationInvalid),
|
||||||
|
SlashableAttestation2Invalid(SlashableAttestationInvalid),
|
||||||
|
UnknownValidator,
|
||||||
|
NoSlashableIndices,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from_beacon_state_error!(AttesterSlashingValidationError);
|
||||||
|
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `SlashableAttestation` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum SlashableAttestationValidationError {
|
||||||
|
Invalid(SlashableAttestationInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum SlashableAttestationInvalid {
|
||||||
|
CustodyBitfieldHasSetBits,
|
||||||
|
NoValidatorIndices,
|
||||||
|
BadValidatorIndicesOrdering,
|
||||||
|
BadCustodyBitfieldLength,
|
||||||
|
MaxIndicesExceed,
|
||||||
|
UnknownValidator,
|
||||||
|
BadSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<SlashableAttestationInvalid> for SlashableAttestationValidationError {
|
||||||
|
fn into(self) -> SlashableAttestationInvalid {
|
||||||
|
match self {
|
||||||
|
SlashableAttestationValidationError::Invalid(e) => e,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `ProposerSlashing` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ProposerSlashingValidationError {
|
||||||
|
Invalid(ProposerSlashingInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ProposerSlashingInvalid {
|
||||||
|
ProposerUnknown,
|
||||||
|
ProposalSlotMismatch,
|
||||||
|
ProposalShardMismatch,
|
||||||
|
ProposalBlockRootMismatch,
|
||||||
|
ProposerAlreadySlashed,
|
||||||
|
BadProposal1Signature,
|
||||||
|
BadProposal2Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_into_with_index_without_beacon_error!(
|
||||||
|
ProposerSlashingValidationError,
|
||||||
|
ProposerSlashingInvalid
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `Deposit` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DepositValidationError {
|
||||||
|
Invalid(DepositInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DepositInvalid {
|
||||||
|
BadIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_into_with_index_without_beacon_error!(DepositValidationError, DepositInvalid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `Exit` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ExitValidationError {
|
||||||
|
Invalid(ExitInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum ExitInvalid {
|
||||||
|
ValidatorUnknown,
|
||||||
|
AlreadyExited,
|
||||||
|
FutureEpoch,
|
||||||
|
BadSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_into_with_index_without_beacon_error!(ExitValidationError, ExitInvalid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `Transfer` Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum TransferValidationError {
|
||||||
|
Invalid(TransferInvalid),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum TransferInvalid {}
|
||||||
|
|
||||||
|
impl_into_with_index_without_beacon_error!(TransferValidationError, TransferInvalid);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod block_processable;
|
pub mod per_block_processing;
|
||||||
// mod epoch_processable;
|
// mod epoch_processable;
|
||||||
mod errors;
|
pub mod errors;
|
||||||
// mod slot_processable;
|
// mod slot_processable;
|
||||||
|
|
||||||
pub use block_processable::{
|
pub use errors::{BlockInvalid, BlockProcessingError};
|
||||||
validate_attestation, validate_attestation_without_signature, BlockProcessable,
|
pub use per_block_processing::{
|
||||||
Error as BlockProcessingError,
|
per_block_processing, per_block_processing_without_verifying_block_signature,
|
||||||
};
|
};
|
||||||
// pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError};
|
// pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError};
|
||||||
// pub use slot_processable::{Error as SlotProcessingError, SlotProcessable};
|
// pub use slot_processable::{Error as SlotProcessingError, SlotProcessable};
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
#[macro_use]
|
macro_rules! verify {
|
||||||
macro_rules! ensure {
|
($condition: expr, $result: expr) => {
|
||||||
($condition: expr, $result: ident) => {
|
|
||||||
if !$condition {
|
if !$condition {
|
||||||
return Err(Error::Invalid(Invalid::$result));
|
return Err(Error::Invalid($result));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! invalid {
|
||||||
|
($result: expr) => {
|
||||||
|
return Err(Error::Invalid($result));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
268
eth2/state_processing/src/per_block_processing.rs
Normal file
268
eth2/state_processing/src/per_block_processing.rs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||||
|
use crate::errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
|
||||||
|
use hashing::hash;
|
||||||
|
use log::debug;
|
||||||
|
use ssz::{ssz_encode, SignedRoot, TreeHash};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
pub use self::verify_attester_slashing::verify_attester_slashing;
|
||||||
|
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
|
||||||
|
pub use verify_deposit::verify_deposit;
|
||||||
|
pub use verify_exit::verify_exit;
|
||||||
|
pub use verify_transfer::verify_transfer;
|
||||||
|
|
||||||
|
mod validate_attestation;
|
||||||
|
mod verify_attester_slashing;
|
||||||
|
mod verify_deposit;
|
||||||
|
mod verify_exit;
|
||||||
|
mod verify_proposer_slashing;
|
||||||
|
mod verify_slashable_attestation;
|
||||||
|
mod verify_transfer;
|
||||||
|
|
||||||
|
pub fn per_block_processing(
|
||||||
|
state: &mut BeaconState,
|
||||||
|
block: &BeaconBlock,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
per_block_processing_signature_optional(state, block, true, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn per_block_processing_without_verifying_block_signature(
|
||||||
|
state: &mut BeaconState,
|
||||||
|
block: &BeaconBlock,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
per_block_processing_signature_optional(state, block, false, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn per_block_processing_signature_optional(
|
||||||
|
mut state: &mut BeaconState,
|
||||||
|
block: &BeaconBlock,
|
||||||
|
should_verify_block_signature: bool,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Verify that `block.slot == state.slot`.
|
||||||
|
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
|
||||||
|
|
||||||
|
// Get the epoch for future ergonomics.
|
||||||
|
let epoch = block.slot.epoch(spec.slots_per_epoch);
|
||||||
|
|
||||||
|
// Ensure the current epoch cache is built.
|
||||||
|
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
||||||
|
|
||||||
|
// Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`.
|
||||||
|
let block_proposer =
|
||||||
|
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
|
||||||
|
|
||||||
|
// Block signature
|
||||||
|
if should_verify_block_signature {
|
||||||
|
verify!(
|
||||||
|
verify_block_signature(&block, &block_proposer, &state.fork, spec,),
|
||||||
|
Invalid::BadSignature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randao
|
||||||
|
|
||||||
|
// 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))`.
|
||||||
|
verify!(
|
||||||
|
block.randao_reveal.verify(
|
||||||
|
&state.current_epoch(spec).hash_tree_root()[..],
|
||||||
|
spec.get_domain(epoch, Domain::Randao, &state.fork),
|
||||||
|
&block_proposer.pubkey
|
||||||
|
),
|
||||||
|
Invalid::BadRandaoSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update the state's RANDAO mix with the one revealed in the block.
|
||||||
|
update_randao(&mut state, &block.randao_reveal, spec)?;
|
||||||
|
|
||||||
|
// Eth1 Data
|
||||||
|
|
||||||
|
// Either increment the eth1_data vote count, or add a new eth1_data.
|
||||||
|
let matching_eth1_vote_index = state
|
||||||
|
.eth1_data_votes
|
||||||
|
.iter()
|
||||||
|
.position(|vote| vote.eth1_data == block.eth1_data);
|
||||||
|
if let Some(index) = matching_eth1_vote_index {
|
||||||
|
state.eth1_data_votes[index].vote_count += 1;
|
||||||
|
} else {
|
||||||
|
state.eth1_data_votes.push(Eth1DataVote {
|
||||||
|
eth1_data: block.eth1_data.clone(),
|
||||||
|
vote_count: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Proposer slashings
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings,
|
||||||
|
Invalid::MaxProposerSlashingsExceeded
|
||||||
|
);
|
||||||
|
for (i, proposer_slashing) in block.body.proposer_slashings.iter().enumerate() {
|
||||||
|
verify_proposer_slashing(proposer_slashing, &state, spec)
|
||||||
|
.map_err(|e| e.into_with_index(i))?;
|
||||||
|
state.slash_validator(proposer_slashing.proposer_index as usize, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attester Slashings
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
|
||||||
|
Invalid::MaxAttesterSlashingsExceed
|
||||||
|
);
|
||||||
|
for (i, attester_slashing) in block.body.attester_slashings.iter().enumerate() {
|
||||||
|
let slashable_indices = verify_attester_slashing(&state, &attester_slashing, spec)
|
||||||
|
.map_err(|e| e.into_with_index(i))?;
|
||||||
|
for i in slashable_indices {
|
||||||
|
state.slash_validator(i as usize, spec)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attestations
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
block.body.attestations.len() as u64 <= spec.max_attestations,
|
||||||
|
Invalid::MaxAttestationsExceeded
|
||||||
|
);
|
||||||
|
for (i, attestation) in block.body.attestations.iter().enumerate() {
|
||||||
|
// Build the previous epoch cache only if required by an attestation.
|
||||||
|
if attestation.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec) {
|
||||||
|
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_attestation(&mut state, attestation, spec).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
|
let pending_attestation = PendingAttestation {
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||||
|
custody_bitfield: attestation.custody_bitfield.clone(),
|
||||||
|
inclusion_slot: state.slot,
|
||||||
|
};
|
||||||
|
state.latest_attestations.push(pending_attestation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deposits
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
block.body.deposits.len() as u64 <= spec.max_deposits,
|
||||||
|
Invalid::MaxDepositsExceeded
|
||||||
|
);
|
||||||
|
for (i, deposit) in block.body.deposits.iter().enumerate() {
|
||||||
|
verify_deposit(&mut state, deposit, spec).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
|
state
|
||||||
|
.process_deposit(
|
||||||
|
deposit.deposit_data.deposit_input.pubkey.clone(),
|
||||||
|
deposit.deposit_data.amount,
|
||||||
|
deposit
|
||||||
|
.deposit_data
|
||||||
|
.deposit_input
|
||||||
|
.proof_of_possession
|
||||||
|
.clone(),
|
||||||
|
deposit.deposit_data.deposit_input.withdrawal_credentials,
|
||||||
|
None,
|
||||||
|
spec,
|
||||||
|
)
|
||||||
|
.map_err(|_| Error::Invalid(Invalid::DepositProcessingFailed(i)))?;
|
||||||
|
|
||||||
|
state.deposit_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exits
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
block.body.voluntary_exits.len() as u64 <= spec.max_voluntary_exits,
|
||||||
|
Invalid::MaxExitsExceeded
|
||||||
|
);
|
||||||
|
for (i, exit) in block.body.voluntary_exits.iter().enumerate() {
|
||||||
|
verify_exit(&state, exit, spec).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
|
state.initiate_validator_exit(exit.validator_index as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfers
|
||||||
|
verify!(
|
||||||
|
block.body.transfers.len() as u64 <= spec.max_transfers,
|
||||||
|
Invalid::MaxTransfersExceed
|
||||||
|
);
|
||||||
|
for (i, transfer) in block.body.transfers.iter().enumerate() {
|
||||||
|
verify_transfer(&state, transfer, spec).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
|
let block_proposer = state.get_beacon_proposer_index(state.slot, spec)?;
|
||||||
|
|
||||||
|
state.validator_balances[transfer.from as usize] -= transfer.amount + transfer.fee;
|
||||||
|
state.validator_balances[transfer.to as usize] += transfer.amount + transfer.fee;
|
||||||
|
state.validator_balances[block_proposer as usize] += transfer.fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("State transition complete.");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verifies the signature of a block.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_block_signature(
|
||||||
|
block: &BeaconBlock,
|
||||||
|
block_proposer: &Validator,
|
||||||
|
fork: &Fork,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> bool {
|
||||||
|
// Let proposal = `Proposal(block.slot, BEACON_CHAIN_SHARD_NUMBER, signed_root(block,
|
||||||
|
// "signature"), block.signature)`.
|
||||||
|
let proposal = Proposal {
|
||||||
|
slot: block.slot,
|
||||||
|
shard: spec.beacon_chain_shard_number,
|
||||||
|
block_root: Hash256::from(&block.signed_root()[..]),
|
||||||
|
signature: block.signature.clone(),
|
||||||
|
};
|
||||||
|
let domain = spec.get_domain(
|
||||||
|
block.slot.epoch(spec.slots_per_epoch),
|
||||||
|
Domain::Proposal,
|
||||||
|
fork,
|
||||||
|
);
|
||||||
|
// Verify that `bls_verify(pubkey=proposer.pubkey, message_hash=signed_root(proposal,
|
||||||
|
// "signature"), signature=proposal.signature, domain=get_domain(state.fork,
|
||||||
|
// get_current_epoch(state), DOMAIN_PROPOSAL))`.
|
||||||
|
proposal
|
||||||
|
.signature
|
||||||
|
.verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the present randao mix.
|
||||||
|
///
|
||||||
|
/// 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))`.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn update_randao(
|
||||||
|
state: &mut BeaconState,
|
||||||
|
reveal: &Signature,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), BeaconStateError> {
|
||||||
|
let hashed_reveal = {
|
||||||
|
let encoded_signature = ssz_encode(reveal);
|
||||||
|
Hash256::from(&hash(&encoded_signature[..])[..])
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_epoch = state.slot.epoch(spec.slots_per_epoch);
|
||||||
|
|
||||||
|
let current_mix = state
|
||||||
|
.get_randao_mix(current_epoch, spec)
|
||||||
|
.ok_or_else(|| BeaconStateError::InsufficientRandaoMixes)?;
|
||||||
|
|
||||||
|
let new_mix = *current_mix ^ hashed_reveal;
|
||||||
|
|
||||||
|
let index = current_epoch.as_usize() % spec.latest_randao_mixes_length;
|
||||||
|
|
||||||
|
if index < state.latest_randao_mixes.len() {
|
||||||
|
state.latest_randao_mixes[index] = new_mix;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(BeaconStateError::InsufficientRandaoMixes)
|
||||||
|
}
|
||||||
|
}
|
@ -3,27 +3,6 @@ use ssz::TreeHash;
|
|||||||
use types::beacon_state::helpers::*;
|
use types::beacon_state::helpers::*;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Validate the attestations in some block, converting each into a `PendingAttestation` which is
|
|
||||||
/// then added to `state.latest_attestations`.
|
|
||||||
///
|
|
||||||
/// Spec v0.4.0
|
|
||||||
pub fn validate_attestations(
|
|
||||||
state: &BeaconState,
|
|
||||||
block: &BeaconBlock,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
ensure!(
|
|
||||||
block.body.attestations.len() as u64 <= spec.max_attestations,
|
|
||||||
MaxAttestationsExceeded
|
|
||||||
);
|
|
||||||
|
|
||||||
for attestation in &block.body.attestations {
|
|
||||||
validate_attestation(&state, attestation, spec)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate an attestation, checking the aggregate signature.
|
/// Validate an attestation, checking the aggregate signature.
|
||||||
///
|
///
|
||||||
/// Spec v0.4.0
|
/// Spec v0.4.0
|
||||||
@ -48,7 +27,7 @@ pub fn validate_attestation_without_signature(
|
|||||||
|
|
||||||
/// Validate an attestation, optionally checking the aggregate signature.
|
/// Validate an attestation, optionally checking the aggregate signature.
|
||||||
///
|
///
|
||||||
/// Spec v0.2.0
|
/// Spec v0.4.0
|
||||||
fn validate_attestation_signature_optional(
|
fn validate_attestation_signature_optional(
|
||||||
state: &BeaconState,
|
state: &BeaconState,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -56,38 +35,41 @@ fn validate_attestation_signature_optional(
|
|||||||
verify_signature: bool,
|
verify_signature: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
|
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
|
||||||
ensure!(attestation.data.slot >= spec.genesis_slot, PreGenesis);
|
verify!(
|
||||||
|
attestation.data.slot >= spec.genesis_slot,
|
||||||
|
Invalid::PreGenesis
|
||||||
|
);
|
||||||
|
|
||||||
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
|
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||||
IncludedTooEarly
|
Invalid::IncludedTooEarly
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
|
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
|
||||||
ensure!(
|
verify!(
|
||||||
state.slot < attestation.data.slot + spec.slots_per_epoch,
|
state.slot < attestation.data.slot + spec.slots_per_epoch,
|
||||||
IncludedTooLate
|
Invalid::IncludedTooLate
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if
|
// 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
|
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
|
||||||
// state.previous_justified_epoch`.
|
// state.previous_justified_epoch`.
|
||||||
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
|
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.data.justified_epoch == state.justified_epoch,
|
attestation.data.justified_epoch == state.justified_epoch,
|
||||||
WrongJustifiedSlot
|
Invalid::WrongJustifiedSlot
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.data.justified_epoch == state.previous_justified_epoch,
|
attestation.data.justified_epoch == state.previous_justified_epoch,
|
||||||
WrongJustifiedSlot
|
Invalid::WrongJustifiedSlot
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
|
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
|
||||||
// get_epoch_start_slot(attestation.data.justified_epoch))`.
|
// get_epoch_start_slot(attestation.data.justified_epoch))`.
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.data.justified_block_root
|
attestation.data.justified_block_root
|
||||||
== *state
|
== *state
|
||||||
.get_block_root(
|
.get_block_root(
|
||||||
@ -98,7 +80,7 @@ fn validate_attestation_signature_optional(
|
|||||||
&spec
|
&spec
|
||||||
)
|
)
|
||||||
.ok_or(BeaconStateError::InsufficientBlockRoots)?,
|
.ok_or(BeaconStateError::InsufficientBlockRoots)?,
|
||||||
WrongJustifiedRoot
|
Invalid::WrongJustifiedRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that either:
|
// Verify that either:
|
||||||
@ -112,59 +94,61 @@ fn validate_attestation_signature_optional(
|
|||||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
crosslink_data_root: attestation.data.crosslink_data_root,
|
||||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
||||||
};
|
};
|
||||||
ensure!(
|
verify!(
|
||||||
(attestation.data.latest_crosslink
|
(attestation.data.latest_crosslink
|
||||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
||||||
BadLatestCrosslinkRoot
|
Invalid::BadLatestCrosslinkRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the committee for this attestation
|
// Get the committee for this attestation
|
||||||
let (committee, _shard) = state
|
let (committee, _shard) = state
|
||||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(committee, shard)| *shard == attestation.data.shard)
|
.find(|(_committee, shard)| *shard == attestation.data.shard)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?;
|
.ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?;
|
||||||
|
|
||||||
// Custody bitfield is all zeros (phase 0 requirement).
|
// Custody bitfield is all zeros (phase 0 requirement).
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.custody_bitfield.num_set_bits() == 0,
|
attestation.custody_bitfield.num_set_bits() == 0,
|
||||||
CustodyBitfieldHasSetBits
|
Invalid::CustodyBitfieldHasSetBits
|
||||||
);
|
);
|
||||||
// Custody bitfield length is correct.
|
// Custody bitfield length is correct.
|
||||||
ensure!(
|
verify!(
|
||||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||||
BadCustodyBitfieldLength
|
Invalid::BadCustodyBitfieldLength
|
||||||
);
|
);
|
||||||
// Aggregation bitfield isn't empty.
|
// Aggregation bitfield isn't empty.
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
attestation.aggregation_bitfield.num_set_bits() != 0,
|
||||||
AggregationBitfieldIsEmpty
|
Invalid::AggregationBitfieldIsEmpty
|
||||||
);
|
);
|
||||||
// Aggregation bitfield length is correct.
|
// Aggregation bitfield length is correct.
|
||||||
ensure!(
|
verify!(
|
||||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||||
BadAggregationBitfieldLength
|
Invalid::BadAggregationBitfieldLength
|
||||||
);
|
);
|
||||||
|
|
||||||
if verify_signature {
|
if verify_signature {
|
||||||
ensure!(
|
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||||
|
verify!(
|
||||||
verify_attestation_signature(
|
verify_attestation_signature(
|
||||||
state,
|
state,
|
||||||
committee,
|
committee,
|
||||||
|
attestation_epoch,
|
||||||
&attestation.custody_bitfield,
|
&attestation.custody_bitfield,
|
||||||
&attestation.data,
|
&attestation.data,
|
||||||
&attestation.aggregate_signature,
|
&attestation.aggregate_signature,
|
||||||
spec
|
spec
|
||||||
),
|
),
|
||||||
BadSignature
|
Invalid::BadSignature
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
|
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
|
||||||
ensure!(
|
verify!(
|
||||||
attestation.data.crosslink_data_root == spec.zero_hash,
|
attestation.data.crosslink_data_root == spec.zero_hash,
|
||||||
ShardBlockRootNotZero
|
Invalid::ShardBlockRootNotZero
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -177,9 +161,12 @@ fn validate_attestation_signature_optional(
|
|||||||
/// - `aggregate_signature` was not signed correctly.
|
/// - `aggregate_signature` was not signed correctly.
|
||||||
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
||||||
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
fn verify_attestation_signature(
|
fn verify_attestation_signature(
|
||||||
state: &BeaconState,
|
state: &BeaconState,
|
||||||
committee: &[usize],
|
committee: &[usize],
|
||||||
|
attestation_epoch: Epoch,
|
||||||
custody_bitfield: &Bitfield,
|
custody_bitfield: &Bitfield,
|
||||||
attestation_data: &AttestationData,
|
attestation_data: &AttestationData,
|
||||||
aggregate_signature: &AggregateSignature,
|
aggregate_signature: &AggregateSignature,
|
||||||
@ -234,5 +221,7 @@ fn verify_attestation_signature(
|
|||||||
keys.push(&aggregate_pubs[1]);
|
keys.push(&aggregate_pubs[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregate_signature.verify_multiple(&messages[..], spec.domain_attestation, &keys[..])
|
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
|
||||||
|
|
||||||
|
aggregate_signature.verify_multiple(&messages[..], domain, &keys[..])
|
||||||
}
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
use super::verify_slashable_attestation::verify_slashable_attestation;
|
||||||
|
use crate::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
|
||||||
|
/// otherwise returns an `Err`.
|
||||||
|
///
|
||||||
|
/// Returns the slashable indices from the `AttesterSlashing`.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_attester_slashing(
|
||||||
|
state: &BeaconState,
|
||||||
|
attester_slashing: &AttesterSlashing,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<Vec<u64>, Error> {
|
||||||
|
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
||||||
|
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
slashable_attestation_1.data != slashable_attestation_2.data,
|
||||||
|
Invalid::AttestationDataIdentical
|
||||||
|
);
|
||||||
|
verify!(
|
||||||
|
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
||||||
|
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
||||||
|
Invalid::NotSlashable
|
||||||
|
);
|
||||||
|
|
||||||
|
verify_slashable_attestation(state, &slashable_attestation_1, spec)
|
||||||
|
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
|
||||||
|
verify_slashable_attestation(state, &slashable_attestation_2, spec)
|
||||||
|
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
|
||||||
|
|
||||||
|
let mut slashable_indices = vec![];
|
||||||
|
for i in &slashable_attestation_1.validator_indices {
|
||||||
|
let validator = state
|
||||||
|
.validator_registry
|
||||||
|
.get(*i as usize)
|
||||||
|
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator))?;
|
||||||
|
|
||||||
|
if slashable_attestation_1.validator_indices.contains(&i) & !validator.slashed {
|
||||||
|
slashable_indices.push(*i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
|
||||||
|
|
||||||
|
Ok(slashable_indices)
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
use crate::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
|
||||||
|
use ssz::TreeHash;
|
||||||
|
use types::beacon_state::helpers::verify_bitfield_length;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Verify validity of ``slashable_attestation`` fields.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if all fields are valid.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_deposit(
|
||||||
|
state: &BeaconState,
|
||||||
|
deposit: &Deposit,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// TODO: verify serialized deposit data.
|
||||||
|
|
||||||
|
// TODO: verify deposit index.
|
||||||
|
verify!(deposit.index == state.deposit_index, Invalid::BadIndex);
|
||||||
|
|
||||||
|
// TODO: verify merkle branch.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
use crate::errors::{ExitInvalid as Invalid, ExitValidationError as Error};
|
||||||
|
use ssz::SignedRoot;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Verify validity of ``slashable_attestation`` fields.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if all fields are valid.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_exit(
|
||||||
|
state: &BeaconState,
|
||||||
|
exit: &VoluntaryExit,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let validator = state
|
||||||
|
.validator_registry
|
||||||
|
.get(exit.validator_index as usize)
|
||||||
|
.ok_or(Error::Invalid(Invalid::ValidatorUnknown))?;
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
validator.exit_epoch
|
||||||
|
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec),
|
||||||
|
Invalid::AlreadyExited
|
||||||
|
);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
state.current_epoch(spec) >= exit.epoch,
|
||||||
|
Invalid::FutureEpoch
|
||||||
|
);
|
||||||
|
|
||||||
|
let message = exit.signed_root();
|
||||||
|
let domain = spec.get_domain(exit.epoch, Domain::Exit, &state.fork);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
exit.signature
|
||||||
|
.verify(&message[..], domain, &validator.pubkey),
|
||||||
|
Invalid::BadSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
use crate::errors::{ProposerSlashingInvalid as Invalid, ProposerSlashingValidationError as Error};
|
||||||
|
use ssz::SignedRoot;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Returns `Ok(())` if some `ProposerSlashing` is valid to be included in some `BeaconState`,
|
||||||
|
/// otherwise returns an `Err`.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_proposer_slashing(
|
||||||
|
proposer_slashing: &ProposerSlashing,
|
||||||
|
state: &BeaconState,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let proposer = state
|
||||||
|
.validator_registry
|
||||||
|
.get(proposer_slashing.proposer_index as usize)
|
||||||
|
.ok_or(Error::Invalid(Invalid::ProposerUnknown))?;
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot,
|
||||||
|
Invalid::ProposalSlotMismatch
|
||||||
|
);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard,
|
||||||
|
Invalid::ProposalShardMismatch
|
||||||
|
);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
|
||||||
|
Invalid::ProposalBlockRootMismatch
|
||||||
|
);
|
||||||
|
|
||||||
|
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
verify_proposal_signature(
|
||||||
|
&proposer_slashing.proposal_1,
|
||||||
|
&proposer.pubkey,
|
||||||
|
&state.fork,
|
||||||
|
spec
|
||||||
|
),
|
||||||
|
Invalid::BadProposal1Signature
|
||||||
|
);
|
||||||
|
verify!(
|
||||||
|
verify_proposal_signature(
|
||||||
|
&proposer_slashing.proposal_2,
|
||||||
|
&proposer.pubkey,
|
||||||
|
&state.fork,
|
||||||
|
spec
|
||||||
|
),
|
||||||
|
Invalid::BadProposal2Signature
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_proposal_signature(
|
||||||
|
proposal: &Proposal,
|
||||||
|
pubkey: &PublicKey,
|
||||||
|
fork: &Fork,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> bool {
|
||||||
|
let message = proposal.signed_root();
|
||||||
|
let domain = spec.get_domain(
|
||||||
|
proposal.slot.epoch(spec.slots_per_epoch),
|
||||||
|
Domain::Proposal,
|
||||||
|
fork,
|
||||||
|
);
|
||||||
|
proposal.signature.verify(&message[..], domain, pubkey)
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
use crate::errors::{
|
||||||
|
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
|
||||||
|
};
|
||||||
|
use ssz::TreeHash;
|
||||||
|
use types::beacon_state::helpers::verify_bitfield_length;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Verify validity of ``slashable_attestation`` fields.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if all fields are valid.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_slashable_attestation(
|
||||||
|
state: &BeaconState,
|
||||||
|
slashable_attestation: &SlashableAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
||||||
|
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if slashable_attestation.validator_indices.is_empty() {
|
||||||
|
invalid!(Invalid::NoValidatorIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
||||||
|
if slashable_attestation.validator_indices[i]
|
||||||
|
>= slashable_attestation.validator_indices[i + 1]
|
||||||
|
{
|
||||||
|
invalid!(Invalid::BadValidatorIndicesOrdering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !verify_bitfield_length(
|
||||||
|
&slashable_attestation.custody_bitfield,
|
||||||
|
slashable_attestation.validator_indices.len(),
|
||||||
|
) {
|
||||||
|
invalid!(Invalid::BadCustodyBitfieldLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize
|
||||||
|
{
|
||||||
|
invalid!(Invalid::MaxIndicesExceed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this signature verification could likely be replaced with:
|
||||||
|
//
|
||||||
|
// super::validate_attestation::validate_attestation_signature(..)
|
||||||
|
|
||||||
|
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||||
|
let mut message_exists = vec![false; 2];
|
||||||
|
|
||||||
|
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
|
||||||
|
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
|
||||||
|
Ok(bit) => bit,
|
||||||
|
Err(_) => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
message_exists[custody_bit as usize] = true;
|
||||||
|
|
||||||
|
match state.validator_registry.get(*v as usize) {
|
||||||
|
Some(validator) => {
|
||||||
|
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
||||||
|
}
|
||||||
|
None => invalid!(Invalid::UnknownValidator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_0 = AttestationDataAndCustodyBit {
|
||||||
|
data: slashable_attestation.data.clone(),
|
||||||
|
custody_bit: false,
|
||||||
|
}
|
||||||
|
.hash_tree_root();
|
||||||
|
let message_1 = AttestationDataAndCustodyBit {
|
||||||
|
data: slashable_attestation.data.clone(),
|
||||||
|
custody_bit: true,
|
||||||
|
}
|
||||||
|
.hash_tree_root();
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
let mut keys = vec![];
|
||||||
|
|
||||||
|
if message_exists[0] {
|
||||||
|
messages.push(&message_0[..]);
|
||||||
|
keys.push(&aggregate_pubs[0]);
|
||||||
|
}
|
||||||
|
if message_exists[1] {
|
||||||
|
messages.push(&message_1[..]);
|
||||||
|
keys.push(&aggregate_pubs[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain = {
|
||||||
|
let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||||
|
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
||||||
|
};
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
slashable_attestation
|
||||||
|
.aggregate_signature
|
||||||
|
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||||
|
Invalid::BadSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
use crate::errors::{TransferInvalid as Invalid, TransferValidationError as Error};
|
||||||
|
use ssz::TreeHash;
|
||||||
|
use types::beacon_state::helpers::verify_bitfield_length;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Verify validity of ``slashable_attestation`` fields.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if all fields are valid.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn verify_transfer(
|
||||||
|
state: &BeaconState,
|
||||||
|
transfer: &Transfer,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// TODO: verify transfer.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -12,7 +12,7 @@ impl AttesterSlashingBuilder {
|
|||||||
/// - `validator_index: u64`
|
/// - `validator_index: u64`
|
||||||
/// - `message: &[u8]`
|
/// - `message: &[u8]`
|
||||||
/// - `epoch: Epoch`
|
/// - `epoch: Epoch`
|
||||||
/// - `domain: u64`
|
/// - `domain: Domain`
|
||||||
///
|
///
|
||||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||||
pub fn double_vote<F>(
|
pub fn double_vote<F>(
|
||||||
@ -21,7 +21,7 @@ impl AttesterSlashingBuilder {
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> AttesterSlashing
|
) -> AttesterSlashing
|
||||||
where
|
where
|
||||||
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
||||||
{
|
{
|
||||||
let double_voted_slot = Slot::new(0);
|
let double_voted_slot = Slot::new(0);
|
||||||
let shard = 0;
|
let shard = 0;
|
||||||
@ -75,12 +75,7 @@ impl AttesterSlashingBuilder {
|
|||||||
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
|
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
|
||||||
};
|
};
|
||||||
let message = attestation_data_and_custody_bit.hash_tree_root();
|
let message = attestation_data_and_custody_bit.hash_tree_root();
|
||||||
let signature = signer(
|
let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
|
||||||
*validator_index,
|
|
||||||
&message[..],
|
|
||||||
epoch,
|
|
||||||
spec.domain_attestation,
|
|
||||||
);
|
|
||||||
attestation.aggregate_signature.add(&signature);
|
attestation.aggregate_signature.add(&signature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -112,6 +112,7 @@ pub struct BeaconState {
|
|||||||
// Ethereum 1.0 chain data
|
// Ethereum 1.0 chain data
|
||||||
pub latest_eth1_data: Eth1Data,
|
pub latest_eth1_data: Eth1Data,
|
||||||
pub eth1_data_votes: Vec<Eth1DataVote>,
|
pub eth1_data_votes: Vec<Eth1DataVote>,
|
||||||
|
pub deposit_index: u64,
|
||||||
|
|
||||||
// Caching (not in the spec)
|
// Caching (not in the spec)
|
||||||
pub cache_index_offset: usize,
|
pub cache_index_offset: usize,
|
||||||
@ -187,6 +188,7 @@ impl BeaconState {
|
|||||||
*/
|
*/
|
||||||
latest_eth1_data,
|
latest_eth1_data,
|
||||||
eth1_data_votes: vec![],
|
eth1_data_votes: vec![],
|
||||||
|
deposit_index: 0,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Caching (not in spec)
|
* Caching (not in spec)
|
||||||
@ -224,6 +226,8 @@ impl BeaconState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genesis_state.deposit_index = initial_validator_deposits.len() as u64;
|
||||||
|
|
||||||
let genesis_active_index_root = hash_tree_root(get_active_validator_indices(
|
let genesis_active_index_root = hash_tree_root(get_active_validator_indices(
|
||||||
&genesis_state.validator_registry,
|
&genesis_state.validator_registry,
|
||||||
spec.genesis_epoch,
|
spec.genesis_epoch,
|
||||||
@ -566,92 +570,6 @@ impl BeaconState {
|
|||||||
.fold(0, |acc, i| acc + self.get_effective_balance(*i, spec))
|
.fold(0, |acc, i| acc + self.get_effective_balance(*i, spec))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify validity of ``slashable_attestation`` fields.
|
|
||||||
///
|
|
||||||
/// Spec v0.4.0
|
|
||||||
pub fn verify_slashable_attestation(
|
|
||||||
&self,
|
|
||||||
slashable_attestation: &SlashableAttestation,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> bool {
|
|
||||||
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if slashable_attestation.validator_indices.is_empty() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
|
||||||
if slashable_attestation.validator_indices[i]
|
|
||||||
>= slashable_attestation.validator_indices[i + 1]
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !verify_bitfield_length(
|
|
||||||
&slashable_attestation.custody_bitfield,
|
|
||||||
slashable_attestation.validator_indices.len(),
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if slashable_attestation.validator_indices.len()
|
|
||||||
> spec.max_indices_per_slashable_vote as usize
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
|
||||||
let mut message_exists = vec![false; 2];
|
|
||||||
|
|
||||||
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
|
|
||||||
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
|
|
||||||
Ok(bit) => bit,
|
|
||||||
Err(_) => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
message_exists[custody_bit as usize] = true;
|
|
||||||
|
|
||||||
match self.validator_registry.get(*v as usize) {
|
|
||||||
Some(validator) => {
|
|
||||||
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
|
|
||||||
}
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
|
||||||
data: slashable_attestation.data.clone(),
|
|
||||||
custody_bit: false,
|
|
||||||
}
|
|
||||||
.hash_tree_root();
|
|
||||||
let message_1 = AttestationDataAndCustodyBit {
|
|
||||||
data: slashable_attestation.data.clone(),
|
|
||||||
custody_bit: true,
|
|
||||||
}
|
|
||||||
.hash_tree_root();
|
|
||||||
|
|
||||||
let mut messages = vec![];
|
|
||||||
let mut keys = vec![];
|
|
||||||
|
|
||||||
if message_exists[0] {
|
|
||||||
messages.push(&message_0[..]);
|
|
||||||
keys.push(&aggregate_pubs[0]);
|
|
||||||
}
|
|
||||||
if message_exists[1] {
|
|
||||||
messages.push(&message_1[..]);
|
|
||||||
keys.push(&aggregate_pubs[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
slashable_attestation.aggregate_signature.verify_multiple(
|
|
||||||
&messages[..],
|
|
||||||
spec.domain_attestation,
|
|
||||||
&keys[..],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
||||||
///
|
///
|
||||||
/// Spec v0.4.0
|
/// Spec v0.4.0
|
||||||
@ -1163,8 +1081,11 @@ impl BeaconState {
|
|||||||
|
|
||||||
proof_of_possession.verify(
|
proof_of_possession.verify(
|
||||||
&proof_of_possession_data.hash_tree_root(),
|
&proof_of_possession_data.hash_tree_root(),
|
||||||
self.fork
|
spec.get_domain(
|
||||||
.get_domain(self.slot.epoch(spec.slots_per_epoch), spec.domain_deposit),
|
self.slot.epoch(spec.slots_per_epoch),
|
||||||
|
Domain::Deposit,
|
||||||
|
&self.fork,
|
||||||
|
),
|
||||||
&pubkey,
|
&pubkey,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1338,6 +1259,7 @@ impl Encodable for BeaconState {
|
|||||||
s.append(&self.batched_block_roots);
|
s.append(&self.batched_block_roots);
|
||||||
s.append(&self.latest_eth1_data);
|
s.append(&self.latest_eth1_data);
|
||||||
s.append(&self.eth1_data_votes);
|
s.append(&self.eth1_data_votes);
|
||||||
|
s.append(&self.deposit_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1368,6 +1290,7 @@ impl Decodable for BeaconState {
|
|||||||
let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?;
|
let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?;
|
||||||
let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?;
|
let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?;
|
||||||
let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?;
|
let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?;
|
||||||
|
let (deposit_index, i) = <_>::ssz_decode(bytes, i)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Self {
|
Self {
|
||||||
@ -1396,6 +1319,7 @@ impl Decodable for BeaconState {
|
|||||||
batched_block_roots,
|
batched_block_roots,
|
||||||
latest_eth1_data,
|
latest_eth1_data,
|
||||||
eth1_data_votes,
|
eth1_data_votes,
|
||||||
|
deposit_index,
|
||||||
cache_index_offset: 0,
|
cache_index_offset: 0,
|
||||||
caches: vec![EpochCache::empty(); CACHED_EPOCHS],
|
caches: vec![EpochCache::empty(); CACHED_EPOCHS],
|
||||||
},
|
},
|
||||||
@ -1440,6 +1364,7 @@ impl TreeHash for BeaconState {
|
|||||||
result.append(&mut self.batched_block_roots.hash_tree_root_internal());
|
result.append(&mut self.batched_block_roots.hash_tree_root_internal());
|
||||||
result.append(&mut self.latest_eth1_data.hash_tree_root_internal());
|
result.append(&mut self.latest_eth1_data.hash_tree_root_internal());
|
||||||
result.append(&mut self.eth1_data_votes.hash_tree_root_internal());
|
result.append(&mut self.eth1_data_votes.hash_tree_root_internal());
|
||||||
|
result.append(&mut self.deposit_index.hash_tree_root_internal());
|
||||||
hash(&result)
|
hash(&result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1472,6 +1397,7 @@ impl<T: RngCore> TestRandom<T> for BeaconState {
|
|||||||
batched_block_roots: <_>::random_for_test(rng),
|
batched_block_roots: <_>::random_for_test(rng),
|
||||||
latest_eth1_data: <_>::random_for_test(rng),
|
latest_eth1_data: <_>::random_for_test(rng),
|
||||||
eth1_data_votes: <_>::random_for_test(rng),
|
eth1_data_votes: <_>::random_for_test(rng),
|
||||||
|
deposit_index: <_>::random_for_test(rng),
|
||||||
cache_index_offset: 0,
|
cache_index_offset: 0,
|
||||||
caches: vec![EpochCache::empty(); CACHED_EPOCHS],
|
caches: vec![EpochCache::empty(); CACHED_EPOCHS],
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,17 @@
|
|||||||
use crate::{Address, Epoch, Hash256, Slot};
|
use crate::{Address, Epoch, Fork, Hash256, Slot};
|
||||||
use bls::Signature;
|
use bls::Signature;
|
||||||
|
|
||||||
const GWEI: u64 = 1_000_000_000;
|
const GWEI: u64 = 1_000_000_000;
|
||||||
|
|
||||||
|
pub enum Domain {
|
||||||
|
Deposit,
|
||||||
|
Attestation,
|
||||||
|
Proposal,
|
||||||
|
Exit,
|
||||||
|
Randao,
|
||||||
|
Transfer,
|
||||||
|
}
|
||||||
|
|
||||||
/// Holds all the "constants" for a BeaconChain.
|
/// Holds all the "constants" for a BeaconChain.
|
||||||
///
|
///
|
||||||
/// Spec v0.4.0
|
/// Spec v0.4.0
|
||||||
@ -85,14 +94,20 @@ pub struct ChainSpec {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Signature domains
|
* Signature domains
|
||||||
|
*
|
||||||
|
* Fields should be private to prevent accessing a domain that hasn't been modified to suit
|
||||||
|
* some `Fork`.
|
||||||
|
*
|
||||||
|
* Use `ChainSpec::get_domain(..)` to access these values.
|
||||||
*/
|
*/
|
||||||
pub domain_deposit: u64,
|
domain_deposit: u64,
|
||||||
pub domain_attestation: u64,
|
domain_attestation: u64,
|
||||||
pub domain_proposal: u64,
|
domain_proposal: u64,
|
||||||
pub domain_exit: u64,
|
domain_exit: u64,
|
||||||
pub domain_randao: u64,
|
domain_randao: u64,
|
||||||
pub domain_transfer: u64,
|
domain_transfer: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChainSpec {
|
impl ChainSpec {
|
||||||
/// Return the number of committees in one epoch.
|
/// Return the number of committees in one epoch.
|
||||||
///
|
///
|
||||||
@ -107,6 +122,23 @@ impl ChainSpec {
|
|||||||
) * self.slots_per_epoch
|
) * self.slots_per_epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the domain number that represents the fork meta and signature domain.
|
||||||
|
///
|
||||||
|
/// Spec v0.4.0
|
||||||
|
pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 {
|
||||||
|
let domain_constant = match domain {
|
||||||
|
Domain::Deposit => self.domain_deposit,
|
||||||
|
Domain::Attestation => self.domain_attestation,
|
||||||
|
Domain::Proposal => self.domain_proposal,
|
||||||
|
Domain::Exit => self.domain_exit,
|
||||||
|
Domain::Randao => self.domain_randao,
|
||||||
|
Domain::Transfer => self.domain_transfer,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fork_version = fork.get_fork_version(epoch);
|
||||||
|
fork_version * u64::pow(2, 32) + domain_constant
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
|
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
|
||||||
///
|
///
|
||||||
/// Spec v0.4.0
|
/// Spec v0.4.0
|
||||||
|
@ -24,14 +24,6 @@ impl Fork {
|
|||||||
}
|
}
|
||||||
self.current_version
|
self.current_version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the domain number that represents the fork meta and signature domain.
|
|
||||||
///
|
|
||||||
/// Spec v0.4.0
|
|
||||||
pub fn get_domain(&self, epoch: Epoch, domain_type: u64) -> u64 {
|
|
||||||
let fork_version = self.get_fork_version(epoch);
|
|
||||||
fork_version * u64::pow(2, 32) + domain_type
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -43,7 +43,7 @@ pub use crate::beacon_block_body::BeaconBlockBody;
|
|||||||
pub use crate::beacon_state::{
|
pub use crate::beacon_state::{
|
||||||
BeaconState, Error as BeaconStateError, InclusionError, RelativeEpoch,
|
BeaconState, Error as BeaconStateError, InclusionError, RelativeEpoch,
|
||||||
};
|
};
|
||||||
pub use crate::chain_spec::ChainSpec;
|
pub use crate::chain_spec::{ChainSpec, Domain};
|
||||||
pub use crate::crosslink::Crosslink;
|
pub use crate::crosslink::Crosslink;
|
||||||
pub use crate::deposit::Deposit;
|
pub use crate::deposit::Deposit;
|
||||||
pub use crate::deposit_data::DepositData;
|
pub use crate::deposit_data::DepositData;
|
||||||
|
@ -12,12 +12,12 @@ impl ProposerSlashingBuilder {
|
|||||||
/// - `validator_index: u64`
|
/// - `validator_index: u64`
|
||||||
/// - `message: &[u8]`
|
/// - `message: &[u8]`
|
||||||
/// - `epoch: Epoch`
|
/// - `epoch: Epoch`
|
||||||
/// - `domain: u64`
|
/// - `domain: Domain`
|
||||||
///
|
///
|
||||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||||
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
|
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
|
||||||
where
|
where
|
||||||
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
||||||
{
|
{
|
||||||
let slot = Slot::new(0);
|
let slot = Slot::new(0);
|
||||||
let shard = 0;
|
let shard = 0;
|
||||||
@ -39,15 +39,13 @@ impl ProposerSlashingBuilder {
|
|||||||
proposal_1.signature = {
|
proposal_1.signature = {
|
||||||
let message = proposal_1.signed_root();
|
let message = proposal_1.signed_root();
|
||||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
let epoch = slot.epoch(spec.slots_per_epoch);
|
||||||
let domain = spec.domain_proposal;
|
signer(proposer_index, &message[..], epoch, Domain::Proposal)
|
||||||
signer(proposer_index, &message[..], epoch, domain)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
proposal_2.signature = {
|
proposal_2.signature = {
|
||||||
let message = proposal_2.signed_root();
|
let message = proposal_2.signed_root();
|
||||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
let epoch = slot.epoch(spec.slots_per_epoch);
|
||||||
let domain = spec.domain_proposal;
|
signer(proposer_index, &message[..], epoch, Domain::Proposal)
|
||||||
signer(proposer_index, &message[..], epoch, domain)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProposerSlashing {
|
ProposerSlashing {
|
||||||
|
Loading…
Reference in New Issue
Block a user