merge with upstream

This commit is contained in:
realbigsean
2023-02-15 14:20:12 -05:00
63 changed files with 2499 additions and 454 deletions
+9 -8
View File
@@ -722,7 +722,7 @@ where
op: &InvalidationOperation,
) -> Result<(), Error<T::Error>> {
self.proto_array
.process_execution_payload_invalidation(op)
.process_execution_payload_invalidation::<E>(op)
.map_err(Error::FailedToProcessInvalidExecutionPayload)
}
@@ -1288,7 +1288,7 @@ where
if store.best_justified_checkpoint().epoch > store.justified_checkpoint().epoch {
let store = &self.fc_store;
if self.is_descendant_of_finalized(store.best_justified_checkpoint().root) {
if self.is_finalized_checkpoint_or_descendant(store.best_justified_checkpoint().root) {
let store = &mut self.fc_store;
store
.set_justified_checkpoint(*store.best_justified_checkpoint())
@@ -1329,12 +1329,13 @@ where
/// Returns `true` if the block is known **and** a descendant of the finalized root.
pub fn contains_block(&self, block_root: &Hash256) -> bool {
self.proto_array.contains_block(block_root) && self.is_descendant_of_finalized(*block_root)
self.proto_array.contains_block(block_root)
&& self.is_finalized_checkpoint_or_descendant(*block_root)
}
/// Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
pub fn get_block(&self, block_root: &Hash256) -> Option<ProtoBlock> {
if self.is_descendant_of_finalized(*block_root) {
if self.is_finalized_checkpoint_or_descendant(*block_root) {
self.proto_array.get_block(block_root)
} else {
None
@@ -1343,7 +1344,7 @@ where
/// Returns an `ExecutionStatus` if the block is known **and** a descendant of the finalized root.
pub fn get_block_execution_status(&self, block_root: &Hash256) -> Option<ExecutionStatus> {
if self.is_descendant_of_finalized(*block_root) {
if self.is_finalized_checkpoint_or_descendant(*block_root) {
self.proto_array.get_block_execution_status(block_root)
} else {
None
@@ -1378,10 +1379,10 @@ where
})
}
/// Return `true` if `block_root` is equal to the finalized root, or a known descendant of it.
pub fn is_descendant_of_finalized(&self, block_root: Hash256) -> bool {
/// Return `true` if `block_root` is equal to the finalized checkpoint, or a known descendant of it.
pub fn is_finalized_checkpoint_or_descendant(&self, block_root: Hash256) -> bool {
self.proto_array
.is_descendant(self.fc_store.finalized_checkpoint().root, block_root)
.is_finalized_checkpoint_or_descendant::<E>(block_root)
}
/// Returns `Ok(true)` if `block_root` has been imported optimistically or deemed invalid.
@@ -273,7 +273,7 @@ impl ForkChoiceTestDefinition {
}
};
fork_choice
.process_execution_payload_invalidation(&op)
.process_execution_payload_invalidation::<MainnetEthSpec>(&op)
.unwrap()
}
Operation::AssertWeight { block_root, weight } => assert_eq!(
+72 -2
View File
@@ -451,7 +451,7 @@ impl ProtoArray {
/// Invalidate zero or more blocks, as specified by the `InvalidationOperation`.
///
/// See the documentation of `InvalidationOperation` for usage.
pub fn propagate_execution_payload_invalidation(
pub fn propagate_execution_payload_invalidation<E: EthSpec>(
&mut self,
op: &InvalidationOperation,
) -> Result<(), Error> {
@@ -482,7 +482,7 @@ impl ProtoArray {
let latest_valid_ancestor_is_descendant =
latest_valid_ancestor_root.map_or(false, |ancestor_root| {
self.is_descendant(ancestor_root, head_block_root)
&& self.is_descendant(self.finalized_checkpoint.root, ancestor_root)
&& self.is_finalized_checkpoint_or_descendant::<E>(ancestor_root)
});
// Collect all *ancestors* which were declared invalid since they reside between the
@@ -977,6 +977,12 @@ impl ProtoArray {
/// ## Notes
///
/// Still returns `true` if `ancestor_root` is known and `ancestor_root == descendant_root`.
///
/// ## Warning
///
/// Do not use this function to check if a block is a descendant of the
/// finalized checkpoint. Use `Self::is_finalized_checkpoint_or_descendant`
/// instead.
pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool {
self.indices
.get(&ancestor_root)
@@ -990,6 +996,70 @@ impl ProtoArray {
.unwrap_or(false)
}
/// Returns `true` if `root` is equal to or a descendant of
/// `self.finalized_checkpoint`.
///
/// Notably, this function is checking ancestory of the finalized
/// *checkpoint* not the finalized *block*.
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(&self, root: Hash256) -> bool {
let finalized_root = self.finalized_checkpoint.root;
let finalized_slot = self
.finalized_checkpoint
.epoch
.start_slot(E::slots_per_epoch());
let mut node = if let Some(node) = self
.indices
.get(&root)
.and_then(|index| self.nodes.get(*index))
{
node
} else {
// An unknown root is not a finalized descendant. This line can only
// be reached if the user supplies a root that is not known to fork
// choice.
return false;
};
// The finalized and justified checkpoints represent a list of known
// ancestors of `node` that are likely to coincide with the store's
// finalized checkpoint.
//
// Run this check once, outside of the loop rather than inside the loop.
// If the conditions don't match for this node then they're unlikely to
// start matching for its ancestors.
for checkpoint in &[
node.finalized_checkpoint,
node.justified_checkpoint,
node.unrealized_finalized_checkpoint,
node.unrealized_justified_checkpoint,
] {
if checkpoint.map_or(false, |cp| cp == self.finalized_checkpoint) {
return true;
}
}
loop {
// If `node` is less than or equal to the finalized slot then `node`
// must be the finalized block.
if node.slot <= finalized_slot {
return node.root == finalized_root;
}
// Since `node` is from a higher slot that the finalized checkpoint,
// replace `node` with the parent of `node`.
if let Some(parent) = node.parent.and_then(|index| self.nodes.get(index)) {
node = parent
} else {
// If `node` is not the finalized block and its parent does not
// exist in fork choice, then the parent must have been pruned.
// Proto-array only prunes blocks prior to the finalized block,
// so this means the parent conflicts with finality.
return false;
};
}
}
/// Returns the first *beacon block root* which contains an execution payload with the given
/// `block_hash`, if any.
pub fn execution_block_hash_to_beacon_block_root(
@@ -358,12 +358,12 @@ impl ProtoArrayForkChoice {
}
/// See `ProtoArray::propagate_execution_payload_invalidation` for documentation.
pub fn process_execution_payload_invalidation(
pub fn process_execution_payload_invalidation<E: EthSpec>(
&mut self,
op: &InvalidationOperation,
) -> Result<(), String> {
self.proto_array
.propagate_execution_payload_invalidation(op)
.propagate_execution_payload_invalidation::<E>(op)
.map_err(|e| format!("Failed to process invalid payload: {:?}", e))
}
@@ -748,6 +748,15 @@ impl ProtoArrayForkChoice {
.is_descendant(ancestor_root, descendant_root)
}
/// See `ProtoArray` documentation.
pub fn is_finalized_checkpoint_or_descendant<E: EthSpec>(
&self,
descendant_root: Hash256,
) -> bool {
self.proto_array
.is_finalized_checkpoint_or_descendant::<E>(descendant_root)
}
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
if validator_index < self.votes.0.len() {
let vote = &self.votes.0[validator_index];
@@ -928,6 +937,10 @@ mod test_compute_deltas {
epoch: genesis_epoch,
root: finalized_root,
};
let junk_checkpoint = Checkpoint {
epoch: Epoch::new(42),
root: Hash256::repeat_byte(42),
};
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
genesis_slot,
@@ -973,8 +986,10 @@ mod test_compute_deltas {
target_root: finalized_root,
current_epoch_shuffling_id: junk_shuffling_id.clone(),
next_epoch_shuffling_id: junk_shuffling_id,
justified_checkpoint: genesis_checkpoint,
finalized_checkpoint: genesis_checkpoint,
// Use the junk checkpoint for the next to values to prevent
// the loop-shortcutting mechanism from triggering.
justified_checkpoint: junk_checkpoint,
finalized_checkpoint: junk_checkpoint,
execution_status,
unrealized_justified_checkpoint: None,
unrealized_finalized_checkpoint: None,
@@ -993,6 +1008,11 @@ mod test_compute_deltas {
assert!(!fc.is_descendant(finalized_root, not_finalized_desc));
assert!(!fc.is_descendant(finalized_root, unknown));
assert!(fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_root));
assert!(fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_desc));
assert!(!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(not_finalized_desc));
assert!(!fc.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(unknown));
assert!(!fc.is_descendant(finalized_desc, not_finalized_desc));
assert!(fc.is_descendant(finalized_desc, finalized_desc));
assert!(!fc.is_descendant(finalized_desc, finalized_root));
@@ -1004,6 +1024,171 @@ mod test_compute_deltas {
assert!(!fc.is_descendant(not_finalized_desc, unknown));
}
/// This test covers an interesting case where a block can be a descendant
/// of the finalized *block*, but not a descenant of the finalized
/// *checkpoint*.
///
/// ## Example
///
/// Consider this block tree which has three blocks (`A`, `B` and `C`):
///
/// ```ignore
/// [A] <--- [-] <--- [B]
/// |
/// |--[C]
/// ```
///
/// - `A` (slot 31) is the common descendant.
/// - `B` (slot 33) descends from `A`, but there is a single skip slot
/// between it and `A`.
/// - `C` (slot 32) descends from `A` and conflicts with `B`.
///
/// Imagine that the `B` chain is finalized at epoch 1. This means that the
/// finalized checkpoint points to the skipped slot at 32. The root of the
/// finalized checkpoint is `A`.
///
/// In this scenario, the block `C` has the finalized root (`A`) as an
/// ancestor whilst simultaneously conflicting with the finalized
/// checkpoint.
///
/// This means that to ensure a block does not conflict with finality we
/// must check to ensure that it's an ancestor of the finalized
/// *checkpoint*, not just the finalized *block*.
#[test]
fn finalized_descendant_edge_case() {
let get_block_root = Hash256::from_low_u64_be;
let genesis_slot = Slot::new(0);
let junk_state_root = Hash256::zero();
let junk_shuffling_id =
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
let execution_status = ExecutionStatus::irrelevant();
let genesis_checkpoint = Checkpoint {
epoch: Epoch::new(0),
root: get_block_root(0),
};
let mut fc = ProtoArrayForkChoice::new::<MainnetEthSpec>(
genesis_slot,
junk_state_root,
genesis_checkpoint,
genesis_checkpoint,
junk_shuffling_id.clone(),
junk_shuffling_id.clone(),
execution_status,
CountUnrealizedFull::default(),
)
.unwrap();
struct TestBlock {
slot: u64,
root: u64,
parent_root: u64,
}
let insert_block = |fc: &mut ProtoArrayForkChoice, block: TestBlock| {
fc.proto_array
.on_block::<MainnetEthSpec>(
Block {
slot: Slot::from(block.slot),
root: get_block_root(block.root),
parent_root: Some(get_block_root(block.parent_root)),
state_root: Hash256::zero(),
target_root: Hash256::zero(),
current_epoch_shuffling_id: junk_shuffling_id.clone(),
next_epoch_shuffling_id: junk_shuffling_id.clone(),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(0),
root: get_block_root(0),
},
finalized_checkpoint: genesis_checkpoint,
execution_status,
unrealized_justified_checkpoint: Some(genesis_checkpoint),
unrealized_finalized_checkpoint: Some(genesis_checkpoint),
},
Slot::from(block.slot),
)
.unwrap();
};
/*
* Start of interesting part of tests.
*/
// Produce the 0th epoch of blocks. They should all form a chain from
// the genesis block.
for i in 1..MainnetEthSpec::slots_per_epoch() {
insert_block(
&mut fc,
TestBlock {
slot: i,
root: i,
parent_root: i - 1,
},
)
}
let last_slot_of_epoch_0 = MainnetEthSpec::slots_per_epoch() - 1;
// Produce a block that descends from the last block of epoch -.
//
// This block will be non-canonical.
let non_canonical_slot = last_slot_of_epoch_0 + 1;
insert_block(
&mut fc,
TestBlock {
slot: non_canonical_slot,
root: non_canonical_slot,
parent_root: non_canonical_slot - 1,
},
);
// Produce a block that descends from the last block of the 0th epoch,
// that skips the 1st slot of the 1st epoch.
//
// This block will be canonical.
let canonical_slot = last_slot_of_epoch_0 + 2;
insert_block(
&mut fc,
TestBlock {
slot: canonical_slot,
root: canonical_slot,
parent_root: non_canonical_slot - 1,
},
);
let finalized_root = get_block_root(last_slot_of_epoch_0);
// Set the finalized checkpoint to finalize the first slot of epoch 1 on
// the canonical chain.
fc.proto_array.finalized_checkpoint = Checkpoint {
root: finalized_root,
epoch: Epoch::new(1),
};
assert!(
fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(finalized_root),
"the finalized checkpoint is the finalized checkpoint"
);
assert!(
fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(get_block_root(
canonical_slot
)),
"the canonical block is a descendant of the finalized checkpoint"
);
assert!(
!fc.proto_array
.is_finalized_checkpoint_or_descendant::<MainnetEthSpec>(get_block_root(
non_canonical_slot
)),
"although the non-canonical block is a descendant of the finalized block, \
it's not a descendant of the finalized checkpoint"
);
}
#[test]
fn zero_hash() {
let validator_count: usize = 16;
+1 -1
View File
@@ -12,4 +12,4 @@ pub mod u64_hex_be;
pub mod u8_hex;
pub use fixed_bytes_hex::{bytes_4_hex, bytes_8_hex};
pub use quoted_int::{quoted_u256, quoted_u32, quoted_u64, quoted_u8};
pub use quoted_int::{quoted_i64, quoted_u256, quoted_u32, quoted_u64, quoted_u8};
+32 -4
View File
@@ -11,7 +11,7 @@ use std::convert::TryFrom;
use std::marker::PhantomData;
macro_rules! define_mod {
($int: ty, $visit_fn: ident) => {
($int: ty) => {
/// Serde support for deserializing quoted integers.
///
/// Configurable so that quotes are either required or optional.
@@ -140,19 +140,25 @@ macro_rules! define_mod {
pub mod quoted_u8 {
use super::*;
define_mod!(u8, visit_u8);
define_mod!(u8);
}
pub mod quoted_u32 {
use super::*;
define_mod!(u32, visit_u32);
define_mod!(u32);
}
pub mod quoted_u64 {
use super::*;
define_mod!(u64, visit_u64);
define_mod!(u64);
}
pub mod quoted_i64 {
use super::*;
define_mod!(i64);
}
pub mod quoted_u256 {
@@ -216,4 +222,26 @@ mod test {
fn u256_without_quotes() {
serde_json::from_str::<WrappedU256>("1").unwrap_err();
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
struct WrappedI64(#[serde(with = "quoted_i64")] i64);
#[test]
fn negative_i64_with_quotes() {
assert_eq!(
serde_json::from_str::<WrappedI64>("\"-200\"").unwrap().0,
-200
);
assert_eq!(
serde_json::to_string(&WrappedI64(-12_500)).unwrap(),
"\"-12500\""
);
}
// It would be OK if this worked, but we don't need it to (i64s should always be quoted).
#[test]
fn negative_i64_without_quotes() {
serde_json::from_str::<WrappedI64>("-200").unwrap_err();
}
}
@@ -348,8 +348,7 @@ where
&mut self,
block: &'a SignedBeaconBlock<T, Payload>,
) -> Result<()> {
// FIXME(capella): to improve performance we might want to decompress the withdrawal pubkeys
// in parallel.
// To improve performance we might want to decompress the withdrawal pubkeys in parallel.
if let Ok(bls_to_execution_changes) = block.message().body().bls_to_execution_changes() {
for bls_to_execution_change in bls_to_execution_changes {
self.sets.push(bls_execution_change_signature_set(
@@ -37,10 +37,9 @@ pub fn verify_bls_to_execution_change<T: EthSpec>(
Invalid::NonBlsWithdrawalCredentials
);
// Re-hashing the pubkey isn't necessary during block replay, so we may want to skip that in
// future.
let pubkey_hash = hash(address_change.from_bls_pubkey.as_serialized());
// FIXME: Should this check be put inside the verify_signatures.is_true() condition?
// I believe that's used for fuzzing so this is a Mehdi question..
verify!(
validator.withdrawal_credentials.as_bytes().get(1..) == pubkey_hash.get(1..),
Invalid::WithdrawalCredentialsMismatch
+18
View File
@@ -685,6 +685,24 @@ impl<E: EthSpec> From<BeaconBlock<E, FullPayload<E>>>
}
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for BeaconBlock<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"BeaconBlock failed to deserialize: {:?}",
e
)))?
))
}
}
#[cfg(test)]
mod tests {
use super::*;
+6 -6
View File
@@ -278,7 +278,7 @@ impl<E: EthSpec> From<BeaconBlockBodyMerge<E, FullPayload<E>>>
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayloadMerge {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: From::from(&execution_payload),
},
},
Some(execution_payload),
@@ -319,7 +319,7 @@ impl<E: EthSpec> From<BeaconBlockBodyCapella<E, FullPayload<E>>>
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayloadCapella {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: From::from(&execution_payload),
},
bls_to_execution_changes,
},
@@ -362,7 +362,7 @@ impl<E: EthSpec> From<BeaconBlockBodyEip4844<E, FullPayload<E>>>
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayloadEip4844 {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: From::from(&execution_payload),
},
bls_to_execution_changes,
blob_kzg_commitments,
@@ -413,7 +413,7 @@ impl<E: EthSpec> BeaconBlockBodyMerge<E, FullPayload<E>> {
voluntary_exits: voluntary_exits.clone(),
sync_aggregate: sync_aggregate.clone(),
execution_payload: BlindedPayloadMerge {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: execution_payload.into(),
},
}
}
@@ -446,7 +446,7 @@ impl<E: EthSpec> BeaconBlockBodyCapella<E, FullPayload<E>> {
voluntary_exits: voluntary_exits.clone(),
sync_aggregate: sync_aggregate.clone(),
execution_payload: BlindedPayloadCapella {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: execution_payload.into(),
},
bls_to_execution_changes: bls_to_execution_changes.clone(),
}
@@ -481,7 +481,7 @@ impl<E: EthSpec> BeaconBlockBodyEip4844<E, FullPayload<E>> {
voluntary_exits: voluntary_exits.clone(),
sync_aggregate: sync_aggregate.clone(),
execution_payload: BlindedPayloadEip4844 {
execution_payload_header: From::from(execution_payload.clone()),
execution_payload_header: execution_payload.into(),
},
bls_to_execution_changes: bls_to_execution_changes.clone(),
blob_kzg_commitments: blob_kzg_commitments.clone(),
+18
View File
@@ -301,8 +301,10 @@ where
// Capella
#[superstruct(only(Capella, Eip4844), partial_getter(copy))]
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub next_withdrawal_index: u64,
#[superstruct(only(Capella, Eip4844), partial_getter(copy))]
#[serde(with = "eth2_serde_utils::quoted_u64")]
pub next_withdrawal_validator_index: u64,
// Deep history valid from Capella onwards.
#[superstruct(only(Capella, Eip4844))]
@@ -1853,3 +1855,19 @@ impl<T: EthSpec> CompareFields for BeaconState<T> {
}
}
}
impl<T: EthSpec> ForkVersionDeserialize for BeaconState<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"BeaconState failed to deserialize: {:?}",
e
)))?
))
}
}
+56 -2
View File
@@ -1,6 +1,6 @@
use crate::{
AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, SignedRoot,
Uint256,
AbstractExecPayload, ChainSpec, EthSpec, ExecPayload, ExecutionPayloadHeader, ForkName,
ForkVersionDeserialize, SignedRoot, Uint256,
};
use bls::PublicKeyBytes;
use bls::Signature;
@@ -34,6 +34,60 @@ pub struct SignedBuilderBid<E: EthSpec, Payload: AbstractExecPayload<E>> {
pub signature: Signature,
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for BuilderBid<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |_| {
serde::de::Error::custom(
"BuilderBid failed to deserialize: unable to convert payload header to payload",
)
};
#[derive(Deserialize)]
struct Helper {
header: serde_json::Value,
#[serde(with = "eth2_serde_utils::quoted_u256")]
value: Uint256,
pubkey: PublicKeyBytes,
}
let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
let payload_header =
ExecutionPayloadHeader::deserialize_by_fork::<'de, D>(helper.header, fork_name)?;
Ok(Self {
header: Payload::try_from(payload_header).map_err(convert_err)?,
value: helper.value,
pubkey: helper.pubkey,
_phantom_data: Default::default(),
})
}
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> ForkVersionDeserialize
for SignedBuilderBid<T, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Helper {
pub message: serde_json::Value,
pub signature: Signature,
}
let helper: Helper = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(Self {
message: BuilderBid::deserialize_by_fork::<'de, D>(helper.message, fork_name)?,
signature: helper.signature,
})
}
}
struct BlindedPayloadAsHeader<E>(PhantomData<E>);
impl<E: EthSpec, Payload: ExecPayload<E>> SerializeAs<Payload> for BlindedPayloadAsHeader<E> {
+26 -1
View File
@@ -35,7 +35,9 @@ pub type Withdrawals<T> = VariableList<Withdrawal, <T as EthSpec>::MaxWithdrawal
arbitrary(bound = "T: EthSpec")
),
cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant")
partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"),
map_into(FullPayload, BlindedPayload),
map_ref_into(ExecutionPayloadHeader)
)]
#[derive(
Debug, Clone, Serialize, Encode, Deserialize, TreeHash, Derivative, arbitrary::Arbitrary,
@@ -146,3 +148,26 @@ impl<T: EthSpec> ExecutionPayload<T> {
+ (T::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
}
impl<T: EthSpec> ForkVersionDeserialize for ExecutionPayload<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |e| {
serde::de::Error::custom(format!("ExecutionPayload failed to deserialize: {:?}", e))
};
Ok(match fork_name {
ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Eip4844 => Self::Eip4844(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayload failed to deserialize: unsupported fork '{}'",
fork_name
)));
}
})
}
}
+57 -28
View File
@@ -159,40 +159,40 @@ impl<T: EthSpec> ExecutionPayloadHeaderCapella<T> {
}
}
impl<T: EthSpec> From<ExecutionPayloadMerge<T>> for ExecutionPayloadHeaderMerge<T> {
fn from(payload: ExecutionPayloadMerge<T>) -> Self {
impl<'a, T: EthSpec> From<&'a ExecutionPayloadMerge<T>> for ExecutionPayloadHeaderMerge<T> {
fn from(payload: &'a ExecutionPayloadMerge<T>) -> Self {
Self {
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,
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,
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(),
}
}
}
impl<T: EthSpec> From<ExecutionPayloadCapella<T>> for ExecutionPayloadHeaderCapella<T> {
fn from(payload: ExecutionPayloadCapella<T>) -> Self {
impl<'a, T: EthSpec> From<&'a ExecutionPayloadCapella<T>> for ExecutionPayloadHeaderCapella<T> {
fn from(payload: &'a ExecutionPayloadCapella<T>) -> Self {
Self {
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,
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,
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(),
@@ -200,20 +200,21 @@ impl<T: EthSpec> From<ExecutionPayloadCapella<T>> for ExecutionPayloadHeaderCape
}
}
}
impl<T: EthSpec> From<ExecutionPayloadEip4844<T>> for ExecutionPayloadHeaderEip4844<T> {
fn from(payload: ExecutionPayloadEip4844<T>) -> Self {
impl<'a, T: EthSpec> From<&'a ExecutionPayloadEip4844<T>> for ExecutionPayloadHeaderEip4844<T> {
fn from(payload: &'a ExecutionPayloadEip4844<T>) -> Self {
Self {
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,
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,
extra_data: payload.extra_data.clone(),
base_fee_per_gas: payload.base_fee_per_gas,
excess_data_gas: payload.excess_data_gas,
block_hash: payload.block_hash,
@@ -223,31 +224,33 @@ impl<T: EthSpec> From<ExecutionPayloadEip4844<T>> for ExecutionPayloadHeaderEip4
}
}
impl<T: EthSpec> From<ExecutionPayloadMerge<T>> for ExecutionPayloadHeader<T> {
fn from(payload: ExecutionPayloadMerge<T>) -> Self {
Self::Merge(ExecutionPayloadHeaderMerge::from(payload))
// These impls are required to work around an inelegance in `to_execution_payload_header`.
// They only clone headers so they should be relatively cheap.
impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderMerge<T> {
fn from(payload: &'a Self) -> Self {
payload.clone()
}
}
impl<T: EthSpec> From<ExecutionPayloadCapella<T>> for ExecutionPayloadHeader<T> {
fn from(payload: ExecutionPayloadCapella<T>) -> Self {
Self::Capella(ExecutionPayloadHeaderCapella::from(payload))
impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderCapella<T> {
fn from(payload: &'a Self) -> Self {
payload.clone()
}
}
impl<T: EthSpec> From<ExecutionPayloadEip4844<T>> for ExecutionPayloadHeader<T> {
fn from(payload: ExecutionPayloadEip4844<T>) -> Self {
Self::Eip4844(ExecutionPayloadHeaderEip4844::from(payload))
impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderEip4844<T> {
fn from(payload: &'a Self) -> Self {
payload.clone()
}
}
impl<T: EthSpec> From<ExecutionPayload<T>> for ExecutionPayloadHeader<T> {
fn from(payload: ExecutionPayload<T>) -> Self {
match payload {
ExecutionPayload::Merge(payload) => Self::from(payload),
ExecutionPayload::Capella(payload) => Self::from(payload),
ExecutionPayload::Eip4844(payload) => Self::from(payload),
}
impl<'a, T: EthSpec> From<ExecutionPayloadRef<'a, T>> for ExecutionPayloadHeader<T> {
fn from(payload: ExecutionPayloadRef<'a, T>) -> Self {
map_execution_payload_ref_into_execution_payload_header!(
&'a _,
payload,
|inner, cons| cons(inner.into())
)
}
}
@@ -282,3 +285,29 @@ impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for ExecutionPayloadHeaderEi
}
}
}
impl<T: EthSpec> ForkVersionDeserialize for ExecutionPayloadHeader<T> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
let convert_err = |e| {
serde::de::Error::custom(format!(
"ExecutionPayloadHeader failed to deserialize: {:?}",
e
))
};
Ok(match fork_name {
ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Eip4844 => Self::Eip4844(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'",
fork_name
)));
}
})
}
}
@@ -0,0 +1,138 @@
use crate::ForkName;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::value::Value;
use std::sync::Arc;
// Deserialize is only implemented for types that implement ForkVersionDeserialize
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct ExecutionOptimisticForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<ForkName>,
pub execution_optimistic: Option<bool>,
pub data: T,
}
impl<'de, F> serde::Deserialize<'de> for ExecutionOptimisticForkVersionedResponse<F>
where
F: ForkVersionDeserialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
version: Option<ForkName>,
execution_optimistic: Option<bool>,
data: serde_json::Value,
}
let helper = Helper::deserialize(deserializer)?;
let data = match helper.version {
Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?,
None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?,
};
Ok(ExecutionOptimisticForkVersionedResponse {
version: helper.version,
execution_optimistic: helper.execution_optimistic,
data,
})
}
}
pub trait ForkVersionDeserialize: Sized + DeserializeOwned {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error>;
}
// Deserialize is only implemented for types that implement ForkVersionDeserialize
#[derive(Debug, PartialEq, Clone, Serialize)]
pub struct ForkVersionedResponse<T> {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<ForkName>,
pub data: T,
}
impl<'de, F> serde::Deserialize<'de> for ForkVersionedResponse<F>
where
F: ForkVersionDeserialize,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Helper {
version: Option<ForkName>,
data: serde_json::Value,
}
let helper = Helper::deserialize(deserializer)?;
let data = match helper.version {
Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?,
None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?,
};
Ok(ForkVersionedResponse {
version: helper.version,
data,
})
}
}
impl<F: ForkVersionDeserialize> ForkVersionDeserialize for Arc<F> {
fn deserialize_by_fork<'de, D: Deserializer<'de>>(
value: Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(Arc::new(F::deserialize_by_fork::<'de, D>(
value, fork_name,
)?))
}
}
#[cfg(test)]
mod fork_version_response_tests {
use crate::{
ExecutionPayload, ExecutionPayloadMerge, ForkName, ForkVersionedResponse, MainnetEthSpec,
};
use serde_json::json;
#[test]
fn fork_versioned_response_deserialize_correct_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: Some(ForkName::Merge),
data: ExecutionPayload::Merge(ExecutionPayloadMerge::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_ok());
}
#[test]
fn fork_versioned_response_deserialize_incorrect_fork() {
type E = MainnetEthSpec;
let response_json =
serde_json::to_string(&json!(ForkVersionedResponse::<ExecutionPayload<E>> {
version: Some(ForkName::Capella),
data: ExecutionPayload::Merge(ExecutionPayloadMerge::default()),
}))
.unwrap();
let result: Result<ForkVersionedResponse<ExecutionPayload<E>>, _> =
serde_json::from_str(&response_json);
assert!(result.is_err());
}
}
+4
View File
@@ -46,6 +46,7 @@ pub mod execution_payload_header;
pub mod fork;
pub mod fork_data;
pub mod fork_name;
pub mod fork_versioned_response;
pub mod free_attestation;
pub mod graffiti;
pub mod historical_batch;
@@ -150,6 +151,9 @@ pub use crate::fork::Fork;
pub use crate::fork_context::ForkContext;
pub use crate::fork_data::ForkData;
pub use crate::fork_name::{ForkName, InconsistentFork};
pub use crate::fork_versioned_response::{
ExecutionOptimisticForkVersionedResponse, ForkVersionDeserialize, ForkVersionedResponse,
};
pub use crate::free_attestation::FreeAttestation;
pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN};
pub use crate::historical_batch::HistoricalBatch;
+55 -56
View File
@@ -4,6 +4,7 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::hash::Hash;
@@ -90,15 +91,15 @@ pub trait AbstractExecPayload<T: EthSpec>:
type Merge: OwnedExecPayload<T>
+ Into<Self>
+ From<ExecutionPayloadMerge<T>>
+ for<'a> From<Cow<'a, ExecutionPayloadMerge<T>>>
+ TryFrom<ExecutionPayloadHeaderMerge<T>>;
type Capella: OwnedExecPayload<T>
+ Into<Self>
+ From<ExecutionPayloadCapella<T>>
+ for<'a> From<Cow<'a, ExecutionPayloadCapella<T>>>
+ TryFrom<ExecutionPayloadHeaderCapella<T>>;
type Eip4844: OwnedExecPayload<T>
+ Into<Self>
+ From<ExecutionPayloadEip4844<T>>
+ for<'a> From<Cow<'a, ExecutionPayloadEip4844<T>>>
+ TryFrom<ExecutionPayloadHeaderEip4844<T>>;
fn default_at_fork(fork_name: ForkName) -> Result<Self, Error>;
@@ -150,31 +151,21 @@ pub struct FullPayload<T: EthSpec> {
impl<T: EthSpec> From<FullPayload<T>> for ExecutionPayload<T> {
fn from(full_payload: FullPayload<T>) -> Self {
match full_payload {
FullPayload::Merge(payload) => ExecutionPayload::Merge(payload.execution_payload),
FullPayload::Capella(payload) => ExecutionPayload::Capella(payload.execution_payload),
FullPayload::Eip4844(payload) => ExecutionPayload::Eip4844(payload.execution_payload),
}
map_full_payload_into_execution_payload!(full_payload, move |payload, cons| {
cons(payload.execution_payload)
})
}
}
impl<'a, T: EthSpec> From<FullPayloadRef<'a, T>> for ExecutionPayload<T> {
fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self {
match full_payload_ref {
FullPayloadRef::Merge(payload) => {
ExecutionPayload::Merge(payload.execution_payload.clone())
}
FullPayloadRef::Capella(payload) => {
ExecutionPayload::Capella(payload.execution_payload.clone())
}
FullPayloadRef::Eip4844(payload) => {
ExecutionPayload::Eip4844(payload.execution_payload.clone())
}
}
map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| {
cons(payload);
payload.execution_payload.clone().into()
})
}
}
// FIXME: can this be implemented as Deref or Clone somehow?
impl<'a, T: EthSpec> From<FullPayloadRef<'a, T>> for FullPayload<T> {
fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self {
map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| {
@@ -189,11 +180,12 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
BlockType::Full
}
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
let payload = map_full_payload_into_execution_payload!(self.clone(), |inner, cons| {
cons(inner.execution_payload)
});
ExecutionPayloadHeader::from(payload)
fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> {
map_full_payload_ref!(&'a _, self.to_ref(), move |inner, cons| {
cons(inner);
let exec_payload_ref: ExecutionPayloadRef<'a, T> = From::from(&inner.execution_payload);
ExecutionPayloadHeader::from(exec_payload_ref)
})
}
fn parent_hash<'a>(&'a self) -> ExecutionBlockHash {
@@ -404,17 +396,9 @@ impl<T: EthSpec> AbstractExecPayload<T> for FullPayload<T> {
impl<T: EthSpec> From<ExecutionPayload<T>> for FullPayload<T> {
fn from(execution_payload: ExecutionPayload<T>) -> Self {
match execution_payload {
ExecutionPayload::Merge(execution_payload) => {
Self::Merge(FullPayloadMerge { execution_payload })
}
ExecutionPayload::Capella(execution_payload) => {
Self::Capella(FullPayloadCapella { execution_payload })
}
ExecutionPayload::Eip4844(execution_payload) => {
Self::Eip4844(FullPayloadEip4844 { execution_payload })
}
}
map_execution_payload_into_full_payload!(execution_payload, |inner, cons| {
cons(inner.into())
})
}
}
@@ -666,6 +650,7 @@ macro_rules! impl_exec_payload_common {
$wrapped_field:ident, // execution_payload_header | execution_payload
$fork_variant:ident, // Merge | Merge
$block_type_variant:ident, // Blinded | Full
$is_default_with_empty_roots:block,
$f:block,
$g:block) => {
impl<T: EthSpec> ExecPayload<T> for $wrapper_type<T> {
@@ -675,7 +660,7 @@ macro_rules! impl_exec_payload_common {
fn to_execution_payload_header(&self) -> ExecutionPayloadHeader<T> {
ExecutionPayloadHeader::$fork_variant($wrapped_type_header::from(
self.$wrapped_field.clone(),
&self.$wrapped_field,
))
}
@@ -712,15 +697,8 @@ macro_rules! impl_exec_payload_common {
}
fn is_default_with_empty_roots(&self) -> bool {
// FIXME: is there a better way than ignoring this lint?
// This is necessary because the first invocation of this macro might expand to:
// self.execution_payload_header == ExecutionPayloadHeaderMerge::from(ExecutionPayloadMerge::default())
// but the second invocation might expand to:
// self.execution_payload == ExecutionPayloadMerge::from(ExecutionPayloadMerge::default())
#[allow(clippy::cmp_owned)]
{
self.$wrapped_field == $wrapped_type::from($wrapped_type_full::default())
}
let f = $is_default_with_empty_roots;
f(self)
}
fn transactions(&self) -> Option<&Transactions<T>> {
@@ -755,6 +733,12 @@ macro_rules! impl_exec_payload_for_fork {
execution_payload_header,
$fork_variant, // Merge
Blinded,
{
|wrapper: &$wrapper_type_header<T>| {
wrapper.execution_payload_header
== $wrapped_type_header::from(&$wrapped_type_full::default())
}
},
{ |_| { None } },
{
let c: for<'a> fn(&'a $wrapper_type_header<T>) -> Result<Hash256, Error> =
@@ -788,7 +772,7 @@ macro_rules! impl_exec_payload_for_fork {
fn default() -> Self {
Self {
execution_payload_header: $wrapped_type_header::from(
$wrapped_type_full::default(),
&$wrapped_type_full::default(),
),
}
}
@@ -806,11 +790,11 @@ macro_rules! impl_exec_payload_for_fork {
}
}
// FIXME(sproul): consider adding references to these From impls
impl<T: EthSpec> From<$wrapped_type_full<T>> for $wrapper_type_header<T> {
fn from(execution_payload: $wrapped_type_full<T>) -> Self {
// BlindedPayload* from CoW reference to ExecutionPayload* (hopefully just a reference).
impl<'a, T: EthSpec> From<Cow<'a, $wrapped_type_full<T>>> for $wrapper_type_header<T> {
fn from(execution_payload: Cow<'a, $wrapped_type_full<T>>) -> Self {
Self {
execution_payload_header: $wrapped_type_header::from(execution_payload),
execution_payload_header: $wrapped_type_header::from(&*execution_payload),
}
}
}
@@ -825,6 +809,11 @@ macro_rules! impl_exec_payload_for_fork {
execution_payload,
$fork_variant, // Merge
Full,
{
|wrapper: &$wrapper_type_full<T>| {
wrapper.execution_payload == $wrapped_type_full::default()
}
},
{
let c: for<'a> fn(&'a $wrapper_type_full<T>) -> Option<&'a Transactions<T>> =
|payload: &$wrapper_type_full<T>| Some(&payload.execution_payload.transactions);
@@ -848,6 +837,15 @@ macro_rules! impl_exec_payload_for_fork {
}
}
// FullPayload * from CoW reference to ExecutionPayload* (hopefully already owned).
impl<'a, T: EthSpec> From<Cow<'a, $wrapped_type_full<T>>> for $wrapper_type_full<T> {
fn from(execution_payload: Cow<'a, $wrapped_type_full<T>>) -> Self {
Self {
execution_payload: $wrapped_type_full::from(execution_payload.into_owned()),
}
}
}
impl<T: EthSpec> TryFrom<ExecutionPayloadHeader<T>> for $wrapper_type_full<T> {
type Error = Error;
fn try_from(_: ExecutionPayloadHeader<T>) -> Result<Self, Self::Error> {
@@ -915,11 +913,12 @@ impl<T: EthSpec> AbstractExecPayload<T> for BlindedPayload<T> {
impl<T: EthSpec> From<ExecutionPayload<T>> for BlindedPayload<T> {
fn from(payload: ExecutionPayload<T>) -> Self {
match payload {
ExecutionPayload::Merge(payload) => BlindedPayload::Merge(payload.into()),
ExecutionPayload::Capella(payload) => BlindedPayload::Capella(payload.into()),
ExecutionPayload::Eip4844(payload) => BlindedPayload::Eip4844(payload.into()),
}
// This implementation is a bit wasteful in that it discards the payload body.
// Required by the top-level constraint on AbstractExecPayload but could maybe be loosened
// in future.
map_execution_payload_into_blinded_payload!(payload, |inner, cons| cons(From::from(
Cow::Owned(inner)
)))
}
}
@@ -522,6 +522,24 @@ impl<E: EthSpec> SignedBeaconBlock<E> {
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> ForkVersionDeserialize
for SignedBeaconBlock<E, Payload>
{
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
fork_name: ForkName,
) -> Result<Self, D::Error> {
Ok(map_fork_name!(
fork_name,
Self,
serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!(
"SignedBeaconBlock failed to deserialize: {:?}",
e
)))?
))
}
}
#[cfg(test)]
mod test {
use super::*;