Merge remote-tracking branch 'origin/unstable' into tree-states

This commit is contained in:
Michael Sproul
2022-05-24 10:01:05 +10:00
237 changed files with 8506 additions and 3598 deletions

View File

@@ -9,6 +9,7 @@ use derivative::Derivative;
use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, DecodeError};
use ssz_derive::{Decode, Encode};
use std::marker::PhantomData;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
@@ -29,23 +30,25 @@ use tree_hash_derive::TreeHash;
TestRandom,
Derivative,
),
derivative(PartialEq, Hash(bound = "T: EthSpec")),
serde(bound = "T: EthSpec", deny_unknown_fields),
derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: ExecPayload<T>")),
serde(bound = "T: EthSpec, Payload: ExecPayload<T>", deny_unknown_fields),
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)),
),
ref_attributes(
derive(Debug, PartialEq, TreeHash),
tree_hash(enum_behaviour = "transparent")
)
),
map_ref_into(BeaconBlockBodyRef),
map_ref_mut_into(BeaconBlockBodyRefMut)
)]
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
#[serde(untagged)]
#[serde(bound = "T: EthSpec")]
#[serde(bound = "T: EthSpec, Payload: ExecPayload<T>")]
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
pub struct BeaconBlock<T: EthSpec> {
pub struct BeaconBlock<T: EthSpec, Payload: ExecPayload<T> = FullPayload<T>> {
#[superstruct(getter(copy))]
pub slot: Slot,
#[superstruct(getter(copy))]
@@ -56,17 +59,17 @@ pub struct BeaconBlock<T: EthSpec> {
#[superstruct(getter(copy))]
pub state_root: Hash256,
#[superstruct(only(Base), partial_getter(rename = "body_base"))]
pub body: BeaconBlockBodyBase<T>,
pub body: BeaconBlockBodyBase<T, Payload>,
#[superstruct(only(Altair), partial_getter(rename = "body_altair"))]
pub body: BeaconBlockBodyAltair<T>,
pub body: BeaconBlockBodyAltair<T, Payload>,
#[superstruct(only(Merge), partial_getter(rename = "body_merge"))]
pub body: BeaconBlockBodyMerge<T>,
pub body: BeaconBlockBodyMerge<T, Payload>,
}
impl<T: EthSpec> SignedRoot for BeaconBlock<T> {}
impl<'a, T: EthSpec> SignedRoot for BeaconBlockRef<'a, T> {}
impl<T: EthSpec, Payload: ExecPayload<T>> SignedRoot for BeaconBlock<T, Payload> {}
impl<'a, T: EthSpec, Payload: ExecPayload<T>> SignedRoot for BeaconBlockRef<'a, T, Payload> {}
impl<T: EthSpec> BeaconBlock<T> {
impl<T: EthSpec, Payload: ExecPayload<T>> BeaconBlock<T, Payload> {
/// Returns an empty block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
if spec.bellatrix_fork_epoch == Some(T::genesis_epoch()) {
@@ -114,12 +117,12 @@ impl<T: EthSpec> BeaconBlock<T> {
}
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
pub fn body(&self) -> BeaconBlockBodyRef<'_, T> {
pub fn body(&self) -> BeaconBlockBodyRef<'_, T, Payload> {
self.to_ref().body()
}
/// Convenience accessor for the `body` as a `BeaconBlockBodyRefMut`.
pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T> {
pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T, Payload> {
self.to_mut().body_mut()
}
@@ -160,7 +163,7 @@ impl<T: EthSpec> BeaconBlock<T> {
fork: &Fork,
genesis_validators_root: Hash256,
spec: &ChainSpec,
) -> SignedBeaconBlock<T> {
) -> SignedBeaconBlock<T, Payload> {
let domain = spec.get_domain(
self.epoch(),
Domain::BeaconProposer,
@@ -173,7 +176,7 @@ impl<T: EthSpec> BeaconBlock<T> {
}
}
impl<'a, T: EthSpec> BeaconBlockRef<'a, T> {
impl<'a, T: EthSpec, Payload: ExecPayload<T>> BeaconBlockRef<'a, T, Payload> {
/// Returns the name of the fork pertaining to `self`.
///
/// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
@@ -197,21 +200,18 @@ impl<'a, T: EthSpec> BeaconBlockRef<'a, T> {
}
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
pub fn body(&self) -> BeaconBlockBodyRef<'a, T> {
match self {
BeaconBlockRef::Base(block) => BeaconBlockBodyRef::Base(&block.body),
BeaconBlockRef::Altair(block) => BeaconBlockBodyRef::Altair(&block.body),
BeaconBlockRef::Merge(block) => BeaconBlockBodyRef::Merge(&block.body),
}
pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> {
map_beacon_block_ref_into_beacon_block_body_ref!(&'a _, *self, |block, cons| cons(
&block.body
))
}
/// Return the tree hash root of the block's body.
pub fn body_root(&self) -> Hash256 {
match self {
BeaconBlockRef::Base(block) => block.body.tree_hash_root(),
BeaconBlockRef::Altair(block) => block.body.tree_hash_root(),
BeaconBlockRef::Merge(block) => block.body.tree_hash_root(),
}
map_beacon_block_ref!(&'a _, *self, |block, cons| {
let _: Self = cons(block);
block.body.tree_hash_root()
})
}
/// Returns the epoch corresponding to `self.slot()`.
@@ -240,23 +240,21 @@ impl<'a, T: EthSpec> BeaconBlockRef<'a, T> {
/// Extracts a reference to an execution payload from a block, returning an error if the block
/// is pre-merge.
pub fn execution_payload(&self) -> Result<&ExecutionPayload<T>, Error> {
pub fn execution_payload(&self) -> Result<&Payload, Error> {
self.body().execution_payload()
}
}
impl<'a, T: EthSpec> BeaconBlockRefMut<'a, T> {
impl<'a, T: EthSpec, Payload: ExecPayload<T>> BeaconBlockRefMut<'a, T, Payload> {
/// Convert a mutable reference to a beacon block to a mutable ref to its body.
pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T> {
match self {
BeaconBlockRefMut::Base(block) => BeaconBlockBodyRefMut::Base(&mut block.body),
BeaconBlockRefMut::Altair(block) => BeaconBlockBodyRefMut::Altair(&mut block.body),
BeaconBlockRefMut::Merge(block) => BeaconBlockBodyRefMut::Merge(&mut block.body),
}
pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T, Payload> {
map_beacon_block_ref_mut_into_beacon_block_body_ref_mut!(&'a _, self, |block, cons| cons(
&mut block.body
))
}
}
impl<T: EthSpec> BeaconBlockBase<T> {
impl<T: EthSpec, Payload: ExecPayload<T>> BeaconBlockBase<T, Payload> {
/// Returns an empty block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
BeaconBlockBase {
@@ -277,6 +275,7 @@ impl<T: EthSpec> BeaconBlockBase<T> {
attestations: VariableList::empty(),
deposits: VariableList::empty(),
voluntary_exits: VariableList::empty(),
_phantom: PhantomData,
},
}
}
@@ -343,7 +342,7 @@ impl<T: EthSpec> BeaconBlockBase<T> {
signature: Signature::empty(),
};
let mut block = BeaconBlockBase::<T>::empty(spec);
let mut block = BeaconBlockBase::<T, Payload>::empty(spec);
for _ in 0..T::MaxProposerSlashings::to_usize() {
block
.body
@@ -376,7 +375,7 @@ impl<T: EthSpec> BeaconBlockBase<T> {
}
}
impl<T: EthSpec> BeaconBlockAltair<T> {
impl<T: EthSpec, Payload: ExecPayload<T>> BeaconBlockAltair<T, Payload> {
/// Returns an empty Altair block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
BeaconBlockAltair {
@@ -398,13 +397,14 @@ impl<T: EthSpec> BeaconBlockAltair<T> {
deposits: VariableList::empty(),
voluntary_exits: VariableList::empty(),
sync_aggregate: SyncAggregate::empty(),
_phantom: PhantomData,
},
}
}
/// Return an Altair block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let base_block = BeaconBlockBase::full(spec);
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
@@ -428,12 +428,13 @@ impl<T: EthSpec> BeaconBlockAltair<T> {
deposit_count: 0,
},
graffiti: Graffiti::default(),
_phantom: PhantomData,
},
}
}
}
impl<T: EthSpec> BeaconBlockMerge<T> {
impl<T: EthSpec, Payload: ExecPayload<T>> BeaconBlockMerge<T, Payload> {
/// Returns an empty Merge block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
BeaconBlockMerge {
@@ -455,39 +456,105 @@ impl<T: EthSpec> BeaconBlockMerge<T> {
deposits: VariableList::empty(),
voluntary_exits: VariableList::empty(),
sync_aggregate: SyncAggregate::empty(),
execution_payload: ExecutionPayload::empty(),
execution_payload: Payload::default(),
},
}
}
}
/// Return an Merge block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let altair_block = BeaconBlockAltair::full(spec);
BeaconBlockMerge {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyMerge {
proposer_slashings: altair_block.body.proposer_slashings,
attester_slashings: altair_block.body.attester_slashings,
attestations: altair_block.body.attestations,
deposits: altair_block.body.deposits,
voluntary_exits: altair_block.body.voluntary_exits,
sync_aggregate: altair_block.body.sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
execution_payload: ExecutionPayload::default(),
},
// We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads.
impl<E: EthSpec> From<BeaconBlockBase<E, BlindedPayload<E>>>
for BeaconBlockBase<E, FullPayload<E>>
{
fn from(block: BeaconBlockBase<E, BlindedPayload<E>>) -> Self {
let BeaconBlockBase {
slot,
proposer_index,
parent_root,
state_root,
body,
} = block;
BeaconBlockBase {
slot,
proposer_index,
parent_root,
state_root,
body: body.into(),
}
}
}
impl<E: EthSpec> From<BeaconBlockAltair<E, BlindedPayload<E>>>
for BeaconBlockAltair<E, FullPayload<E>>
{
fn from(block: BeaconBlockAltair<E, BlindedPayload<E>>) -> Self {
let BeaconBlockAltair {
slot,
proposer_index,
parent_root,
state_root,
body,
} = block;
BeaconBlockAltair {
slot,
proposer_index,
parent_root,
state_root,
body: body.into(),
}
}
}
// We can convert blocks with payloads to blocks without payloads, and an optional payload.
macro_rules! impl_from {
($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>, $body_expr:expr) => {
impl<E: EthSpec> From<$ty_name<$($from_params),*>>
for ($ty_name<$($to_params),*>, Option<ExecutionPayload<E>>)
{
#[allow(clippy::redundant_closure_call)]
fn from(block: $ty_name<$($from_params),*>) -> Self {
let $ty_name {
slot,
proposer_index,
parent_root,
state_root,
body,
} = block;
let (body, payload) = ($body_expr)(body);
($ty_name {
slot,
proposer_index,
parent_root,
state_root,
body,
}, payload)
}
}
}
}
impl_from!(BeaconBlockBase, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyBase<_, _>| body.into());
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());
impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
for (
BeaconBlock<E, BlindedPayload<E>>,
Option<ExecutionPayload<E>>,
)
{
fn from(block: BeaconBlock<E, FullPayload<E>>) -> Self {
map_beacon_block!(block, |inner, cons| {
let (block, payload) = inner.into();
(cons(block), payload)
})
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -4,6 +4,7 @@ use derivative::Derivative;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use ssz_types::VariableList;
use std::marker::PhantomData;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
@@ -25,8 +26,8 @@ use tree_hash_derive::TreeHash;
TestRandom,
Derivative,
),
derivative(PartialEq, Hash(bound = "T: EthSpec")),
serde(bound = "T: EthSpec", deny_unknown_fields),
derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: ExecPayload<T>")),
serde(bound = "T: EthSpec, Payload: ExecPayload<T>", deny_unknown_fields),
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
@@ -35,9 +36,9 @@ use tree_hash_derive::TreeHash;
#[derive(Debug, Clone, Serialize, Deserialize, Derivative)]
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
#[serde(untagged)]
#[serde(bound = "T: EthSpec")]
#[serde(bound = "T: EthSpec, Payload: ExecPayload<T>")]
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
pub struct BeaconBlockBody<T: EthSpec> {
pub struct BeaconBlockBody<T: EthSpec, Payload: ExecPayload<T> = FullPayload<T>> {
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
pub graffiti: Graffiti,
@@ -48,8 +49,17 @@ pub struct BeaconBlockBody<T: EthSpec> {
pub voluntary_exits: VariableList<SignedVoluntaryExit, T::MaxVoluntaryExits>,
#[superstruct(only(Altair, Merge))]
pub sync_aggregate: SyncAggregate<T>,
// We flatten the execution payload so that serde can use the name of the inner type,
// either `execution_payload` for full payloads, or `execution_payload_header` for blinded
// payloads.
#[superstruct(only(Merge))]
pub execution_payload: ExecutionPayload<T>,
#[serde(flatten)]
pub execution_payload: Payload,
#[superstruct(only(Base, Altair))]
#[ssz(skip_serializing, skip_deserializing)]
#[tree_hash(skip_hashing)]
#[serde(skip)]
pub _phantom: PhantomData<Payload>,
}
impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> {
@@ -63,6 +73,198 @@ impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> {
}
}
// We can convert pre-Bellatrix block bodies without payloads into block bodies "with" payloads.
impl<E: EthSpec> From<BeaconBlockBodyBase<E, BlindedPayload<E>>>
for BeaconBlockBodyBase<E, FullPayload<E>>
{
fn from(body: BeaconBlockBodyBase<E, BlindedPayload<E>>) -> Self {
let BeaconBlockBodyBase {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
_phantom,
} = body;
BeaconBlockBodyBase {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
_phantom: PhantomData,
}
}
}
impl<E: EthSpec> From<BeaconBlockBodyAltair<E, BlindedPayload<E>>>
for BeaconBlockBodyAltair<E, FullPayload<E>>
{
fn from(body: BeaconBlockBodyAltair<E, BlindedPayload<E>>) -> Self {
let BeaconBlockBodyAltair {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
_phantom,
} = body;
BeaconBlockBodyAltair {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
_phantom: PhantomData,
}
}
}
// Likewise bodies with payloads can be transformed into bodies without.
impl<E: EthSpec> From<BeaconBlockBodyBase<E, FullPayload<E>>>
for (
BeaconBlockBodyBase<E, BlindedPayload<E>>,
Option<ExecutionPayload<E>>,
)
{
fn from(body: BeaconBlockBodyBase<E, FullPayload<E>>) -> Self {
let BeaconBlockBodyBase {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
_phantom,
} = body;
(
BeaconBlockBodyBase {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
_phantom: PhantomData,
},
None,
)
}
}
impl<E: EthSpec> From<BeaconBlockBodyAltair<E, FullPayload<E>>>
for (
BeaconBlockBodyAltair<E, BlindedPayload<E>>,
Option<ExecutionPayload<E>>,
)
{
fn from(body: BeaconBlockBodyAltair<E, FullPayload<E>>) -> Self {
let BeaconBlockBodyAltair {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
_phantom,
} = body;
(
BeaconBlockBodyAltair {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
_phantom: PhantomData,
},
None,
)
}
}
impl<E: EthSpec> From<BeaconBlockBodyMerge<E, FullPayload<E>>>
for (
BeaconBlockBodyMerge<E, BlindedPayload<E>>,
Option<ExecutionPayload<E>>,
)
{
fn from(body: BeaconBlockBodyMerge<E, FullPayload<E>>) -> Self {
let BeaconBlockBodyMerge {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayload { execution_payload },
} = body;
(
BeaconBlockBodyMerge {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayload {
execution_payload_header: From::from(&execution_payload),
},
},
Some(execution_payload),
)
}
}
impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
for (
BeaconBlockBody<E, BlindedPayload<E>>,
Option<ExecutionPayload<E>>,
)
{
fn from(body: BeaconBlockBody<E, FullPayload<E>>) -> Self {
map_beacon_block_body!(body, |inner, cons| {
let (block, payload) = inner.into();
(cons(block), payload)
})
}
}
#[cfg(test)]
mod tests {
mod base {

View File

@@ -507,7 +507,7 @@ impl ChainSpec {
* Fork choice
*/
safe_slots_to_update_justified: 8,
proposer_score_boost: None,
proposer_score_boost: Some(40),
/*
* Eth1
@@ -705,7 +705,7 @@ impl ChainSpec {
* Fork choice
*/
safe_slots_to_update_justified: 8,
proposer_score_boost: None,
proposer_score_boost: Some(40),
/*
* Eth1
@@ -1268,7 +1268,7 @@ mod yaml_tests {
EJECTION_BALANCE: 16000000000
MIN_PER_EPOCH_CHURN_LIMIT: 4
CHURN_LIMIT_QUOTIENT: 65536
PROPOSER_SCORE_BOOST: 70
PROPOSER_SCORE_BOOST: 40
DEPOSIT_CHAIN_ID: 1
DEPOSIT_NETWORK_ID: 1
DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa

View File

@@ -8,7 +8,11 @@ use tree_hash_derive::TreeHash;
use ssz_types::{FixedVector, VariableList};
pub type Transaction<T> = VariableList<u8, T>;
pub type Transaction<N> = VariableList<u8, N>;
pub type Transactions<T> = VariableList<
Transaction<<T as EthSpec>::MaxBytesPerTransaction>,
<T as EthSpec>::MaxTransactionsPerPayload,
>;
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(
@@ -38,8 +42,7 @@ pub struct ExecutionPayload<T: EthSpec> {
pub base_fee_per_gas: Uint256,
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions:
VariableList<Transaction<T::MaxBytesPerTransaction>, T::MaxTransactionsPerPayload>,
pub transactions: Transactions<T>,
}
impl<T: EthSpec> ExecutionPayload<T> {
@@ -52,9 +55,9 @@ impl<T: EthSpec> ExecutionPayload<T> {
pub fn max_execution_payload_size() -> usize {
// Fixed part
Self::empty().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (T::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction()))
// Max size of variable length `extra_data` field
+ (T::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction()))
}
}

View File

@@ -1,15 +1,18 @@
use crate::{test_utils::TestRandom, *};
use derivative::Derivative;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
use ssz_types::{FixedVector, VariableList};
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(
Default, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
Default, Debug, Clone, Serialize, Deserialize, Derivative, Encode, Decode, TreeHash, TestRandom,
)]
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
pub struct ExecutionPayloadHeader<T: EthSpec> {
pub parent_hash: ExecutionBlockHash,
pub fee_recipient: Address,
@@ -39,3 +42,24 @@ impl<T: EthSpec> ExecutionPayloadHeader<T> {
Self::default()
}
}
impl<'a, T: EthSpec> From<&'a ExecutionPayload<T>> for ExecutionPayloadHeader<T> {
fn from(payload: &'a ExecutionPayload<T>) -> Self {
ExecutionPayloadHeader {
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom.clone(),
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data.clone(),
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions_root: payload.transactions.tree_hash_root(),
}
}
}

View File

@@ -70,6 +70,7 @@ pub mod config_and_preset;
pub mod fork_context;
pub mod participation_flags;
pub mod participation_list;
pub mod payload;
pub mod preset;
pub mod slot_epoch;
pub mod subnet_id;
@@ -114,7 +115,7 @@ pub use crate::enr_fork_id::EnrForkId;
pub use crate::eth1_data::Eth1Data;
pub use crate::eth_spec::EthSpecId;
pub use crate::execution_block_hash::ExecutionBlockHash;
pub use crate::execution_payload::{ExecutionPayload, Transaction};
pub use crate::execution_payload::{ExecutionPayload, Transaction, Transactions};
pub use crate::execution_payload_header::ExecutionPayloadHeader;
pub use crate::fork::Fork;
pub use crate::fork_context::ForkContext;
@@ -126,6 +127,7 @@ pub use crate::historical_batch::HistoricalBatch;
pub use crate::indexed_attestation::IndexedAttestation;
pub use crate::participation_flags::ParticipationFlags;
pub use crate::participation_list::ParticipationList;
pub use crate::payload::{BlindedPayload, BlockType, ExecPayload, FullPayload};
pub use crate::pending_attestation::PendingAttestation;
pub use crate::preset::{AltairPreset, BasePreset, BellatrixPreset};
pub use crate::proposer_preparation_data::ProposerPreparationData;
@@ -136,7 +138,7 @@ pub use crate::shuffling_id::AttestationShufflingId;
pub use crate::signed_aggregate_and_proof::SignedAggregateAndProof;
pub use crate::signed_beacon_block::{
SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockHash,
SignedBeaconBlockMerge,
SignedBeaconBlockMerge, SignedBlindedBeaconBlock,
};
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
pub use crate::signed_contribution_and_proof::SignedContributionAndProof;

View File

@@ -0,0 +1,262 @@
use crate::{test_utils::TestRandom, *};
use derivative::Derivative;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use ssz::{Decode, DecodeError, Encode};
use std::convert::TryFrom;
use std::fmt::Debug;
use std::hash::Hash;
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
pub enum BlockType {
Blinded,
Full,
}
pub trait ExecPayload<T: EthSpec>:
Debug
+ Clone
+ Encode
+ Decode
+ TestRandom
+ TreeHash
+ Default
+ PartialEq
+ Serialize
+ DeserializeOwned
+ Hash
+ TryFrom<ExecutionPayloadHeader<T>>
+ From<ExecutionPayload<T>>
{
fn block_type() -> BlockType;
/// Convert the payload into a payload header.
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T>;
// We provide a subset of field accessors, for the fields used in `consensus`.
//
// More fields can be added here if you wish.
fn parent_hash(&self) -> ExecutionBlockHash;
fn prev_randao(&self) -> Hash256;
fn block_number(&self) -> u64;
fn timestamp(&self) -> u64;
fn block_hash(&self) -> ExecutionBlockHash;
}
impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
fn block_type() -> BlockType {
BlockType::Full
}
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
ExecutionPayloadHeader::from(&self.execution_payload)
}
fn parent_hash(&self) -> ExecutionBlockHash {
self.execution_payload.parent_hash
}
fn prev_randao(&self) -> Hash256 {
self.execution_payload.prev_randao
}
fn block_number(&self) -> u64 {
self.execution_payload.block_number
}
fn timestamp(&self) -> u64 {
self.execution_payload.timestamp
}
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload.block_hash
}
}
impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
fn block_type() -> BlockType {
BlockType::Blinded
}
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
self.execution_payload_header.clone()
}
fn parent_hash(&self) -> ExecutionBlockHash {
self.execution_payload_header.parent_hash
}
fn prev_randao(&self) -> Hash256 {
self.execution_payload_header.prev_randao
}
fn block_number(&self) -> u64 {
self.execution_payload_header.block_number
}
fn timestamp(&self) -> u64 {
self.execution_payload_header.timestamp
}
fn block_hash(&self) -> ExecutionBlockHash {
self.execution_payload_header.block_hash
}
}
#[derive(Debug, Clone, TestRandom, Serialize, Deserialize, Derivative)]
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
#[serde(bound = "T: EthSpec")]
pub struct BlindedPayload<T: EthSpec> {
pub execution_payload_header: ExecutionPayloadHeader<T>,
}
// NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default`
// implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the
// default payload in `is_merge_transition_block` to determine whether the merge has occurred.
//
// The default `BlindedPayload` is therefore the payload header that results from blinding the
// default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that
// its `transactions_root` is the hash of the empty list rather than 0x0.
impl<T: EthSpec> Default for BlindedPayload<T> {
fn default() -> Self {
Self {
execution_payload_header: ExecutionPayloadHeader::from(&ExecutionPayload::default()),
}
}
}
impl<T: EthSpec> From<ExecutionPayloadHeader<T>> for BlindedPayload<T> {
fn from(execution_payload_header: ExecutionPayloadHeader<T>) -> Self {
Self {
execution_payload_header,
}
}
}
impl<T: EthSpec> From<BlindedPayload<T>> for ExecutionPayloadHeader<T> {
fn from(blinded: BlindedPayload<T>) -> Self {
blinded.execution_payload_header
}
}
impl<T: EthSpec> From<ExecutionPayload<T>> for BlindedPayload<T> {
fn from(execution_payload: ExecutionPayload<T>) -> Self {
Self {
execution_payload_header: ExecutionPayloadHeader::from(&execution_payload),
}
}
}
impl<T: EthSpec> TreeHash for BlindedPayload<T> {
fn tree_hash_type() -> tree_hash::TreeHashType {
<ExecutionPayloadHeader<T>>::tree_hash_type()
}
fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding {
self.execution_payload_header.tree_hash_packed_encoding()
}
fn tree_hash_packing_factor() -> usize {
<ExecutionPayloadHeader<T>>::tree_hash_packing_factor()
}
fn tree_hash_root(&self) -> tree_hash::Hash256 {
self.execution_payload_header.tree_hash_root()
}
}
impl<T: EthSpec> Decode for BlindedPayload<T> {
fn is_ssz_fixed_len() -> bool {
<ExecutionPayloadHeader<T> as Decode>::is_ssz_fixed_len()
}
fn ssz_fixed_len() -> usize {
<ExecutionPayloadHeader<T> as Decode>::ssz_fixed_len()
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
execution_payload_header: ExecutionPayloadHeader::from_ssz_bytes(bytes)?,
})
}
}
impl<T: EthSpec> Encode for BlindedPayload<T> {
fn is_ssz_fixed_len() -> bool {
<ExecutionPayloadHeader<T> as Encode>::is_ssz_fixed_len()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
self.execution_payload_header.ssz_append(buf)
}
fn ssz_bytes_len(&self) -> usize {
self.execution_payload_header.ssz_bytes_len()
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, TestRandom, Derivative)]
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
#[serde(bound = "T: EthSpec")]
pub struct FullPayload<T: EthSpec> {
pub execution_payload: ExecutionPayload<T>,
}
impl<T: EthSpec> From<ExecutionPayload<T>> for FullPayload<T> {
fn from(execution_payload: ExecutionPayload<T>) -> Self {
Self { execution_payload }
}
}
impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for FullPayload<T> {
type Error = ();
fn try_from(_: ExecutionPayloadHeader<T>) -> Result<Self, Self::Error> {
Err(())
}
}
impl<T: EthSpec> TreeHash for FullPayload<T> {
fn tree_hash_type() -> tree_hash::TreeHashType {
<ExecutionPayload<T>>::tree_hash_type()
}
fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding {
self.execution_payload.tree_hash_packed_encoding()
}
fn tree_hash_packing_factor() -> usize {
<ExecutionPayload<T>>::tree_hash_packing_factor()
}
fn tree_hash_root(&self) -> tree_hash::Hash256 {
self.execution_payload.tree_hash_root()
}
}
impl<T: EthSpec> Decode for FullPayload<T> {
fn is_ssz_fixed_len() -> bool {
<ExecutionPayload<T> as Decode>::is_ssz_fixed_len()
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Ok(FullPayload {
execution_payload: Decode::from_ssz_bytes(bytes)?,
})
}
}
impl<T: EthSpec> Encode for FullPayload<T> {
fn is_ssz_fixed_len() -> bool {
<ExecutionPayload<T> as Encode>::is_ssz_fixed_len()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
self.execution_payload.ssz_append(buf)
}
fn ssz_bytes_len(&self) -> usize {
self.execution_payload.ssz_bytes_len()
}
}

View File

@@ -52,27 +52,32 @@ impl From<SignedBeaconBlockHash> for Hash256 {
),
derivative(PartialEq, Hash(bound = "E: EthSpec")),
cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary)),
serde(bound = "E: EthSpec")
)
serde(bound = "E: EthSpec, Payload: ExecPayload<E>"),
),
map_into(BeaconBlock),
map_ref_into(BeaconBlockRef),
map_ref_mut_into(BeaconBlockRefMut)
)]
#[derive(Debug, Clone, Serialize, Deserialize, Encode, TreeHash, Derivative)]
#[derivative(PartialEq, Hash(bound = "E: EthSpec"))]
#[serde(untagged)]
#[serde(bound = "E: EthSpec")]
#[serde(bound = "E: EthSpec, Payload: ExecPayload<E>")]
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[tree_hash(enum_behaviour = "transparent")]
#[ssz(enum_behaviour = "transparent")]
pub struct SignedBeaconBlock<E: EthSpec> {
pub struct SignedBeaconBlock<E: EthSpec, Payload: ExecPayload<E> = FullPayload<E>> {
#[superstruct(only(Base), partial_getter(rename = "message_base"))]
pub message: BeaconBlockBase<E>,
pub message: BeaconBlockBase<E, Payload>,
#[superstruct(only(Altair), partial_getter(rename = "message_altair"))]
pub message: BeaconBlockAltair<E>,
pub message: BeaconBlockAltair<E, Payload>,
#[superstruct(only(Merge), partial_getter(rename = "message_merge"))]
pub message: BeaconBlockMerge<E>,
pub message: BeaconBlockMerge<E, Payload>,
pub signature: Signature,
}
impl<E: EthSpec> SignedBeaconBlock<E> {
pub type SignedBlindedBeaconBlock<E> = SignedBeaconBlock<E, BlindedPayload<E>>;
impl<E: EthSpec, Payload: ExecPayload<E>> SignedBeaconBlock<E, Payload> {
/// Returns the name of the fork pertaining to `self`.
///
/// Will return an `Err` if `self` has been instantiated to a variant conflicting with the fork
@@ -94,7 +99,7 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
/// SSZ decode with custom decode function.
pub fn from_ssz_bytes_with(
bytes: &[u8],
block_decoder: impl FnOnce(&[u8]) -> Result<BeaconBlock<E>, ssz::DecodeError>,
block_decoder: impl FnOnce(&[u8]) -> Result<BeaconBlock<E, Payload>, ssz::DecodeError>,
) -> Result<Self, ssz::DecodeError> {
// We need the customer decoder for `BeaconBlock`, which doesn't compose with the other
// SSZ utils, so we duplicate some parts of `ssz_derive` here.
@@ -113,7 +118,7 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
}
/// Create a new `SignedBeaconBlock` from a `BeaconBlock` and `Signature`.
pub fn from_block(block: BeaconBlock<E>, signature: Signature) -> Self {
pub fn from_block(block: BeaconBlock<E, Payload>, signature: Signature) -> Self {
match block {
BeaconBlock::Base(message) => {
SignedBeaconBlock::Base(SignedBeaconBlockBase { message, signature })
@@ -131,32 +136,28 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
///
/// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because
/// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_.
pub fn deconstruct(self) -> (BeaconBlock<E>, Signature) {
match self {
SignedBeaconBlock::Base(block) => (BeaconBlock::Base(block.message), block.signature),
SignedBeaconBlock::Altair(block) => {
(BeaconBlock::Altair(block.message), block.signature)
}
SignedBeaconBlock::Merge(block) => (BeaconBlock::Merge(block.message), block.signature),
}
pub fn deconstruct(self) -> (BeaconBlock<E, Payload>, Signature) {
map_signed_beacon_block_into_beacon_block!(self, |block, beacon_block_cons| {
(beacon_block_cons(block.message), block.signature)
})
}
/// Accessor for the block's `message` field as a ref.
pub fn message(&self) -> BeaconBlockRef<'_, E> {
match self {
SignedBeaconBlock::Base(inner) => BeaconBlockRef::Base(&inner.message),
SignedBeaconBlock::Altair(inner) => BeaconBlockRef::Altair(&inner.message),
SignedBeaconBlock::Merge(inner) => BeaconBlockRef::Merge(&inner.message),
}
pub fn message<'a>(&'a self) -> BeaconBlockRef<'a, E, Payload> {
map_signed_beacon_block_ref_into_beacon_block_ref!(
&'a _,
self.to_ref(),
|inner, cons| cons(&inner.message)
)
}
/// Accessor for the block's `message` as a mutable reference (for testing only).
pub fn message_mut(&mut self) -> BeaconBlockRefMut<'_, E> {
match self {
SignedBeaconBlock::Base(inner) => BeaconBlockRefMut::Base(&mut inner.message),
SignedBeaconBlock::Altair(inner) => BeaconBlockRefMut::Altair(&mut inner.message),
SignedBeaconBlock::Merge(inner) => BeaconBlockRefMut::Merge(&mut inner.message),
}
pub fn message_mut<'a>(&'a mut self) -> BeaconBlockRefMut<'a, E, Payload> {
map_signed_beacon_block_ref_mut_into_beacon_block_ref_mut!(
&'a _,
self.to_mut(),
|inner, cons| cons(&mut inner.message)
)
}
/// Verify `self.signature`.
@@ -225,3 +226,165 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
self.message().tree_hash_root()
}
}
// We can convert pre-Bellatrix blocks without payloads into blocks with payloads.
impl<E: EthSpec> From<SignedBeaconBlockBase<E, BlindedPayload<E>>>
for SignedBeaconBlockBase<E, FullPayload<E>>
{
fn from(signed_block: SignedBeaconBlockBase<E, BlindedPayload<E>>) -> Self {
let SignedBeaconBlockBase { message, signature } = signed_block;
SignedBeaconBlockBase {
message: message.into(),
signature,
}
}
}
impl<E: EthSpec> From<SignedBeaconBlockAltair<E, BlindedPayload<E>>>
for SignedBeaconBlockAltair<E, FullPayload<E>>
{
fn from(signed_block: SignedBeaconBlockAltair<E, BlindedPayload<E>>) -> Self {
let SignedBeaconBlockAltair { message, signature } = signed_block;
SignedBeaconBlockAltair {
message: message.into(),
signature,
}
}
}
// Post-Bellatrix blocks can be "unblinded" by adding the full payload.
// NOTE: It might be nice to come up with a `superstruct` pattern to abstract over this before
// the first fork after Bellatrix.
impl<E: EthSpec> SignedBeaconBlockMerge<E, BlindedPayload<E>> {
pub fn into_full_block(
self,
execution_payload: ExecutionPayload<E>,
) -> SignedBeaconBlockMerge<E, FullPayload<E>> {
let SignedBeaconBlockMerge {
message:
BeaconBlockMerge {
slot,
proposer_index,
parent_root,
state_root,
body:
BeaconBlockBodyMerge {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayload { .. },
},
},
signature,
} = self;
SignedBeaconBlockMerge {
message: BeaconBlockMerge {
slot,
proposer_index,
parent_root,
state_root,
body: BeaconBlockBodyMerge {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayload { execution_payload },
},
},
signature,
}
}
}
impl<E: EthSpec> SignedBeaconBlock<E, BlindedPayload<E>> {
pub fn try_into_full_block(
self,
execution_payload: Option<ExecutionPayload<E>>,
) -> Option<SignedBeaconBlock<E, FullPayload<E>>> {
let full_block = match self {
SignedBeaconBlock::Base(block) => SignedBeaconBlock::Base(block.into()),
SignedBeaconBlock::Altair(block) => SignedBeaconBlock::Altair(block.into()),
SignedBeaconBlock::Merge(block) => {
SignedBeaconBlock::Merge(block.into_full_block(execution_payload?))
}
};
Some(full_block)
}
}
// We can blind blocks with payloads by converting the payload into a header.
//
// We can optionally keep the header, or discard it.
impl<E: EthSpec> From<SignedBeaconBlock<E>>
for (SignedBlindedBeaconBlock<E>, Option<ExecutionPayload<E>>)
{
fn from(signed_block: SignedBeaconBlock<E>) -> Self {
let (block, signature) = signed_block.deconstruct();
let (blinded_block, payload) = block.into();
(
SignedBeaconBlock::from_block(blinded_block, signature),
payload,
)
}
}
impl<E: EthSpec> From<SignedBeaconBlock<E>> for SignedBlindedBeaconBlock<E> {
fn from(signed_block: SignedBeaconBlock<E>) -> Self {
let (blinded_block, _) = signed_block.into();
blinded_block
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn add_remove_payload_roundtrip() {
type E = MainnetEthSpec;
let spec = &E::default_spec();
let sig = Signature::empty();
let blocks = vec![
SignedBeaconBlock::<E>::from_block(
BeaconBlock::Base(BeaconBlockBase::empty(spec)),
sig.clone(),
),
SignedBeaconBlock::from_block(
BeaconBlock::Altair(BeaconBlockAltair::empty(spec)),
sig.clone(),
),
SignedBeaconBlock::from_block(BeaconBlock::Merge(BeaconBlockMerge::empty(spec)), sig),
];
for block in blocks {
let (blinded_block, payload): (SignedBlindedBeaconBlock<E>, _) = block.clone().into();
assert_eq!(blinded_block.tree_hash_root(), block.tree_hash_root());
if let Some(payload) = &payload {
assert_eq!(
payload.tree_hash_root(),
block
.message()
.execution_payload()
.unwrap()
.tree_hash_root()
);
}
let reconstructed = blinded_block.try_into_full_block(payload).unwrap();
assert_eq!(reconstructed, block);
}
}
}

View File

@@ -3,6 +3,7 @@ use rand::RngCore;
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use ssz_types::typenum::Unsigned;
use std::marker::PhantomData;
use std::sync::Arc;
mod address;
@@ -25,6 +26,12 @@ pub trait TestRandom {
fn random_for_test(rng: &mut impl RngCore) -> Self;
}
impl<T> TestRandom for PhantomData<T> {
fn random_for_test(_rng: &mut impl RngCore) -> Self {
PhantomData::default()
}
}
impl TestRandom for bool {
fn random_for_test(rng: &mut impl RngCore) -> Self {
(rng.next_u32() % 2) == 1