Merge branch 'unstable' of https://github.com/sigp/lighthouse into merge-unstable-deneb-jul-14

This commit is contained in:
realbigsean 2023-07-17 09:33:37 -04:00
commit b96db45090
No known key found for this signature in database
GPG Key ID: BE1B3DB104F6C788
51 changed files with 642 additions and 294 deletions

View File

@ -58,8 +58,8 @@ jobs:
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install anvil
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests in release
run: make test-release
release-tests-windows:
@ -78,9 +78,8 @@ jobs:
run: |
choco install python protoc visualstudio2019-workload-vctools -y
npm config set msvs_version 2019
- name: Install anvil
# Extra feature to work around https://github.com/foundry-rs/foundry/issues/5115
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil --features ethers/ipc
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Install make
run: choco install -y make
- uses: KyleMayes/install-llvm-action@v1
@ -155,8 +154,8 @@ jobs:
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install anvil
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests in debug
run: make test-debug
state-transition-vectors-ubuntu:
@ -211,8 +210,8 @@ jobs:
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install anvil
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Run the beacon chain sim that starts from an eth1 contract
run: cargo run --release --bin simulator eth1-sim
merge-transition-ubuntu:
@ -227,8 +226,8 @@ jobs:
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install anvil
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Run the beacon chain sim and go through the merge transition
run: cargo run --release --bin simulator eth1-sim --post-merge
no-eth1-simulator-ubuntu:
@ -257,8 +256,8 @@ jobs:
uses: arduino/setup-protoc@e52d9eb8f7b63115df1ac544a1376fdbf5a39612
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install anvil
run: cargo install --git https://github.com/foundry-rs/foundry --locked anvil
- name: Install Foundry (anvil)
uses: foundry-rs/foundry-toolchain@v1
- name: Run the syncing simulator
run: cargo run --release --bin simulator syncing-sim
doppelganger-protection-test:

97
Cargo.lock generated
View File

@ -257,6 +257,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "anvil-rpc"
version = "0.1.0"
source = "git+https://github.com/foundry-rs/foundry?rev=b45456717ffae1af65acdc71099f8cb95e6683a0#b45456717ffae1af65acdc71099f8cb95e6683a0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "anyhow"
version = "1.0.71"
@ -506,9 +515,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.5.17"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43"
checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
dependencies = [
"async-trait",
"axum-core",
@ -524,22 +533,23 @@ dependencies = [
"mime",
"percent-encoding",
"pin-project-lite 0.2.9",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.2.9"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
@ -547,6 +557,7 @@ dependencies = [
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
@ -605,7 +616,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "beacon-api-client"
version = "0.1.0"
source = "git+https://github.com/ralexstokes/beacon-api-client#30679e9e25d61731cde54e14cd8a3688a39d8e5b"
source = "git+https://github.com/ralexstokes/beacon-api-client?rev=93d7e8c#93d7e8c38fe9782c4862909663e7b57c44f805a9"
dependencies = [
"ethereum-consensus",
"http",
@ -2605,7 +2616,7 @@ dependencies = [
[[package]]
name = "ethereum-consensus"
version = "0.1.1"
source = "git+https://github.com/ralexstokes//ethereum-consensus?rev=9b0ee0a8a45b968c8df5e7e64ea1c094e16f053d#9b0ee0a8a45b968c8df5e7e64ea1c094e16f053d"
source = "git+https://github.com/ralexstokes/ethereum-consensus?rev=e380108#e380108d15fcc40349927fdf3d11c71f9edb67c2"
dependencies = [
"async-stream",
"blst",
@ -2618,8 +2629,9 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_json",
"serde_yaml",
"sha2 0.9.9",
"ssz-rs",
"ssz_rs",
"thiserror",
"tokio",
"tokio-stream",
@ -2893,7 +2905,7 @@ dependencies = [
"serde_json",
"slog",
"slot_clock",
"ssz-rs",
"ssz_rs",
"ssz_types",
"state_processing",
"strum",
@ -3618,12 +3630,6 @@ dependencies = [
"pin-project-lite 0.2.9",
]
[[package]]
name = "http-range-header"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
[[package]]
name = "http_api"
version = "0.1.0"
@ -5104,9 +5110,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.5.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
[[package]]
name = "md-5"
@ -5195,16 +5201,20 @@ dependencies = [
[[package]]
name = "mev-rs"
version = "0.2.1"
source = "git+https://github.com/ralexstokes//mev-rs?rev=7813d4a4a564e0754e9aaab2d95520ba437c3889#7813d4a4a564e0754e9aaab2d95520ba437c3889"
version = "0.3.0"
source = "git+https://github.com/ralexstokes/mev-rs?rev=216657016d5c0889b505857c89ae42c7aa2764af#216657016d5c0889b505857c89ae42c7aa2764af"
dependencies = [
"anvil-rpc",
"async-trait",
"axum",
"beacon-api-client",
"ethereum-consensus",
"hyper",
"parking_lot 0.12.1",
"reqwest",
"serde",
"ssz-rs",
"serde_json",
"ssz_rs",
"thiserror",
"tokio",
"tracing",
@ -5655,6 +5665,7 @@ dependencies = [
"execution_layer",
"sensitive_url",
"tempfile",
"tokio",
"types",
"validator_client",
"validator_dir",
@ -7492,6 +7503,16 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_path_to_error"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1b6471d7496b051e03f1958802a73f88b947866f5146f329e47e36554f4e55"
dependencies = [
"itoa",
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.12"
@ -8001,23 +8022,24 @@ dependencies = [
]
[[package]]
name = "ssz-rs"
version = "0.8.0"
source = "git+https://github.com/ralexstokes//ssz-rs?rev=adf1a0b14cef90b9536f28ef89da1fab316465e1#adf1a0b14cef90b9536f28ef89da1fab316465e1"
name = "ssz_rs"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057291e5631f280978fa9c8009390663ca4613359fc1318e36a8c24c392f6d1f"
dependencies = [
"bitvec 1.0.1",
"hex",
"num-bigint",
"serde",
"sha2 0.9.9",
"ssz-rs-derive",
"thiserror",
"ssz_rs_derive",
]
[[package]]
name = "ssz-rs-derive"
version = "0.8.0"
source = "git+https://github.com/ralexstokes//ssz-rs?rev=adf1a0b14cef90b9536f28ef89da1fab316465e1#adf1a0b14cef90b9536f28ef89da1fab316465e1"
name = "ssz_rs_derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f07d54c4d01a1713eb363b55ba51595da15f6f1211435b71466460da022aa140"
dependencies = [
"proc-macro2",
"quote",
@ -8812,25 +8834,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
dependencies = [
"bitflags 1.3.2",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-range-header",
"pin-project-lite 0.2.9",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.2"

View File

@ -93,13 +93,6 @@ resolver = "2"
warp = { git = "https://github.com/macladson/warp", rev="7e75acc368229a46a236a8c991bf251fe7fe50ef" }
arbitrary = { git = "https://github.com/michaelsproul/arbitrary", rev="f002b99989b561ddce62e4cf2887b0f8860ae991" }
[patch."https://github.com/ralexstokes/mev-rs"]
mev-rs = { git = "https://github.com/ralexstokes//mev-rs", rev = "7813d4a4a564e0754e9aaab2d95520ba437c3889" }
[patch."https://github.com/ralexstokes/ethereum-consensus"]
ethereum-consensus = { git = "https://github.com/ralexstokes//ethereum-consensus", rev = "9b0ee0a8a45b968c8df5e7e64ea1c094e16f053d" }
[patch."https://github.com/ralexstokes/ssz-rs"]
ssz-rs = { git = "https://github.com/ralexstokes//ssz-rs", rev = "adf1a0b14cef90b9536f28ef89da1fab316465e1" }
[profile.maxperf]
inherits = "release"
lto = "fat"

View File

@ -86,7 +86,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let ideal_reward = reward_numerator
.safe_div(active_increments)?
.safe_div(WEIGHT_DENOMINATOR)?;
if !state.is_in_inactivity_leak(previous_epoch, spec) {
if !state.is_in_inactivity_leak(previous_epoch, spec)? {
ideal_rewards_hashmap
.insert((flag_index, effective_balance), (ideal_reward, penalty));
} else {

View File

@ -236,6 +236,17 @@ pub struct PrePayloadAttributes {
pub parent_block_number: u64,
}
/// Information about a state/block at a specific slot.
#[derive(Debug, Clone, Copy)]
pub struct FinalizationAndCanonicity {
/// True if the slot of the state or block is finalized.
///
/// This alone DOES NOT imply that the state/block is finalized, use `self.is_finalized()`.
pub slot_is_finalized: bool,
/// True if the state or block is canonical at its slot.
pub canonical: bool,
}
/// Define whether a forkchoiceUpdate needs to be checked for an override (`Yes`) or has already
/// been checked (`AlreadyApplied`). It is safe to specify `Yes` even if re-orgs are disabled.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
@ -470,6 +481,12 @@ pub struct BeaconChain<T: BeaconChainTypes> {
type BeaconBlockAndState<T, Payload> = (BeaconBlock<T, Payload>, BeaconState<T>);
impl FinalizationAndCanonicity {
pub fn is_finalized(self) -> bool {
self.slot_is_finalized && self.canonical
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Checks if a block is finalized.
/// The finalization check is done with the block slot. The block root is used to verify that
@ -499,16 +516,30 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
state_root: &Hash256,
state_slot: Slot,
) -> Result<bool, Error> {
self.state_finalization_and_canonicity(state_root, state_slot)
.map(FinalizationAndCanonicity::is_finalized)
}
/// Fetch the finalization and canonicity status of the state with `state_root`.
pub fn state_finalization_and_canonicity(
&self,
state_root: &Hash256,
state_slot: Slot,
) -> Result<FinalizationAndCanonicity, Error> {
let finalized_slot = self
.canonical_head
.cached_head()
.finalized_checkpoint()
.epoch
.start_slot(T::EthSpec::slots_per_epoch());
let is_canonical = self
let slot_is_finalized = state_slot <= finalized_slot;
let canonical = self
.state_root_at_slot(state_slot)?
.map_or(false, |canonical_root| state_root == &canonical_root);
Ok(state_slot <= finalized_slot && is_canonical)
Ok(FinalizationAndCanonicity {
slot_is_finalized,
canonical,
})
}
/// Persists the head tracker and fork choice.

View File

@ -83,6 +83,8 @@ pub struct ChainConfig {
pub enable_backfill_rate_limiting: bool,
/// Whether to use `ProgressiveBalancesCache` in unrealized FFG progression calculation.
pub progressive_balances_mode: ProgressiveBalancesMode,
/// Number of epochs between each migration of data from the hot database to the freezer.
pub epochs_per_migration: u64,
}
impl Default for ChainConfig {
@ -114,6 +116,7 @@ impl Default for ChainConfig {
always_prepare_payload: false,
enable_backfill_rate_limiting: true,
progressive_balances_mode: ProgressiveBalancesMode::Checked,
epochs_per_migration: crate::migrate::DEFAULT_EPOCHS_PER_MIGRATION,
}
}
}

View File

@ -81,6 +81,7 @@ pub use execution_payload::NotifyExecutionLayer;
pub use fork_choice::{ExecutionStatus, ForkchoiceUpdateParameters};
pub use kzg::TrustedSetup;
pub use metrics::scrape_for_metrics;
pub use migrate::MigratorConfig;
pub use parking_lot;
pub use slot_clock;
pub use state_processing::per_block_processing::errors::{

View File

@ -25,10 +25,15 @@ const MIN_COMPACTION_PERIOD_SECONDS: u64 = 7200;
/// Compact after a large finality gap, if we respect `MIN_COMPACTION_PERIOD_SECONDS`.
const COMPACTION_FINALITY_DISTANCE: u64 = 1024;
/// Default number of epochs to wait between finalization migrations.
pub const DEFAULT_EPOCHS_PER_MIGRATION: u64 = 1;
/// The background migrator runs a thread to perform pruning and migrate state from the hot
/// to the cold database.
pub struct BackgroundMigrator<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
db: Arc<HotColdDB<E, Hot, Cold>>,
/// Record of when the last migration ran, for enforcing `epochs_per_migration`.
prev_migration: Arc<Mutex<PrevMigration>>,
#[allow(clippy::type_complexity)]
tx_thread: Option<Mutex<(mpsc::Sender<Notification>, thread::JoinHandle<()>)>>,
/// Genesis block root, for persisting the `PersistedBeaconChain`.
@ -36,9 +41,22 @@ pub struct BackgroundMigrator<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>
log: Logger,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MigratorConfig {
pub blocking: bool,
/// Run migrations at most once per `epochs_per_migration`.
///
/// If set to 0 or 1, then run every finalization.
pub epochs_per_migration: u64,
}
impl Default for MigratorConfig {
fn default() -> Self {
Self {
blocking: false,
epochs_per_migration: DEFAULT_EPOCHS_PER_MIGRATION,
}
}
}
impl MigratorConfig {
@ -46,6 +64,19 @@ impl MigratorConfig {
self.blocking = true;
self
}
pub fn epochs_per_migration(mut self, epochs_per_migration: u64) -> Self {
self.epochs_per_migration = epochs_per_migration;
self
}
}
/// Record of when the last migration ran.
pub struct PrevMigration {
/// The epoch at which the last finalization migration ran.
epoch: Epoch,
/// The number of epochs to wait between runs.
epochs_per_migration: u64,
}
/// Pruning can be successful, or in rare cases deferred to a later point.
@ -93,6 +124,7 @@ pub struct FinalizationNotification {
finalized_state_root: BeaconStateHash,
finalized_checkpoint: Checkpoint,
head_tracker: Arc<HeadTracker>,
prev_migration: Arc<Mutex<PrevMigration>>,
genesis_block_root: Hash256,
}
@ -104,6 +136,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
genesis_block_root: Hash256,
log: Logger,
) -> Self {
// Estimate last migration run from DB split slot.
let prev_migration = Arc::new(Mutex::new(PrevMigration {
epoch: db.get_split_slot().epoch(E::slots_per_epoch()),
epochs_per_migration: config.epochs_per_migration,
}));
let tx_thread = if config.blocking {
None
} else {
@ -112,6 +149,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
Self {
db,
tx_thread,
prev_migration,
genesis_block_root,
log,
}
@ -132,6 +170,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
finalized_state_root,
finalized_checkpoint,
head_tracker,
prev_migration: self.prev_migration.clone(),
genesis_block_root: self.genesis_block_root,
};
@ -227,6 +266,26 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
notif: FinalizationNotification,
log: &Logger,
) {
// Do not run too frequently.
let epoch = notif.finalized_checkpoint.epoch;
let mut prev_migration = notif.prev_migration.lock();
if epoch < prev_migration.epoch + prev_migration.epochs_per_migration {
debug!(
log,
"Database consolidation deferred";
"last_finalized_epoch" => prev_migration.epoch,
"new_finalized_epoch" => epoch,
"epochs_per_migration" => prev_migration.epochs_per_migration,
);
return;
}
// Update the previous migration epoch immediately to avoid holding the lock. If the
// migration doesn't succeed then the next migration will be retried at the next scheduled
// run.
prev_migration.epoch = epoch;
drop(prev_migration);
debug!(log, "Database consolidation started");
let finalized_state_root = notif.finalized_state_root;

View File

@ -519,6 +519,7 @@ where
let validator_keypairs = self
.validator_keypairs
.expect("cannot build without validator keypairs");
let chain_config = self.chain_config.unwrap_or_default();
let trusted_setup: TrustedSetup =
serde_json::from_reader(eth2_network_config::get_trusted_setup::<E::Kzg>())
.map_err(|e| format!("Unable to read trusted setup file: {}", e))
@ -528,13 +529,17 @@ where
.logger(log.clone())
.custom_spec(spec)
.store(self.store.expect("cannot build without store"))
.store_migrator_config(MigratorConfig::default().blocking())
.store_migrator_config(
MigratorConfig::default()
.blocking()
.epochs_per_migration(chain_config.epochs_per_migration),
)
.task_executor(self.runtime.task_executor.clone())
.execution_layer(self.execution_layer)
.dummy_eth1_backend()
.expect("should build dummy backend")
.shutdown_sender(shutdown_tx)
.chain_config(self.chain_config.unwrap_or_default())
.chain_config(chain_config)
.event_handler(Some(ServerSentEventHandler::new_with_capacity(
log.clone(),
5,

View File

@ -12,7 +12,7 @@ use beacon_chain::{
slot_clock::{SlotClock, SystemTimeSlotClock},
state_advance_timer::spawn_state_advance_timer,
store::{HotColdDB, ItemStore, LevelDB, StoreConfig},
BeaconChain, BeaconChainTypes, Eth1ChainBackend, ServerSentEventHandler,
BeaconChain, BeaconChainTypes, Eth1ChainBackend, MigratorConfig, ServerSentEventHandler,
};
use beacon_processor::{
work_reprocessing_queue::ReprocessQueueMessage, BeaconProcessor, BeaconProcessorSend,
@ -188,6 +188,9 @@ where
.store(store)
.task_executor(context.executor.clone())
.custom_spec(spec.clone())
.store_migrator_config(
MigratorConfig::default().epochs_per_migration(chain_config.epochs_per_migration),
)
.chain_config(chain_config)
.graffiti(graffiti)
.event_handler(event_handler)

View File

@ -42,9 +42,9 @@ lazy_static = "1.4.0"
ethers-core = "1.0.2"
builder_client = { path = "../builder_client" }
fork_choice = { path = "../../consensus/fork_choice" }
mev-rs = { git = "https://github.com/ralexstokes/mev-rs" }
ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus" }
ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs" }
mev-rs = { git = "https://github.com/ralexstokes/mev-rs", rev = "216657016d5c0889b505857c89ae42c7aa2764af" }
ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "e380108" }
ssz_rs = "0.9.0"
tokio-stream = { version = "0.1.9", features = [ "sync" ] }
strum = "0.24.0"
keccak-hash = "0.10.0"

View File

@ -237,6 +237,7 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BlockProposalContents<T, Paylo
BlockProposalContents::Payload {
payload: Payload::default_at_fork(fork_name)?,
block_value: Uint256::zero(),
_phantom: PhantomData,
}
}
ForkName::Deneb => BlockProposalContents::PayloadAndBlobs {
@ -938,6 +939,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
BlockProposalContents::Payload {
payload: relay.data.message.header,
block_value: relay.data.message.value,
_phantom: PhantomData,
},
)),
Err(reason) if !reason.payload_invalid() => {
@ -992,6 +994,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
BlockProposalContents::Payload {
payload: relay.data.message.header,
block_value: relay.data.message.value,
_phantom: PhantomData,
},
)),
// If the payload is valid then use it. The local EE failed
@ -1000,6 +1003,7 @@ impl<T: EthSpec> ExecutionLayer<T> {
BlockProposalContents::Payload {
payload: relay.data.message.header,
block_value: relay.data.message.value,
_phantom: PhantomData,
},
)),
Err(reason) => {
@ -1178,30 +1182,36 @@ impl<T: EthSpec> ExecutionLayer<T> {
"parent_hash" => ?parent_hash,
);
engine.api.get_payload::<T>(current_fork, payload_id).await
}.await?;
if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.suggested_fee_recipient() {
error!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?payload_response.execution_payload_ref().fee_recipient(),
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
);
}
if f(self, payload_response.execution_payload_ref()).is_some() {
warn!(
self.log(),
"Duplicate payload cached, this might indicate redundant proposal \
attempts."
);
}
Ok(payload_response.into())
};
let payload_response = payload_fut.await;
let (execution_payload, block_value) = payload_response.map(|payload_response| {
if payload_response.execution_payload_ref().fee_recipient() != payload_attributes.suggested_fee_recipient() {
error!(
self.log(),
"Inconsistent fee recipient";
"msg" => "The fee recipient returned from the Execution Engine differs \
from the suggested_fee_recipient set on the beacon node. This could \
indicate that fees are being diverted to another address. Please \
ensure that the value of suggested_fee_recipient is set correctly and \
that the Execution Engine is trusted.",
"fee_recipient" => ?payload_response.execution_payload_ref().fee_recipient(),
"suggested_fee_recipient" => ?payload_attributes.suggested_fee_recipient(),
);
}
if f(self, payload_response.execution_payload_ref()).is_some() {
warn!(
self.log(),
"Duplicate payload cached, this might indicate redundant proposal \
attempts."
);
}
payload_response.into()
})?;
Ok(BlockProposalContents::Payload {
payload: execution_payload.into(),
block_value,
_phantom: PhantomData,
})
})
.await
.map_err(Box::new)
@ -2126,6 +2136,22 @@ async fn timed_future<F: Future<Output = T>, T>(metric: &str, future: F) -> (T,
(result, duration)
}
fn noop<T: EthSpec>(
_: &ExecutionLayer<T>,
_: ExecutionPayloadRef<T>,
) -> Option<ExecutionPayload<T>> {
None
}
#[cfg(test)]
/// Returns the duration since the unix epoch.
fn timestamp_now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_secs()
}
#[derive(Debug)]
pub enum BlobTxConversionError {
/// The transaction type was not set.
@ -2356,12 +2382,3 @@ mod test {
.await;
}
}
#[cfg(test)]
/// Returns the duration since the unix epoch.
fn timestamp_now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_secs()
}

View File

@ -11,11 +11,17 @@ use ethereum_consensus::{
};
use fork_choice::ForkchoiceUpdateParameters;
use mev_rs::{
bellatrix::{BuilderBid as BuilderBidBellatrix, SignedBuilderBid as SignedBuilderBidBellatrix},
capella::{BuilderBid as BuilderBidCapella, SignedBuilderBid as SignedBuilderBidCapella},
sign_builder_message, verify_signed_builder_message, BidRequest, BlindedBlockProviderError,
BlindedBlockProviderServer, BuilderBid, ExecutionPayload as ServerPayload,
SignedBlindedBeaconBlock, SignedBuilderBid, SignedValidatorRegistration,
blinded_block_provider::Server as BlindedBlockProviderServer,
signing::{sign_builder_message, verify_signed_builder_message},
types::{
bellatrix::{
BuilderBid as BuilderBidBellatrix, SignedBuilderBid as SignedBuilderBidBellatrix,
},
capella::{BuilderBid as BuilderBidCapella, SignedBuilderBid as SignedBuilderBidCapella},
BidRequest, BuilderBid, ExecutionPayload as ServerPayload, SignedBlindedBeaconBlock,
SignedBuilderBid, SignedValidatorRegistration,
},
Error as MevError,
};
use parking_lot::RwLock;
use sensitive_url::SensitiveUrl;
@ -47,7 +53,7 @@ pub enum Operation {
}
impl Operation {
fn apply<B: BidStuff>(self, bid: &mut B) -> Result<(), BlindedBlockProviderError> {
fn apply<B: BidStuff>(self, bid: &mut B) -> Result<(), MevError> {
match self {
Operation::FeeRecipient(fee_recipient) => {
*bid.fee_recipient_mut() = to_ssz_rs(&fee_recipient)?
@ -73,7 +79,7 @@ pub trait BidStuff {
fn prev_randao_mut(&mut self) -> &mut Hash32;
fn block_number_mut(&mut self) -> &mut u64;
fn timestamp_mut(&mut self) -> &mut u64;
fn withdrawals_root_mut(&mut self) -> Result<&mut Root, BlindedBlockProviderError>;
fn withdrawals_root_mut(&mut self) -> Result<&mut Root, MevError>;
fn sign_builder_message(
&mut self,
@ -134,11 +140,9 @@ impl BidStuff for BuilderBid {
}
}
fn withdrawals_root_mut(&mut self) -> Result<&mut Root, BlindedBlockProviderError> {
fn withdrawals_root_mut(&mut self) -> Result<&mut Root, MevError> {
match self {
Self::Bellatrix(_) => Err(BlindedBlockProviderError::Custom(
"withdrawals_root called on bellatrix bid".to_string(),
)),
Self::Bellatrix(_) => Err(MevError::InvalidFork),
Self::Capella(bid) => Ok(&mut bid.header.withdrawals_root),
}
}
@ -274,7 +278,7 @@ impl<E: EthSpec> MockBuilder<E> {
*self.invalidate_signatures.write() = false;
}
fn apply_operations<B: BidStuff>(&self, bid: &mut B) -> Result<(), BlindedBlockProviderError> {
fn apply_operations<B: BidStuff>(&self, bid: &mut B) -> Result<(), MevError> {
let mut guard = self.operations.write();
while let Some(op) = guard.pop() {
op.apply(bid)?;
@ -288,7 +292,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
async fn register_validators(
&self,
registrations: &mut [SignedValidatorRegistration],
) -> Result<(), BlindedBlockProviderError> {
) -> Result<(), MevError> {
for registration in registrations {
let pubkey = registration.message.public_key.clone();
let message = &mut registration.message;
@ -307,10 +311,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
Ok(())
}
async fn fetch_best_bid(
&self,
bid_request: &BidRequest,
) -> Result<SignedBuilderBid, BlindedBlockProviderError> {
async fn fetch_best_bid(&self, bid_request: &BidRequest) -> Result<SignedBuilderBid, MevError> {
let slot = Slot::new(bid_request.slot);
let fork = self.spec.fork_name_at_slot::<E>(slot);
let signed_cached_data = self
@ -336,7 +337,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
.map_err(convert_err)?
.block_hash();
if head_execution_hash != from_ssz_rs(&bid_request.parent_hash)? {
return Err(BlindedBlockProviderError::Custom(format!(
return Err(custom_err(format!(
"head mismatch: {} {}",
head_execution_hash, bid_request.parent_hash
)));
@ -396,7 +397,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
.get_debug_beacon_states(StateId::Head)
.await
.map_err(convert_err)?
.ok_or_else(|| BlindedBlockProviderError::Custom("missing head state".to_string()))?
.ok_or_else(|| custom_err("missing head state".to_string()))?
.data;
let prev_randao = head_state
.get_randao_mix(head_state.current_epoch())
@ -409,10 +410,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
PayloadAttributes::new(timestamp, *prev_randao, fee_recipient, Some(vec![]))
}
ForkName::Base | ForkName::Altair => {
return Err(BlindedBlockProviderError::Custom(format!(
"Unsupported fork: {}",
fork
)));
return Err(MevError::InvalidFork);
}
};
@ -452,12 +450,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
value: to_ssz_rs(&Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI))?,
public_key: self.builder_sk.public_key(),
}),
ForkName::Base | ForkName::Altair | ForkName::Deneb => {
return Err(BlindedBlockProviderError::Custom(format!(
"Unsupported fork: {}",
fork
)))
}
ForkName::Base | ForkName::Altair | ForkName::Deneb => return Err(MevError::InvalidFork),
};
*message.gas_limit_mut() = cached_data.gas_limit;
@ -475,7 +468,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
async fn open_bid(
&self,
signed_block: &mut SignedBlindedBeaconBlock,
) -> Result<ServerPayload, BlindedBlockProviderError> {
) -> Result<ServerPayload, MevError> {
let node = match signed_block {
SignedBlindedBeaconBlock::Bellatrix(block) => {
block.message.body.execution_payload_header.hash_tree_root()
@ -496,9 +489,7 @@ impl<E: EthSpec> mev_rs::BlindedBlockProvider for MockBuilder<E> {
}
}
pub fn from_ssz_rs<T: SimpleSerialize, U: Decode>(
ssz_rs_data: &T,
) -> Result<U, BlindedBlockProviderError> {
pub fn from_ssz_rs<T: SimpleSerialize, U: Decode>(ssz_rs_data: &T) -> Result<U, MevError> {
U::from_ssz_bytes(
ssz_rs::serialize(ssz_rs_data)
.map_err(convert_err)?
@ -507,12 +498,17 @@ pub fn from_ssz_rs<T: SimpleSerialize, U: Decode>(
.map_err(convert_err)
}
pub fn to_ssz_rs<T: Encode, U: SimpleSerialize>(
ssz_data: &T,
) -> Result<U, BlindedBlockProviderError> {
pub fn to_ssz_rs<T: Encode, U: SimpleSerialize>(ssz_data: &T) -> Result<U, MevError> {
ssz_rs::deserialize::<U>(&ssz_data.as_ssz_bytes()).map_err(convert_err)
}
fn convert_err<E: Debug>(e: E) -> BlindedBlockProviderError {
BlindedBlockProviderError::Custom(format!("{e:?}"))
fn convert_err<E: Debug>(e: E) -> MevError {
custom_err(format!("{e:?}"))
}
// This is a bit of a hack since the `Custom` variant was removed from `mev_rs::Error`.
fn custom_err(s: String) -> MevError {
MevError::Consensus(ethereum_consensus::state_transition::Error::Io(
std::io::Error::new(std::io::ErrorKind::Other, s),
))
}

View File

@ -75,7 +75,7 @@ impl<T: EthSpec> PackingEfficiencyHandler<T> {
available_attestations: HashSet::new(),
included_attestations: HashMap::new(),
committee_store: CommitteeStore::new(),
_phantom: PhantomData::default(),
_phantom: PhantomData,
};
handler.compute_epoch(start_epoch, &starting_state, spec)?;

View File

@ -70,15 +70,32 @@ impl StateId {
.map_err(BeaconChainError::DBError)
.map_err(warp_utils::reject::beacon_chain_error)?
{
let execution_optimistic = chain
.canonical_head
.fork_choice_read_lock()
.is_optimistic_or_invalid_block_no_fallback(&hot_summary.latest_block_root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?;
let finalized = chain
.is_finalized_state(root, hot_summary.slot)
let finalization_status = chain
.state_finalization_and_canonicity(root, hot_summary.slot)
.map_err(warp_utils::reject::beacon_chain_error)?;
let finalized = finalization_status.is_finalized();
let fork_choice = chain.canonical_head.fork_choice_read_lock();
let execution_optimistic = if finalization_status.slot_is_finalized
&& !finalization_status.canonical
{
// This block is permanently orphaned and has likely been pruned from fork
// choice. If it isn't found in fork choice, mark it optimistic to be on the
// safe side.
fork_choice
.is_optimistic_or_invalid_block_no_fallback(
&hot_summary.latest_block_root,
)
.unwrap_or(true)
} else {
// This block is either old and finalized, or recent and unfinalized, so
// it's safe to fallback to the optimistic status of the finalized block.
chain
.canonical_head
.fork_choice_read_lock()
.is_optimistic_or_invalid_block(&hot_summary.latest_block_root)
.map_err(BeaconChainError::ForkChoiceError)
.map_err(warp_utils::reject::beacon_chain_error)?
};
return Ok((*root, execution_optimistic, finalized));
} else if let Some(_cold_state_slot) = chain
.store

View File

@ -2,8 +2,9 @@
use beacon_chain::{
chain_config::{DisallowedReOrgOffsets, ReOrgThreshold},
test_utils::{AttestationStrategy, BlockStrategy, SyncCommitteeStrategy},
ChainConfig,
};
use eth2::types::DepositContractData;
use eth2::types::{DepositContractData, StateId};
use execution_layer::{ForkchoiceState, PayloadAttributes};
use http_api::test_utils::InteractiveTester;
use parking_lot::Mutex;
@ -17,7 +18,7 @@ use std::time::Duration;
use tree_hash::TreeHash;
use types::{
Address, Epoch, EthSpec, ExecPayload, ExecutionBlockHash, ForkName, FullPayload,
MainnetEthSpec, ProposerPreparationData, Slot,
MainnetEthSpec, MinimalEthSpec, ProposerPreparationData, Slot,
};
type E = MainnetEthSpec;
@ -48,6 +49,76 @@ async fn deposit_contract_custom_network() {
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.finalized.unwrap());
assert!(!response.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 {

View File

@ -1,7 +1,10 @@
//! ENR extension trait to support libp2p integration.
use crate::{Enr, Multiaddr, PeerId};
use discv5::enr::{CombinedKey, CombinedPublicKey};
use libp2p::core::{identity::Keypair, identity::PublicKey, multiaddr::Protocol};
use libp2p::{
core::{identity::Keypair, identity::PublicKey, multiaddr::Protocol},
identity::secp256k1,
};
use tiny_keccak::{Hasher, Keccak};
/// Extend ENR for libp2p types.
@ -36,6 +39,8 @@ pub trait CombinedKeyPublicExt {
pub trait CombinedKeyExt {
/// Converts a libp2p key into an ENR combined key.
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str>;
/// Converts a [`secp256k1::Keypair`] into and Enr [`CombinedKey`].
fn from_secp256k1(key: &secp256k1::Keypair) -> CombinedKey;
}
impl EnrExt for Enr {
@ -220,12 +225,7 @@ impl CombinedKeyPublicExt for CombinedPublicKey {
impl CombinedKeyExt for CombinedKey {
fn from_libp2p(key: &libp2p::core::identity::Keypair) -> Result<CombinedKey, &'static str> {
match key {
Keypair::Secp256k1(key) => {
let secret =
discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes())
.expect("libp2p key must be valid");
Ok(CombinedKey::Secp256k1(secret))
}
Keypair::Secp256k1(key) => Ok(CombinedKey::from_secp256k1(key)),
Keypair::Ed25519(key) => {
let ed_keypair = discv5::enr::ed25519_dalek::SigningKey::from_bytes(
&(key.encode()[..32])
@ -237,6 +237,11 @@ impl CombinedKeyExt for CombinedKey {
Keypair::Ecdsa(_) => Err("Ecdsa keypairs not supported"),
}
}
fn from_secp256k1(key: &secp256k1::Keypair) -> Self {
let secret = discv5::enr::k256::ecdsa::SigningKey::from_slice(&key.secret().to_bytes())
.expect("libp2p key must be valid");
CombinedKey::Secp256k1(secret)
}
}
// helper function to convert a peer_id to a node_id. This is only possible for secp256k1/ed25519 libp2p

View File

@ -1101,6 +1101,7 @@ mod tests {
use super::*;
use crate::rpc::methods::{MetaData, MetaDataV2};
use enr::EnrBuilder;
use libp2p::identity::secp256k1;
use slog::{o, Drain};
use types::{BitVector, MinimalEthSpec, SubnetId};
@ -1119,10 +1120,10 @@ mod tests {
}
async fn build_discovery() -> Discovery<E> {
let keypair = libp2p::identity::Keypair::generate_secp256k1();
let keypair = secp256k1::Keypair::generate();
let mut config = NetworkConfig::default();
config.set_listening_addr(crate::ListenAddress::unused_v4_ports());
let enr_key: CombinedKey = CombinedKey::from_libp2p(&keypair).unwrap();
let enr_key: CombinedKey = CombinedKey::from_secp256k1(&keypair);
let enr: Enr = build_enr::<E>(&enr_key, &config, &EnrForkId::default()).unwrap();
let log = build_log(slog::Level::Debug, false);
let globals = NetworkGlobals::new(
@ -1138,6 +1139,7 @@ mod tests {
false,
&log,
);
let keypair = Keypair::Secp256k1(keypair);
Discovery::new(&keypair, &config, Arc::new(globals), &log)
.await
.unwrap()
@ -1184,8 +1186,8 @@ mod tests {
fn make_enr(subnet_ids: Vec<usize>) -> Enr {
let mut builder = EnrBuilder::new("v4");
let keypair = libp2p::identity::Keypair::generate_secp256k1();
let enr_key: CombinedKey = CombinedKey::from_libp2p(&keypair).unwrap();
let keypair = secp256k1::Keypair::generate();
let enr_key: CombinedKey = CombinedKey::from_secp256k1(&keypair);
// set the "attnets" field on our ENR
let mut bitfield = BitVector::<ssz_types::typenum::U64>::new();

View File

@ -134,9 +134,8 @@ impl<TSpec: EthSpec> NetworkGlobals<TSpec> {
log: &slog::Logger,
) -> NetworkGlobals<TSpec> {
use crate::CombinedKeyExt;
let keypair = libp2p::identity::Keypair::generate_secp256k1();
let enr_key: discv5::enr::CombinedKey =
discv5::enr::CombinedKey::from_libp2p(&keypair).unwrap();
let keypair = libp2p::identity::secp256k1::Keypair::generate();
let enr_key: discv5::enr::CombinedKey = discv5::enr::CombinedKey::from_secp256k1(&keypair);
let enr = discv5::enr::EnrBuilder::new("v4").build(&enr_key).unwrap();
NetworkGlobals::new(
enr,

View File

@ -540,6 +540,16 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
[default: 8192 (mainnet) or 64 (minimal)]")
.takes_value(true)
)
.arg(
Arg::with_name("epochs-per-migration")
.long("epochs-per-migration")
.value_name("N")
.help("The number of epochs to wait between running the migration of data from the \
hot DB to the cold DB. Less frequent runs can be useful for minimizing disk \
writes")
.default_value("1")
.takes_value(true)
)
.arg(
Arg::with_name("block-cache-size")
.long("block-cache-size")

View File

@ -421,6 +421,12 @@ pub fn get_config<E: EthSpec>(
client_config.store.prune_payloads = prune_payloads;
}
if let Some(epochs_per_migration) =
clap_utils::parse_optional(cli_args, "epochs-per-migration")?
{
client_config.chain.epochs_per_migration = epochs_per_migration;
}
if let Some(prune_blobs) = clap_utils::parse_optional(cli_args, "prune-blobs")? {
client_config.store.prune_blobs = prune_blobs;
}

View File

@ -2,7 +2,7 @@
#![cfg_attr(
not(test),
deny(
clippy::integer_arithmetic,
clippy::arithmetic_side_effects,
clippy::disallowed_methods,
clippy::indexing_slicing,
clippy::unwrap_used,

View File

@ -1,4 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::arithmetic_side_effects)]
use super::signature_sets::{Error as SignatureSetError, *};
use crate::per_block_processing::errors::{AttestationInvalid, BlockOperationError};

View File

@ -960,7 +960,7 @@ async fn fork_spanning_exit() {
spec.bellatrix_fork_epoch = Some(Epoch::new(4));
spec.shard_committee_period = 0;
let harness = BeaconChainHarness::builder(MainnetEthSpec::default())
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.clone())
.deterministic_keypairs(VALIDATOR_COUNT)
.mock_execution_layer()

View File

@ -34,7 +34,7 @@ pub fn process_inactivity_updates<T: EthSpec>(
.safe_add_assign(spec.inactivity_score_bias)?;
}
// Decrease the score of all validators for forgiveness when not during a leak
if !state.is_in_inactivity_leak(previous_epoch, spec) {
if !state.is_in_inactivity_leak(previous_epoch, spec)? {
let inactivity_score = state.get_inactivity_score_mut(index)?;
inactivity_score
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;

View File

@ -77,7 +77,7 @@ pub fn get_flag_index_deltas<T: EthSpec>(
let mut delta = Delta::default();
if unslashed_participating_indices.contains(index)? {
if !state.is_in_inactivity_leak(previous_epoch, spec) {
if !state.is_in_inactivity_leak(previous_epoch, spec)? {
let reward_numerator = base_reward
.safe_mul(weight)?
.safe_mul(unslashed_participating_increments)?;

View File

@ -138,7 +138,7 @@ impl<E: EthSpec> VerifyOperation<E> for SignedVoluntaryExit {
Ok(SigVerifiedOp::new(self, state))
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
smallvec![self.message.epoch]
}
@ -156,7 +156,7 @@ impl<E: EthSpec> VerifyOperation<E> for AttesterSlashing<E> {
Ok(SigVerifiedOp::new(self, state))
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
smallvec![
self.attestation_1.data.target.epoch,
@ -177,7 +177,7 @@ impl<E: EthSpec> VerifyOperation<E> for ProposerSlashing {
Ok(SigVerifiedOp::new(self, state))
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
// Only need a single epoch because the slots of the two headers must be equal.
smallvec![self
@ -200,7 +200,7 @@ impl<E: EthSpec> VerifyOperation<E> for SignedBlsToExecutionChange {
Ok(SigVerifiedOp::new(self, state))
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
fn verification_epochs(&self) -> SmallVec<[Epoch; MAX_FORKS_VERIFIED_AGAINST]> {
smallvec![]
}

View File

@ -455,7 +455,7 @@ impl<T: EthSpec> BeaconState<T> {
}
/// Specialised deserialisation method that uses the `ChainSpec` as context.
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
pub fn from_ssz_bytes(bytes: &[u8], spec: &ChainSpec) -> Result<Self, ssz::DecodeError> {
// Slot is after genesis_time (u64) and genesis_validators_root (Hash256).
let slot_start = <u64 as Decode>::ssz_fixed_len() + <Hash256 as Decode>::ssz_fixed_len();
@ -1761,16 +1761,22 @@ impl<T: EthSpec> BeaconState<T> {
previous_epoch: Epoch,
val_index: usize,
) -> Result<bool, Error> {
self.get_validator(val_index).map(|val| {
val.is_active_at(previous_epoch)
|| (val.slashed && previous_epoch + Epoch::new(1) < val.withdrawable_epoch)
})
let val = self.get_validator(val_index)?;
Ok(val.is_active_at(previous_epoch)
|| (val.slashed && previous_epoch.safe_add(Epoch::new(1))? < val.withdrawable_epoch))
}
/// Passing `previous_epoch` to this function rather than computing it internally provides
/// a tangible speed improvement in state processing.
pub fn is_in_inactivity_leak(&self, previous_epoch: Epoch, spec: &ChainSpec) -> bool {
(previous_epoch - self.finalized_checkpoint().epoch) > spec.min_epochs_to_inactivity_penalty
pub fn is_in_inactivity_leak(
&self,
previous_epoch: Epoch,
spec: &ChainSpec,
) -> Result<bool, safe_arith::ArithError> {
Ok(
(previous_epoch.safe_sub(self.finalized_checkpoint().epoch)?)
> spec.min_epochs_to_inactivity_penalty,
)
}
/// Get the `SyncCommittee` associated with the next slot. Useful because sync committees

View File

@ -1,4 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::arithmetic_side_effects)]
use super::BeaconState;
use crate::*;

View File

@ -1,4 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::disallowed_methods)]
#![allow(clippy::indexing_slicing)]

View File

@ -468,7 +468,7 @@ impl ChainSpec {
Hash256::from(domain)
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
pub const fn attestation_subnet_prefix_bits(&self) -> u32 {
let attestation_subnet_count_bits = self.attestation_subnet_count.ilog2();
self.attestation_subnet_extra_bits as u32 + attestation_subnet_count_bits

View File

@ -115,7 +115,7 @@ impl<T: EthSpec> ExecutionPayload<T> {
}
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_merge_size() -> usize {
// Fixed part
@ -126,7 +126,7 @@ impl<T: EthSpec> ExecutionPayload<T> {
+ (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction()))
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_capella_size() -> usize {
// Fixed part

View File

@ -3,7 +3,7 @@
#![cfg_attr(
not(test),
deny(
clippy::integer_arithmetic,
clippy::arithmetic_side_effects,
clippy::disallowed_methods,
clippy::indexing_slicing
)

View File

@ -1,4 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::arithmetic_side_effects)]
use crate::{Hash256, ParticipationFlags, Unsigned, VariableList};
use cached_tree_hash::{int_log, CacheArena, CachedTreeHash, Error, TreeHashCache};

View File

@ -72,7 +72,7 @@ impl SubnetId {
.into())
}
#[allow(clippy::integer_arithmetic)]
#[allow(clippy::arithmetic_side_effects)]
/// Computes the set of subnets the node should be subscribed to during the current epoch,
/// along with the first epoch in which these subscriptions are no longer valid.
pub fn compute_subnets_for_epoch<T: EthSpec>(

View File

@ -1,4 +1,4 @@
#![allow(clippy::integer_arithmetic)]
#![allow(clippy::arithmetic_side_effects)]
use std::fmt::Debug;

View File

@ -30,7 +30,7 @@ pub trait TestRandom {
impl<T> TestRandom for PhantomData<T> {
fn random_for_test(_rng: &mut impl RngCore) -> Self {
PhantomData::default()
PhantomData
}
}

View File

@ -1,6 +1,7 @@
use clap::ArgMatches;
use lighthouse_network::{
discovery::{build_enr, CombinedKey, CombinedKeyExt, Keypair, ENR_FILENAME},
discovery::{build_enr, CombinedKey, CombinedKeyExt, ENR_FILENAME},
libp2p::identity::secp256k1,
NetworkConfig, NETWORK_KEY_FILENAME,
};
use std::fs::File;
@ -29,8 +30,8 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
config.enr_udp4_port = Some(udp_port);
config.enr_tcp6_port = Some(tcp_port);
let local_keypair = Keypair::generate_secp256k1();
let enr_key = CombinedKey::from_libp2p(&local_keypair)?;
let secp256k1_keypair = secp256k1::Keypair::generate();
let enr_key = CombinedKey::from_secp256k1(&secp256k1_keypair);
let enr_fork_id = EnrForkId {
fork_digest: ChainSpec::compute_fork_digest(genesis_fork_version, Hash256::zero()),
next_fork_version: genesis_fork_version,
@ -47,13 +48,10 @@ pub fn run<T: EthSpec>(matches: &ArgMatches) -> Result<(), String> {
.write_all(enr.to_base64().as_bytes())
.map_err(|e| format!("Unable to write ENR to {}: {:?}", ENR_FILENAME, e))?;
let secret_bytes = match local_keypair {
Keypair::Secp256k1(key) => key.secret().to_bytes(),
_ => return Err("Key is not a secp256k1 key".into()),
};
let mut key_file = File::create(output_dir.join(NETWORK_KEY_FILENAME))
.map_err(|e| format!("Unable to create {}: {:?}", NETWORK_KEY_FILENAME, e))?;
let secret_bytes = secp256k1_keypair.secret().to_bytes();
key_file
.write_all(&secret_bytes)
.map_err(|e| format!("Unable to write key to {}: {:?}", NETWORK_KEY_FILENAME, e))?;

View File

@ -40,7 +40,6 @@ fn main() {
.long("spec")
.value_name("STRING")
.takes_value(true)
.required(true)
.possible_values(&["minimal", "mainnet", "gnosis"])
.default_value("mainnet")
.global(true),
@ -372,7 +371,6 @@ fn main() {
.index(2)
.value_name("BIP39_MNENMONIC")
.takes_value(true)
.required(true)
.default_value(
"replace nephew blur decorate waste convince soup column \
orient excite play baby",
@ -393,7 +391,6 @@ fn main() {
.help("The block hash used when generating an execution payload. This \
value is used for `execution_payload_header.block_hash` as well as \
`execution_payload_header.random`")
.required(true)
.default_value(
"0x0000000000000000000000000000000000000000000000000000000000000000",
),
@ -411,7 +408,6 @@ fn main() {
.value_name("INTEGER")
.takes_value(true)
.help("The base fee per gas field in the execution payload generated.")
.required(true)
.default_value("1000000000"),
)
.arg(
@ -420,7 +416,6 @@ fn main() {
.value_name("INTEGER")
.takes_value(true)
.help("The gas limit field in the execution payload generated.")
.required(true)
.default_value("30000000"),
)
.arg(

View File

@ -1772,6 +1772,24 @@ fn no_reconstruct_historic_states_flag() {
.run_with_zero_port()
.with_config(|config| assert!(!config.chain.reconstruct_historic_states));
}
#[test]
fn epochs_per_migration_default() {
CommandLineTest::new()
.run_with_zero_port()
.with_config(|config| {
assert_eq!(
config.chain.epochs_per_migration,
beacon_node::beacon_chain::migrate::DEFAULT_EPOCHS_PER_MIGRATION
)
});
}
#[test]
fn epochs_per_migration_override() {
CommandLineTest::new()
.flag("epochs-per-migration", Some("128"))
.run_with_zero_port()
.with_config(|config| assert_eq!(config.chain.epochs_per_migration, 128));
}
// Tests for Slasher flags.
// Using `--slasher-max-db-size` to work around https://github.com/sigp/lighthouse/issues/2342

View File

@ -9,5 +9,5 @@ set -Eeuo pipefail
source ./vars.env
if [ -d $DATADIR ]; then
rm -r $DATADIR
rm -rf $DATADIR
fi

View File

@ -1,4 +1,4 @@
#![cfg(all(feature = "lmdb"))]
#![cfg(feature = "lmdb")]
use slasher::{config::MDBX_DATA_FILENAME, Config, DatabaseBackend, DatabaseBackendOverride};
use std::fs::File;

View File

@ -13,7 +13,7 @@ BLS_TARBALL = $(patsubst %,%-$(BLS_TEST_TAG).tar.gz,$(BLS_TEST))
BLS_OUTPUT_DIR := $(OUTPUT_DIR)/$(BLS_TEST_REPO_NAME)
BLS_BASE_URL := https://github.com/ethereum/$(BLS_TEST_REPO_NAME)/releases/download/$(BLS_TEST_TAG)
WGET := $(if $(LIGHTHOUSE_GITHUB_TOKEN),wget --header="Authorization: $(LIGHTHOUSE_GITHUB_TOKEN)",wget)
CURL := $(if $(LIGHTHOUSE_GITHUB_TOKEN),curl -L --header "Authorization: $(LIGHTHOUSE_GITHUB_TOKEN)",curl -L)
all:
make $(OUTPUT_DIR)
@ -27,11 +27,11 @@ $(OUTPUT_DIR): $(TARBALLS)
$(BLS_OUTPUT_DIR):
mkdir $(BLS_OUTPUT_DIR)
$(WGET) $(BLS_BASE_URL)/$(BLS_TEST).tar.gz -O $(BLS_TARBALL)
$(CURL) $(BLS_BASE_URL)/$(BLS_TEST).tar.gz -o $(BLS_TARBALL)
tar -xzf $(BLS_TARBALL) -C $(BLS_OUTPUT_DIR)
%-$(TESTS_TAG).tar.gz:
$(WGET) $(BASE_URL)/$*.tar.gz -O $@
$(CURL) $(BASE_URL)/$*.tar.gz -o $@
clean-test-files:
rm -rf $(OUTPUT_DIR) $(BLS_OUTPUT_DIR)

View File

@ -14,3 +14,4 @@ validator_client = { path = "../../validator_client" }
validator_dir = { path = "../../common/validator_dir", features = ["insecure_keys"] }
sensitive_url = { path = "../../common/sensitive_url" }
execution_layer = { path = "../../beacon_node/execution_layer" }
tokio = { version = "1.14.0", features = ["time"] }

View File

@ -10,6 +10,7 @@ use std::path::PathBuf;
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
use tempfile::{Builder as TempBuilder, TempDir};
use tokio::time::timeout;
use types::EthSpec;
use validator_client::ProductionValidatorClient;
use validator_dir::insecure_keys::build_deterministic_validator_dirs;
@ -24,6 +25,8 @@ pub use validator_client::Config as ValidatorConfig;
/// The global timeout for HTTP requests to the beacon node.
const HTTP_TIMEOUT: Duration = Duration::from_secs(4);
/// The timeout for a beacon node to start up.
const STARTUP_TIMEOUT: Duration = Duration::from_secs(60);
/// Provides a beacon node that is running in the current process on a given tokio executor (it
/// is _local_ to this process).
@ -51,12 +54,16 @@ impl<E: EthSpec> LocalBeaconNode<E> {
client_config.set_data_dir(datadir.path().into());
client_config.network.network_dir = PathBuf::from(datadir.path()).join("network");
ProductionBeaconNode::new(context, client_config)
.await
.map(move |client| Self {
client: client.into_inner(),
datadir,
})
timeout(
STARTUP_TIMEOUT,
ProductionBeaconNode::new(context, client_config),
)
.await
.map_err(|_| format!("Beacon node startup timed out after {:?}", STARTUP_TIMEOUT))?
.map(move |client| Self {
client: client.into_inner(),
datadir,
})
}
}

View File

@ -25,8 +25,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.default_value("4")
.help("Number of beacon nodes"))
.arg(Arg::with_name("proposer-nodes")
.short("n")
.long("nodes")
.short("p")
.long("proposer_nodes")
.takes_value(true)
.default_value("2")
.help("Number of proposer-only beacon nodes"))
@ -64,8 +64,8 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
.default_value("4")
.help("Number of beacon nodes"))
.arg(Arg::with_name("proposer-nodes")
.short("n")
.long("nodes")
.short("p")
.long("proposer_nodes")
.takes_value(true)
.default_value("2")
.help("Number of proposer-only beacon nodes"))

View File

@ -1,14 +1,16 @@
use crate::local_network::{EXECUTION_PORT, TERMINAL_BLOCK, TERMINAL_DIFFICULTY};
use crate::{checks, LocalNetwork, E};
use crate::{checks, LocalNetwork};
use clap::ArgMatches;
use eth1::{Eth1Endpoint, DEFAULT_CHAIN_ID};
use eth1_test_rig::AnvilEth1Instance;
use crate::retry::with_retry;
use execution_layer::http::deposit_methods::Eth1Id;
use futures::prelude::*;
use node_test_rig::environment::RuntimeContext;
use node_test_rig::{
environment::{EnvironmentBuilder, LoggerConfig},
testing_client_config, testing_validator_config, ClientGenesis, ValidatorFiles,
testing_client_config, testing_validator_config, ClientConfig, ClientGenesis, ValidatorFiles,
};
use rayon::prelude::*;
use sensitive_url::SensitiveUrl;
@ -107,71 +109,24 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
let context = env.core_context();
let main_future = async {
/*
* Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit
* validators.
*/
let anvil_eth1_instance = AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?;
let deposit_contract = anvil_eth1_instance.deposit_contract;
let chain_id = anvil_eth1_instance.anvil.chain_id();
let anvil = anvil_eth1_instance.anvil;
let eth1_endpoint = SensitiveUrl::parse(anvil.endpoint().as_str())
.expect("Unable to parse anvil endpoint.");
let deposit_contract_address = deposit_contract.address();
// Start a timer that produces eth1 blocks on an interval.
tokio::spawn(async move {
let mut interval = tokio::time::interval(eth1_block_time);
loop {
interval.tick().await;
let _ = anvil.evm_mine().await;
}
});
// Submit deposits to the deposit contract.
tokio::spawn(async move {
for i in 0..total_validator_count {
println!("Submitting deposit for validator {}...", i);
let _ = deposit_contract
.deposit_deterministic_async::<E>(i, deposit_amount)
.await;
}
});
let mut beacon_config = testing_client_config();
beacon_config.genesis = ClientGenesis::DepositContract;
beacon_config.eth1.endpoint = Eth1Endpoint::NoAuth(eth1_endpoint);
beacon_config.eth1.deposit_contract_address = deposit_contract_address;
beacon_config.eth1.deposit_contract_deploy_block = 0;
beacon_config.eth1.lowest_cached_block_number = 0;
beacon_config.eth1.follow_distance = 1;
beacon_config.eth1.node_far_behind_seconds = 20;
beacon_config.dummy_eth1_backend = false;
beacon_config.sync_eth1_chain = true;
beacon_config.eth1.auto_update_interval_millis = eth1_block_time.as_millis() as u64;
beacon_config.eth1.chain_id = Eth1Id::from(chain_id);
beacon_config.network.target_peers = node_count + proposer_nodes - 1;
beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None);
if post_merge_sim {
let el_config = execution_layer::Config {
execution_endpoints: vec![SensitiveUrl::parse(&format!(
"http://localhost:{}",
EXECUTION_PORT
))
.unwrap()],
..Default::default()
};
beacon_config.execution_layer = Some(el_config);
}
/*
* Create a new `LocalNetwork` with one beacon node.
*/
let network = LocalNetwork::new(context.clone(), beacon_config.clone()).await?;
let max_retries = 3;
let (network, beacon_config) = with_retry(max_retries, || {
Box::pin(create_local_network(
LocalNetworkParams {
eth1_block_time,
total_validator_count,
deposit_amount,
node_count,
proposer_nodes,
post_merge_sim,
},
context.clone(),
))
})
.await?;
/*
* One by one, add beacon nodes to the network.
@ -341,3 +296,88 @@ pub fn run_eth1_sim(matches: &ArgMatches) -> Result<(), String> {
Ok(())
}
struct LocalNetworkParams {
eth1_block_time: Duration,
total_validator_count: usize,
deposit_amount: u64,
node_count: usize,
proposer_nodes: usize,
post_merge_sim: bool,
}
async fn create_local_network<E: EthSpec>(
LocalNetworkParams {
eth1_block_time,
total_validator_count,
deposit_amount,
node_count,
proposer_nodes,
post_merge_sim,
}: LocalNetworkParams,
context: RuntimeContext<E>,
) -> Result<(LocalNetwork<E>, ClientConfig), String> {
/*
* Deploy the deposit contract, spawn tasks to keep creating new blocks and deposit
* validators.
*/
let anvil_eth1_instance = AnvilEth1Instance::new(DEFAULT_CHAIN_ID.into()).await?;
let deposit_contract = anvil_eth1_instance.deposit_contract;
let chain_id = anvil_eth1_instance.anvil.chain_id();
let anvil = anvil_eth1_instance.anvil;
let eth1_endpoint =
SensitiveUrl::parse(anvil.endpoint().as_str()).expect("Unable to parse anvil endpoint.");
let deposit_contract_address = deposit_contract.address();
// Start a timer that produces eth1 blocks on an interval.
tokio::spawn(async move {
let mut interval = tokio::time::interval(eth1_block_time);
loop {
interval.tick().await;
let _ = anvil.evm_mine().await;
}
});
// Submit deposits to the deposit contract.
tokio::spawn(async move {
for i in 0..total_validator_count {
println!("Submitting deposit for validator {}...", i);
let _ = deposit_contract
.deposit_deterministic_async::<E>(i, deposit_amount)
.await;
}
});
let mut beacon_config = testing_client_config();
beacon_config.genesis = ClientGenesis::DepositContract;
beacon_config.eth1.endpoint = Eth1Endpoint::NoAuth(eth1_endpoint);
beacon_config.eth1.deposit_contract_address = deposit_contract_address;
beacon_config.eth1.deposit_contract_deploy_block = 0;
beacon_config.eth1.lowest_cached_block_number = 0;
beacon_config.eth1.follow_distance = 1;
beacon_config.eth1.node_far_behind_seconds = 20;
beacon_config.dummy_eth1_backend = false;
beacon_config.sync_eth1_chain = true;
beacon_config.eth1.auto_update_interval_millis = eth1_block_time.as_millis() as u64;
beacon_config.eth1.chain_id = Eth1Id::from(chain_id);
beacon_config.network.target_peers = node_count + proposer_nodes - 1;
beacon_config.network.enr_address = (Some(Ipv4Addr::LOCALHOST), None);
if post_merge_sim {
let el_config = execution_layer::Config {
execution_endpoints: vec![SensitiveUrl::parse(&format!(
"http://localhost:{}",
EXECUTION_PORT
))
.unwrap()],
..Default::default()
};
beacon_config.execution_layer = Some(el_config);
}
let network = LocalNetwork::new(context, beacon_config.clone()).await?;
Ok((network, beacon_config))
}

View File

@ -21,6 +21,7 @@ mod cli;
mod eth1_sim;
mod local_network;
mod no_eth1_sim;
mod retry;
mod sync_sim;
use cli::cli_app;

View File

@ -0,0 +1,63 @@
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;
/// Executes the function with a specified number of retries if the function returns an error.
/// Once it exceeds `max_retries` and still fails, the error is returned.
pub async fn with_retry<T, E, F>(max_retries: usize, mut func: F) -> Result<T, E>
where
F: FnMut() -> Pin<Box<dyn Future<Output = Result<T, E>>>>,
E: Debug,
{
let mut retry_count = 0;
loop {
let result = Box::pin(func()).await;
if result.is_ok() || retry_count >= max_retries {
break result;
}
retry_count += 1;
if let Err(e) = result {
eprintln!(
"Operation failed with error {:?}, retrying {} of {}",
e, retry_count, max_retries
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
async fn my_async_func(is_ok: bool) -> Result<(), ()> {
if is_ok {
Ok(())
} else {
Err(())
}
}
#[tokio::test]
async fn test_with_retry_ok() {
let res = with_retry(3, || Box::pin(my_async_func(true))).await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_with_retry_2nd_ok() {
let mut mock_results = VecDeque::from([false, true]);
let res = with_retry(3, || {
Box::pin(my_async_func(mock_results.pop_front().unwrap()))
})
.await;
assert!(res.is_ok());
}
#[tokio::test]
async fn test_with_retry_fail() {
let res = with_retry(3, || Box::pin(my_async_func(false))).await;
assert!(res.is_err());
}
}

View File

@ -21,7 +21,7 @@ types = { path = "../consensus/types" }
eth2 = { path = "../common/eth2" }
beacon_node = { path = "../beacon_node"}
tokio = { version = "1.14.0", features = ["time"] }
axum = "0.5.15"
axum = "0.6.18"
hyper = "0.14.20"
serde = "1.0.116"
serde_json = "1.0.58"

View File

@ -5,7 +5,6 @@ use crate::config::Config as FullConfig;
use crate::database::{self, PgPool};
use crate::suboptimal_attestations::{attestation_routes, blockprint_attestation_routes};
use axum::{
handler::Handler,
http::{StatusCode, Uri},
routing::get,
Extension, Json, Router,
@ -104,7 +103,7 @@ pub fn start_server(
}
let app = routes
.fallback(route_not_found.into_service())
.fallback(route_not_found)
.layer(Extension(pool))
.layer(Extension(slots_per_epoch));