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]
|
||||
criterion = "0.2"
|
||||
test_harness = { path = "../../beacon_node/beacon_chain/test_harness" }
|
||||
env_logger = "0.6.0"
|
||||
|
||||
[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;
|
||||
|
||||
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)]
|
||||
pub enum AttestationValidationError {
|
||||
/// The `Attestation` is invalid.
|
||||
Invalid(AttestationInvalid),
|
||||
ProcessingError(BeaconStateError),
|
||||
/// Encountered a `BeaconStateError` whilst attempting to determine validity.
|
||||
BeaconStateError(BeaconStateError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -23,8 +112,132 @@ pub enum AttestationInvalid {
|
||||
ShardBlockRootNotZero,
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for AttestationValidationError {
|
||||
fn from(e: BeaconStateError) -> AttestationValidationError {
|
||||
AttestationValidationError::ProcessingError(e)
|
||||
impl_from_beacon_state_error!(AttestationValidationError);
|
||||
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
||||
|
||||
/*
|
||||
* `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]
|
||||
mod macros;
|
||||
mod block_processable;
|
||||
pub mod per_block_processing;
|
||||
// mod epoch_processable;
|
||||
mod errors;
|
||||
pub mod errors;
|
||||
// mod slot_processable;
|
||||
|
||||
pub use block_processable::{
|
||||
validate_attestation, validate_attestation_without_signature, BlockProcessable,
|
||||
Error as BlockProcessingError,
|
||||
pub use errors::{BlockInvalid, BlockProcessingError};
|
||||
pub use per_block_processing::{
|
||||
per_block_processing, per_block_processing_without_verifying_block_signature,
|
||||
};
|
||||
// pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError};
|
||||
// pub use slot_processable::{Error as SlotProcessingError, SlotProcessable};
|
||||
|
@ -1,8 +1,13 @@
|
||||
#[macro_use]
|
||||
macro_rules! ensure {
|
||||
($condition: expr, $result: ident) => {
|
||||
macro_rules! verify {
|
||||
($condition: expr, $result: expr) => {
|
||||
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::*;
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
@ -48,7 +27,7 @@ pub fn validate_attestation_without_signature(
|
||||
|
||||
/// Validate an attestation, optionally checking the aggregate signature.
|
||||
///
|
||||
/// Spec v0.2.0
|
||||
/// Spec v0.4.0
|
||||
fn validate_attestation_signature_optional(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
@ -56,38 +35,41 @@ fn validate_attestation_signature_optional(
|
||||
verify_signature: bool,
|
||||
) -> Result<(), Error> {
|
||||
// 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`.
|
||||
ensure!(
|
||||
verify!(
|
||||
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
IncludedTooEarly
|
||||
Invalid::IncludedTooEarly
|
||||
);
|
||||
|
||||
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
|
||||
ensure!(
|
||||
verify!(
|
||||
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
|
||||
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
|
||||
// state.previous_justified_epoch`.
|
||||
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
|
||||
ensure!(
|
||||
verify!(
|
||||
attestation.data.justified_epoch == state.justified_epoch,
|
||||
WrongJustifiedSlot
|
||||
Invalid::WrongJustifiedSlot
|
||||
);
|
||||
} else {
|
||||
ensure!(
|
||||
verify!(
|
||||
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,
|
||||
// get_epoch_start_slot(attestation.data.justified_epoch))`.
|
||||
ensure!(
|
||||
verify!(
|
||||
attestation.data.justified_block_root
|
||||
== *state
|
||||
.get_block_root(
|
||||
@ -98,7 +80,7 @@ fn validate_attestation_signature_optional(
|
||||
&spec
|
||||
)
|
||||
.ok_or(BeaconStateError::InsufficientBlockRoots)?,
|
||||
WrongJustifiedRoot
|
||||
Invalid::WrongJustifiedRoot
|
||||
);
|
||||
|
||||
// Verify that either:
|
||||
@ -112,59 +94,61 @@ fn validate_attestation_signature_optional(
|
||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
||||
};
|
||||
ensure!(
|
||||
verify!(
|
||||
(attestation.data.latest_crosslink
|
||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
||||
BadLatestCrosslinkRoot
|
||||
Invalid::BadLatestCrosslinkRoot
|
||||
);
|
||||
|
||||
// Get the committee for this attestation
|
||||
let (committee, _shard) = state
|
||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||
.iter()
|
||||
.find(|(committee, shard)| *shard == attestation.data.shard)
|
||||
.find(|(_committee, shard)| *shard == attestation.data.shard)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?;
|
||||
|
||||
// Custody bitfield is all zeros (phase 0 requirement).
|
||||
ensure!(
|
||||
verify!(
|
||||
attestation.custody_bitfield.num_set_bits() == 0,
|
||||
CustodyBitfieldHasSetBits
|
||||
Invalid::CustodyBitfieldHasSetBits
|
||||
);
|
||||
// Custody bitfield length is correct.
|
||||
ensure!(
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
BadCustodyBitfieldLength
|
||||
Invalid::BadCustodyBitfieldLength
|
||||
);
|
||||
// Aggregation bitfield isn't empty.
|
||||
ensure!(
|
||||
verify!(
|
||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
||||
AggregationBitfieldIsEmpty
|
||||
Invalid::AggregationBitfieldIsEmpty
|
||||
);
|
||||
// Aggregation bitfield length is correct.
|
||||
ensure!(
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
BadAggregationBitfieldLength
|
||||
Invalid::BadAggregationBitfieldLength
|
||||
);
|
||||
|
||||
if verify_signature {
|
||||
ensure!(
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
verify!(
|
||||
verify_attestation_signature(
|
||||
state,
|
||||
committee,
|
||||
attestation_epoch,
|
||||
&attestation.custody_bitfield,
|
||||
&attestation.data,
|
||||
&attestation.aggregate_signature,
|
||||
spec
|
||||
),
|
||||
BadSignature
|
||||
Invalid::BadSignature
|
||||
);
|
||||
}
|
||||
|
||||
// [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,
|
||||
ShardBlockRootNotZero
|
||||
Invalid::ShardBlockRootNotZero
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@ -177,9 +161,12 @@ fn validate_attestation_signature_optional(
|
||||
/// - `aggregate_signature` was not signed correctly.
|
||||
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
||||
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
fn verify_attestation_signature(
|
||||
state: &BeaconState,
|
||||
committee: &[usize],
|
||||
attestation_epoch: Epoch,
|
||||
custody_bitfield: &Bitfield,
|
||||
attestation_data: &AttestationData,
|
||||
aggregate_signature: &AggregateSignature,
|
||||
@ -234,5 +221,7 @@ fn verify_attestation_signature(
|
||||
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`
|
||||
/// - `message: &[u8]`
|
||||
/// - `epoch: Epoch`
|
||||
/// - `domain: u64`
|
||||
/// - `domain: Domain`
|
||||
///
|
||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||
pub fn double_vote<F>(
|
||||
@ -21,7 +21,7 @@ impl AttesterSlashingBuilder {
|
||||
spec: &ChainSpec,
|
||||
) -> AttesterSlashing
|
||||
where
|
||||
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
||||
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
||||
{
|
||||
let double_voted_slot = Slot::new(0);
|
||||
let shard = 0;
|
||||
@ -75,12 +75,7 @@ impl AttesterSlashingBuilder {
|
||||
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
|
||||
};
|
||||
let message = attestation_data_and_custody_bit.hash_tree_root();
|
||||
let signature = signer(
|
||||
*validator_index,
|
||||
&message[..],
|
||||
epoch,
|
||||
spec.domain_attestation,
|
||||
);
|
||||
let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
|
||||
attestation.aggregate_signature.add(&signature);
|
||||
}
|
||||
};
|
||||
|
@ -112,6 +112,7 @@ pub struct BeaconState {
|
||||
// Ethereum 1.0 chain data
|
||||
pub latest_eth1_data: Eth1Data,
|
||||
pub eth1_data_votes: Vec<Eth1DataVote>,
|
||||
pub deposit_index: u64,
|
||||
|
||||
// Caching (not in the spec)
|
||||
pub cache_index_offset: usize,
|
||||
@ -187,6 +188,7 @@ impl BeaconState {
|
||||
*/
|
||||
latest_eth1_data,
|
||||
eth1_data_votes: vec![],
|
||||
deposit_index: 0,
|
||||
|
||||
/*
|
||||
* 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(
|
||||
&genesis_state.validator_registry,
|
||||
spec.genesis_epoch,
|
||||
@ -566,92 +570,6 @@ impl BeaconState {
|
||||
.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.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
@ -1163,8 +1081,11 @@ impl BeaconState {
|
||||
|
||||
proof_of_possession.verify(
|
||||
&proof_of_possession_data.hash_tree_root(),
|
||||
self.fork
|
||||
.get_domain(self.slot.epoch(spec.slots_per_epoch), spec.domain_deposit),
|
||||
spec.get_domain(
|
||||
self.slot.epoch(spec.slots_per_epoch),
|
||||
Domain::Deposit,
|
||||
&self.fork,
|
||||
),
|
||||
&pubkey,
|
||||
)
|
||||
}
|
||||
@ -1338,6 +1259,7 @@ impl Encodable for BeaconState {
|
||||
s.append(&self.batched_block_roots);
|
||||
s.append(&self.latest_eth1_data);
|
||||
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 (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?;
|
||||
let (deposit_index, i) = <_>::ssz_decode(bytes, i)?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
@ -1396,6 +1319,7 @@ impl Decodable for BeaconState {
|
||||
batched_block_roots,
|
||||
latest_eth1_data,
|
||||
eth1_data_votes,
|
||||
deposit_index,
|
||||
cache_index_offset: 0,
|
||||
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.latest_eth1_data.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)
|
||||
}
|
||||
}
|
||||
@ -1472,6 +1397,7 @@ impl<T: RngCore> TestRandom<T> for BeaconState {
|
||||
batched_block_roots: <_>::random_for_test(rng),
|
||||
latest_eth1_data: <_>::random_for_test(rng),
|
||||
eth1_data_votes: <_>::random_for_test(rng),
|
||||
deposit_index: <_>::random_for_test(rng),
|
||||
cache_index_offset: 0,
|
||||
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;
|
||||
|
||||
const GWEI: u64 = 1_000_000_000;
|
||||
|
||||
pub enum Domain {
|
||||
Deposit,
|
||||
Attestation,
|
||||
Proposal,
|
||||
Exit,
|
||||
Randao,
|
||||
Transfer,
|
||||
}
|
||||
|
||||
/// Holds all the "constants" for a BeaconChain.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
@ -85,14 +94,20 @@ pub struct ChainSpec {
|
||||
|
||||
/*
|
||||
* 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,
|
||||
pub domain_attestation: u64,
|
||||
pub domain_proposal: u64,
|
||||
pub domain_exit: u64,
|
||||
pub domain_randao: u64,
|
||||
pub domain_transfer: u64,
|
||||
domain_deposit: u64,
|
||||
domain_attestation: u64,
|
||||
domain_proposal: u64,
|
||||
domain_exit: u64,
|
||||
domain_randao: u64,
|
||||
domain_transfer: u64,
|
||||
}
|
||||
|
||||
impl ChainSpec {
|
||||
/// Return the number of committees in one epoch.
|
||||
///
|
||||
@ -107,6 +122,23 @@ impl ChainSpec {
|
||||
) * 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.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
|
@ -24,14 +24,6 @@ impl Fork {
|
||||
}
|
||||
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)]
|
||||
|
@ -43,7 +43,7 @@ pub use crate::beacon_block_body::BeaconBlockBody;
|
||||
pub use crate::beacon_state::{
|
||||
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::deposit::Deposit;
|
||||
pub use crate::deposit_data::DepositData;
|
||||
|
@ -12,12 +12,12 @@ impl ProposerSlashingBuilder {
|
||||
/// - `validator_index: u64`
|
||||
/// - `message: &[u8]`
|
||||
/// - `epoch: Epoch`
|
||||
/// - `domain: u64`
|
||||
/// - `domain: Domain`
|
||||
///
|
||||
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
|
||||
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
|
||||
where
|
||||
F: Fn(u64, &[u8], Epoch, u64) -> Signature,
|
||||
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
||||
{
|
||||
let slot = Slot::new(0);
|
||||
let shard = 0;
|
||||
@ -39,15 +39,13 @@ impl ProposerSlashingBuilder {
|
||||
proposal_1.signature = {
|
||||
let message = proposal_1.signed_root();
|
||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
||||
let domain = spec.domain_proposal;
|
||||
signer(proposer_index, &message[..], epoch, domain)
|
||||
signer(proposer_index, &message[..], epoch, Domain::Proposal)
|
||||
};
|
||||
|
||||
proposal_2.signature = {
|
||||
let message = proposal_2.signed_root();
|
||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
||||
let domain = spec.domain_proposal;
|
||||
signer(proposer_index, &message[..], epoch, domain)
|
||||
signer(proposer_index, &message[..], epoch, Domain::Proposal)
|
||||
};
|
||||
|
||||
ProposerSlashing {
|
||||
|
Loading…
Reference in New Issue
Block a user