Restrict fork choice getters to finalized blocks (#1475)
## Issue Addressed - Resolves #1451 ## Proposed Changes - Restricts the `contains_block` and `contains_block` so they only indicate a block is present if it descends from the finalized root. This helps to ensure that fork choice never points to a block that has been pruned from the database. - Resolves #1451 - Before importing a block, double-check that its parent is known and a descendant of the finalized root. - Split a big, monolithic block verification test into smaller tests. ## Additional Notes I suspect there would be a craftier way to do the `is_descendant_of_finalized` check, but we're a bit tight on time now and we can optimize later if it starts showing in benches. ## TODO - [x] Tests
This commit is contained in:
parent
b0a3731fff
commit
619ad106cf
@ -3,8 +3,9 @@ use crate::attestation_verification::{
|
|||||||
VerifiedUnaggregatedAttestation,
|
VerifiedUnaggregatedAttestation,
|
||||||
};
|
};
|
||||||
use crate::block_verification::{
|
use crate::block_verification::{
|
||||||
check_block_relevancy, get_block_root, signature_verify_chain_segment, BlockError,
|
check_block_is_finalized_descendant, check_block_relevancy, get_block_root,
|
||||||
FullyVerifiedBlock, GossipVerifiedBlock, IntoFullyVerifiedBlock,
|
signature_verify_chain_segment, BlockError, FullyVerifiedBlock, GossipVerifiedBlock,
|
||||||
|
IntoFullyVerifiedBlock,
|
||||||
};
|
};
|
||||||
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
||||||
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||||
@ -1214,6 +1215,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
// However, we will potentially get a `ParentUnknown` on a later block. The sync
|
// However, we will potentially get a `ParentUnknown` on a later block. The sync
|
||||||
// protocol will need to ensure this is handled gracefully.
|
// protocol will need to ensure this is handled gracefully.
|
||||||
Err(BlockError::WouldRevertFinalizedSlot { .. }) => continue,
|
Err(BlockError::WouldRevertFinalizedSlot { .. }) => continue,
|
||||||
|
// The block has a known parent that does not descend from the finalized block.
|
||||||
|
// There is no need to process this block or any children.
|
||||||
|
Err(BlockError::NotFinalizedDescendant { block_parent_root }) => {
|
||||||
|
return ChainSegmentResult::Failed {
|
||||||
|
imported_blocks,
|
||||||
|
error: BlockError::NotFinalizedDescendant { block_parent_root },
|
||||||
|
}
|
||||||
|
}
|
||||||
// If there was an error whilst determining if the block was invalid, return that
|
// If there was an error whilst determining if the block was invalid, return that
|
||||||
// error.
|
// error.
|
||||||
Err(BlockError::BeaconChainError(e)) => {
|
Err(BlockError::BeaconChainError(e)) => {
|
||||||
@ -1415,7 +1424,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
fully_verified_block: FullyVerifiedBlock<T>,
|
fully_verified_block: FullyVerifiedBlock<T>,
|
||||||
) -> Result<Hash256, BlockError<T::EthSpec>> {
|
) -> Result<Hash256, BlockError<T::EthSpec>> {
|
||||||
let signed_block = fully_verified_block.block;
|
let signed_block = fully_verified_block.block;
|
||||||
let block = &signed_block.message;
|
|
||||||
let block_root = fully_verified_block.block_root;
|
let block_root = fully_verified_block.block_root;
|
||||||
let state = fully_verified_block.state;
|
let state = fully_verified_block.state;
|
||||||
let parent_block = fully_verified_block.parent_block;
|
let parent_block = fully_verified_block.parent_block;
|
||||||
@ -1427,7 +1435,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
// Iterate through the attestations in the block and register them as an "observed
|
// Iterate through the attestations in the block and register them as an "observed
|
||||||
// attestation". This will stop us from propagating them on the gossip network.
|
// attestation". This will stop us from propagating them on the gossip network.
|
||||||
for a in &block.body.attestations {
|
for a in &signed_block.message.body.attestations {
|
||||||
match self.observed_attestations.observe_attestation(a, None) {
|
match self.observed_attestations.observe_attestation(a, None) {
|
||||||
// If the observation was successful or if the slot for the attestation was too
|
// If the observation was successful or if the slot for the attestation was too
|
||||||
// low, continue.
|
// low, continue.
|
||||||
@ -1477,6 +1485,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
let mut fork_choice = self.fork_choice.write();
|
let mut fork_choice = self.fork_choice.write();
|
||||||
|
|
||||||
|
// Do not import a block that doesn't descend from the finalized root.
|
||||||
|
let signed_block =
|
||||||
|
check_block_is_finalized_descendant::<T, _>(signed_block, &fork_choice, &self.store)?;
|
||||||
|
let block = &signed_block.message;
|
||||||
|
|
||||||
// Register the new block with the fork choice service.
|
// Register the new block with the fork choice service.
|
||||||
{
|
{
|
||||||
let _fork_choice_block_timer =
|
let _fork_choice_block_timer =
|
||||||
|
@ -48,6 +48,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
metrics, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot,
|
metrics, BeaconChain, BeaconChainError, BeaconChainTypes, BeaconSnapshot,
|
||||||
};
|
};
|
||||||
|
use fork_choice::{ForkChoice, ForkChoiceStore};
|
||||||
use parking_lot::RwLockReadGuard;
|
use parking_lot::RwLockReadGuard;
|
||||||
use slog::{error, Logger};
|
use slog::{error, Logger};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
@ -62,7 +63,7 @@ use std::borrow::Cow;
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use store::{Error as DBError, HotStateSummary, StoreOp};
|
use store::{Error as DBError, HotColdDB, HotStateSummary, StoreOp};
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::{
|
use types::{
|
||||||
BeaconBlock, BeaconState, BeaconStateError, ChainSpec, CloneConfig, EthSpec, Hash256,
|
BeaconBlock, BeaconState, BeaconStateError, ChainSpec, CloneConfig, EthSpec, Hash256,
|
||||||
@ -118,6 +119,13 @@ pub enum BlockError<T: EthSpec> {
|
|||||||
block_slot: Slot,
|
block_slot: Slot,
|
||||||
finalized_slot: Slot,
|
finalized_slot: Slot,
|
||||||
},
|
},
|
||||||
|
/// The block conflicts with finalization, no need to propagate.
|
||||||
|
///
|
||||||
|
/// ## Peer scoring
|
||||||
|
///
|
||||||
|
/// It's unclear if this block is valid, but it conflicts with finality and shouldn't be
|
||||||
|
/// imported.
|
||||||
|
NotFinalizedDescendant { block_parent_root: Hash256 },
|
||||||
/// Block is already known, no need to re-import.
|
/// Block is already known, no need to re-import.
|
||||||
///
|
///
|
||||||
/// ## Peer scoring
|
/// ## Peer scoring
|
||||||
@ -397,6 +405,15 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not process a block that doesn't descend from the finalized root.
|
||||||
|
//
|
||||||
|
// We check this *before* we load the parent so that we can return a more detailed error.
|
||||||
|
let block = check_block_is_finalized_descendant::<T, _>(
|
||||||
|
block,
|
||||||
|
&chain.fork_choice.read(),
|
||||||
|
&chain.store,
|
||||||
|
)?;
|
||||||
|
|
||||||
let (mut parent, block) = load_parent(block, chain)?;
|
let (mut parent, block) = load_parent(block, chain)?;
|
||||||
let block_root = get_block_root(&block);
|
let block_root = get_block_root(&block);
|
||||||
|
|
||||||
@ -779,6 +796,36 @@ fn check_block_against_finalized_slot<T: BeaconChainTypes>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Ok(block)` if the block descends from the finalized root.
|
||||||
|
pub fn check_block_is_finalized_descendant<T: BeaconChainTypes, F: ForkChoiceStore<T::EthSpec>>(
|
||||||
|
block: SignedBeaconBlock<T::EthSpec>,
|
||||||
|
fork_choice: &ForkChoice<F, T::EthSpec>,
|
||||||
|
store: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
|
||||||
|
) -> Result<SignedBeaconBlock<T::EthSpec>, BlockError<T::EthSpec>> {
|
||||||
|
if fork_choice.is_descendant_of_finalized(block.parent_root()) {
|
||||||
|
Ok(block)
|
||||||
|
} else {
|
||||||
|
// If fork choice does *not* consider the parent to be a descendant of the finalized block,
|
||||||
|
// then there are two more cases:
|
||||||
|
//
|
||||||
|
// 1. We have the parent stored in our database. Because fork-choice has confirmed the
|
||||||
|
// parent is *not* in our post-finalization DAG, all other blocks must be either
|
||||||
|
// pre-finalization or conflicting with finalization.
|
||||||
|
// 2. The parent is unknown to us, we probably want to download it since it might actually
|
||||||
|
// descend from the finalized root.
|
||||||
|
if store
|
||||||
|
.item_exists::<SignedBeaconBlock<T::EthSpec>>(&block.parent_root())
|
||||||
|
.map_err(|e| BlockError::BeaconChainError(e.into()))?
|
||||||
|
{
|
||||||
|
Err(BlockError::NotFinalizedDescendant {
|
||||||
|
block_parent_root: block.parent_root(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(BlockError::ParentUnknown(Box::new(block)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs simple, cheap checks to ensure that the block is relevant to be 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
|
/// `Ok(block_root)` is returned if the block passes these checks and should progress with
|
||||||
|
@ -18,8 +18,9 @@ use types::{
|
|||||||
type E = MainnetEthSpec;
|
type E = MainnetEthSpec;
|
||||||
|
|
||||||
// Should ideally be divisible by 3.
|
// Should ideally be divisible by 3.
|
||||||
pub const VALIDATOR_COUNT: usize = 24;
|
const VALIDATOR_COUNT: usize = 24;
|
||||||
pub const CHAIN_SEGMENT_LENGTH: usize = 64 * 5;
|
const CHAIN_SEGMENT_LENGTH: usize = 64 * 5;
|
||||||
|
const BLOCK_INDICES: &[usize] = &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT_LENGTH - 1];
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// A cached set of keys.
|
/// A cached set of keys.
|
||||||
@ -272,17 +273,73 @@ fn chain_segment_non_linear_slots() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_invalid_signature(
|
||||||
|
harness: &BeaconChainHarness<HarnessType<E>>,
|
||||||
|
block_index: usize,
|
||||||
|
snapshots: &[BeaconSnapshot<E>],
|
||||||
|
item: &str,
|
||||||
|
) {
|
||||||
|
let blocks = snapshots
|
||||||
|
.iter()
|
||||||
|
.map(|snapshot| snapshot.beacon_block.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Ensure the block will be rejected if imported in a chain segment.
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
harness
|
||||||
|
.chain
|
||||||
|
.process_chain_segment(blocks)
|
||||||
|
.into_block_error(),
|
||||||
|
Err(BlockError::InvalidSignature)
|
||||||
|
),
|
||||||
|
"should not import chain segment with an invalid {} signature",
|
||||||
|
item
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
||||||
|
let ancestor_blocks = CHAIN_SEGMENT
|
||||||
|
.iter()
|
||||||
|
.take(block_index)
|
||||||
|
.map(|snapshot| snapshot.beacon_block.clone())
|
||||||
|
.collect();
|
||||||
|
// We don't care if this fails, we just call this to ensure that all prior blocks have been
|
||||||
|
// imported prior to this test.
|
||||||
|
let _ = harness.chain.process_chain_segment(ancestor_blocks);
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
harness
|
||||||
|
.chain
|
||||||
|
.process_block(snapshots[block_index].beacon_block.clone()),
|
||||||
|
Err(BlockError::InvalidSignature)
|
||||||
|
),
|
||||||
|
"should not import individual block with an invalid {} signature",
|
||||||
|
item
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: we choose not to check gossip verification here. It only checks one signature
|
||||||
|
// (proposal) and that is already tested elsewhere in this file.
|
||||||
|
//
|
||||||
|
// It's not trivial to just check gossip verification since it will start refusing
|
||||||
|
// blocks as soon as it has seen one valid proposal signature for a given (validator,
|
||||||
|
// slot) tuple.
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_invalid_sigs_harness() -> BeaconChainHarness<HarnessType<E>> {
|
||||||
|
let harness = get_harness(VALIDATOR_COUNT);
|
||||||
|
harness
|
||||||
|
.chain
|
||||||
|
.slot_clock
|
||||||
|
.set_slot(CHAIN_SEGMENT.last().unwrap().beacon_block.slot().as_u64());
|
||||||
|
harness
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_signatures() {
|
fn invalid_signature_gossip_block() {
|
||||||
let mut checked_attestation = false;
|
for &block_index in BLOCK_INDICES {
|
||||||
|
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
||||||
for &block_index in &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT.len() - 1] {
|
let harness = get_invalid_sigs_harness();
|
||||||
let harness = get_harness(VALIDATOR_COUNT);
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
harness
|
snapshots[block_index].beacon_block.signature = junk_signature();
|
||||||
.chain
|
|
||||||
.slot_clock
|
|
||||||
.set_slot(CHAIN_SEGMENT.last().unwrap().beacon_block.slot().as_u64());
|
|
||||||
|
|
||||||
// Import all the ancestors before the `block_index` block.
|
// Import all the ancestors before the `block_index` block.
|
||||||
let ancestor_blocks = CHAIN_SEGMENT
|
let ancestor_blocks = CHAIN_SEGMENT
|
||||||
.iter()
|
.iter()
|
||||||
@ -294,75 +351,6 @@ fn invalid_signatures() {
|
|||||||
.process_chain_segment(ancestor_blocks)
|
.process_chain_segment(ancestor_blocks)
|
||||||
.into_block_error()
|
.into_block_error()
|
||||||
.expect("should import all blocks prior to the one being tested");
|
.expect("should import all blocks prior to the one being tested");
|
||||||
|
|
||||||
// For the given snapshots, test the following:
|
|
||||||
//
|
|
||||||
// - The `process_chain_segment` function returns `InvalidSignature`.
|
|
||||||
// - The `process_block` function returns `InvalidSignature` when importing the
|
|
||||||
// `SignedBeaconBlock` directly.
|
|
||||||
// - The `verify_block_for_gossip` function does _not_ return an error.
|
|
||||||
// - The `process_block` function returns `InvalidSignature` when verifying the
|
|
||||||
// `GossipVerifiedBlock`.
|
|
||||||
let assert_invalid_signature = |snapshots: &[BeaconSnapshot<E>], item: &str| {
|
|
||||||
let blocks = snapshots
|
|
||||||
.iter()
|
|
||||||
.map(|snapshot| snapshot.beacon_block.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Ensure the block will be rejected if imported in a chain segment.
|
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
harness
|
|
||||||
.chain
|
|
||||||
.process_chain_segment(blocks)
|
|
||||||
.into_block_error(),
|
|
||||||
Err(BlockError::InvalidSignature)
|
|
||||||
),
|
|
||||||
"should not import chain segment with an invalid {} signature",
|
|
||||||
item
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
harness
|
|
||||||
.chain
|
|
||||||
.process_block(snapshots[block_index].beacon_block.clone()),
|
|
||||||
Err(BlockError::InvalidSignature)
|
|
||||||
),
|
|
||||||
"should not import individual block with an invalid {} signature",
|
|
||||||
item
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: we choose not to check gossip verification here. It only checks one signature
|
|
||||||
// (proposal) and that is already tested elsewhere in this file.
|
|
||||||
//
|
|
||||||
// It's not trivial to just check gossip verification since it will start refusing
|
|
||||||
// blocks as soon as it has seen one valid proposal signature for a given (validator,
|
|
||||||
// slot) tuple.
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Block proposal
|
|
||||||
*/
|
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
|
||||||
snapshots[block_index].beacon_block.signature = junk_signature();
|
|
||||||
let blocks = snapshots
|
|
||||||
.iter()
|
|
||||||
.map(|snapshot| snapshot.beacon_block.clone())
|
|
||||||
.collect();
|
|
||||||
// Ensure the block will be rejected if imported in a chain segment.
|
|
||||||
assert!(
|
|
||||||
matches!(
|
|
||||||
harness
|
|
||||||
.chain
|
|
||||||
.process_chain_segment(blocks)
|
|
||||||
.into_block_error(),
|
|
||||||
Err(BlockError::InvalidSignature)
|
|
||||||
),
|
|
||||||
"should not import chain segment with an invalid gossip signature",
|
|
||||||
);
|
|
||||||
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(
|
matches!(
|
||||||
harness
|
harness
|
||||||
@ -372,10 +360,37 @@ fn invalid_signatures() {
|
|||||||
),
|
),
|
||||||
"should not import individual block with an invalid gossip signature",
|
"should not import individual block with an invalid gossip signature",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
#[test]
|
||||||
* Randao reveal
|
fn invalid_signature_block_proposal() {
|
||||||
*/
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
|
snapshots[block_index].beacon_block.signature = junk_signature();
|
||||||
|
let blocks = snapshots
|
||||||
|
.iter()
|
||||||
|
.map(|snapshot| snapshot.beacon_block.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// Ensure the block will be rejected if imported in a chain segment.
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
harness
|
||||||
|
.chain
|
||||||
|
.process_chain_segment(blocks)
|
||||||
|
.into_block_error(),
|
||||||
|
Err(BlockError::InvalidSignature)
|
||||||
|
),
|
||||||
|
"should not import chain segment with an invalid block signature",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_signature_randao_reveal() {
|
||||||
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
snapshots[block_index]
|
snapshots[block_index]
|
||||||
.beacon_block
|
.beacon_block
|
||||||
@ -384,11 +399,14 @@ fn invalid_signatures() {
|
|||||||
.randao_reveal = junk_signature();
|
.randao_reveal = junk_signature();
|
||||||
update_parent_roots(&mut snapshots);
|
update_parent_roots(&mut snapshots);
|
||||||
update_proposal_signatures(&mut snapshots, &harness);
|
update_proposal_signatures(&mut snapshots, &harness);
|
||||||
assert_invalid_signature(&snapshots, "randao");
|
assert_invalid_signature(&harness, block_index, &snapshots, "randao");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
#[test]
|
||||||
* Proposer slashing
|
fn invalid_signature_proposer_slashing() {
|
||||||
*/
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
let proposer_slashing = ProposerSlashing {
|
let proposer_slashing = ProposerSlashing {
|
||||||
signed_header_1: SignedBeaconBlockHeader {
|
signed_header_1: SignedBeaconBlockHeader {
|
||||||
@ -409,11 +427,14 @@ fn invalid_signatures() {
|
|||||||
.expect("should update proposer slashing");
|
.expect("should update proposer slashing");
|
||||||
update_parent_roots(&mut snapshots);
|
update_parent_roots(&mut snapshots);
|
||||||
update_proposal_signatures(&mut snapshots, &harness);
|
update_proposal_signatures(&mut snapshots, &harness);
|
||||||
assert_invalid_signature(&snapshots, "proposer slashing");
|
assert_invalid_signature(&harness, block_index, &snapshots, "proposer slashing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
#[test]
|
||||||
* Attester slashing
|
fn invalid_signature_attester_slashing() {
|
||||||
*/
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
let indexed_attestation = IndexedAttestation {
|
let indexed_attestation = IndexedAttestation {
|
||||||
attesting_indices: vec![0].into(),
|
attesting_indices: vec![0].into(),
|
||||||
@ -445,11 +466,16 @@ fn invalid_signatures() {
|
|||||||
.expect("should update attester slashing");
|
.expect("should update attester slashing");
|
||||||
update_parent_roots(&mut snapshots);
|
update_parent_roots(&mut snapshots);
|
||||||
update_proposal_signatures(&mut snapshots, &harness);
|
update_proposal_signatures(&mut snapshots, &harness);
|
||||||
assert_invalid_signature(&snapshots, "attester slashing");
|
assert_invalid_signature(&harness, block_index, &snapshots, "attester slashing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
#[test]
|
||||||
* Attestation
|
fn invalid_signature_attestation() {
|
||||||
*/
|
let mut checked_attestation = false;
|
||||||
|
|
||||||
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
if let Some(attestation) = snapshots[block_index]
|
if let Some(attestation) = snapshots[block_index]
|
||||||
.beacon_block
|
.beacon_block
|
||||||
@ -461,15 +487,22 @@ fn invalid_signatures() {
|
|||||||
attestation.signature = junk_aggregate_signature();
|
attestation.signature = junk_aggregate_signature();
|
||||||
update_parent_roots(&mut snapshots);
|
update_parent_roots(&mut snapshots);
|
||||||
update_proposal_signatures(&mut snapshots, &harness);
|
update_proposal_signatures(&mut snapshots, &harness);
|
||||||
assert_invalid_signature(&snapshots, "attestation");
|
assert_invalid_signature(&harness, block_index, &snapshots, "attestation");
|
||||||
checked_attestation = true;
|
checked_attestation = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
assert!(
|
||||||
* Deposit
|
checked_attestation,
|
||||||
*
|
"the test should check an attestation signature"
|
||||||
* Note: an invalid deposit signature is permitted!
|
)
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_signature_deposit() {
|
||||||
|
for &block_index in BLOCK_INDICES {
|
||||||
|
// Note: an invalid deposit signature is permitted!
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
let deposit = Deposit {
|
let deposit = Deposit {
|
||||||
proof: vec![Hash256::zero(); DEPOSIT_TREE_DEPTH + 1].into(),
|
proof: vec![Hash256::zero(); DEPOSIT_TREE_DEPTH + 1].into(),
|
||||||
@ -503,10 +536,13 @@ fn invalid_signatures() {
|
|||||||
),
|
),
|
||||||
"should not throw an invalid signature error for a bad deposit signature"
|
"should not throw an invalid signature error for a bad deposit signature"
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
#[test]
|
||||||
* Voluntary exit
|
fn invalid_signature_exit() {
|
||||||
*/
|
for &block_index in BLOCK_INDICES {
|
||||||
|
let harness = get_invalid_sigs_harness();
|
||||||
let mut snapshots = CHAIN_SEGMENT.clone();
|
let mut snapshots = CHAIN_SEGMENT.clone();
|
||||||
let epoch = snapshots[block_index].beacon_state.current_epoch();
|
let epoch = snapshots[block_index].beacon_state.current_epoch();
|
||||||
snapshots[block_index]
|
snapshots[block_index]
|
||||||
@ -524,13 +560,8 @@ fn invalid_signatures() {
|
|||||||
.expect("should update deposit");
|
.expect("should update deposit");
|
||||||
update_parent_roots(&mut snapshots);
|
update_parent_roots(&mut snapshots);
|
||||||
update_proposal_signatures(&mut snapshots, &harness);
|
update_proposal_signatures(&mut snapshots, &harness);
|
||||||
assert_invalid_signature(&snapshots, "voluntary exit");
|
assert_invalid_signature(&harness, block_index, &snapshots, "voluntary exit");
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(
|
|
||||||
checked_attestation,
|
|
||||||
"the test should check an attestation signature"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwrap_err<T, E>(result: Result<T, E>) -> E {
|
fn unwrap_err<T, E>(result: Result<T, E>) -> E {
|
||||||
@ -641,6 +672,48 @@ fn block_gossip_verification() {
|
|||||||
"should not import a block with an invalid proposal signature"
|
"should not import a block with an invalid proposal signature"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test ensures that:
|
||||||
|
*
|
||||||
|
* Spec v0.12.2
|
||||||
|
*
|
||||||
|
* The block's parent (defined by block.parent_root) passes validation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let mut block = CHAIN_SEGMENT[block_index].beacon_block.clone();
|
||||||
|
let parent_root = Hash256::from_low_u64_be(42);
|
||||||
|
block.message.parent_root = parent_root;
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
unwrap_err(harness.chain.verify_block_for_gossip(block)),
|
||||||
|
BlockError::ParentUnknown(block)
|
||||||
|
if block.parent_root() == parent_root
|
||||||
|
),
|
||||||
|
"should not import a block for an unknown parent"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This test ensures that:
|
||||||
|
*
|
||||||
|
* Spec v0.12.2
|
||||||
|
*
|
||||||
|
* The current finalized_checkpoint is an ancestor of block -- i.e. get_ancestor(store,
|
||||||
|
* block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
|
||||||
|
* store.finalized_checkpoint.root
|
||||||
|
*/
|
||||||
|
|
||||||
|
let mut block = CHAIN_SEGMENT[block_index].beacon_block.clone();
|
||||||
|
let parent_root = CHAIN_SEGMENT[0].beacon_block_root;
|
||||||
|
block.message.parent_root = parent_root;
|
||||||
|
assert!(
|
||||||
|
matches!(
|
||||||
|
unwrap_err(harness.chain.verify_block_for_gossip(block)),
|
||||||
|
BlockError::NotFinalizedDescendant { block_parent_root }
|
||||||
|
if block_parent_root == parent_root
|
||||||
|
),
|
||||||
|
"should not import a block that conflicts with finality"
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This test ensures that:
|
* This test ensures that:
|
||||||
*
|
*
|
||||||
|
@ -738,14 +738,25 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the block is known.
|
/// Returns `true` if the block is known **and** a descendant of the finalized root.
|
||||||
pub fn contains_block(&self, block_root: &Hash256) -> bool {
|
pub fn contains_block(&self, block_root: &Hash256) -> bool {
|
||||||
self.proto_array.contains_block(block_root)
|
self.proto_array.contains_block(block_root) && self.is_descendant_of_finalized(*block_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `ProtoBlock` if the block is known.
|
/// 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> {
|
pub fn get_block(&self, block_root: &Hash256) -> Option<ProtoBlock> {
|
||||||
self.proto_array.get_block(block_root)
|
self.proto_array.get_block(block_root).filter(|block| {
|
||||||
|
// If available, use the parent_root to perform the lookup since it will involve one
|
||||||
|
// less lookup. This involves making the assumption that the finalized block will
|
||||||
|
// always have `block.parent_root` of `None`.
|
||||||
|
self.is_descendant_of_finalized(block.parent_root.unwrap_or(block.root))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
.is_descendant(self.fc_store.finalized_checkpoint().root, block_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the latest message for a given validator, if any.
|
/// Returns the latest message for a given validator, if any.
|
||||||
|
@ -198,6 +198,27 @@ 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`.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
|
pub fn latest_message(&self, validator_index: usize) -> Option<(Hash256, Epoch)> {
|
||||||
if validator_index < self.votes.0.len() {
|
if validator_index < self.votes.0.len() {
|
||||||
let vote = &self.votes.0[validator_index];
|
let vote = &self.votes.0[validator_index];
|
||||||
@ -309,6 +330,73 @@ mod test_compute_deltas {
|
|||||||
Hash256::from_low_u64_be(i as u64 + 1)
|
Hash256::from_low_u64_be(i as u64 + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finalized_descendant() {
|
||||||
|
let genesis_slot = Slot::new(0);
|
||||||
|
let genesis_epoch = Epoch::new(0);
|
||||||
|
|
||||||
|
let state_root = Hash256::from_low_u64_be(0);
|
||||||
|
let finalized_root = Hash256::from_low_u64_be(1);
|
||||||
|
let finalized_desc = Hash256::from_low_u64_be(2);
|
||||||
|
let not_finalized_desc = Hash256::from_low_u64_be(3);
|
||||||
|
let unknown = Hash256::from_low_u64_be(4);
|
||||||
|
|
||||||
|
let mut fc = ProtoArrayForkChoice::new(
|
||||||
|
genesis_slot,
|
||||||
|
state_root,
|
||||||
|
genesis_epoch,
|
||||||
|
genesis_epoch,
|
||||||
|
finalized_root,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Add block that is a finalized descendant.
|
||||||
|
fc.proto_array
|
||||||
|
.on_block(Block {
|
||||||
|
slot: genesis_slot + 1,
|
||||||
|
root: finalized_desc,
|
||||||
|
parent_root: Some(finalized_root),
|
||||||
|
state_root,
|
||||||
|
target_root: finalized_root,
|
||||||
|
justified_epoch: genesis_epoch,
|
||||||
|
finalized_epoch: genesis_epoch,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Add block that is *not* a finalized descendant.
|
||||||
|
fc.proto_array
|
||||||
|
.on_block(Block {
|
||||||
|
slot: genesis_slot + 1,
|
||||||
|
root: not_finalized_desc,
|
||||||
|
parent_root: None,
|
||||||
|
state_root,
|
||||||
|
target_root: finalized_root,
|
||||||
|
justified_epoch: genesis_epoch,
|
||||||
|
finalized_epoch: genesis_epoch,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(!fc.is_descendant(unknown, unknown));
|
||||||
|
assert!(!fc.is_descendant(unknown, finalized_root));
|
||||||
|
assert!(!fc.is_descendant(unknown, finalized_desc));
|
||||||
|
assert!(!fc.is_descendant(unknown, not_finalized_desc));
|
||||||
|
|
||||||
|
assert!(fc.is_descendant(finalized_root, finalized_root));
|
||||||
|
assert!(fc.is_descendant(finalized_root, finalized_desc));
|
||||||
|
assert!(!fc.is_descendant(finalized_root, not_finalized_desc));
|
||||||
|
assert!(!fc.is_descendant(finalized_root, 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));
|
||||||
|
assert!(!fc.is_descendant(finalized_desc, unknown));
|
||||||
|
|
||||||
|
assert!(fc.is_descendant(not_finalized_desc, not_finalized_desc));
|
||||||
|
assert!(!fc.is_descendant(not_finalized_desc, finalized_desc));
|
||||||
|
assert!(!fc.is_descendant(not_finalized_desc, finalized_root));
|
||||||
|
assert!(!fc.is_descendant(not_finalized_desc, unknown));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_hash() {
|
fn zero_hash() {
|
||||||
let validator_count: usize = 16;
|
let validator_count: usize = 16;
|
||||||
|
Loading…
Reference in New Issue
Block a user