Retrospective invalidation of exec. payloads for opt. sync (#2837)

## Issue Addressed

NA

## Proposed Changes

Adds the functionality to allow blocks to be validated/invalidated after their import as per the [optimistic sync spec](https://github.com/ethereum/consensus-specs/blob/dev/sync/optimistic.md#how-to-optimistically-import-blocks). This means:

- Updating `ProtoArray` to allow flipping the `execution_status` of ancestors/descendants based on payload validity updates.
- Creating separation between `execution_layer` and the `beacon_chain` by creating a `PayloadStatus` struct.
- Refactoring how the `execution_layer` selects a `PayloadStatus` from the multiple statuses returned from multiple EEs.
- Adding testing framework for optimistic imports.
- Add `ExecutionBlockHash(Hash256)` new-type struct to avoid confusion between *beacon block roots* and *execution payload hashes*.
- Add `merge` to [`FORKS`](c3a793fd73/Makefile (L17)) in the `Makefile` to ensure we test the beacon chain with merge settings.
    - Fix some tests here that were failing due to a missing execution layer.

## TODO

- [ ] Balance tests

Co-authored-by: Mark Mackey <mark@sigmaprime.io>
This commit is contained in:
Paul Hauner
2022-02-28 22:07:48 +00:00
parent 5e1f8a8480
commit 27e83b888c
50 changed files with 3358 additions and 768 deletions

View File

@@ -6,8 +6,8 @@ use std::marker::PhantomData;
use std::time::Duration;
use types::{
consts::merge::INTERVALS_PER_SLOT, AttestationShufflingId, BeaconBlock, BeaconState,
BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, IndexedAttestation,
RelativeEpoch, SignedBeaconBlock, Slot,
BeaconStateError, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
IndexedAttestation, RelativeEpoch, SignedBeaconBlock, Slot,
};
#[derive(Debug)]
@@ -17,6 +17,7 @@ pub enum Error<T> {
ProtoArrayError(String),
InvalidProtoArrayBytes(String),
InvalidLegacyProtoArrayBytes(String),
FailedToProcessInvalidExecutionPayload(String),
MissingProtoArrayBlock(Hash256),
UnknownAncestor {
ancestor_slot: Slot,
@@ -43,6 +44,12 @@ pub enum Error<T> {
block_root: Hash256,
payload_verification_status: PayloadVerificationStatus,
},
MissingJustifiedBlock {
justified_checkpoint: Checkpoint,
},
MissingFinalizedBlock {
finalized_checkpoint: Checkpoint,
},
}
impl<T> From<InvalidAttestation> for Error<T> {
@@ -299,9 +306,15 @@ where
let execution_status = anchor_block.message_merge().map_or_else(
|()| ExecutionStatus::irrelevant(),
|message| {
// Assume that this payload is valid, since the anchor should be a trusted block and
// state.
ExecutionStatus::Valid(message.body.execution_payload.block_hash)
let execution_payload = &message.body.execution_payload;
if execution_payload == &<_>::default() {
// A default payload does not have execution enabled.
ExecutionStatus::irrelevant()
} else {
// Assume that this payload is valid, since the anchor should be a trusted block and
// state.
ExecutionStatus::Valid(message.body.execution_payload.block_hash)
}
},
);
@@ -464,6 +477,17 @@ where
Ok(true)
}
/// See `ProtoArrayForkChoice::process_execution_payload_invalidation` for documentation.
pub fn on_invalid_execution_payload(
&mut self,
head_block_root: Hash256,
latest_valid_ancestor_root: Option<ExecutionBlockHash>,
) -> Result<(), Error<T::Error>> {
self.proto_array
.process_execution_payload_invalidation(head_block_root, latest_valid_ancestor_root)
.map_err(Error::FailedToProcessInvalidExecutionPayload)
}
/// Add `block` to the fork choice DAG.
///
/// - `block_root` is the root of `block.
@@ -592,7 +616,7 @@ where
let execution_status = if let Ok(execution_payload) = block.body().execution_payload() {
let block_hash = execution_payload.block_hash;
if block_hash == Hash256::zero() {
if block_hash == ExecutionBlockHash::zero() {
// The block is post-merge-fork, but pre-terminal-PoW block. We don't need to verify
// the payload.
ExecutionStatus::irrelevant()
@@ -875,6 +899,29 @@ where
}
}
/// Returns the `ProtoBlock` for the justified checkpoint.
///
/// ## Notes
///
/// This does *not* return the "best justified checkpoint". It returns the justified checkpoint
/// that is used for computing balances.
pub fn get_justified_block(&self) -> Result<ProtoBlock, Error<T::Error>> {
let justified_checkpoint = self.justified_checkpoint();
self.get_block(&justified_checkpoint.root)
.ok_or(Error::MissingJustifiedBlock {
justified_checkpoint,
})
}
/// Returns the `ProtoBlock` for the finalized checkpoint.
pub fn get_finalized_block(&self) -> Result<ProtoBlock, Error<T::Error>> {
let finalized_checkpoint = self.finalized_checkpoint();
self.get_block(&finalized_checkpoint.root)
.ok_or(Error::MissingFinalizedBlock {
finalized_checkpoint,
})
}
/// 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 {
self.proto_array

View File

@@ -122,18 +122,24 @@ impl ForkChoiceTest {
}
/// Assert there was a shutdown signal sent by the beacon chain.
pub fn assert_shutdown_signal_sent(mut self) -> Self {
self.harness.shutdown_receiver.close();
let msg = self.harness.shutdown_receiver.try_next().unwrap();
assert!(msg.is_some());
pub fn shutdown_signal_sent(&self) -> bool {
let mutex = self.harness.shutdown_receiver.clone();
let mut shutdown_receiver = mutex.lock();
shutdown_receiver.close();
let msg = shutdown_receiver.try_next().unwrap();
msg.is_some()
}
/// Assert there was a shutdown signal sent by the beacon chain.
pub fn assert_shutdown_signal_sent(self) -> Self {
assert!(self.shutdown_signal_sent());
self
}
/// Assert no shutdown was signal sent by the beacon chain.
pub fn assert_shutdown_signal_not_sent(mut self) -> Self {
self.harness.shutdown_receiver.close();
let msg = self.harness.shutdown_receiver.try_next().unwrap();
assert!(msg.is_none());
pub fn assert_shutdown_signal_not_sent(self) -> Self {
assert!(!self.shutdown_signal_sent());
self
}
@@ -479,6 +485,22 @@ fn is_safe_to_update(slot: Slot, spec: &ChainSpec) -> bool {
slot % E::slots_per_epoch() < spec.safe_slots_to_update_justified
}
#[test]
fn justified_and_finalized_blocks() {
let tester = ForkChoiceTest::new();
let fork_choice = tester.harness.chain.fork_choice.read();
let justified_checkpoint = fork_choice.justified_checkpoint();
assert_eq!(justified_checkpoint.epoch, 0);
assert!(justified_checkpoint.root != Hash256::zero());
assert!(fork_choice.get_justified_block().is_ok());
let finalized_checkpoint = fork_choice.finalized_checkpoint();
assert_eq!(finalized_checkpoint.epoch, 0);
assert!(finalized_checkpoint.root != Hash256::zero());
assert!(fork_choice.get_finalized_block().is_ok());
}
/// - The new justified checkpoint descends from the current.
/// - Current slot is within `SAFE_SLOTS_TO_UPDATE_JUSTIFIED`
#[test]

View File

@@ -6,6 +6,18 @@ fn main() {
write_test_def_to_yaml("no_votes.yaml", get_no_votes_test_definition());
write_test_def_to_yaml("ffg_01.yaml", get_ffg_case_01_test_definition());
write_test_def_to_yaml("ffg_02.yaml", get_ffg_case_02_test_definition());
write_test_def_to_yaml(
"execution_status_01.yaml",
get_execution_status_test_definition_01(),
);
write_test_def_to_yaml(
"execution_status_02.yaml",
get_execution_status_test_definition_02(),
);
write_test_def_to_yaml(
"execution_status_03.yaml",
get_execution_status_test_definition_03(),
);
}
fn write_test_def_to_yaml(filename: &str, def: ForkChoiceTestDefinition) {

View File

@@ -1,9 +1,10 @@
use types::{Checkpoint, Epoch, Hash256};
use types::{Checkpoint, Epoch, ExecutionBlockHash, Hash256};
#[derive(Clone, PartialEq, Debug)]
pub enum Error {
FinalizedNodeUnknown(Hash256),
JustifiedNodeUnknown(Hash256),
NodeUnknown(Hash256),
InvalidFinalizedRootChange,
InvalidNodeIndex(usize),
InvalidParentIndex(usize),
@@ -15,6 +16,7 @@ pub enum Error {
DeltaOverflow(usize),
ProposerBoostOverflow(usize),
IndexOverflow(&'static str),
InvalidExecutionDeltaOverflow(usize),
InvalidDeltaLen {
deltas: usize,
indices: usize,
@@ -26,7 +28,21 @@ pub enum Error {
InvalidBestNode(Box<InvalidBestNodeInfo>),
InvalidAncestorOfValidPayload {
ancestor_block_root: Hash256,
ancestor_payload_block_hash: Hash256,
ancestor_payload_block_hash: ExecutionBlockHash,
},
ValidExecutionStatusBecameInvalid {
block_root: Hash256,
payload_block_hash: ExecutionBlockHash,
},
InvalidJustifiedCheckpointExecutionStatus {
justified_root: Hash256,
},
UnknownLatestValidAncestorHash {
block_root: Hash256,
latest_valid_ancestor_hash: Option<ExecutionBlockHash>,
},
IrrelevantDescendant {
block_root: Hash256,
},
}

View File

@@ -1,11 +1,16 @@
mod execution_status;
mod ffg_updates;
mod no_votes;
mod votes;
use crate::proto_array_fork_choice::{Block, ExecutionStatus, ProtoArrayForkChoice};
use serde_derive::{Deserialize, Serialize};
use types::{AttestationShufflingId, Checkpoint, Epoch, EthSpec, Hash256, MainnetEthSpec, Slot};
use types::{
AttestationShufflingId, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
MainnetEthSpec, Slot,
};
pub use execution_status::*;
pub use ffg_updates::*;
pub use no_votes::*;
pub use votes::*;
@@ -18,6 +23,13 @@ pub enum Operation {
justified_state_balances: Vec<u64>,
expected_head: Hash256,
},
ProposerBoostFindHead {
justified_checkpoint: Checkpoint,
finalized_checkpoint: Checkpoint,
justified_state_balances: Vec<u64>,
expected_head: Hash256,
proposer_boost_root: Hash256,
},
InvalidFindHead {
justified_checkpoint: Checkpoint,
finalized_checkpoint: Checkpoint,
@@ -40,6 +52,14 @@ pub enum Operation {
prune_threshold: usize,
expected_len: usize,
},
InvalidatePayload {
head_block_root: Hash256,
latest_valid_ancestor_root: Option<ExecutionBlockHash>,
},
AssertWeight {
block_root: Hash256,
weight: u64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -52,9 +72,11 @@ pub struct ForkChoiceTestDefinition {
impl ForkChoiceTestDefinition {
pub fn run(self) {
let mut spec = MainnetEthSpec::default_spec();
spec.proposer_score_boost = Some(50);
let junk_shuffling_id =
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
let execution_status = ExecutionStatus::irrelevant();
let mut fork_choice = ProtoArrayForkChoice::new(
self.finalized_block_slot,
Hash256::zero(),
@@ -62,7 +84,7 @@ impl ForkChoiceTestDefinition {
self.finalized_checkpoint,
junk_shuffling_id.clone(),
junk_shuffling_id,
execution_status,
ExecutionStatus::Unknown(ExecutionBlockHash::zero()),
)
.expect("should create fork choice struct");
@@ -80,7 +102,7 @@ impl ForkChoiceTestDefinition {
finalized_checkpoint,
&justified_state_balances,
Hash256::zero(),
&MainnetEthSpec::default_spec(),
&spec,
)
.map_err(|e| e)
.unwrap_or_else(|e| {
@@ -89,7 +111,34 @@ impl ForkChoiceTestDefinition {
assert_eq!(
head, expected_head,
"Operation at index {} failed checks. Operation: {:?}",
"Operation at index {} failed head check. Operation: {:?}",
op_index, op
);
check_bytes_round_trip(&fork_choice);
}
Operation::ProposerBoostFindHead {
justified_checkpoint,
finalized_checkpoint,
justified_state_balances,
expected_head,
proposer_boost_root,
} => {
let head = fork_choice
.find_head::<MainnetEthSpec>(
justified_checkpoint,
finalized_checkpoint,
&justified_state_balances,
proposer_boost_root,
&spec,
)
.map_err(|e| e)
.unwrap_or_else(|e| {
panic!("find_head op at index {} returned error {}", op_index, e)
});
assert_eq!(
head, expected_head,
"Operation at index {} failed head check. Operation: {:?}",
op_index, op
);
check_bytes_round_trip(&fork_choice);
@@ -104,7 +153,7 @@ impl ForkChoiceTestDefinition {
finalized_checkpoint,
&justified_state_balances,
Hash256::zero(),
&MainnetEthSpec::default_spec(),
&spec,
);
assert!(
@@ -138,7 +187,10 @@ impl ForkChoiceTestDefinition {
),
justified_checkpoint,
finalized_checkpoint,
execution_status,
// All blocks are imported optimistically.
execution_status: ExecutionStatus::Unknown(ExecutionBlockHash::from_root(
root,
)),
};
fork_choice.process_block(block).unwrap_or_else(|e| {
panic!(
@@ -183,22 +235,41 @@ impl ForkChoiceTestDefinition {
expected_len
);
}
Operation::InvalidatePayload {
head_block_root,
latest_valid_ancestor_root,
} => fork_choice
.process_execution_payload_invalidation(
head_block_root,
latest_valid_ancestor_root,
)
.unwrap(),
Operation::AssertWeight { block_root, weight } => assert_eq!(
fork_choice.get_weight(&block_root).unwrap(),
weight,
"block weight"
),
}
}
}
}
/// Gives a hash that is not the zero hash (unless i is `usize::max_value)`.
fn get_hash(i: u64) -> Hash256 {
/// Gives a root that is not the zero hash (unless i is `usize::max_value)`.
fn get_root(i: u64) -> Hash256 {
Hash256::from_low_u64_be(i + 1)
}
/// Gives a hash that is not the zero hash (unless i is `usize::max_value)`.
fn get_hash(i: u64) -> ExecutionBlockHash {
ExecutionBlockHash::from_root(get_root(i))
}
/// Gives a checkpoint with a root that is not the zero hash (unless i is `usize::max_value)`.
/// `Epoch` will always equal `i`.
fn get_checkpoint(i: u64) -> Checkpoint {
Checkpoint {
epoch: Epoch::new(i),
root: get_hash(i),
root: get_root(i),
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(0),
expected_head: get_root(0),
});
// Build the following tree (stick? lol).
@@ -23,22 +23,22 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition {
// 3 <- just: 2, fin: 1
ops.push(Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(1),
parent_root: get_hash(0),
root: get_root(1),
parent_root: get_root(0),
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(2),
parent_root: get_hash(1),
root: get_root(2),
parent_root: get_root(1),
justified_checkpoint: get_checkpoint(1),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(3),
root: get_hash(3),
parent_root: get_hash(2),
root: get_root(3),
parent_root: get_root(2),
justified_checkpoint: get_checkpoint(2),
finalized_checkpoint: get_checkpoint(1),
});
@@ -56,7 +56,7 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(3),
expected_head: get_root(3),
});
// Ensure that with justified epoch 1 we find 2
@@ -72,7 +72,7 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(1),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Ensure that with justified epoch 2 we find 3
@@ -88,7 +88,7 @@ pub fn get_ffg_case_01_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(2),
finalized_checkpoint: get_checkpoint(1),
justified_state_balances: balances,
expected_head: get_hash(3),
expected_head: get_root(3),
});
// END OF TESTS
@@ -109,7 +109,7 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(0),
expected_head: get_root(0),
});
// Build the following tree.
@@ -129,48 +129,48 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
// Left branch
ops.push(Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(1),
parent_root: get_hash(0),
root: get_root(1),
parent_root: get_root(0),
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(3),
parent_root: get_hash(1),
root: get_root(3),
parent_root: get_root(1),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(1),
root: get_root(1),
},
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(3),
root: get_hash(5),
parent_root: get_hash(3),
root: get_root(5),
parent_root: get_root(3),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(1),
root: get_root(1),
},
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(4),
root: get_hash(7),
parent_root: get_hash(5),
root: get_root(7),
parent_root: get_root(5),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(1),
root: get_root(1),
},
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(5),
root: get_hash(9),
parent_root: get_hash(7),
root: get_root(9),
parent_root: get_root(7),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(3),
root: get_root(3),
},
finalized_checkpoint: get_checkpoint(0),
});
@@ -178,42 +178,42 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
// Right branch
ops.push(Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(2),
parent_root: get_hash(0),
root: get_root(2),
parent_root: get_root(0),
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(4),
parent_root: get_hash(2),
root: get_root(4),
parent_root: get_root(2),
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(3),
root: get_hash(6),
parent_root: get_hash(4),
root: get_root(6),
parent_root: get_root(4),
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(4),
root: get_hash(8),
parent_root: get_hash(6),
root: get_root(8),
parent_root: get_root(6),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(2),
root: get_root(2),
},
finalized_checkpoint: get_checkpoint(0),
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(5),
root: get_hash(10),
parent_root: get_hash(8),
root: get_root(10),
parent_root: get_root(8),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(4),
root: get_root(4),
},
finalized_checkpoint: get_checkpoint(0),
});
@@ -235,23 +235,23 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above, but with justified epoch 2.
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(4),
root: get_root(4),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above, but with justified epoch 3 (should be invalid).
ops.push(Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(3),
root: get_hash(6),
root: get_root(6),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
@@ -272,7 +272,7 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
// 9 10
ops.push(Operation::ProcessAttestation {
validator_index: 0,
block_root: get_hash(1),
block_root: get_root(1),
target_epoch: Epoch::new(0),
});
@@ -293,23 +293,23 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Save as above but justified epoch 2.
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(3),
root: get_root(3),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Save as above but justified epoch 3 (should fail).
ops.push(Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(3),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
@@ -330,7 +330,7 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
// 9 10
ops.push(Operation::ProcessAttestation {
validator_index: 1,
block_root: get_hash(2),
block_root: get_root(2),
target_epoch: Epoch::new(0),
});
@@ -351,23 +351,23 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above but justified epoch 2.
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(4),
root: get_root(4),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above but justified epoch 3 (should fail).
ops.push(Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(3),
root: get_hash(6),
root: get_root(6),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
@@ -389,27 +389,27 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(0),
root: get_hash(1),
root: get_root(1),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Same as above but justified epoch 2.
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(3),
root: get_root(3),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Same as above but justified epoch 3 (should fail).
ops.push(Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(3),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
@@ -432,23 +432,23 @@ pub fn get_ffg_case_02_test_definition() -> ForkChoiceTestDefinition {
justified_checkpoint: get_checkpoint(0),
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above but justified epoch 2.
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(4),
root: get_root(4),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Same as above but justified epoch 3 (should fail).
ops.push(Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(3),
root: get_hash(6),
root: get_root(6),
},
finalized_checkpoint: get_checkpoint(0),
justified_state_balances: balances,

View File

@@ -24,7 +24,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 2
Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(2),
root: get_root(2),
parent_root: Hash256::zero(),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
@@ -50,7 +50,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
},
// Add block 1
//
@@ -59,8 +59,8 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 2 1
Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(1),
parent_root: get_hash(0),
root: get_root(1),
parent_root: get_root(0),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: Hash256::zero(),
@@ -85,7 +85,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
},
// Add block 3
//
@@ -96,8 +96,8 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 3
Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(3),
parent_root: get_hash(1),
root: get_root(3),
parent_root: get_root(1),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: Hash256::zero(),
@@ -124,7 +124,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
},
// Add block 4
//
@@ -135,8 +135,8 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 4 3
Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(4),
parent_root: get_hash(2),
root: get_root(4),
parent_root: get_root(2),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: Hash256::zero(),
@@ -163,7 +163,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(4),
expected_head: get_root(4),
},
// Add block 5 with a justified epoch of 2
//
@@ -176,8 +176,8 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 5 <- justified epoch = 2
Operation::ProcessBlock {
slot: Slot::new(3),
root: get_hash(5),
parent_root: get_hash(4),
root: get_root(5),
parent_root: get_root(4),
justified_checkpoint: get_checkpoint(2),
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
@@ -203,7 +203,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(4),
expected_head: get_root(4),
},
// Ensure there is an error when starting from a block that has the wrong justified epoch.
//
@@ -217,7 +217,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
Operation::InvalidFindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
@@ -241,7 +241,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(5),
expected_head: get_root(5),
},
// Add block 6
//
@@ -256,8 +256,8 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
// 6
Operation::ProcessBlock {
slot: Slot::new(4),
root: get_hash(6),
parent_root: get_hash(5),
root: get_root(6),
parent_root: get_root(5),
justified_checkpoint: get_checkpoint(2),
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
@@ -282,7 +282,7 @@ pub fn get_no_votes_test_definition() -> ForkChoiceTestDefinition {
root: Hash256::zero(),
},
justified_state_balances: balances,
expected_head: get_hash(6),
expected_head: get_root(6),
},
];

View File

@@ -8,14 +8,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(0),
expected_head: get_root(0),
});
// Add a block with a hash of 2.
@@ -25,15 +25,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 2
ops.push(Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(2),
parent_root: get_hash(0),
root: get_root(2),
parent_root: get_root(0),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
});
@@ -45,14 +45,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Add a block with a hash of 1 that comes off the genesis block (this is a fork compared
@@ -63,15 +63,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 2 1
ops.push(Operation::ProcessBlock {
slot: Slot::new(1),
root: get_hash(1),
parent_root: get_hash(0),
root: get_root(1),
parent_root: get_root(0),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
});
@@ -83,14 +83,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Add a vote to block 1
@@ -100,7 +100,7 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 2 1 <- +vote
ops.push(Operation::ProcessAttestation {
validator_index: 0,
block_root: get_hash(1),
block_root: get_root(1),
target_epoch: Epoch::new(2),
});
@@ -112,14 +112,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(1),
expected_head: get_root(1),
});
// Add a vote to block 2
@@ -129,7 +129,7 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// +vote-> 2 1
ops.push(Operation::ProcessAttestation {
validator_index: 1,
block_root: get_hash(2),
block_root: get_root(2),
target_epoch: Epoch::new(2),
});
@@ -141,14 +141,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Add block 3.
@@ -160,15 +160,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 3
ops.push(Operation::ProcessBlock {
slot: Slot::new(2),
root: get_hash(3),
parent_root: get_hash(1),
root: get_root(3),
parent_root: get_root(1),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
});
@@ -182,14 +182,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Move validator #0 vote from 1 to 3
@@ -201,7 +201,7 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 3 <- +vote
ops.push(Operation::ProcessAttestation {
validator_index: 0,
block_root: get_hash(3),
block_root: get_root(3),
target_epoch: Epoch::new(3),
});
@@ -215,14 +215,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(2),
expected_head: get_root(2),
});
// Move validator #1 vote from 2 to 1 (this is an equivocation, but fork choice doesn't
@@ -235,7 +235,7 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 3
ops.push(Operation::ProcessAttestation {
validator_index: 1,
block_root: get_hash(1),
block_root: get_root(1),
target_epoch: Epoch::new(3),
});
@@ -249,14 +249,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(3),
expected_head: get_root(3),
});
// Add block 4.
@@ -270,15 +270,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 4
ops.push(Operation::ProcessBlock {
slot: Slot::new(3),
root: get_hash(4),
parent_root: get_hash(3),
root: get_root(4),
parent_root: get_root(3),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
});
@@ -294,14 +294,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(4),
expected_head: get_root(4),
});
// Add block 5, which has a justified epoch of 2.
@@ -317,15 +317,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 5 <- justified epoch = 2
ops.push(Operation::ProcessBlock {
slot: Slot::new(4),
root: get_hash(5),
parent_root: get_hash(4),
root: get_root(5),
parent_root: get_root(4),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(1),
root: get_root(1),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(1),
root: get_root(1),
},
});
@@ -343,14 +343,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(4),
expected_head: get_root(4),
});
// Add block 6, which has a justified epoch of 0.
@@ -366,15 +366,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 5 6 <- justified epoch = 0
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(6),
parent_root: get_hash(4),
root: get_root(6),
parent_root: get_root(4),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
});
@@ -391,12 +391,12 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// +2 vote-> 5 6
ops.push(Operation::ProcessAttestation {
validator_index: 0,
block_root: get_hash(5),
block_root: get_root(5),
target_epoch: Epoch::new(4),
});
ops.push(Operation::ProcessAttestation {
validator_index: 1,
block_root: get_hash(5),
block_root: get_root(5),
target_epoch: Epoch::new(4),
});
@@ -420,41 +420,41 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 9
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(7),
parent_root: get_hash(5),
root: get_root(7),
parent_root: get_root(5),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(8),
parent_root: get_hash(7),
root: get_root(8),
parent_root: get_root(7),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
});
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(9),
parent_root: get_hash(8),
root: get_root(9),
parent_root: get_root(8),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
});
@@ -479,14 +479,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(6),
expected_head: get_root(6),
});
// Change fork-choice justified epoch to 1, and the start block to 5 and ensure that 9 is
@@ -512,14 +512,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Change fork-choice justified epoch to 1, and the start block to 5 and ensure that 9 is
@@ -544,12 +544,12 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 9 <- +2 votes
ops.push(Operation::ProcessAttestation {
validator_index: 0,
block_root: get_hash(9),
block_root: get_root(9),
target_epoch: Epoch::new(5),
});
ops.push(Operation::ProcessAttestation {
validator_index: 1,
block_root: get_hash(9),
block_root: get_root(9),
target_epoch: Epoch::new(5),
});
@@ -572,15 +572,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 9 10
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(10),
parent_root: get_hash(8),
root: get_root(10),
parent_root: get_root(8),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
});
@@ -588,14 +588,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Introduce 2 more validators into the system
@@ -620,12 +620,12 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 9 10 <- +2 votes
ops.push(Operation::ProcessAttestation {
validator_index: 2,
block_root: get_hash(10),
block_root: get_root(10),
target_epoch: Epoch::new(5),
});
ops.push(Operation::ProcessAttestation {
validator_index: 3,
block_root: get_hash(10),
block_root: get_root(10),
target_epoch: Epoch::new(5),
});
@@ -649,14 +649,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Set the balances of the last two validators to zero
@@ -674,14 +674,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Set the balances of the last two validators back to 1
@@ -699,14 +699,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(10),
expected_head: get_root(10),
});
// Remove the last two validators
@@ -725,19 +725,19 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Ensure that pruning below the prune threshold does not prune.
ops.push(Operation::Prune {
finalized_root: get_hash(5),
finalized_root: get_root(5),
prune_threshold: usize::max_value(),
expected_len: 11,
});
@@ -746,14 +746,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Ensure that pruning above the prune threshold does prune.
@@ -775,7 +775,7 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// / \
// 9 10
ops.push(Operation::Prune {
finalized_root: get_hash(5),
finalized_root: get_root(5),
prune_threshold: 1,
expected_len: 6,
});
@@ -784,14 +784,14 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances.clone(),
expected_head: get_hash(9),
expected_head: get_root(9),
});
// Add block 11
@@ -807,15 +807,15 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
// 11
ops.push(Operation::ProcessBlock {
slot: Slot::new(0),
root: get_hash(11),
parent_root: get_hash(9),
root: get_root(11),
parent_root: get_root(9),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
});
@@ -833,25 +833,25 @@ pub fn get_votes_test_definition() -> ForkChoiceTestDefinition {
ops.push(Operation::FindHead {
justified_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(2),
root: get_hash(5),
root: get_root(5),
},
justified_state_balances: balances,
expected_head: get_hash(11),
expected_head: get_root(11),
});
ForkChoiceTestDefinition {
finalized_block_slot: Slot::new(0),
justified_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
finalized_checkpoint: Checkpoint {
epoch: Epoch::new(1),
root: get_hash(0),
root: get_root(0),
},
operations: ops,
}

View File

@@ -4,8 +4,11 @@ use serde_derive::{Deserialize, Serialize};
use ssz::four_byte_option_impl;
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use std::collections::HashMap;
use types::{AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, Slot};
use std::collections::{HashMap, HashSet};
use types::{
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
Slot,
};
// Define a "legacy" implementation of `Option<usize>` which uses four bytes for encoding the union
// selector.
@@ -126,26 +129,42 @@ impl ProtoArray {
continue;
}
let mut node_delta = deltas
.get(node_index)
.copied()
.ok_or(Error::InvalidNodeDelta(node_index))?;
let execution_status_is_invalid = node.execution_status.is_invalid();
let mut node_delta = if execution_status_is_invalid {
// If the node has an invalid execution payload, reduce its weight to zero.
0_i64
.checked_sub(node.weight as i64)
.ok_or(Error::InvalidExecutionDeltaOverflow(node_index))?
} else {
deltas
.get(node_index)
.copied()
.ok_or(Error::InvalidNodeDelta(node_index))?
};
// If we find the node for which the proposer boost was previously applied, decrease
// the delta by the previous score amount.
if self.previous_proposer_boost.root != Hash256::zero()
&& self.previous_proposer_boost.root == node.root
// Invalid nodes will always have a weight of zero so there's no need to subtract
// the proposer boost delta.
&& !execution_status_is_invalid
{
node_delta = node_delta
.checked_sub(self.previous_proposer_boost.score as i64)
.ok_or(Error::DeltaOverflow(node_index))?;
}
// If we find the node matching the current proposer boost root, increase
// the delta by the new score amount.
// the delta by the new score amount (unless the block has an invalid execution status).
//
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/fork-choice.md#get_latest_attesting_balance
if let Some(proposer_score_boost) = spec.proposer_score_boost {
if proposer_boost_root != Hash256::zero() && proposer_boost_root == node.root {
if proposer_boost_root != Hash256::zero()
&& proposer_boost_root == node.root
// Invalid nodes (or their ancestors) should not receive a proposer boost.
&& !execution_status_is_invalid
{
proposer_score =
calculate_proposer_boost::<E>(new_balances, proposer_score_boost)
.ok_or(Error::ProposerBoostOverflow(node_index))?;
@@ -156,7 +175,10 @@ impl ProtoArray {
}
// Apply the delta to the node.
if node_delta < 0 {
if execution_status_is_invalid {
// Invalid nodes always have a weight of 0.
node.weight = 0
} else if node_delta < 0 {
// Note: I am conflicted about whether to use `saturating_sub` or `checked_sub`
// here.
//
@@ -250,14 +272,20 @@ impl ProtoArray {
self.maybe_update_best_child_and_descendant(parent_index, node_index)?;
if matches!(block.execution_status, ExecutionStatus::Valid(_)) {
self.propagate_execution_payload_verification(parent_index)?;
self.propagate_execution_payload_validation(parent_index)?;
}
}
Ok(())
}
pub fn propagate_execution_payload_verification(
/// Updates the `verified_node_index` and all ancestors to have validated execution payloads.
///
/// Returns an error if:
///
/// - The `verified_node_index` is unknown.
/// - Any of the to-be-validated payloads are already invalid.
pub fn propagate_execution_payload_validation(
&mut self,
verified_node_index: usize,
) -> Result<(), Error> {
@@ -300,6 +328,213 @@ impl ProtoArray {
}
}
/// Invalidate the relevant ancestors and descendants of a block with an invalid execution
/// payload.
///
/// The `head_block_root` should be the beacon block root of the block with the invalid
/// execution payload, _or_ its parent where the block with the invalid payload has not yet
/// been applied to `self`.
///
/// The `latest_valid_hash` should be the hash of most recent *valid* execution payload
/// contained in an ancestor block of `head_block_root`.
///
/// This function will invalidate:
///
/// * The block matching `head_block_root` _unless_ that block has a payload matching `latest_valid_hash`.
/// * All ancestors of `head_block_root` back to the block with payload matching
/// `latest_valid_hash` (endpoint > exclusive). In the case where the `head_block_root` is the parent
/// of the invalid block and itself matches `latest_valid_hash`, no ancestors will be invalidated.
/// * All descendants of `latest_valid_hash` if supplied and consistent with `head_block_root`,
/// or else all descendants of `head_block_root`.
///
/// ## Details
///
/// If `head_block_root` is not known to fork choice, an error is returned.
///
/// If `latest_valid_hash` is `Some(hash)` where `hash` is either not known to fork choice
/// (perhaps it's junk or pre-finalization), then only the `head_block_root` block will be
/// invalidated (no ancestors). No error will be returned in this case.
///
/// If `latest_valid_hash` is `Some(hash)` where `hash` is a known ancestor of
/// `head_block_root`, then all blocks between `head_block_root` and `latest_valid_hash` will
/// be invalidated. Additionally, all blocks that descend from a newly-invalidated block will
/// also be invalidated.
pub fn propagate_execution_payload_invalidation(
&mut self,
head_block_root: Hash256,
latest_valid_ancestor_hash: Option<ExecutionBlockHash>,
) -> Result<(), Error> {
let mut invalidated_indices: HashSet<usize> = <_>::default();
/*
* Step 1:
*
* Find the `head_block_root` and maybe iterate backwards and invalidate ancestors. Record
* all invalidated block indices in `invalidated_indices`.
*/
let mut index = *self
.indices
.get(&head_block_root)
.ok_or(Error::NodeUnknown(head_block_root))?;
// Try to map the ancestor payload *hash* to an ancestor beacon block *root*.
let latest_valid_ancestor_root = latest_valid_ancestor_hash
.and_then(|hash| self.execution_block_hash_to_beacon_block_root(&hash));
// Set to `true` if both conditions are satisfied:
//
// 1. The `head_block_root` is a descendant of `latest_valid_ancestor_hash`
// 2. The `latest_valid_ancestor_hash` is equal to or a descendant of the finalized block.
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)
});
// Collect all *ancestors* which were declared invalid since they reside between the
// `head_block_root` and the `latest_valid_ancestor_root`.
loop {
let node = self
.nodes
.get_mut(index)
.ok_or(Error::InvalidNodeIndex(index))?;
match node.execution_status {
ExecutionStatus::Valid(hash)
| ExecutionStatus::Invalid(hash)
| ExecutionStatus::Unknown(hash) => {
// If we're no longer processing the `head_block_root` and the last valid
// ancestor is unknown, exit this loop and proceed to invalidate and
// descendants of `head_block_root`/`latest_valid_ancestor_root`.
//
// In effect, this means that if an unknown hash (junk or pre-finalization) is
// supplied, don't validate any ancestors. The alternative is to invalidate
// *all* ancestors, which would likely involve shutting down the client due to
// an invalid justified checkpoint.
if !latest_valid_ancestor_is_descendant && node.root != head_block_root {
break;
} else if Some(hash) == latest_valid_ancestor_hash {
// If the `best_child` or `best_descendant` of the latest valid hash was
// invalidated, set those fields to `None`.
//
// In theory, an invalid `best_child` necessarily infers an invalid
// `best_descendant`. However, we check each variable independently to
// defend against errors which might result in an invalid block being set as
// head.
if node
.best_child
.map_or(false, |i| invalidated_indices.contains(&i))
{
node.best_child = None
}
if node
.best_descendant
.map_or(false, |i| invalidated_indices.contains(&i))
{
node.best_descendant = None
}
// It might be new knowledge that this block is valid, ensure that it and all
// ancestors are marked as valid.
self.propagate_execution_payload_validation(index)?;
break;
}
}
ExecutionStatus::Irrelevant(_) => break,
}
match &node.execution_status {
// It's illegal for an execution client to declare that some previously-valid block
// is now invalid. This is a consensus failure on their behalf.
ExecutionStatus::Valid(hash) => {
return Err(Error::ValidExecutionStatusBecameInvalid {
block_root: node.root,
payload_block_hash: *hash,
})
}
ExecutionStatus::Unknown(hash) => {
node.execution_status = ExecutionStatus::Invalid(*hash);
// It's impossible for an invalid block to lead to a "best" block, so set these
// fields to `None`.
//
// Failing to set these values will result in `Self::node_leads_to_viable_head`
// returning `false` for *valid* ancestors of invalid blocks.
node.best_child = None;
node.best_descendant = None;
}
// The block is already invalid, but keep going backwards to ensure all ancestors
// are updated.
ExecutionStatus::Invalid(_) => (),
// This block is pre-merge, therefore it has no execution status. Nor do its
// ancestors.
ExecutionStatus::Irrelevant(_) => break,
}
invalidated_indices.insert(index);
if let Some(parent_index) = node.parent {
index = parent_index
} else {
// The root of the block tree has been reached (aka the finalized block), without
// matching `latest_valid_ancestor_hash`. It's not possible or useful to go any
// further back: the finalized checkpoint is invalid so all is lost!
break;
}
}
/*
* Step 2:
*
* Start at either the `latest_valid_ancestor` or the `head_block_root` and iterate
* *forwards* to invalidate all descendants of all blocks in `invalidated_indices`.
*/
let starting_block_root = latest_valid_ancestor_root
.filter(|_| latest_valid_ancestor_is_descendant)
.unwrap_or(head_block_root);
let latest_valid_ancestor_index = *self
.indices
.get(&starting_block_root)
.ok_or(Error::NodeUnknown(starting_block_root))?;
let first_potential_descendant = latest_valid_ancestor_index + 1;
// Collect all *descendants* which have been declared invalid since they're the descendant of a block
// with an invalid execution payload.
for index in first_potential_descendant..self.nodes.len() {
let node = self
.nodes
.get_mut(index)
.ok_or(Error::InvalidNodeIndex(index))?;
if let Some(parent_index) = node.parent {
if invalidated_indices.contains(&parent_index) {
match &node.execution_status {
ExecutionStatus::Valid(hash) => {
return Err(Error::ValidExecutionStatusBecameInvalid {
block_root: node.root,
payload_block_hash: *hash,
})
}
ExecutionStatus::Unknown(hash) | ExecutionStatus::Invalid(hash) => {
node.execution_status = ExecutionStatus::Invalid(*hash)
}
ExecutionStatus::Irrelevant(_) => {
return Err(Error::IrrelevantDescendant {
block_root: node.root,
})
}
}
invalidated_indices.insert(index);
}
}
}
Ok(())
}
/// Follows the best-descendant links to find the best-block (i.e., head-block).
///
/// ## Notes
@@ -320,6 +555,19 @@ impl ProtoArray {
.get(justified_index)
.ok_or(Error::InvalidJustifiedIndex(justified_index))?;
// Since there are no valid descendants of a justified block with an invalid execution
// payload, there would be no head to choose from.
//
// Fork choice is effectively broken until a new justified root is set. It might not be
// practically possible to set a new justified root if we are unable to find a new head.
//
// This scenario is *unsupported*. It represents a serious consensus failure.
if justified_node.execution_status.is_invalid() {
return Err(Error::InvalidJustifiedCheckpointExecutionStatus {
justified_root: *justified_root,
});
}
let best_descendant_index = justified_node.best_descendant.unwrap_or(justified_index);
let best_node = self
@@ -537,6 +785,10 @@ impl ProtoArray {
/// Any node that has a different finalized or justified epoch should not be viable for the
/// head.
fn node_is_viable_for_head(&self, node: &ProtoNode) -> bool {
if node.execution_status.is_invalid() {
return false;
}
if let (Some(node_justified_checkpoint), Some(node_finalized_checkpoint)) =
(node.justified_checkpoint, node.finalized_checkpoint)
{
@@ -568,6 +820,42 @@ impl ProtoArray {
self.iter_nodes(block_root)
.map(|node| (node.root, node.slot))
}
/// Returns `true` if the `descendant_root` has an ancestor with `ancestor_root`. Always
/// returns `false` if either input root is unknown.
///
/// ## Notes
///
/// Still returns `true` if `ancestor_root` is known and `ancestor_root == descendant_root`.
pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool {
self.indices
.get(&ancestor_root)
.and_then(|ancestor_index| self.nodes.get(*ancestor_index))
.and_then(|ancestor| {
self.iter_block_roots(&descendant_root)
.take_while(|(_root, slot)| *slot >= ancestor.slot)
.find(|(_root, slot)| *slot == ancestor.slot)
.map(|(root, _slot)| root == ancestor_root)
})
.unwrap_or(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(
&self,
block_hash: &ExecutionBlockHash,
) -> Option<Hash256> {
self.nodes
.iter()
.rev()
.find(|node| {
node.execution_status
.block_hash()
.map_or(false, |node_block_hash| node_block_hash == *block_hash)
})
.map(|node| node.root)
}
}
/// A helper method to calculate the proposer boost based on the given `validator_balances`.

View File

@@ -5,7 +5,10 @@ use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use std::collections::HashMap;
use types::{AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, Hash256, Slot};
use types::{
AttestationShufflingId, ChainSpec, Checkpoint, Epoch, EthSpec, ExecutionBlockHash, Hash256,
Slot,
};
pub const DEFAULT_PRUNE_THRESHOLD: usize = 256;
@@ -21,11 +24,11 @@ pub struct VoteTracker {
#[ssz(enum_behaviour = "union")]
pub enum ExecutionStatus {
/// An EL has determined that the payload is valid.
Valid(Hash256),
Valid(ExecutionBlockHash),
/// An EL has determined that the payload is invalid.
Invalid(Hash256),
Invalid(ExecutionBlockHash),
/// An EL has not yet verified the execution payload.
Unknown(Hash256),
Unknown(ExecutionBlockHash),
/// The block is either prior to the merge fork, or after the merge fork but before the terminal
/// PoW block has been found.
///
@@ -41,7 +44,7 @@ impl ExecutionStatus {
ExecutionStatus::Irrelevant(false)
}
pub fn block_hash(&self) -> Option<Hash256> {
pub fn block_hash(&self) -> Option<ExecutionBlockHash> {
match self {
ExecutionStatus::Valid(hash)
| ExecutionStatus::Invalid(hash)
@@ -49,6 +52,37 @@ impl ExecutionStatus {
ExecutionStatus::Irrelevant(_) => None,
}
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has a valid payload
pub fn is_valid(&self) -> bool {
matches!(self, ExecutionStatus::Valid(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has a payload that has not yet been verified by an EL.
pub fn is_not_verified(&self) -> bool {
matches!(self, ExecutionStatus::Unknown(_))
}
/// Returns `true` if the block:
///
/// - Has execution enabled
/// - Has an invalid payload.
pub fn is_invalid(&self) -> bool {
matches!(self, ExecutionStatus::Invalid(_))
}
/// Returns `true` if the block:
///
/// - Does not have execution enabled (before or after Bellatrix fork)
pub fn is_irrelevant(&self) -> bool {
matches!(self, ExecutionStatus::Irrelevant(_))
}
}
/// A block that is to be applied to the fork choice.
@@ -150,6 +184,17 @@ impl ProtoArrayForkChoice {
})
}
/// See `ProtoArray::propagate_execution_payload_invalidation` for documentation.
pub fn process_execution_payload_invalidation(
&mut self,
head_block_root: Hash256,
latest_valid_ancestor_root: Option<ExecutionBlockHash>,
) -> Result<(), String> {
self.proto_array
.propagate_execution_payload_invalidation(head_block_root, latest_valid_ancestor_root)
.map_err(|e| format!("Failed to process invalid payload: {:?}", e))
}
pub fn process_attestation(
&mut self,
validator_index: usize,
@@ -267,25 +312,19 @@ impl ProtoArrayForkChoice {
}
}
/// Returns `true` if the `descendant_root` has an ancestor with `ancestor_root`. Always
/// returns `false` if either input roots are unknown.
///
/// ## Notes
///
/// Still returns `true` if `ancestor_root` is known and `ancestor_root == descendant_root`.
/// Returns the weight of a given block.
pub fn get_weight(&self, block_root: &Hash256) -> Option<u64> {
let block_index = self.proto_array.indices.get(block_root)?;
self.proto_array
.nodes
.get(*block_index)
.map(|node| node.weight)
}
/// See `ProtoArray` documentation.
pub fn is_descendant(&self, ancestor_root: Hash256, descendant_root: Hash256) -> bool {
self.proto_array
.indices
.get(&ancestor_root)
.and_then(|ancestor_index| self.proto_array.nodes.get(*ancestor_index))
.and_then(|ancestor| {
self.proto_array
.iter_block_roots(&descendant_root)
.take_while(|(_root, slot)| *slot >= ancestor.slot)
.find(|(_root, slot)| *slot == ancestor.slot)
.map(|(root, _slot)| root == ancestor_root)
})
.unwrap_or(false)
.is_descendant(ancestor_root, descendant_root)
}
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {

View File

@@ -58,8 +58,8 @@ pub enum BlockProcessingError {
InconsistentBlockFork(InconsistentFork),
InconsistentStateFork(InconsistentFork),
ExecutionHashChainIncontiguous {
expected: Hash256,
found: Hash256,
expected: ExecutionBlockHash,
found: ExecutionBlockHash,
},
ExecutionRandaoMismatch {
expected: Hash256,

View File

@@ -144,7 +144,7 @@ pub struct ChainSpec {
/// The Merge fork epoch is optional, with `None` representing "Merge never happens".
pub bellatrix_fork_epoch: Option<Epoch>,
pub terminal_total_difficulty: Uint256,
pub terminal_block_hash: Hash256,
pub terminal_block_hash: ExecutionBlockHash,
pub terminal_block_hash_activation_epoch: Epoch,
/*
@@ -549,7 +549,7 @@ impl ChainSpec {
// `Uint256::MAX` which is `2*256- 1`.
.checked_add(Uint256::one())
.expect("addition does not overflow"),
terminal_block_hash: Hash256::zero(),
terminal_block_hash: ExecutionBlockHash::zero(),
terminal_block_hash_activation_epoch: Epoch::new(u64::MAX),
/*
@@ -746,7 +746,7 @@ impl ChainSpec {
// `Uint256::MAX` which is `2*256- 1`.
.checked_add(Uint256::one())
.expect("addition does not overflow"),
terminal_block_hash: Hash256::zero(),
terminal_block_hash: ExecutionBlockHash::zero(),
terminal_block_hash_activation_epoch: Epoch::new(u64::MAX),
/*
@@ -787,7 +787,7 @@ pub struct Config {
pub terminal_total_difficulty: Uint256,
// TODO(merge): remove this default
#[serde(default = "default_terminal_block_hash")]
pub terminal_block_hash: Hash256,
pub terminal_block_hash: ExecutionBlockHash,
// TODO(merge): remove this default
#[serde(default = "default_terminal_block_hash_activation_epoch")]
pub terminal_block_hash_activation_epoch: Epoch,
@@ -870,8 +870,8 @@ const fn default_terminal_total_difficulty() -> Uint256 {
])
}
fn default_terminal_block_hash() -> Hash256 {
Hash256::zero()
fn default_terminal_block_hash() -> ExecutionBlockHash {
ExecutionBlockHash::zero()
}
fn default_terminal_block_hash_activation_epoch() -> Epoch {

View File

@@ -0,0 +1,101 @@
use crate::test_utils::TestRandom;
use crate::Hash256;
use rand::RngCore;
use serde_derive::{Deserialize, Serialize};
use ssz::{Decode, DecodeError, Encode};
use std::fmt;
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(transparent)]
pub struct ExecutionBlockHash(Hash256);
impl ExecutionBlockHash {
pub fn zero() -> Self {
Self(Hash256::zero())
}
pub fn repeat_byte(b: u8) -> Self {
Self(Hash256::repeat_byte(b))
}
pub fn from_root(root: Hash256) -> Self {
Self(root)
}
pub fn into_root(self) -> Hash256 {
self.0
}
}
impl Encode for ExecutionBlockHash {
fn is_ssz_fixed_len() -> bool {
<Hash256 as Encode>::is_ssz_fixed_len()
}
fn ssz_fixed_len() -> usize {
<Hash256 as Encode>::ssz_fixed_len()
}
fn ssz_bytes_len(&self) -> usize {
self.0.ssz_bytes_len()
}
fn ssz_append(&self, buf: &mut Vec<u8>) {
self.0.ssz_append(buf)
}
}
impl Decode for ExecutionBlockHash {
fn is_ssz_fixed_len() -> bool {
<Hash256 as Decode>::is_ssz_fixed_len()
}
fn ssz_fixed_len() -> usize {
<Hash256 as Decode>::ssz_fixed_len()
}
fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
Hash256::from_ssz_bytes(bytes).map(Self)
}
}
impl tree_hash::TreeHash for ExecutionBlockHash {
fn tree_hash_type() -> tree_hash::TreeHashType {
Hash256::tree_hash_type()
}
fn tree_hash_packed_encoding(&self) -> Vec<u8> {
self.0.tree_hash_packed_encoding()
}
fn tree_hash_packing_factor() -> usize {
Hash256::tree_hash_packing_factor()
}
fn tree_hash_root(&self) -> tree_hash::Hash256 {
self.0.tree_hash_root()
}
}
impl TestRandom for ExecutionBlockHash {
fn random_for_test(rng: &mut impl RngCore) -> Self {
Self(Hash256::random_for_test(rng))
}
}
impl std::str::FromStr for ExecutionBlockHash {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Hash256::from_str(s)
.map(Self)
.map_err(|e| format!("{:?}", e))
}
}
impl fmt::Display for ExecutionBlockHash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

View File

@@ -15,7 +15,7 @@ pub type Transaction<T> = VariableList<u8, T>;
#[derivative(PartialEq, Hash(bound = "T: EthSpec"))]
#[serde(bound = "T: EthSpec")]
pub struct ExecutionPayload<T: EthSpec> {
pub parent_hash: Hash256,
pub parent_hash: ExecutionBlockHash,
pub fee_recipient: Address,
pub state_root: Hash256,
pub receipts_root: Hash256,
@@ -34,7 +34,7 @@ pub struct ExecutionPayload<T: EthSpec> {
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
#[serde(with = "eth2_serde_utils::quoted_u256")]
pub base_fee_per_gas: Uint256,
pub block_hash: Hash256,
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions:
VariableList<Transaction<T::MaxBytesPerTransaction>, T::MaxTransactionsPerPayload>,

View File

@@ -9,7 +9,7 @@ use tree_hash_derive::TreeHash;
Default, Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
)]
pub struct ExecutionPayloadHeader<T: EthSpec> {
pub parent_hash: Hash256,
pub parent_hash: ExecutionBlockHash,
pub fee_recipient: Address,
pub state_root: Hash256,
pub receipts_root: Hash256,
@@ -28,7 +28,7 @@ pub struct ExecutionPayloadHeader<T: EthSpec> {
pub extra_data: VariableList<u8, T::MaxExtraDataBytes>,
#[serde(with = "eth2_serde_utils::quoted_u256")]
pub base_fee_per_gas: Uint256,
pub block_hash: Hash256,
pub block_hash: ExecutionBlockHash,
pub transactions_root: Hash256,
}

View File

@@ -37,6 +37,7 @@ pub mod deposit_message;
pub mod enr_fork_id;
pub mod eth1_data;
pub mod eth_spec;
pub mod execution_block_hash;
pub mod execution_payload;
pub mod execution_payload_header;
pub mod fork;
@@ -113,6 +114,7 @@ pub use crate::deposit_message::DepositMessage;
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_header::ExecutionPayloadHeader;
pub use crate::fork::Fork;