mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-03 00:31:50 +00:00
* 1D PeerDAS prototype: Data format and Distribution (#5050) * Build and publish column sidecars. Add stubs for gossip. * Add blob column subnets * Add `BlobColumnSubnetId` and initial compute subnet logic. * Subscribe to blob column subnets. * Introduce `BLOB_COLUMN_SUBNET_COUNT` based on DAS configuration parameter changes. * Fix column sidecar type to use `VariableList` for data. * Fix lint errors. * Update types and naming to latest consensus-spec #3574. * Fix test and some cleanups. * Merge branch 'unstable' into das * Merge branch 'unstable' into das * Merge branch 'unstable' into das # Conflicts: # consensus/types/src/chain_spec.rs * Add `DataColumnSidecarsByRoot ` req/resp protocol (#5196) * Add stub for `DataColumnsByRoot` * Add basic implementation of serving RPC data column from DA checker. * Store data columns in early attester cache and blobs db. * Apply suggestions from code review Co-authored-by: Eitan Seri-Levi <eserilev@gmail.com> Co-authored-by: Jacob Kaufmann <jacobkaufmann18@gmail.com> * Fix build. * Store `DataColumnInfo` in database and various cleanups. * Update `DataColumnSidecar` ssz max size and remove panic code. --------- Co-authored-by: Eitan Seri-Levi <eserilev@gmail.com> Co-authored-by: Jacob Kaufmann <jacobkaufmann18@gmail.com> * feat: add DAS KZG in data col construction (#5210) * feat: add DAS KZG in data col construction * refactor data col sidecar construction * refactor: add data cols to GossipVerifiedBlockContents * Disable windows tests for `das` branch. (c-kzg doesn't build on windows) * Formatting and lint changes only. * refactor: remove iters in construction of data cols * Update vec capacity and error handling. * Add `data_column_sidecar_computation_seconds` metric. --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Merge branch 'unstable' into das # Conflicts: # .github/workflows/test-suite.yml # beacon_node/lighthouse_network/src/types/topics.rs * fix: update data col subnet count from 64 to 32 (#5413) * feat: add peerdas custody field to ENR (#5409) * feat: add peerdas custody field to ENR * add hash prefix step in subnet computation * refactor test and fix possible u64 overflow * default to min custody value if not present in ENR * Merge branch 'unstable' into das * Merge branch 'unstable' into das-unstable-merge-0415 # Conflicts: # Cargo.lock # beacon_node/beacon_chain/src/data_availability_checker.rs # beacon_node/beacon_chain/src/data_availability_checker/availability_view.rs # beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs # beacon_node/beacon_chain/src/data_availability_checker/processing_cache.rs # beacon_node/lighthouse_network/src/rpc/methods.rs # beacon_node/network/src/network_beacon_processor/mod.rs # beacon_node/network/src/sync/block_lookups/tests.rs # crypto/kzg/Cargo.toml * Merge remote-tracking branch 'sigp/unstable' into das * Merge remote-tracking branch 'sigp/unstable' into das * Fix merge conflicts. * Send custody data column to `DataAvailabilityChecker` for determining block importability (#5570) * Only import custody data columns after publishing a block. * Add `subscribe-all-data-column-subnets` and pass custody column count to `availability_cache`. * Add custody requirement checks to `availability_cache`. * Fix config not being passed to DAChecker and add more logging. * Introduce `peer_das_epoch` and make blobs and columns mutually exclusive. * Add DA filter for PeerDAS. * Fix data availability check and use test_logger in tests. * Fix subscribe to all data column subnets not working correctly. * Fix tests. * Only publish column sidecars if PeerDAS is activated. Add `PEER_DAS_EPOCH` chain spec serialization. * Remove unused data column index in `OverflowKey`. * Fix column sidecars incorrectly produced when there are no blobs. * Re-instate index to `OverflowKey::DataColumn` and downgrade noisy debug log to `trace`. * DAS sampling on sync (#5616) * Data availability sampling on sync * Address @jimmygchen review * Trigger sampling * Address some review comments and only send `SamplingBlock` sync message after PEER_DAS_EPOCH. --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Merge branch 'unstable' into das # Conflicts: # Cargo.lock # Cargo.toml # beacon_node/beacon_chain/src/block_verification.rs # beacon_node/http_api/src/publish_blocks.rs # beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs # beacon_node/lighthouse_network/src/rpc/protocol.rs # beacon_node/lighthouse_network/src/types/pubsub.rs # beacon_node/network/src/sync/block_lookups/single_block_lookup.rs # beacon_node/store/src/hot_cold_store.rs # consensus/types/src/beacon_state.rs # consensus/types/src/chain_spec.rs # consensus/types/src/eth_spec.rs * Merge branch 'unstable' into das * Re-process early sampling requests (#5569) * Re-process early sampling requests # Conflicts: # beacon_node/beacon_processor/src/work_reprocessing_queue.rs # beacon_node/lighthouse_network/src/rpc/methods.rs # beacon_node/network/src/network_beacon_processor/rpc_methods.rs * Update beacon_node/beacon_processor/src/work_reprocessing_queue.rs Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Add missing var * Beta compiler fixes and small typo fixes. * Remove duplicate method. --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Merge remote-tracking branch 'sigp/unstable' into das * Fix merge conflict. * Add data columns by root to currently supported protocol list (#5678) * Add data columns by root to currently supported protocol list. * Add missing data column by roots handling. * Merge branch 'unstable' into das # Conflicts: # Cargo.lock # Cargo.toml # beacon_node/network/src/sync/block_lookups/tests.rs # beacon_node/network/src/sync/manager.rs * Fix simulator tests on `das` branch (#5731) * Bump genesis delay in sim tests as KZG setup takes longer for DAS. * Fix incorrect YAML spacing. * DataColumnByRange boilerplate (#5353) * add boilerplate * fmt * PeerDAS custody lookup sync (#5684) * Implement custody sync * Lint * Fix tests * Fix rebase issue * Add data column kzg verification and update `c-kzg`. (#5701) * Add data column kzg verification and update `c-kzg`. * Fix incorrect `Cell` size. * Add kzg verification on rpc blocks. * Add kzg verification on rpc data columns. * Rename `PEER_DAS_EPOCH` to `EIP7594_FORK_EPOCH` for client interop. (#5750) * Fetch custody columns in range sync (#5747) * Fetch custody columns in range sync * Clean up todos * Remove `BlobSidecar` construction and publish after PeerDAS activated (#5759) * Avoid building and publishing blob sidecars after PeerDAS. * Ignore gossip blobs with a slot greater than peer das activation epoch. * Only attempt to verify blob count and import blobs before PeerDAS. * #5684 review comments (#5748) * #5684 review comments. * Doc and message update only. * Fix incorrect condition when constructing `RpcBlock` with `DataColumn`s * Make sampling tests deterministic (#5775) * PeerDAS spec tests (#5772) * Add get_custody_columns spec tests. * Add kzg merkle proof spec tests. * Add SSZ spec tests. * Add remaining KZG tests * Load KZG only once per process, exclude electra tests and add missing SSZ tests. * Fix lint and missing changes. * Ignore macOS generated file. * Merge remote branch 'sigp/unstable' into das * Merge remote tracking branch 'origin/unstable' into das * Implement unconditional reconstruction for supernodes (#5781) * Implement unconditional reconstruction for supernodes * Move code into KzgVerifiedCustodyDataColumn * Remove expect * Add test * Thanks justin * Add withhold attack mode for interop (#5788) * Add withhold attack mode * Update readme * Drop added readmes * Undo styling changes * Add column gossip verification and handle unknown parent block (#5783) * Add column gossip verification and handle missing parent for columns. * Review PR * Fix rebase issue * more lint issues :) --------- Co-authored-by: dapplion <35266934+dapplion@users.noreply.github.com> * Trigger sampling on sync events (#5776) * Trigger sampling on sync events * Update beacon_chain.rs * Fix tests * Fix tests * PeerDAS parameter changes for devnet-0 (#5779) * Update PeerDAS parameters to latest values. * Lint fix * Fix lint. * Update hardcoded subnet count to 64 (#5791) * Fix incorrect columns per subnet and config cleanup (#5792) * Tidy up PeerDAS preset and config values. * Fix broken config * Fix DAS branch CI (#5793) * Fix invalid syntax. * Update cli doc. Ignore get_custody_columns test temporarily. * Fix failing test and add verify inclusion test. * Undo accidentally removed code. * Only attempt reconstruct columns once. (#5794) * Re-enable precompute table for peerdas kzg (#5795) * Merge branch 'unstable' into das * Update subscription filter. (#5797) * Remove penalty for duplicate columns (expected due to reconstruction) (#5798) * Revert DAS config for interop testing. Optimise get_custody_columns function. (#5799) * Don't perform reconstruction for proposer node as it already has all the columns. (#5806) * Multithread compute_cells_and_proofs (#5805) * Multi-thread reconstruct data columns * Multi-thread path for block production * Merge branch 'unstable' into das # Conflicts: # .github/workflows/test-suite.yml # beacon_node/network/src/sync/block_lookups/mod.rs # beacon_node/network/src/sync/block_lookups/single_block_lookup.rs # beacon_node/network/src/sync/network_context.rs * Fix CI errors. * Move PeerDAS type-level config to configurable `ChainSpec` (#5828) * Move PeerDAS type level config to `ChainSpec`. * Fix tests * Misc custody lookup improvements (#5821) * Improve custody requests * Type DataColumnsByRootRequestId * Prioritize peers and load balance * Update tests * Address PR review * Merge branch 'unstable' into das * Rename deploy_block in network config (`das` branch) (#5852) * Rename deploy_block.txt to deposit_contract_block.txt * fmt --------- Co-authored-by: Pawan Dhananjay <pawandhananjay@gmail.com> * Merge branch 'unstable' into das * Fix CI and merge issues. * Merge branch 'unstable' into das # Conflicts: # beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs # lcli/src/main.rs * Store data columns individually in store and caches (#5890) * Store data columns individually in store and caches * Implement data column pruning * Merge branch 'unstable' into das # Conflicts: # Cargo.lock * Update reconstruction benches to newer criterion version. (#5949) * Merge branch 'unstable' into das # Conflicts: # .github/workflows/test-suite.yml * chore: add `recover_cells_and_compute_proofs` method (#5938) * chore: add recover_cells_and_compute_proofs method * Introduce type alias `CellsAndKzgProofs` to address type complexity. --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Update `csc` format in ENR and spec tests for devnet-1 (#5966) * Update `csc` format in ENR. * Add spec tests for `recover_cells_and_kzg_proofs`. * Add tests for ENR. * Fix failing tests. * Add protection against invalid csc value in ENR. * Fix lint * Fix csc encoding and decoding (#5997) * Fix data column rpc request not being sent due to incorrect limits set. (#6000) * Fix incorrect inbound request count causing rate limiting. (#6025) * Merge branch 'stable' into das # Conflicts: # beacon_node/network/src/sync/block_lookups/tests.rs # beacon_node/network/src/sync/block_sidecar_coupling.rs # beacon_node/network/src/sync/manager.rs # beacon_node/network/src/sync/network_context.rs # beacon_node/network/src/sync/network_context/requests.rs * Merge remote-tracking branch 'unstable' into das * Add kurtosis config for DAS testing (#5968) * Add kurtosis config for DAS testing. * Fix invalid yaml file * Update network parameter files. * chore: add rust PeerdasKZG crypto library for peerdas functionality and rollback c-kzg dependency to 4844 version (#5941) * chore: add recover_cells_and_compute_proofs method * chore: add rust peerdas crypto library * chore: integrate peerdaskzg rust library into kzg crate * chore(multi): - update `ssz_cell_to_crypto_cell` - update conversion from the crypto cell type to a Vec<u8>. Since the Rust library defines them as references to an array, the conversion is simply `to_vec` * chore(multi): - update rest of code to handle the new crypto `Cell` type - update test case code to no longer use the Box type * chore: cleanup of superfluous conversions * chore: revert c-kzg dependency back to v1 * chore: move dependency into correct order * chore: update rust dependency - This version includes a new method `PeerDasContext::with_num_threads` * chore: remove Default initialization of PeerDasContext and explicitly set the parameters in `new_from_trusted_setup` * chore: cleanup exports * chore: commit updated cargo.lock * Update Cargo.toml Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * chore: rename dependency * chore: update peerdas lib - sets the blst version to 0.3 so that it matches whatever lighthouse is using. Although 0.3.12 is latest, lighthouse is pinned to 0.3.3 * chore: fix clippy lifetime - Rust doesn't allow you to elide the lifetime on type aliases * chore: cargo clippy fix * chore: cargo fmt * chore: update lib to add redundant checks (these will be removed in consensus-specs PR 3819) * chore: update dependency to ignore proofs * chore: update peerdas lib to latest * update lib * chore: remove empty proof parameter --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Update PeerDAS interop testnet config (#6069) * Update interop testnet config. * Fix typo and remove target peers * Avoid retrying same sampling peer that previously failed. (#6084) * Various fixes to custody range sync (#6004) * Only start requesting batches when there are good peers across all custody columns to avoid spaming block requests. * Add custody peer check before mutating `BatchInfo` to avoid inconsistent state. * Add check to cover a case where batch is not processed while waiting for custody peers to become available. * Fix lint and logic error * Fix `good_peers_on_subnet` always returning false for `DataColumnSubnet`. * Add test for `get_custody_peers_for_column` * Revert epoch parameter refactor. * Fall back to default custody requiremnt if peer ENR is not present. * Add metrics and update code comment. * Add more debug logs. * Use subscribed peers on subnet before MetaDataV3 is implemented. Remove peer_id matching when injecting error because multiple peers are used for range requests. Use randomized custodial peer to avoid repeatedly sending requests to failing peers. Batch by range request where possible. * Remove unused code and update docs. * Add comment * chore: update peerdas-kzg library (#6118) * chore: update peerDAS lib * chore: update library * chore: update library to version that include "init context" benchmarks and optional validation checks * chore: (can remove) -- Add benchmarks for init context * Prevent continuous searchers for low-peer networks (#6162) * Merge branch 'unstable' into das * Fix merge conflicts * Add cli flag to enable sampling and disable by default. (#6209) * chore: Use reference to an array representing a blob instead of an owned KzgBlob (#6179) * add KzgBlobRef type * modify code to use KzgBlobRef * clippy * Remove Deneb blob related changes to maintain compatibility with `c-kzg-4844`. --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com> * Store computed custody subnets in PeerDB and fix custody lookup test (#6218) * Fix failing custody lookup tests. * Store custody subnets in PeerDB, fix custody lookup test and refactor some methods. * Merge branch 'unstable' into das # Conflicts: # beacon_node/beacon_chain/src/beacon_chain.rs # beacon_node/beacon_chain/src/block_verification_types.rs # beacon_node/beacon_chain/src/builder.rs # beacon_node/beacon_chain/src/data_availability_checker.rs # beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs # beacon_node/beacon_chain/src/data_column_verification.rs # beacon_node/beacon_chain/src/early_attester_cache.rs # beacon_node/beacon_chain/src/historical_blocks.rs # beacon_node/beacon_chain/tests/store_tests.rs # beacon_node/lighthouse_network/src/discovery/enr.rs # beacon_node/network/src/service.rs # beacon_node/src/cli.rs # beacon_node/store/src/hot_cold_store.rs # beacon_node/store/src/lib.rs # lcli/src/generate_bootnode_enr.rs * Fix CI failures after merge. * Batch sampling requests by peer (#6256) * Batch sampling requests by peer * Fix clippy errors * Fix tests * Add column_index to error message for ease of tracing * Remove outdated comment * Fix range sync never evaluating request as finished, causing it to get stuck. (#6276) * Merge branch 'unstable' into das-0821-merge # Conflicts: # Cargo.lock # Cargo.toml # beacon_node/beacon_chain/src/beacon_chain.rs # beacon_node/beacon_chain/src/data_availability_checker.rs # beacon_node/beacon_chain/src/data_availability_checker/overflow_lru_cache.rs # beacon_node/beacon_chain/src/data_column_verification.rs # beacon_node/beacon_chain/src/kzg_utils.rs # beacon_node/beacon_chain/src/metrics.rs # beacon_node/beacon_processor/src/lib.rs # beacon_node/lighthouse_network/src/rpc/codec/ssz_snappy.rs # beacon_node/lighthouse_network/src/rpc/config.rs # beacon_node/lighthouse_network/src/rpc/methods.rs # beacon_node/lighthouse_network/src/rpc/outbound.rs # beacon_node/lighthouse_network/src/rpc/rate_limiter.rs # beacon_node/lighthouse_network/src/service/api_types.rs # beacon_node/lighthouse_network/src/types/globals.rs # beacon_node/network/src/network_beacon_processor/mod.rs # beacon_node/network/src/network_beacon_processor/rpc_methods.rs # beacon_node/network/src/network_beacon_processor/sync_methods.rs # beacon_node/network/src/sync/block_lookups/common.rs # beacon_node/network/src/sync/block_lookups/mod.rs # beacon_node/network/src/sync/block_lookups/single_block_lookup.rs # beacon_node/network/src/sync/block_lookups/tests.rs # beacon_node/network/src/sync/manager.rs # beacon_node/network/src/sync/network_context.rs # consensus/types/src/data_column_sidecar.rs # crypto/kzg/Cargo.toml # crypto/kzg/benches/benchmark.rs # crypto/kzg/src/lib.rs * Fix custody tests and load PeerDAS KZG instead. * Fix ef tests and bench compilation. * Fix failing sampling test. * Merge pull request #6287 from jimmygchen/das-0821-merge Merge `unstable` into `das` 20240821 * Remove get_block_import_status * Merge branch 'unstable' into das * Re-enable Windows release tests. * Address some review comments. * Address more review comments and cleanups. * Comment out peer DAS KZG EF tests for now * Address more review comments and fix build. * Merge branch 'das' of github.com:sigp/lighthouse into das * Unignore Electra tests * Fix metric name * Address some of Pawan's review comments * Merge remote-tracking branch 'origin/unstable' into das * Update PeerDAS network parameters for peerdas-devnet-2 (#6290) * update subnet count & custody req * das network params * update ef tests --------- Co-authored-by: Jimmy Chen <jchen.tc@gmail.com>
1737 lines
59 KiB
Rust
1737 lines
59 KiB
Rust
#![cfg(not(debug_assertions))]
|
|
|
|
use beacon_chain::block_verification_types::{AsBlock, ExecutedBlock, RpcBlock};
|
|
use beacon_chain::{
|
|
test_utils::{
|
|
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType,
|
|
},
|
|
AvailabilityProcessingStatus, BeaconChain, BeaconChainTypes, ExecutionPendingBlock,
|
|
};
|
|
use beacon_chain::{
|
|
BeaconSnapshot, BlockError, ChainConfig, ChainSegmentResult, IntoExecutionPendingBlock,
|
|
NotifyExecutionLayer,
|
|
};
|
|
use logging::test_logger;
|
|
use slasher::{Config as SlasherConfig, Slasher};
|
|
use state_processing::{
|
|
common::{attesting_indices_base, attesting_indices_electra},
|
|
per_block_processing::{per_block_processing, BlockSignatureStrategy},
|
|
per_slot_processing, BlockProcessingError, ConsensusContext, VerifyBlockRoot,
|
|
};
|
|
use std::marker::PhantomData;
|
|
use std::sync::{Arc, LazyLock};
|
|
use tempfile::tempdir;
|
|
use types::{test_utils::generate_deterministic_keypair, *};
|
|
|
|
type E = MainnetEthSpec;
|
|
|
|
// Should ideally be divisible by 3.
|
|
const VALIDATOR_COUNT: usize = 24;
|
|
const CHAIN_SEGMENT_LENGTH: usize = 64 * 5;
|
|
const BLOCK_INDICES: &[usize] = &[0, 1, 32, 64, 68 + 1, 129, CHAIN_SEGMENT_LENGTH - 1];
|
|
|
|
/// A cached set of keys.
|
|
static KEYPAIRS: LazyLock<Vec<Keypair>> =
|
|
LazyLock::new(|| types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT));
|
|
|
|
async fn get_chain_segment() -> (Vec<BeaconSnapshot<E>>, Vec<Option<BlobSidecarList<E>>>) {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
|
|
harness
|
|
.extend_chain(
|
|
CHAIN_SEGMENT_LENGTH,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
let mut segment = Vec::with_capacity(CHAIN_SEGMENT_LENGTH);
|
|
let mut segment_blobs = Vec::with_capacity(CHAIN_SEGMENT_LENGTH);
|
|
for snapshot in harness
|
|
.chain
|
|
.chain_dump()
|
|
.expect("should dump chain")
|
|
.into_iter()
|
|
.skip(1)
|
|
{
|
|
let full_block = harness
|
|
.chain
|
|
.get_block(&snapshot.beacon_block_root)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
segment.push(BeaconSnapshot {
|
|
beacon_block_root: snapshot.beacon_block_root,
|
|
beacon_block: Arc::new(full_block),
|
|
beacon_state: snapshot.beacon_state,
|
|
});
|
|
segment_blobs.push(Some(
|
|
harness
|
|
.chain
|
|
.get_blobs(&snapshot.beacon_block_root)
|
|
.unwrap(),
|
|
))
|
|
}
|
|
(segment, segment_blobs)
|
|
}
|
|
|
|
async fn get_chain_segment_with_blob_sidecars(
|
|
) -> (Vec<BeaconSnapshot<E>>, Vec<Option<BlobSidecarList<E>>>) {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
|
|
harness
|
|
.extend_chain(
|
|
CHAIN_SEGMENT_LENGTH,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
let mut segment = Vec::with_capacity(CHAIN_SEGMENT_LENGTH);
|
|
let mut segment_blobs = Vec::with_capacity(CHAIN_SEGMENT_LENGTH);
|
|
for snapshot in harness
|
|
.chain
|
|
.chain_dump()
|
|
.expect("should dump chain")
|
|
.into_iter()
|
|
.skip(1)
|
|
{
|
|
let full_block = harness
|
|
.chain
|
|
.get_block(&snapshot.beacon_block_root)
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
segment.push(BeaconSnapshot {
|
|
beacon_block_root: snapshot.beacon_block_root,
|
|
beacon_block: Arc::new(full_block),
|
|
beacon_state: snapshot.beacon_state,
|
|
});
|
|
let blob_sidecars = harness
|
|
.chain
|
|
.get_blobs(&snapshot.beacon_block_root)
|
|
.unwrap();
|
|
segment_blobs.push(Some(blob_sidecars))
|
|
}
|
|
(segment, segment_blobs)
|
|
}
|
|
|
|
fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.default_spec()
|
|
.chain_config(ChainConfig {
|
|
reconstruct_historic_states: true,
|
|
..ChainConfig::default()
|
|
})
|
|
.keypairs(KEYPAIRS[0..validator_count].to_vec())
|
|
.fresh_ephemeral_store()
|
|
.mock_execution_layer()
|
|
.build();
|
|
|
|
harness.advance_slot();
|
|
|
|
harness
|
|
}
|
|
|
|
fn chain_segment_blocks(
|
|
chain_segment: &[BeaconSnapshot<E>],
|
|
blobs: &[Option<BlobSidecarList<E>>],
|
|
) -> Vec<RpcBlock<E>> {
|
|
chain_segment
|
|
.iter()
|
|
.zip(blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn junk_signature() -> Signature {
|
|
let kp = generate_deterministic_keypair(VALIDATOR_COUNT);
|
|
let message = Hash256::from_slice(&[42; 32]);
|
|
kp.sk.sign(message)
|
|
}
|
|
|
|
fn junk_aggregate_signature() -> AggregateSignature {
|
|
let mut agg_sig = AggregateSignature::empty();
|
|
agg_sig.add_assign(&junk_signature());
|
|
agg_sig
|
|
}
|
|
|
|
fn update_proposal_signatures(
|
|
snapshots: &mut [BeaconSnapshot<E>],
|
|
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
|
|
) {
|
|
for snapshot in snapshots {
|
|
let spec = &harness.chain.spec;
|
|
let slot = snapshot.beacon_block.slot();
|
|
let state = &snapshot.beacon_state;
|
|
let proposer_index = state
|
|
.get_beacon_proposer_index(slot, spec)
|
|
.expect("should find proposer index");
|
|
let keypair = harness
|
|
.validator_keypairs
|
|
.get(proposer_index)
|
|
.expect("proposer keypair should be available");
|
|
|
|
let (block, _) = snapshot.beacon_block.as_ref().clone().deconstruct();
|
|
snapshot.beacon_block = Arc::new(block.sign(
|
|
&keypair.sk,
|
|
&state.fork(),
|
|
state.genesis_validators_root(),
|
|
spec,
|
|
));
|
|
}
|
|
}
|
|
|
|
fn update_parent_roots(
|
|
snapshots: &mut [BeaconSnapshot<E>],
|
|
blobs: &mut [Option<BlobSidecarList<E>>],
|
|
) {
|
|
for i in 0..snapshots.len() {
|
|
let root = snapshots[i].beacon_block.canonical_root();
|
|
if let (Some(child), Some(child_blobs)) = (snapshots.get_mut(i + 1), blobs.get_mut(i + 1)) {
|
|
let (mut block, signature) = child.beacon_block.as_ref().clone().deconstruct();
|
|
*block.parent_root_mut() = root;
|
|
let new_child = Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
if let Some(blobs) = child_blobs {
|
|
update_blob_signed_header(&new_child, blobs);
|
|
}
|
|
child.beacon_block = new_child;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_blob_signed_header<E: EthSpec>(
|
|
signed_block: &SignedBeaconBlock<E>,
|
|
blobs: &mut BlobSidecarList<E>,
|
|
) {
|
|
for old_blob_sidecar in blobs.iter_mut() {
|
|
let new_blob = Arc::new(BlobSidecar::<E> {
|
|
index: old_blob_sidecar.index,
|
|
blob: old_blob_sidecar.blob.clone(),
|
|
kzg_commitment: old_blob_sidecar.kzg_commitment,
|
|
kzg_proof: old_blob_sidecar.kzg_proof,
|
|
signed_block_header: signed_block.signed_block_header(),
|
|
kzg_commitment_inclusion_proof: signed_block
|
|
.message()
|
|
.body()
|
|
.kzg_commitment_merkle_proof(old_blob_sidecar.index as usize)
|
|
.unwrap(),
|
|
});
|
|
*old_blob_sidecar = new_blob;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chain_segment_full_segment() {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
let blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(blocks.last().unwrap().slot().as_u64());
|
|
|
|
// Sneak in a little check to ensure we can process empty chain segments.
|
|
harness
|
|
.chain
|
|
.process_chain_segment(vec![], NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error()
|
|
.expect("should import empty chain segment");
|
|
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks.clone(), NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error()
|
|
.expect("should import chain segment");
|
|
|
|
harness.chain.recompute_head_at_current_slot().await;
|
|
|
|
assert_eq!(
|
|
harness.head_block_root(),
|
|
blocks.last().unwrap().canonical_root(),
|
|
"harness should have last block as head"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chain_segment_varying_chunk_size() {
|
|
for chunk_size in &[1, 2, 3, 5, 31, 32, 33, 42] {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
let blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(blocks.last().unwrap().slot().as_u64());
|
|
|
|
for chunk in blocks.chunks(*chunk_size) {
|
|
harness
|
|
.chain
|
|
.process_chain_segment(chunk.to_vec(), NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error()
|
|
.unwrap_or_else(|_| panic!("should import chain segment of len {}", chunk_size));
|
|
}
|
|
|
|
harness.chain.recompute_head_at_current_slot().await;
|
|
|
|
assert_eq!(
|
|
harness.head_block_root(),
|
|
blocks.last().unwrap().canonical_root(),
|
|
"harness should have last block as head"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chain_segment_non_linear_parent_roots() {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(chain_segment.last().unwrap().beacon_block.slot().as_u64());
|
|
|
|
/*
|
|
* Test with a block removed.
|
|
*/
|
|
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
blocks.remove(2);
|
|
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::NonLinearParentRoots)
|
|
),
|
|
"should not import chain with missing parent"
|
|
);
|
|
|
|
/*
|
|
* Test with a modified parent root.
|
|
*/
|
|
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
|
|
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
|
*block.parent_root_mut() = Hash256::zero();
|
|
blocks[3] = RpcBlock::new_without_blobs(
|
|
None,
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
|
);
|
|
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::NonLinearParentRoots)
|
|
),
|
|
"should not import chain with a broken parent root link"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn chain_segment_non_linear_slots() {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(chain_segment.last().unwrap().beacon_block.slot().as_u64());
|
|
|
|
/*
|
|
* Test where a child is lower than the parent.
|
|
*/
|
|
|
|
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
|
*block.slot_mut() = Slot::new(0);
|
|
blocks[3] = RpcBlock::new_without_blobs(
|
|
None,
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
|
);
|
|
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::NonLinearSlots)
|
|
),
|
|
"should not import chain with a parent that has a lower slot than its child"
|
|
);
|
|
|
|
/*
|
|
* Test where a child is equal to the parent.
|
|
*/
|
|
|
|
let mut blocks: Vec<RpcBlock<E>> = chain_segment_blocks(&chain_segment, &chain_segment_blobs)
|
|
.into_iter()
|
|
.collect();
|
|
let (mut block, signature) = blocks[3].as_block().clone().deconstruct();
|
|
*block.slot_mut() = blocks[2].slot();
|
|
blocks[3] = RpcBlock::new_without_blobs(
|
|
None,
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature)),
|
|
);
|
|
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::NonLinearSlots)
|
|
),
|
|
"should not import chain with a parent that has an equal slot to its child"
|
|
);
|
|
}
|
|
|
|
async fn assert_invalid_signature(
|
|
chain_segment: &[BeaconSnapshot<E>],
|
|
chain_segment_blobs: &[Option<BlobSidecarList<E>>],
|
|
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
|
|
block_index: usize,
|
|
snapshots: &[BeaconSnapshot<E>],
|
|
item: &str,
|
|
) {
|
|
let blocks: Vec<RpcBlock<E>> = snapshots
|
|
.iter()
|
|
.zip(chain_segment_blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.collect();
|
|
|
|
// Ensure the block will be rejected if imported in a chain segment.
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::InvalidSignature)
|
|
),
|
|
"should not import chain segment with an invalid {} signature",
|
|
item
|
|
);
|
|
|
|
// Call fork choice to update cached head (including finalization).
|
|
harness.chain.recompute_head_at_current_slot().await;
|
|
|
|
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
|
let ancestor_blocks = chain_segment
|
|
.iter()
|
|
.take(block_index)
|
|
.zip(chain_segment_blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.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, NotifyExecutionLayer::Yes)
|
|
.await;
|
|
harness.chain.recompute_head_at_current_slot().await;
|
|
|
|
let process_res = harness
|
|
.chain
|
|
.process_block(
|
|
snapshots[block_index].beacon_block.canonical_root(),
|
|
RpcBlock::new(
|
|
None,
|
|
snapshots[block_index].beacon_block.clone(),
|
|
chain_segment_blobs[block_index].clone(),
|
|
)
|
|
.unwrap(),
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await;
|
|
assert!(
|
|
matches!(process_res, Err(BlockError::InvalidSignature)),
|
|
"should not import individual block with an invalid {} signature, got: {:?}",
|
|
item,
|
|
process_res
|
|
);
|
|
|
|
// 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.
|
|
}
|
|
|
|
async fn get_invalid_sigs_harness(
|
|
chain_segment: &[BeaconSnapshot<E>],
|
|
) -> BeaconChainHarness<EphemeralHarnessType<E>> {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(chain_segment.last().unwrap().beacon_block.slot().as_u64());
|
|
harness
|
|
}
|
|
#[tokio::test]
|
|
async fn invalid_signature_gossip_block() {
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
// Ensure the block will be rejected if imported on its own (without gossip checking).
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let (block, _) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(
|
|
block.clone(),
|
|
junk_signature(),
|
|
));
|
|
// Import all the ancestors before the `block_index` block.
|
|
let ancestor_blocks = chain_segment
|
|
.iter()
|
|
.take(block_index)
|
|
.zip(chain_segment_blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.collect();
|
|
harness
|
|
.chain
|
|
.process_chain_segment(ancestor_blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error()
|
|
.expect("should import all blocks prior to the one being tested");
|
|
let signed_block = SignedBeaconBlock::from_block(block, junk_signature());
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
signed_block.canonical_root(),
|
|
Arc::new(signed_block),
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await,
|
|
Err(BlockError::InvalidSignature)
|
|
),
|
|
"should not import individual block with an invalid gossip signature",
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_block_proposal() {
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let (block, _) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
snapshots[block_index].beacon_block = Arc::new(SignedBeaconBlock::from_block(
|
|
block.clone(),
|
|
junk_signature(),
|
|
));
|
|
let blocks: Vec<RpcBlock<E>> = snapshots
|
|
.iter()
|
|
.zip(chain_segment_blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
// Ensure the block will be rejected if imported in a chain segment.
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::InvalidSignature)
|
|
),
|
|
"should not import chain segment with an invalid block signature",
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_randao_reveal() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
*block.body_mut().randao_reveal_mut() = junk_signature();
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
assert_invalid_signature(
|
|
&chain_segment,
|
|
&chain_segment_blobs,
|
|
&harness,
|
|
block_index,
|
|
&snapshots,
|
|
"randao",
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_proposer_slashing() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
let proposer_slashing = ProposerSlashing {
|
|
signed_header_1: SignedBeaconBlockHeader {
|
|
message: block.block_header(),
|
|
signature: junk_signature(),
|
|
},
|
|
signed_header_2: SignedBeaconBlockHeader {
|
|
message: block.block_header(),
|
|
signature: junk_signature(),
|
|
},
|
|
};
|
|
block
|
|
.body_mut()
|
|
.proposer_slashings_mut()
|
|
.push(proposer_slashing)
|
|
.expect("should update proposer slashing");
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
assert_invalid_signature(
|
|
&chain_segment,
|
|
&chain_segment_blobs,
|
|
&harness,
|
|
block_index,
|
|
&snapshots,
|
|
"proposer slashing",
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_attester_slashing() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let fork_name = harness.chain.spec.fork_name_at_slot::<E>(Slot::new(0));
|
|
|
|
let attester_slashing = if fork_name.electra_enabled() {
|
|
let indexed_attestation = IndexedAttestationElectra {
|
|
attesting_indices: vec![0].into(),
|
|
data: AttestationData {
|
|
slot: Slot::new(0),
|
|
index: 0,
|
|
beacon_block_root: Hash256::zero(),
|
|
source: Checkpoint {
|
|
epoch: Epoch::new(0),
|
|
root: Hash256::zero(),
|
|
},
|
|
target: Checkpoint {
|
|
epoch: Epoch::new(0),
|
|
root: Hash256::zero(),
|
|
},
|
|
},
|
|
signature: junk_aggregate_signature(),
|
|
};
|
|
let attester_slashing = AttesterSlashingElectra {
|
|
attestation_1: indexed_attestation.clone(),
|
|
attestation_2: indexed_attestation,
|
|
};
|
|
|
|
AttesterSlashing::Electra(attester_slashing)
|
|
} else {
|
|
let indexed_attestation = IndexedAttestationBase {
|
|
attesting_indices: vec![0].into(),
|
|
data: AttestationData {
|
|
slot: Slot::new(0),
|
|
index: 0,
|
|
beacon_block_root: Hash256::zero(),
|
|
source: Checkpoint {
|
|
epoch: Epoch::new(0),
|
|
root: Hash256::zero(),
|
|
},
|
|
target: Checkpoint {
|
|
epoch: Epoch::new(0),
|
|
root: Hash256::zero(),
|
|
},
|
|
},
|
|
signature: junk_aggregate_signature(),
|
|
};
|
|
let attester_slashing = AttesterSlashingBase {
|
|
attestation_1: indexed_attestation.clone(),
|
|
attestation_2: indexed_attestation,
|
|
};
|
|
|
|
AttesterSlashing::Base(attester_slashing)
|
|
};
|
|
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
match &mut block.body_mut() {
|
|
BeaconBlockBodyRefMut::Base(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_base().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
BeaconBlockBodyRefMut::Altair(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_base().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
BeaconBlockBodyRefMut::Bellatrix(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_base().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
BeaconBlockBodyRefMut::Capella(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_base().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
BeaconBlockBodyRefMut::Deneb(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_base().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
BeaconBlockBodyRefMut::Electra(ref mut blk) => {
|
|
blk.attester_slashings
|
|
.push(attester_slashing.as_electra().unwrap().clone())
|
|
.expect("should update attester slashing");
|
|
}
|
|
}
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
assert_invalid_signature(
|
|
&chain_segment,
|
|
&chain_segment_blobs,
|
|
&harness,
|
|
block_index,
|
|
&snapshots,
|
|
"attester slashing",
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_attestation() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
let mut checked_attestation = false;
|
|
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
match &mut block.body_mut() {
|
|
BeaconBlockBodyRefMut::Base(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
BeaconBlockBodyRefMut::Altair(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
BeaconBlockBodyRefMut::Bellatrix(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
BeaconBlockBodyRefMut::Capella(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
BeaconBlockBodyRefMut::Deneb(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
BeaconBlockBodyRefMut::Electra(ref mut blk) => blk
|
|
.attestations
|
|
.get_mut(0)
|
|
.map(|att| att.signature = junk_aggregate_signature()),
|
|
};
|
|
|
|
if block.body().attestations_len() > 0 {
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
assert_invalid_signature(
|
|
&chain_segment,
|
|
&chain_segment_blobs,
|
|
&harness,
|
|
block_index,
|
|
&snapshots,
|
|
"attestation",
|
|
)
|
|
.await;
|
|
checked_attestation = true;
|
|
}
|
|
}
|
|
|
|
assert!(
|
|
checked_attestation,
|
|
"the test should check an attestation signature"
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_deposit() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
// Note: an invalid deposit signature is permitted!
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let deposit = Deposit {
|
|
proof: vec![Hash256::zero(); DEPOSIT_TREE_DEPTH + 1].into(),
|
|
data: DepositData {
|
|
pubkey: Keypair::random().pk.into(),
|
|
withdrawal_credentials: Hash256::zero(),
|
|
amount: 0,
|
|
signature: junk_signature().into(),
|
|
},
|
|
};
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
block
|
|
.body_mut()
|
|
.deposits_mut()
|
|
.push(deposit)
|
|
.expect("should update deposit");
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
let blocks: Vec<RpcBlock<E>> = snapshots
|
|
.iter()
|
|
.zip(chain_segment_blobs.iter())
|
|
.map(|(snapshot, blobs)| {
|
|
RpcBlock::new(None, snapshot.beacon_block.clone(), blobs.clone()).unwrap()
|
|
})
|
|
.collect();
|
|
assert!(
|
|
!matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(blocks, NotifyExecutionLayer::Yes)
|
|
.await
|
|
.into_block_error(),
|
|
Err(BlockError::InvalidSignature)
|
|
),
|
|
"should not throw an invalid signature error for a bad deposit signature"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_signature_exit() {
|
|
let (chain_segment, mut chain_segment_blobs) = get_chain_segment().await;
|
|
for &block_index in BLOCK_INDICES {
|
|
let harness = get_invalid_sigs_harness(&chain_segment).await;
|
|
let mut snapshots = chain_segment.clone();
|
|
let epoch = snapshots[block_index].beacon_state.current_epoch();
|
|
let (mut block, signature) = snapshots[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
block
|
|
.body_mut()
|
|
.voluntary_exits_mut()
|
|
.push(SignedVoluntaryExit {
|
|
message: VoluntaryExit {
|
|
epoch,
|
|
validator_index: 0,
|
|
},
|
|
signature: junk_signature(),
|
|
})
|
|
.expect("should update deposit");
|
|
snapshots[block_index].beacon_block =
|
|
Arc::new(SignedBeaconBlock::from_block(block, signature));
|
|
update_parent_roots(&mut snapshots, &mut chain_segment_blobs);
|
|
update_proposal_signatures(&mut snapshots, &harness);
|
|
assert_invalid_signature(
|
|
&chain_segment,
|
|
&chain_segment_blobs,
|
|
&harness,
|
|
block_index,
|
|
&snapshots,
|
|
"voluntary exit",
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
|
|
fn unwrap_err<T, U>(result: Result<T, U>) -> U {
|
|
match result {
|
|
Ok(_) => panic!("called unwrap_err on Ok"),
|
|
Err(e) => e,
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn block_gossip_verification() {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
let (chain_segment, chain_segment_blobs) = get_chain_segment_with_blob_sidecars().await;
|
|
|
|
let block_index = CHAIN_SEGMENT_LENGTH - 2;
|
|
|
|
harness
|
|
.chain
|
|
.slot_clock
|
|
.set_slot(chain_segment[block_index].beacon_block.slot().as_u64());
|
|
|
|
// Import the ancestors prior to the block we're testing.
|
|
for (snapshot, blobs_opt) in chain_segment[0..block_index]
|
|
.iter()
|
|
.zip(chain_segment_blobs.iter())
|
|
{
|
|
let gossip_verified = harness
|
|
.chain
|
|
.verify_block_for_gossip(snapshot.beacon_block.clone())
|
|
.await
|
|
.expect("should obtain gossip verified block");
|
|
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
gossip_verified.block_root,
|
|
gossip_verified,
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await
|
|
.expect("should import valid gossip verified block");
|
|
if let Some(blob_sidecars) = blobs_opt {
|
|
for blob_sidecar in blob_sidecars {
|
|
let blob_index = blob_sidecar.index;
|
|
let gossip_verified = harness
|
|
.chain
|
|
.verify_blob_sidecar_for_gossip(blob_sidecar.clone(), blob_index)
|
|
.expect("should obtain gossip verified blob");
|
|
|
|
harness
|
|
.chain
|
|
.process_gossip_blob(gossip_verified)
|
|
.await
|
|
.expect("should import valid gossip verified blob");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recompute the head to ensure we cache the latest view of fork choice.
|
|
harness.chain.recompute_head_at_current_slot().await;
|
|
|
|
/*
|
|
* This test ensures that:
|
|
*
|
|
* Spec v0.12.1
|
|
*
|
|
* The block is not from a future slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) --
|
|
* i.e. validate that signed_beacon_block.message.slot <= current_slot (a client MAY queue
|
|
* future blocks for processing at the appropriate slot).
|
|
*/
|
|
|
|
let (mut block, signature) = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
let expected_block_slot = block.slot() + 1;
|
|
*block.slot_mut() = expected_block_slot;
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
|
|
BlockError::FutureSlot {
|
|
present_slot,
|
|
block_slot,
|
|
}
|
|
if present_slot == expected_block_slot - 1 && block_slot == expected_block_slot
|
|
),
|
|
"should not import a block with a future slot"
|
|
);
|
|
|
|
/*
|
|
* This test ensure that:
|
|
*
|
|
* Spec v0.12.1
|
|
*
|
|
* The block is from a slot greater than the latest finalized slot -- i.e. validate that
|
|
* signed_beacon_block.message.slot >
|
|
* compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) (a client MAY choose to
|
|
* validate and store such blocks for additional purposes -- e.g. slashing detection, archive
|
|
* nodes, etc).
|
|
*/
|
|
|
|
let (mut block, signature) = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
let expected_finalized_slot = harness
|
|
.finalized_checkpoint()
|
|
.epoch
|
|
.start_slot(E::slots_per_epoch());
|
|
*block.slot_mut() = expected_finalized_slot;
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
|
|
BlockError::WouldRevertFinalizedSlot {
|
|
block_slot,
|
|
finalized_slot,
|
|
}
|
|
if block_slot == expected_finalized_slot && finalized_slot == expected_finalized_slot
|
|
),
|
|
"should not import a block with a finalized slot"
|
|
);
|
|
|
|
/*
|
|
* This test ensures that:
|
|
*
|
|
* Spec v0.12.1
|
|
*
|
|
* The proposer signature, signed_beacon_block.signature, is valid with respect to the
|
|
* proposer_index pubkey.
|
|
*/
|
|
|
|
let block = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct()
|
|
.0;
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(
|
|
harness
|
|
.chain
|
|
.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(
|
|
block,
|
|
junk_signature()
|
|
)))
|
|
.await
|
|
),
|
|
BlockError::ProposalSignatureInvalid
|
|
),
|
|
"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, signature) = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
let parent_root = Hash256::from_low_u64_be(42);
|
|
*block.parent_root_mut() = parent_root;
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
|
|
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, signature) = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct();
|
|
let parent_root = chain_segment[0].beacon_block_root;
|
|
*block.parent_root_mut() = parent_root;
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block(block, signature))).await),
|
|
BlockError::NotFinalizedDescendant { block_parent_root }
|
|
if block_parent_root == parent_root
|
|
),
|
|
"should not import a block that conflicts with finality"
|
|
);
|
|
|
|
/*
|
|
* This test ensures that:
|
|
*
|
|
* Spec v0.12.1
|
|
*
|
|
* The block is proposed by the expected proposer_index for the block's slot in the context of
|
|
* the current shuffling (defined by parent_root/slot). If the proposer_index cannot
|
|
* immediately be verified against the expected shuffling, the block MAY be queued for later
|
|
* processing while proposers for the block's branch are calculated.
|
|
*/
|
|
|
|
let mut block = chain_segment[block_index]
|
|
.beacon_block
|
|
.as_ref()
|
|
.clone()
|
|
.deconstruct()
|
|
.0;
|
|
let expected_proposer = block.proposer_index();
|
|
let other_proposer = (0..VALIDATOR_COUNT as u64)
|
|
.find(|i| *i != block.proposer_index())
|
|
.expect("there must be more than one validator in this test");
|
|
*block.proposer_index_mut() = other_proposer;
|
|
let block = block.sign(
|
|
&generate_deterministic_keypair(other_proposer as usize).sk,
|
|
&harness.chain.canonical_head.cached_head().head_fork(),
|
|
harness.chain.genesis_validators_root,
|
|
&harness.chain.spec,
|
|
);
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await),
|
|
BlockError::IncorrectBlockProposer {
|
|
block,
|
|
local_shuffling,
|
|
}
|
|
if block == other_proposer && local_shuffling == expected_proposer
|
|
),
|
|
"should not import a block with the wrong proposer index"
|
|
);
|
|
// Check to ensure that we registered this is a valid block from this proposer.
|
|
assert!(
|
|
matches!(
|
|
unwrap_err(harness.chain.verify_block_for_gossip(Arc::new(block.clone())).await),
|
|
BlockError::BlockIsAlreadyKnown(_),
|
|
),
|
|
"should register any valid signature against the proposer, even if the block failed later verification"
|
|
);
|
|
|
|
let block = chain_segment[block_index].beacon_block.clone();
|
|
assert!(
|
|
harness.chain.verify_block_for_gossip(block).await.is_ok(),
|
|
"the valid block should be processed"
|
|
);
|
|
|
|
/*
|
|
* This test ensures that:
|
|
*
|
|
* Spec v0.12.1
|
|
*
|
|
* The block is the first block with valid signature received for the proposer for the slot,
|
|
* signed_beacon_block.message.slot.
|
|
*/
|
|
|
|
let block = chain_segment[block_index].beacon_block.clone();
|
|
assert!(
|
|
matches!(
|
|
harness
|
|
.chain
|
|
.verify_block_for_gossip(block.clone())
|
|
.await
|
|
.expect_err("should error when processing known block"),
|
|
BlockError::BlockIsAlreadyKnown(_)
|
|
),
|
|
"the second proposal by this validator should be rejected"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn verify_block_for_gossip_slashing_detection() {
|
|
let slasher_dir = tempdir().unwrap();
|
|
let spec = Arc::new(test_spec::<E>());
|
|
let slasher = Arc::new(
|
|
Slasher::open(
|
|
SlasherConfig::new(slasher_dir.path().into()),
|
|
spec,
|
|
test_logger(),
|
|
)
|
|
.unwrap(),
|
|
);
|
|
|
|
let inner_slasher = slasher.clone();
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.default_spec()
|
|
.keypairs(KEYPAIRS.to_vec())
|
|
.fresh_ephemeral_store()
|
|
.initial_mutator(Box::new(move |builder| builder.slasher(inner_slasher)))
|
|
.mock_execution_layer()
|
|
.build();
|
|
harness.advance_slot();
|
|
|
|
let state = harness.get_current_state();
|
|
let ((block1, blobs1), _) = harness.make_block(state.clone(), Slot::new(1)).await;
|
|
let ((block2, _blobs2), _) = harness.make_block(state, Slot::new(1)).await;
|
|
|
|
let verified_block = harness.chain.verify_block_for_gossip(block1).await.unwrap();
|
|
|
|
if let Some((kzg_proofs, blobs)) = blobs1 {
|
|
let sidecars =
|
|
BlobSidecar::build_sidecars(blobs, verified_block.block(), kzg_proofs).unwrap();
|
|
for sidecar in sidecars {
|
|
let blob_index = sidecar.index;
|
|
let verified_blob = harness
|
|
.chain
|
|
.verify_blob_sidecar_for_gossip(sidecar, blob_index)
|
|
.unwrap();
|
|
harness
|
|
.chain
|
|
.process_gossip_blob(verified_blob)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
}
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
verified_block.block_root,
|
|
verified_block,
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
unwrap_err(harness.chain.verify_block_for_gossip(block2).await);
|
|
|
|
// Slasher should have been handed the two conflicting blocks and crafted a slashing.
|
|
slasher.process_queued(Epoch::new(0)).unwrap();
|
|
let proposer_slashings = slasher.get_proposer_slashings();
|
|
assert_eq!(proposer_slashings.len(), 1);
|
|
// windows won't delete the temporary directory if you don't do this..
|
|
drop(harness);
|
|
drop(slasher);
|
|
slasher_dir.close().unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn verify_block_for_gossip_doppelganger_detection() {
|
|
let harness = get_harness(VALIDATOR_COUNT);
|
|
|
|
let state = harness.get_current_state();
|
|
let ((block, _), _) = harness.make_block(state.clone(), Slot::new(1)).await;
|
|
let attestations = block
|
|
.message()
|
|
.body()
|
|
.attestations()
|
|
.map(|att| att.clone_as_attestation())
|
|
.collect::<Vec<_>>();
|
|
let verified_block = harness.chain.verify_block_for_gossip(block).await.unwrap();
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
verified_block.block_root,
|
|
verified_block,
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
for att in attestations.iter() {
|
|
let epoch = att.data().target.epoch;
|
|
let indexed_attestation = match att {
|
|
Attestation::Base(att) => {
|
|
let committee = state
|
|
.get_beacon_committee(att.data.slot, att.data.index)
|
|
.unwrap();
|
|
attesting_indices_base::get_indexed_attestation(committee.committee, att).unwrap()
|
|
}
|
|
Attestation::Electra(att) => {
|
|
attesting_indices_electra::get_indexed_attestation_from_state(&state, att).unwrap()
|
|
}
|
|
};
|
|
|
|
for index in match indexed_attestation {
|
|
IndexedAttestation::Base(att) => att.attesting_indices.into_iter(),
|
|
IndexedAttestation::Electra(att) => att.attesting_indices.into_iter(),
|
|
} {
|
|
let index = index as usize;
|
|
|
|
assert!(harness.chain.validator_seen_at_epoch(index, epoch));
|
|
|
|
// Check the correct beacon cache is populated
|
|
assert!(harness
|
|
.chain
|
|
.observed_block_attesters
|
|
.read()
|
|
.validator_has_been_observed(epoch, index)
|
|
.expect("should check if block attester was observed"));
|
|
assert!(!harness
|
|
.chain
|
|
.observed_gossip_attesters
|
|
.read()
|
|
.validator_has_been_observed(epoch, index)
|
|
.expect("should check if gossip attester was observed"));
|
|
assert!(!harness
|
|
.chain
|
|
.observed_aggregators
|
|
.read()
|
|
.validator_has_been_observed(epoch, index)
|
|
.expect("should check if gossip aggregator was observed"));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_base_block_to_altair_chain() {
|
|
let mut spec = MainnetEthSpec::default_spec();
|
|
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
|
|
|
// The Altair fork happens at epoch 1.
|
|
spec.altair_fork_epoch = Some(Epoch::new(1));
|
|
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.spec(spec)
|
|
.keypairs(KEYPAIRS[..].to_vec())
|
|
.fresh_ephemeral_store()
|
|
.mock_execution_layer()
|
|
.build();
|
|
|
|
// Move out of the genesis slot.
|
|
harness.advance_slot();
|
|
|
|
// Build out all the blocks in epoch 0.
|
|
harness
|
|
.extend_chain(
|
|
slots_per_epoch as usize,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
// Move into the next empty slot.
|
|
harness.advance_slot();
|
|
|
|
// Produce an Altair block.
|
|
let state = harness.get_current_state();
|
|
let slot = harness.get_current_slot();
|
|
let ((altair_signed_block, _), _) = harness.make_block(state.clone(), slot).await;
|
|
let altair_block = &altair_signed_block
|
|
.as_altair()
|
|
.expect("test expects an altair block")
|
|
.message;
|
|
let altair_body = &altair_block.body;
|
|
|
|
// Create a Base-equivalent of `altair_block`.
|
|
let base_block = SignedBeaconBlock::Base(SignedBeaconBlockBase {
|
|
message: BeaconBlockBase {
|
|
slot: altair_block.slot,
|
|
proposer_index: altair_block.proposer_index,
|
|
parent_root: altair_block.parent_root,
|
|
state_root: altair_block.state_root,
|
|
body: BeaconBlockBodyBase {
|
|
randao_reveal: altair_body.randao_reveal.clone(),
|
|
eth1_data: altair_body.eth1_data.clone(),
|
|
graffiti: altair_body.graffiti,
|
|
proposer_slashings: altair_body.proposer_slashings.clone(),
|
|
attester_slashings: altair_body.attester_slashings.clone(),
|
|
attestations: altair_body.attestations.clone(),
|
|
deposits: altair_body.deposits.clone(),
|
|
voluntary_exits: altair_body.voluntary_exits.clone(),
|
|
_phantom: PhantomData,
|
|
},
|
|
},
|
|
signature: Signature::empty(),
|
|
});
|
|
|
|
// Ensure that it would be impossible to apply this block to `per_block_processing`.
|
|
{
|
|
let mut state = state;
|
|
let mut ctxt = ConsensusContext::new(base_block.slot());
|
|
per_slot_processing(&mut state, None, &harness.chain.spec).unwrap();
|
|
assert!(matches!(
|
|
per_block_processing(
|
|
&mut state,
|
|
&base_block,
|
|
BlockSignatureStrategy::NoVerification,
|
|
VerifyBlockRoot::True,
|
|
&mut ctxt,
|
|
&harness.chain.spec,
|
|
),
|
|
Err(BlockProcessingError::InconsistentBlockFork(
|
|
InconsistentFork {
|
|
fork_at_slot: ForkName::Altair,
|
|
object_fork: ForkName::Base,
|
|
}
|
|
))
|
|
));
|
|
}
|
|
|
|
// Ensure that it would be impossible to verify this block for gossip.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.verify_block_for_gossip(Arc::new(base_block.clone()))
|
|
.await
|
|
.expect_err("should error when processing base block"),
|
|
BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Altair,
|
|
object_fork: ForkName::Base,
|
|
})
|
|
));
|
|
|
|
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
base_block.canonical_root(),
|
|
Arc::new(base_block.clone()),
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await
|
|
.expect_err("should error when processing base block"),
|
|
BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Altair,
|
|
object_fork: ForkName::Base,
|
|
})
|
|
));
|
|
|
|
// Ensure that it would be impossible to import via `BeaconChain::process_chain_segment`.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(
|
|
vec![RpcBlock::new_without_blobs(None, Arc::new(base_block))],
|
|
NotifyExecutionLayer::Yes,
|
|
)
|
|
.await,
|
|
ChainSegmentResult::Failed {
|
|
imported_blocks: _,
|
|
error: BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Altair,
|
|
object_fork: ForkName::Base,
|
|
})
|
|
}
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn add_altair_block_to_base_chain() {
|
|
let mut spec = MainnetEthSpec::default_spec();
|
|
|
|
// Altair never happens.
|
|
spec.altair_fork_epoch = None;
|
|
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.spec(spec)
|
|
.keypairs(KEYPAIRS[..].to_vec())
|
|
.fresh_ephemeral_store()
|
|
.mock_execution_layer()
|
|
.build();
|
|
|
|
// Move out of the genesis slot.
|
|
harness.advance_slot();
|
|
|
|
// Build one block.
|
|
harness
|
|
.extend_chain(
|
|
1,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
// Move into the next empty slot.
|
|
harness.advance_slot();
|
|
|
|
// Produce an altair block.
|
|
let state = harness.get_current_state();
|
|
let slot = harness.get_current_slot();
|
|
let ((base_signed_block, _), _) = harness.make_block(state.clone(), slot).await;
|
|
let base_block = &base_signed_block
|
|
.as_base()
|
|
.expect("test expects a base block")
|
|
.message;
|
|
let base_body = &base_block.body;
|
|
|
|
// Create an Altair-equivalent of `altair_block`.
|
|
let altair_block = SignedBeaconBlock::Altair(SignedBeaconBlockAltair {
|
|
message: BeaconBlockAltair {
|
|
slot: base_block.slot,
|
|
proposer_index: base_block.proposer_index,
|
|
parent_root: base_block.parent_root,
|
|
state_root: base_block.state_root,
|
|
body: BeaconBlockBodyAltair {
|
|
randao_reveal: base_body.randao_reveal.clone(),
|
|
eth1_data: base_body.eth1_data.clone(),
|
|
graffiti: base_body.graffiti,
|
|
proposer_slashings: base_body.proposer_slashings.clone(),
|
|
attester_slashings: base_body.attester_slashings.clone(),
|
|
attestations: base_body.attestations.clone(),
|
|
deposits: base_body.deposits.clone(),
|
|
voluntary_exits: base_body.voluntary_exits.clone(),
|
|
sync_aggregate: SyncAggregate::empty(),
|
|
_phantom: PhantomData,
|
|
},
|
|
},
|
|
signature: Signature::empty(),
|
|
});
|
|
|
|
// Ensure that it would be impossible to apply this block to `per_block_processing`.
|
|
{
|
|
let mut state = state;
|
|
let mut ctxt = ConsensusContext::new(altair_block.slot());
|
|
per_slot_processing(&mut state, None, &harness.chain.spec).unwrap();
|
|
assert!(matches!(
|
|
per_block_processing(
|
|
&mut state,
|
|
&altair_block,
|
|
BlockSignatureStrategy::NoVerification,
|
|
VerifyBlockRoot::True,
|
|
&mut ctxt,
|
|
&harness.chain.spec,
|
|
),
|
|
Err(BlockProcessingError::InconsistentBlockFork(
|
|
InconsistentFork {
|
|
fork_at_slot: ForkName::Base,
|
|
object_fork: ForkName::Altair,
|
|
}
|
|
))
|
|
));
|
|
}
|
|
|
|
// Ensure that it would be impossible to verify this block for gossip.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.verify_block_for_gossip(Arc::new(altair_block.clone()))
|
|
.await
|
|
.expect_err("should error when processing altair block"),
|
|
BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Base,
|
|
object_fork: ForkName::Altair,
|
|
})
|
|
));
|
|
|
|
// Ensure that it would be impossible to import via `BeaconChain::process_block`.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.process_block(
|
|
altair_block.canonical_root(),
|
|
Arc::new(altair_block.clone()),
|
|
NotifyExecutionLayer::Yes,
|
|
BlockImportSource::Lookup,
|
|
|| Ok(()),
|
|
)
|
|
.await
|
|
.expect_err("should error when processing altair block"),
|
|
BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Base,
|
|
object_fork: ForkName::Altair,
|
|
})
|
|
));
|
|
|
|
// Ensure that it would be impossible to import via `BeaconChain::process_chain_segment`.
|
|
assert!(matches!(
|
|
harness
|
|
.chain
|
|
.process_chain_segment(
|
|
vec![RpcBlock::new_without_blobs(None, Arc::new(altair_block))],
|
|
NotifyExecutionLayer::Yes
|
|
)
|
|
.await,
|
|
ChainSegmentResult::Failed {
|
|
imported_blocks: _,
|
|
error: BlockError::InconsistentFork(InconsistentFork {
|
|
fork_at_slot: ForkName::Base,
|
|
object_fork: ForkName::Altair,
|
|
})
|
|
}
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn import_duplicate_block_unrealized_justification() {
|
|
let spec = MainnetEthSpec::default_spec();
|
|
|
|
let harness = BeaconChainHarness::builder(MainnetEthSpec)
|
|
.spec(spec)
|
|
.keypairs(KEYPAIRS[..].to_vec())
|
|
.fresh_ephemeral_store()
|
|
.mock_execution_layer()
|
|
.build();
|
|
let chain = &harness.chain;
|
|
|
|
// Move out of the genesis slot.
|
|
harness.advance_slot();
|
|
|
|
// Build the chain out to the first justification opportunity 2/3rds of the way through epoch 2.
|
|
let num_slots = E::slots_per_epoch() as usize * 8 / 3;
|
|
harness
|
|
.extend_chain(
|
|
num_slots,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
// Move into the next empty slot.
|
|
harness.advance_slot();
|
|
|
|
// The store's justified checkpoint must still be at epoch 0, while unrealized justification
|
|
// must be at epoch 1.
|
|
{
|
|
let fc = chain.canonical_head.fork_choice_read_lock();
|
|
assert_eq!(fc.justified_checkpoint().epoch, 0);
|
|
assert_eq!(fc.unrealized_justified_checkpoint().epoch, 1);
|
|
drop(fc);
|
|
}
|
|
|
|
// Produce a block to justify epoch 2.
|
|
let state = harness.get_current_state();
|
|
let slot = harness.get_current_slot();
|
|
let (block_contents, _) = harness.make_block(state.clone(), slot).await;
|
|
let (block, _) = block_contents;
|
|
let block_root = block.canonical_root();
|
|
|
|
// Create two verified variants of the block, representing the same block being processed in
|
|
// parallel.
|
|
let notify_execution_layer = NotifyExecutionLayer::Yes;
|
|
let verified_block1 = block
|
|
.clone()
|
|
.into_execution_pending_block(block_root, chain, notify_execution_layer)
|
|
.unwrap();
|
|
let verified_block2 = block
|
|
.into_execution_pending_block(block_root, chain, notify_execution_layer)
|
|
.unwrap();
|
|
|
|
// Import the first block, simulating a block processed via a finalized chain segment.
|
|
import_execution_pending_block(chain.clone(), verified_block1)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Unrealized justification should NOT have updated.
|
|
let unrealized_justification = {
|
|
let fc = chain.canonical_head.fork_choice_read_lock();
|
|
assert_eq!(fc.justified_checkpoint().epoch, 0);
|
|
let unrealized_justification = fc.unrealized_justified_checkpoint();
|
|
assert_eq!(unrealized_justification.epoch, 2);
|
|
// The fork choice node for the block should have unrealized justification.
|
|
let fc_block = fc.get_block(&block_root).unwrap();
|
|
assert_eq!(
|
|
fc_block.unrealized_justified_checkpoint,
|
|
Some(unrealized_justification)
|
|
);
|
|
drop(fc);
|
|
unrealized_justification
|
|
};
|
|
|
|
// Import the second verified block, simulating a block processed via RPC.
|
|
import_execution_pending_block(chain.clone(), verified_block2)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Unrealized justification should still be updated.
|
|
let fc3 = chain.canonical_head.fork_choice_read_lock();
|
|
assert_eq!(fc3.justified_checkpoint().epoch, 0);
|
|
assert_eq!(
|
|
fc3.unrealized_justified_checkpoint(),
|
|
unrealized_justification
|
|
);
|
|
|
|
// The fork choice node for the block should still have the unrealized justified checkpoint.
|
|
let fc_block = fc3.get_block(&block_root).unwrap();
|
|
drop(fc3);
|
|
assert_eq!(
|
|
fc_block.unrealized_justified_checkpoint,
|
|
Some(unrealized_justification)
|
|
);
|
|
}
|
|
|
|
async fn import_execution_pending_block<T: BeaconChainTypes>(
|
|
chain: Arc<BeaconChain<T>>,
|
|
execution_pending_block: ExecutionPendingBlock<T>,
|
|
) -> Result<AvailabilityProcessingStatus, String> {
|
|
match chain
|
|
.clone()
|
|
.into_executed_block(execution_pending_block)
|
|
.await
|
|
.unwrap()
|
|
{
|
|
ExecutedBlock::Available(block) => chain
|
|
.import_available_block(Box::from(block))
|
|
.await
|
|
.map_err(|e| format!("{e:?}")),
|
|
ExecutedBlock::AvailabilityPending(_) => {
|
|
Err("AvailabilityPending not expected in this test. Block not imported.".to_string())
|
|
}
|
|
}
|
|
}
|