merge with unstable

This commit is contained in:
realbigsean
2022-11-01 13:18:00 -04:00
143 changed files with 4773 additions and 1876 deletions
+177 -8
View File
@@ -19,6 +19,8 @@ lazy_static! {
/// indices are populated by non-zero leaves (perfect for the deposit contract tree).
#[derive(Debug, PartialEq)]
pub enum MerkleTree {
/// Finalized Node
Finalized(H256),
/// Leaf node with the hash of its content.
Leaf(H256),
/// Internal node with hash, left subtree and right subtree.
@@ -41,6 +43,24 @@ pub enum MerkleTreeError {
DepthTooSmall,
// Overflow occurred
ArithError,
// Can't finalize a zero node
ZeroNodeFinalized,
// Can't push to finalized node
FinalizedNodePushed,
// Invalid Snapshot
InvalidSnapshot(InvalidSnapshot),
// Can't proof a finalized node
ProofEncounteredFinalizedNode,
// This should never happen
PleaseNotifyTheDevs,
}
#[derive(Debug, PartialEq, Clone)]
pub enum InvalidSnapshot {
// Branch hashes are empty but deposits are not
EmptyBranchWithNonZeroDeposits(usize),
// End of tree reached but deposits != 1
EndOfTree,
}
impl MerkleTree {
@@ -97,9 +117,11 @@ impl MerkleTree {
let right: &mut MerkleTree = &mut *right;
match (&*left, &*right) {
// Tree is full
(Leaf(_), Leaf(_)) => return Err(MerkleTreeError::MerkleTreeFull),
(Leaf(_), Leaf(_)) | (Finalized(_), Leaf(_)) => {
return Err(MerkleTreeError::MerkleTreeFull)
}
// There is a right node so insert in right node
(Node(_, _, _), Node(_, _, _)) => {
(Node(_, _, _), Node(_, _, _)) | (Finalized(_), Node(_, _, _)) => {
right.push_leaf(elem, depth - 1)?;
}
// Both branches are zero, insert in left one
@@ -107,7 +129,7 @@ impl MerkleTree {
*left = MerkleTree::create(&[elem], depth - 1);
}
// Leaf on left branch and zero on right branch, insert on right side
(Leaf(_), Zero(_)) => {
(Leaf(_), Zero(_)) | (Finalized(_), Zero(_)) => {
*right = MerkleTree::create(&[elem], depth - 1);
}
// Try inserting on the left node -> if it fails because it is full, insert in right side.
@@ -129,6 +151,7 @@ impl MerkleTree {
right.hash().as_bytes(),
));
}
Finalized(_) => return Err(MerkleTreeError::FinalizedNodePushed),
}
Ok(())
@@ -137,6 +160,7 @@ impl MerkleTree {
/// Retrieve the root hash of this Merkle tree.
pub fn hash(&self) -> H256 {
match *self {
MerkleTree::Finalized(h) => h,
MerkleTree::Leaf(h) => h,
MerkleTree::Node(h, _, _) => h,
MerkleTree::Zero(depth) => H256::from_slice(&ZERO_HASHES[depth]),
@@ -146,7 +170,7 @@ impl MerkleTree {
/// Get a reference to the left and right subtrees if they exist.
pub fn left_and_right_branches(&self) -> Option<(&Self, &Self)> {
match *self {
MerkleTree::Leaf(_) | MerkleTree::Zero(0) => None,
MerkleTree::Finalized(_) | MerkleTree::Leaf(_) | MerkleTree::Zero(0) => None,
MerkleTree::Node(_, ref l, ref r) => Some((l, r)),
MerkleTree::Zero(depth) => Some((&ZERO_NODES[depth - 1], &ZERO_NODES[depth - 1])),
}
@@ -157,16 +181,125 @@ impl MerkleTree {
matches!(self, MerkleTree::Leaf(_))
}
/// Finalize deposits up to deposit with count = deposits_to_finalize
pub fn finalize_deposits(
&mut self,
deposits_to_finalize: usize,
level: usize,
) -> Result<(), MerkleTreeError> {
match self {
MerkleTree::Finalized(_) => Ok(()),
MerkleTree::Zero(_) => Err(MerkleTreeError::ZeroNodeFinalized),
MerkleTree::Leaf(hash) => {
if level != 0 {
// This shouldn't happen but this is a sanity check
return Err(MerkleTreeError::PleaseNotifyTheDevs);
}
*self = MerkleTree::Finalized(*hash);
Ok(())
}
MerkleTree::Node(hash, left, right) => {
if level == 0 {
// this shouldn't happen but we'll put it here for safety
return Err(MerkleTreeError::PleaseNotifyTheDevs);
}
let deposits = 0x1 << level;
if deposits <= deposits_to_finalize {
*self = MerkleTree::Finalized(*hash);
return Ok(());
}
left.finalize_deposits(deposits_to_finalize, level - 1)?;
if deposits_to_finalize > deposits / 2 {
let remaining = deposits_to_finalize - deposits / 2;
right.finalize_deposits(remaining, level - 1)?;
}
Ok(())
}
}
}
fn append_finalized_hashes(&self, result: &mut Vec<H256>) {
match self {
MerkleTree::Zero(_) | MerkleTree::Leaf(_) => {}
MerkleTree::Finalized(h) => result.push(*h),
MerkleTree::Node(_, left, right) => {
left.append_finalized_hashes(result);
right.append_finalized_hashes(result);
}
}
}
pub fn get_finalized_hashes(&self) -> Vec<H256> {
let mut result = vec![];
self.append_finalized_hashes(&mut result);
result
}
pub fn from_finalized_snapshot(
finalized_branch: &[H256],
deposit_count: usize,
level: usize,
) -> Result<Self, MerkleTreeError> {
if finalized_branch.is_empty() {
return if deposit_count == 0 {
Ok(MerkleTree::Zero(level))
} else {
Err(InvalidSnapshot::EmptyBranchWithNonZeroDeposits(deposit_count).into())
};
}
if deposit_count == (0x1 << level) {
return Ok(MerkleTree::Finalized(
*finalized_branch
.get(0)
.ok_or(MerkleTreeError::PleaseNotifyTheDevs)?,
));
}
if level == 0 {
return Err(InvalidSnapshot::EndOfTree.into());
}
let (left, right) = match deposit_count.checked_sub(0x1 << (level - 1)) {
// left tree is fully finalized
Some(right_deposits) => {
let (left_hash, right_branch) = finalized_branch
.split_first()
.ok_or(MerkleTreeError::PleaseNotifyTheDevs)?;
(
MerkleTree::Finalized(*left_hash),
MerkleTree::from_finalized_snapshot(right_branch, right_deposits, level - 1)?,
)
}
// left tree is not fully finalized -> right tree is zero
None => (
MerkleTree::from_finalized_snapshot(finalized_branch, deposit_count, level - 1)?,
MerkleTree::Zero(level - 1),
),
};
let hash = H256::from_slice(&hash32_concat(
left.hash().as_bytes(),
right.hash().as_bytes(),
));
Ok(MerkleTree::Node(hash, Box::new(left), Box::new(right)))
}
/// Return the leaf at `index` and a Merkle proof of its inclusion.
///
/// The Merkle proof is in "bottom-up" order, starting with a leaf node
/// and moving up the tree. Its length will be exactly equal to `depth`.
pub fn generate_proof(&self, index: usize, depth: usize) -> (H256, Vec<H256>) {
pub fn generate_proof(
&self,
index: usize,
depth: usize,
) -> Result<(H256, Vec<H256>), MerkleTreeError> {
let mut proof = vec![];
let mut current_node = self;
let mut current_depth = depth;
while current_depth > 0 {
let ith_bit = (index >> (current_depth - 1)) & 0x01;
if let &MerkleTree::Finalized(_) = current_node {
return Err(MerkleTreeError::ProofEncounteredFinalizedNode);
}
// Note: unwrap is safe because leaves are only ever constructed at depth == 0.
let (left, right) = current_node.left_and_right_branches().unwrap();
@@ -187,7 +320,33 @@ impl MerkleTree {
// Put proof in bottom-up order.
proof.reverse();
(current_node.hash(), proof)
Ok((current_node.hash(), proof))
}
/// useful for debugging
pub fn print_node(&self, mut space: u32) {
const SPACES: u32 = 10;
space += SPACES;
let (pair, text) = match self {
MerkleTree::Node(hash, left, right) => (Some((left, right)), format!("Node({})", hash)),
MerkleTree::Leaf(hash) => (None, format!("Leaf({})", hash)),
MerkleTree::Zero(depth) => (
None,
format!("Z[{}]({})", depth, H256::from_slice(&ZERO_HASHES[*depth])),
),
MerkleTree::Finalized(hash) => (None, format!("Finl({})", hash)),
};
if let Some((_, right)) = pair {
right.print_node(space);
}
println!();
for _i in SPACES..space {
print!(" ");
}
println!("{}", text);
if let Some((left, _)) = pair {
left.print_node(space);
}
}
}
@@ -235,6 +394,12 @@ impl From<ArithError> for MerkleTreeError {
}
}
impl From<InvalidSnapshot> for MerkleTreeError {
fn from(e: InvalidSnapshot) -> Self {
MerkleTreeError::InvalidSnapshot(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -255,7 +420,9 @@ mod tests {
let merkle_root = merkle_tree.hash();
let proofs_ok = (0..leaves.len()).all(|i| {
let (leaf, branch) = merkle_tree.generate_proof(i, depth);
let (leaf, branch) = merkle_tree
.generate_proof(i, depth)
.expect("should generate proof");
leaf == leaves[i] && verify_merkle_proof(leaf, &branch, depth, i, merkle_root)
});
@@ -274,7 +441,9 @@ mod tests {
let proofs_ok = leaves_iter.enumerate().all(|(i, leaf)| {
assert_eq!(merkle_tree.push_leaf(leaf, depth), Ok(()));
let (stored_leaf, branch) = merkle_tree.generate_proof(i, depth);
let (stored_leaf, branch) = merkle_tree
.generate_proof(i, depth)
.expect("should generate proof");
stored_leaf == leaf && verify_merkle_proof(leaf, &branch, depth, i, merkle_tree.hash())
});
+14
View File
@@ -246,6 +246,20 @@ impl Decode for NonZeroUsize {
}
}
impl<T: Decode> Decode for Option<T> {
fn is_ssz_fixed_len() -> bool {
false
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
let (selector, body) = split_union_bytes(bytes)?;
match selector.into() {
0u8 => Ok(None),
1u8 => <T as Decode>::from_ssz_bytes(body).map(Option::Some),
other => Err(DecodeError::UnionSelectorInvalid(other)),
}
}
}
impl<T: Decode> Decode for Arc<T> {
fn is_ssz_fixed_len() -> bool {
T::is_ssz_fixed_len()
+36
View File
@@ -203,6 +203,34 @@ impl_encode_for_tuples! {
}
}
impl<T: Encode> Encode for Option<T> {
fn is_ssz_fixed_len() -> bool {
false
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
match self {
Option::None => {
let union_selector: u8 = 0u8;
buf.push(union_selector);
}
Option::Some(ref inner) => {
let union_selector: u8 = 1u8;
buf.push(union_selector);
inner.ssz_append(buf);
}
}
}
fn ssz_bytes_len(&self) -> usize {
match self {
Option::None => 1usize,
Option::Some(ref inner) => inner
.ssz_bytes_len()
.checked_add(1)
.expect("encoded length must be less than usize::max_value"),
}
}
}
impl<T: Encode> Encode for Arc<T> {
fn is_ssz_fixed_len() -> bool {
T::is_ssz_fixed_len()
@@ -562,6 +590,14 @@ mod tests {
);
}
#[test]
fn ssz_encode_option_u8() {
let opt: Option<u8> = None;
assert_eq!(opt.as_ssz_bytes(), vec![0]);
let opt: Option<u8> = Some(2);
assert_eq!(opt.as_ssz_bytes(), vec![1, 2]);
}
#[test]
fn ssz_encode_bool() {
assert_eq!(true.as_ssz_bytes(), vec![1]);
+18
View File
@@ -22,6 +22,13 @@ mod round_trip {
round_trip(items);
}
#[test]
fn option_u16() {
let items: Vec<Option<u16>> = vec![None, Some(2u16)];
round_trip(items);
}
#[test]
fn u8_array_4() {
let items: Vec<[u8; 4]> = vec![[0, 0, 0, 0], [1, 0, 0, 0], [1, 2, 3, 4], [1, 2, 0, 4]];
@@ -46,6 +53,17 @@ mod round_trip {
round_trip(items);
}
#[test]
fn option_vec_h256() {
let items: Vec<Option<Vec<H256>>> = vec![
None,
Some(vec![]),
Some(vec![H256::zero(), H256::from([1; 32]), H256::random()]),
];
round_trip(items);
}
#[test]
fn vec_u16() {
let items: Vec<Vec<u16>> = vec![
@@ -1,6 +1,7 @@
use crate::{
per_block_processing, per_epoch_processing::EpochProcessingSummary, per_slot_processing,
BlockProcessingError, BlockSignatureStrategy, SlotProcessingError, VerifyBlockRoot,
BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError,
VerifyBlockRoot,
};
use std::marker::PhantomData;
use types::{BeaconState, BlindedPayload, ChainSpec, EthSpec, Hash256, SignedBeaconBlock, Slot};
@@ -254,12 +255,16 @@ where
VerifyBlockRoot::False
}
});
// Proposer index was already checked when this block was originally processed, we
// can omit recomputing it during replay.
let mut ctxt = ConsensusContext::new(block.slot())
.set_proposer_index(block.message().proposer_index());
per_block_processing(
&mut self.state,
block,
None,
self.block_sig_strategy,
verify_block_root,
&mut ctxt,
self.spec,
)
.map_err(BlockReplayError::from)?;
@@ -2,12 +2,14 @@ use eth2_hashing::hash;
use int_to_bytes::int_to_bytes32;
use merkle_proof::{MerkleTree, MerkleTreeError};
use safe_arith::SafeArith;
use types::Hash256;
use types::{DepositTreeSnapshot, FinalizedExecutionBlock, Hash256};
/// Emulates the eth1 deposit contract merkle tree.
#[derive(PartialEq)]
pub struct DepositDataTree {
tree: MerkleTree,
mix_in_length: usize,
finalized_execution_block: Option<FinalizedExecutionBlock>,
depth: usize,
}
@@ -17,6 +19,7 @@ impl DepositDataTree {
Self {
tree: MerkleTree::create(leaves, depth),
mix_in_length,
finalized_execution_block: None,
depth,
}
}
@@ -38,10 +41,10 @@ impl DepositDataTree {
///
/// The Merkle proof is in "bottom-up" order, starting with a leaf node
/// and moving up the tree. Its length will be exactly equal to `depth + 1`.
pub fn generate_proof(&self, index: usize) -> (Hash256, Vec<Hash256>) {
let (root, mut proof) = self.tree.generate_proof(index, self.depth);
pub fn generate_proof(&self, index: usize) -> Result<(Hash256, Vec<Hash256>), MerkleTreeError> {
let (root, mut proof) = self.tree.generate_proof(index, self.depth)?;
proof.push(Hash256::from_slice(&self.length_bytes()));
(root, proof)
Ok((root, proof))
}
/// Add a deposit to the merkle tree.
@@ -50,4 +53,50 @@ impl DepositDataTree {
self.mix_in_length.safe_add_assign(1)?;
Ok(())
}
/// Finalize deposits up to `finalized_execution_block.deposit_count`
pub fn finalize(
&mut self,
finalized_execution_block: FinalizedExecutionBlock,
) -> Result<(), MerkleTreeError> {
self.tree
.finalize_deposits(finalized_execution_block.deposit_count as usize, self.depth)?;
self.finalized_execution_block = Some(finalized_execution_block);
Ok(())
}
/// Get snapshot of finalized deposit tree (if tree is finalized)
pub fn get_snapshot(&self) -> Option<DepositTreeSnapshot> {
let finalized_execution_block = self.finalized_execution_block.as_ref()?;
Some(DepositTreeSnapshot {
finalized: self.tree.get_finalized_hashes(),
deposit_root: finalized_execution_block.deposit_root,
deposit_count: finalized_execution_block.deposit_count,
execution_block_hash: finalized_execution_block.block_hash,
execution_block_height: finalized_execution_block.block_height,
})
}
/// Create a new Merkle tree from a snapshot
pub fn from_snapshot(
snapshot: &DepositTreeSnapshot,
depth: usize,
) -> Result<Self, MerkleTreeError> {
Ok(Self {
tree: MerkleTree::from_finalized_snapshot(
&snapshot.finalized,
snapshot.deposit_count as usize,
depth,
)?,
mix_in_length: snapshot.deposit_count as usize,
finalized_execution_block: Some(snapshot.into()),
depth,
})
}
#[allow(dead_code)]
pub fn print_tree(&self) {
self.tree.print_node(0);
println!("========================================================");
}
}
@@ -1,9 +1,13 @@
use crate::common::{decrease_balance, increase_balance, initiate_validator_exit};
use crate::{
common::{decrease_balance, increase_balance, initiate_validator_exit},
per_block_processing::errors::BlockProcessingError,
ConsensusContext,
};
use safe_arith::SafeArith;
use std::cmp;
use types::{
consts::altair::{PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
BeaconStateError as Error, *,
*,
};
/// Slash the validator with index `slashed_index`.
@@ -11,8 +15,9 @@ pub fn slash_validator<T: EthSpec>(
state: &mut BeaconState<T>,
slashed_index: usize,
opt_whistleblower_index: Option<usize>,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), Error> {
) -> Result<(), BlockProcessingError> {
let epoch = state.current_epoch();
initiate_validator_exit(state, slashed_index, spec)?;
@@ -39,7 +44,7 @@ pub fn slash_validator<T: EthSpec>(
)?;
// Apply proposer and whistleblower rewards
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)?;
let proposer_index = ctxt.get_proposer_index(state, spec)? as usize;
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblower_reward =
validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?;
@@ -55,7 +60,7 @@ pub fn slash_validator<T: EthSpec>(
// Ensure the whistleblower index is in the validator registry.
if state.validators().get(whistleblower_index).is_none() {
return Err(BeaconStateError::UnknownValidator(whistleblower_index));
return Err(BeaconStateError::UnknownValidator(whistleblower_index).into());
}
increase_balance(state, proposer_index, proposer_reward)?;
@@ -0,0 +1,92 @@
use std::marker::PhantomData;
use tree_hash::TreeHash;
use types::{
BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, Hash256, SignedBeaconBlock,
Slot,
};
#[derive(Debug)]
pub struct ConsensusContext<T: EthSpec> {
/// Slot to act as an identifier/safeguard
slot: Slot,
/// Proposer index of the block at `slot`.
proposer_index: Option<u64>,
/// Block root of the block at `slot`.
current_block_root: Option<Hash256>,
_phantom: PhantomData<T>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum ContextError {
BeaconState(BeaconStateError),
SlotMismatch { slot: Slot, expected: Slot },
}
impl From<BeaconStateError> for ContextError {
fn from(e: BeaconStateError) -> Self {
Self::BeaconState(e)
}
}
impl<T: EthSpec> ConsensusContext<T> {
pub fn new(slot: Slot) -> Self {
Self {
slot,
proposer_index: None,
current_block_root: None,
_phantom: PhantomData,
}
}
pub fn set_proposer_index(mut self, proposer_index: u64) -> Self {
self.proposer_index = Some(proposer_index);
self
}
pub fn get_proposer_index(
&mut self,
state: &BeaconState<T>,
spec: &ChainSpec,
) -> Result<u64, ContextError> {
self.check_slot(state.slot())?;
if let Some(proposer_index) = self.proposer_index {
return Ok(proposer_index);
}
let proposer_index = state.get_beacon_proposer_index(self.slot, spec)? as u64;
self.proposer_index = Some(proposer_index);
Ok(proposer_index)
}
pub fn set_current_block_root(mut self, block_root: Hash256) -> Self {
self.current_block_root = Some(block_root);
self
}
pub fn get_current_block_root<Payload: ExecPayload<T>>(
&mut self,
block: &SignedBeaconBlock<T, Payload>,
) -> Result<Hash256, ContextError> {
self.check_slot(block.slot())?;
if let Some(current_block_root) = self.current_block_root {
return Ok(current_block_root);
}
let current_block_root = block.message().tree_hash_root();
self.current_block_root = Some(current_block_root);
Ok(current_block_root)
}
fn check_slot(&self, slot: Slot) -> Result<(), ContextError> {
if slot == self.slot {
Ok(())
} else {
Err(ContextError::SlotMismatch {
slot,
expected: self.slot,
})
}
}
}
+2
View File
@@ -18,6 +18,7 @@ mod metrics;
pub mod block_replayer;
pub mod common;
pub mod consensus_context;
pub mod genesis;
pub mod per_block_processing;
pub mod per_epoch_processing;
@@ -27,6 +28,7 @@ pub mod upgrade;
pub mod verify_operation;
pub use block_replayer::{BlockReplayError, BlockReplayer, StateRootStrategy};
pub use consensus_context::{ConsensusContext, ContextError};
pub use genesis::{
eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state,
process_activations,
@@ -1,3 +1,4 @@
use crate::consensus_context::ConsensusContext;
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
use rayon::prelude::*;
use safe_arith::{ArithError, SafeArith};
@@ -92,9 +93,9 @@ pub enum VerifyBlockRoot {
pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &mut BeaconState<T>,
signed_block: &SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
block_signature_strategy: BlockSignatureStrategy,
verify_block_root: VerifyBlockRoot,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let block = signed_block.message();
@@ -112,6 +113,8 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
let verify_signatures = match block_signature_strategy {
BlockSignatureStrategy::VerifyBulk => {
// Verify all signatures in the block at once.
let block_root = Some(ctxt.get_current_block_root(signed_block)?);
let proposer_index = Some(ctxt.get_proposer_index(state, spec)?);
block_verify!(
BlockSignatureVerifier::verify_entire_block(
state,
@@ -119,6 +122,7 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
|pk_bytes| pk_bytes.decompress().ok().map(Cow::Owned),
signed_block,
block_root,
proposer_index,
spec
)
.is_ok(),
@@ -135,11 +139,12 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
state,
block.temporary_block_header(),
verify_block_root,
ctxt,
spec,
)?;
if verify_signatures.is_true() {
verify_block_signature(state, signed_block, block_root, spec)?;
verify_block_signature(state, signed_block, ctxt, spec)?;
}
let verify_randao = if let BlockSignatureStrategy::VerifyRandao = block_signature_strategy {
@@ -159,9 +164,9 @@ pub fn per_block_processing<T: EthSpec, Payload: AbstractExecPayload<T>>(
process_execution_payload::<T, Payload>(state, payload, spec)?;
}
process_randao(state, block, verify_randao, spec)?;
process_randao(state, block, verify_randao, ctxt, spec)?;
process_eth1_data(state, block.body().eth1_data())?;
process_operations(state, block.body(), proposer_index, verify_signatures, spec)?;
process_operations(state, block.body(), verify_signatures, ctxt, spec)?;
if let Ok(sync_aggregate) = block.body().sync_aggregate() {
process_sync_aggregate(
@@ -183,6 +188,7 @@ pub fn process_block_header<T: EthSpec>(
state: &mut BeaconState<T>,
block_header: BeaconBlockHeader,
verify_block_root: VerifyBlockRoot,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<u64, BlockOperationError<HeaderInvalid>> {
// Verify that the slots match
@@ -201,8 +207,8 @@ pub fn process_block_header<T: EthSpec>(
);
// Verify that proposer index is the correct index
let proposer_index = block_header.proposer_index as usize;
let state_proposer_index = state.get_beacon_proposer_index(block_header.slot, spec)?;
let proposer_index = block_header.proposer_index;
let state_proposer_index = ctxt.get_proposer_index(state, spec)?;
verify!(
proposer_index == state_proposer_index,
HeaderInvalid::ProposerIndexMismatch {
@@ -226,11 +232,11 @@ pub fn process_block_header<T: EthSpec>(
// Verify proposer is not slashed
verify!(
!state.get_validator(proposer_index)?.slashed,
!state.get_validator(proposer_index as usize)?.slashed,
HeaderInvalid::ProposerSlashed(proposer_index)
);
Ok(proposer_index as u64)
Ok(proposer_index)
}
/// Verifies the signature of a block.
@@ -239,15 +245,18 @@ pub fn process_block_header<T: EthSpec>(
pub fn verify_block_signature<T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &BeaconState<T>,
block: &SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockOperationError<HeaderInvalid>> {
let block_root = Some(ctxt.get_current_block_root(block)?);
let proposer_index = Some(ctxt.get_proposer_index(state, spec)?);
verify!(
block_proposal_signature_set(
state,
|i| get_pubkey_from_state(state, i),
block,
block_root,
proposer_index,
spec
)?
.verify(),
@@ -263,12 +272,21 @@ pub fn process_randao<T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &mut BeaconState<T>,
block: BeaconBlockRef<'_, T, Payload>,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
if verify_signatures.is_true() {
// Verify RANDAO reveal signature.
let proposer_index = ctxt.get_proposer_index(state, spec)?;
block_verify!(
randao_signature_set(state, |i| get_pubkey_from_state(state, i), block, spec)?.verify(),
randao_signature_set(
state,
|i| get_pubkey_from_state(state, i),
block,
Some(proposer_index),
spec
)?
.verify(),
BlockProcessingError::RandaoSignatureInvalid
);
}
@@ -123,10 +123,11 @@ where
decompressor: D,
block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
spec: &'a ChainSpec,
) -> Result<()> {
let mut verifier = Self::new(state, get_pubkey, decompressor, spec);
verifier.include_all_signatures(block, block_root)?;
verifier.include_all_signatures(block, block_root, verified_proposer_index)?;
verifier.verify()
}
@@ -135,9 +136,10 @@ where
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
) -> Result<()> {
self.include_block_proposal(block, block_root)?;
self.include_all_signatures_except_proposal(block)?;
self.include_block_proposal(block, block_root, verified_proposer_index)?;
self.include_all_signatures_except_proposal(block, verified_proposer_index)?;
Ok(())
}
@@ -147,8 +149,9 @@ where
pub fn include_all_signatures_except_proposal<Payload: AbstractExecPayload<T>>(
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
verified_proposer_index: Option<u64>,
) -> Result<()> {
self.include_randao_reveal(block)?;
self.include_randao_reveal(block, verified_proposer_index)?;
self.include_proposer_slashings(block)?;
self.include_attester_slashings(block)?;
self.include_attestations(block)?;
@@ -164,12 +167,14 @@ where
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
) -> Result<()> {
let set = block_proposal_signature_set(
self.state,
self.get_pubkey.clone(),
block,
block_root,
verified_proposer_index,
self.spec,
)?;
self.sets.push(set);
@@ -180,11 +185,13 @@ where
pub fn include_randao_reveal<Payload: AbstractExecPayload<T>>(
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
verified_proposer_index: Option<u64>,
) -> Result<()> {
let set = randao_signature_set(
self.state,
self.get_pubkey.clone(),
block.message(),
verified_proposer_index,
self.spec,
)?;
self.sets.push(set);
@@ -1,4 +1,5 @@
use super::signature_sets::Error as SignatureSetError;
use crate::ContextError;
use merkle_proof::MerkleTreeError;
use safe_arith::ArithError;
use ssz::DecodeError;
@@ -72,6 +73,7 @@ pub enum BlockProcessingError {
found: u64,
},
ExecutionInvalid,
ConsensusContext(ContextError),
BlobVersionHashMismatch,
/// The number of commitments in blob transactions in the payload does not match the number
/// of commitments in the block.
@@ -122,6 +124,12 @@ impl From<SyncAggregateInvalid> for BlockProcessingError {
}
}
impl From<ContextError> for BlockProcessingError {
fn from(e: ContextError) -> Self {
BlockProcessingError::ConsensusContext(e)
}
}
impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
match e {
@@ -129,6 +137,7 @@ impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e),
BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e),
BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e),
}
}
@@ -156,6 +165,7 @@ macro_rules! impl_into_block_processing_error_with_index {
BlockOperationError::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockProcessingError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockProcessingError::SszTypesError(e),
BlockOperationError::ConsensusContext(e) => BlockProcessingError::ConsensusContext(e),
BlockOperationError::ArithError(e) => BlockProcessingError::ArithError(e),
}
}
@@ -187,6 +197,7 @@ pub enum BlockOperationError<T> {
BeaconStateError(BeaconStateError),
SignatureSetError(SignatureSetError),
SszTypesError(ssz_types::Error),
ConsensusContext(ContextError),
ArithError(ArithError),
}
@@ -219,6 +230,12 @@ impl<T> From<ArithError> for BlockOperationError<T> {
}
}
impl<T> From<ContextError> for BlockOperationError<T> {
fn from(e: ContextError) -> Self {
BlockOperationError::ConsensusContext(e)
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum HeaderInvalid {
ProposalSignatureInvalid,
@@ -228,14 +245,14 @@ pub enum HeaderInvalid {
block_slot: Slot,
},
ProposerIndexMismatch {
block_proposer_index: usize,
state_proposer_index: usize,
block_proposer_index: u64,
state_proposer_index: u64,
},
ParentBlockRootMismatch {
state: Hash256,
block: Hash256,
},
ProposerSlashed(usize),
ProposerSlashed(u64),
}
#[derive(Debug, PartialEq, Clone)]
@@ -330,6 +347,7 @@ impl From<BlockOperationError<IndexedAttestationInvalid>>
BlockOperationError::BeaconStateError(e) => BlockOperationError::BeaconStateError(e),
BlockOperationError::SignatureSetError(e) => BlockOperationError::SignatureSetError(e),
BlockOperationError::SszTypesError(e) => BlockOperationError::SszTypesError(e),
BlockOperationError::ConsensusContext(e) => BlockOperationError::ConsensusContext(e),
BlockOperationError::ArithError(e) => BlockOperationError::ArithError(e),
}
}
@@ -12,23 +12,25 @@ use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_
pub fn process_operations<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &mut BeaconState<T>,
block_body: BeaconBlockBodyRef<'a, T, Payload>,
proposer_index: u64,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
process_proposer_slashings(
state,
block_body.proposer_slashings(),
verify_signatures,
ctxt,
spec,
)?;
process_attester_slashings(
state,
block_body.attester_slashings(),
verify_signatures,
ctxt,
spec,
)?;
process_attestations(state, block_body, proposer_index, verify_signatures, spec)?;
process_attestations(state, block_body, verify_signatures, ctxt, spec)?;
process_deposits(state, block_body.deposits(), spec)?;
process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?;
Ok(())
@@ -45,12 +47,13 @@ pub mod base {
state: &mut BeaconState<T>,
attestations: &[Attestation<T>],
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Ensure the previous epoch cache exists.
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)? as u64;
let proposer_index = ctxt.get_proposer_index(state, spec)?;
// Verify and apply each attestation.
for (i, attestation) in attestations.iter().enumerate() {
@@ -87,10 +90,11 @@ pub mod altair {
pub fn process_attestations<T: EthSpec>(
state: &mut BeaconState<T>,
attestations: &[Attestation<T>],
proposer_index: u64,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
let proposer_index = ctxt.get_proposer_index(state, spec)?;
attestations
.iter()
.enumerate()
@@ -170,6 +174,7 @@ pub fn process_proposer_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
proposer_slashings: &[ProposerSlashing],
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
// Verify and apply proposer slashings in series.
@@ -186,6 +191,7 @@ pub fn process_proposer_slashings<T: EthSpec>(
state,
proposer_slashing.signed_header_1.message.proposer_index as usize,
None,
ctxt,
spec,
)?;
@@ -201,6 +207,7 @@ pub fn process_attester_slashings<T: EthSpec>(
state: &mut BeaconState<T>,
attester_slashings: &[AttesterSlashing<T>],
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
@@ -211,7 +218,7 @@ pub fn process_attester_slashings<T: EthSpec>(
get_slashable_indices(state, attester_slashing).map_err(|e| e.into_with_index(i))?;
for i in slashable_indices {
slash_validator(state, i as usize, None, spec)?;
slash_validator(state, i as usize, None, ctxt, spec)?;
}
}
@@ -222,13 +229,19 @@ pub fn process_attester_slashings<T: EthSpec>(
pub fn process_attestations<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
state: &mut BeaconState<T>,
block_body: BeaconBlockBodyRef<'a, T, Payload>,
proposer_index: u64,
verify_signatures: VerifySignatures,
ctxt: &mut ConsensusContext<T>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
match block_body {
BeaconBlockBodyRef::Base(_) => {
base::process_attestations(state, block_body.attestations(), verify_signatures, spec)?;
base::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
}
BeaconBlockBodyRef::Altair(_)
| BeaconBlockBodyRef::Merge(_)
@@ -237,8 +250,8 @@ pub fn process_attestations<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
altair::process_attestations(
state,
block_body.attestations(),
proposer_index,
verify_signatures,
ctxt,
spec,
)?;
}
@@ -76,6 +76,7 @@ pub fn block_proposal_signature_set<'a, T, F, Payload: AbstractExecPayload<T>>(
get_pubkey: F,
signed_block: &'a SignedBeaconBlock<T, Payload>,
block_root: Option<Hash256>,
verified_proposer_index: Option<u64>,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>>
where
@@ -83,8 +84,12 @@ where
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let block = signed_block.message();
let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)? as u64;
let proposer_index = if let Some(proposer_index) = verified_proposer_index {
proposer_index
} else {
state.get_beacon_proposer_index(block.slot(), spec)? as u64
};
if proposer_index != block.proposer_index() {
return Err(Error::IncorrectBlockProposer {
block: block.proposer_index(),
@@ -156,13 +161,18 @@ pub fn randao_signature_set<'a, T, F, Payload: AbstractExecPayload<T>>(
state: &'a BeaconState<T>,
get_pubkey: F,
block: BeaconBlockRef<'a, T, Payload>,
verified_proposer_index: Option<u64>,
spec: &'a ChainSpec,
) -> Result<SignatureSet<'a>>
where
T: EthSpec,
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
{
let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?;
let proposer_index = if let Some(proposer_index) = verified_proposer_index {
proposer_index
} else {
state.get_beacon_proposer_index(block.slot(), spec)? as u64
};
let domain = spec.get_domain(
block.slot().epoch(T::slots_per_epoch()),
@@ -178,7 +188,7 @@ where
Ok(SignatureSet::single_pubkey(
block.body().randao_reveal(),
get_pubkey(proposer_index).ok_or(Error::ValidatorUnknown(proposer_index as u64))?,
get_pubkey(proposer_index as usize).ok_or(Error::ValidatorUnknown(proposer_index))?,
message,
))
}
@@ -8,7 +8,7 @@ use crate::per_block_processing::errors::{
};
use crate::{
per_block_processing::{process_operations, verify_exit::verify_exit},
BlockSignatureStrategy, VerifyBlockRoot, VerifySignatures,
BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, VerifySignatures,
};
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
use lazy_static::lazy_static;
@@ -67,12 +67,13 @@ async fn valid_block_ok() {
.make_block_return_pre_state(state, slot + Slot::new(1))
.await;
let mut ctxt = ConsensusContext::new(block.slot());
let result = per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::VerifyIndividual,
VerifyBlockRoot::True,
&mut ctxt,
&spec,
);
@@ -91,12 +92,13 @@ async fn invalid_block_header_state_slot() {
let (mut block, signature) = signed_block.deconstruct();
*block.slot_mut() = slot + Slot::new(1);
let mut ctxt = ConsensusContext::new(block.slot());
let result = per_block_processing(
&mut state,
&SignedBeaconBlock::from_block(block, signature),
None,
BlockSignatureStrategy::VerifyIndividual,
VerifyBlockRoot::True,
&mut ctxt,
&spec,
);
@@ -122,12 +124,13 @@ async fn invalid_parent_block_root() {
let (mut block, signature) = signed_block.deconstruct();
*block.parent_root_mut() = Hash256::from([0xAA; 32]);
let mut ctxt = ConsensusContext::new(block.slot());
let result = per_block_processing(
&mut state,
&SignedBeaconBlock::from_block(block, signature),
None,
BlockSignatureStrategy::VerifyIndividual,
VerifyBlockRoot::True,
&mut ctxt,
&spec,
);
@@ -154,12 +157,13 @@ async fn invalid_block_signature() {
.await;
let (block, _) = signed_block.deconstruct();
let mut ctxt = ConsensusContext::new(block.slot());
let result = per_block_processing(
&mut state,
&SignedBeaconBlock::from_block(block, Signature::empty()),
None,
BlockSignatureStrategy::VerifyIndividual,
VerifyBlockRoot::True,
&mut ctxt,
&spec,
);
@@ -186,12 +190,13 @@ async fn invalid_randao_reveal_signature() {
})
.await;
let mut ctxt = ConsensusContext::new(signed_block.slot());
let result = per_block_processing(
&mut state,
&signed_block,
None,
BlockSignatureStrategy::VerifyIndividual,
VerifyBlockRoot::True,
&mut ctxt,
&spec,
);
@@ -386,11 +391,12 @@ async fn invalid_attestation_no_committee_for_index() {
head_block.to_mut().body_mut().attestations_mut()[0]
.data
.index += 1;
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -424,11 +430,12 @@ async fn invalid_attestation_wrong_justified_checkpoint() {
.data
.source = new_justified_checkpoint;
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -463,11 +470,12 @@ async fn invalid_attestation_bad_aggregation_bitfield_len() {
head_block.to_mut().body_mut().attestations_mut()[0].aggregation_bits =
Bitfield::with_capacity(spec.target_committee_size).unwrap();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -495,11 +503,12 @@ async fn invalid_attestation_bad_signature() {
.0;
head_block.to_mut().body_mut().attestations_mut()[0].signature = AggregateSignature::empty();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
// Expecting BadSignature because we're signing with invalid secret_keys
@@ -533,11 +542,12 @@ async fn invalid_attestation_included_too_early() {
.data
.slot = new_attesation_slot;
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -575,11 +585,12 @@ async fn invalid_attestation_included_too_late() {
.data
.slot = new_attesation_slot;
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
assert_eq!(
@@ -613,11 +624,12 @@ async fn invalid_attestation_target_epoch_slot_mismatch() {
.target
.epoch += Epoch::new(1);
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attestations(
&mut state,
head_block.body(),
head_block.proposer_index(),
VerifySignatures::True,
&mut ctxt,
&spec,
);
assert_eq!(
@@ -640,10 +652,12 @@ async fn valid_insert_attester_slashing() {
let attester_slashing = harness.make_attester_slashing(vec![1, 2]);
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attester_slashings(
&mut state,
&[attester_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -660,10 +674,12 @@ async fn invalid_attester_slashing_not_slashable() {
attester_slashing.attestation_1 = attester_slashing.attestation_2.clone();
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attester_slashings(
&mut state,
&[attester_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -686,10 +702,12 @@ async fn invalid_attester_slashing_1_invalid() {
attester_slashing.attestation_1.attesting_indices = VariableList::from(vec![2, 1]);
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attester_slashings(
&mut state,
&[attester_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -715,10 +733,12 @@ async fn invalid_attester_slashing_2_invalid() {
attester_slashing.attestation_2.attesting_indices = VariableList::from(vec![2, 1]);
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_attester_slashings(
&mut state,
&[attester_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -741,10 +761,12 @@ async fn valid_insert_proposer_slashing() {
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 mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
// Expecting Ok(_) because we inserted a valid proposer slashing
@@ -760,10 +782,12 @@ async fn invalid_proposer_slashing_proposals_identical() {
proposer_slashing.signed_header_1.message = proposer_slashing.signed_header_2.message.clone();
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -787,10 +811,12 @@ async fn invalid_proposer_slashing_proposer_unknown() {
proposer_slashing.signed_header_2.message.proposer_index = 3_141_592;
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -811,10 +837,12 @@ async fn invalid_proposer_slashing_duplicate_slashing() {
let proposer_slashing = harness.make_proposer_slashing(1);
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result_1 = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing.clone()],
VerifySignatures::False,
&mut ctxt,
&spec,
);
assert!(result_1.is_ok());
@@ -823,6 +851,7 @@ async fn invalid_proposer_slashing_duplicate_slashing() {
&mut state,
&[proposer_slashing],
VerifySignatures::False,
&mut ctxt,
&spec,
);
// Expecting ProposerNotSlashable because we've already slashed the validator
@@ -842,10 +871,12 @@ async fn invalid_bad_proposal_1_signature() {
let mut proposer_slashing = harness.make_proposer_slashing(1);
proposer_slashing.signed_header_1.signature = Signature::empty();
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -866,10 +897,12 @@ async fn invalid_bad_proposal_2_signature() {
let mut proposer_slashing = harness.make_proposer_slashing(1);
proposer_slashing.signed_header_2.signature = Signature::empty();
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::True,
&mut ctxt,
&spec,
);
@@ -891,10 +924,12 @@ async fn invalid_proposer_slashing_proposal_epoch_mismatch() {
proposer_slashing.signed_header_1.message.slot = Slot::new(0);
proposer_slashing.signed_header_2.message.slot = Slot::new(128);
let mut state = harness.get_current_state();
let mut ctxt = ConsensusContext::new(state.slot());
let result = process_operations::process_proposer_slashings(
&mut state,
&[proposer_slashing],
VerifySignatures::False,
&mut ctxt,
&spec,
);
@@ -29,9 +29,7 @@ pub fn verify_deposit_signature(deposit_data: &DepositData, spec: &ChainSpec) ->
/// Returns a `Some(validator index)` if a pubkey already exists in the `validators`,
/// otherwise returns `None`.
///
/// ## Errors
///
/// Errors if the state's `pubkey_cache` is not current.
/// Builds the pubkey cache if it is not already built.
pub fn get_existing_validator_index<T: EthSpec>(
state: &mut BeaconState<T>,
pub_key: &PublicKeyBytes,
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "types"
version = "0.2.0"
version = "0.2.1"
authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com>"]
edition = "2021"
+25
View File
@@ -178,6 +178,9 @@ pub struct ChainSpec {
pub attestation_subnet_count: u64,
pub random_subnets_per_validator: u64,
pub epochs_per_random_subnet_subscription: u64,
pub subnets_per_node: u8,
pub epochs_per_subnet_subscription: u64,
attestation_subnet_extra_bits: u8,
/*
* Application params
@@ -467,6 +470,22 @@ impl ChainSpec {
Hash256::from(domain)
}
#[allow(clippy::integer_arithmetic)]
pub const fn attestation_subnet_prefix_bits(&self) -> u32 {
// maybe use log2 when stable https://github.com/rust-lang/rust/issues/70887
// NOTE: this line is here simply to guarantee that if self.attestation_subnet_count type
// is changed, a compiler warning will be raised. This code depends on the type being u64.
let attestation_subnet_count: u64 = self.attestation_subnet_count;
let attestation_subnet_count_bits = if attestation_subnet_count == 0 {
0
} else {
63 - attestation_subnet_count.leading_zeros()
};
self.attestation_subnet_extra_bits as u32 + attestation_subnet_count_bits
}
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
pub fn mainnet() -> Self {
Self {
@@ -630,9 +649,12 @@ impl ChainSpec {
attestation_propagation_slot_range: 32,
attestation_subnet_count: 64,
random_subnets_per_validator: 1,
subnets_per_node: 1,
maximum_gossip_clock_disparity_millis: 500,
target_aggregators_per_committee: 16,
epochs_per_random_subnet_subscription: 256,
epochs_per_subnet_subscription: 256,
attestation_subnet_extra_bits: 6,
/*
* Application specific
@@ -865,9 +887,12 @@ impl ChainSpec {
attestation_propagation_slot_range: 32,
attestation_subnet_count: 64,
random_subnets_per_validator: 1,
subnets_per_node: 1,
maximum_gossip_clock_disparity_millis: 500,
target_aggregators_per_committee: 16,
epochs_per_random_subnet_subscription: 256,
epochs_per_subnet_subscription: 256,
attestation_subnet_extra_bits: 6,
/*
* Application specific
@@ -0,0 +1,83 @@
use crate::*;
use eth2_hashing::{hash32_concat, ZERO_HASHES};
use int_to_bytes::int_to_bytes32;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use test_utils::TestRandom;
use DEPOSIT_TREE_DEPTH;
#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq, TestRandom)]
pub struct FinalizedExecutionBlock {
pub deposit_root: Hash256,
pub deposit_count: u64,
pub block_hash: Hash256,
pub block_height: u64,
}
impl From<&DepositTreeSnapshot> for FinalizedExecutionBlock {
fn from(snapshot: &DepositTreeSnapshot) -> Self {
Self {
deposit_root: snapshot.deposit_root,
deposit_count: snapshot.deposit_count,
block_hash: snapshot.execution_block_hash,
block_height: snapshot.execution_block_height,
}
}
}
#[derive(Encode, Decode, Deserialize, Serialize, Clone, Debug, PartialEq, TestRandom)]
pub struct DepositTreeSnapshot {
pub finalized: Vec<Hash256>,
pub deposit_root: Hash256,
pub deposit_count: u64,
pub execution_block_hash: Hash256,
pub execution_block_height: u64,
}
impl Default for DepositTreeSnapshot {
fn default() -> Self {
let mut result = Self {
finalized: vec![],
deposit_root: Hash256::default(),
deposit_count: 0,
execution_block_hash: Hash256::zero(),
execution_block_height: 0,
};
// properly set the empty deposit root
result.deposit_root = result.calculate_root().unwrap();
result
}
}
impl DepositTreeSnapshot {
// Calculates the deposit tree root from the hashes in the snapshot
pub fn calculate_root(&self) -> Option<Hash256> {
let mut size = self.deposit_count;
let mut index = self.finalized.len();
let mut deposit_root = [0; 32];
for height in 0..DEPOSIT_TREE_DEPTH {
deposit_root = if (size & 1) == 1 {
index = index.checked_sub(1)?;
hash32_concat(self.finalized.get(index)?.as_bytes(), &deposit_root)
} else {
hash32_concat(&deposit_root, ZERO_HASHES.get(height)?)
};
size /= 2;
}
// add mix-in-length
deposit_root = hash32_concat(&deposit_root, &int_to_bytes32(self.deposit_count));
Some(Hash256::from_slice(&deposit_root))
}
pub fn is_valid(&self) -> bool {
self.calculate_root()
.map_or(false, |calculated| self.deposit_root == calculated)
}
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(DepositTreeSnapshot);
}
+5
View File
@@ -35,6 +35,7 @@ pub mod contribution_and_proof;
pub mod deposit;
pub mod deposit_data;
pub mod deposit_message;
pub mod deposit_tree_snapshot;
pub mod enr_fork_id;
pub mod eth1_data;
pub mod eth_spec;
@@ -48,6 +49,9 @@ pub mod free_attestation;
pub mod graffiti;
pub mod historical_batch;
pub mod indexed_attestation;
pub mod light_client_bootstrap;
pub mod light_client_optimistic_update;
pub mod light_client_update;
pub mod pending_attestation;
pub mod proposer_preparation_data;
pub mod proposer_slashing;
@@ -122,6 +126,7 @@ pub use crate::contribution_and_proof::ContributionAndProof;
pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH};
pub use crate::deposit_data::DepositData;
pub use crate::deposit_message::DepositMessage;
pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock};
pub use crate::enr_fork_id::EnrForkId;
pub use crate::eth1_data::Eth1Data;
pub use crate::eth_spec::EthSpecId;
@@ -0,0 +1,45 @@
use super::{BeaconBlockHeader, BeaconState, EthSpec, FixedVector, Hash256, SyncCommittee};
use crate::{light_client_update::*, test_utils::TestRandom};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use std::sync::Arc;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
/// A LightClientBootstrap is the initializer we send over to lightclient nodes
/// that are trying to generate their basic storage when booting up.
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[serde(bound = "T: EthSpec")]
pub struct LightClientBootstrap<T: EthSpec> {
/// Requested beacon block header.
pub header: BeaconBlockHeader,
/// The `SyncCommittee` used in the requested period.
pub current_sync_committee: Arc<SyncCommittee<T>>,
/// Merkle proof for sync committee
pub current_sync_committee_branch: FixedVector<Hash256, CurrentSyncCommitteeProofLen>,
}
impl<T: EthSpec> LightClientBootstrap<T> {
pub fn from_beacon_state(beacon_state: BeaconState<T>) -> Result<Self, Error> {
let mut header = beacon_state.latest_block_header().clone();
header.state_root = beacon_state.tree_hash_root();
Ok(LightClientBootstrap {
header,
current_sync_committee: beacon_state.current_sync_committee()?.clone(),
/// TODO(Giulio2002): Generate Merkle Proof, this is just empty hashes
current_sync_committee_branch: FixedVector::new(vec![
Hash256::zero();
CURRENT_SYNC_COMMITTEE_PROOF_LEN
])?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientBootstrap<MainnetEthSpec>);
}
@@ -0,0 +1,80 @@
use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee};
use crate::{light_client_update::*, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec};
use safe_arith::ArithError;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::{U5, U6};
use std::sync::Arc;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
/// A LightClientFinalityUpdate is the update lightclient request or received by a gossip that
/// signal a new finalized beacon block header for the light client sync protocol.
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[serde(bound = "T: EthSpec")]
pub struct LightClientFinalityUpdate<T: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: BeaconBlockHeader,
/// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch).
pub finalized_header: BeaconBlockHeader,
/// Merkle proof attesting finalized header.
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<T>,
/// Slot of the sync aggregated singature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientFinalityUpdate<T> {
pub fn new(
chain_spec: ChainSpec,
beacon_state: BeaconState<T>,
block: BeaconBlock<T>,
attested_state: BeaconState<T>,
finalized_block: BeaconBlock<T>,
) -> Result<Self, Error> {
let altair_fork_epoch = chain_spec
.altair_fork_epoch
.ok_or(Error::AltairForkNotActive)?;
if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch {
return Err(Error::AltairForkNotActive);
}
let sync_aggregate = block.body().sync_aggregate()?;
if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize {
return Err(Error::NotEnoughSyncCommitteeParticipants);
}
// Compute and validate attested header.
let mut attested_header = attested_state.latest_block_header().clone();
attested_header.state_root = attested_state.tree_hash_root();
// Build finalized header from finalized block
let finalized_header = BeaconBlockHeader {
slot: finalized_block.slot(),
proposer_index: finalized_block.proposer_index(),
parent_root: finalized_block.parent_root(),
state_root: finalized_block.state_root(),
body_root: finalized_block.body_root(),
};
if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root {
return Err(Error::InvalidFinalizedBlock);
}
// TODO(Giulio2002): compute proper merkle proofs.
Ok(Self {
attested_header: attested_header,
finalized_header: finalized_header,
finality_branch: FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN])?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientFinalityUpdate<MainnetEthSpec>);
}
@@ -0,0 +1,59 @@
use super::{BeaconBlockHeader, EthSpec, Slot, SyncAggregate};
use crate::{
light_client_update::Error, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec,
};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
/// A LightClientOptimisticUpdate is the update we send on each slot,
/// it is based off the current unfinalized epoch is verified only against BLS signature.
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[serde(bound = "T: EthSpec")]
pub struct LightClientOptimisticUpdate<T: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: BeaconBlockHeader,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<T>,
/// Slot of the sync aggregated singature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientOptimisticUpdate<T> {
pub fn new(
chain_spec: ChainSpec,
block: BeaconBlock<T>,
attested_state: BeaconState<T>,
) -> Result<Self, Error> {
let altair_fork_epoch = chain_spec
.altair_fork_epoch
.ok_or(Error::AltairForkNotActive)?;
if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch {
return Err(Error::AltairForkNotActive);
}
let sync_aggregate = block.body().sync_aggregate()?;
if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize {
return Err(Error::NotEnoughSyncCommitteeParticipants);
}
// Compute and validate attested header.
let mut attested_header = attested_state.latest_block_header().clone();
attested_header.state_root = attested_state.tree_hash_root();
Ok(Self {
attested_header,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientOptimisticUpdate<MainnetEthSpec>);
}
+171
View File
@@ -0,0 +1,171 @@
use super::{BeaconBlockHeader, EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee};
use crate::{beacon_state, test_utils::TestRandom, BeaconBlock, BeaconState, ChainSpec};
use safe_arith::ArithError;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::typenum::{U5, U6};
use std::sync::Arc;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
pub const FINALIZED_ROOT_INDEX: usize = 105;
pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 54;
pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 55;
pub type FinalizedRootProofLen = U6;
pub type CurrentSyncCommitteeProofLen = U5;
pub type NextSyncCommitteeProofLen = U5;
pub const FINALIZED_ROOT_PROOF_LEN: usize = 6;
pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5;
pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5;
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
SszTypesError(ssz_types::Error),
BeaconStateError(beacon_state::Error),
ArithError(ArithError),
AltairForkNotActive,
NotEnoughSyncCommitteeParticipants,
MismatchingPeriods,
InvalidFinalizedBlock,
}
impl From<ssz_types::Error> for Error {
fn from(e: ssz_types::Error) -> Error {
Error::SszTypesError(e)
}
}
impl From<beacon_state::Error> for Error {
fn from(e: beacon_state::Error) -> Error {
Error::BeaconStateError(e)
}
}
impl From<ArithError> for Error {
fn from(e: ArithError) -> Error {
Error::ArithError(e)
}
}
/// A LightClientUpdate is the update we request solely to either complete the bootstraping process,
/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period
/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD].
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom)]
#[serde(bound = "T: EthSpec")]
pub struct LightClientUpdate<T: EthSpec> {
/// The last `BeaconBlockHeader` from the last attested block by the sync committee.
pub attested_header: BeaconBlockHeader,
/// The `SyncCommittee` used in the next period.
pub next_sync_committee: Arc<SyncCommittee<T>>,
/// Merkle proof for next sync committee
pub next_sync_committee_branch: FixedVector<Hash256, NextSyncCommitteeProofLen>,
/// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch).
pub finalized_header: BeaconBlockHeader,
/// Merkle proof attesting finalized header.
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<T>,
/// Slot of the sync aggregated singature
pub signature_slot: Slot,
}
impl<T: EthSpec> LightClientUpdate<T> {
pub fn new(
chain_spec: ChainSpec,
beacon_state: BeaconState<T>,
block: BeaconBlock<T>,
attested_state: BeaconState<T>,
finalized_block: BeaconBlock<T>,
) -> Result<Self, Error> {
let altair_fork_epoch = chain_spec
.altair_fork_epoch
.ok_or(Error::AltairForkNotActive)?;
if attested_state.slot().epoch(T::slots_per_epoch()) < altair_fork_epoch {
return Err(Error::AltairForkNotActive);
}
let sync_aggregate = block.body().sync_aggregate()?;
if sync_aggregate.num_set_bits() < chain_spec.min_sync_committee_participants as usize {
return Err(Error::NotEnoughSyncCommitteeParticipants);
}
let signature_period = block.epoch().sync_committee_period(&chain_spec)?;
// Compute and validate attested header.
let mut attested_header = attested_state.latest_block_header().clone();
attested_header.state_root = attested_state.tree_hash_root();
let attested_period = attested_header
.slot
.epoch(T::slots_per_epoch())
.sync_committee_period(&chain_spec)?;
if attested_period != signature_period {
return Err(Error::MismatchingPeriods);
}
// Build finalized header from finalized block
let finalized_header = BeaconBlockHeader {
slot: finalized_block.slot(),
proposer_index: finalized_block.proposer_index(),
parent_root: finalized_block.parent_root(),
state_root: finalized_block.state_root(),
body_root: finalized_block.body_root(),
};
if finalized_header.tree_hash_root() != beacon_state.finalized_checkpoint().root {
return Err(Error::InvalidFinalizedBlock);
}
// TODO(Giulio2002): compute proper merkle proofs.
Ok(Self {
attested_header,
next_sync_committee: attested_state.next_sync_committee()?.clone(),
next_sync_committee_branch: FixedVector::new(vec![
Hash256::zero();
NEXT_SYNC_COMMITTEE_PROOF_LEN
])?,
finalized_header,
finality_branch: FixedVector::new(vec![Hash256::zero(); FINALIZED_ROOT_PROOF_LEN])?,
sync_aggregate: sync_aggregate.clone(),
signature_slot: block.slot(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MainnetEthSpec;
use ssz_types::typenum::Unsigned;
ssz_tests!(LightClientUpdate<MainnetEthSpec>);
#[test]
fn finalized_root_params() {
assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX);
assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32 + 1) > FINALIZED_ROOT_INDEX);
assert_eq!(FinalizedRootProofLen::to_usize(), FINALIZED_ROOT_PROOF_LEN);
}
#[test]
fn current_sync_committee_params() {
assert!(
2usize.pow(CURRENT_SYNC_COMMITTEE_PROOF_LEN as u32) <= CURRENT_SYNC_COMMITTEE_INDEX
);
assert!(
2usize.pow(CURRENT_SYNC_COMMITTEE_PROOF_LEN as u32 + 1) > CURRENT_SYNC_COMMITTEE_INDEX
);
assert_eq!(
CurrentSyncCommitteeProofLen::to_usize(),
CURRENT_SYNC_COMMITTEE_PROOF_LEN
);
}
#[test]
fn next_sync_committee_params() {
assert!(2usize.pow(NEXT_SYNC_COMMITTEE_PROOF_LEN as u32) <= NEXT_SYNC_COMMITTEE_INDEX);
assert!(2usize.pow(NEXT_SYNC_COMMITTEE_PROOF_LEN as u32 + 1) > NEXT_SYNC_COMMITTEE_INDEX);
assert_eq!(
NextSyncCommitteeProofLen::to_usize(),
NEXT_SYNC_COMMITTEE_PROOF_LEN
);
}
}
+41 -1
View File
@@ -1,8 +1,9 @@
//! Identifies each shard by an integer identifier.
use crate::{AttestationData, ChainSpec, CommitteeIndex, EthSpec, Slot};
use crate::{AttestationData, ChainSpec, CommitteeIndex, Epoch, EthSpec, Slot};
use safe_arith::{ArithError, SafeArith};
use serde_derive::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use swap_or_not_shuffle::compute_shuffled_index;
const MAX_SUBNET_ID: usize = 64;
@@ -71,6 +72,45 @@ impl SubnetId {
.safe_rem(spec.attestation_subnet_count)?
.into())
}
#[allow(clippy::integer_arithmetic)]
/// Computes the set of subnets the node should be subscribed to during the current epoch,
/// along with the first epoch in which these subscriptions are no longer valid.
pub fn compute_subnets_for_epoch<T: EthSpec>(
node_id: ethereum_types::U256,
epoch: Epoch,
spec: &ChainSpec,
) -> Result<(impl Iterator<Item = SubnetId>, Epoch), &'static str> {
let node_id_prefix =
(node_id >> (256 - spec.attestation_subnet_prefix_bits() as usize)).as_usize();
let subscription_event_idx = epoch.as_u64() / spec.epochs_per_subnet_subscription;
let permutation_seed =
eth2_hashing::hash(&int_to_bytes::int_to_bytes8(subscription_event_idx));
let num_subnets = 1 << spec.attestation_subnet_prefix_bits();
let permutated_prefix = compute_shuffled_index(
node_id_prefix,
num_subnets,
&permutation_seed,
spec.shuffle_round_count,
)
.ok_or("Unable to shuffle")? as u64;
// Get the constants we need to avoid holding a reference to the spec
let &ChainSpec {
subnets_per_node,
attestation_subnet_count,
..
} = spec;
let subnet_set_generator = (0..subnets_per_node).map(move |idx| {
SubnetId::new((permutated_prefix + idx as u64) % attestation_subnet_count)
});
let valid_until_epoch = (subscription_event_idx + 1) * spec.epochs_per_subnet_subscription;
Ok((subnet_set_generator, valid_until_epoch.into()))
}
}
impl Deref for SubnetId {