mirror of
https://github.com/sigp/lighthouse.git
synced 2026-04-17 21:08:32 +00:00
add handling of failed batches that imported blocks (#996)
This commit is contained in:
@@ -79,6 +79,19 @@ pub enum AttestationType {
|
||||
Aggregated,
|
||||
}
|
||||
|
||||
/// The result of a chain segment processing.
|
||||
#[derive(Debug)]
|
||||
pub enum ChainSegmentResult {
|
||||
/// Processing this chain segment finished successfully.
|
||||
Successful { imported_blocks: usize },
|
||||
/// There was an error processing this chain segment. Before the error, some blocks could
|
||||
/// have been imported.
|
||||
Failed {
|
||||
imported_blocks: usize,
|
||||
error: BlockError,
|
||||
},
|
||||
}
|
||||
|
||||
/// The accepted clock drift for nodes gossiping blocks and attestations (spec v0.11.0). See:
|
||||
///
|
||||
/// https://github.com/ethereum/eth2.0-specs/blob/v0.11.0/specs/phase0/p2p-interface.md#configuration
|
||||
@@ -1307,8 +1320,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
pub fn process_chain_segment(
|
||||
&self,
|
||||
chain_segment: Vec<SignedBeaconBlock<T::EthSpec>>,
|
||||
) -> Result<Vec<Hash256>, BlockError> {
|
||||
) -> ChainSegmentResult {
|
||||
let mut filtered_chain_segment = Vec::with_capacity(chain_segment.len());
|
||||
let mut imported_blocks = 0;
|
||||
|
||||
// Produce a list of the parent root and slot of the child of each block.
|
||||
//
|
||||
@@ -1329,12 +1343,18 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// Without this check it would be possible to have a block verified using the
|
||||
// incorrect shuffling. That would be bad, mmkay.
|
||||
if block_root != *child_parent_root {
|
||||
return Err(BlockError::NonLinearParentRoots);
|
||||
return ChainSegmentResult::Failed {
|
||||
imported_blocks,
|
||||
error: BlockError::NonLinearParentRoots,
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure that the slots are strictly increasing throughout the chain segement.
|
||||
// Ensure that the slots are strictly increasing throughout the chain segment.
|
||||
if *child_slot <= block.slot() {
|
||||
return Err(BlockError::NonLinearSlots);
|
||||
return ChainSegmentResult::Failed {
|
||||
imported_blocks,
|
||||
error: BlockError::NonLinearSlots,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1348,7 +1368,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// If there was an error whilst determining if the block was invalid, return that
|
||||
// error.
|
||||
Err(BlockError::BeaconChainError(e)) => {
|
||||
return Err(BlockError::BeaconChainError(e))
|
||||
return ChainSegmentResult::Failed {
|
||||
imported_blocks,
|
||||
error: BlockError::BeaconChainError(e),
|
||||
}
|
||||
}
|
||||
// If the block was decided to be irrelevant for any other reason, don't include
|
||||
// this block or any of it's children in the filtered chain segment.
|
||||
@@ -1356,8 +1379,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut roots = Vec::with_capacity(filtered_chain_segment.len());
|
||||
|
||||
while !filtered_chain_segment.is_empty() {
|
||||
// Determine the epoch of the first block in the remaining segment.
|
||||
let start_epoch = filtered_chain_segment
|
||||
@@ -1383,15 +1404,31 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
std::mem::swap(&mut blocks, &mut filtered_chain_segment);
|
||||
|
||||
// Verify the signature of the blocks, returning early if the signature is invalid.
|
||||
let signature_verified_blocks = signature_verify_chain_segment(blocks, self)?;
|
||||
let signature_verified_blocks = match signature_verify_chain_segment(blocks, self) {
|
||||
Ok(blocks) => blocks,
|
||||
Err(error) => {
|
||||
return ChainSegmentResult::Failed {
|
||||
imported_blocks,
|
||||
error,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Import the blocks into the chain.
|
||||
for signature_verified_block in signature_verified_blocks {
|
||||
roots.push(self.process_block(signature_verified_block)?);
|
||||
match self.process_block(signature_verified_block) {
|
||||
Ok(_) => imported_blocks += 1,
|
||||
Err(error) => {
|
||||
return ChainSegmentResult::Failed {
|
||||
imported_blocks,
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(roots)
|
||||
ChainSegmentResult::Successful { imported_blocks }
|
||||
}
|
||||
|
||||
/// Returns `Ok(GossipVerifiedBlock)` if the supplied `block` should be forwarded onto the
|
||||
@@ -2024,7 +2061,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
Ok(dump)
|
||||
}
|
||||
|
||||
/// Gets the current EnrForkId.
|
||||
/// Gets the current `EnrForkId`.
|
||||
pub fn enr_fork_id(&self) -> EnrForkId {
|
||||
// If we are unable to read the slot clock we assume that it is prior to genesis and
|
||||
// therefore use the genesis slot.
|
||||
@@ -2081,3 +2118,12 @@ impl From<BeaconStateError> for Error {
|
||||
Error::BeaconStateError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainSegmentResult {
|
||||
pub fn to_block_error(self) -> Result<(), BlockError> {
|
||||
match self {
|
||||
ChainSegmentResult::Failed { error, .. } => Err(error),
|
||||
ChainSegmentResult::Successful { .. } => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ pub enum BlockError {
|
||||
InvalidSignature,
|
||||
/// The provided block is from an earlier slot than its parent.
|
||||
BlockIsNotLaterThanParent { block_slot: Slot, state_slot: Slot },
|
||||
/// At least one block in the chain segement did not have it's parent root set to the root of
|
||||
/// At least one block in the chain segment did not have it's parent root set to the root of
|
||||
/// the prior block.
|
||||
NonLinearParentRoots,
|
||||
/// The slots of the blocks in the chain segment were not strictly increasing. I.e., a child
|
||||
@@ -153,7 +153,7 @@ impl From<DBError> for BlockError {
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// The given `chain_segement` must span no more than two epochs, otherwise an error will be
|
||||
/// The given `chain_segment` must span no more than two epochs, otherwise an error will be
|
||||
/// returned.
|
||||
pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
|
||||
chain_segment: Vec<(Hash256, SignedBeaconBlock<T::EthSpec>)>,
|
||||
@@ -592,7 +592,7 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs simple, cheap checks to ensure that the block is relevant to imported.
|
||||
/// Performs simple, cheap checks to ensure that the block is relevant to be imported.
|
||||
///
|
||||
/// `Ok(block_root)` is returned if the block passes these checks and should progress with
|
||||
/// verification (viz., it is relevant).
|
||||
|
||||
@@ -21,7 +21,8 @@ mod timeout_rw_lock;
|
||||
mod validator_pubkey_cache;
|
||||
|
||||
pub use self::beacon_chain::{
|
||||
AttestationProcessingOutcome, AttestationType, BeaconChain, BeaconChainTypes, StateSkipConfig,
|
||||
AttestationProcessingOutcome, AttestationType, BeaconChain, BeaconChainTypes,
|
||||
ChainSegmentResult, StateSkipConfig,
|
||||
};
|
||||
pub use self::beacon_snapshot::BeaconSnapshot;
|
||||
pub use self::errors::{BeaconChainError, BlockProductionError};
|
||||
|
||||
@@ -121,11 +121,13 @@ fn chain_segment_full_segment() {
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(vec![])
|
||||
.to_block_error()
|
||||
.expect("should import empty chain segment");
|
||||
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(blocks.clone())
|
||||
.to_block_error()
|
||||
.expect("should import chain segment");
|
||||
|
||||
harness.chain.fork_choice().expect("should run fork choice");
|
||||
@@ -156,6 +158,7 @@ fn chain_segment_varying_chunk_size() {
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(chunk.to_vec())
|
||||
.to_block_error()
|
||||
.expect(&format!(
|
||||
"should import chain segment of len {}",
|
||||
chunk_size
|
||||
@@ -191,7 +194,10 @@ fn chain_segment_non_linear_parent_roots() {
|
||||
blocks.remove(2);
|
||||
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks.clone()),
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(blocks.clone())
|
||||
.to_block_error(),
|
||||
Err(BlockError::NonLinearParentRoots),
|
||||
"should not import chain with missing parent"
|
||||
);
|
||||
@@ -203,7 +209,10 @@ fn chain_segment_non_linear_parent_roots() {
|
||||
blocks[3].message.parent_root = Hash256::zero();
|
||||
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks.clone()),
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(blocks.clone())
|
||||
.to_block_error(),
|
||||
Err(BlockError::NonLinearParentRoots),
|
||||
"should not import chain with a broken parent root link"
|
||||
);
|
||||
@@ -225,7 +234,10 @@ fn chain_segment_non_linear_slots() {
|
||||
blocks[3].message.slot = Slot::new(0);
|
||||
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks.clone()),
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(blocks.clone())
|
||||
.to_block_error(),
|
||||
Err(BlockError::NonLinearSlots),
|
||||
"should not import chain with a parent that has a lower slot than its child"
|
||||
);
|
||||
@@ -238,7 +250,10 @@ fn chain_segment_non_linear_slots() {
|
||||
blocks[3].message.slot = blocks[2].message.slot;
|
||||
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks.clone()),
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(blocks.clone())
|
||||
.to_block_error(),
|
||||
Err(BlockError::NonLinearSlots),
|
||||
"should not import chain with a parent that has an equal slot to its child"
|
||||
);
|
||||
@@ -264,6 +279,7 @@ fn invalid_signatures() {
|
||||
harness
|
||||
.chain
|
||||
.process_chain_segment(ancestor_blocks)
|
||||
.to_block_error()
|
||||
.expect("should import all blocks prior to the one being tested");
|
||||
|
||||
// For the given snapshots, test the following:
|
||||
@@ -273,7 +289,7 @@ fn invalid_signatures() {
|
||||
// `SignedBeaconBlock` directly.
|
||||
// - The `verify_block_for_gossip` function does _not_ return an error.
|
||||
// - The `process_block` function returns `InvalidSignature` when verifying the
|
||||
// GossipVerifiedBlock.
|
||||
// `GossipVerifiedBlock`.
|
||||
let assert_invalid_signature = |snapshots: &[BeaconSnapshot<E>], item: &str| {
|
||||
let blocks = snapshots
|
||||
.iter()
|
||||
@@ -282,7 +298,7 @@ fn invalid_signatures() {
|
||||
|
||||
// Ensure the block will be rejected if imported in a chain segment.
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks),
|
||||
harness.chain.process_chain_segment(blocks).to_block_error(),
|
||||
Err(BlockError::InvalidSignature),
|
||||
"should not import chain segment with an invalid {} signature",
|
||||
item
|
||||
@@ -321,7 +337,7 @@ fn invalid_signatures() {
|
||||
.collect();
|
||||
// Ensure the block will be rejected if imported in a chain segment.
|
||||
assert_eq!(
|
||||
harness.chain.process_chain_segment(blocks),
|
||||
harness.chain.process_chain_segment(blocks).to_block_error(),
|
||||
Err(BlockError::InvalidSignature),
|
||||
"should not import chain segment with an invalid gossip signature",
|
||||
);
|
||||
@@ -455,7 +471,8 @@ fn invalid_signatures() {
|
||||
.map(|snapshot| snapshot.beacon_block.clone())
|
||||
.collect();
|
||||
assert!(
|
||||
harness.chain.process_chain_segment(blocks) != Err(BlockError::InvalidSignature),
|
||||
harness.chain.process_chain_segment(blocks).to_block_error()
|
||||
!= Err(BlockError::InvalidSignature),
|
||||
"should not throw an invalid signature error for a bad deposit signature"
|
||||
);
|
||||
|
||||
@@ -516,7 +533,7 @@ fn gossip_verification() {
|
||||
harness
|
||||
.chain
|
||||
.process_block(gossip_verified)
|
||||
.expect("should import valid gossip verfied block");
|
||||
.expect("should import valid gossip verified block");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user