mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-05 17:51:41 +00:00
* Attestation superstruct changes for EIP 7549 (#5644)
* update
* experiment
* superstruct changes
* revert
* superstruct changes
* fix tests
* indexed attestation
* indexed attestation superstruct
* updated TODOs
* `superstruct` the `AttesterSlashing` (#5636)
* `superstruct` Attester Fork Variants
* Push a little further
* Deal with Encode / Decode of AttesterSlashing
* not so sure about this..
* Stop Encode/Decode Bounds from Propagating Out
* Tons of Changes..
* More Conversions to AttestationRef
* Add AsReference trait (#15)
* Add AsReference trait
* Fix some snafus
* Got it Compiling! :D
* Got Tests Building
* Get beacon chain tests compiling
---------
Co-authored-by: Michael Sproul <micsproul@gmail.com>
* Merge remote-tracking branch 'upstream/unstable' into electra_attestation_changes
* Make EF Tests Fork-Agnostic (#5713)
* Finish EF Test Fork Agnostic (#5714)
* Superstruct `AggregateAndProof` (#5715)
* Upgrade `superstruct` to `0.8.0`
* superstruct `AggregateAndProof`
* Merge remote-tracking branch 'sigp/unstable' into electra_attestation_changes
* cargo fmt
* Merge pull request #5726 from realbigsean/electra_attestation_changes
Merge unstable into Electra attestation changes
* process withdrawals updates
* cleanup withdrawals processing
* update `process_operations` deposit length check
* add apply_deposit changes
* add execution layer withdrawal request processing
* process deposit receipts
* add consolidation processing
* update process operations function
* exit updates
* clean up
* update slash_validator
* EIP7549 `get_attestation_indices` (#5657)
* get attesting indices electra impl
* fmt
* get tests to pass
* fmt
* fix some beacon chain tests
* fmt
* fix slasher test
* fmt got me again
* fix more tests
* fix tests
* Some small changes (#5739)
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* cargo fmt (#5740)
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* fix attestation verification
* Add new engine api methods
* Fix the versioning of v4 requests
* Handle new engine api methods in mock EL
* Note todo
* Fix todos
* Add support for electra fields in getPayloadBodies
* Add comments for potential versioning confusion
* udpates for aggregate attestation endpoint
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Sketch op pool changes
* fix get attesting indices (#5742)
* fix get attesting indices
* better errors
* fix compile
* only get committee index once
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Ef test fixes (#5753)
* attestation related ef test fixes
* delete commented out stuff
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Fix Aggregation Pool for Electra (#5754)
* Fix Aggregation Pool for Electra
* Remove Outdated Interface
* fix ssz (#5755)
* Get `electra_op_pool` up to date (#5756)
* fix get attesting indices (#5742)
* fix get attesting indices
* better errors
* fix compile
* only get committee index once
* Ef test fixes (#5753)
* attestation related ef test fixes
* delete commented out stuff
* Fix Aggregation Pool for Electra (#5754)
* Fix Aggregation Pool for Electra
* Remove Outdated Interface
* fix ssz (#5755)
---------
Co-authored-by: realbigsean <sean@sigmaprime.io>
* Revert "Get `electra_op_pool` up to date (#5756)" (#5757)
This reverts commit ab9e58aa3d.
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into electra_op_pool
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Compute on chain aggregate impl (#5752)
* add compute_on_chain_agg impl to op pool changes
* fmt
* get op pool tests to pass
* update beacon api aggregate attestationendpoint
* update the naive agg pool interface (#5760)
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* updates after merge
* Fix bugs in cross-committee aggregation
* Add comment to max cover optimisation
* Fix assert
* Electra epoch processing
* add deposit limit for old deposit queue
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Merge pull request #5749 from sigp/electra_op_pool
Optimise Electra op pool aggregation
* don't fail on empty consolidations
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* update committee offset
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* update committee offset
* update committee offset
* update committee offset
* only increment the state deposit index on old deposit flow
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* use correct max eb in epoch cache initialization
* drop initiate validator ordering optimization
* fix initiate exit for single pass
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* accept new payload v4 in mock el
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Fix Electra Fork Choice Tests (#5764)
* Fix Electra Fork Choice Tests (#5764)
* Fix Electra Fork Choice Tests (#5764)
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Fix Consolidation Sigs & Withdrawals
* Merge pull request #5766 from ethDreamer/two_fixes
Fix Consolidation Sigs & Withdrawals
* Merge branches 'block-processing-electra' and 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Send unagg attestation based on fork
* Fix ser/de
* Merge branch 'electra-engine-api' into beacon-api-electra
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* Subscribe to the correct subnets for electra attestations (#5782)
* subscribe to the correct att subnets for electra
* subscribe to the correct att subnets for electra
* cargo fmt
* update electra readiness with new endpoints
* fix slashing handling
* Fix Bug In Block Processing with 0x02 Credentials
* Merge remote-tracking branch 'upstream/unstable'
* Send unagg attestation based on fork
* Publish all aggregates
* just one more check bro plz..
* Merge pull request #5832 from ethDreamer/electra_attestation_changes_merge_unstable
Merge `unstable` into `electra_attestation_changes`
* Merge pull request #5835 from realbigsean/fix-validator-logic
Fix validator logic
* Merge pull request #5816 from realbigsean/electra-attestation-slashing-handling
Electra slashing handling
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* fix: serde rename camle case for execution payload body (#5846)
* Merge branch 'electra-engine-api' into beacon-api-electra
* Electra attestation changes rm decode impl (#5856)
* Remove Crappy Decode impl for Attestation
* Remove Inefficient Attestation Decode impl
* Implement Schema Upgrade / Downgrade
* Update beacon_node/beacon_chain/src/schema_change/migration_schema_v20.rs
Co-authored-by: Michael Sproul <micsproul@gmail.com>
---------
Co-authored-by: Michael Sproul <micsproul@gmail.com>
* Fix failing attestation tests and misc electra attestation cleanup (#5810)
* - get attestation related beacon chain tests to pass
- observed attestations are now keyed off of data + committee index
- rename op pool attestationref to compactattestationref
- remove unwraps in agg pool and use options instead
- cherry pick some changes from ef-tests-electra
* cargo fmt
* fix failing test
* Revert dockerfile changes
* make committee_index return option
* function args shouldnt be a ref to attestation ref
* fmt
* fix dup imports
---------
Co-authored-by: realbigsean <seananderson33@GMAIL.com>
* fix some todos (#5817)
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* add consolidations to merkle calc for inclusion proof
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Remove Duplicate KZG Commitment Merkle Proof Code (#5874)
* Remove Duplicate KZG Commitment Merkle Proof Code
* s/tree_lists/fields/
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* fix compile
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Fix slasher tests (#5906)
* Fix electra tests
* Add electra attestations to double vote tests
* Update superstruct to 0.8
* Merge remote-tracking branch 'origin/unstable' into electra_attestation_changes
* Small cleanup in slasher tests
* Clean up Electra observed aggregates (#5929)
* Use consistent key in observed_attestations
* Remove unwraps from observed aggregates
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* De-dup attestation constructor logic
* Remove unwraps in Attestation construction
* Dedup match_attestation_data
* Remove outdated TODO
* Use ForkName Ord in fork-choice tests
* Use ForkName Ord in BeaconBlockBody
* Make to_electra not fallible
* Remove TestRandom impl for IndexedAttestation
* Remove IndexedAttestation faulty Decode impl
* Drop TestRandom impl
* Add PendingAttestationInElectra
* Indexed att on disk (#35)
* indexed att on disk
* fix lints
* Update slasher/src/migrate.rs
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
---------
Co-authored-by: Lion - dapplion <35266934+dapplion@users.noreply.github.com>
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
* add electra fork enabled fn to ForkName impl (#36)
* add electra fork enabled fn to ForkName impl
* remove inadvertent file
* Update common/eth2/src/types.rs
Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
* Dedup attestation constructor logic in attester cache
* Use if let Ok for committee_bits
* Dedup Attestation constructor code
* Diff reduction in tests
* Fix beacon_chain tests
* Diff reduction
* Use Ord for ForkName in pubsub
* Resolve into_attestation_and_indices todo
* Remove stale TODO
* Fix beacon_chain tests
* Test spec invariant
* Use electra_enabled in pubsub
* Remove get_indexed_attestation_from_signed_aggregate
* Use ok_or instead of if let else
* committees are sorted
* remove dup method `get_indexed_attestation_from_committees`
* Merge pull request #5940 from dapplion/electra_attestation_changes_lionreview
Electra attestations #5712 review
* update default persisted op pool deserialization
* ensure aggregate and proof uses serde untagged on ref
* Fork aware ssz static attestation tests
* Electra attestation changes from Lions review (#5971)
* dedup/cleanup and remove unneeded hashset use
* remove irrelevant TODOs
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* Merge branch 'electra_attestation_changes' of https://github.com/sigp/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* Fix Compilation Break
* Merge pull request #5973 from ethDreamer/beacon-api-electra
Fix Compilation Break
* Electra attestation changes sean review (#5972)
* instantiate empty bitlist in unreachable code
* clean up error conversion
* fork enabled bool cleanup
* remove a couple todos
* return bools instead of options in `aggregate` and use the result
* delete commented out code
* use map macros in simple transformations
* remove signers_disjoint_from
* get ef tests compiling
* get ef tests compiling
* update intentionally excluded files
* Avoid changing slasher schema for Electra
* Delete slasher schema v4
* Fix clippy
* Fix compilation of beacon_chain tests
* Update database.rs
* Update per_block_processing.rs
* Add electra lightclient types
* Update slasher/src/database.rs
* fix imports
* Merge pull request #5980 from dapplion/electra-lightclient
Add electra lightclient types
* Merge pull request #5975 from michaelsproul/electra-slasher-no-migration
Avoid changing slasher schema for Electra
* Update beacon_node/beacon_chain/src/attestation_verification.rs
* Update beacon_node/beacon_chain/src/attestation_verification.rs
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra_attestation_changes
* Merge branch 'electra_attestation_changes' of https://github.com/realbigsean/lighthouse into block-processing-electra
* Merge branch 'block-processing-electra' of https://github.com/sigp/lighthouse into electra-epoch-proc
* Merge branch 'electra-epoch-proc' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* The great renaming receipt -> request
* Address some more review comments
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra-engine-api
* Update beacon_node/beacon_chain/src/electra_readiness.rs
* Update consensus/types/src/chain_spec.rs
* update GET requests
* update POST requests
* add client updates and test updates
* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra-engine-api
* Merge branch 'electra-engine-api' of https://github.com/sigp/lighthouse into beacon-api-electra
* compile after merge
* unwrap -> unwrap_err
* self review
* fix tests
* convert op pool messages to electra in electra
* remove methods to post without content header
* filter instead of convert
925 lines
31 KiB
Rust
925 lines
31 KiB
Rust
//! Generic tests that make use of the (newer) `InteractiveApiTester`
|
|
use beacon_chain::{
|
|
chain_config::{DisallowedReOrgOffsets, ReOrgThreshold},
|
|
test_utils::{AttestationStrategy, BlockStrategy, SyncCommitteeStrategy},
|
|
ChainConfig,
|
|
};
|
|
use beacon_processor::work_reprocessing_queue::ReprocessQueueMessage;
|
|
use eth2::types::ProduceBlockV3Response;
|
|
use eth2::types::{DepositContractData, StateId};
|
|
use execution_layer::{ForkchoiceState, PayloadAttributes};
|
|
use http_api::test_utils::InteractiveTester;
|
|
use parking_lot::Mutex;
|
|
use slot_clock::SlotClock;
|
|
use state_processing::{
|
|
per_block_processing::get_expected_withdrawals, state_advance::complete_state_advance,
|
|
};
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
use types::{
|
|
Address, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, MainnetEthSpec,
|
|
MinimalEthSpec, ProposerPreparationData, Slot,
|
|
};
|
|
|
|
type E = MainnetEthSpec;
|
|
|
|
// Test that the deposit_contract endpoint returns the correct chain_id and address.
|
|
// Regression test for https://github.com/sigp/lighthouse/issues/2657
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn deposit_contract_custom_network() {
|
|
let validator_count = 24;
|
|
let mut spec = E::default_spec();
|
|
|
|
// Rinkeby, which we don't use elsewhere.
|
|
spec.deposit_chain_id = 4;
|
|
spec.deposit_network_id = 4;
|
|
// Arbitrary contract address.
|
|
spec.deposit_contract_address = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap();
|
|
|
|
let tester = InteractiveTester::<E>::new(Some(spec.clone()), validator_count).await;
|
|
let client = &tester.client;
|
|
|
|
let result = client.get_config_deposit_contract().await.unwrap().data;
|
|
|
|
let expected = DepositContractData {
|
|
address: spec.deposit_contract_address,
|
|
chain_id: spec.deposit_chain_id,
|
|
};
|
|
|
|
assert_eq!(result, expected);
|
|
}
|
|
|
|
// Test that state lookups by root function correctly for states that are finalized but still
|
|
// present in the hot database, and have had their block pruned from fork choice.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn state_by_root_pruned_from_fork_choice() {
|
|
type E = MinimalEthSpec;
|
|
|
|
let validator_count = 24;
|
|
let spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
|
|
|
let tester = InteractiveTester::<E>::new_with_initializer_and_mutator(
|
|
Some(spec.clone()),
|
|
validator_count,
|
|
Some(Box::new(move |builder| {
|
|
builder
|
|
.deterministic_keypairs(validator_count)
|
|
.fresh_ephemeral_store()
|
|
.chain_config(ChainConfig {
|
|
epochs_per_migration: 1024,
|
|
..ChainConfig::default()
|
|
})
|
|
})),
|
|
None,
|
|
)
|
|
.await;
|
|
|
|
let client = &tester.client;
|
|
let harness = &tester.harness;
|
|
|
|
// Create some chain depth and finalize beyond fork choice's pruning depth.
|
|
let num_epochs = 8_u64;
|
|
let num_initial = num_epochs * E::slots_per_epoch();
|
|
harness.advance_slot();
|
|
harness
|
|
.extend_chain_with_sync(
|
|
num_initial as usize,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
SyncCommitteeStrategy::NoValidators,
|
|
)
|
|
.await;
|
|
|
|
// Should now be finalized.
|
|
let finalized_epoch = harness.finalized_checkpoint().epoch;
|
|
assert_eq!(finalized_epoch, num_epochs - 2);
|
|
|
|
// The split slot should still be at 0.
|
|
assert_eq!(harness.chain.store.get_split_slot(), 0);
|
|
|
|
// States that are between the split and the finalized slot should be able to be looked up by
|
|
// state root.
|
|
for slot in 0..finalized_epoch.start_slot(E::slots_per_epoch()).as_u64() {
|
|
let state_root = harness
|
|
.chain
|
|
.state_root_at_slot(Slot::new(slot))
|
|
.unwrap()
|
|
.unwrap();
|
|
let response = client
|
|
.get_debug_beacon_states::<E>(StateId::Root(state_root))
|
|
.await
|
|
.unwrap()
|
|
.unwrap();
|
|
|
|
assert!(response.metadata.finalized.unwrap());
|
|
assert!(!response.metadata.execution_optimistic.unwrap());
|
|
|
|
let mut state = response.data;
|
|
assert_eq!(state.update_tree_hash_cache().unwrap(), state_root);
|
|
}
|
|
}
|
|
|
|
/// Data structure for tracking fork choice updates received by the mock execution layer.
|
|
#[derive(Debug, Default)]
|
|
struct ForkChoiceUpdates {
|
|
updates: HashMap<ExecutionBlockHash, Vec<ForkChoiceUpdateMetadata>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct ForkChoiceUpdateMetadata {
|
|
received_at: Duration,
|
|
state: ForkchoiceState,
|
|
payload_attributes: Option<PayloadAttributes>,
|
|
}
|
|
|
|
impl ForkChoiceUpdates {
|
|
fn insert(&mut self, update: ForkChoiceUpdateMetadata) {
|
|
self.updates
|
|
.entry(update.state.head_block_hash)
|
|
.or_insert_with(Vec::new)
|
|
.push(update);
|
|
}
|
|
|
|
fn contains_update_for(&self, block_hash: ExecutionBlockHash) -> bool {
|
|
self.updates.contains_key(&block_hash)
|
|
}
|
|
|
|
/// Find the first fork choice update for `head_block_hash` with payload attributes for a
|
|
/// block proposal at `proposal_timestamp`.
|
|
fn first_update_with_payload_attributes(
|
|
&self,
|
|
head_block_hash: ExecutionBlockHash,
|
|
proposal_timestamp: u64,
|
|
) -> Option<ForkChoiceUpdateMetadata> {
|
|
self.updates
|
|
.get(&head_block_hash)?
|
|
.iter()
|
|
.find(|update| {
|
|
update
|
|
.payload_attributes
|
|
.as_ref()
|
|
.map_or(false, |payload_attributes| {
|
|
payload_attributes.timestamp() == proposal_timestamp
|
|
})
|
|
})
|
|
.cloned()
|
|
}
|
|
}
|
|
|
|
pub struct ReOrgTest {
|
|
head_slot: Slot,
|
|
/// Number of slots between parent block and canonical head.
|
|
parent_distance: u64,
|
|
/// Number of slots between head block and block proposal slot.
|
|
head_distance: u64,
|
|
re_org_threshold: u64,
|
|
max_epochs_since_finalization: u64,
|
|
percent_parent_votes: usize,
|
|
percent_empty_votes: usize,
|
|
percent_head_votes: usize,
|
|
should_re_org: bool,
|
|
misprediction: bool,
|
|
/// Whether to expect withdrawals to change on epoch boundaries.
|
|
expect_withdrawals_change_on_epoch: bool,
|
|
/// Epoch offsets to avoid proposing reorg blocks at.
|
|
disallowed_offsets: Vec<u64>,
|
|
}
|
|
|
|
impl Default for ReOrgTest {
|
|
/// Default config represents a regular easy re-org.
|
|
fn default() -> Self {
|
|
Self {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 2),
|
|
parent_distance: 1,
|
|
head_distance: 1,
|
|
re_org_threshold: 20,
|
|
max_epochs_since_finalization: 2,
|
|
percent_parent_votes: 100,
|
|
percent_empty_votes: 100,
|
|
percent_head_votes: 0,
|
|
should_re_org: true,
|
|
misprediction: false,
|
|
expect_withdrawals_change_on_epoch: false,
|
|
disallowed_offsets: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that the beacon node will try to perform proposer boost re-orgs on late blocks when
|
|
// configured.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_zero_weight() {
|
|
proposer_boost_re_org_test(ReOrgTest::default()).await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_epoch_boundary() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 1),
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_epoch_boundary_skip1() {
|
|
// Proposing a block on a boundary after a skip will change the set of expected withdrawals
|
|
// sent in the payload attributes.
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(2 * E::slots_per_epoch() - 2),
|
|
head_distance: 2,
|
|
should_re_org: false,
|
|
expect_withdrawals_change_on_epoch: true,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_epoch_boundary_skip32() {
|
|
// Propose a block at 64 after a whole epoch of skipped slots.
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 1),
|
|
head_distance: E::slots_per_epoch() + 1,
|
|
should_re_org: false,
|
|
expect_withdrawals_change_on_epoch: true,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_slot_after_epoch_boundary() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(33),
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_bad_ffg() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(64 + 22),
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_no_finality() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(96),
|
|
percent_parent_votes: 100,
|
|
percent_empty_votes: 0,
|
|
percent_head_votes: 100,
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_finality() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(129),
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_parent_distance() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 2),
|
|
parent_distance: 2,
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_head_distance() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 3),
|
|
head_distance: 2,
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Check that a re-org at a disallowed offset fails.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_disallowed_offset() {
|
|
let offset = 4;
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() + offset - 1),
|
|
disallowed_offsets: vec![offset],
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Check that a re-org at the *only* allowed offset succeeds.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_disallowed_offset_exact() {
|
|
let offset = 4;
|
|
let disallowed_offsets = (0..E::slots_per_epoch()).filter(|o| *o != offset).collect();
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() + offset - 1),
|
|
disallowed_offsets,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_very_unhealthy() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
head_slot: Slot::new(E::slots_per_epoch() - 1),
|
|
parent_distance: 2,
|
|
head_distance: 2,
|
|
percent_parent_votes: 10,
|
|
percent_empty_votes: 10,
|
|
percent_head_votes: 10,
|
|
should_re_org: false,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
/// The head block is late but still receives 30% of the committee vote, leading to a misprediction.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn proposer_boost_re_org_weight_misprediction() {
|
|
proposer_boost_re_org_test(ReOrgTest {
|
|
percent_empty_votes: 70,
|
|
percent_head_votes: 30,
|
|
should_re_org: false,
|
|
misprediction: true,
|
|
..Default::default()
|
|
})
|
|
.await;
|
|
}
|
|
|
|
/// Run a proposer boost re-org test.
|
|
///
|
|
/// - `head_slot`: the slot of the canonical head to be reorged
|
|
/// - `reorg_threshold`: committee percentage value for reorging
|
|
/// - `num_empty_votes`: percentage of comm of attestations for the parent block
|
|
/// - `num_head_votes`: number of attestations for the head block
|
|
/// - `should_re_org`: whether the proposer should build on the parent rather than the head
|
|
pub async fn proposer_boost_re_org_test(
|
|
ReOrgTest {
|
|
head_slot,
|
|
parent_distance,
|
|
head_distance,
|
|
re_org_threshold,
|
|
max_epochs_since_finalization,
|
|
percent_parent_votes,
|
|
percent_empty_votes,
|
|
percent_head_votes,
|
|
should_re_org,
|
|
misprediction,
|
|
expect_withdrawals_change_on_epoch,
|
|
disallowed_offsets,
|
|
}: ReOrgTest,
|
|
) {
|
|
assert!(head_slot > 0);
|
|
|
|
// Test using the latest fork so that we simulate conditions as similar to mainnet as possible.
|
|
let mut spec = ForkName::latest().make_genesis_spec(E::default_spec());
|
|
spec.terminal_total_difficulty = 1.into();
|
|
|
|
// Ensure there are enough validators to have `attesters_per_slot`.
|
|
let attesters_per_slot = 10;
|
|
let validator_count = E::slots_per_epoch() as usize * attesters_per_slot;
|
|
let all_validators = (0..validator_count).collect::<Vec<usize>>();
|
|
let num_initial = head_slot.as_u64().checked_sub(parent_distance + 1).unwrap();
|
|
|
|
// Check that the required vote percentages can be satisfied exactly using `attesters_per_slot`.
|
|
assert_eq!(100 % attesters_per_slot, 0);
|
|
let percent_per_attester = 100 / attesters_per_slot;
|
|
assert_eq!(percent_parent_votes % percent_per_attester, 0);
|
|
assert_eq!(percent_empty_votes % percent_per_attester, 0);
|
|
assert_eq!(percent_head_votes % percent_per_attester, 0);
|
|
let num_parent_votes = Some(attesters_per_slot * percent_parent_votes / 100);
|
|
let num_empty_votes = Some(attesters_per_slot * percent_empty_votes / 100);
|
|
let num_head_votes = Some(attesters_per_slot * percent_head_votes / 100);
|
|
|
|
let tester = InteractiveTester::<E>::new_with_initializer_and_mutator(
|
|
Some(spec),
|
|
validator_count,
|
|
None,
|
|
Some(Box::new(move |builder| {
|
|
builder
|
|
.proposer_re_org_head_threshold(Some(ReOrgThreshold(re_org_threshold)))
|
|
.proposer_re_org_max_epochs_since_finalization(Epoch::new(
|
|
max_epochs_since_finalization,
|
|
))
|
|
.proposer_re_org_disallowed_offsets(
|
|
DisallowedReOrgOffsets::new::<E>(disallowed_offsets).unwrap(),
|
|
)
|
|
})),
|
|
)
|
|
.await;
|
|
let harness = &tester.harness;
|
|
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
|
|
let execution_ctx = mock_el.server.ctx.clone();
|
|
let slot_clock = &harness.chain.slot_clock;
|
|
|
|
// Move to terminal block.
|
|
mock_el.server.all_payloads_valid();
|
|
execution_ctx
|
|
.execution_block_generator
|
|
.write()
|
|
.move_to_terminal_block()
|
|
.unwrap();
|
|
|
|
// Send proposer preparation data for all validators.
|
|
let proposer_preparation_data = all_validators
|
|
.iter()
|
|
.map(|i| ProposerPreparationData {
|
|
validator_index: *i as u64,
|
|
fee_recipient: Address::from_low_u64_be(*i as u64),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
harness
|
|
.chain
|
|
.execution_layer
|
|
.as_ref()
|
|
.unwrap()
|
|
.update_proposer_preparation(
|
|
head_slot.epoch(E::slots_per_epoch()) + 1,
|
|
&proposer_preparation_data,
|
|
)
|
|
.await;
|
|
|
|
// Create some chain depth. Sign sync committee signatures so validator balances don't dip
|
|
// below 32 ETH and become ineligible for withdrawals.
|
|
harness.advance_slot();
|
|
harness
|
|
.extend_chain_with_sync(
|
|
num_initial as usize,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
SyncCommitteeStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
// Start collecting fork choice updates.
|
|
let forkchoice_updates = Arc::new(Mutex::new(ForkChoiceUpdates::default()));
|
|
let forkchoice_updates_inner = forkchoice_updates.clone();
|
|
let chain_inner = harness.chain.clone();
|
|
|
|
execution_ctx
|
|
.hook
|
|
.lock()
|
|
.set_forkchoice_updated_hook(Box::new(move |state, payload_attributes| {
|
|
let received_at = chain_inner.slot_clock.now_duration().unwrap();
|
|
let state = ForkchoiceState::from(state);
|
|
let payload_attributes = payload_attributes.map(Into::into);
|
|
let update = ForkChoiceUpdateMetadata {
|
|
received_at,
|
|
state,
|
|
payload_attributes,
|
|
};
|
|
forkchoice_updates_inner.lock().insert(update);
|
|
None
|
|
}));
|
|
|
|
// We set up the following block graph, where B is a block that arrives late and is re-orged
|
|
// by C.
|
|
//
|
|
// A | B | - |
|
|
// ^ | - | C |
|
|
|
|
let slot_a = Slot::new(num_initial + 1);
|
|
let slot_b = slot_a + parent_distance;
|
|
let slot_c = slot_b + head_distance;
|
|
|
|
// We need to transition to at least epoch 2 in order to trigger
|
|
// `process_rewards_and_penalties`. This allows us to test withdrawals changes at epoch
|
|
// boundaries.
|
|
if expect_withdrawals_change_on_epoch {
|
|
assert!(
|
|
slot_c.epoch(E::slots_per_epoch()) >= 2,
|
|
"for withdrawals to change, test must end at an epoch >= 2"
|
|
);
|
|
}
|
|
|
|
harness.advance_slot();
|
|
let (block_a_root, block_a, mut state_a) = harness
|
|
.add_block_at_slot(slot_a, harness.get_current_state())
|
|
.await
|
|
.unwrap();
|
|
let state_a_root = state_a.canonical_root().unwrap();
|
|
|
|
// Attest to block A during slot A.
|
|
let (block_a_parent_votes, _) = harness.make_attestations_with_limit(
|
|
&all_validators,
|
|
&state_a,
|
|
state_a_root,
|
|
block_a_root,
|
|
slot_a,
|
|
num_parent_votes,
|
|
);
|
|
harness.process_attestations(block_a_parent_votes);
|
|
|
|
// Attest to block A during slot B.
|
|
for _ in 0..parent_distance {
|
|
harness.advance_slot();
|
|
}
|
|
let (block_a_empty_votes, block_a_attesters) = harness.make_attestations_with_limit(
|
|
&all_validators,
|
|
&state_a,
|
|
state_a_root,
|
|
block_a_root,
|
|
slot_b,
|
|
num_empty_votes,
|
|
);
|
|
harness.process_attestations(block_a_empty_votes);
|
|
|
|
let remaining_attesters = all_validators
|
|
.iter()
|
|
.copied()
|
|
.filter(|index| !block_a_attesters.contains(index))
|
|
.collect::<Vec<_>>();
|
|
|
|
// Produce block B and process it halfway through the slot.
|
|
let (block_b, mut state_b) = harness.make_block(state_a.clone(), slot_b).await;
|
|
let state_b_root = state_b.canonical_root().unwrap();
|
|
let block_b_root = block_b.0.canonical_root();
|
|
|
|
let obs_time = slot_clock.start_of(slot_b).unwrap() + slot_clock.slot_duration() / 2;
|
|
slot_clock.set_current_time(obs_time);
|
|
harness.chain.block_times_cache.write().set_time_observed(
|
|
block_b_root,
|
|
slot_b,
|
|
obs_time,
|
|
None,
|
|
None,
|
|
);
|
|
harness.process_block_result(block_b.clone()).await.unwrap();
|
|
|
|
// Add attestations to block B.
|
|
let (block_b_head_votes, _) = harness.make_attestations_with_limit(
|
|
&remaining_attesters,
|
|
&state_b,
|
|
state_b_root,
|
|
block_b_root.into(),
|
|
slot_b,
|
|
num_head_votes,
|
|
);
|
|
harness.process_attestations(block_b_head_votes);
|
|
|
|
let payload_lookahead = harness.chain.config.prepare_payload_lookahead;
|
|
let fork_choice_lookahead = Duration::from_millis(500);
|
|
while harness.get_current_slot() != slot_c {
|
|
let current_slot = harness.get_current_slot();
|
|
let next_slot = current_slot + 1;
|
|
|
|
// Simulate the scheduled call to prepare proposers at 8 seconds into the slot.
|
|
harness.advance_to_slot_lookahead(next_slot, payload_lookahead);
|
|
harness
|
|
.chain
|
|
.prepare_beacon_proposer(current_slot)
|
|
.await
|
|
.unwrap();
|
|
|
|
// Simulate the scheduled call to fork choice + prepare proposers 500ms before the
|
|
// next slot.
|
|
harness.advance_to_slot_lookahead(next_slot, fork_choice_lookahead);
|
|
harness.chain.recompute_head_at_slot(next_slot).await;
|
|
harness
|
|
.chain
|
|
.prepare_beacon_proposer(current_slot)
|
|
.await
|
|
.unwrap();
|
|
|
|
harness.advance_slot();
|
|
harness.chain.per_slot_task().await;
|
|
}
|
|
|
|
// Produce block C.
|
|
// Advance state_b so we can get the proposer.
|
|
assert_eq!(state_b.slot(), slot_b);
|
|
let pre_advance_withdrawals = get_expected_withdrawals(&state_b, &harness.chain.spec)
|
|
.unwrap()
|
|
.0
|
|
.to_vec();
|
|
complete_state_advance(&mut state_b, None, slot_c, &harness.chain.spec).unwrap();
|
|
|
|
let proposer_index = state_b
|
|
.get_beacon_proposer_index(slot_c, &harness.chain.spec)
|
|
.unwrap();
|
|
let randao_reveal = harness
|
|
.sign_randao_reveal(&state_b, proposer_index, slot_c)
|
|
.into();
|
|
let (unsigned_block_type, _) = tester
|
|
.client
|
|
.get_validator_blocks_v3::<E>(slot_c, &randao_reveal, None, None)
|
|
.await
|
|
.unwrap();
|
|
|
|
let (unsigned_block_c, block_c_blobs) = match unsigned_block_type.data {
|
|
ProduceBlockV3Response::Full(unsigned_block_contents_c) => {
|
|
unsigned_block_contents_c.deconstruct()
|
|
}
|
|
ProduceBlockV3Response::Blinded(_) => {
|
|
panic!("Should not be a blinded block");
|
|
}
|
|
};
|
|
let block_c = Arc::new(harness.sign_beacon_block(unsigned_block_c, &state_b));
|
|
|
|
if should_re_org {
|
|
// Block C should build on A.
|
|
assert_eq!(block_c.parent_root(), block_a_root.into());
|
|
} else {
|
|
// Block C should build on B.
|
|
assert_eq!(block_c.parent_root(), block_b_root);
|
|
}
|
|
|
|
// Applying block C should cause it to become head regardless (re-org or continuation).
|
|
let block_root_c = harness
|
|
.process_block_result((block_c.clone(), block_c_blobs))
|
|
.await
|
|
.unwrap()
|
|
.into();
|
|
assert_eq!(harness.head_block_root(), block_root_c);
|
|
|
|
// Check the fork choice updates that were sent.
|
|
let forkchoice_updates = forkchoice_updates.lock();
|
|
let block_a_exec_hash = block_a
|
|
.0
|
|
.message()
|
|
.execution_payload()
|
|
.unwrap()
|
|
.block_hash();
|
|
let block_b_exec_hash = block_b
|
|
.0
|
|
.message()
|
|
.execution_payload()
|
|
.unwrap()
|
|
.block_hash();
|
|
|
|
let block_c_timestamp = block_c.message().execution_payload().unwrap().timestamp();
|
|
|
|
// If we re-orged then no fork choice update for B should have been sent.
|
|
assert_eq!(
|
|
should_re_org,
|
|
!forkchoice_updates.contains_update_for(block_b_exec_hash),
|
|
"{block_b_exec_hash:?}"
|
|
);
|
|
|
|
// Check the timing of the first fork choice update with payload attributes for block C.
|
|
let c_parent_hash = if should_re_org {
|
|
block_a_exec_hash
|
|
} else {
|
|
block_b_exec_hash
|
|
};
|
|
let first_update = forkchoice_updates
|
|
.first_update_with_payload_attributes(c_parent_hash, block_c_timestamp)
|
|
.unwrap();
|
|
let payload_attribs = first_update.payload_attributes.as_ref().unwrap();
|
|
|
|
// Check that withdrawals from the payload attributes match those computed from the parent's
|
|
// advanced state.
|
|
let expected_withdrawals = if should_re_org {
|
|
let mut state_a_advanced = state_a.clone();
|
|
complete_state_advance(&mut state_a_advanced, None, slot_c, &harness.chain.spec).unwrap();
|
|
get_expected_withdrawals(&state_a_advanced, &harness.chain.spec)
|
|
} else {
|
|
get_expected_withdrawals(&state_b, &harness.chain.spec)
|
|
}
|
|
.unwrap()
|
|
.0
|
|
.to_vec();
|
|
let payload_attribs_withdrawals = payload_attribs.withdrawals().unwrap();
|
|
assert_eq!(expected_withdrawals, *payload_attribs_withdrawals);
|
|
assert!(!expected_withdrawals.is_empty());
|
|
|
|
if should_re_org
|
|
|| expect_withdrawals_change_on_epoch
|
|
&& slot_c.epoch(E::slots_per_epoch()) != slot_b.epoch(E::slots_per_epoch())
|
|
{
|
|
assert_ne!(expected_withdrawals, pre_advance_withdrawals);
|
|
}
|
|
|
|
// Check that the `parent_beacon_block_root` of the payload attributes are correct.
|
|
if let Ok(parent_beacon_block_root) = payload_attribs.parent_beacon_block_root() {
|
|
assert_eq!(parent_beacon_block_root, block_c.parent_root());
|
|
}
|
|
|
|
let lookahead = slot_clock
|
|
.start_of(slot_c)
|
|
.unwrap()
|
|
.checked_sub(first_update.received_at)
|
|
.unwrap();
|
|
|
|
if !misprediction {
|
|
assert_eq!(
|
|
lookahead,
|
|
payload_lookahead,
|
|
"lookahead={lookahead:?}, timestamp={}, prev_randao={:?}",
|
|
payload_attribs.timestamp(),
|
|
payload_attribs.prev_randao(),
|
|
);
|
|
} else {
|
|
// On a misprediction we issue the first fcU 500ms before creating a block!
|
|
assert_eq!(
|
|
lookahead,
|
|
fork_choice_lookahead,
|
|
"timestamp={}, prev_randao={:?}",
|
|
payload_attribs.timestamp(),
|
|
payload_attribs.prev_randao(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Test that running fork choice before proposing results in selection of the correct head.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
pub async fn fork_choice_before_proposal() {
|
|
// Validator count needs to be at least 32 or proposer boost gets set to 0 when computing
|
|
// `validator_count // 32`.
|
|
let validator_count = 64;
|
|
let all_validators = (0..validator_count).collect::<Vec<_>>();
|
|
let num_initial: u64 = 31;
|
|
|
|
let tester = InteractiveTester::<E>::new(None, validator_count).await;
|
|
let harness = &tester.harness;
|
|
|
|
// Create some chain depth.
|
|
harness.advance_slot();
|
|
harness
|
|
.extend_chain(
|
|
num_initial as usize,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
// We set up the following block graph, where B is a block that is temporarily orphaned by C,
|
|
// but is then reinstated and built upon by D.
|
|
//
|
|
// A | B | - | D |
|
|
// ^ | - | C |
|
|
let slot_a = Slot::new(num_initial);
|
|
let slot_b = slot_a + 1;
|
|
let slot_c = slot_a + 2;
|
|
let slot_d = slot_a + 3;
|
|
|
|
let state_a = harness.get_current_state();
|
|
let (block_b, mut state_b) = harness.make_block(state_a.clone(), slot_b).await;
|
|
let block_root_b = harness
|
|
.process_block(slot_b, block_b.0.canonical_root(), block_b)
|
|
.await
|
|
.unwrap();
|
|
let state_root_b = state_b.canonical_root().unwrap();
|
|
|
|
// Create attestations to B but keep them in reserve until after C has been processed.
|
|
let attestations_b = harness.make_attestations(
|
|
&all_validators,
|
|
&state_b,
|
|
state_root_b,
|
|
block_root_b,
|
|
slot_b,
|
|
);
|
|
|
|
let (block_c, mut state_c) = harness.make_block(state_a, slot_c).await;
|
|
let block_root_c = harness
|
|
.process_block(slot_c, block_c.0.canonical_root(), block_c.clone())
|
|
.await
|
|
.unwrap();
|
|
let state_root_c = state_c.canonical_root().unwrap();
|
|
|
|
// Create attestations to C from a small number of validators and process them immediately.
|
|
let attestations_c = harness.make_attestations(
|
|
&all_validators[..validator_count / 2],
|
|
&state_c,
|
|
state_root_c,
|
|
block_root_c,
|
|
slot_c,
|
|
);
|
|
harness.process_attestations(attestations_c);
|
|
|
|
// Apply the attestations to B, but don't re-run fork choice.
|
|
harness.process_attestations(attestations_b);
|
|
|
|
// Due to proposer boost, the head should be C during slot C.
|
|
assert_eq!(
|
|
harness.chain.canonical_head.cached_head().head_block_root(),
|
|
block_root_c.into()
|
|
);
|
|
|
|
// Ensure that building a block via the HTTP API re-runs fork choice and builds block D upon B.
|
|
// Manually prod the per-slot task, because the slot timer doesn't run in the background in
|
|
// these tests.
|
|
harness.advance_slot();
|
|
harness.chain.per_slot_task().await;
|
|
|
|
let proposer_index = state_b
|
|
.get_beacon_proposer_index(slot_d, &harness.chain.spec)
|
|
.unwrap();
|
|
let randao_reveal = harness
|
|
.sign_randao_reveal(&state_b, proposer_index, slot_d)
|
|
.into();
|
|
let block_d = tester
|
|
.client
|
|
.get_validator_blocks::<E>(slot_d, &randao_reveal, None)
|
|
.await
|
|
.unwrap()
|
|
.data
|
|
.deconstruct()
|
|
.0;
|
|
|
|
// Head is now B.
|
|
assert_eq!(
|
|
harness.chain.canonical_head.cached_head().head_block_root(),
|
|
block_root_b.into()
|
|
);
|
|
// D's parent is B.
|
|
assert_eq!(block_d.parent_root(), block_root_b.into());
|
|
}
|
|
|
|
// Test that attestations to unknown blocks are requeued and processed when their block arrives.
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
async fn queue_attestations_from_http() {
|
|
let validator_count = 128;
|
|
let all_validators = (0..validator_count).collect::<Vec<_>>();
|
|
|
|
let tester = InteractiveTester::<E>::new(None, validator_count).await;
|
|
let harness = &tester.harness;
|
|
let client = tester.client.clone();
|
|
|
|
let num_initial = 5;
|
|
|
|
// Slot of the block attested to.
|
|
let attestation_slot = Slot::new(num_initial) + 1;
|
|
|
|
// Make some initial blocks.
|
|
harness.advance_slot();
|
|
harness
|
|
.extend_chain(
|
|
num_initial as usize,
|
|
BlockStrategy::OnCanonicalHead,
|
|
AttestationStrategy::AllValidators,
|
|
)
|
|
.await;
|
|
|
|
harness.advance_slot();
|
|
assert_eq!(harness.get_current_slot(), attestation_slot);
|
|
|
|
// Make the attested-to block without applying it.
|
|
let pre_state = harness.get_current_state();
|
|
let (block, post_state) = harness.make_block(pre_state, attestation_slot).await;
|
|
let block_root = block.0.canonical_root();
|
|
|
|
// Make attestations to the block and POST them to the beacon node on a background thread.
|
|
let attestations = harness
|
|
.make_unaggregated_attestations(
|
|
&all_validators,
|
|
&post_state,
|
|
block.0.state_root(),
|
|
block_root.into(),
|
|
attestation_slot,
|
|
)
|
|
.into_iter()
|
|
.flat_map(|attestations| attestations.into_iter().map(|(att, _subnet)| att))
|
|
.collect::<Vec<_>>();
|
|
|
|
let fork_name = tester.harness.spec.fork_name_at_slot::<E>(attestation_slot);
|
|
let attestation_future = tokio::spawn(async move {
|
|
client
|
|
.post_beacon_pool_attestations_v2(&attestations, fork_name)
|
|
.await
|
|
.expect("attestations should be processed successfully")
|
|
});
|
|
|
|
// In parallel, apply the block. We need to manually notify the reprocess queue, because the
|
|
// `beacon_chain` does not know about the queue and will not update it for us.
|
|
let parent_root = block.0.parent_root();
|
|
harness
|
|
.process_block(attestation_slot, block_root, block)
|
|
.await
|
|
.unwrap();
|
|
tester
|
|
.ctx
|
|
.beacon_processor_reprocess_send
|
|
.as_ref()
|
|
.unwrap()
|
|
.send(ReprocessQueueMessage::BlockImported {
|
|
block_root,
|
|
parent_root,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
attestation_future.await.unwrap();
|
|
}
|