mirror of
https://github.com/sigp/lighthouse.git
synced 2026-07-03 12:54:27 +00:00
Merge remote-tracking branch 'origin/unstable' into tree-states
This commit is contained in:
@@ -8,10 +8,13 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
types = { path = "../types" }
|
||||
state_processing = { path = "../state_processing" }
|
||||
proto_array = { path = "../proto_array" }
|
||||
eth2_ssz = "0.4.1"
|
||||
eth2_ssz_derive = "0.3.0"
|
||||
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||
|
||||
[dev-dependencies]
|
||||
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||
store = { path = "../../beacon_node/store" }
|
||||
tokio = { version = "1.14.0", features = ["rt-multi-thread"] }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, ExecPayload, Hash256, Slot};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt::Debug;
|
||||
use types::{BeaconBlockRef, BeaconState, Checkpoint, EthSpec, ExecPayload, Hash256, Slot};
|
||||
|
||||
/// Approximates the `Store` in "Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice":
|
||||
///
|
||||
@@ -17,7 +19,7 @@ use types::{BeaconBlock, BeaconState, Checkpoint, EthSpec, ExecPayload, Hash256,
|
||||
/// concrete struct is to allow this crate to be free from "impure" on-disk database logic,
|
||||
/// hopefully making auditing easier.
|
||||
pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
type Error;
|
||||
type Error: Debug;
|
||||
|
||||
/// Returns the last value passed to `Self::set_current_slot`.
|
||||
fn get_current_slot(&self) -> Slot;
|
||||
@@ -33,7 +35,7 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// choice. Allows the implementer to performing caching or other housekeeping duties.
|
||||
fn on_verified_block<Payload: ExecPayload<T>>(
|
||||
&mut self,
|
||||
block: &BeaconBlock<T, Payload>,
|
||||
block: BeaconBlockRef<T, Payload>,
|
||||
block_root: Hash256,
|
||||
state: &BeaconState<T>,
|
||||
) -> Result<(), Self::Error>;
|
||||
@@ -50,6 +52,12 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// Returns the `finalized_checkpoint`.
|
||||
fn finalized_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `unrealized_justified_checkpoint`.
|
||||
fn unrealized_justified_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `unrealized_finalized_checkpoint`.
|
||||
fn unrealized_finalized_checkpoint(&self) -> &Checkpoint;
|
||||
|
||||
/// Returns the `proposer_boost_root`.
|
||||
fn proposer_boost_root(&self) -> Hash256;
|
||||
|
||||
@@ -62,6 +70,18 @@ pub trait ForkChoiceStore<T: EthSpec>: Sized {
|
||||
/// Sets the `best_justified_checkpoint`.
|
||||
fn set_best_justified_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the `unrealized_justified_checkpoint`.
|
||||
fn set_unrealized_justified_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the `unrealized_finalized_checkpoint`.
|
||||
fn set_unrealized_finalized_checkpoint(&mut self, checkpoint: Checkpoint);
|
||||
|
||||
/// Sets the proposer boost root.
|
||||
fn set_proposer_boost_root(&mut self, proposer_boost_root: Hash256);
|
||||
|
||||
/// Gets the equivocating indices.
|
||||
fn equivocating_indices(&self) -> &BTreeSet<u64>;
|
||||
|
||||
/// Adds to the set of equivocating indices.
|
||||
fn extend_equivocating_indices(&mut self, indices: impl IntoIterator<Item = u64>);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ mod fork_choice;
|
||||
mod fork_choice_store;
|
||||
|
||||
pub use crate::fork_choice::{
|
||||
AttestationFromBlock, Error, ForkChoice, InvalidAttestation, InvalidBlock,
|
||||
PayloadVerificationStatus, PersistedForkChoice, QueuedAttestation,
|
||||
AttestationFromBlock, CountUnrealized, Error, ForkChoice, ForkChoiceView,
|
||||
ForkchoiceUpdateParameters, InvalidAttestation, InvalidBlock, PayloadVerificationStatus,
|
||||
PersistedForkChoice, QueuedAttestation, ResetPayloadStatuses,
|
||||
};
|
||||
pub use fork_choice_store::ForkChoiceStore;
|
||||
pub use proto_array::{Block as ProtoBlock, ExecutionStatus, InvalidationOperation};
|
||||
pub use proto_array::{
|
||||
Block as ProtoBlock, CountUnrealizedFull, ExecutionStatus, InvalidationOperation,
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
use types::{Checkpoint, Epoch, ExecutionBlockHash, Hash256};
|
||||
use types::{Checkpoint, Epoch, ExecutionBlockHash, Hash256, Slot};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum Error {
|
||||
@@ -52,6 +52,7 @@ pub enum Error {
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct InvalidBestNodeInfo {
|
||||
pub current_slot: Slot,
|
||||
pub start_root: Hash256,
|
||||
pub justified_checkpoint: Checkpoint,
|
||||
pub finalized_checkpoint: Checkpoint,
|
||||
|
||||
@@ -3,9 +3,11 @@ mod ffg_updates;
|
||||
mod no_votes;
|
||||
mod votes;
|
||||
|
||||
use crate::proto_array::CountUnrealizedFull;
|
||||
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
use crate::InvalidationOperation;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use types::{
|
||||
AttestationShufflingId, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
|
||||
MainnetEthSpec, Slot,
|
||||
@@ -78,7 +80,7 @@ impl ForkChoiceTestDefinition {
|
||||
|
||||
let junk_shuffling_id =
|
||||
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
|
||||
let mut fork_choice = ProtoArrayForkChoice::new(
|
||||
let mut fork_choice = ProtoArrayForkChoice::new::<MainnetEthSpec>(
|
||||
self.finalized_block_slot,
|
||||
Hash256::zero(),
|
||||
self.justified_checkpoint,
|
||||
@@ -86,8 +88,10 @@ impl ForkChoiceTestDefinition {
|
||||
junk_shuffling_id.clone(),
|
||||
junk_shuffling_id,
|
||||
ExecutionStatus::Optimistic(ExecutionBlockHash::zero()),
|
||||
CountUnrealizedFull::default(),
|
||||
)
|
||||
.expect("should create fork choice struct");
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for (op_index, op) in self.operations.into_iter().enumerate() {
|
||||
match op.clone() {
|
||||
@@ -103,9 +107,10 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
Hash256::zero(),
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
)
|
||||
.map_err(|e| e)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("find_head op at index {} returned error {}", op_index, e)
|
||||
});
|
||||
@@ -130,9 +135,10 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
proposer_boost_root,
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
)
|
||||
.map_err(|e| e)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("find_head op at index {} returned error {}", op_index, e)
|
||||
});
|
||||
@@ -154,6 +160,8 @@ impl ForkChoiceTestDefinition {
|
||||
finalized_checkpoint,
|
||||
&justified_state_balances,
|
||||
Hash256::zero(),
|
||||
&equivocating_indices,
|
||||
Slot::new(0),
|
||||
&spec,
|
||||
);
|
||||
|
||||
@@ -192,13 +200,17 @@ impl ForkChoiceTestDefinition {
|
||||
execution_status: ExecutionStatus::Optimistic(
|
||||
ExecutionBlockHash::from_root(root),
|
||||
),
|
||||
unrealized_justified_checkpoint: None,
|
||||
unrealized_finalized_checkpoint: None,
|
||||
};
|
||||
fork_choice.process_block(block).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"process_block op at index {} returned error: {:?}",
|
||||
op_index, e
|
||||
)
|
||||
});
|
||||
fork_choice
|
||||
.process_block::<MainnetEthSpec>(block, slot)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"process_block op at index {} returned error: {:?}",
|
||||
op_index, e
|
||||
)
|
||||
});
|
||||
check_bytes_round_trip(&fork_choice);
|
||||
}
|
||||
Operation::ProcessAttestation {
|
||||
@@ -286,8 +298,8 @@ fn get_checkpoint(i: u64) -> Checkpoint {
|
||||
|
||||
fn check_bytes_round_trip(original: &ProtoArrayForkChoice) {
|
||||
let bytes = original.as_bytes();
|
||||
let decoded =
|
||||
ProtoArrayForkChoice::from_bytes(&bytes).expect("fork choice should decode from bytes");
|
||||
let decoded = ProtoArrayForkChoice::from_bytes(&bytes, CountUnrealizedFull::default())
|
||||
.expect("fork choice should decode from bytes");
|
||||
assert!(
|
||||
*original == decoded,
|
||||
"fork choice should encode and decode without change"
|
||||
|
||||
@@ -4,7 +4,7 @@ mod proto_array;
|
||||
mod proto_array_fork_choice;
|
||||
mod ssz_container;
|
||||
|
||||
pub use crate::proto_array::InvalidationOperation;
|
||||
pub use crate::proto_array::{CountUnrealizedFull, InvalidationOperation};
|
||||
pub use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
|
||||
pub use error::Error;
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ four_byte_option_impl!(four_byte_option_usize, usize);
|
||||
four_byte_option_impl!(four_byte_option_checkpoint, Checkpoint);
|
||||
|
||||
/// Defines an operation which may invalidate the `execution_status` of some nodes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InvalidationOperation {
|
||||
/// Invalidate only `block_root` and it's descendants. Don't invalidate any ancestors.
|
||||
InvalidateOne { block_root: Hash256 },
|
||||
@@ -96,6 +97,10 @@ pub struct ProtoNode {
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
#[ssz(with = "four_byte_option_checkpoint")]
|
||||
pub unrealized_justified_checkpoint: Option<Checkpoint>,
|
||||
#[ssz(with = "four_byte_option_checkpoint")]
|
||||
pub unrealized_finalized_checkpoint: Option<Checkpoint>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Encode, Decode, Serialize, Deserialize, Copy, Clone)]
|
||||
@@ -113,6 +118,24 @@ impl Default for ProposerBoost {
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicate whether we should strictly count unrealized justification/finalization votes.
|
||||
#[derive(Default, PartialEq, Eq, Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
pub enum CountUnrealizedFull {
|
||||
True,
|
||||
#[default]
|
||||
False,
|
||||
}
|
||||
|
||||
impl From<bool> for CountUnrealizedFull {
|
||||
fn from(b: bool) -> Self {
|
||||
if b {
|
||||
CountUnrealizedFull::True
|
||||
} else {
|
||||
CountUnrealizedFull::False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ProtoArray {
|
||||
/// Do not attempt to prune the tree unless it has at least this many nodes. Small prunes
|
||||
@@ -123,6 +146,7 @@ pub struct ProtoArray {
|
||||
pub nodes: Vec<ProtoNode>,
|
||||
pub indices: HashMap<Hash256, usize>,
|
||||
pub previous_proposer_boost: ProposerBoost,
|
||||
pub count_unrealized_full: CountUnrealizedFull,
|
||||
}
|
||||
|
||||
impl ProtoArray {
|
||||
@@ -139,6 +163,7 @@ impl ProtoArray {
|
||||
/// - Compare the current node with the parents best-child, updating it if the current node
|
||||
/// should become the best child.
|
||||
/// - If required, update the parents best-descendant with the current node or its best-descendant.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn apply_score_changes<E: EthSpec>(
|
||||
&mut self,
|
||||
mut deltas: Vec<i64>,
|
||||
@@ -146,6 +171,7 @@ impl ProtoArray {
|
||||
finalized_checkpoint: Checkpoint,
|
||||
new_balances: &[u64],
|
||||
proposer_boost_root: Hash256,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if deltas.len() != self.indices.len() {
|
||||
@@ -240,7 +266,7 @@ impl ProtoArray {
|
||||
// not exist.
|
||||
node.weight = node
|
||||
.weight
|
||||
.checked_sub(node_delta.abs() as u64)
|
||||
.checked_sub(node_delta.unsigned_abs())
|
||||
.ok_or(Error::DeltaOverflow(node_index))?;
|
||||
} else {
|
||||
node.weight = node
|
||||
@@ -279,7 +305,11 @@ impl ProtoArray {
|
||||
|
||||
// If the node has a parent, try to update its best-child and best-descendant.
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
|
||||
self.maybe_update_best_child_and_descendant::<E>(
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +319,7 @@ impl ProtoArray {
|
||||
/// Register a block with the fork choice.
|
||||
///
|
||||
/// It is only sane to supply a `None` parent for the genesis block.
|
||||
pub fn on_block(&mut self, block: Block) -> Result<(), Error> {
|
||||
pub fn on_block<E: EthSpec>(&mut self, block: Block, current_slot: Slot) -> Result<(), Error> {
|
||||
// If the block is already known, simply ignore it.
|
||||
if self.indices.contains_key(&block.root) {
|
||||
return Ok(());
|
||||
@@ -313,6 +343,8 @@ impl ProtoArray {
|
||||
best_child: None,
|
||||
best_descendant: None,
|
||||
execution_status: block.execution_status,
|
||||
unrealized_justified_checkpoint: block.unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint,
|
||||
};
|
||||
|
||||
// If the parent has an invalid execution status, return an error before adding the block to
|
||||
@@ -334,7 +366,11 @@ impl ProtoArray {
|
||||
self.nodes.push(node.clone());
|
||||
|
||||
if let Some(parent_index) = node.parent {
|
||||
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
|
||||
self.maybe_update_best_child_and_descendant::<E>(
|
||||
parent_index,
|
||||
node_index,
|
||||
current_slot,
|
||||
)?;
|
||||
|
||||
if matches!(block.execution_status, ExecutionStatus::Valid(_)) {
|
||||
self.propagate_execution_payload_validation_by_index(parent_index)?;
|
||||
@@ -490,9 +526,6 @@ impl ProtoArray {
|
||||
node.best_descendant = None
|
||||
}
|
||||
|
||||
// It might be new knowledge that this block is valid, ensure that it and all
|
||||
// ancestors are marked as valid.
|
||||
self.propagate_execution_payload_validation_by_index(index)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -606,7 +639,11 @@ impl ProtoArray {
|
||||
/// been called without a subsequent `Self::apply_score_changes` call. This is because
|
||||
/// `on_new_block` does not attempt to walk backwards through the tree and update the
|
||||
/// best-child/best-descendant links.
|
||||
pub fn find_head(&self, justified_root: &Hash256) -> Result<Hash256, Error> {
|
||||
pub fn find_head<E: EthSpec>(
|
||||
&self,
|
||||
justified_root: &Hash256,
|
||||
current_slot: Slot,
|
||||
) -> Result<Hash256, Error> {
|
||||
let justified_index = self
|
||||
.indices
|
||||
.get(justified_root)
|
||||
@@ -639,8 +676,9 @@ impl ProtoArray {
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
// Perform a sanity check that the node is indeed valid to be the head.
|
||||
if !self.node_is_viable_for_head(best_node) {
|
||||
if !self.node_is_viable_for_head::<E>(best_node, current_slot) {
|
||||
return Err(Error::InvalidBestNode(Box::new(InvalidBestNodeInfo {
|
||||
current_slot,
|
||||
start_root: *justified_root,
|
||||
justified_checkpoint: self.justified_checkpoint,
|
||||
finalized_checkpoint: self.finalized_checkpoint,
|
||||
@@ -735,10 +773,11 @@ impl ProtoArray {
|
||||
/// best-descendant.
|
||||
/// - The child is not the best child but becomes the best child.
|
||||
/// - The child is not the best child and does not become the best child.
|
||||
fn maybe_update_best_child_and_descendant(
|
||||
fn maybe_update_best_child_and_descendant<E: EthSpec>(
|
||||
&mut self,
|
||||
parent_index: usize,
|
||||
child_index: usize,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), Error> {
|
||||
let child = self
|
||||
.nodes
|
||||
@@ -750,7 +789,8 @@ impl ProtoArray {
|
||||
.get(parent_index)
|
||||
.ok_or(Error::InvalidNodeIndex(parent_index))?;
|
||||
|
||||
let child_leads_to_viable_head = self.node_leads_to_viable_head(child)?;
|
||||
let child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(child, current_slot)?;
|
||||
|
||||
// These three variables are aliases to the three options that we may set the
|
||||
// `parent.best_child` and `parent.best_descendant` to.
|
||||
@@ -763,54 +803,54 @@ impl ProtoArray {
|
||||
);
|
||||
let no_change = (parent.best_child, parent.best_descendant);
|
||||
|
||||
let (new_best_child, new_best_descendant) = if let Some(best_child_index) =
|
||||
parent.best_child
|
||||
{
|
||||
if best_child_index == child_index && !child_leads_to_viable_head {
|
||||
// If the child is already the best-child of the parent but it's not viable for
|
||||
// the head, remove it.
|
||||
change_to_none
|
||||
} else if best_child_index == child_index {
|
||||
// If the child is the best-child already, set it again to ensure that the
|
||||
// best-descendant of the parent is updated.
|
||||
change_to_child
|
||||
} else {
|
||||
let best_child = self
|
||||
.nodes
|
||||
.get(best_child_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_child_index))?;
|
||||
|
||||
let best_child_leads_to_viable_head = self.node_leads_to_viable_head(best_child)?;
|
||||
|
||||
if child_leads_to_viable_head && !best_child_leads_to_viable_head {
|
||||
// The child leads to a viable head, but the current best-child doesn't.
|
||||
let (new_best_child, new_best_descendant) =
|
||||
if let Some(best_child_index) = parent.best_child {
|
||||
if best_child_index == child_index && !child_leads_to_viable_head {
|
||||
// If the child is already the best-child of the parent but it's not viable for
|
||||
// the head, remove it.
|
||||
change_to_none
|
||||
} else if best_child_index == child_index {
|
||||
// If the child is the best-child already, set it again to ensure that the
|
||||
// best-descendant of the parent is updated.
|
||||
change_to_child
|
||||
} else if !child_leads_to_viable_head && best_child_leads_to_viable_head {
|
||||
// The best child leads to a viable head, but the child doesn't.
|
||||
no_change
|
||||
} else if child.weight == best_child.weight {
|
||||
// Tie-breaker of equal weights by root.
|
||||
if child.root >= best_child.root {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
} else {
|
||||
// Choose the winner by weight.
|
||||
if child.weight >= best_child.weight {
|
||||
let best_child = self
|
||||
.nodes
|
||||
.get(best_child_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_child_index))?;
|
||||
|
||||
let best_child_leads_to_viable_head =
|
||||
self.node_leads_to_viable_head::<E>(best_child, current_slot)?;
|
||||
|
||||
if child_leads_to_viable_head && !best_child_leads_to_viable_head {
|
||||
// The child leads to a viable head, but the current best-child doesn't.
|
||||
change_to_child
|
||||
} else {
|
||||
} else if !child_leads_to_viable_head && best_child_leads_to_viable_head {
|
||||
// The best child leads to a viable head, but the child doesn't.
|
||||
no_change
|
||||
} else if child.weight == best_child.weight {
|
||||
// Tie-breaker of equal weights by root.
|
||||
if child.root >= best_child.root {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
} else {
|
||||
// Choose the winner by weight.
|
||||
if child.weight >= best_child.weight {
|
||||
change_to_child
|
||||
} else {
|
||||
no_change
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if child_leads_to_viable_head {
|
||||
// There is no current best-child and the child is viable.
|
||||
change_to_child
|
||||
} else {
|
||||
// There is no current best-child but the child is not viable.
|
||||
no_change
|
||||
};
|
||||
} else if child_leads_to_viable_head {
|
||||
// There is no current best-child and the child is viable.
|
||||
change_to_child
|
||||
} else {
|
||||
// There is no current best-child but the child is not viable.
|
||||
no_change
|
||||
};
|
||||
|
||||
let parent = self
|
||||
.nodes
|
||||
@@ -825,7 +865,11 @@ impl ProtoArray {
|
||||
|
||||
/// Indicates if the node itself is viable for the head, or if it's best descendant is viable
|
||||
/// for the head.
|
||||
fn node_leads_to_viable_head(&self, node: &ProtoNode) -> Result<bool, Error> {
|
||||
fn node_leads_to_viable_head<E: EthSpec>(
|
||||
&self,
|
||||
node: &ProtoNode,
|
||||
current_slot: Slot,
|
||||
) -> Result<bool, Error> {
|
||||
let best_descendant_is_viable_for_head =
|
||||
if let Some(best_descendant_index) = node.best_descendant {
|
||||
let best_descendant = self
|
||||
@@ -833,12 +877,13 @@ impl ProtoArray {
|
||||
.get(best_descendant_index)
|
||||
.ok_or(Error::InvalidBestDescendant(best_descendant_index))?;
|
||||
|
||||
self.node_is_viable_for_head(best_descendant)
|
||||
self.node_is_viable_for_head::<E>(best_descendant, current_slot)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(best_descendant_is_viable_for_head || self.node_is_viable_for_head(node))
|
||||
Ok(best_descendant_is_viable_for_head
|
||||
|| self.node_is_viable_for_head::<E>(node, current_slot))
|
||||
}
|
||||
|
||||
/// This is the equivalent to the `filter_block_tree` function in the eth2 spec:
|
||||
@@ -847,18 +892,58 @@ impl ProtoArray {
|
||||
///
|
||||
/// Any node that has a different finalized or justified epoch should not be viable for the
|
||||
/// head.
|
||||
fn node_is_viable_for_head(&self, node: &ProtoNode) -> bool {
|
||||
fn node_is_viable_for_head<E: EthSpec>(&self, node: &ProtoNode, current_slot: Slot) -> bool {
|
||||
if node.execution_status.is_invalid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let (Some(node_justified_checkpoint), Some(node_finalized_checkpoint)) =
|
||||
let genesis_epoch = Epoch::new(0);
|
||||
|
||||
let checkpoint_match_predicate =
|
||||
|node_justified_checkpoint: Checkpoint, node_finalized_checkpoint: Checkpoint| {
|
||||
let correct_justified = node_justified_checkpoint == self.justified_checkpoint
|
||||
|| self.justified_checkpoint.epoch == genesis_epoch;
|
||||
let correct_finalized = node_finalized_checkpoint == self.finalized_checkpoint
|
||||
|| self.finalized_checkpoint.epoch == genesis_epoch;
|
||||
correct_justified && correct_finalized
|
||||
};
|
||||
|
||||
if let (
|
||||
Some(unrealized_justified_checkpoint),
|
||||
Some(unrealized_finalized_checkpoint),
|
||||
Some(justified_checkpoint),
|
||||
Some(finalized_checkpoint),
|
||||
) = (
|
||||
node.unrealized_justified_checkpoint,
|
||||
node.unrealized_finalized_checkpoint,
|
||||
node.justified_checkpoint,
|
||||
node.finalized_checkpoint,
|
||||
) {
|
||||
let current_epoch = current_slot.epoch(E::slots_per_epoch());
|
||||
|
||||
// If previous epoch is justified, pull up all tips to at least the previous epoch
|
||||
if CountUnrealizedFull::True == self.count_unrealized_full
|
||||
&& (current_epoch > genesis_epoch
|
||||
&& self.justified_checkpoint.epoch + 1 == current_epoch)
|
||||
{
|
||||
unrealized_justified_checkpoint.epoch + 1 >= current_epoch
|
||||
// If previous epoch is not justified, pull up only tips from past epochs up to the current epoch
|
||||
} else {
|
||||
// If block is from a previous epoch, filter using unrealized justification & finalization information
|
||||
if node.slot.epoch(E::slots_per_epoch()) < current_epoch {
|
||||
checkpoint_match_predicate(
|
||||
unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint,
|
||||
)
|
||||
// If block is from the current epoch, filter using the head state's justification & finalization information
|
||||
} else {
|
||||
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
|
||||
}
|
||||
}
|
||||
} else if let (Some(justified_checkpoint), Some(finalized_checkpoint)) =
|
||||
(node.justified_checkpoint, node.finalized_checkpoint)
|
||||
{
|
||||
(node_justified_checkpoint == self.justified_checkpoint
|
||||
|| self.justified_checkpoint.epoch == Epoch::new(0))
|
||||
&& (node_finalized_checkpoint == self.finalized_checkpoint
|
||||
|| self.finalized_checkpoint.epoch == Epoch::new(0))
|
||||
checkpoint_match_predicate(justified_checkpoint, finalized_checkpoint)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -930,7 +1015,7 @@ impl ProtoArray {
|
||||
/// Returns `None` if there is an overflow or underflow when calculating the score.
|
||||
///
|
||||
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#get_latest_attesting_balance
|
||||
fn calculate_proposer_boost<E: EthSpec>(
|
||||
pub fn calculate_proposer_boost<E: EthSpec>(
|
||||
validator_balances: &[u64],
|
||||
proposer_score_boost: u64,
|
||||
) -> Option<u64> {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use crate::error::Error;
|
||||
use crate::proto_array::{InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode};
|
||||
use crate::proto_array::CountUnrealizedFull;
|
||||
use crate::proto_array::{
|
||||
calculate_proposer_boost, InvalidationOperation, Iter, ProposerBoost, ProtoArray, ProtoNode,
|
||||
};
|
||||
use crate::ssz_container::SszContainer;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use types::{
|
||||
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
|
||||
Slot,
|
||||
@@ -36,7 +39,7 @@ pub enum ExecutionStatus {
|
||||
///
|
||||
/// This `bool` only exists to satisfy our SSZ implementation which requires all variants
|
||||
/// to have a value. It can be set to anything.
|
||||
Irrelevant(bool), // TODO(merge): fix bool.
|
||||
Irrelevant(bool),
|
||||
}
|
||||
|
||||
impl ExecutionStatus {
|
||||
@@ -87,10 +90,22 @@ impl ExecutionStatus {
|
||||
///
|
||||
/// - Has execution enabled, AND
|
||||
/// - Has a payload that has not yet been verified by an EL.
|
||||
pub fn is_optimistic(&self) -> bool {
|
||||
pub fn is_strictly_optimistic(&self) -> bool {
|
||||
matches!(self, ExecutionStatus::Optimistic(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if the block:
|
||||
///
|
||||
/// - Has execution enabled, AND
|
||||
/// - Has a payload that has not yet been verified by an EL, OR.
|
||||
/// - Has a payload that has been deemed invalid by an EL.
|
||||
pub fn is_optimistic_or_invalid(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ExecutionStatus::Optimistic(_) | ExecutionStatus::Invalid(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `true` if the block:
|
||||
///
|
||||
/// - Has execution enabled, AND
|
||||
@@ -124,6 +139,8 @@ pub struct Block {
|
||||
/// Indicates if an execution node has marked this block as valid. Also contains the execution
|
||||
/// block hash.
|
||||
pub execution_status: ExecutionStatus,
|
||||
pub unrealized_justified_checkpoint: Option<Checkpoint>,
|
||||
pub unrealized_finalized_checkpoint: Option<Checkpoint>,
|
||||
}
|
||||
|
||||
/// A Vec-wrapper which will grow to match any request.
|
||||
@@ -162,7 +179,7 @@ pub struct ProtoArrayForkChoice {
|
||||
|
||||
impl ProtoArrayForkChoice {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pub fn new<E: EthSpec>(
|
||||
finalized_block_slot: Slot,
|
||||
finalized_block_state_root: Hash256,
|
||||
justified_checkpoint: Checkpoint,
|
||||
@@ -170,6 +187,7 @@ impl ProtoArrayForkChoice {
|
||||
current_epoch_shuffling_id: AttestationShufflingId,
|
||||
next_epoch_shuffling_id: AttestationShufflingId,
|
||||
execution_status: ExecutionStatus,
|
||||
count_unrealized_full: CountUnrealizedFull,
|
||||
) -> Result<Self, String> {
|
||||
let mut proto_array = ProtoArray {
|
||||
prune_threshold: DEFAULT_PRUNE_THRESHOLD,
|
||||
@@ -178,6 +196,7 @@ impl ProtoArrayForkChoice {
|
||||
nodes: Vec::with_capacity(1),
|
||||
indices: HashMap::with_capacity(1),
|
||||
previous_proposer_boost: ProposerBoost::default(),
|
||||
count_unrealized_full,
|
||||
};
|
||||
|
||||
let block = Block {
|
||||
@@ -193,10 +212,12 @@ impl ProtoArrayForkChoice {
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: Some(justified_checkpoint),
|
||||
unrealized_finalized_checkpoint: Some(finalized_checkpoint),
|
||||
};
|
||||
|
||||
proto_array
|
||||
.on_block(block)
|
||||
.on_block::<E>(block, finalized_block_slot)
|
||||
.map_err(|e| format!("Failed to add finalized block to proto_array: {:?}", e))?;
|
||||
|
||||
Ok(Self {
|
||||
@@ -242,22 +263,29 @@ impl ProtoArrayForkChoice {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_block(&mut self, block: Block) -> Result<(), String> {
|
||||
pub fn process_block<E: EthSpec>(
|
||||
&mut self,
|
||||
block: Block,
|
||||
current_slot: Slot,
|
||||
) -> Result<(), String> {
|
||||
if block.parent_root.is_none() {
|
||||
return Err("Missing parent root".to_string());
|
||||
}
|
||||
|
||||
self.proto_array
|
||||
.on_block(block)
|
||||
.on_block::<E>(block, current_slot)
|
||||
.map_err(|e| format!("process_block_error: {:?}", e))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn find_head<E: EthSpec>(
|
||||
&mut self,
|
||||
justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
justified_state_balances: &[u64],
|
||||
proposer_boost_root: Hash256,
|
||||
equivocating_indices: &BTreeSet<u64>,
|
||||
current_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Hash256, String> {
|
||||
let old_balances = &mut self.balances;
|
||||
@@ -269,6 +297,7 @@ impl ProtoArrayForkChoice {
|
||||
&mut self.votes,
|
||||
old_balances,
|
||||
new_balances,
|
||||
equivocating_indices,
|
||||
)
|
||||
.map_err(|e| format!("find_head compute_deltas failed: {:?}", e))?;
|
||||
|
||||
@@ -279,6 +308,7 @@ impl ProtoArrayForkChoice {
|
||||
finalized_checkpoint,
|
||||
new_balances,
|
||||
proposer_boost_root,
|
||||
current_slot,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| format!("find_head apply_score_changes failed: {:?}", e))?;
|
||||
@@ -286,10 +316,121 @@ impl ProtoArrayForkChoice {
|
||||
*old_balances = new_balances.to_vec();
|
||||
|
||||
self.proto_array
|
||||
.find_head(&justified_checkpoint.root)
|
||||
.find_head::<E>(&justified_checkpoint.root, current_slot)
|
||||
.map_err(|e| format!("find_head failed: {:?}", e))
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any blocks in `self` with an `INVALID` execution payload status.
|
||||
///
|
||||
/// This will operate on *all* blocks, even those that do not descend from the finalized
|
||||
/// ancestor.
|
||||
pub fn contains_invalid_payloads(&mut self) -> bool {
|
||||
self.proto_array
|
||||
.nodes
|
||||
.iter()
|
||||
.any(|node| node.execution_status.is_invalid())
|
||||
}
|
||||
|
||||
/// For all nodes, regardless of their relationship to the finalized block, set their execution
|
||||
/// status to be optimistic.
|
||||
///
|
||||
/// In practice this means forgetting any `VALID` or `INVALID` statuses.
|
||||
pub fn set_all_blocks_to_optimistic<E: EthSpec>(
|
||||
&mut self,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), String> {
|
||||
// Iterate backwards through all nodes in the `proto_array`. Whilst it's not strictly
|
||||
// required to do this process in reverse, it seems natural when we consider how LMD votes
|
||||
// are counted.
|
||||
//
|
||||
// This function will touch all blocks, even those that do not descend from the finalized
|
||||
// block. Since this function is expected to run at start-up during very rare
|
||||
// circumstances we prefer simplicity over efficiency.
|
||||
for node_index in (0..self.proto_array.nodes.len()).rev() {
|
||||
let node = self
|
||||
.proto_array
|
||||
.nodes
|
||||
.get_mut(node_index)
|
||||
.ok_or("unreachable index out of bounds in proto_array nodes")?;
|
||||
|
||||
match node.execution_status {
|
||||
ExecutionStatus::Invalid(block_hash) => {
|
||||
node.execution_status = ExecutionStatus::Optimistic(block_hash);
|
||||
|
||||
// Restore the weight of the node, it would have been set to `0` in
|
||||
// `apply_score_changes` when it was invalidated.
|
||||
let mut restored_weight: u64 = self
|
||||
.votes
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(validator_index, vote)| {
|
||||
if vote.current_root == node.root {
|
||||
// Any voting validator that does not have a balance should be
|
||||
// ignored. This is consistent with `compute_deltas`.
|
||||
self.balances.get(validator_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
// If the invalid root was boosted, apply the weight to it and
|
||||
// ancestors.
|
||||
if let Some(proposer_score_boost) = spec.proposer_score_boost {
|
||||
if self.proto_array.previous_proposer_boost.root == node.root {
|
||||
// Compute the score based upon the current balances. We can't rely on
|
||||
// the `previous_proposr_boost.score` since it is set to zero with an
|
||||
// invalid node.
|
||||
let proposer_score =
|
||||
calculate_proposer_boost::<E>(&self.balances, proposer_score_boost)
|
||||
.ok_or("Failed to compute proposer boost")?;
|
||||
// Store the score we've applied here so it can be removed in
|
||||
// a later call to `apply_score_changes`.
|
||||
self.proto_array.previous_proposer_boost.score = proposer_score;
|
||||
// Apply this boost to this node.
|
||||
restored_weight = restored_weight
|
||||
.checked_add(proposer_score)
|
||||
.ok_or("Overflow when adding boost to weight")?;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the restored weight to the node and all ancestors.
|
||||
if restored_weight > 0 {
|
||||
let mut node_or_ancestor = node;
|
||||
loop {
|
||||
node_or_ancestor.weight = node_or_ancestor
|
||||
.weight
|
||||
.checked_add(restored_weight)
|
||||
.ok_or("Overflow when adding weight to ancestor")?;
|
||||
|
||||
if let Some(parent_index) = node_or_ancestor.parent {
|
||||
node_or_ancestor = self
|
||||
.proto_array
|
||||
.nodes
|
||||
.get_mut(parent_index)
|
||||
.ok_or(format!("Missing parent index: {}", parent_index))?;
|
||||
} else {
|
||||
// This is either the finalized block or a block that does not
|
||||
// descend from the finalized block.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// There are no balance changes required if the node was either valid or
|
||||
// optimistic.
|
||||
ExecutionStatus::Valid(block_hash) | ExecutionStatus::Optimistic(block_hash) => {
|
||||
node.execution_status = ExecutionStatus::Optimistic(block_hash)
|
||||
}
|
||||
// An irrelevant node cannot become optimistic, this is a no-op.
|
||||
ExecutionStatus::Irrelevant(_) => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn maybe_prune(&mut self, finalized_root: Hash256) -> Result<(), String> {
|
||||
self.proto_array
|
||||
.maybe_prune(finalized_root)
|
||||
@@ -341,6 +482,8 @@ impl ProtoArrayForkChoice {
|
||||
justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
execution_status: block.execution_status,
|
||||
unrealized_justified_checkpoint: block.unrealized_justified_checkpoint,
|
||||
unrealized_finalized_checkpoint: block.unrealized_finalized_checkpoint,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -391,8 +534,12 @@ impl ProtoArrayForkChoice {
|
||||
SszContainer::from(self).as_ssz_bytes()
|
||||
}
|
||||
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
|
||||
pub fn from_bytes(
|
||||
bytes: &[u8],
|
||||
count_unrealized_full: CountUnrealizedFull,
|
||||
) -> Result<Self, String> {
|
||||
SszContainer::from_ssz_bytes(bytes)
|
||||
.map(|container| (container, count_unrealized_full))
|
||||
.map(Into::into)
|
||||
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))
|
||||
}
|
||||
@@ -427,6 +574,7 @@ fn compute_deltas(
|
||||
votes: &mut ElasticList<VoteTracker>,
|
||||
old_balances: &[u64],
|
||||
new_balances: &[u64],
|
||||
equivocating_indices: &BTreeSet<u64>,
|
||||
) -> Result<Vec<i64>, Error> {
|
||||
let mut deltas = vec![0_i64; indices.len()];
|
||||
|
||||
@@ -437,6 +585,38 @@ fn compute_deltas(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle newly slashed validators by deducting their weight from their current vote. We
|
||||
// determine if they are newly slashed by checking whether their `vote.current_root` is
|
||||
// non-zero. After applying the deduction a single time we set their `current_root` to zero
|
||||
// and never update it again (thus preventing repeat deductions).
|
||||
//
|
||||
// Even if they make new attestations which are processed by `process_attestation` these
|
||||
// will only update their `vote.next_root`.
|
||||
if equivocating_indices.contains(&(val_index as u64)) {
|
||||
// First time we've processed this slashing in fork choice:
|
||||
//
|
||||
// 1. Add a negative delta for their `current_root`.
|
||||
// 2. Set their `current_root` (permanently) to zero.
|
||||
if !vote.current_root.is_zero() {
|
||||
let old_balance = old_balances.get(val_index).copied().unwrap_or(0);
|
||||
|
||||
if let Some(current_delta_index) = indices.get(&vote.current_root).copied() {
|
||||
let delta = deltas
|
||||
.get(current_delta_index)
|
||||
.ok_or(Error::InvalidNodeDelta(current_delta_index))?
|
||||
.checked_sub(old_balance as i64)
|
||||
.ok_or(Error::DeltaOverflow(current_delta_index))?;
|
||||
|
||||
// Array access safe due to check on previous line.
|
||||
deltas[current_delta_index] = delta;
|
||||
}
|
||||
|
||||
vote.current_root = Hash256::zero();
|
||||
}
|
||||
// We've handled this slashed validator, continue without applying an ordinary delta.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the validator was not included in the _old_ balances (i.e., it did not exist yet)
|
||||
// then say its balance was zero.
|
||||
let old_balance = old_balances.get(val_index).copied().unwrap_or(0);
|
||||
@@ -485,6 +665,7 @@ fn compute_deltas(
|
||||
#[cfg(test)]
|
||||
mod test_compute_deltas {
|
||||
use super::*;
|
||||
use types::MainnetEthSpec;
|
||||
|
||||
/// Gives a hash that is not the zero hash (unless i is `usize::max_value)`.
|
||||
fn hash_from_index(i: usize) -> Hash256 {
|
||||
@@ -510,7 +691,7 @@ mod test_compute_deltas {
|
||||
root: finalized_root,
|
||||
};
|
||||
|
||||
let mut fc = ProtoArrayForkChoice::new(
|
||||
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
|
||||
genesis_slot,
|
||||
state_root,
|
||||
genesis_checkpoint,
|
||||
@@ -518,39 +699,50 @@ mod test_compute_deltas {
|
||||
junk_shuffling_id.clone(),
|
||||
junk_shuffling_id.clone(),
|
||||
execution_status,
|
||||
CountUnrealizedFull::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Add block that is a finalized descendant.
|
||||
fc.proto_array
|
||||
.on_block(Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: finalized_desc,
|
||||
parent_root: Some(finalized_root),
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
})
|
||||
.on_block::<MainnetEthSpec>(
|
||||
Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: finalized_desc,
|
||||
parent_root: Some(finalized_root),
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: Some(genesis_checkpoint),
|
||||
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
|
||||
},
|
||||
genesis_slot + 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Add block that is *not* a finalized descendant.
|
||||
fc.proto_array
|
||||
.on_block(Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: not_finalized_desc,
|
||||
parent_root: None,
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id,
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
})
|
||||
.on_block::<MainnetEthSpec>(
|
||||
Block {
|
||||
slot: genesis_slot + 1,
|
||||
root: not_finalized_desc,
|
||||
parent_root: None,
|
||||
state_root,
|
||||
target_root: finalized_root,
|
||||
current_epoch_shuffling_id: junk_shuffling_id.clone(),
|
||||
next_epoch_shuffling_id: junk_shuffling_id,
|
||||
justified_checkpoint: genesis_checkpoint,
|
||||
finalized_checkpoint: genesis_checkpoint,
|
||||
execution_status,
|
||||
unrealized_justified_checkpoint: None,
|
||||
unrealized_finalized_checkpoint: None,
|
||||
},
|
||||
genesis_slot + 1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(!fc.is_descendant(unknown, unknown));
|
||||
@@ -582,6 +774,7 @@ mod test_compute_deltas {
|
||||
let mut votes = ElasticList::default();
|
||||
let mut old_balances = vec![];
|
||||
let mut new_balances = vec![];
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for i in 0..validator_count {
|
||||
indices.insert(hash_from_index(i), i);
|
||||
@@ -594,8 +787,14 @@ mod test_compute_deltas {
|
||||
new_balances.push(0);
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(
|
||||
deltas.len(),
|
||||
@@ -626,6 +825,7 @@ mod test_compute_deltas {
|
||||
let mut votes = ElasticList::default();
|
||||
let mut old_balances = vec![];
|
||||
let mut new_balances = vec![];
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for i in 0..validator_count {
|
||||
indices.insert(hash_from_index(i), i);
|
||||
@@ -638,8 +838,14 @@ mod test_compute_deltas {
|
||||
new_balances.push(BALANCE);
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(
|
||||
deltas.len(),
|
||||
@@ -677,6 +883,7 @@ mod test_compute_deltas {
|
||||
let mut votes = ElasticList::default();
|
||||
let mut old_balances = vec![];
|
||||
let mut new_balances = vec![];
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for i in 0..validator_count {
|
||||
indices.insert(hash_from_index(i), i);
|
||||
@@ -689,8 +896,14 @@ mod test_compute_deltas {
|
||||
new_balances.push(BALANCE);
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(
|
||||
deltas.len(),
|
||||
@@ -723,6 +936,7 @@ mod test_compute_deltas {
|
||||
let mut votes = ElasticList::default();
|
||||
let mut old_balances = vec![];
|
||||
let mut new_balances = vec![];
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for i in 0..validator_count {
|
||||
indices.insert(hash_from_index(i), i);
|
||||
@@ -735,8 +949,14 @@ mod test_compute_deltas {
|
||||
new_balances.push(BALANCE);
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(
|
||||
deltas.len(),
|
||||
@@ -774,6 +994,7 @@ mod test_compute_deltas {
|
||||
|
||||
let mut indices = HashMap::new();
|
||||
let mut votes = ElasticList::default();
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
// There is only one block.
|
||||
indices.insert(hash_from_index(1), 0);
|
||||
@@ -796,8 +1017,14 @@ mod test_compute_deltas {
|
||||
next_epoch: Epoch::new(0),
|
||||
});
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(deltas.len(), 1, "deltas should have expected length");
|
||||
|
||||
@@ -826,6 +1053,7 @@ mod test_compute_deltas {
|
||||
let mut votes = ElasticList::default();
|
||||
let mut old_balances = vec![];
|
||||
let mut new_balances = vec![];
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
for i in 0..validator_count {
|
||||
indices.insert(hash_from_index(i), i);
|
||||
@@ -838,8 +1066,14 @@ mod test_compute_deltas {
|
||||
new_balances.push(NEW_BALANCE);
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(
|
||||
deltas.len(),
|
||||
@@ -879,6 +1113,7 @@ mod test_compute_deltas {
|
||||
|
||||
let mut indices = HashMap::new();
|
||||
let mut votes = ElasticList::default();
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
// There are two blocks.
|
||||
indices.insert(hash_from_index(1), 0);
|
||||
@@ -898,8 +1133,14 @@ mod test_compute_deltas {
|
||||
});
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(deltas.len(), 2, "deltas should have expected length");
|
||||
|
||||
@@ -928,6 +1169,7 @@ mod test_compute_deltas {
|
||||
|
||||
let mut indices = HashMap::new();
|
||||
let mut votes = ElasticList::default();
|
||||
let equivocating_indices = BTreeSet::new();
|
||||
|
||||
// There are two blocks.
|
||||
indices.insert(hash_from_index(1), 0);
|
||||
@@ -947,8 +1189,14 @@ mod test_compute_deltas {
|
||||
});
|
||||
}
|
||||
|
||||
let deltas = compute_deltas(&indices, &mut votes, &old_balances, &new_balances)
|
||||
.expect("should compute deltas");
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(deltas.len(), 2, "deltas should have expected length");
|
||||
|
||||
@@ -969,4 +1217,72 @@ mod test_compute_deltas {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_equivocates() {
|
||||
const OLD_BALANCE: u64 = 42;
|
||||
const NEW_BALANCE: u64 = 43;
|
||||
|
||||
let mut indices = HashMap::new();
|
||||
let mut votes = ElasticList::default();
|
||||
|
||||
// There are two blocks.
|
||||
indices.insert(hash_from_index(1), 0);
|
||||
indices.insert(hash_from_index(2), 1);
|
||||
|
||||
// There are two validators.
|
||||
let old_balances = vec![OLD_BALANCE; 2];
|
||||
let new_balances = vec![NEW_BALANCE; 2];
|
||||
|
||||
// Both validator move votes from block 1 to block 2.
|
||||
for _ in 0..2 {
|
||||
votes.0.push(VoteTracker {
|
||||
current_root: hash_from_index(1),
|
||||
next_root: hash_from_index(2),
|
||||
next_epoch: Epoch::new(0),
|
||||
});
|
||||
}
|
||||
|
||||
// Validator 0 is slashed.
|
||||
let equivocating_indices = BTreeSet::from_iter([0]);
|
||||
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&old_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
|
||||
assert_eq!(deltas.len(), 2, "deltas should have expected length");
|
||||
|
||||
assert_eq!(
|
||||
deltas[0],
|
||||
-2 * OLD_BALANCE as i64,
|
||||
"block 1 should have lost two old balances"
|
||||
);
|
||||
assert_eq!(
|
||||
deltas[1], NEW_BALANCE as i64,
|
||||
"block 2 should have gained one balance"
|
||||
);
|
||||
|
||||
// Validator 0's current root should have been reset.
|
||||
assert_eq!(votes.0[0].current_root, Hash256::zero());
|
||||
assert_eq!(votes.0[0].next_root, hash_from_index(2));
|
||||
|
||||
// Validator 1's current root should have been updated.
|
||||
assert_eq!(votes.0[1].current_root, hash_from_index(2));
|
||||
|
||||
// Re-computing the deltas should be a no-op (no repeat deduction for the slashed validator).
|
||||
let deltas = compute_deltas(
|
||||
&indices,
|
||||
&mut votes,
|
||||
&new_balances,
|
||||
&new_balances,
|
||||
&equivocating_indices,
|
||||
)
|
||||
.expect("should compute deltas");
|
||||
assert_eq!(deltas, vec![0, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::proto_array::ProposerBoost;
|
||||
use crate::{
|
||||
proto_array::{ProtoArray, ProtoNode},
|
||||
proto_array::{CountUnrealizedFull, ProtoArray, ProtoNode},
|
||||
proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker},
|
||||
};
|
||||
use ssz::{four_byte_option_impl, Encode};
|
||||
@@ -41,8 +41,8 @@ impl From<&ProtoArrayForkChoice> for SszContainer {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SszContainer> for ProtoArrayForkChoice {
|
||||
fn from(from: SszContainer) -> Self {
|
||||
impl From<(SszContainer, CountUnrealizedFull)> for ProtoArrayForkChoice {
|
||||
fn from((from, count_unrealized_full): (SszContainer, CountUnrealizedFull)) -> Self {
|
||||
let proto_array = ProtoArray {
|
||||
prune_threshold: from.prune_threshold,
|
||||
justified_checkpoint: from.justified_checkpoint,
|
||||
@@ -50,6 +50,7 @@ impl From<SszContainer> for ProtoArrayForkChoice {
|
||||
nodes: from.nodes,
|
||||
indices: from.indices.into_iter().collect::<HashMap<_, _>>(),
|
||||
previous_proposer_boost: from.previous_proposer_boost,
|
||||
count_unrealized_full,
|
||||
};
|
||||
|
||||
Self {
|
||||
|
||||
@@ -6,6 +6,7 @@ pub mod hex_vec;
|
||||
pub mod json_str;
|
||||
pub mod list_of_bytes_lists;
|
||||
pub mod quoted_u64_vec;
|
||||
pub mod u256_hex_be;
|
||||
pub mod u32_hex;
|
||||
pub mod u64_hex_be;
|
||||
pub mod u8_hex;
|
||||
|
||||
144
consensus/serde_utils/src/u256_hex_be.rs
Normal file
144
consensus/serde_utils/src/u256_hex_be.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use ethereum_types::U256;
|
||||
|
||||
use serde::de::Visitor;
|
||||
use serde::{de, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn serialize<S>(num: &U256, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
num.serialize(serializer)
|
||||
}
|
||||
|
||||
pub struct U256Visitor;
|
||||
|
||||
impl<'de> Visitor<'de> for U256Visitor {
|
||||
type Value = String;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("a well formatted hex string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if !value.starts_with("0x") {
|
||||
return Err(de::Error::custom("must start with 0x"));
|
||||
}
|
||||
let stripped = &value[2..];
|
||||
if stripped.is_empty() {
|
||||
Err(de::Error::custom(format!(
|
||||
"quantity cannot be {:?}",
|
||||
stripped
|
||||
)))
|
||||
} else if stripped == "0" {
|
||||
Ok(value.to_string())
|
||||
} else if stripped.starts_with('0') {
|
||||
Err(de::Error::custom("cannot have leading zero"))
|
||||
} else {
|
||||
Ok(value.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let decoded = deserializer.deserialize_string(U256Visitor)?;
|
||||
|
||||
U256::from_str(&decoded).map_err(|e| de::Error::custom(format!("Invalid U256 string: {}", e)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use ethereum_types::U256;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
struct Wrapper {
|
||||
#[serde(with = "super")]
|
||||
val: U256,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding() {
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper { val: 0.into() }).unwrap(),
|
||||
"\"0x0\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper { val: 1.into() }).unwrap(),
|
||||
"\"0x1\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper { val: 256.into() }).unwrap(),
|
||||
"\"0x100\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper { val: 65.into() }).unwrap(),
|
||||
"\"0x41\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper { val: 1024.into() }).unwrap(),
|
||||
"\"0x400\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper {
|
||||
val: U256::max_value() - 1
|
||||
})
|
||||
.unwrap(),
|
||||
"\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\""
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::to_string(&Wrapper {
|
||||
val: U256::max_value()
|
||||
})
|
||||
.unwrap(),
|
||||
"\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decoding() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Wrapper>("\"0x0\"").unwrap(),
|
||||
Wrapper { val: 0.into() },
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Wrapper>("\"0x41\"").unwrap(),
|
||||
Wrapper { val: 65.into() },
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Wrapper>("\"0x400\"").unwrap(),
|
||||
Wrapper { val: 1024.into() },
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Wrapper>(
|
||||
"\"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\""
|
||||
)
|
||||
.unwrap(),
|
||||
Wrapper {
|
||||
val: U256::max_value() - 1
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Wrapper>(
|
||||
"\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\""
|
||||
)
|
||||
.unwrap(),
|
||||
Wrapper {
|
||||
val: U256::max_value()
|
||||
},
|
||||
);
|
||||
serde_json::from_str::<Wrapper>("\"0x\"").unwrap_err();
|
||||
serde_json::from_str::<Wrapper>("\"0x0400\"").unwrap_err();
|
||||
serde_json::from_str::<Wrapper>("\"400\"").unwrap_err();
|
||||
serde_json::from_str::<Wrapper>("\"ff\"").unwrap_err();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use core::num::NonZeroUsize;
|
||||
use ethereum_types::{H160, H256, U128, U256};
|
||||
use itertools::process_results;
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::iter::{self, FromIterator};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -431,6 +431,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Decode for BTreeSet<T>
|
||||
where
|
||||
T: Decode + Ord,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
|
||||
if bytes.is_empty() {
|
||||
Ok(Self::from_iter(iter::empty()))
|
||||
} else if T::is_ssz_fixed_len() {
|
||||
bytes
|
||||
.chunks(T::ssz_fixed_len())
|
||||
.map(T::from_ssz_bytes)
|
||||
.collect()
|
||||
} else {
|
||||
decode_list_of_variable_length_items(bytes, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes `bytes` as if it were a list of variable-length items.
|
||||
///
|
||||
/// The `ssz::SszDecoder` can also perform this functionality, however this function is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -62,6 +62,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TryFromIter<T> for BTreeSet<T>
|
||||
where
|
||||
T: Ord,
|
||||
{
|
||||
type Error = Infallible;
|
||||
|
||||
fn try_from_iter<I>(iter: I) -> Result<Self, Self::Error>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
{
|
||||
Ok(Self::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
/// Partial variant of `collect`.
|
||||
pub trait TryCollect: Iterator {
|
||||
fn try_collect<C>(self) -> Result<C, C::Error>
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::*;
|
||||
use core::num::NonZeroUsize;
|
||||
use ethereum_types::{H160, H256, U128, U256};
|
||||
use smallvec::SmallVec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
macro_rules! impl_encodable_for_uint {
|
||||
@@ -326,6 +326,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Encode for BTreeSet<T>
|
||||
where
|
||||
T: Encode + Ord,
|
||||
{
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn ssz_bytes_len(&self) -> usize {
|
||||
sequence_ssz_bytes_len(self.iter())
|
||||
}
|
||||
|
||||
fn ssz_append(&self, buf: &mut Vec<u8>) {
|
||||
sequence_ssz_append(self.iter(), buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for bool {
|
||||
fn is_ssz_fixed_len() -> bool {
|
||||
true
|
||||
|
||||
@@ -353,7 +353,7 @@ mod test {
|
||||
let vec = vec![0, 2, 4, 6];
|
||||
let fixed: FixedVector<u64, U4> = FixedVector::from(vec);
|
||||
|
||||
assert_eq!(fixed.get(0), Some(&0));
|
||||
assert_eq!(fixed.first(), Some(&0));
|
||||
assert_eq!(fixed.get(3), Some(&6));
|
||||
assert_eq!(fixed.get(4), None);
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ mod test {
|
||||
let vec = vec![0, 2, 4, 6];
|
||||
let fixed: VariableList<u64, U4> = VariableList::from(vec);
|
||||
|
||||
assert_eq!(fixed.get(0), Some(&0));
|
||||
assert_eq!(fixed.first(), Some(&0));
|
||||
assert_eq!(fixed.get(3), Some(&6));
|
||||
assert_eq!(fixed.get(4), None);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ edition = "2021"
|
||||
[dev-dependencies]
|
||||
env_logger = "0.9.0"
|
||||
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||
tokio = { version = "1.14.0", features = ["rt-multi-thread"] }
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../../crypto/bls" }
|
||||
integer-sqrt = "0.1.5"
|
||||
itertools = "0.10.0"
|
||||
eth2_ssz = "0.4.1"
|
||||
eth2_ssz_derive = "0.3.0"
|
||||
eth2_ssz_types = "0.2.2"
|
||||
merkle_proof = { path = "../merkle_proof" }
|
||||
safe_arith = { path = "../safe_arith" }
|
||||
@@ -27,6 +29,7 @@ lighthouse_metrics = { path = "../../common/lighthouse_metrics", optional = true
|
||||
lazy_static = { version = "1.4.0", optional = true }
|
||||
rustc-hash = "1.1.0"
|
||||
vec_map = "0.8.2"
|
||||
derivative = "2.1.1"
|
||||
|
||||
[features]
|
||||
default = ["legacy-arith", "metrics"]
|
||||
|
||||
@@ -2,25 +2,43 @@ use integer_sqrt::IntegerSquareRoot;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::*;
|
||||
|
||||
/// This type exists to avoid confusing `total_active_balance` with `base_reward_per_increment`,
|
||||
/// since they are used in close proximity and the same type (`u64`).
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct BaseRewardPerIncrement(u64);
|
||||
|
||||
impl BaseRewardPerIncrement {
|
||||
pub fn new(total_active_balance: u64, spec: &ChainSpec) -> Result<Self, ArithError> {
|
||||
get_base_reward_per_increment(total_active_balance, spec).map(Self)
|
||||
}
|
||||
|
||||
pub fn as_u64(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// The function has a different interface to the spec since it accepts the
|
||||
/// `base_reward_per_increment` without computing it each time. Avoiding the re computation has
|
||||
/// shown to be a significant optimisation.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_base_reward(
|
||||
validator_effective_balance: u64,
|
||||
// Should be == get_total_active_balance(state, spec)
|
||||
total_active_balance: u64,
|
||||
base_reward_per_increment: BaseRewardPerIncrement,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, Error> {
|
||||
validator_effective_balance
|
||||
.safe_div(spec.effective_balance_increment)?
|
||||
.safe_mul(get_base_reward_per_increment(total_active_balance, spec)?)
|
||||
.safe_mul(base_reward_per_increment.as_u64())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_base_reward_per_increment(
|
||||
fn get_base_reward_per_increment(
|
||||
total_active_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, ArithError> {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
use types::*;
|
||||
|
||||
/// Returns validator indices which participated in the attestation, sorted by increasing index.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn get_attesting_indices<T: EthSpec>(
|
||||
committee: &[usize],
|
||||
bitlist: &BitList<T::MaxValidatorsPerCommittee>,
|
||||
) -> Result<Vec<usize>, BeaconStateError> {
|
||||
) -> Result<Vec<u64>, BeaconStateError> {
|
||||
if bitlist.len() != committee.len() {
|
||||
return Err(BeaconStateError::InvalidBitfield);
|
||||
}
|
||||
@@ -15,7 +13,7 @@ pub fn get_attesting_indices<T: EthSpec>(
|
||||
|
||||
for (i, validator_index) in committee.iter().enumerate() {
|
||||
if let Ok(true) = bitlist.get(i) {
|
||||
indices.push(*validator_index)
|
||||
indices.push(*validator_index as u64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,3 +21,12 @@ pub fn get_attesting_indices<T: EthSpec>(
|
||||
|
||||
Ok(indices)
|
||||
}
|
||||
|
||||
/// Shortcut for getting the attesting indices while fetching the committee from the state's cache.
|
||||
pub fn get_attesting_indices_from_state<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
att: &Attestation<T>,
|
||||
) -> Result<Vec<u64>, BeaconStateError> {
|
||||
let committee = state.get_beacon_committee(att.data.slot, att.data.index)?;
|
||||
get_attesting_indices::<T>(committee.committee, &att.aggregation_bits)
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ pub fn get_indexed_attestation<T: EthSpec>(
|
||||
let attesting_indices = get_attesting_indices::<T>(committee, &attestation.aggregation_bits)?;
|
||||
|
||||
Ok(IndexedAttestation {
|
||||
attesting_indices: VariableList::new(
|
||||
attesting_indices.into_iter().map(|x| x as u64).collect(),
|
||||
)?,
|
||||
attesting_indices: VariableList::new(attesting_indices)?,
|
||||
data: attestation.data.clone(),
|
||||
signature: attestation.signature.clone(),
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod base;
|
||||
|
||||
pub use deposit_data_tree::DepositDataTree;
|
||||
pub use get_attestation_participation::get_attestation_participation_flag_indices;
|
||||
pub use get_attesting_indices::get_attesting_indices;
|
||||
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_from_state};
|
||||
pub use get_indexed_attestation::get_indexed_attestation;
|
||||
pub use initiate_validator_exit::initiate_validator_exit;
|
||||
pub use slash_validator::slash_validator;
|
||||
|
||||
@@ -41,7 +41,7 @@ use arbitrary::Arbitrary;
|
||||
|
||||
/// The strategy to be used when validating the block's signatures.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum BlockSignatureStrategy {
|
||||
/// Do not validate any signature. Use with caution.
|
||||
NoVerification,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::common::{altair::get_base_reward_per_increment, decrease_balance, increase_balance};
|
||||
use crate::common::{altair::BaseRewardPerIncrement, decrease_balance, increase_balance};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, SyncAggregateInvalid};
|
||||
use crate::{signature_sets::sync_aggregate_signature_set, VerifySignatures};
|
||||
use safe_arith::SafeArith;
|
||||
@@ -72,7 +72,8 @@ pub fn compute_sync_aggregate_rewards<T: EthSpec>(
|
||||
let total_active_balance = state.get_total_active_balance()?;
|
||||
let total_active_increments =
|
||||
total_active_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let total_base_rewards = get_base_reward_per_increment(total_active_balance, spec)?
|
||||
let total_base_rewards = BaseRewardPerIncrement::new(total_active_balance, spec)?
|
||||
.as_u64()
|
||||
.safe_mul(total_active_increments)?;
|
||||
let max_participant_rewards = total_base_rewards
|
||||
.safe_mul(SYNC_REWARD_WEIGHT)?
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::*;
|
||||
use crate::common::{
|
||||
altair::get_base_reward, get_attestation_participation_flag_indices, increase_balance,
|
||||
initiate_validator_exit, slash_validator,
|
||||
altair::{get_base_reward, BaseRewardPerIncrement},
|
||||
get_attestation_participation_flag_indices, increase_balance, initiate_validator_exit,
|
||||
slash_validator,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
|
||||
use crate::VerifySignatures;
|
||||
@@ -129,6 +130,7 @@ pub mod altair {
|
||||
|
||||
// Update epoch participation flags.
|
||||
let total_active_balance = state.get_total_active_balance()?;
|
||||
let base_reward_per_increment = BaseRewardPerIncrement::new(total_active_balance, spec)?;
|
||||
let mut proposer_reward_numerator = 0;
|
||||
for index in &indexed_attestation.attesting_indices {
|
||||
let index = *index as usize;
|
||||
@@ -146,7 +148,7 @@ pub mod altair {
|
||||
validator_participation.add_flag(flag_index)?;
|
||||
let effective_balance = state.get_validator(index)?.effective_balance;
|
||||
proposer_reward_numerator.safe_add_assign(
|
||||
get_base_reward(effective_balance, total_active_balance, spec)?
|
||||
get_base_reward(effective_balance, base_reward_per_increment, spec)?
|
||||
.safe_mul(weight)?,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::per_block_processing::errors::{
|
||||
ProposerSlashingInvalid,
|
||||
};
|
||||
use crate::{
|
||||
per_block_processing::process_operations, BlockSignatureStrategy, ConsensusContext,
|
||||
VerifyBlockRoot, VerifySignatures,
|
||||
per_block_processing::{process_operations, verify_exit::verify_exit},
|
||||
BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, VerifySignatures,
|
||||
};
|
||||
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -27,7 +27,7 @@ lazy_static! {
|
||||
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(MAX_VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
fn get_harness<E: EthSpec>(
|
||||
async fn get_harness<E: EthSpec>(
|
||||
epoch_offset: u64,
|
||||
num_validators: usize,
|
||||
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
||||
@@ -41,27 +41,31 @@ fn get_harness<E: EthSpec>(
|
||||
.build();
|
||||
let state = harness.get_current_state();
|
||||
if last_slot_of_epoch > Slot::new(0) {
|
||||
harness.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
(1..last_slot_of_epoch.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..num_validators).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
harness
|
||||
.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
(1..last_slot_of_epoch.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..num_validators).collect::<Vec<_>>().as_slice(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
harness
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_block_ok() {
|
||||
#[tokio::test]
|
||||
async fn valid_block_ok() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let state = harness.get_current_state();
|
||||
|
||||
let slot = state.slot();
|
||||
let (block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1));
|
||||
let (block, mut state) = harness
|
||||
.make_block_return_pre_state(state, slot + Slot::new(1))
|
||||
.await;
|
||||
|
||||
let mut ctxt = ConsensusContext::new(block.slot());
|
||||
let result = per_block_processing(
|
||||
@@ -76,15 +80,15 @@ fn valid_block_ok() {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_block_header_state_slot() {
|
||||
#[tokio::test]
|
||||
async fn invalid_block_header_state_slot() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let state = harness.get_current_state();
|
||||
let slot = state.slot() + Slot::new(1);
|
||||
|
||||
let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot);
|
||||
let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot).await;
|
||||
let (mut block, signature) = signed_block.deconstruct();
|
||||
*block.slot_mut() = slot + Slot::new(1);
|
||||
|
||||
@@ -106,15 +110,17 @@ fn invalid_block_header_state_slot() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_parent_block_root() {
|
||||
#[tokio::test]
|
||||
async fn invalid_parent_block_root() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let state = harness.get_current_state();
|
||||
let slot = state.slot();
|
||||
|
||||
let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1));
|
||||
let (signed_block, mut state) = harness
|
||||
.make_block_return_pre_state(state, slot + Slot::new(1))
|
||||
.await;
|
||||
let (mut block, signature) = signed_block.deconstruct();
|
||||
*block.parent_root_mut() = Hash256::from([0xAA; 32]);
|
||||
|
||||
@@ -139,14 +145,16 @@ fn invalid_parent_block_root() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_block_signature() {
|
||||
#[tokio::test]
|
||||
async fn invalid_block_signature() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let state = harness.get_current_state();
|
||||
let slot = state.slot();
|
||||
let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1));
|
||||
let (signed_block, mut state) = harness
|
||||
.make_block_return_pre_state(state, slot + Slot::new(1))
|
||||
.await;
|
||||
let (block, _) = signed_block.deconstruct();
|
||||
|
||||
let mut ctxt = ConsensusContext::new(block.slot());
|
||||
@@ -168,17 +176,19 @@ fn invalid_block_signature() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_randao_reveal_signature() {
|
||||
#[tokio::test]
|
||||
async fn invalid_randao_reveal_signature() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let state = harness.get_current_state();
|
||||
let slot = state.slot();
|
||||
|
||||
let (signed_block, mut state) = harness.make_block_with_modifier(state, slot + 1, |block| {
|
||||
*block.body_mut().randao_reveal_mut() = Signature::empty();
|
||||
});
|
||||
let (signed_block, mut state) = harness
|
||||
.make_block_with_modifier(state, slot + 1, |block| {
|
||||
*block.body_mut().randao_reveal_mut() = Signature::empty();
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut ctxt = ConsensusContext::new(signed_block.slot());
|
||||
let result = per_block_processing(
|
||||
@@ -194,16 +204,22 @@ fn invalid_randao_reveal_signature() {
|
||||
assert_eq!(result, Err(BlockProcessingError::RandaoSignatureInvalid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_4_deposits() {
|
||||
#[tokio::test]
|
||||
async fn valid_4_deposits() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) = harness.make_deposits(&mut state, 4, None, None);
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
|
||||
let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec);
|
||||
@@ -212,16 +228,22 @@ fn valid_4_deposits() {
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit_deposit_count_too_big() {
|
||||
#[tokio::test]
|
||||
async fn invalid_deposit_deposit_count_too_big() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) = harness.make_deposits(&mut state, 1, None, None);
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
|
||||
let big_deposit_count = NUM_DEPOSITS + 1;
|
||||
@@ -238,16 +260,22 @@ fn invalid_deposit_deposit_count_too_big() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit_count_too_small() {
|
||||
#[tokio::test]
|
||||
async fn invalid_deposit_count_too_small() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) = harness.make_deposits(&mut state, 1, None, None);
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
|
||||
let small_deposit_count = NUM_DEPOSITS - 1;
|
||||
@@ -264,16 +292,22 @@ fn invalid_deposit_count_too_small() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit_bad_merkle_proof() {
|
||||
#[tokio::test]
|
||||
async fn invalid_deposit_bad_merkle_proof() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) = harness.make_deposits(&mut state, 1, None, None);
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
let bad_index = state.eth1_deposit_index() as usize;
|
||||
|
||||
@@ -292,17 +326,23 @@ fn invalid_deposit_bad_merkle_proof() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit_wrong_sig() {
|
||||
#[tokio::test]
|
||||
async fn invalid_deposit_wrong_sig() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) =
|
||||
harness.make_deposits(&mut state, 1, None, Some(SignatureBytes::empty()));
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
|
||||
let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec);
|
||||
@@ -310,17 +350,23 @@ fn invalid_deposit_wrong_sig() {
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_deposit_invalid_pub_key() {
|
||||
#[tokio::test]
|
||||
async fn invalid_deposit_invalid_pub_key() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut state = harness.get_current_state();
|
||||
|
||||
let (deposits, state) =
|
||||
harness.make_deposits(&mut state, 1, Some(PublicKeyBytes::empty()), None);
|
||||
let deposits = VariableList::from(deposits);
|
||||
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
*head_block.to_mut().body_mut().deposits_mut() = deposits;
|
||||
|
||||
let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec);
|
||||
@@ -329,13 +375,19 @@ fn invalid_deposit_invalid_pub_key() {
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_no_committee_for_index() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_no_committee_for_index() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
head_block.to_mut().body_mut().attestations_mut()[0]
|
||||
.data
|
||||
.index += 1;
|
||||
@@ -347,7 +399,7 @@ fn invalid_attestation_no_committee_for_index() {
|
||||
&spec,
|
||||
);
|
||||
|
||||
// Expecting NoCommitee because we manually set the attestation's index to be invalid
|
||||
// Expecting NoCommittee because we manually set the attestation's index to be invalid
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(BlockProcessingError::AttestationInvalid {
|
||||
@@ -357,13 +409,19 @@ fn invalid_attestation_no_committee_for_index() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_wrong_justified_checkpoint() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_wrong_justified_checkpoint() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
let old_justified_checkpoint = head_block.body().attestations()[0].data.source;
|
||||
let mut new_justified_checkpoint = old_justified_checkpoint;
|
||||
new_justified_checkpoint.epoch += Epoch::new(1);
|
||||
@@ -394,13 +452,19 @@ fn invalid_attestation_wrong_justified_checkpoint() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_bad_aggregation_bitfield_len() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_bad_aggregation_bitfield_len() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
head_block.to_mut().body_mut().attestations_mut()[0].aggregation_bits =
|
||||
Bitfield::with_capacity(spec.target_committee_size).unwrap();
|
||||
|
||||
@@ -412,7 +476,7 @@ fn invalid_attestation_bad_aggregation_bitfield_len() {
|
||||
&spec,
|
||||
);
|
||||
|
||||
// Expecting InvalidBitfield because the size of the aggregation_bitfield is bigger than the commitee size.
|
||||
// Expecting InvalidBitfield because the size of the aggregation_bitfield is bigger than the committee size.
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(BlockProcessingError::BeaconStateError(
|
||||
@@ -421,13 +485,19 @@ fn invalid_attestation_bad_aggregation_bitfield_len() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_bad_signature() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_bad_signature() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, 97); // minimal number of required validators for this test
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, 97).await; // minimal number of required validators for this test
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
head_block.to_mut().body_mut().attestations_mut()[0].signature = AggregateSignature::empty();
|
||||
|
||||
let result = process_operations::process_attestations(
|
||||
@@ -449,13 +519,19 @@ fn invalid_attestation_bad_signature() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_included_too_early() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_included_too_early() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
let new_attesation_slot = head_block.body().attestations()[0].data.slot
|
||||
+ Slot::new(MainnetEthSpec::slots_per_epoch());
|
||||
head_block.to_mut().body_mut().attestations_mut()[0]
|
||||
@@ -484,14 +560,20 @@ fn invalid_attestation_included_too_early() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_included_too_late() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_included_too_late() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
// note to maintainer: might need to increase validator count if we get NoCommittee
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
let new_attesation_slot = head_block.body().attestations()[0].data.slot
|
||||
- Slot::new(MainnetEthSpec::slots_per_epoch());
|
||||
head_block.to_mut().body_mut().attestations_mut()[0]
|
||||
@@ -517,14 +599,20 @@ fn invalid_attestation_included_too_late() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attestation_target_epoch_slot_mismatch() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attestation_target_epoch_slot_mismatch() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
// note to maintainer: might need to increase validator count if we get NoCommittee
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut state = harness.get_current_state();
|
||||
let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0;
|
||||
let mut head_block = harness
|
||||
.chain
|
||||
.head_beacon_block()
|
||||
.as_ref()
|
||||
.clone()
|
||||
.deconstruct()
|
||||
.0;
|
||||
head_block.to_mut().body_mut().attestations_mut()[0]
|
||||
.data
|
||||
.target
|
||||
@@ -549,10 +637,10 @@ fn invalid_attestation_target_epoch_slot_mismatch() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_insert_attester_slashing() {
|
||||
#[tokio::test]
|
||||
async fn valid_insert_attester_slashing() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let attester_slashing = harness.make_attester_slashing(vec![1, 2]);
|
||||
|
||||
@@ -568,10 +656,10 @@ fn valid_insert_attester_slashing() {
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attester_slashing_not_slashable() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attester_slashing_not_slashable() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]);
|
||||
attester_slashing.attestation_1 = attester_slashing.attestation_2.clone();
|
||||
@@ -594,10 +682,10 @@ fn invalid_attester_slashing_not_slashable() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attester_slashing_1_invalid() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attester_slashing_1_invalid() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]);
|
||||
attester_slashing.attestation_1.attesting_indices = VariableList::from(vec![2, 1]);
|
||||
@@ -623,10 +711,10 @@ fn invalid_attester_slashing_1_invalid() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_attester_slashing_2_invalid() {
|
||||
#[tokio::test]
|
||||
async fn invalid_attester_slashing_2_invalid() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]);
|
||||
attester_slashing.attestation_2.attesting_indices = VariableList::from(vec![2, 1]);
|
||||
@@ -652,10 +740,10 @@ fn invalid_attester_slashing_2_invalid() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_insert_proposer_slashing() {
|
||||
#[tokio::test]
|
||||
async fn valid_insert_proposer_slashing() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let proposer_slashing = harness.make_proposer_slashing(1);
|
||||
let mut state = harness.get_current_state();
|
||||
let result = process_operations::process_proposer_slashings(
|
||||
@@ -668,10 +756,10 @@ fn valid_insert_proposer_slashing() {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_proposer_slashing_proposals_identical() {
|
||||
#[tokio::test]
|
||||
async fn invalid_proposer_slashing_proposals_identical() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut proposer_slashing = harness.make_proposer_slashing(1);
|
||||
proposer_slashing.signed_header_1.message = proposer_slashing.signed_header_2.message.clone();
|
||||
@@ -694,10 +782,10 @@ fn invalid_proposer_slashing_proposals_identical() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_proposer_slashing_proposer_unknown() {
|
||||
#[tokio::test]
|
||||
async fn invalid_proposer_slashing_proposer_unknown() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let mut proposer_slashing = harness.make_proposer_slashing(1);
|
||||
proposer_slashing.signed_header_1.message.proposer_index = 3_141_592;
|
||||
@@ -721,10 +809,10 @@ fn invalid_proposer_slashing_proposer_unknown() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_proposer_slashing_duplicate_slashing() {
|
||||
#[tokio::test]
|
||||
async fn invalid_proposer_slashing_duplicate_slashing() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
|
||||
let proposer_slashing = harness.make_proposer_slashing(1);
|
||||
let mut state = harness.get_current_state();
|
||||
@@ -752,10 +840,10 @@ fn invalid_proposer_slashing_duplicate_slashing() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_bad_proposal_1_signature() {
|
||||
#[tokio::test]
|
||||
async fn invalid_bad_proposal_1_signature() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut proposer_slashing = harness.make_proposer_slashing(1);
|
||||
proposer_slashing.signed_header_1.signature = Signature::empty();
|
||||
let mut state = harness.get_current_state();
|
||||
@@ -776,10 +864,10 @@ fn invalid_bad_proposal_1_signature() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_bad_proposal_2_signature() {
|
||||
#[tokio::test]
|
||||
async fn invalid_bad_proposal_2_signature() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut proposer_slashing = harness.make_proposer_slashing(1);
|
||||
proposer_slashing.signed_header_2.signature = Signature::empty();
|
||||
let mut state = harness.get_current_state();
|
||||
@@ -800,10 +888,10 @@ fn invalid_bad_proposal_2_signature() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_proposer_slashing_proposal_epoch_mismatch() {
|
||||
#[tokio::test]
|
||||
async fn invalid_proposer_slashing_proposal_epoch_mismatch() {
|
||||
let spec = MainnetEthSpec::default_spec();
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT);
|
||||
let harness = get_harness::<MainnetEthSpec>(EPOCH_OFFSET, VALIDATOR_COUNT).await;
|
||||
let mut proposer_slashing = harness.make_proposer_slashing(1);
|
||||
proposer_slashing.signed_header_1.message.slot = Slot::new(0);
|
||||
proposer_slashing.signed_header_2.message.slot = Slot::new(128);
|
||||
@@ -827,3 +915,70 @@ fn invalid_proposer_slashing_proposal_epoch_mismatch() {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fork_spanning_exit() {
|
||||
let mut spec = MainnetEthSpec::default_spec();
|
||||
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
||||
|
||||
spec.altair_fork_epoch = Some(Epoch::new(2));
|
||||
spec.bellatrix_fork_epoch = Some(Epoch::new(4));
|
||||
spec.shard_committee_period = 0;
|
||||
|
||||
let harness = BeaconChainHarness::builder(MainnetEthSpec::default())
|
||||
.spec(spec.clone())
|
||||
.deterministic_keypairs(VALIDATOR_COUNT)
|
||||
.mock_execution_layer()
|
||||
.fresh_ephemeral_store()
|
||||
.build();
|
||||
|
||||
harness.extend_to_slot(slots_per_epoch.into()).await;
|
||||
|
||||
/*
|
||||
* Produce an exit *before* Altair.
|
||||
*/
|
||||
|
||||
let signed_exit = harness.make_voluntary_exit(0, Epoch::new(1));
|
||||
assert!(signed_exit.message.epoch < spec.altair_fork_epoch.unwrap());
|
||||
|
||||
/*
|
||||
* Ensure the exit verifies before Altair.
|
||||
*/
|
||||
|
||||
let head = harness.chain.canonical_head.cached_head();
|
||||
let head_state = &head.snapshot.beacon_state;
|
||||
assert!(head_state.current_epoch() < spec.altair_fork_epoch.unwrap());
|
||||
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
|
||||
.expect("phase0 exit verifies against phase0 state");
|
||||
|
||||
/*
|
||||
* Ensure the exit verifies after Altair.
|
||||
*/
|
||||
|
||||
harness
|
||||
.extend_to_slot(spec.altair_fork_epoch.unwrap().start_slot(slots_per_epoch))
|
||||
.await;
|
||||
let head = harness.chain.canonical_head.cached_head();
|
||||
let head_state = &head.snapshot.beacon_state;
|
||||
assert!(head_state.current_epoch() >= spec.altair_fork_epoch.unwrap());
|
||||
assert!(head_state.current_epoch() < spec.bellatrix_fork_epoch.unwrap());
|
||||
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
|
||||
.expect("phase0 exit verifies against altair state");
|
||||
|
||||
/*
|
||||
* Ensure the exit no longer verifies after Bellatrix.
|
||||
*/
|
||||
|
||||
harness
|
||||
.extend_to_slot(
|
||||
spec.bellatrix_fork_epoch
|
||||
.unwrap()
|
||||
.start_slot(slots_per_epoch),
|
||||
)
|
||||
.await;
|
||||
let head = harness.chain.canonical_head.cached_head();
|
||||
let head_state = &head.snapshot.beacon_state;
|
||||
assert!(head_state.current_epoch() >= spec.bellatrix_fork_epoch.unwrap());
|
||||
verify_exit(head_state, &signed_exit, VerifySignatures::True, &spec)
|
||||
.expect_err("phase0 exit does not verify against bellatrix state");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use crate::metrics;
|
||||
pub use epoch_processing_summary::EpochProcessingSummary;
|
||||
use errors::EpochProcessingError as Error;
|
||||
pub use justification_and_finalization_state::JustificationAndFinalizationState;
|
||||
pub use registry_updates::process_registry_updates;
|
||||
use safe_arith::SafeArith;
|
||||
pub use slashings::process_slashings;
|
||||
@@ -15,6 +16,7 @@ pub mod effective_balance_updates;
|
||||
pub mod epoch_processing_summary;
|
||||
pub mod errors;
|
||||
pub mod historical_roots_update;
|
||||
pub mod justification_and_finalization_state;
|
||||
pub mod registry_updates;
|
||||
pub mod resets;
|
||||
pub mod slashings;
|
||||
|
||||
@@ -33,7 +33,9 @@ pub fn process_epoch<T: EthSpec>(
|
||||
let sync_committee = state.current_sync_committee()?.clone();
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &participation_cache)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
process_inactivity_updates(state, &mut participation_cache, spec)?;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ pub fn process_inactivity_updates<T: EthSpec>(
|
||||
participation_cache: &mut ParticipationCache,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
// Score updates based on previous epoch participation, skip genesis epoch
|
||||
if state.current_epoch() == T::genesis_epoch() {
|
||||
return Ok(());
|
||||
@@ -25,7 +26,7 @@ pub fn process_inactivity_updates<T: EthSpec>(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_in_inactivity_leak = state.is_in_inactivity_leak(spec);
|
||||
let is_in_inactivity_leak = state.is_in_inactivity_leak(previous_epoch, spec);
|
||||
|
||||
let mut inactivity_scores = state.inactivity_scores_mut()?.iter_cow();
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
use super::ParticipationCache;
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
state: &BeaconState<T>,
|
||||
participation_cache: &ParticipationCache,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
let total_active_balance = participation_cache.current_epoch_total_active_balance();
|
||||
let previous_target_balance = participation_cache.previous_epoch_target_attesting_balance()?;
|
||||
let current_target_balance = participation_cache.current_epoch_target_attesting_balance()?;
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
justification_and_finalization_state,
|
||||
total_active_balance,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
//! Additionally, this cache is returned from the `altair::process_epoch` function and can be used
|
||||
//! to get useful summaries about the validator participation in an epoch.
|
||||
|
||||
use crate::common::altair::get_base_reward;
|
||||
use crate::common::altair::{get_base_reward, BaseRewardPerIncrement};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::milhouse::update_map::{MaxMap, UpdateMap};
|
||||
use types::{
|
||||
@@ -31,6 +31,7 @@ pub enum Error {
|
||||
MissingValidator(usize),
|
||||
BeaconState(BeaconStateError),
|
||||
Arith(ArithError),
|
||||
InvalidValidatorIndex(usize),
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for Error {
|
||||
@@ -115,11 +116,13 @@ impl SingleEpochParticipationCache {
|
||||
val_index: usize,
|
||||
validator: &Validator,
|
||||
epoch_participation: &ParticipationFlags,
|
||||
state: &BeaconState<T>,
|
||||
// FIXME(sproul): remove state argument
|
||||
_state: &BeaconState<T>,
|
||||
current_epoch: Epoch,
|
||||
relative_epoch: RelativeEpoch,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
// Sanity check to ensure the validator is active.
|
||||
let epoch = relative_epoch.into_epoch(state.current_epoch());
|
||||
let epoch = relative_epoch.into_epoch(current_epoch);
|
||||
if !validator.is_active_at(epoch) {
|
||||
return Err(BeaconStateError::ValidatorIsInactive { val_index });
|
||||
}
|
||||
@@ -220,6 +223,8 @@ impl ParticipationCache {
|
||||
let mut validators = ValidatorInfoCache::new(state.validators().len());
|
||||
|
||||
let current_epoch_total_active_balance = state.get_total_active_balance()?;
|
||||
let base_reward_per_increment =
|
||||
BaseRewardPerIncrement::new(current_epoch_total_active_balance, spec)?;
|
||||
|
||||
// Contains the set of validators which are either:
|
||||
//
|
||||
@@ -257,7 +262,7 @@ impl ParticipationCache {
|
||||
for (val_index, (((val, curr_epoch_flags), prev_epoch_flags), inactivity_score)) in iter {
|
||||
let is_active_current_epoch = val.is_active_at(current_epoch);
|
||||
let is_active_previous_epoch = val.is_active_at(previous_epoch);
|
||||
let is_eligible = state.is_eligible_validator(val);
|
||||
let is_eligible = state.is_eligible_validator(previous_epoch, val);
|
||||
|
||||
if is_active_current_epoch {
|
||||
current_epoch_participation.process_active_validator(
|
||||
@@ -265,6 +270,7 @@ impl ParticipationCache {
|
||||
val,
|
||||
curr_epoch_flags,
|
||||
state,
|
||||
current_epoch,
|
||||
RelativeEpoch::Current,
|
||||
)?;
|
||||
}
|
||||
@@ -277,6 +283,7 @@ impl ParticipationCache {
|
||||
val,
|
||||
prev_epoch_flags,
|
||||
state,
|
||||
current_epoch,
|
||||
RelativeEpoch::Previous,
|
||||
)?;
|
||||
}
|
||||
@@ -326,7 +333,7 @@ impl ParticipationCache {
|
||||
if is_eligible || is_active_current_epoch {
|
||||
let effective_balance = val.effective_balance;
|
||||
let base_reward =
|
||||
get_base_reward(effective_balance, current_epoch_total_active_balance, spec)?;
|
||||
get_base_reward(effective_balance, base_reward_per_increment, spec)?;
|
||||
validator_info.base_reward = base_reward;
|
||||
validators.info[val_index] = Some(validator_info);
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ pub fn get_flag_index_deltas<T: EthSpec>(
|
||||
let unslashed_participating_increments =
|
||||
unslashed_participating_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let active_increments = total_active_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let previous_epoch = state.previous_epoch();
|
||||
|
||||
for &index in participation_cache.eligible_validator_indices() {
|
||||
let validator = participation_cache.get_validator(index)?;
|
||||
@@ -84,7 +85,7 @@ pub fn get_flag_index_deltas<T: EthSpec>(
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if validator.is_unslashed_participating_index(flag_index)? {
|
||||
if !state.is_in_inactivity_leak(spec) {
|
||||
if !state.is_in_inactivity_leak(previous_epoch, spec) {
|
||||
let reward_numerator = base_reward
|
||||
.safe_mul(weight)?
|
||||
.safe_mul(unslashed_participating_increments)?;
|
||||
|
||||
@@ -31,7 +31,9 @@ pub fn process_epoch<T: EthSpec>(
|
||||
validator_statuses.process_attestations(state)?;
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
let justification_and_finalization_state =
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
justification_and_finalization_state.apply_changes_to_state(state);
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
use crate::per_epoch_processing::base::TotalBalances;
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{
|
||||
weigh_justification_and_finalization, JustificationAndFinalizationState,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
state: &BeaconState<T>,
|
||||
total_balances: &TotalBalances,
|
||||
_spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let justification_and_finalization_state = JustificationAndFinalizationState::new(state);
|
||||
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
return Ok(justification_and_finalization_state);
|
||||
}
|
||||
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
justification_and_finalization_state,
|
||||
total_balances.current_epoch(),
|
||||
total_balances.previous_epoch_target_attesters(),
|
||||
total_balances.current_epoch_target_attesters(),
|
||||
|
||||
@@ -78,6 +78,7 @@ pub fn get_attestation_deltas<T: EthSpec>(
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<AttestationDelta>, Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let finality_delay = state
|
||||
.previous_epoch()
|
||||
.safe_sub(state.finalized_checkpoint().epoch)?
|
||||
@@ -94,7 +95,7 @@ pub fn get_attestation_deltas<T: EthSpec>(
|
||||
// eligible.
|
||||
// FIXME(sproul): this is inefficient
|
||||
let full_validator = state.get_validator(index)?;
|
||||
if !state.is_eligible_validator(full_validator) {
|
||||
if !state.is_eligible_validator(previous_epoch, full_validator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -278,8 +278,8 @@ impl ValidatorStatuses {
|
||||
// Loop through the participating validator indices and update the status vec.
|
||||
for validator_index in attesting_indices {
|
||||
self.statuses
|
||||
.get_mut(validator_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(validator_index))?
|
||||
.get_mut(validator_index as usize)
|
||||
.ok_or(BeaconStateError::UnknownValidator(validator_index as usize))?
|
||||
.update(&status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,12 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache.is_current_epoch_timely_target_attester(val_index),
|
||||
} => participation_cache
|
||||
.is_current_epoch_timely_target_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +223,12 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache.is_previous_epoch_timely_target_attester(val_index),
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_target_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +254,12 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache.is_previous_epoch_timely_head_attester(val_index),
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_head_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +285,12 @@ impl<T: EthSpec> EpochProcessingSummary<T> {
|
||||
EpochProcessingSummary::Altair {
|
||||
participation_cache,
|
||||
..
|
||||
} => participation_cache.is_previous_epoch_timely_source_attester(val_index),
|
||||
} => participation_cache
|
||||
.is_previous_epoch_timely_source_attester(val_index)
|
||||
.or_else(|e| match e {
|
||||
ParticipationCacheError::InvalidValidatorIndex(_) => Ok(false),
|
||||
e => Err(e),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use types::{BeaconState, BeaconStateError, BitVector, Checkpoint, Epoch, EthSpec, Hash256};
|
||||
|
||||
/// This is a subset of the `BeaconState` which is used to compute justification and finality
|
||||
/// without modifying the `BeaconState`.
|
||||
///
|
||||
/// A `JustificationAndFinalizationState` can be created from a `BeaconState` to compute
|
||||
/// justification/finality changes and then applied to a `BeaconState` to enshrine those changes.
|
||||
#[must_use = "this value must be applied to a state or explicitly dropped"]
|
||||
pub struct JustificationAndFinalizationState<T: EthSpec> {
|
||||
/*
|
||||
* Immutable fields.
|
||||
*/
|
||||
previous_epoch: Epoch,
|
||||
previous_epoch_target_root: Result<Hash256, BeaconStateError>,
|
||||
current_epoch: Epoch,
|
||||
current_epoch_target_root: Result<Hash256, BeaconStateError>,
|
||||
/*
|
||||
* Mutable fields.
|
||||
*/
|
||||
previous_justified_checkpoint: Checkpoint,
|
||||
current_justified_checkpoint: Checkpoint,
|
||||
finalized_checkpoint: Checkpoint,
|
||||
justification_bits: BitVector<T::JustificationBitsLength>,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> JustificationAndFinalizationState<T> {
|
||||
pub fn new(state: &BeaconState<T>) -> Self {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
Self {
|
||||
previous_epoch,
|
||||
previous_epoch_target_root: state.get_block_root_at_epoch(previous_epoch).copied(),
|
||||
current_epoch,
|
||||
current_epoch_target_root: state.get_block_root_at_epoch(current_epoch).copied(),
|
||||
previous_justified_checkpoint: state.previous_justified_checkpoint(),
|
||||
current_justified_checkpoint: state.current_justified_checkpoint(),
|
||||
finalized_checkpoint: state.finalized_checkpoint(),
|
||||
justification_bits: state.justification_bits().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_changes_to_state(self, state: &mut BeaconState<T>) {
|
||||
let Self {
|
||||
/*
|
||||
* Immutable fields do not need to be used.
|
||||
*/
|
||||
previous_epoch: _,
|
||||
previous_epoch_target_root: _,
|
||||
current_epoch: _,
|
||||
current_epoch_target_root: _,
|
||||
/*
|
||||
* Mutable fields *must* be used.
|
||||
*/
|
||||
previous_justified_checkpoint,
|
||||
current_justified_checkpoint,
|
||||
finalized_checkpoint,
|
||||
justification_bits,
|
||||
} = self;
|
||||
|
||||
*state.previous_justified_checkpoint_mut() = previous_justified_checkpoint;
|
||||
*state.current_justified_checkpoint_mut() = current_justified_checkpoint;
|
||||
*state.finalized_checkpoint_mut() = finalized_checkpoint;
|
||||
*state.justification_bits_mut() = justification_bits;
|
||||
}
|
||||
|
||||
pub fn previous_epoch(&self) -> Epoch {
|
||||
self.previous_epoch
|
||||
}
|
||||
|
||||
pub fn current_epoch(&self) -> Epoch {
|
||||
self.current_epoch
|
||||
}
|
||||
|
||||
pub fn get_block_root_at_epoch(&self, epoch: Epoch) -> Result<Hash256, BeaconStateError> {
|
||||
if epoch == self.previous_epoch {
|
||||
self.previous_epoch_target_root.clone()
|
||||
} else if epoch == self.current_epoch {
|
||||
self.current_epoch_target_root.clone()
|
||||
} else {
|
||||
Err(BeaconStateError::SlotOutOfBounds)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous_justified_checkpoint(&self) -> Checkpoint {
|
||||
self.previous_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn previous_justified_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.previous_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn current_justified_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.current_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn current_justified_checkpoint(&self) -> Checkpoint {
|
||||
self.current_justified_checkpoint
|
||||
}
|
||||
|
||||
pub fn finalized_checkpoint(&self) -> Checkpoint {
|
||||
self.finalized_checkpoint
|
||||
}
|
||||
|
||||
pub fn finalized_checkpoint_mut(&mut self) -> &mut Checkpoint {
|
||||
&mut self.finalized_checkpoint
|
||||
}
|
||||
|
||||
pub fn justification_bits(&self) -> &BitVector<T::JustificationBitsLength> {
|
||||
&self.justification_bits
|
||||
}
|
||||
|
||||
pub fn justification_bits_mut(&mut self) -> &mut BitVector<T::JustificationBitsLength> {
|
||||
&mut self.justification_bits
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ use bls::Hash256;
|
||||
use env_logger::{Builder, Env};
|
||||
use types::Slot;
|
||||
|
||||
#[test]
|
||||
fn runs_without_error() {
|
||||
#[tokio::test]
|
||||
async fn runs_without_error() {
|
||||
Builder::from_env(Env::default().default_filter_or("error")).init();
|
||||
|
||||
let harness = BeaconChainHarness::builder(MinimalEthSpec)
|
||||
@@ -22,15 +22,17 @@ fn runs_without_error() {
|
||||
(MinimalEthSpec::genesis_epoch() + 4).end_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
let state = harness.get_current_state();
|
||||
harness.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
(1..target_slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..8).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
harness
|
||||
.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
(1..target_slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..8).collect::<Vec<_>>().as_slice(),
|
||||
)
|
||||
.await;
|
||||
let mut new_head_state = harness.get_current_state();
|
||||
|
||||
process_epoch(&mut new_head_state, &spec).unwrap();
|
||||
@@ -45,8 +47,8 @@ mod release_tests {
|
||||
use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy};
|
||||
use types::{Epoch, ForkName, InconsistentFork, MainnetEthSpec};
|
||||
|
||||
#[test]
|
||||
fn altair_state_on_base_fork() {
|
||||
#[tokio::test]
|
||||
async fn altair_state_on_base_fork() {
|
||||
let mut spec = MainnetEthSpec::default_spec();
|
||||
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
||||
// The Altair fork happens at epoch 1.
|
||||
@@ -61,12 +63,14 @@ mod release_tests {
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness.extend_chain(
|
||||
// Build out enough blocks so we get an Altair block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
harness
|
||||
.extend_chain(
|
||||
// Build out enough blocks so we get an Altair block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
harness.get_current_state()
|
||||
};
|
||||
@@ -103,8 +107,8 @@ mod release_tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_state_on_altair_fork() {
|
||||
#[tokio::test]
|
||||
async fn base_state_on_altair_fork() {
|
||||
let mut spec = MainnetEthSpec::default_spec();
|
||||
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
||||
// The Altair fork never happens.
|
||||
@@ -119,12 +123,14 @@ mod release_tests {
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness.extend_chain(
|
||||
// Build out enough blocks so we get a block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
harness
|
||||
.extend_chain(
|
||||
// Build out enough blocks so we get a block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
)
|
||||
.await;
|
||||
|
||||
harness.get_current_state()
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::per_epoch_processing::Error;
|
||||
use crate::per_epoch_processing::{Error, JustificationAndFinalizationState};
|
||||
use safe_arith::SafeArith;
|
||||
use std::ops::Range;
|
||||
use types::{BeaconState, Checkpoint, EthSpec};
|
||||
use types::{Checkpoint, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
|
||||
pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
mut state: JustificationAndFinalizationState<T>,
|
||||
total_active_balance: u64,
|
||||
previous_target_balance: u64,
|
||||
current_target_balance: u64,
|
||||
) -> Result<(), Error> {
|
||||
) -> Result<JustificationAndFinalizationState<T>, Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
if previous_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: previous_epoch,
|
||||
root: *state.get_block_root_at_epoch(previous_epoch)?,
|
||||
root: state.get_block_root_at_epoch(previous_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(1, true)?;
|
||||
}
|
||||
@@ -32,7 +32,7 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
if current_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: *state.get_block_root_at_epoch(current_epoch)?,
|
||||
root: state.get_block_root_at_epoch(current_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(0, true)?;
|
||||
}
|
||||
@@ -66,5 +66,5 @@ pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ pub fn translate_participation<E: EthSpec>(
|
||||
for index in attesting_indices {
|
||||
for flag_index in &participation_flag_indices {
|
||||
epoch_participation
|
||||
.get_mut(index)
|
||||
.ok_or(Error::UnknownValidator(index))?
|
||||
.get_mut(index as usize)
|
||||
.ok_or(Error::UnknownValidator(index as usize))?
|
||||
.add_flag(*flag_index)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,36 +5,120 @@ use crate::per_block_processing::{
|
||||
verify_attester_slashing, verify_exit, verify_proposer_slashing,
|
||||
};
|
||||
use crate::VerifySignatures;
|
||||
use derivative::Derivative;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use ssz::{Decode, Encode};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use std::marker::PhantomData;
|
||||
use types::{
|
||||
AttesterSlashing, BeaconState, ChainSpec, EthSpec, ProposerSlashing, SignedVoluntaryExit,
|
||||
AttesterSlashing, BeaconState, ChainSpec, Epoch, EthSpec, Fork, ForkVersion, ProposerSlashing,
|
||||
SignedVoluntaryExit,
|
||||
};
|
||||
|
||||
const MAX_FORKS_VERIFIED_AGAINST: usize = 2;
|
||||
|
||||
/// Wrapper around an operation type that acts as proof that its signature has been checked.
|
||||
///
|
||||
/// The inner field is private, meaning instances of this type can only be constructed
|
||||
/// The inner `op` field is private, meaning instances of this type can only be constructed
|
||||
/// by calling `validate`.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct SigVerifiedOp<T>(T);
|
||||
#[derive(Derivative, Debug, Clone, Encode, Decode)]
|
||||
#[derivative(
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash(bound = "T: Encode + Decode + std::hash::Hash, E: EthSpec")
|
||||
)]
|
||||
pub struct SigVerifiedOp<T: Encode + Decode, E: EthSpec> {
|
||||
op: T,
|
||||
verified_against: VerifiedAgainst,
|
||||
#[ssz(skip_serializing, skip_deserializing)]
|
||||
_phantom: PhantomData<E>,
|
||||
}
|
||||
|
||||
/// Information about the fork versions that this message was verified against.
|
||||
///
|
||||
/// In general it is not safe to assume that a `SigVerifiedOp` constructed at some point in the past
|
||||
/// will continue to be valid in the presence of a changing `state.fork()`. The reason for this
|
||||
/// is that the fork versions that the message's epochs map to might change.
|
||||
///
|
||||
/// For example a proposer slashing at a phase0 slot verified against an Altair state will use
|
||||
/// the phase0 fork version, but will become invalid once the Bellatrix fork occurs because that
|
||||
/// slot will start to map to the Altair fork version. This is because `Fork::get_fork_version` only
|
||||
/// remembers the most recent two forks.
|
||||
///
|
||||
/// In the other direction, a proposer slashing at a Bellatrix slot verified against an Altair state
|
||||
/// will use the Altair fork version, but will become invalid once the Bellatrix fork occurs because
|
||||
/// that slot will start to map to the Bellatrix fork version.
|
||||
///
|
||||
/// We need to store multiple `ForkVersion`s because attester slashings contain two indexed
|
||||
/// attestations which may be signed using different versions.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Encode, Decode)]
|
||||
pub struct VerifiedAgainst {
|
||||
fork_versions: SmallVec<[ForkVersion; MAX_FORKS_VERIFIED_AGAINST]>,
|
||||
}
|
||||
|
||||
impl<T, E> SigVerifiedOp<T, E>
|
||||
where
|
||||
T: VerifyOperation<E>,
|
||||
E: EthSpec,
|
||||
{
|
||||
/// This function must be private because it assumes that `op` has already been verified.
|
||||
fn new(op: T, state: &BeaconState<E>) -> Self {
|
||||
let verified_against = VerifiedAgainst {
|
||||
fork_versions: op
|
||||
.verification_epochs()
|
||||
.into_iter()
|
||||
.map(|epoch| state.fork().get_fork_version(epoch))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
SigVerifiedOp {
|
||||
op,
|
||||
verified_against,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SigVerifiedOp<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
self.op
|
||||
}
|
||||
|
||||
pub fn as_inner(&self) -> &T {
|
||||
&self.0
|
||||
&self.op
|
||||
}
|
||||
|
||||
pub fn signature_is_still_valid(&self, current_fork: &Fork) -> bool {
|
||||
self.as_inner()
|
||||
.verification_epochs()
|
||||
.into_iter()
|
||||
.zip(self.verified_against.fork_versions.iter())
|
||||
.all(|(epoch, verified_fork_version)| {
|
||||
current_fork.get_fork_version(epoch) == *verified_fork_version
|
||||
})
|
||||
}
|
||||
|
||||
/// Return one of the fork versions this message was verified against.
|
||||
///
|
||||
/// This is only required for the v12 schema downgrade and can be deleted once all nodes
|
||||
/// are upgraded to v12.
|
||||
pub fn first_fork_verified_against(&self) -> Option<ForkVersion> {
|
||||
self.verified_against.fork_versions.first().copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for operations that can be verified and transformed into a `SigVerifiedOp`.
|
||||
pub trait VerifyOperation<E: EthSpec>: Sized {
|
||||
pub trait VerifyOperation<E: EthSpec>: Encode + Decode + Sized {
|
||||
type Error;
|
||||
|
||||
fn validate(
|
||||
self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<SigVerifiedOp<Self>, Self::Error>;
|
||||
) -> Result<SigVerifiedOp<Self, E>, Self::Error>;
|
||||
|
||||
/// Return the epochs at which parts of this message were verified.
|
||||
///
|
||||
/// These need to map 1-to-1 to the `SigVerifiedOp::verified_against` for this type.
|
||||
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]>;
|
||||
}
|
||||
|
||||
impl<E: EthSpec> VerifyOperation<E> for SignedVoluntaryExit {
|
||||
@@ -44,9 +128,14 @@ impl<E: EthSpec> VerifyOperation<E> for SignedVoluntaryExit {
|
||||
self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<SigVerifiedOp<Self>, Self::Error> {
|
||||
) -> Result<SigVerifiedOp<Self, E>, Self::Error> {
|
||||
verify_exit(state, &self, VerifySignatures::True, spec)?;
|
||||
Ok(SigVerifiedOp(self))
|
||||
Ok(SigVerifiedOp::new(self, state))
|
||||
}
|
||||
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
|
||||
smallvec![self.message.epoch]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +146,17 @@ impl<E: EthSpec> VerifyOperation<E> for AttesterSlashing<E> {
|
||||
self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<SigVerifiedOp<Self>, Self::Error> {
|
||||
) -> Result<SigVerifiedOp<Self, E>, Self::Error> {
|
||||
verify_attester_slashing(state, &self, VerifySignatures::True, spec)?;
|
||||
Ok(SigVerifiedOp(self))
|
||||
Ok(SigVerifiedOp::new(self, state))
|
||||
}
|
||||
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
|
||||
smallvec![
|
||||
self.attestation_1.data.target.epoch,
|
||||
self.attestation_2.data.target.epoch
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +167,18 @@ impl<E: EthSpec> VerifyOperation<E> for ProposerSlashing {
|
||||
self,
|
||||
state: &BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<SigVerifiedOp<Self>, Self::Error> {
|
||||
) -> Result<SigVerifiedOp<Self, E>, Self::Error> {
|
||||
verify_proposer_slashing(&self, state, VerifySignatures::True, spec)?;
|
||||
Ok(SigVerifiedOp(self))
|
||||
Ok(SigVerifiedOp::new(self, state))
|
||||
}
|
||||
|
||||
#[allow(clippy::integer_arithmetic)]
|
||||
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
|
||||
// Only need a single epoch because the slots of the two headers must be equal.
|
||||
smallvec![self
|
||||
.signed_header_1
|
||||
.message
|
||||
.slot
|
||||
.epoch(E::slots_per_epoch())]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ fn get_harness<T: EthSpec>() -> BeaconChainHarness<EphemeralHarnessType<T>> {
|
||||
}
|
||||
|
||||
fn build_state<T: EthSpec>() -> BeaconState<T> {
|
||||
let state = get_harness::<T>().chain.head_beacon_state().unwrap();
|
||||
let state = get_harness::<T>().chain.head_beacon_state_cloned();
|
||||
|
||||
assert_eq!(state.as_base().unwrap().validators.len(), VALIDATOR_COUNT);
|
||||
assert_eq!(state.as_base().unwrap().balances.len(), VALIDATOR_COUNT);
|
||||
|
||||
@@ -48,12 +48,15 @@ serde_json = "1.0.74"
|
||||
smallvec = "1.8.0"
|
||||
milhouse = { git = "https://github.com/sigp/milhouse", branch = "main" }
|
||||
rpds = "0.11.0"
|
||||
serde_with = "1.13.0"
|
||||
maplit = "1.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.3"
|
||||
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||
eth2_interop_keypairs = { path = "../../common/eth2_interop_keypairs" }
|
||||
state_processing = { path = "../state_processing" }
|
||||
tokio = "1.14.0"
|
||||
|
||||
[features]
|
||||
default = ["sqlite", "legacy-arith"]
|
||||
|
||||
16
consensus/types/src/application_domain.rs
Normal file
16
consensus/types/src/application_domain.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
/// This value is an application index of 0 with the bitmask applied (so it's equivalent to the bit mask).
|
||||
/// Little endian hex: 0x00000001, Binary: 1000000000000000000000000
|
||||
pub const APPLICATION_DOMAIN_BUILDER: u32 = 16777216;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum ApplicationDomain {
|
||||
Builder,
|
||||
}
|
||||
|
||||
impl ApplicationDomain {
|
||||
pub fn get_domain_constant(&self) -> u32 {
|
||||
match self {
|
||||
ApplicationDomain::Builder => APPLICATION_DOMAIN_BUILDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ use tree_hash_derive::TreeHash;
|
||||
derive(Debug, PartialEq, TreeHash),
|
||||
tree_hash(enum_behaviour = "transparent")
|
||||
),
|
||||
map_ref_into(BeaconBlockBodyRef),
|
||||
map_ref_into(BeaconBlockBodyRef, BeaconBlock),
|
||||
map_ref_mut_into(BeaconBlockBodyRefMut)
|
||||
)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
|
||||
@@ -541,6 +541,50 @@ impl_from!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body:
|
||||
impl_from!(BeaconBlockAltair, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyAltair<_, _>| body.into());
|
||||
impl_from!(BeaconBlockMerge, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyMerge<_, _>| body.into());
|
||||
|
||||
// We can clone blocks with payloads to blocks without payloads, without cloning the payload.
|
||||
macro_rules! impl_clone_as_blinded {
|
||||
($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>) => {
|
||||
impl<E: EthSpec> $ty_name<$($from_params),*>
|
||||
{
|
||||
pub fn clone_as_blinded(&self) -> $ty_name<$($to_params),*> {
|
||||
let $ty_name {
|
||||
slot,
|
||||
proposer_index,
|
||||
parent_root,
|
||||
state_root,
|
||||
body,
|
||||
} = self;
|
||||
|
||||
$ty_name {
|
||||
slot: *slot,
|
||||
proposer_index: *proposer_index,
|
||||
parent_root: *parent_root,
|
||||
state_root: *state_root,
|
||||
body: body.clone_as_blinded(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_clone_as_blinded!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
|
||||
impl_clone_as_blinded!(BeaconBlockAltair, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
|
||||
impl_clone_as_blinded!(BeaconBlockMerge, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
|
||||
|
||||
// A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the
|
||||
// execution payload.
|
||||
impl<'a, E: EthSpec> From<BeaconBlockRef<'a, E, FullPayload<E>>>
|
||||
for BeaconBlock<E, BlindedPayload<E>>
|
||||
{
|
||||
fn from(
|
||||
full_block: BeaconBlockRef<'a, E, FullPayload<E>>,
|
||||
) -> BeaconBlock<E, BlindedPayload<E>> {
|
||||
map_beacon_block_ref_into_beacon_block!(&'a _, full_block, |inner, cons| {
|
||||
cons(inner.clone_as_blinded())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlock<E, BlindedPayload<E>>,
|
||||
@@ -607,19 +651,17 @@ mod tests {
|
||||
#[test]
|
||||
fn decode_base_and_altair() {
|
||||
type E = MainnetEthSpec;
|
||||
let spec = E::default_spec();
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
|
||||
let fork_epoch = spec.altair_fork_epoch.unwrap();
|
||||
|
||||
let base_epoch = fork_epoch.saturating_sub(1_u64);
|
||||
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
|
||||
let altair_epoch = fork_epoch;
|
||||
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(fork_epoch);
|
||||
|
||||
// BeaconBlockBase
|
||||
{
|
||||
let good_base_block = BeaconBlock::Base(BeaconBlockBase {
|
||||
|
||||
@@ -251,6 +251,53 @@ impl<E: EthSpec> From<BeaconBlockBodyMerge<E, FullPayload<E>>>
|
||||
}
|
||||
}
|
||||
|
||||
// We can clone a full block into a blinded block, without cloning the payload.
|
||||
impl<E: EthSpec> BeaconBlockBodyBase<E, FullPayload<E>> {
|
||||
pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase<E, BlindedPayload<E>> {
|
||||
let (block_body, _payload) = self.clone().into();
|
||||
block_body
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> BeaconBlockBodyAltair<E, FullPayload<E>> {
|
||||
pub fn clone_as_blinded(&self) -> BeaconBlockBodyAltair<E, BlindedPayload<E>> {
|
||||
let (block_body, _payload) = self.clone().into();
|
||||
block_body
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> BeaconBlockBodyMerge<E, FullPayload<E>> {
|
||||
pub fn clone_as_blinded(&self) -> BeaconBlockBodyMerge<E, BlindedPayload<E>> {
|
||||
let BeaconBlockBodyMerge {
|
||||
randao_reveal,
|
||||
eth1_data,
|
||||
graffiti,
|
||||
proposer_slashings,
|
||||
attester_slashings,
|
||||
attestations,
|
||||
deposits,
|
||||
voluntary_exits,
|
||||
sync_aggregate,
|
||||
execution_payload: FullPayload { execution_payload },
|
||||
} = self;
|
||||
|
||||
BeaconBlockBodyMerge {
|
||||
randao_reveal: randao_reveal.clone(),
|
||||
eth1_data: eth1_data.clone(),
|
||||
graffiti: *graffiti,
|
||||
proposer_slashings: proposer_slashings.clone(),
|
||||
attester_slashings: attester_slashings.clone(),
|
||||
attestations: attestations.clone(),
|
||||
deposits: deposits.clone(),
|
||||
voluntary_exits: voluntary_exits.clone(),
|
||||
sync_aggregate: sync_aggregate.clone(),
|
||||
execution_payload: BlindedPayload {
|
||||
execution_payload_header: From::from(execution_payload),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
|
||||
for (
|
||||
BeaconBlockBody<E, BlindedPayload<E>>,
|
||||
|
||||
@@ -985,6 +985,13 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the minimum epoch for which `get_randao_mix` will return a non-error value.
|
||||
pub fn min_randao_epoch(&self) -> Epoch {
|
||||
self.current_epoch()
|
||||
.saturating_add(1u64)
|
||||
.saturating_sub(T::EpochsPerHistoricalVector::to_u64())
|
||||
}
|
||||
|
||||
/// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`.
|
||||
///
|
||||
/// # Errors:
|
||||
@@ -1625,15 +1632,17 @@ impl<T: EthSpec> BeaconState<T> {
|
||||
Ok(self.validators().tree_hash_root())
|
||||
}
|
||||
|
||||
pub fn is_eligible_validator(&self, val: &Validator) -> bool {
|
||||
let previous_epoch = self.previous_epoch();
|
||||
/// Passing `previous_epoch` to this function rather than computing it internally provides
|
||||
/// a tangible speed improvement in state processing.
|
||||
pub fn is_eligible_validator(&self, previous_epoch: Epoch, val: &Validator) -> bool {
|
||||
val.is_active_at(previous_epoch)
|
||||
|| (val.slashed && previous_epoch + Epoch::new(1) < val.withdrawable_epoch)
|
||||
}
|
||||
|
||||
pub fn is_in_inactivity_leak(&self, spec: &ChainSpec) -> bool {
|
||||
(self.previous_epoch() - self.finalized_checkpoint().epoch)
|
||||
> spec.min_epochs_to_inactivity_penalty
|
||||
/// Passing `previous_epoch` to this function rather than computing it internally provides
|
||||
/// a tangible speed improvement in state processing.
|
||||
pub fn is_in_inactivity_leak(&self, previous_epoch: Epoch, spec: &ChainSpec) -> bool {
|
||||
(previous_epoch - self.finalized_checkpoint().epoch) > spec.min_epochs_to_inactivity_penalty
|
||||
}
|
||||
|
||||
/// Get the `SyncCommittee` associated with the next slot. Useful because sync committees
|
||||
|
||||
@@ -39,8 +39,18 @@ impl CommitteeCache {
|
||||
epoch: Epoch,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Arc<CommitteeCache>, Error> {
|
||||
RelativeEpoch::from_epoch(state.current_epoch(), epoch)
|
||||
.map_err(|_| Error::EpochOutOfBounds)?;
|
||||
// Check that the cache is being built for an in-range epoch.
|
||||
//
|
||||
// We allow caches to be constructed for historic epochs, per:
|
||||
//
|
||||
// https://github.com/sigp/lighthouse/issues/3270
|
||||
let reqd_randao_epoch = epoch
|
||||
.saturating_sub(spec.min_seed_lookahead)
|
||||
.saturating_sub(1u64);
|
||||
|
||||
if reqd_randao_epoch < state.min_randao_epoch() || epoch > state.current_epoch() + 1 {
|
||||
return Err(Error::EpochOutOfBounds);
|
||||
}
|
||||
|
||||
// May cause divide-by-zero errors.
|
||||
if T::slots_per_epoch() == 0 {
|
||||
|
||||
@@ -34,32 +34,34 @@ fn default_values() {
|
||||
assert!(cache.get_beacon_committees_at_slot(Slot::new(0)).is_err());
|
||||
}
|
||||
|
||||
fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
|
||||
async fn new_state<T: EthSpec>(validator_count: usize, slot: Slot) -> BeaconState<T> {
|
||||
let harness = get_harness(validator_count);
|
||||
let head_state = harness.get_current_state();
|
||||
if slot > Slot::new(0) {
|
||||
harness.add_attested_blocks_at_slots(
|
||||
head_state,
|
||||
Hash256::zero(),
|
||||
(1..slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
harness
|
||||
.add_attested_blocks_at_slots(
|
||||
head_state,
|
||||
Hash256::zero(),
|
||||
(1..=slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
harness.get_current_state()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
fn fails_without_validators() {
|
||||
new_state::<MinimalEthSpec>(0, Slot::new(0));
|
||||
async fn fails_without_validators() {
|
||||
new_state::<MinimalEthSpec>(0, Slot::new(0)).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initializes_with_the_right_epoch() {
|
||||
let state = new_state::<MinimalEthSpec>(16, Slot::new(0));
|
||||
#[tokio::test]
|
||||
async fn initializes_with_the_right_epoch() {
|
||||
let state = new_state::<MinimalEthSpec>(16, Slot::new(0)).await;
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
let cache = CommitteeCache::default();
|
||||
@@ -75,17 +77,20 @@ fn initializes_with_the_right_epoch() {
|
||||
assert!(cache.is_initialized_at(state.next_epoch().unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shuffles_for_the_right_epoch() {
|
||||
#[tokio::test]
|
||||
async fn shuffles_for_the_right_epoch() {
|
||||
let num_validators = MinimalEthSpec::minimum_validator_count() * 2;
|
||||
let epoch = Epoch::new(6);
|
||||
let slot = epoch.start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
|
||||
let mut state = new_state::<MinimalEthSpec>(num_validators, slot);
|
||||
let mut state = new_state::<MinimalEthSpec>(num_validators, slot).await;
|
||||
let spec = &MinimalEthSpec::default_spec();
|
||||
|
||||
let distinct_hashes = (0..MinimalEthSpec::epochs_per_historical_vector())
|
||||
.map(|i| Hash256::from_low_u64_be(i as u64));
|
||||
assert_eq!(state.current_epoch(), epoch);
|
||||
|
||||
let distinct_hashes: Vec<Hash256> = (0..MinimalEthSpec::epochs_per_historical_vector())
|
||||
.map(|i| Hash256::from_low_u64_be(i as u64))
|
||||
.collect();
|
||||
|
||||
*state.randao_mixes_mut() = FixedVector::try_from_iter(distinct_hashes).unwrap();
|
||||
|
||||
@@ -121,15 +126,41 @@ fn shuffles_for_the_right_epoch() {
|
||||
}
|
||||
};
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.current_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling(), shuffling_with_seed(current_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
// We can initialize the committee cache at recent epochs in the past, and one epoch into the
|
||||
// future.
|
||||
for e in (0..=epoch.as_u64() + 1).map(Epoch::new) {
|
||||
let seed = state.get_seed(e, Domain::BeaconAttester, spec).unwrap();
|
||||
let cache = CommitteeCache::initialized(&state, e, spec)
|
||||
.unwrap_or_else(|_| panic!("failed at epoch {}", e));
|
||||
assert_eq!(cache.shuffling(), shuffling_with_seed(seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
}
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.previous_epoch(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling(), shuffling_with_seed(previous_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
|
||||
let cache = CommitteeCache::initialized(&state, state.next_epoch().unwrap(), spec).unwrap();
|
||||
assert_eq!(cache.shuffling(), shuffling_with_seed(next_seed));
|
||||
assert_shuffling_positions_accurate(&cache);
|
||||
// We should *not* be able to build a committee cache for the epoch after the next epoch.
|
||||
assert_eq!(
|
||||
CommitteeCache::initialized(&state, epoch + 2, spec),
|
||||
Err(BeaconStateError::EpochOutOfBounds)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn min_randao_epoch_correct() {
|
||||
let num_validators = MinimalEthSpec::minimum_validator_count() * 2;
|
||||
let current_epoch = Epoch::new(MinimalEthSpec::epochs_per_historical_vector() as u64 * 2);
|
||||
|
||||
let mut state = new_state::<MinimalEthSpec>(
|
||||
num_validators,
|
||||
Epoch::new(1).start_slot(MinimalEthSpec::slots_per_epoch()),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Override the epoch so that there's some room to move.
|
||||
*state.slot_mut() = current_epoch.start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
assert_eq!(state.current_epoch(), current_epoch);
|
||||
|
||||
// The min_randao_epoch should be the minimum epoch such that `get_randao_mix` returns `Ok`.
|
||||
let min_randao_epoch = state.min_randao_epoch();
|
||||
state.get_randao_mix(min_randao_epoch).unwrap();
|
||||
state.get_randao_mix(min_randao_epoch - 1).unwrap_err();
|
||||
state.get_randao_mix(min_randao_epoch + 1).unwrap();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ use beacon_chain::types::{
|
||||
ChainSpec, Domain, Epoch, EthSpec, FixedVector, Hash256, Keypair, MainnetEthSpec,
|
||||
MinimalEthSpec, RelativeEpoch, Slot,
|
||||
};
|
||||
use safe_arith::SafeArith;
|
||||
use ssz::{Decode, Encode};
|
||||
use state_processing::per_slot_processing;
|
||||
use std::ops::Mul;
|
||||
use swap_or_not_shuffle::compute_shuffled_index;
|
||||
use tree_hash::TreeHash;
|
||||
@@ -20,7 +22,7 @@ lazy_static! {
|
||||
static ref KEYPAIRS: Vec<Keypair> = generate_deterministic_keypairs(MAX_VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
fn get_harness<E: EthSpec>(
|
||||
async fn get_harness<E: EthSpec>(
|
||||
validator_count: usize,
|
||||
slot: Slot,
|
||||
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
||||
@@ -36,24 +38,26 @@ fn get_harness<E: EthSpec>(
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>();
|
||||
let state = harness.get_current_state();
|
||||
harness.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
slots.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
harness
|
||||
.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
slots.as_slice(),
|
||||
(0..validator_count).collect::<Vec<_>>().as_slice(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
harness
|
||||
}
|
||||
|
||||
fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
||||
async fn build_state<E: EthSpec>(validator_count: usize) -> BeaconState<E> {
|
||||
get_harness(validator_count, Slot::new(0))
|
||||
.await
|
||||
.chain
|
||||
.head_beacon_state()
|
||||
.unwrap()
|
||||
.head_beacon_state_cloned()
|
||||
}
|
||||
|
||||
fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
async fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
let spec = T::default_spec();
|
||||
|
||||
// Get the i'th candidate proposer for the given state and slot
|
||||
@@ -80,20 +84,20 @@ fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
|
||||
// Test where we have one validator per slot.
|
||||
// 0th candidate should be chosen every time.
|
||||
let state = build_state(T::slots_per_epoch() as usize);
|
||||
let state = build_state(T::slots_per_epoch() as usize).await;
|
||||
for i in 0..T::slots_per_epoch() {
|
||||
test(&state, Slot::from(i), 0);
|
||||
}
|
||||
|
||||
// Test where we have two validators per slot.
|
||||
// 0th candidate should be chosen every time.
|
||||
let state = build_state((T::slots_per_epoch() as usize).mul(2));
|
||||
let state = build_state((T::slots_per_epoch() as usize).mul(2)).await;
|
||||
for i in 0..T::slots_per_epoch() {
|
||||
test(&state, Slot::from(i), 0);
|
||||
}
|
||||
|
||||
// Test with two validators per slot, first validator has zero balance.
|
||||
let mut state = build_state::<T>((T::slots_per_epoch() as usize).mul(2));
|
||||
let mut state = build_state::<T>((T::slots_per_epoch() as usize).mul(2)).await;
|
||||
let slot0_candidate0 = ith_candidate(&state, Slot::new(0), 0, &spec);
|
||||
state
|
||||
.validators_mut()
|
||||
@@ -106,9 +110,9 @@ fn test_beacon_proposer_index<T: EthSpec>() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_proposer_index() {
|
||||
test_beacon_proposer_index::<MinimalEthSpec>();
|
||||
#[tokio::test]
|
||||
async fn beacon_proposer_index() {
|
||||
test_beacon_proposer_index::<MinimalEthSpec>().await;
|
||||
}
|
||||
|
||||
/// Test that
|
||||
@@ -143,11 +147,11 @@ fn test_cache_initialization<T: EthSpec>(
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_initialization() {
|
||||
#[tokio::test]
|
||||
async fn cache_initialization() {
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let mut state = build_state::<MinimalEthSpec>(16);
|
||||
let mut state = build_state::<MinimalEthSpec>(16).await;
|
||||
|
||||
*state.slot_mut() =
|
||||
(MinimalEthSpec::genesis_epoch() + 1).start_slot(MinimalEthSpec::slots_per_epoch());
|
||||
@@ -236,7 +240,7 @@ mod committees {
|
||||
assert!(expected_indices_iter.next().is_none());
|
||||
}
|
||||
|
||||
fn committee_consistency_test<T: EthSpec>(
|
||||
async fn committee_consistency_test<T: EthSpec>(
|
||||
validator_count: usize,
|
||||
state_epoch: Epoch,
|
||||
cache_epoch: RelativeEpoch,
|
||||
@@ -244,7 +248,7 @@ mod committees {
|
||||
let spec = &T::default_spec();
|
||||
|
||||
let slot = state_epoch.start_slot(T::slots_per_epoch());
|
||||
let harness = get_harness::<T>(validator_count, slot);
|
||||
let harness = get_harness::<T>(validator_count, slot).await;
|
||||
let mut new_head_state = harness.get_current_state();
|
||||
|
||||
let distinct_hashes =
|
||||
@@ -271,7 +275,7 @@ mod committees {
|
||||
);
|
||||
}
|
||||
|
||||
fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
|
||||
async fn committee_consistency_test_suite<T: EthSpec>(cached_epoch: RelativeEpoch) {
|
||||
let spec = T::default_spec();
|
||||
|
||||
let validator_count = spec
|
||||
@@ -280,13 +284,15 @@ mod committees {
|
||||
.mul(spec.target_committee_size)
|
||||
.add(1);
|
||||
|
||||
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch);
|
||||
committee_consistency_test::<T>(validator_count as usize, Epoch::new(0), cached_epoch)
|
||||
.await;
|
||||
|
||||
committee_consistency_test::<T>(
|
||||
validator_count as usize,
|
||||
T::genesis_epoch() + 4,
|
||||
cached_epoch,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
committee_consistency_test::<T>(
|
||||
validator_count as usize,
|
||||
@@ -295,38 +301,39 @@ mod committees {
|
||||
.mul(T::slots_per_epoch())
|
||||
.mul(4),
|
||||
cached_epoch,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Current);
|
||||
#[tokio::test]
|
||||
async fn current_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Current).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn previous_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Previous);
|
||||
#[tokio::test]
|
||||
async fn previous_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Previous).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Next);
|
||||
#[tokio::test]
|
||||
async fn next_epoch_committee_consistency() {
|
||||
committee_consistency_test_suite::<MinimalEthSpec>(RelativeEpoch::Next).await;
|
||||
}
|
||||
}
|
||||
|
||||
mod get_outstanding_deposit_len {
|
||||
use super::*;
|
||||
|
||||
fn state() -> BeaconState<MinimalEthSpec> {
|
||||
async fn state() -> BeaconState<MinimalEthSpec> {
|
||||
get_harness(16, Slot::new(0))
|
||||
.await
|
||||
.chain
|
||||
.head_beacon_state()
|
||||
.unwrap()
|
||||
.head_beacon_state_cloned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_ok() {
|
||||
let mut state = state();
|
||||
#[tokio::test]
|
||||
async fn returns_ok() {
|
||||
let mut state = state().await;
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(0));
|
||||
|
||||
state.eth1_data_mut().deposit_count = 17;
|
||||
@@ -334,9 +341,9 @@ mod get_outstanding_deposit_len {
|
||||
assert_eq!(state.get_outstanding_deposit_len(), Ok(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_err_if_the_state_is_invalid() {
|
||||
let mut state = state();
|
||||
#[tokio::test]
|
||||
async fn returns_err_if_the_state_is_invalid() {
|
||||
let mut state = state().await;
|
||||
// The state is invalid, deposit count is lower than deposit index.
|
||||
state.eth1_data_mut().deposit_count = 16;
|
||||
*state.eth1_deposit_index_mut() = 17;
|
||||
@@ -354,62 +361,60 @@ mod get_outstanding_deposit_len {
|
||||
#[test]
|
||||
fn decode_base_and_altair() {
|
||||
type E = MainnetEthSpec;
|
||||
let spec = E::default_spec();
|
||||
|
||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let fork_epoch = Epoch::from_ssz_bytes(&[7, 6, 5, 4, 3, 2, 1, 0]).unwrap();
|
||||
let fork_epoch = spec.altair_fork_epoch.unwrap();
|
||||
|
||||
let base_epoch = fork_epoch.saturating_sub(1_u64);
|
||||
let base_slot = base_epoch.end_slot(E::slots_per_epoch());
|
||||
let altair_epoch = fork_epoch;
|
||||
let altair_slot = altair_epoch.start_slot(E::slots_per_epoch());
|
||||
|
||||
let mut spec = E::default_spec();
|
||||
spec.altair_fork_epoch = Some(altair_epoch);
|
||||
|
||||
// BeaconStateBase
|
||||
{
|
||||
let good_base_block: BeaconState<MainnetEthSpec> = BeaconState::Base(BeaconStateBase {
|
||||
let good_base_state: BeaconState<MainnetEthSpec> = BeaconState::Base(BeaconStateBase {
|
||||
slot: base_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have a base block with a slot higher than the fork slot.
|
||||
let bad_base_block = {
|
||||
let mut bad = good_base_block.clone();
|
||||
// It's invalid to have a base state with a slot higher than the fork slot.
|
||||
let bad_base_state = {
|
||||
let mut bad = good_base_state.clone();
|
||||
*bad.slot_mut() = altair_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconState::from_ssz_bytes(&good_base_block.as_ssz_bytes(), &spec)
|
||||
.expect("good base block can be decoded"),
|
||||
good_base_block
|
||||
BeaconState::from_ssz_bytes(&good_base_state.as_ssz_bytes(), &spec)
|
||||
.expect("good base state can be decoded"),
|
||||
good_base_state
|
||||
);
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_base_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad base block cannot be decoded");
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_base_state.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad base state cannot be decoded");
|
||||
}
|
||||
|
||||
// BeaconStateAltair
|
||||
{
|
||||
let good_altair_block: BeaconState<MainnetEthSpec> =
|
||||
let good_altair_state: BeaconState<MainnetEthSpec> =
|
||||
BeaconState::Altair(BeaconStateAltair {
|
||||
slot: altair_slot,
|
||||
..<_>::random_for_test(rng)
|
||||
});
|
||||
// It's invalid to have an Altair block with a slot lower than the fork slot.
|
||||
let bad_altair_block = {
|
||||
let mut bad = good_altair_block.clone();
|
||||
// It's invalid to have an Altair state with a slot lower than the fork slot.
|
||||
let bad_altair_state = {
|
||||
let mut bad = good_altair_state.clone();
|
||||
*bad.slot_mut() = base_slot;
|
||||
bad
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
BeaconState::from_ssz_bytes(&good_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect("good altair block can be decoded"),
|
||||
good_altair_block
|
||||
BeaconState::from_ssz_bytes(&good_altair_state.as_ssz_bytes(), &spec)
|
||||
.expect("good altair state can be decoded"),
|
||||
good_altair_state
|
||||
);
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_altair_block.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad altair block cannot be decoded");
|
||||
<BeaconState<MainnetEthSpec>>::from_ssz_bytes(&bad_altair_state.as_ssz_bytes(), &spec)
|
||||
.expect_err("bad altair state cannot be decoded");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
70
consensus/types/src/builder_bid.rs
Normal file
70
consensus/types/src/builder_bid.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use crate::{ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot, Uint256};
|
||||
use bls::PublicKeyBytes;
|
||||
use bls::Signature;
|
||||
use serde::{Deserialize as De, Deserializer, Serialize as Ser, Serializer};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, DeserializeAs, SerializeAs};
|
||||
use std::marker::PhantomData;
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone)]
|
||||
#[serde(bound = "E: EthSpec, Payload: ExecPayload<E>")]
|
||||
pub struct BuilderBid<E: EthSpec, Payload: ExecPayload<E>> {
|
||||
#[serde_as(as = "BlindedPayloadAsHeader<E>")]
|
||||
pub header: Payload,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u256")]
|
||||
pub value: Uint256,
|
||||
pub pubkey: PublicKeyBytes,
|
||||
#[serde(skip)]
|
||||
#[tree_hash(skip_hashing)]
|
||||
_phantom_data: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Payload: ExecPayload<E>> SignedRoot for BuilderBid<E, Payload> {}
|
||||
|
||||
/// Validator registration, for use in interacting with servers implementing the builder API.
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
#[serde(bound = "E: EthSpec, Payload: ExecPayload<E>")]
|
||||
pub struct SignedBuilderBid<E: EthSpec, Payload: ExecPayload<E>> {
|
||||
pub message: BuilderBid<E, Payload>,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
struct BlindedPayloadAsHeader<E>(PhantomData<E>);
|
||||
|
||||
impl<E: EthSpec, Payload: ExecPayload<E>> SerializeAs<Payload> for BlindedPayloadAsHeader<E> {
|
||||
fn serialize_as<S>(source: &Payload, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
source.to_execution_payload_header().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, E: EthSpec, Payload: ExecPayload<E>> DeserializeAs<'de, Payload>
|
||||
for BlindedPayloadAsHeader<E>
|
||||
{
|
||||
fn deserialize_as<D>(deserializer: D) -> Result<Payload, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let payload_header = ExecutionPayloadHeader::deserialize(deserializer)?;
|
||||
Payload::try_from(payload_header)
|
||||
.map_err(|_| serde::de::Error::custom("unable to convert payload header to payload"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: EthSpec, Payload: ExecPayload<E>> SignedBuilderBid<E, Payload> {
|
||||
pub fn verify_signature(&self, spec: &ChainSpec) -> bool {
|
||||
self.message
|
||||
.pubkey
|
||||
.decompress()
|
||||
.map(|pubkey| {
|
||||
let domain = spec.get_builder_domain();
|
||||
let message = self.message.signing_root(domain);
|
||||
self.signature.verify(&pubkey, message)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::application_domain::{ApplicationDomain, APPLICATION_DOMAIN_BUILDER};
|
||||
use crate::*;
|
||||
use eth2_serde_utils::quoted_u64::MaybeQuoted;
|
||||
use int_to_bytes::int_to_bytes4;
|
||||
@@ -20,6 +21,7 @@ pub enum Domain {
|
||||
SyncCommittee,
|
||||
ContributionAndProof,
|
||||
SyncCommitteeSelectionProof,
|
||||
ApplicationMask(ApplicationDomain),
|
||||
}
|
||||
|
||||
/// Lighthouse's internal configuration struct.
|
||||
@@ -159,6 +161,11 @@ pub struct ChainSpec {
|
||||
pub attestation_subnet_count: u64,
|
||||
pub random_subnets_per_validator: u64,
|
||||
pub epochs_per_random_subnet_subscription: u64,
|
||||
|
||||
/*
|
||||
* Application params
|
||||
*/
|
||||
pub(crate) domain_application_mask: u32,
|
||||
}
|
||||
|
||||
impl ChainSpec {
|
||||
@@ -333,6 +340,7 @@ impl ChainSpec {
|
||||
Domain::SyncCommittee => self.domain_sync_committee,
|
||||
Domain::ContributionAndProof => self.domain_contribution_and_proof,
|
||||
Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof,
|
||||
Domain::ApplicationMask(application_domain) => application_domain.get_domain_constant(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +368,17 @@ impl ChainSpec {
|
||||
self.compute_domain(Domain::Deposit, self.genesis_fork_version, Hash256::zero())
|
||||
}
|
||||
|
||||
// This should be updated to include the current fork and the genesis validators root, but discussion is ongoing:
|
||||
//
|
||||
// https://github.com/ethereum/builder-specs/issues/14
|
||||
pub fn get_builder_domain(&self) -> Hash256 {
|
||||
self.compute_domain(
|
||||
Domain::ApplicationMask(ApplicationDomain::Builder),
|
||||
self.genesis_fork_version,
|
||||
Hash256::zero(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`.
|
||||
///
|
||||
/// This is used primarily in signature domains to avoid collisions across forks/chains.
|
||||
@@ -549,14 +568,9 @@ impl ChainSpec {
|
||||
.expect("pow does not overflow"),
|
||||
proportional_slashing_multiplier_bellatrix: 3,
|
||||
bellatrix_fork_version: [0x02, 0x00, 0x00, 0x00],
|
||||
bellatrix_fork_epoch: None,
|
||||
terminal_total_difficulty: Uint256::MAX
|
||||
.checked_sub(Uint256::from(2u64.pow(10)))
|
||||
.expect("subtraction does not overflow")
|
||||
// Add 1 since the spec declares `2**256 - 2**10` and we use
|
||||
// `Uint256::MAX` which is `2*256- 1`.
|
||||
.checked_add(Uint256::one())
|
||||
.expect("addition does not overflow"),
|
||||
bellatrix_fork_epoch: Some(Epoch::new(144896)),
|
||||
terminal_total_difficulty: Uint256::from_dec_str("58750000000000000000000")
|
||||
.expect("terminal_total_difficulty is a valid integer"),
|
||||
terminal_block_hash: ExecutionBlockHash::zero(),
|
||||
terminal_block_hash_activation_epoch: Epoch::new(u64::MAX),
|
||||
safe_slots_to_import_optimistically: 128u64,
|
||||
@@ -572,6 +586,11 @@ impl ChainSpec {
|
||||
maximum_gossip_clock_disparity_millis: 500,
|
||||
target_aggregators_per_committee: 16,
|
||||
epochs_per_random_subnet_subscription: 256,
|
||||
|
||||
/*
|
||||
* Application specific
|
||||
*/
|
||||
domain_application_mask: APPLICATION_DOMAIN_BUILDER,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -604,6 +623,13 @@ impl ChainSpec {
|
||||
// Merge
|
||||
bellatrix_fork_version: [0x02, 0x00, 0x00, 0x01],
|
||||
bellatrix_fork_epoch: None,
|
||||
terminal_total_difficulty: Uint256::MAX
|
||||
.checked_sub(Uint256::from(2u64.pow(10)))
|
||||
.expect("subtraction does not overflow")
|
||||
// Add 1 since the spec declares `2**256 - 2**10` and we use
|
||||
// `Uint256::MAX` which is `2*256- 1`.
|
||||
.checked_add(Uint256::one())
|
||||
.expect("addition does not overflow"),
|
||||
// Other
|
||||
network_id: 2, // lighthouse testnet network id
|
||||
deposit_chain_id: 5,
|
||||
@@ -770,6 +796,11 @@ impl ChainSpec {
|
||||
maximum_gossip_clock_disparity_millis: 500,
|
||||
target_aggregators_per_committee: 16,
|
||||
epochs_per_random_subnet_subscription: 256,
|
||||
|
||||
/*
|
||||
* Application specific
|
||||
*/
|
||||
domain_application_mask: APPLICATION_DOMAIN_BUILDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -781,6 +812,10 @@ impl Default for ChainSpec {
|
||||
}
|
||||
|
||||
/// Exact implementation of the *config* object from the Ethereum spec (YAML/JSON).
|
||||
///
|
||||
/// Fields relevant to hard forks after Altair should be optional so that we can continue
|
||||
/// to parse Altair configs. This default approach turns out to be much simpler than trying to
|
||||
/// make `Config` a superstruct because of the hassle of deserializing an untagged enum.
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub struct Config {
|
||||
@@ -791,17 +826,13 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub preset_base: String,
|
||||
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_terminal_total_difficulty")]
|
||||
#[serde(with = "eth2_serde_utils::quoted_u256")]
|
||||
pub terminal_total_difficulty: Uint256,
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_terminal_block_hash")]
|
||||
pub terminal_block_hash: ExecutionBlockHash,
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_terminal_block_hash_activation_epoch")]
|
||||
pub terminal_block_hash_activation_epoch: Epoch,
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_safe_slots_to_import_optimistically")]
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub safe_slots_to_import_optimistically: u64,
|
||||
@@ -821,12 +852,10 @@ pub struct Config {
|
||||
#[serde(deserialize_with = "deserialize_fork_epoch")]
|
||||
pub altair_fork_epoch: Option<MaybeQuoted<Epoch>>,
|
||||
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_bellatrix_fork_version")]
|
||||
#[serde(with = "eth2_serde_utils::bytes_4_hex")]
|
||||
bellatrix_fork_version: [u8; 4],
|
||||
// TODO(merge): remove this default
|
||||
#[serde(default = "default_bellatrix_fork_epoch")]
|
||||
#[serde(default)]
|
||||
#[serde(serialize_with = "serialize_fork_epoch")]
|
||||
#[serde(deserialize_with = "deserialize_fork_epoch")]
|
||||
pub bellatrix_fork_epoch: Option<MaybeQuoted<Epoch>>,
|
||||
@@ -868,10 +897,6 @@ fn default_bellatrix_fork_version() -> [u8; 4] {
|
||||
[0xff, 0xff, 0xff, 0xff]
|
||||
}
|
||||
|
||||
fn default_bellatrix_fork_epoch() -> Option<MaybeQuoted<Epoch>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912).
|
||||
///
|
||||
/// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16
|
||||
@@ -1126,6 +1151,27 @@ mod tests {
|
||||
&spec,
|
||||
);
|
||||
test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec);
|
||||
|
||||
// The builder domain index is zero
|
||||
let builder_domain_pre_mask = [0; 4];
|
||||
test_domain(
|
||||
Domain::ApplicationMask(ApplicationDomain::Builder),
|
||||
apply_bit_mask(builder_domain_pre_mask, &spec),
|
||||
&spec,
|
||||
);
|
||||
}
|
||||
|
||||
fn apply_bit_mask(domain_bytes: [u8; 4], spec: &ChainSpec) -> u32 {
|
||||
let mut domain = [0; 4];
|
||||
let mask_bytes = int_to_bytes4(spec.domain_application_mask);
|
||||
|
||||
// Apply application bit mask
|
||||
for (i, (domain_byte, mask_byte)) in domain_bytes.iter().zip(mask_bytes.iter()).enumerate()
|
||||
{
|
||||
domain[i] = domain_byte | mask_byte;
|
||||
}
|
||||
|
||||
u32::from_le_bytes(domain)
|
||||
}
|
||||
|
||||
// Test that `fork_name_at_epoch` and `fork_epoch` are consistent.
|
||||
@@ -1292,10 +1338,7 @@ mod yaml_tests {
|
||||
default_safe_slots_to_import_optimistically()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
chain_spec.bellatrix_fork_epoch,
|
||||
default_bellatrix_fork_epoch()
|
||||
);
|
||||
assert_eq!(chain_spec.bellatrix_fork_epoch, None);
|
||||
|
||||
assert_eq!(
|
||||
chain_spec.bellatrix_fork_version,
|
||||
@@ -1312,4 +1355,12 @@ mod yaml_tests {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_domain_builder() {
|
||||
assert_eq!(
|
||||
int_to_bytes4(ApplicationDomain::Builder.get_domain_constant()),
|
||||
[0, 0, 0, 1]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
use crate::{AltairPreset, BasePreset, BellatrixPreset, ChainSpec, Config, EthSpec};
|
||||
use crate::{
|
||||
consts::altair, AltairPreset, BasePreset, BellatrixPreset, ChainSpec, Config, EthSpec, ForkName,
|
||||
};
|
||||
use maplit::hashmap;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use superstruct::superstruct;
|
||||
|
||||
/// Fusion of a runtime-config with the compile-time preset values.
|
||||
///
|
||||
/// Mostly useful for the API.
|
||||
#[superstruct(
|
||||
variants(Altair, Bellatrix),
|
||||
variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone))
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub struct ConfigAndPreset {
|
||||
#[serde(flatten)]
|
||||
pub config: Config,
|
||||
@@ -15,76 +24,75 @@ pub struct ConfigAndPreset {
|
||||
pub base_preset: BasePreset,
|
||||
#[serde(flatten)]
|
||||
pub altair_preset: AltairPreset,
|
||||
// TODO(merge): re-enable
|
||||
// #[serde(flatten)]
|
||||
// pub bellatrix_preset: BellatrixPreset,
|
||||
#[superstruct(only(Bellatrix))]
|
||||
#[serde(flatten)]
|
||||
pub bellatrix_preset: BellatrixPreset,
|
||||
/// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks.
|
||||
#[serde(flatten)]
|
||||
pub extra_fields: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
impl ConfigAndPreset {
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec) -> Self {
|
||||
pub fn from_chain_spec<T: EthSpec>(spec: &ChainSpec, fork_name: Option<ForkName>) -> Self {
|
||||
let config = Config::from_chain_spec::<T>(spec);
|
||||
let base_preset = BasePreset::from_chain_spec::<T>(spec);
|
||||
let altair_preset = AltairPreset::from_chain_spec::<T>(spec);
|
||||
// TODO(merge): re-enable
|
||||
let _bellatrix_preset = BellatrixPreset::from_chain_spec::<T>(spec);
|
||||
let extra_fields = HashMap::new();
|
||||
let extra_fields = get_extra_fields(spec);
|
||||
|
||||
Self {
|
||||
config,
|
||||
base_preset,
|
||||
altair_preset,
|
||||
extra_fields,
|
||||
if spec.bellatrix_fork_epoch.is_some()
|
||||
|| fork_name == None
|
||||
|| fork_name == Some(ForkName::Merge)
|
||||
{
|
||||
let bellatrix_preset = BellatrixPreset::from_chain_spec::<T>(spec);
|
||||
|
||||
ConfigAndPreset::Bellatrix(ConfigAndPresetBellatrix {
|
||||
config,
|
||||
base_preset,
|
||||
altair_preset,
|
||||
bellatrix_preset,
|
||||
extra_fields,
|
||||
})
|
||||
} else {
|
||||
ConfigAndPreset::Altair(ConfigAndPresetAltair {
|
||||
config,
|
||||
base_preset,
|
||||
altair_preset,
|
||||
extra_fields,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add fields that were previously part of the config but are now constants.
|
||||
pub fn make_backwards_compat(&mut self, spec: &ChainSpec) {
|
||||
let hex_string = |value: &[u8]| format!("0x{}", hex::encode(&value));
|
||||
let u32_hex = |v: u32| hex_string(&v.to_le_bytes());
|
||||
let u8_hex = |v: u8| hex_string(&v.to_le_bytes());
|
||||
let fields = vec![
|
||||
(
|
||||
"bls_withdrawal_prefix",
|
||||
u8_hex(spec.bls_withdrawal_prefix_byte),
|
||||
),
|
||||
(
|
||||
"domain_beacon_proposer",
|
||||
u32_hex(spec.domain_beacon_proposer),
|
||||
),
|
||||
(
|
||||
"domain_beacon_attester",
|
||||
u32_hex(spec.domain_beacon_attester),
|
||||
),
|
||||
("domain_randao", u32_hex(spec.domain_randao)),
|
||||
("domain_deposit", u32_hex(spec.domain_deposit)),
|
||||
("domain_voluntary_exit", u32_hex(spec.domain_voluntary_exit)),
|
||||
(
|
||||
"domain_selection_proof",
|
||||
u32_hex(spec.domain_selection_proof),
|
||||
),
|
||||
(
|
||||
"domain_aggregate_and_proof",
|
||||
u32_hex(spec.domain_aggregate_and_proof),
|
||||
),
|
||||
(
|
||||
"target_aggregators_per_committee",
|
||||
spec.target_aggregators_per_committee.to_string(),
|
||||
),
|
||||
(
|
||||
"random_subnets_per_validator",
|
||||
spec.random_subnets_per_validator.to_string(),
|
||||
),
|
||||
(
|
||||
"epochs_per_random_subnet_subscription",
|
||||
spec.epochs_per_random_subnet_subscription.to_string(),
|
||||
),
|
||||
];
|
||||
for (key, value) in fields {
|
||||
self.extra_fields.insert(key.to_uppercase(), value.into());
|
||||
}
|
||||
/// Get a hashmap of constants to add to the `PresetAndConfig`
|
||||
pub fn get_extra_fields(spec: &ChainSpec) -> HashMap<String, Value> {
|
||||
let hex_string = |value: &[u8]| format!("0x{}", hex::encode(&value)).into();
|
||||
let u32_hex = |v: u32| hex_string(&v.to_le_bytes());
|
||||
let u8_hex = |v: u8| hex_string(&v.to_le_bytes());
|
||||
hashmap! {
|
||||
"bls_withdrawal_prefix".to_uppercase() => u8_hex(spec.bls_withdrawal_prefix_byte),
|
||||
"domain_beacon_proposer".to_uppercase() => u32_hex(spec.domain_beacon_proposer),
|
||||
"domain_beacon_attester".to_uppercase() => u32_hex(spec.domain_beacon_attester),
|
||||
"domain_randao".to_uppercase()=> u32_hex(spec.domain_randao),
|
||||
"domain_deposit".to_uppercase()=> u32_hex(spec.domain_deposit),
|
||||
"domain_voluntary_exit".to_uppercase() => u32_hex(spec.domain_voluntary_exit),
|
||||
"domain_selection_proof".to_uppercase() => u32_hex(spec.domain_selection_proof),
|
||||
"domain_aggregate_and_proof".to_uppercase() => u32_hex(spec.domain_aggregate_and_proof),
|
||||
"domain_application_mask".to_uppercase()=> u32_hex(spec.domain_application_mask),
|
||||
"target_aggregators_per_committee".to_uppercase() =>
|
||||
spec.target_aggregators_per_committee.to_string().into(),
|
||||
"random_subnets_per_validator".to_uppercase() =>
|
||||
spec.random_subnets_per_validator.to_string().into(),
|
||||
"epochs_per_random_subnet_subscription".to_uppercase() =>
|
||||
spec.epochs_per_random_subnet_subscription.to_string().into(),
|
||||
"domain_contribution_and_proof".to_uppercase() =>
|
||||
u32_hex(spec.domain_contribution_and_proof),
|
||||
"domain_sync_committee".to_uppercase() => u32_hex(spec.domain_sync_committee),
|
||||
"domain_sync_committee_selection_proof".to_uppercase() =>
|
||||
u32_hex(spec.domain_sync_committee_selection_proof),
|
||||
"sync_committee_subnet_count".to_uppercase() =>
|
||||
altair::SYNC_COMMITTEE_SUBNET_COUNT.to_string().into(),
|
||||
"target_aggregators_per_sync_subcommittee".to_uppercase() =>
|
||||
altair::TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE.to_string().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,15 +112,16 @@ mod test {
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error opening file");
|
||||
let mainnet_spec = ChainSpec::mainnet();
|
||||
let mut yamlconfig = ConfigAndPreset::from_chain_spec::<MainnetEthSpec>(&mainnet_spec);
|
||||
let mut yamlconfig =
|
||||
ConfigAndPreset::from_chain_spec::<MainnetEthSpec>(&mainnet_spec, None);
|
||||
let (k1, v1) = ("SAMPLE_HARDFORK_KEY1", "123456789");
|
||||
let (k2, v2) = ("SAMPLE_HARDFORK_KEY2", "987654321");
|
||||
let (k3, v3) = ("SAMPLE_HARDFORK_KEY3", 32);
|
||||
let (k4, v4) = ("SAMPLE_HARDFORK_KEY4", Value::Null);
|
||||
yamlconfig.extra_fields.insert(k1.into(), v1.into());
|
||||
yamlconfig.extra_fields.insert(k2.into(), v2.into());
|
||||
yamlconfig.extra_fields.insert(k3.into(), v3.into());
|
||||
yamlconfig.extra_fields.insert(k4.into(), v4);
|
||||
yamlconfig.extra_fields_mut().insert(k1.into(), v1.into());
|
||||
yamlconfig.extra_fields_mut().insert(k2.into(), v2.into());
|
||||
yamlconfig.extra_fields_mut().insert(k3.into(), v3.into());
|
||||
yamlconfig.extra_fields_mut().insert(k4.into(), v4);
|
||||
|
||||
serde_yaml::to_writer(writer, &yamlconfig).expect("failed to write or serialize");
|
||||
|
||||
@@ -121,8 +130,8 @@ mod test {
|
||||
.write(false)
|
||||
.open(tmp_file.as_ref())
|
||||
.expect("error while opening the file");
|
||||
let from: ConfigAndPreset =
|
||||
let from: ConfigAndPresetBellatrix =
|
||||
serde_yaml::from_reader(reader).expect("error while deserializing");
|
||||
assert_eq!(from, yamlconfig);
|
||||
assert_eq!(ConfigAndPreset::Bellatrix(from), yamlconfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::Hash256;
|
||||
use derivative::Derivative;
|
||||
use rand::RngCore;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{Decode, DecodeError, Encode};
|
||||
use std::fmt;
|
||||
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
|
||||
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
#[derive(Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash, Derivative)]
|
||||
#[derivative(Debug = "transparent")]
|
||||
#[serde(transparent)]
|
||||
pub struct ExecutionBlockHash(Hash256);
|
||||
|
||||
|
||||
@@ -106,14 +106,14 @@ macro_rules! map_fork_name_with {
|
||||
}
|
||||
|
||||
impl FromStr for ForkName {
|
||||
type Err = ();
|
||||
type Err = String;
|
||||
|
||||
fn from_str(fork_name: &str) -> Result<Self, ()> {
|
||||
fn from_str(fork_name: &str) -> Result<Self, String> {
|
||||
Ok(match fork_name.to_lowercase().as_ref() {
|
||||
"phase0" | "base" => ForkName::Base,
|
||||
"altair" => ForkName::Altair,
|
||||
"bellatrix" | "merge" => ForkName::Merge,
|
||||
_ => return Err(()),
|
||||
_ => return Err(format!("unknown fork name: {}", fork_name)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl TryFrom<String> for ForkName {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Self::from_str(&s).map_err(|()| format!("Invalid fork name: {}", s))
|
||||
Self::from_str(&s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@ mod test {
|
||||
assert_eq!(ForkName::from_str("AlTaIr"), Ok(ForkName::Altair));
|
||||
assert_eq!(ForkName::from_str("altair"), Ok(ForkName::Altair));
|
||||
|
||||
assert_eq!(ForkName::from_str("NO_NAME"), Err(()));
|
||||
assert_eq!(ForkName::from_str("no_name"), Err(()));
|
||||
assert!(ForkName::from_str("NO_NAME").is_err());
|
||||
assert!(ForkName::from_str("no_name").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -18,6 +18,7 @@ extern crate lazy_static;
|
||||
pub mod test_utils;
|
||||
|
||||
pub mod aggregate_and_proof;
|
||||
pub mod application_domain;
|
||||
pub mod attestation;
|
||||
pub mod attestation_data;
|
||||
pub mod attestation_duty;
|
||||
@@ -27,6 +28,7 @@ pub mod beacon_block_body;
|
||||
pub mod beacon_block_header;
|
||||
pub mod beacon_committee;
|
||||
pub mod beacon_state;
|
||||
pub mod builder_bid;
|
||||
pub mod chain_spec;
|
||||
pub mod checkpoint;
|
||||
pub mod consts;
|
||||
@@ -81,6 +83,7 @@ pub mod sync_committee_contribution;
|
||||
pub mod sync_committee_message;
|
||||
pub mod sync_selection_proof;
|
||||
pub mod sync_subnet_id;
|
||||
pub mod validator_registration_data;
|
||||
|
||||
pub mod slot_data;
|
||||
#[cfg(feature = "sqlite")]
|
||||
@@ -106,7 +109,9 @@ pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
|
||||
pub use crate::beacon_state::{Error as BeaconStateError, *};
|
||||
pub use crate::chain_spec::{ChainSpec, Config, Domain};
|
||||
pub use crate::checkpoint::Checkpoint;
|
||||
pub use crate::config_and_preset::ConfigAndPreset;
|
||||
pub use crate::config_and_preset::{
|
||||
ConfigAndPreset, ConfigAndPresetAltair, ConfigAndPresetBellatrix,
|
||||
};
|
||||
pub use crate::contribution_and_proof::ContributionAndProof;
|
||||
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
|
||||
pub use crate::deposit_data::DepositData;
|
||||
@@ -156,6 +161,7 @@ pub use crate::sync_duty::SyncDuty;
|
||||
pub use crate::sync_selection_proof::SyncSelectionProof;
|
||||
pub use crate::sync_subnet_id::SyncSubnetId;
|
||||
pub use crate::validator::{Validator, ValidatorImmutable};
|
||||
pub use crate::validator_registration_data::*;
|
||||
pub use crate::validator_subscription::ValidatorSubscription;
|
||||
pub use crate::voluntary_exit::VoluntaryExit;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::hash::Hash;
|
||||
use test_random_derive::TestRandom;
|
||||
use tree_hash::TreeHash;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockType {
|
||||
Blinded,
|
||||
Full,
|
||||
@@ -18,6 +19,7 @@ pub trait ExecPayload<T: EthSpec>:
|
||||
Debug
|
||||
+ Clone
|
||||
+ Encode
|
||||
+ Debug
|
||||
+ Decode
|
||||
+ TestRandom
|
||||
+ TreeHash
|
||||
@@ -28,6 +30,8 @@ pub trait ExecPayload<T: EthSpec>:
|
||||
+ Hash
|
||||
+ TryFrom<ExecutionPayloadHeader<T>>
|
||||
+ From<ExecutionPayload<T>>
|
||||
+ Send
|
||||
+ 'static
|
||||
{
|
||||
fn block_type() -> BlockType;
|
||||
|
||||
@@ -42,6 +46,8 @@ pub trait ExecPayload<T: EthSpec>:
|
||||
fn block_number(&self) -> u64;
|
||||
fn timestamp(&self) -> u64;
|
||||
fn block_hash(&self) -> ExecutionBlockHash;
|
||||
fn fee_recipient(&self) -> Address;
|
||||
fn gas_limit(&self) -> u64;
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
|
||||
@@ -72,6 +78,14 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
|
||||
fn block_hash(&self) -> ExecutionBlockHash {
|
||||
self.execution_payload.block_hash
|
||||
}
|
||||
|
||||
fn fee_recipient(&self) -> Address {
|
||||
self.execution_payload.fee_recipient
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> u64 {
|
||||
self.execution_payload.gas_limit
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
@@ -102,6 +116,14 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
|
||||
fn block_hash(&self) -> ExecutionBlockHash {
|
||||
self.execution_payload_header.block_hash
|
||||
}
|
||||
|
||||
fn fee_recipient(&self) -> Address {
|
||||
self.execution_payload_header.fee_recipient
|
||||
}
|
||||
|
||||
fn gas_limit(&self) -> u64 {
|
||||
self.execution_payload_header.gas_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]
|
||||
|
||||
@@ -18,6 +18,13 @@ pub struct ProposerSlashing {
|
||||
pub signed_header_2: SignedBeaconBlockHeader,
|
||||
}
|
||||
|
||||
impl ProposerSlashing {
|
||||
/// Get proposer index, assuming slashing validity has already been checked.
|
||||
pub fn proposer_index(&self) -> u64 {
|
||||
self.signed_header_1.message.proposer_index
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -346,6 +346,14 @@ impl<E: EthSpec> From<SignedBeaconBlock<E>> for SignedBlindedBeaconBlock<E> {
|
||||
}
|
||||
}
|
||||
|
||||
// We can blind borrowed blocks with payloads by converting the payload into a header (without
|
||||
// cloning the payload contents).
|
||||
impl<E: EthSpec> SignedBeaconBlock<E> {
|
||||
pub fn clone_as_blinded(&self) -> SignedBlindedBeaconBlock<E> {
|
||||
SignedBeaconBlock::from_block(self.message().into(), self.signature().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -13,8 +13,8 @@ macro_rules! ssz_tests {
|
||||
($type: ty) => {
|
||||
#[test]
|
||||
pub fn test_ssz_round_trip() {
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use ssz::{ssz_encode, Decode};
|
||||
use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = <$type>::random_for_test(&mut rng);
|
||||
@@ -33,8 +33,8 @@ macro_rules! tree_hash_tests {
|
||||
($type: ty) => {
|
||||
#[test]
|
||||
pub fn test_tree_hash_root() {
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use tree_hash::TreeHash;
|
||||
use $crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let original = <$type>::random_for_test(&mut rng);
|
||||
|
||||
@@ -129,6 +129,7 @@ macro_rules! impl_test_random_for_u8_array {
|
||||
};
|
||||
}
|
||||
|
||||
impl_test_random_for_u8_array!(3);
|
||||
impl_test_random_for_u8_array!(4);
|
||||
impl_test_random_for_u8_array!(32);
|
||||
impl_test_random_for_u8_array!(48);
|
||||
|
||||
23
consensus/types/src/validator_registration_data.rs
Normal file
23
consensus/types/src/validator_registration_data.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use crate::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssz_derive::{Decode, Encode};
|
||||
use tree_hash_derive::TreeHash;
|
||||
|
||||
/// Validator registration, for use in interacting with servers implementing the builder API.
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct SignedValidatorRegistrationData {
|
||||
pub message: ValidatorRegistrationData,
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode, TreeHash)]
|
||||
pub struct ValidatorRegistrationData {
|
||||
pub fee_recipient: Address,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub gas_limit: u64,
|
||||
#[serde(with = "eth2_serde_utils::quoted_u64")]
|
||||
pub timestamp: u64,
|
||||
pub pubkey: PublicKeyBytes,
|
||||
}
|
||||
|
||||
impl SignedRoot for ValidatorRegistrationData {}
|
||||
Reference in New Issue
Block a user