merge with upstream
This commit is contained in:
@@ -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!(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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(
|
||||
|
||||
+2
-3
@@ -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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
)))?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user