From 7251a93c5edf0b5014e6cac499e643c37e630eab Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 17 Aug 2023 02:37:29 +0000 Subject: [PATCH 01/25] Don't kill SSE stream if channel fills up (#4500) ## Issue Addressed Closes #4245 ## Proposed Changes - If an SSE channel fills up, send a comment instead of terminating the stream. - Add a CLI flag for scaling up the SSE buffer: `--http-sse-capacity-multiplier N`. ## Additional Info ~~Blocked on #4462. I haven't rebased on that PR yet for initial testing, because it still needs some more work to handle long-running HTTP threads.~~ - [x] Add CLI flag tests. --- beacon_node/beacon_chain/src/events.rs | 7 ++-- beacon_node/client/src/builder.rs | 5 ++- beacon_node/http_api/src/lib.rs | 48 ++++++++++++++++---------- beacon_node/http_api/src/test_utils.rs | 1 + beacon_node/src/cli.rs | 9 +++++ beacon_node/src/config.rs | 3 ++ lighthouse/tests/beacon_node.rs | 15 ++++++++ 7 files changed, 67 insertions(+), 21 deletions(-) diff --git a/beacon_node/beacon_chain/src/events.rs b/beacon_node/beacon_chain/src/events.rs index fed05032374..b267cc853f8 100644 --- a/beacon_node/beacon_chain/src/events.rs +++ b/beacon_node/beacon_chain/src/events.rs @@ -21,8 +21,11 @@ pub struct ServerSentEventHandler { } impl ServerSentEventHandler { - pub fn new(log: Logger) -> Self { - Self::new_with_capacity(log, DEFAULT_CHANNEL_CAPACITY) + pub fn new(log: Logger, capacity_multiplier: usize) -> Self { + Self::new_with_capacity( + log, + capacity_multiplier.saturating_mul(DEFAULT_CHANNEL_CAPACITY), + ) } pub fn new_with_capacity(log: Logger, capacity: usize) -> Self { diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index 8383963b7c2..bd2946bb7a4 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -157,7 +157,10 @@ where let context = runtime_context.service_context("beacon".into()); let spec = chain_spec.ok_or("beacon_chain_start_method requires a chain spec")?; let event_handler = if self.http_api_config.enabled { - Some(ServerSentEventHandler::new(context.log().clone())) + Some(ServerSentEventHandler::new( + context.log().clone(), + self.http_api_config.sse_capacity_multiplier, + )) } else { None }; diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 4d5b98a8238..8e316834d0e 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -65,7 +65,10 @@ use tokio::sync::{ mpsc::{Sender, UnboundedSender}, oneshot, }; -use tokio_stream::{wrappers::BroadcastStream, StreamExt}; +use tokio_stream::{ + wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}, + StreamExt, +}; use types::{ Attestation, AttestationData, AttestationShufflingId, AttesterSlashing, BeaconStateError, BlindedPayload, CommitteeCache, ConfigAndPreset, Epoch, EthSpec, ForkName, FullPayload, @@ -132,6 +135,7 @@ pub struct Config { pub allow_sync_stalled: bool, pub spec_fork_name: Option, pub data_dir: PathBuf, + pub sse_capacity_multiplier: usize, pub enable_beacon_processor: bool, } @@ -146,6 +150,7 @@ impl Default for Config { allow_sync_stalled: false, spec_fork_name: None, data_dir: PathBuf::from(DEFAULT_ROOT_DIR), + sse_capacity_multiplier: 1, enable_beacon_processor: true, } } @@ -4348,22 +4353,29 @@ pub fn serve( } }; - receivers.push(BroadcastStream::new(receiver).map(|msg| { - match msg { - Ok(data) => Event::default() - .event(data.topic_name()) - .json_data(data) - .map_err(|e| { - warp_utils::reject::server_sent_event_error(format!( - "{:?}", - e - )) - }), - Err(e) => Err(warp_utils::reject::server_sent_event_error( - format!("{:?}", e), - )), - } - })); + receivers.push( + BroadcastStream::new(receiver) + .map(|msg| { + match msg { + Ok(data) => Event::default() + .event(data.topic_name()) + .json_data(data) + .unwrap_or_else(|e| { + Event::default() + .comment(format!("error - bad json: {e:?}")) + }), + // Do not terminate the stream if the channel fills + // up. Just drop some messages and send a comment to + // the client. + Err(BroadcastStreamRecvError::Lagged(n)) => { + Event::default().comment(format!( + "error - dropped {n} messages" + )) + } + } + }) + .map(Ok::<_, std::convert::Infallible>), + ); } } else { return Err(warp_utils::reject::custom_server_error( @@ -4373,7 +4385,7 @@ pub fn serve( let s = futures::stream::select_all(receivers); - Ok::<_, warp::Rejection>(warp::sse::reply(warp::sse::keep_alive().stream(s))) + Ok(warp::sse::reply(warp::sse::keep_alive().stream(s))) }) }, ); diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index 0367776f8de..dcc25322297 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -225,6 +225,7 @@ pub async fn create_api_server_on_port( allow_sync_stalled: false, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), spec_fork_name: None, + sse_capacity_multiplier: 1, enable_beacon_processor: true, }, chain: Some(chain), diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 0330bd3f7cc..974dabbf0cf 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -382,6 +382,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { stalled. This is useful for very small testnets. TESTING ONLY. DO NOT USE ON \ MAINNET.") ) + .arg( + Arg::with_name("http-sse-capacity-multiplier") + .long("http-sse-capacity-multiplier") + .takes_value(true) + .default_value("1") + .value_name("N") + .help("Multiplier to apply to the length of HTTP server-sent-event (SSE) channels. \ + Increasing this value can prevent messages from being dropped.") + ) .arg( Arg::with_name("http-enable-beacon-processor") .long("http-enable-beacon-processor") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 21df86620df..0d2a5d812be 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -149,6 +149,9 @@ pub fn get_config( client_config.http_api.allow_sync_stalled = true; } + client_config.http_api.sse_capacity_multiplier = + parse_required(cli_args, "http-sse-capacity-multiplier")?; + client_config.http_api.enable_beacon_processor = parse_required(cli_args, "http-enable-beacon-processor")?; diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index ecc936cbfb4..02b95120127 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2349,3 +2349,18 @@ fn beacon_processor_zero_workers() { .flag("beacon-processor-max-workers", Some("0")) .run_with_zero_port(); } + +#[test] +fn http_sse_capacity_multiplier_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| assert_eq!(config.http_api.sse_capacity_multiplier, 1)); +} + +#[test] +fn http_sse_capacity_multiplier_override() { + CommandLineTest::new() + .flag("http-sse-capacity-multiplier", Some("10")) + .run_with_zero_port() + .with_config(|config| assert_eq!(config.http_api.sse_capacity_multiplier, 10)); +} From 609819bb4d08faef914a26b681b6f121cb55ab56 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Thu, 17 Aug 2023 02:37:30 +0000 Subject: [PATCH 02/25] `attester_duties`: remove unnecessary case (#4614) Since `tolerant_current_epoch` is expected to be either `current_epoch` or `current_epoch+1`, we can eliminate a case here. And added a comment about `compute_historic_attester_duties` , since `RelativeEpoch::from_epoch` will only allow `request_epoch == current_epoch-1` when `request_epoch < current_epoch`. --- beacon_node/http_api/src/attester_duties.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/beacon_node/http_api/src/attester_duties.rs b/beacon_node/http_api/src/attester_duties.rs index aad405d56ba..f3242a2b374 100644 --- a/beacon_node/http_api/src/attester_duties.rs +++ b/beacon_node/http_api/src/attester_duties.rs @@ -35,7 +35,6 @@ pub fn attester_duties( .epoch(T::EthSpec::slots_per_epoch()); if request_epoch == current_epoch - || request_epoch == tolerant_current_epoch || request_epoch == current_epoch + 1 || request_epoch == tolerant_current_epoch + 1 { @@ -46,7 +45,7 @@ pub fn attester_duties( request_epoch, current_epoch ))) } else { - // request_epoch < current_epoch + // request_epoch < current_epoch, in fact we only allow `request_epoch == current_epoch-1` in this case compute_historic_attester_duties(request_epoch, request_indices, chain) } } From b33bfe25b7bc6ea82a97a270a3d4acd72f4e9ee0 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Thu, 17 Aug 2023 02:37:31 +0000 Subject: [PATCH 03/25] add `metrics::VALIDATOR_DUTIES_SYNC_HTTP_POST` for `post_validator_duties_sync` (#4617) It seems `post_validator_duties_sync` is the only api which doesn't have its own metric in `duties_service`, this PR adds `metrics::VALIDATOR_DUTIES_SYNC_HTTP_POST` for completeness. --- validator_client/src/duties_service/sync.rs | 6 ++++++ validator_client/src/http_metrics/metrics.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/validator_client/src/duties_service/sync.rs b/validator_client/src/duties_service/sync.rs index 1e66d947a21..cf63d8ac625 100644 --- a/validator_client/src/duties_service/sync.rs +++ b/validator_client/src/duties_service/sync.rs @@ -2,8 +2,10 @@ use crate::beacon_node_fallback::{OfflineOnFailure, RequireSynced}; use crate::{ doppelganger_service::DoppelgangerStatus, duties_service::{DutiesService, Error}, + http_metrics::metrics, validator_store::Error as ValidatorStoreError, }; + use futures::future::join_all; use itertools::Itertools; use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -426,6 +428,10 @@ pub async fn poll_sync_committee_duties_for_period Date: Thu, 17 Aug 2023 02:37:32 +0000 Subject: [PATCH 04/25] Update blst to 0.3.11 (#4624) ## Proposed Changes This PR updates `blst` to 0.3.11, which gives us _runtime detection of CPU features_ :tada: Although [performance benchmarks](https://gist.github.com/michaelsproul/f759fa28dfa4003962507db34b439d6c) don't show a substantial detriment to running the `portable` build vs `modern`, in order to take things slowly I propose the following roll-out strategy: - Keep both `modern` and `portable` builds for releases/Docker images. - Run the `portable` build on half of SigP's infrastructure to monitor for performance deficits. - Listen out for user issues with the `portable` builds (e.g. SIGILLs from misdetected hardware). - Make the `portable` build the default and remove the `modern` build from our release binaries & Docker images. --- Cargo.lock | 16 ++-------------- Makefile | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 233b3901ebf..116f8081fac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,14 +732,13 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a30d0edd9dd1c60ddb42b80341c7852f6f985279a5c1a83659dcb65899dec99" +checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" dependencies = [ "cc", "glob", "threadpool", - "which", "zeroize", ] @@ -8981,17 +8980,6 @@ dependencies = [ "rustls-webpki 0.100.1", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "widestring" version = "0.4.3" diff --git a/Makefile b/Makefile index b833686e1b5..cb447e26a51 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ arbitrary-fuzz: # Runs cargo audit (Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database) audit: cargo install --force cargo-audit - cargo audit --ignore RUSTSEC-2020-0071 + cargo audit --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2022-0093 # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: From 687c58fde02b406441e8e8f2b46449adf6458cb8 Mon Sep 17 00:00:00 2001 From: ethDreamer Date: Fri, 18 Aug 2023 03:22:27 +0000 Subject: [PATCH 05/25] Fix Prefer Builder Flag (#4622) --- beacon_node/client/src/config.rs | 2 -- beacon_node/src/config.rs | 7 ++--- lighthouse/tests/beacon_node.rs | 47 ++++++++++++++++++++++---------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index b4deb52fc31..283efe9c3fc 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -80,7 +80,6 @@ pub struct Config { pub monitoring_api: Option, pub slasher: Option, pub logger_config: LoggerConfig, - pub always_prefer_builder_payload: bool, pub beacon_processor: BeaconProcessorConfig, } @@ -108,7 +107,6 @@ impl Default for Config { validator_monitor_pubkeys: vec![], validator_monitor_individual_tracking_threshold: DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, logger_config: LoggerConfig::default(), - always_prefer_builder_payload: false, beacon_processor: <_>::default(), } } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index 0d2a5d812be..d2e92b48f33 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -351,6 +351,9 @@ pub fn get_config( el_config.default_datadir = client_config.data_dir().clone(); el_config.builder_profit_threshold = clap_utils::parse_required(cli_args, "builder-profit-threshold")?; + el_config.always_prefer_builder_payload = + cli_args.is_present("always-prefer-builder-payload"); + let execution_timeout_multiplier = clap_utils::parse_required(cli_args, "execution-timeout-multiplier")?; el_config.execution_timeout_multiplier = Some(execution_timeout_multiplier); @@ -801,10 +804,6 @@ pub fn get_config( if cli_args.is_present("genesis-backfill") { client_config.chain.genesis_backfill = true; } - // Payload selection configs - if cli_args.is_present("always-prefer-builder-payload") { - client_config.always_prefer_builder_payload = true; - } // Backfill sync rate-limiting client_config.beacon_processor.enable_backfill_rate_limiting = diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 02b95120127..37d224dbc31 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -366,21 +366,6 @@ fn genesis_backfill_with_historic_flag() { .with_config(|config| assert_eq!(config.chain.genesis_backfill, true)); } -#[test] -fn always_prefer_builder_payload_flag() { - CommandLineTest::new() - .flag("always-prefer-builder-payload", None) - .run_with_zero_port() - .with_config(|config| assert!(config.always_prefer_builder_payload)); -} - -#[test] -fn no_flag_sets_always_prefer_builder_payload_to_false() { - CommandLineTest::new() - .run_with_zero_port() - .with_config(|config| assert!(!config.always_prefer_builder_payload)); -} - // Tests for Eth1 flags. #[test] fn dummy_eth1_flag() { @@ -735,6 +720,38 @@ fn builder_fallback_flags() { ); }, ); + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + Some("always-prefer-builder-payload"), + None, + |config| { + assert_eq!( + config + .execution_layer + .as_ref() + .unwrap() + .always_prefer_builder_payload, + true + ); + }, + ); + run_payload_builder_flag_test_with_config( + "builder", + "http://meow.cats", + None, + None, + |config| { + assert_eq!( + config + .execution_layer + .as_ref() + .unwrap() + .always_prefer_builder_payload, + false + ); + }, + ); } #[test] From 20067b94655c9964c62b536831b1d093b3dc57f8 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 21 Aug 2023 05:02:32 +0000 Subject: [PATCH 06/25] Remove checkpoint alignment requirements and enable historic state pruning (#4610) ## Issue Addressed Closes #3210 Closes #3211 ## Proposed Changes - Checkpoint sync from the latest finalized state regardless of its alignment. - Add the `block_root` to the database's split point. This is _only_ added to the in-memory split in order to avoid a schema migration. See `load_split`. - Add a new method to the DB called `get_advanced_state`, which looks up a state _by block root_, with a `state_root` as fallback. Using this method prevents accidental accesses of the split's unadvanced state, which does not exist in the hot DB and is not guaranteed to exist in the freezer DB at all. Previously Lighthouse would look up this state _from the freezer DB_, even if it was required for block/attestation processing, which was suboptimal. - Replace several state look-ups in block and attestation processing with `get_advanced_state` so that they can't hit the split block's unadvanced state. - Do not store any states in the freezer database by default. All states will be deleted upon being evicted from the hot database unless `--reconstruct-historic-states` is set. The anchor info which was previously used for checkpoint sync is used to implement this, including when syncing from genesis. ## Additional Info Needs further testing. I want to stress-test the pruned database under Hydra. The `get_advanced_state` method is intended to become more relevant over time: `tree-states` includes an identically named method that returns advanced states from its in-memory cache. Co-authored-by: realbigsean --- beacon_node/beacon_chain/src/beacon_chain.rs | 13 +- .../src/beacon_fork_choice_store.rs | 12 +- .../beacon_chain/src/block_verification.rs | 17 +- beacon_node/beacon_chain/src/builder.rs | 86 +++-- .../beacon_chain/src/canonical_head.rs | 23 +- beacon_node/beacon_chain/src/migrate.rs | 8 +- .../tests/attestation_verification.rs | 10 +- .../beacon_chain/tests/block_verification.rs | 7 +- .../tests/payload_invalidation.rs | 6 +- beacon_node/beacon_chain/tests/store_tests.rs | 360 +++++++++++++----- beacon_node/beacon_chain/tests/tests.rs | 6 +- beacon_node/client/Cargo.toml | 2 +- beacon_node/client/src/builder.rs | 76 +--- beacon_node/http_api/tests/tests.rs | 21 +- .../gossip_methods.rs | 2 +- beacon_node/store/src/hot_cold_store.rs | 230 ++++++++--- beacon_node/store/src/metadata.rs | 3 + book/src/checkpoint-sync.md | 23 +- consensus/fork_choice/src/fork_choice.rs | 3 +- .../src/fork_choice_test_definition.rs | 1 + .../src/proto_array_fork_choice.rs | 5 +- .../src/per_slot_processing.rs | 2 +- testing/ef_tests/src/cases/fork_choice.rs | 6 +- testing/node_test_rig/src/lib.rs | 3 + watch/tests/tests.rs | 9 +- 25 files changed, 633 insertions(+), 301 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 987ea9e7c33..d5b86d63ff3 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -4656,6 +4656,7 @@ impl BeaconChain { self.log, "Produced block on state"; "block_size" => block_size, + "slot" => block.slot(), ); metrics::observe(&metrics::BLOCK_SIZE, block_size as f64); @@ -5571,14 +5572,16 @@ impl BeaconChain { let (mut state, state_root) = if let Some((state, state_root)) = head_state_opt { (state, state_root) } else { - let state_root = head_block.state_root; - let state = self + let block_state_root = head_block.state_root; + let max_slot = shuffling_epoch.start_slot(T::EthSpec::slots_per_epoch()); + let (state_root, state) = self .store .get_inconsistent_state_for_attestation_verification_only( - &state_root, - Some(head_block.slot), + &head_block_root, + max_slot, + block_state_root, )? - .ok_or(Error::MissingBeaconState(head_block.state_root))?; + .ok_or(Error::MissingBeaconState(block_state_root))?; (state, state_root) }; diff --git a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs index 9b2edbd8b5d..2a42b49b422 100644 --- a/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs +++ b/beacon_node/beacon_chain/src/beacon_fork_choice_store.rs @@ -321,9 +321,17 @@ where .deconstruct() .0; - let state = self + let max_slot = self + .justified_checkpoint + .epoch + .start_slot(E::slots_per_epoch()); + let (_, state) = self .store - .get_state(&justified_block.state_root(), Some(justified_block.slot())) + .get_advanced_hot_state( + self.justified_checkpoint.root, + max_slot, + justified_block.state_root(), + ) .map_err(Error::FailedToReadState)? .ok_or_else(|| Error::MissingState(justified_block.state_root()))?; diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 0a82eae3711..3654484e1f5 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -1261,7 +1261,7 @@ impl ExecutionPendingBlock { // Perform a sanity check on the pre-state. let parent_slot = parent.beacon_block.slot(); - if state.slot() < parent_slot || state.slot() > parent_slot + 1 { + if state.slot() < parent_slot || state.slot() > block.slot() { return Err(BeaconChainError::BadPreState { parent_root: parent.beacon_block_root, parent_slot, @@ -1760,13 +1760,18 @@ fn load_parent( BlockError::from(BeaconChainError::MissingBeaconBlock(block.parent_root())) })?; - // Load the parent blocks state from the database, returning an error if it is not found. + // Load the parent block's state from the database, returning an error if it is not found. // It is an error because if we know the parent block we should also know the parent state. - let parent_state_root = parent_block.state_root(); - let parent_state = chain - .get_state(&parent_state_root, Some(parent_block.slot()))? + // Retrieve any state that is advanced through to at most `block.slot()`: this is + // particularly important if `block` descends from the finalized/split block, but at a slot + // prior to the finalized slot (which is invalid and inaccessible in our DB schema). + let (parent_state_root, parent_state) = chain + .store + .get_advanced_hot_state(root, block.slot(), parent_block.state_root())? .ok_or_else(|| { - BeaconChainError::DBInconsistent(format!("Missing state {:?}", parent_state_root)) + BeaconChainError::DBInconsistent( + format!("Missing state for parent block {root:?}",), + ) })?; metrics::inc_counter(&metrics::BLOCK_PROCESSING_SNAPSHOT_CACHE_MISSES); diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index 044391c415e..bb629d66249 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -24,8 +24,9 @@ use operation_pool::{OperationPool, PersistedOperationPool}; use parking_lot::RwLock; use proto_array::{DisallowedReOrgOffsets, ReOrgThreshold}; use slasher::Slasher; -use slog::{crit, error, info, Logger}; +use slog::{crit, debug, error, info, Logger}; use slot_clock::{SlotClock, TestingSlotClock}; +use state_processing::per_slot_processing; use std::marker::PhantomData; use std::sync::Arc; use std::time::Duration; @@ -287,7 +288,7 @@ where let genesis_state = store .get_state(&genesis_block.state_root(), Some(genesis_block.slot())) .map_err(|e| descriptive_db_error("genesis state", &e))? - .ok_or("Genesis block not found in store")?; + .ok_or("Genesis state not found in store")?; self.genesis_time = Some(genesis_state.genesis_time()); @@ -382,6 +383,16 @@ where let (genesis, updated_builder) = self.set_genesis_state(beacon_state)?; self = updated_builder; + // Stage the database's metadata fields for atomic storage when `build` is called. + // Since v4.4.0 we will set the anchor with a dummy state upper limit in order to prevent + // historic states from being retained (unless `--reconstruct-historic-states` is set). + let retain_historic_states = self.chain_config.reconstruct_historic_states; + self.pending_io_batch.push( + store + .init_anchor_info(genesis.beacon_block.message(), retain_historic_states) + .map_err(|e| format!("Failed to initialize genesis anchor: {:?}", e))?, + ); + let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &genesis) .map_err(|e| format!("Unable to initialize fork choice store: {e:?}"))?; let current_slot = None; @@ -408,30 +419,28 @@ where weak_subj_block: SignedBeaconBlock, genesis_state: BeaconState, ) -> Result { - let store = self.store.clone().ok_or("genesis_state requires a store")?; - - let weak_subj_slot = weak_subj_state.slot(); - let weak_subj_block_root = weak_subj_block.canonical_root(); - let weak_subj_state_root = weak_subj_block.state_root(); - - // Check that the given block lies on an epoch boundary. Due to the database only storing - // full states on epoch boundaries and at restore points it would be difficult to support - // starting from a mid-epoch state. - if weak_subj_slot % TEthSpec::slots_per_epoch() != 0 { - return Err(format!( - "Checkpoint block at slot {} is not aligned to epoch start. \ - Please supply an aligned checkpoint with block.slot % 32 == 0", - weak_subj_block.slot(), - )); - } + let store = self + .store + .clone() + .ok_or("weak_subjectivity_state requires a store")?; + let log = self + .log + .as_ref() + .ok_or("weak_subjectivity_state requires a log")?; - // Check that the block and state have consistent slots and state roots. - if weak_subj_state.slot() != weak_subj_block.slot() { - return Err(format!( - "Slot of snapshot block ({}) does not match snapshot state ({})", - weak_subj_block.slot(), - weak_subj_state.slot(), - )); + // Ensure the state is advanced to an epoch boundary. + let slots_per_epoch = TEthSpec::slots_per_epoch(); + if weak_subj_state.slot() % slots_per_epoch != 0 { + debug!( + log, + "Advancing checkpoint state to boundary"; + "state_slot" => weak_subj_state.slot(), + "block_slot" => weak_subj_block.slot(), + ); + while weak_subj_state.slot() % slots_per_epoch != 0 { + per_slot_processing(&mut weak_subj_state, None, &self.spec) + .map_err(|e| format!("Error advancing state: {e:?}"))?; + } } // Prime all caches before storing the state in the database and computing the tree hash @@ -439,15 +448,19 @@ where weak_subj_state .build_caches(&self.spec) .map_err(|e| format!("Error building caches on checkpoint state: {e:?}"))?; - - let computed_state_root = weak_subj_state + let weak_subj_state_root = weak_subj_state .update_tree_hash_cache() .map_err(|e| format!("Error computing checkpoint state root: {:?}", e))?; - if weak_subj_state_root != computed_state_root { + let weak_subj_slot = weak_subj_state.slot(); + let weak_subj_block_root = weak_subj_block.canonical_root(); + + // Validate the state's `latest_block_header` against the checkpoint block. + let state_latest_block_root = weak_subj_state.get_latest_block_root(weak_subj_state_root); + if weak_subj_block_root != state_latest_block_root { return Err(format!( - "Snapshot state root does not match block, expected: {:?}, got: {:?}", - weak_subj_state_root, computed_state_root + "Snapshot state's most recent block root does not match block, expected: {:?}, got: {:?}", + weak_subj_block_root, state_latest_block_root )); } @@ -464,7 +477,7 @@ where // Set the store's split point *before* storing genesis so that genesis is stored // immediately in the freezer DB. - store.set_split(weak_subj_slot, weak_subj_state_root); + store.set_split(weak_subj_slot, weak_subj_state_root, weak_subj_block_root); let (_, updated_builder) = self.set_genesis_state(genesis_state)?; self = updated_builder; @@ -480,10 +493,11 @@ where // Stage the database's metadata fields for atomic storage when `build` is called. // This prevents the database from restarting in an inconsistent state if the anchor // info or split point is written before the `PersistedBeaconChain`. + let retain_historic_states = self.chain_config.reconstruct_historic_states; self.pending_io_batch.push(store.store_split_in_batch()); self.pending_io_batch.push( store - .init_anchor_info(weak_subj_block.message()) + .init_anchor_info(weak_subj_block.message(), retain_historic_states) .map_err(|e| format!("Failed to initialize anchor info: {:?}", e))?, ); @@ -503,13 +517,12 @@ where let fc_store = BeaconForkChoiceStore::get_forkchoice_store(store, &snapshot) .map_err(|e| format!("Unable to initialize fork choice store: {e:?}"))?; - let current_slot = Some(snapshot.beacon_block.slot()); let fork_choice = ForkChoice::from_anchor( fc_store, snapshot.beacon_block_root, &snapshot.beacon_block, &snapshot.beacon_state, - current_slot, + Some(weak_subj_slot), &self.spec, ) .map_err(|e| format!("Unable to initialize ForkChoice: {:?}", e))?; @@ -672,9 +685,8 @@ where Err(e) => return Err(descriptive_db_error("head block", &e)), }; - let head_state_root = head_block.state_root(); - let head_state = store - .get_state(&head_state_root, Some(head_block.slot())) + let (_head_state_root, head_state) = store + .get_advanced_hot_state(head_block_root, current_slot, head_block.state_root()) .map_err(|e| descriptive_db_error("head state", &e))? .ok_or("Head state not found in store")?; diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index 2b1f714362f..7fa5b015214 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -47,7 +47,8 @@ use crate::{ }; use eth2::types::{EventKind, SseChainReorg, SseFinalizedCheckpoint, SseHead, SseLateHead}; use fork_choice::{ - ExecutionStatus, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock, ResetPayloadStatuses, + ExecutionStatus, ForkChoiceStore, ForkChoiceView, ForkchoiceUpdateParameters, ProtoBlock, + ResetPayloadStatuses, }; use itertools::process_results; use parking_lot::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -298,10 +299,10 @@ impl CanonicalHead { let beacon_block = store .get_full_block(&beacon_block_root)? .ok_or(Error::MissingBeaconBlock(beacon_block_root))?; - let beacon_state_root = beacon_block.state_root(); - let beacon_state = store - .get_state(&beacon_state_root, Some(beacon_block.slot()))? - .ok_or(Error::MissingBeaconState(beacon_state_root))?; + let current_slot = fork_choice.fc_store().get_current_slot(); + let (_, beacon_state) = store + .get_advanced_hot_state(beacon_block_root, current_slot, beacon_block.state_root())? + .ok_or(Error::MissingBeaconState(beacon_block.state_root()))?; let snapshot = BeaconSnapshot { beacon_block_root, @@ -669,10 +670,14 @@ impl BeaconChain { .get_full_block(&new_view.head_block_root)? .ok_or(Error::MissingBeaconBlock(new_view.head_block_root))?; - let beacon_state_root = beacon_block.state_root(); - let beacon_state: BeaconState = self - .get_state(&beacon_state_root, Some(beacon_block.slot()))? - .ok_or(Error::MissingBeaconState(beacon_state_root))?; + let (_, beacon_state) = self + .store + .get_advanced_hot_state( + new_view.head_block_root, + current_slot, + beacon_block.state_root(), + )? + .ok_or(Error::MissingBeaconState(beacon_block.state_root()))?; Ok(BeaconSnapshot { beacon_block: Arc::new(beacon_block), diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index 8306b66d7b5..6353a64e007 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -266,6 +266,7 @@ impl, Cold: ItemStore> BackgroundMigrator state, @@ -319,7 +320,12 @@ impl, Cold: ItemStore> BackgroundMigrator {} Err(Error::HotColdDBError(HotColdDBError::FreezeSlotUnaligned(slot))) => { debug!( diff --git a/beacon_node/beacon_chain/tests/attestation_verification.rs b/beacon_node/beacon_chain/tests/attestation_verification.rs index 5cea51090b9..7878fd14aa0 100644 --- a/beacon_node/beacon_chain/tests/attestation_verification.rs +++ b/beacon_node/beacon_chain/tests/attestation_verification.rs @@ -9,7 +9,7 @@ use beacon_chain::{ test_utils::{ test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, }, - BeaconChain, BeaconChainError, BeaconChainTypes, WhenSlotSkipped, + BeaconChain, BeaconChainError, BeaconChainTypes, ChainConfig, WhenSlotSkipped, }; use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH}; use int_to_bytes::int_to_bytes32; @@ -47,6 +47,10 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness Vec> { fn get_harness(validator_count: usize) -> BeaconChainHarness> { let harness = BeaconChainHarness::builder(MainnetEthSpec) .default_spec() + .chain_config(ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }) .keypairs(KEYPAIRS[0..validator_count].to_vec()) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/payload_invalidation.rs b/beacon_node/beacon_chain/tests/payload_invalidation.rs index 9a8c324d09f..cd4351297bc 100644 --- a/beacon_node/beacon_chain/tests/payload_invalidation.rs +++ b/beacon_node/beacon_chain/tests/payload_invalidation.rs @@ -7,7 +7,7 @@ use beacon_chain::otb_verification_service::{ use beacon_chain::{ canonical_head::{CachedHead, CanonicalHead}, test_utils::{BeaconChainHarness, EphemeralHarnessType}, - BeaconChainError, BlockError, ExecutionPayloadError, NotifyExecutionLayer, + BeaconChainError, BlockError, ChainConfig, ExecutionPayloadError, NotifyExecutionLayer, OverrideForkchoiceUpdate, StateSkipConfig, WhenSlotSkipped, INVALID_FINALIZED_MERGE_TRANSITION_BLOCK_SHUTDOWN_REASON, INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON, @@ -59,6 +59,10 @@ impl InvalidPayloadRig { let harness = BeaconChainHarness::builder(MainnetEthSpec) .spec(spec) + .chain_config(ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }) .logger(test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .mock_execution_layer() diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index 29027748259..bf68657b4f5 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -9,14 +9,15 @@ use beacon_chain::test_utils::{ use beacon_chain::validator_monitor::DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD; use beacon_chain::{ historical_blocks::HistoricalBlockError, migrate::MigratorConfig, BeaconChain, - BeaconChainError, BeaconChainTypes, BeaconSnapshot, ChainConfig, NotifyExecutionLayer, - ServerSentEventHandler, WhenSlotSkipped, + BeaconChainError, BeaconChainTypes, BeaconSnapshot, BlockError, ChainConfig, + NotifyExecutionLayer, ServerSentEventHandler, WhenSlotSkipped, }; use lazy_static::lazy_static; use logging::test_logger; use maplit::hashset; use rand::Rng; -use state_processing::BlockReplayer; +use slot_clock::{SlotClock, TestingSlotClock}; +use state_processing::{state_advance::complete_state_advance, BlockReplayer}; use std::collections::HashMap; use std::collections::HashSet; use std::convert::TryInto; @@ -65,6 +66,19 @@ fn get_store_with_spec( fn get_harness( store: Arc, LevelDB>>, validator_count: usize, +) -> TestHarness { + // Most tests expect to retain historic states, so we use this as the default. + let chain_config = ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }; + get_harness_generic(store, validator_count, chain_config) +} + +fn get_harness_generic( + store: Arc, LevelDB>>, + validator_count: usize, + chain_config: ChainConfig, ) -> TestHarness { let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() @@ -72,6 +86,7 @@ fn get_harness( .logger(store.logger().clone()) .fresh_disk_store(store) .mock_execution_layer() + .chain_config(chain_config) .build(); harness.advance_slot(); harness @@ -460,13 +475,15 @@ async fn block_replay_with_inaccurate_state_roots() { .await; // Slot must not be 0 mod 32 or else no blocks will be replayed. - let (mut head_state, head_root) = harness.get_current_state_and_root(); + let (mut head_state, head_state_root) = harness.get_current_state_and_root(); + let head_block_root = harness.head_block_root(); assert_ne!(head_state.slot() % 32, 0); - let mut fast_head_state = store + let (_, mut fast_head_state) = store .get_inconsistent_state_for_attestation_verification_only( - &head_root, - Some(head_state.slot()), + &head_block_root, + head_state.slot(), + head_state_root, ) .unwrap() .unwrap(); @@ -565,14 +582,7 @@ async fn block_replayer_hooks() { async fn delete_blocks_and_states() { let db_path = tempdir().unwrap(); let store = get_store(&db_path); - let validators_keypairs = - types::test_utils::generate_deterministic_keypairs(LOW_VALIDATOR_COUNT); - let harness = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_disk_store(store.clone()) - .mock_execution_layer() - .build(); + let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT); let unforked_blocks: u64 = 4 * E::slots_per_epoch(); @@ -1015,18 +1025,14 @@ fn check_shuffling_compatible( // Ensure blocks from abandoned forks are pruned from the Hot DB #[tokio::test] async fn prunes_abandoned_fork_between_two_finalized_checkpoints() { - const HONEST_VALIDATOR_COUNT: usize = 32 + 0; - const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0; + const HONEST_VALIDATOR_COUNT: usize = 32; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let slots_per_epoch = rig.slots_per_epoch(); let (mut state, state_root) = rig.get_current_state_and_root(); @@ -1125,18 +1131,14 @@ async fn prunes_abandoned_fork_between_two_finalized_checkpoints() { #[tokio::test] async fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() { - const HONEST_VALIDATOR_COUNT: usize = 32 + 0; - const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0; + const HONEST_VALIDATOR_COUNT: usize = 32; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let slots_per_epoch = rig.slots_per_epoch(); let (state, state_root) = rig.get_current_state_and_root(); @@ -1260,15 +1262,11 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() { const HONEST_VALIDATOR_COUNT: usize = 32; const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let slots_per_epoch = rig.slots_per_epoch(); let (mut state, state_root) = rig.get_current_state_and_root(); @@ -1352,18 +1350,14 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() { #[tokio::test] async fn prunes_fork_growing_past_youngest_finalized_checkpoint() { - const HONEST_VALIDATOR_COUNT: usize = 32 + 0; - const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0; + const HONEST_VALIDATOR_COUNT: usize = 32; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let (state, state_root) = rig.get_current_state_and_root(); // Fill up 0th epoch with canonical chain blocks @@ -1497,18 +1491,14 @@ async fn prunes_fork_growing_past_youngest_finalized_checkpoint() { // This is to check if state outside of normal block processing are pruned correctly. #[tokio::test] async fn prunes_skipped_slots_states() { - const HONEST_VALIDATOR_COUNT: usize = 32 + 0; - const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0; + const HONEST_VALIDATOR_COUNT: usize = 32; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let (state, state_root) = rig.get_current_state_and_root(); let canonical_slots_zeroth_epoch: Vec = @@ -1626,18 +1616,14 @@ async fn prunes_skipped_slots_states() { // This is to check if state outside of normal block processing are pruned correctly. #[tokio::test] async fn finalizes_non_epoch_start_slot() { - const HONEST_VALIDATOR_COUNT: usize = 32 + 0; - const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0; + const HONEST_VALIDATOR_COUNT: usize = 32; + const ADVERSARIAL_VALIDATOR_COUNT: usize = 16; const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT; - let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT); let honest_validators: Vec = (0..HONEST_VALIDATOR_COUNT).collect(); let adversarial_validators: Vec = (HONEST_VALIDATOR_COUNT..VALIDATOR_COUNT).collect(); - let rig = BeaconChainHarness::builder(MinimalEthSpec) - .default_spec() - .keypairs(validators_keypairs) - .fresh_ephemeral_store() - .mock_execution_layer() - .build(); + let db_path = tempdir().unwrap(); + let store = get_store(&db_path); + let rig = get_harness(store.clone(), VALIDATOR_COUNT); let (state, state_root) = rig.get_current_state_and_root(); let canonical_slots_zeroth_epoch: Vec = @@ -2053,39 +2039,82 @@ async fn garbage_collect_temp_states_from_failed_block() { } #[tokio::test] -async fn weak_subjectivity_sync() { +async fn weak_subjectivity_sync_easy() { + let num_initial_slots = E::slots_per_epoch() * 11; + let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9); + let slots = (1..num_initial_slots).map(Slot::new).collect(); + weak_subjectivity_sync_test(slots, checkpoint_slot).await +} + +#[tokio::test] +async fn weak_subjectivity_sync_unaligned_advanced_checkpoint() { + let num_initial_slots = E::slots_per_epoch() * 11; + let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9); + let slots = (1..num_initial_slots) + .map(Slot::new) + .filter(|&slot| { + // Skip 3 slots leading up to the checkpoint slot. + slot <= checkpoint_slot - 3 || slot > checkpoint_slot + }) + .collect(); + weak_subjectivity_sync_test(slots, checkpoint_slot).await +} + +#[tokio::test] +async fn weak_subjectivity_sync_unaligned_unadvanced_checkpoint() { + let num_initial_slots = E::slots_per_epoch() * 11; + let checkpoint_slot = Slot::new(E::slots_per_epoch() * 9 - 3); + let slots = (1..num_initial_slots) + .map(Slot::new) + .filter(|&slot| { + // Skip 3 slots after the checkpoint slot. + slot <= checkpoint_slot || slot > checkpoint_slot + 3 + }) + .collect(); + weak_subjectivity_sync_test(slots, checkpoint_slot).await +} + +async fn weak_subjectivity_sync_test(slots: Vec, checkpoint_slot: Slot) { // Build an initial chain on one harness, representing a synced node with full history. - let num_initial_blocks = E::slots_per_epoch() * 11; let num_final_blocks = E::slots_per_epoch() * 2; let temp1 = tempdir().unwrap(); let full_store = get_store(&temp1); let harness = get_harness(full_store.clone(), LOW_VALIDATOR_COUNT); + let all_validators = (0..LOW_VALIDATOR_COUNT).collect::>(); + + let (genesis_state, genesis_state_root) = harness.get_current_state_and_root(); harness - .extend_chain( - num_initial_blocks as usize, - BlockStrategy::OnCanonicalHead, - AttestationStrategy::AllValidators, + .add_attested_blocks_at_slots( + genesis_state.clone(), + genesis_state_root, + &slots, + &all_validators, ) .await; - let genesis_state = full_store - .get_state(&harness.chain.genesis_state_root, Some(Slot::new(0))) + let wss_block_root = harness + .chain + .block_root_at_slot(checkpoint_slot, WhenSlotSkipped::Prev) + .unwrap() + .unwrap(); + let wss_state_root = harness + .chain + .state_root_at_slot(checkpoint_slot) .unwrap() .unwrap(); - let wss_checkpoint = harness.finalized_checkpoint(); + let wss_block = harness .chain .store - .get_full_block(&wss_checkpoint.root) + .get_full_block(&wss_block_root) .unwrap() .unwrap(); let wss_state = full_store - .get_state(&wss_block.state_root(), None) + .get_state(&wss_state_root, Some(checkpoint_slot)) .unwrap() .unwrap(); - let wss_slot = wss_block.slot(); // Add more blocks that advance finalization further. harness.advance_slot(); @@ -2104,20 +2133,26 @@ async fn weak_subjectivity_sync() { let spec = test_spec::(); let seconds_per_slot = spec.seconds_per_slot; - // Initialise a new beacon chain from the finalized checkpoint + // Initialise a new beacon chain from the finalized checkpoint. + // The slot clock must be set to a time ahead of the checkpoint state. + let slot_clock = TestingSlotClock::new( + Slot::new(0), + Duration::from_secs(harness.chain.genesis_time), + Duration::from_secs(seconds_per_slot), + ); + slot_clock.set_slot(harness.get_current_slot().as_u64()); let beacon_chain = Arc::new( BeaconChainBuilder::new(MinimalEthSpec) .store(store.clone()) .custom_spec(test_spec::()) .task_executor(harness.chain.task_executor.clone()) + .logger(log.clone()) .weak_subjectivity_state(wss_state, wss_block.clone(), genesis_state) .unwrap() - .logger(log.clone()) .store_migrator_config(MigratorConfig::default().blocking()) .dummy_eth1_backend() .expect("should build dummy backend") - .testing_slot_clock(Duration::from_secs(seconds_per_slot)) - .expect("should configure testing slot clock") + .slot_clock(slot_clock) .shutdown_sender(shutdown_tx) .chain_config(ChainConfig::default()) .event_handler(Some(ServerSentEventHandler::new_with_capacity( @@ -2131,9 +2166,9 @@ async fn weak_subjectivity_sync() { // Apply blocks forward to reach head. let chain_dump = harness.chain.chain_dump().unwrap(); - let new_blocks = &chain_dump[wss_slot.as_usize() + 1..]; - - assert_eq!(new_blocks[0].beacon_block.slot(), wss_slot + 1); + let new_blocks = chain_dump + .iter() + .filter(|snapshot| snapshot.beacon_block.slot() > checkpoint_slot); for snapshot in new_blocks { let full_block = harness @@ -2219,13 +2254,17 @@ async fn weak_subjectivity_sync() { assert_eq!(forwards, expected); // All blocks can be loaded. + let mut prev_block_root = Hash256::zero(); for (block_root, slot) in beacon_chain .forwards_iter_block_roots(Slot::new(0)) .unwrap() .map(Result::unwrap) { let block = store.get_blinded_block(&block_root).unwrap().unwrap(); - assert_eq!(block.slot(), slot); + if block_root != prev_block_root { + assert_eq!(block.slot(), slot); + } + prev_block_root = block_root; } // All states from the oldest state slot can be loaded. @@ -2240,14 +2279,141 @@ async fn weak_subjectivity_sync() { assert_eq!(state.canonical_root(), state_root); } - // Anchor slot is still set to the starting slot. - assert_eq!(store.get_anchor_slot(), Some(wss_slot)); + // Anchor slot is still set to the slot of the checkpoint block. + assert_eq!(store.get_anchor_slot(), Some(wss_block.slot())); // Reconstruct states. store.clone().reconstruct_historic_states().unwrap(); assert_eq!(store.get_anchor_slot(), None); } +/// Test that blocks and attestations that refer to states around an unaligned split state are +/// processed correctly. +#[tokio::test] +async fn process_blocks_and_attestations_for_unaligned_checkpoint() { + let temp = tempdir().unwrap(); + let store = get_store(&temp); + let chain_config = ChainConfig { + reconstruct_historic_states: false, + ..ChainConfig::default() + }; + let harness = get_harness_generic(store.clone(), LOW_VALIDATOR_COUNT, chain_config); + + let all_validators = (0..LOW_VALIDATOR_COUNT).collect::>(); + + let split_slot = Slot::new(E::slots_per_epoch() * 4); + let pre_skips = 1; + let post_skips = 1; + + // Build the chain up to the intended split slot, with 3 skips before the split. + let slots = (1..=split_slot.as_u64() - pre_skips) + .map(Slot::new) + .collect::>(); + + let (genesis_state, genesis_state_root) = harness.get_current_state_and_root(); + harness + .add_attested_blocks_at_slots( + genesis_state.clone(), + genesis_state_root, + &slots, + &all_validators, + ) + .await; + + // Before the split slot becomes finalized, create two forking blocks that build on the split + // block: + // + // - one that is invalid because it conflicts with finalization (slot <= finalized_slot) + // - one that is valid because its slot is not finalized (slot > finalized_slot) + let (unadvanced_split_state, unadvanced_split_state_root) = + harness.get_current_state_and_root(); + + let (invalid_fork_block, _) = harness + .make_block(unadvanced_split_state.clone(), split_slot) + .await; + let (valid_fork_block, _) = harness + .make_block(unadvanced_split_state.clone(), split_slot + 1) + .await; + + // Advance the chain so that the intended split slot is finalized. + // Do not attest in the epoch boundary slot, to make attestation production later easier (no + // equivocations). + let finalizing_slot = split_slot + 2 * E::slots_per_epoch(); + for _ in 0..pre_skips + post_skips { + harness.advance_slot(); + } + harness.extend_to_slot(finalizing_slot - 1).await; + harness + .add_block_at_slot(finalizing_slot, harness.get_current_state()) + .await + .unwrap(); + + // Check that the split slot is as intended. + let split = store.get_split_info(); + assert_eq!(split.slot, split_slot); + assert_eq!(split.block_root, valid_fork_block.parent_root()); + assert_ne!(split.state_root, unadvanced_split_state_root); + + // Applying the invalid block should fail. + let err = harness + .chain + .process_block( + invalid_fork_block.canonical_root(), + Arc::new(invalid_fork_block.clone()), + NotifyExecutionLayer::Yes, + || Ok(()), + ) + .await + .unwrap_err(); + assert!(matches!(err, BlockError::WouldRevertFinalizedSlot { .. })); + + // Applying the valid block should succeed, but it should not become head. + harness + .chain + .process_block( + valid_fork_block.canonical_root(), + Arc::new(valid_fork_block.clone()), + NotifyExecutionLayer::Yes, + || Ok(()), + ) + .await + .unwrap(); + harness.chain.recompute_head_at_current_slot().await; + assert_ne!(harness.head_block_root(), valid_fork_block.canonical_root()); + + // Attestations to the split block in the next 2 epochs should be processed successfully. + let attestation_start_slot = harness.get_current_slot(); + let attestation_end_slot = attestation_start_slot + 2 * E::slots_per_epoch(); + let (split_state_root, mut advanced_split_state) = harness + .chain + .store + .get_advanced_hot_state(split.block_root, split.slot, split.state_root) + .unwrap() + .unwrap(); + complete_state_advance( + &mut advanced_split_state, + Some(split_state_root), + attestation_start_slot, + &harness.chain.spec, + ) + .unwrap(); + advanced_split_state + .build_caches(&harness.chain.spec) + .unwrap(); + let advanced_split_state_root = advanced_split_state.update_tree_hash_cache().unwrap(); + for slot in (attestation_start_slot.as_u64()..attestation_end_slot.as_u64()).map(Slot::new) { + let attestations = harness.make_attestations( + &all_validators, + &advanced_split_state, + advanced_split_state_root, + split.block_root.into(), + slot, + ); + harness.advance_slot(); + harness.process_attestations(attestations); + } +} + #[tokio::test] async fn finalizes_after_resuming_from_db() { let validator_count = 16; @@ -2306,6 +2472,7 @@ async fn finalizes_after_resuming_from_db() { .default_spec() .keypairs(KEYPAIRS[0..validator_count].to_vec()) .resumed_disk_store(store) + .testing_slot_clock(original_chain.slot_clock.clone()) .mock_execution_layer() .build(); @@ -2559,6 +2726,9 @@ async fn schema_downgrade_to_min_version() { SchemaVersion(11) }; + // Save the slot clock so that the new harness doesn't revert in time. + let slot_clock = harness.chain.slot_clock.clone(); + // Close the database to ensure everything is written to disk. drop(store); drop(harness); @@ -2589,11 +2759,21 @@ async fn schema_downgrade_to_min_version() { ) .expect("schema upgrade from minimum version should work"); - // Rescreate the harness. + // Recreate the harness. + /* + let slot_clock = TestingSlotClock::new( + Slot::new(0), + Duration::from_secs(harness.chain.genesis_time), + Duration::from_secs(spec.seconds_per_slot), + ); + slot_clock.set_slot(harness.get_current_slot().as_u64()); + */ + let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() .keypairs(KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec()) .logger(store.logger().clone()) + .testing_slot_clock(slot_clock) .resumed_disk_store(store.clone()) .mock_execution_layer() .build(); diff --git a/beacon_node/beacon_chain/tests/tests.rs b/beacon_node/beacon_chain/tests/tests.rs index c5b2892cbdb..8935c69926c 100644 --- a/beacon_node/beacon_chain/tests/tests.rs +++ b/beacon_node/beacon_chain/tests/tests.rs @@ -6,7 +6,7 @@ use beacon_chain::{ AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, OP_POOL_DB_KEY, }, - BeaconChain, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped, + BeaconChain, ChainConfig, NotifyExecutionLayer, StateSkipConfig, WhenSlotSkipped, }; use lazy_static::lazy_static; use operation_pool::PersistedOperationPool; @@ -28,6 +28,10 @@ lazy_static! { fn get_harness(validator_count: usize) -> BeaconChainHarness> { let harness = BeaconChainHarness::builder(MinimalEthSpec) .default_spec() + .chain_config(ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }) .keypairs(KEYPAIRS[0..validator_count].to_vec()) .fresh_ephemeral_store() .mock_execution_layer() diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 72b6a6c7d47..0b517930fb9 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" [dev-dependencies] serde_yaml = "0.8.13" -state_processing = { path = "../../consensus/state_processing" } operation_pool = { path = "../operation_pool" } tokio = "1.14.0" [dependencies] +state_processing = { path = "../../consensus/state_processing" } beacon_chain = { path = "../beacon_chain" } store = { path = "../store" } network = { path = "../network" } diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index bd2946bb7a4..b09cf9cca3b 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -309,7 +309,6 @@ where config.chain.checkpoint_sync_url_timeout, )), ); - let slots_per_epoch = TEthSpec::slots_per_epoch(); let deposit_snapshot = if config.sync_eth1_chain { // We want to fetch deposit snapshot before fetching the finalized beacon state to @@ -356,10 +355,23 @@ where None }; - debug!(context.log(), "Downloading finalized block"); - // Find a suitable finalized block on an epoch boundary. - let mut block = remote - .get_beacon_blocks_ssz::(BlockId::Finalized, &spec) + debug!( + context.log(), + "Downloading finalized state"; + ); + let state = remote + .get_debug_beacon_states_ssz::(StateId::Finalized, &spec) + .await + .map_err(|e| format!("Error loading checkpoint state from remote: {:?}", e))? + .ok_or_else(|| "Checkpoint state missing from remote".to_string())?; + + debug!(context.log(), "Downloaded finalized state"; "slot" => ?state.slot()); + + let finalized_block_slot = state.latest_block_header().slot; + + debug!(context.log(), "Downloading finalized block"; "block_slot" => ?finalized_block_slot); + let block = remote + .get_beacon_blocks_ssz::(BlockId::Slot(finalized_block_slot), &spec) .await .map_err(|e| match e { ApiError::InvalidSsz(e) => format!( @@ -373,65 +385,15 @@ where debug!(context.log(), "Downloaded finalized block"); - let mut block_slot = block.slot(); - - while block.slot() % slots_per_epoch != 0 { - block_slot = (block_slot / slots_per_epoch - 1) * slots_per_epoch; - - debug!( - context.log(), - "Searching for aligned checkpoint block"; - "block_slot" => block_slot - ); - - if let Some(found_block) = remote - .get_beacon_blocks_ssz::(BlockId::Slot(block_slot), &spec) - .await - .map_err(|e| { - format!("Error fetching block at slot {}: {:?}", block_slot, e) - })? - { - block = found_block; - } - } - - debug!( - context.log(), - "Downloaded aligned finalized block"; - "block_root" => ?block.canonical_root(), - "block_slot" => block.slot(), - ); - - let state_root = block.state_root(); - debug!( - context.log(), - "Downloading finalized state"; - "state_root" => ?state_root - ); - let state = remote - .get_debug_beacon_states_ssz::(StateId::Root(state_root), &spec) - .await - .map_err(|e| { - format!( - "Error loading checkpoint state from remote {:?}: {:?}", - state_root, e - ) - })? - .ok_or_else(|| { - format!("Checkpoint state missing from remote: {:?}", state_root) - })?; - - debug!(context.log(), "Downloaded finalized state"); - let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec) .map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?; info!( context.log(), "Loaded checkpoint block and state"; - "slot" => block.slot(), + "block_slot" => block.slot(), + "state_slot" => state.slot(), "block_root" => ?block.canonical_root(), - "state_root" => ?state_root, ); let service = diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 3ae495378e3..3c72441c02f 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -1,7 +1,7 @@ use beacon_chain::test_utils::RelativeSyncCommittee; use beacon_chain::{ test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType}, - BeaconChain, StateSkipConfig, WhenSlotSkipped, + BeaconChain, ChainConfig, StateSkipConfig, WhenSlotSkipped, }; use environment::null_logger; use eth2::{ @@ -77,6 +77,7 @@ struct ApiTester { struct ApiTesterConfig { spec: ChainSpec, + retain_historic_states: bool, builder_threshold: Option, } @@ -86,11 +87,19 @@ impl Default for ApiTesterConfig { spec.shard_committee_period = 2; Self { spec, + retain_historic_states: false, builder_threshold: None, } } } +impl ApiTesterConfig { + fn retain_historic_states(mut self) -> Self { + self.retain_historic_states = true; + self + } +} + impl ApiTester { pub async fn new() -> Self { // This allows for testing voluntary exits without building out a massive chain. @@ -118,6 +127,10 @@ impl ApiTester { let harness = Arc::new( BeaconChainHarness::builder(MainnetEthSpec) .spec(spec.clone()) + .chain_config(ChainConfig { + reconstruct_historic_states: config.retain_historic_states, + ..ChainConfig::default() + }) .logger(logging::test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() @@ -375,6 +388,7 @@ impl ApiTester { pub async fn new_mev_tester_no_builder_threshold() -> Self { let mut config = ApiTesterConfig { builder_threshold: Some(0), + retain_historic_states: false, spec: E::default_spec(), }; config.spec.altair_fork_epoch = Some(Epoch::new(0)); @@ -4705,7 +4719,7 @@ async fn get_validator_duties_attester_with_skip_slots() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_validator_duties_proposer() { - ApiTester::new() + ApiTester::new_from_config(ApiTesterConfig::default().retain_historic_states()) .await .test_get_validator_duties_proposer() .await; @@ -4713,7 +4727,7 @@ async fn get_validator_duties_proposer() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn get_validator_duties_proposer_with_skip_slots() { - ApiTester::new() + ApiTester::new_from_config(ApiTesterConfig::default().retain_historic_states()) .await .skip_slots(E::slots_per_epoch() * 2) .test_get_validator_duties_proposer() @@ -5045,6 +5059,7 @@ async fn builder_payload_chosen_by_profit() { async fn builder_works_post_capella() { let mut config = ApiTesterConfig { builder_threshold: Some(0), + retain_historic_states: false, spec: E::default_spec(), }; config.spec.altair_fork_epoch = Some(Epoch::new(0)); diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index cb4d6f9c2bd..ac7479db011 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -2062,7 +2062,7 @@ impl NetworkBeaconProcessor { ); } AttnError::BeaconChainError(BeaconChainError::DBError(Error::HotColdDBError( - HotColdDBError::AttestationStateIsFinalized { .. }, + HotColdDBError::FinalizedStateNotInHotDatabase { .. }, ))) => { debug!(self.log, "Attestation for finalized state"; "peer_id" => % peer_id); self.propagate_validation_result(message_id, peer_id, MessageAcceptance::Ignore); diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 7695ea520e8..47839ed3e98 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -14,7 +14,7 @@ use crate::memory_store::MemoryStore; use crate::metadata::{ AnchorInfo, CompactionTimestamp, PruningCheckpoint, SchemaVersion, ANCHOR_INFO_KEY, COMPACTION_TIMESTAMP_KEY, CONFIG_KEY, CURRENT_SCHEMA_VERSION, PRUNING_CHECKPOINT_KEY, - SCHEMA_VERSION_KEY, SPLIT_KEY, + SCHEMA_VERSION_KEY, SPLIT_KEY, STATE_UPPER_LIMIT_NO_RETAIN, }; use crate::metrics; use crate::{ @@ -110,10 +110,10 @@ pub enum HotColdDBError { IterationError { unexpected_key: BytesKey, }, - AttestationStateIsFinalized { + FinalizedStateNotInHotDatabase { split_slot: Slot, - request_slot: Option, - state_root: Hash256, + request_slot: Slot, + block_root: Hash256, }, } @@ -545,7 +545,7 @@ impl, Cold: ItemStore> HotColdDB /// upon that state (e.g., state roots). Additionally, only states from the hot store are /// returned. /// - /// See `Self::get_state` for information about `slot`. + /// See `Self::get_advanced_hot_state` for information about `max_slot`. /// /// ## Warning /// @@ -557,23 +557,78 @@ impl, Cold: ItemStore> HotColdDB /// - `state.block_roots` pub fn get_inconsistent_state_for_attestation_verification_only( &self, - state_root: &Hash256, - slot: Option, - ) -> Result>, Error> { + block_root: &Hash256, + max_slot: Slot, + state_root: Hash256, + ) -> Result)>, Error> { metrics::inc_counter(&metrics::BEACON_STATE_GET_COUNT); + self.get_advanced_hot_state_with_strategy( + *block_root, + max_slot, + state_root, + StateProcessingStrategy::Inconsistent, + ) + } - let split_slot = self.get_split_slot(); + /// Get a state with `latest_block_root == block_root` advanced through to at most `max_slot`. + /// + /// The `state_root` argument is used to look up the block's un-advanced state in case an + /// advanced state is not found. + /// + /// Return the `(result_state_root, state)` satisfying: + /// + /// - `result_state_root == state.canonical_root()` + /// - `state.slot() <= max_slot` + /// - `state.get_latest_block_root(result_state_root) == block_root` + /// + /// Presently this is only used to avoid loading the un-advanced split state, but in future will + /// be expanded to return states from an in-memory cache. + pub fn get_advanced_hot_state( + &self, + block_root: Hash256, + max_slot: Slot, + state_root: Hash256, + ) -> Result)>, Error> { + self.get_advanced_hot_state_with_strategy( + block_root, + max_slot, + state_root, + StateProcessingStrategy::Accurate, + ) + } - if slot.map_or(false, |slot| slot < split_slot) { - Err(HotColdDBError::AttestationStateIsFinalized { - split_slot, - request_slot: slot, - state_root: *state_root, + /// Same as `get_advanced_hot_state` but taking a `StateProcessingStrategy`. + pub fn get_advanced_hot_state_with_strategy( + &self, + block_root: Hash256, + max_slot: Slot, + state_root: Hash256, + state_processing_strategy: StateProcessingStrategy, + ) -> Result)>, Error> { + // Hold a read lock on the split point so it can't move while we're trying to load the + // state. + let split = self.split.read_recursive(); + + // Sanity check max-slot against the split slot. + if max_slot < split.slot { + return Err(HotColdDBError::FinalizedStateNotInHotDatabase { + split_slot: split.slot, + request_slot: max_slot, + block_root, } - .into()) - } else { - self.load_hot_state(state_root, StateProcessingStrategy::Inconsistent) + .into()); } + + let state_root = if block_root == split.block_root && split.slot <= max_slot { + split.state_root + } else { + state_root + }; + let state = self + .load_hot_state(&state_root, state_processing_strategy)? + .map(|state| (state_root, state)); + drop(split); + Ok(state) } /// Delete a state, ensuring it is removed from the LRU cache, as well as from on-disk. @@ -1180,8 +1235,12 @@ impl, Cold: ItemStore> HotColdDB *self.split.read_recursive() } - pub fn set_split(&self, slot: Slot, state_root: Hash256) { - *self.split.write() = Split { slot, state_root }; + pub fn set_split(&self, slot: Slot, state_root: Hash256, block_root: Hash256) { + *self.split.write() = Split { + slot, + state_root, + block_root, + }; } /// Fetch the slot of the most recently stored restore point. @@ -1216,25 +1275,36 @@ impl, Cold: ItemStore> HotColdDB } /// Initialise the anchor info for checkpoint sync starting from `block`. - pub fn init_anchor_info(&self, block: BeaconBlockRef<'_, E>) -> Result { + pub fn init_anchor_info( + &self, + block: BeaconBlockRef<'_, E>, + retain_historic_states: bool, + ) -> Result { let anchor_slot = block.slot(); let slots_per_restore_point = self.config.slots_per_restore_point; - // Set the `state_upper_limit` to the slot of the *next* restore point. - // See `get_state_upper_limit` for rationale. - let next_restore_point_slot = if anchor_slot % slots_per_restore_point == 0 { + let state_upper_limit = if !retain_historic_states { + STATE_UPPER_LIMIT_NO_RETAIN + } else if anchor_slot % slots_per_restore_point == 0 { anchor_slot } else { + // Set the `state_upper_limit` to the slot of the *next* restore point. + // See `get_state_upper_limit` for rationale. (anchor_slot / slots_per_restore_point + 1) * slots_per_restore_point }; - let anchor_info = AnchorInfo { - anchor_slot, - oldest_block_slot: anchor_slot, - oldest_block_parent: block.parent_root(), - state_upper_limit: next_restore_point_slot, - state_lower_limit: self.spec.genesis_slot, + let anchor_info = if state_upper_limit == 0 && anchor_slot == 0 { + // Genesis archive node: no anchor because we *will* store all states. + None + } else { + Some(AnchorInfo { + anchor_slot, + oldest_block_slot: anchor_slot, + oldest_block_parent: block.parent_root(), + state_upper_limit, + state_lower_limit: self.spec.genesis_slot, + }) }; - self.compare_and_set_anchor_info(None, Some(anchor_info)) + self.compare_and_set_anchor_info(None, anchor_info) } /// Get a clone of the store's anchor info. @@ -1361,11 +1431,26 @@ impl, Cold: ItemStore> HotColdDB self.hot_db.put(&CONFIG_KEY, &self.config.as_disk_config()) } - /// Load the split point from disk. - fn load_split(&self) -> Result, Error> { + /// Load the split point from disk, sans block root. + fn load_split_partial(&self) -> Result, Error> { self.hot_db.get(&SPLIT_KEY) } + /// Load the split point from disk, including block root. + fn load_split(&self) -> Result, Error> { + match self.load_split_partial()? { + Some(mut split) => { + // Load the hot state summary to get the block root. + let summary = self.load_hot_state_summary(&split.state_root)?.ok_or( + HotColdDBError::MissingSplitState(split.state_root, split.slot), + )?; + split.block_root = summary.latest_block_root; + Ok(Some(split)) + } + None => Ok(None), + } + } + /// Stage the split for storage to disk. pub fn store_split_in_batch(&self) -> KeyValueStoreOp { self.split.read_recursive().as_kv_store_op(SPLIT_KEY) @@ -1611,42 +1696,40 @@ impl, Cold: ItemStore> HotColdDB /// Advance the split point of the store, moving new finalized states to the freezer. pub fn migrate_database, Cold: ItemStore>( store: Arc>, - frozen_head_root: Hash256, - frozen_head: &BeaconState, + finalized_state_root: Hash256, + finalized_block_root: Hash256, + finalized_state: &BeaconState, ) -> Result<(), Error> { debug!( store.log, "Freezer migration started"; - "slot" => frozen_head.slot() + "slot" => finalized_state.slot() ); // 0. Check that the migration is sensible. - // The new frozen head must increase the current split slot, and lie on an epoch + // The new finalized state must increase the current split slot, and lie on an epoch // boundary (in order for the hot state summary scheme to work). let current_split_slot = store.split.read_recursive().slot; - let anchor_slot = store - .anchor_info - .read_recursive() - .as_ref() - .map(|a| a.anchor_slot); + let anchor_info = store.anchor_info.read_recursive().clone(); + let anchor_slot = anchor_info.as_ref().map(|a| a.anchor_slot); - if frozen_head.slot() < current_split_slot { + if finalized_state.slot() < current_split_slot { return Err(HotColdDBError::FreezeSlotError { current_split_slot, - proposed_split_slot: frozen_head.slot(), + proposed_split_slot: finalized_state.slot(), } .into()); } - if frozen_head.slot() % E::slots_per_epoch() != 0 { - return Err(HotColdDBError::FreezeSlotUnaligned(frozen_head.slot()).into()); + if finalized_state.slot() % E::slots_per_epoch() != 0 { + return Err(HotColdDBError::FreezeSlotUnaligned(finalized_state.slot()).into()); } let mut hot_db_ops: Vec> = Vec::new(); - // 1. Copy all of the states between the head and the split slot, from the hot DB + // 1. Copy all of the states between the new finalized state and the split slot, from the hot DB // to the cold DB. Delete the execution payloads of these now-finalized blocks. - let state_root_iter = RootsIterator::new(&store, frozen_head); + let state_root_iter = RootsIterator::new(&store, finalized_state); for maybe_tuple in state_root_iter.take_while(|result| match result { Ok((_, _, slot)) => { slot >= ¤t_split_slot @@ -1656,6 +1739,29 @@ pub fn migrate_database, Cold: ItemStore>( }) { let (block_root, state_root, slot) = maybe_tuple?; + // Delete the execution payload if payload pruning is enabled. At a skipped slot we may + // delete the payload for the finalized block itself, but that's OK as we only guarantee + // that payloads are present for slots >= the split slot. The payload fetching code is also + // forgiving of missing payloads. + if store.config.prune_payloads { + hot_db_ops.push(StoreOp::DeleteExecutionPayload(block_root)); + } + + // Delete the old summary, and the full state if we lie on an epoch boundary. + hot_db_ops.push(StoreOp::DeleteState(state_root, Some(slot))); + + // Do not try to store states if a restore point is yet to be stored, or will never be + // stored (see `STATE_UPPER_LIMIT_NO_RETAIN`). Make an exception for the genesis state + // which always needs to be copied from the hot DB to the freezer and should not be deleted. + if slot != 0 + && anchor_info + .as_ref() + .map_or(false, |anchor| slot < anchor.state_upper_limit) + { + debug!(store.log, "Pruning finalized state"; "slot" => slot); + continue; + } + let mut cold_db_ops: Vec = Vec::new(); if slot % store.config.slots_per_restore_point == 0 { @@ -1674,17 +1780,6 @@ pub fn migrate_database, Cold: ItemStore>( // There are data dependencies between calls to `store_cold_state()` that prevent us from // doing one big call to `store.cold_db.do_atomically()` at end of the loop. store.cold_db.do_atomically(cold_db_ops)?; - - // Delete the old summary, and the full state if we lie on an epoch boundary. - hot_db_ops.push(StoreOp::DeleteState(state_root, Some(slot))); - - // Delete the execution payload if payload pruning is enabled. At a skipped slot we may - // delete the payload for the finalized block itself, but that's OK as we only guarantee - // that payloads are present for slots >= the split slot. The payload fetching code is also - // forgiving of missing payloads. - if store.config.prune_payloads { - hot_db_ops.push(StoreOp::DeleteExecutionPayload(block_root)); - } } // Warning: Critical section. We have to take care not to put any of the two databases in an @@ -1724,8 +1819,9 @@ pub fn migrate_database, Cold: ItemStore>( // Before updating the in-memory split value, we flush it to disk first, so that should the // OS process die at this point, we pick up from the right place after a restart. let split = Split { - slot: frozen_head.slot(), - state_root: frozen_head_root, + slot: finalized_state.slot(), + state_root: finalized_state_root, + block_root: finalized_block_root, }; store.hot_db.put_sync(&SPLIT_KEY, &split)?; @@ -1741,7 +1837,7 @@ pub fn migrate_database, Cold: ItemStore>( debug!( store.log, "Freezer migration complete"; - "slot" => frozen_head.slot() + "slot" => finalized_state.slot() ); Ok(()) @@ -1750,8 +1846,16 @@ pub fn migrate_database, Cold: ItemStore>( /// Struct for storing the split slot and state root in the database. #[derive(Debug, Clone, Copy, PartialEq, Default, Encode, Decode, Deserialize, Serialize)] pub struct Split { - pub(crate) slot: Slot, - pub(crate) state_root: Hash256, + pub slot: Slot, + pub state_root: Hash256, + /// The block root of the split state. + /// + /// This is used to provide special handling for the split state in the case where there are + /// skipped slots. The split state will *always* be the advanced state, so callers + /// who only have the finalized block root should use `get_advanced_hot_state` to get this state, + /// rather than fetching `block.state_root()` (the unaligned state) which will have been pruned. + #[ssz(skip_serializing, skip_deserializing)] + pub block_root: Hash256, } impl StoreItem for Split { diff --git a/beacon_node/store/src/metadata.rs b/beacon_node/store/src/metadata.rs index 6f50d7038f8..ccfddcf8f84 100644 --- a/beacon_node/store/src/metadata.rs +++ b/beacon_node/store/src/metadata.rs @@ -16,6 +16,9 @@ pub const PRUNING_CHECKPOINT_KEY: Hash256 = Hash256::repeat_byte(3); pub const COMPACTION_TIMESTAMP_KEY: Hash256 = Hash256::repeat_byte(4); pub const ANCHOR_INFO_KEY: Hash256 = Hash256::repeat_byte(5); +/// State upper limit value used to indicate that a node is not storing historic states. +pub const STATE_UPPER_LIMIT_NO_RETAIN: Slot = Slot::new(u64::MAX); + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct SchemaVersion(pub u64); diff --git a/book/src/checkpoint-sync.md b/book/src/checkpoint-sync.md index 57883828947..0c375a5f009 100644 --- a/book/src/checkpoint-sync.md +++ b/book/src/checkpoint-sync.md @@ -75,7 +75,7 @@ Once backfill is complete, a `INFO Historical block download complete` log will > Note: Since [v4.1.0](https://github.com/sigp/lighthouse/releases/tag/v4.1.0), Lighthouse implements rate-limited backfilling to mitigate validator performance issues after a recent checkpoint sync. This means that the speed at which historical blocks are downloaded is limited, typically to less than 20 slots/sec. This will not affect validator performance. However, if you would still prefer to sync the chain as fast as possible, you can add the flag `--disable-backfill-rate-limiting` to the beacon node. -> Note: Since [v4.2.0](https://github.com/sigp/lighthouse/releases/tag/v4.2.0), Lighthouse limits the backfill sync to only sync backwards to the weak subjectivity point (approximately 5 months). This will help to save disk space. However, if you would like to sync back to the genesis, you can add the flag `--genesis-backfill` to the beacon node. +> Note: Since [v4.2.0](https://github.com/sigp/lighthouse/releases/tag/v4.2.0), Lighthouse limits the backfill sync to only sync backwards to the weak subjectivity point (approximately 5 months). This will help to save disk space. However, if you would like to sync back to the genesis, you can add the flag `--genesis-backfill` to the beacon node. ## FAQ @@ -116,8 +116,9 @@ states: database. Additionally, the genesis block is always available. * `state_lower_limit`: All states with slots _less than or equal to_ this value are available in the database. The minimum value is 0, indicating that the genesis state is always available. -* `state_upper_limit`: All states with slots _greater than or equal to_ this value are available - in the database. +* `state_upper_limit`: All states with slots _greater than or equal to_ `min(split.slot, + state_upper_limit)` are available in the database. In the case where the `state_upper_limit` is + higher than the `split.slot`, this means states are not being written to the freezer database. Reconstruction runs from the state lower limit to the upper limit, narrowing the window of unavailable states as it goes. It will log messages like the following to show its progress: @@ -153,18 +154,8 @@ To manually specify a checkpoint use the following two flags: * `--checkpoint-state`: accepts an SSZ-encoded `BeaconState` blob * `--checkpoint-block`: accepts an SSZ-encoded `SignedBeaconBlock` blob -_Both_ the state and block must be provided and **must** adhere to the [Alignment -Requirements](#alignment-requirements) described below. - -### Alignment Requirements - -* The block must be a finalized block from an epoch boundary, i.e. `block.slot() % 32 == 0`. -* The state must be the state corresponding to `block` with `state.slot() == block.slot()` - and `state.hash_tree_root() == block.state_root()`. - -These requirements are imposed to align with Lighthouse's database schema, and notably exclude -finalized blocks from skipped slots. You can avoid alignment issues by using -[Automatic Checkpoint Sync](#automatic-checkpoint-sync), which will search for a suitable block -and state pair. +_Both_ the state and block must be provided and the state **must** match the block. The +state may be from the same slot as the block (unadvanced), or advanced to an epoch boundary, +in which case it will be assumed to be finalized at that epoch. [weak-subj]: https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/ diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index 4f563f86398..b3749ea9d83 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -355,7 +355,7 @@ where spec: &ChainSpec, ) -> Result> { // Sanity check: the anchor must lie on an epoch boundary. - if anchor_block.slot() % E::slots_per_epoch() != 0 { + if anchor_state.slot() % E::slots_per_epoch() != 0 { return Err(Error::InvalidAnchor { block_slot: anchor_block.slot(), state_slot: anchor_state.slot(), @@ -391,6 +391,7 @@ where let current_slot = current_slot.unwrap_or_else(|| fc_store.get_current_slot()); let proto_array = ProtoArrayForkChoice::new::( + current_slot, finalized_block_slot, finalized_block_state_root, *fc_store.justified_checkpoint(), diff --git a/consensus/proto_array/src/fork_choice_test_definition.rs b/consensus/proto_array/src/fork_choice_test_definition.rs index 157f072ad37..98d43e4850c 100644 --- a/consensus/proto_array/src/fork_choice_test_definition.rs +++ b/consensus/proto_array/src/fork_choice_test_definition.rs @@ -80,6 +80,7 @@ impl ForkChoiceTestDefinition { let junk_shuffling_id = AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero()); let mut fork_choice = ProtoArrayForkChoice::new::( + self.finalized_block_slot, self.finalized_block_slot, Hash256::zero(), self.justified_checkpoint, diff --git a/consensus/proto_array/src/proto_array_fork_choice.rs b/consensus/proto_array/src/proto_array_fork_choice.rs index fe831b3c357..5911e50fcdc 100644 --- a/consensus/proto_array/src/proto_array_fork_choice.rs +++ b/consensus/proto_array/src/proto_array_fork_choice.rs @@ -345,6 +345,7 @@ pub struct ProtoArrayForkChoice { impl ProtoArrayForkChoice { #[allow(clippy::too_many_arguments)] pub fn new( + current_slot: Slot, finalized_block_slot: Slot, finalized_block_state_root: Hash256, justified_checkpoint: Checkpoint, @@ -380,7 +381,7 @@ impl ProtoArrayForkChoice { }; proto_array - .on_block::(block, finalized_block_slot) + .on_block::(block, current_slot) .map_err(|e| format!("Failed to add finalized block to proto_array: {:?}", e))?; Ok(Self { @@ -983,6 +984,7 @@ mod test_compute_deltas { }; let mut fc = ProtoArrayForkChoice::new::( + genesis_slot, genesis_slot, state_root, genesis_checkpoint, @@ -1108,6 +1110,7 @@ mod test_compute_deltas { }; let mut fc = ProtoArrayForkChoice::new::( + genesis_slot, genesis_slot, junk_state_root, genesis_checkpoint, diff --git a/consensus/state_processing/src/per_slot_processing.rs b/consensus/state_processing/src/per_slot_processing.rs index ead06edbf56..e16fb4a7b11 100644 --- a/consensus/state_processing/src/per_slot_processing.rs +++ b/consensus/state_processing/src/per_slot_processing.rs @@ -21,7 +21,7 @@ impl From for Error { /// /// If the root of the supplied `state` is known, then it can be passed as `state_root`. If /// `state_root` is `None`, the root of `state` will be computed using a cached tree hash. -/// Providing the `state_root` makes this function several orders of magniude faster. +/// Providing the `state_root` makes this function several orders of magnitude faster. pub fn per_slot_processing( state: &mut BeaconState, state_root: Option, diff --git a/testing/ef_tests/src/cases/fork_choice.rs b/testing/ef_tests/src/cases/fork_choice.rs index 9627d2cde03..c4f288a8aa6 100644 --- a/testing/ef_tests/src/cases/fork_choice.rs +++ b/testing/ef_tests/src/cases/fork_choice.rs @@ -7,7 +7,7 @@ use beacon_chain::{ obtain_indexed_attestation_and_committees_per_slot, VerifiedAttestation, }, test_utils::{BeaconChainHarness, EphemeralHarnessType}, - BeaconChainTypes, CachedHead, NotifyExecutionLayer, + BeaconChainTypes, CachedHead, ChainConfig, NotifyExecutionLayer, }; use execution_layer::{json_structures::JsonPayloadStatusV1Status, PayloadStatusV1}; use serde::Deserialize; @@ -303,6 +303,10 @@ impl Tester { let harness = BeaconChainHarness::builder(E::default()) .spec(spec.clone()) .keypairs(vec![]) + .chain_config(ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }) .genesis_state_ephemeral_store(case.anchor_state.clone()) .mock_execution_layer() .recalculate_fork_times_with_genesis(0) diff --git a/testing/node_test_rig/src/lib.rs b/testing/node_test_rig/src/lib.rs index 394f8558fae..3cd8205eb65 100644 --- a/testing/node_test_rig/src/lib.rs +++ b/testing/node_test_rig/src/lib.rs @@ -115,6 +115,9 @@ pub fn testing_client_config() -> ClientConfig { genesis_time: now, }; + // Simulator tests expect historic states to be available for post-run checks. + client_config.chain.reconstruct_historic_states = true; + // Specify a constant count of beacon processor workers. Having this number // too low can cause annoying HTTP timeouts, especially on Github runners // with 2 logical CPUs. diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs index af1cde26b7a..9032b124ab8 100644 --- a/watch/tests/tests.rs +++ b/watch/tests/tests.rs @@ -1,8 +1,9 @@ #![recursion_limit = "256"] #![cfg(unix)] -use beacon_chain::test_utils::{ - AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType, +use beacon_chain::{ + test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType}, + ChainConfig, }; use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use http_api::test_utils::{create_api_server, ApiServer}; @@ -91,6 +92,10 @@ impl TesterBuilder { pub async fn new() -> TesterBuilder { let harness = BeaconChainHarness::builder(E::default()) .default_spec() + .chain_config(ChainConfig { + reconstruct_historic_states: true, + ..ChainConfig::default() + }) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .build(); From b3e1c297c8e0ab2e62b3a5068576d94f0e0585ab Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Mon, 21 Aug 2023 05:02:33 +0000 Subject: [PATCH 07/25] `ForkChoice`: remove an impossible case for `parent_checkpoints` (#4626) `parent_finalized.epoch + 1 > block_epoch` will never be `true` since as the comment says: ``` A block in epoch `N` cannot contain attestations which would finalize an epoch higher than `N - 1`. ``` --- consensus/fork_choice/src/fork_choice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index b3749ea9d83..ea3a58127b2 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -750,7 +750,7 @@ where .unrealized_justified_checkpoint .zip(parent_block.unrealized_finalized_checkpoint) .filter(|(parent_justified, parent_finalized)| { - parent_justified.epoch == block_epoch && parent_finalized.epoch + 1 >= block_epoch + parent_justified.epoch == block_epoch && parent_finalized.epoch + 1 == block_epoch }); let (unrealized_justified_checkpoint, unrealized_finalized_checkpoint) = if let Some(( From 524d9af2884a53eee1e3c5c07db85ac4f6a6df8d Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 21 Aug 2023 05:02:34 +0000 Subject: [PATCH 08/25] Fix beacon-processor-max-workers (#4636) ## Issue Addressed Fixes a bug in the handling of `--beacon-process-max-workers` which caused it to have no effect. ## Proposed Changes For this PR I channeled @ethDreamer and saw deep into the faulty CLI config -- this bug is almost identical to the one Mark found and fixed in #4622. --- Cargo.lock | 2 -- beacon_node/beacon_processor/src/lib.rs | 5 ++--- beacon_node/client/Cargo.toml | 1 - beacon_node/client/src/builder.rs | 2 -- beacon_node/http_api/src/test_utils.rs | 14 ++++++++------ beacon_node/network/Cargo.toml | 1 - .../network/src/network_beacon_processor/tests.rs | 2 -- 7 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 116f8081fac..9a8cf48336c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1053,7 +1053,6 @@ dependencies = [ "logging", "monitoring_api", "network", - "num_cpus", "operation_pool", "parking_lot 0.12.1", "sensitive_url", @@ -5037,7 +5036,6 @@ dependencies = [ "logging", "lru_cache", "matches", - "num_cpus", "operation_pool", "parking_lot 0.12.1", "rand 0.8.5", diff --git a/beacon_node/beacon_processor/src/lib.rs b/beacon_node/beacon_processor/src/lib.rs index bf5d8bced46..4c1da85fa5c 100644 --- a/beacon_node/beacon_processor/src/lib.rs +++ b/beacon_node/beacon_processor/src/lib.rs @@ -708,7 +708,6 @@ impl Stream for InboundEvents { pub struct BeaconProcessor { pub network_globals: Arc>, pub executor: TaskExecutor, - pub max_workers: usize, pub current_workers: usize, pub config: BeaconProcessorConfig, pub log: Logger, @@ -721,7 +720,7 @@ impl BeaconProcessor { /// - Performed immediately, if a worker is available. /// - Queued for later processing, if no worker is currently available. /// - /// Only `self.max_workers` will ever be spawned at one time. Each worker is a `tokio` task + /// Only `self.config.max_workers` will ever be spawned at one time. Each worker is a `tokio` task /// started with `spawn_blocking`. /// /// The optional `work_journal_tx` allows for an outside process to receive a log of all work @@ -896,7 +895,7 @@ impl BeaconProcessor { let _ = work_journal_tx.try_send(id); } - let can_spawn = self.current_workers < self.max_workers; + let can_spawn = self.current_workers < self.config.max_workers; let drop_during_sync = work_event .as_ref() .map_or(false, |event| event.drop_during_sync); diff --git a/beacon_node/client/Cargo.toml b/beacon_node/client/Cargo.toml index 0b517930fb9..87e16509026 100644 --- a/beacon_node/client/Cargo.toml +++ b/beacon_node/client/Cargo.toml @@ -44,4 +44,3 @@ slasher_service = { path = "../../slasher/service" } monitoring_api = {path = "../../common/monitoring_api"} execution_layer = { path = "../execution_layer" } beacon_processor = { path = "../beacon_processor" } -num_cpus = "1.13.0" diff --git a/beacon_node/client/src/builder.rs b/beacon_node/client/src/builder.rs index b09cf9cca3b..4de6d12622d 100644 --- a/beacon_node/client/src/builder.rs +++ b/beacon_node/client/src/builder.rs @@ -29,7 +29,6 @@ use network::{NetworkConfig, NetworkSenders, NetworkService}; use slasher::Slasher; use slasher_service::SlasherService; use slog::{debug, info, warn, Logger}; -use std::cmp; use std::net::TcpListener; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -757,7 +756,6 @@ where BeaconProcessor { network_globals: network_globals.clone(), executor: beacon_processor_context.executor.clone(), - max_workers: cmp::max(1, num_cpus::get()), current_workers: 0, config: beacon_processor_config, log: beacon_processor_context.log().clone(), diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index dcc25322297..b1478f3f290 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -184,7 +184,14 @@ pub async fn create_api_server_on_port( let eth1_service = eth1::Service::new(eth1::Config::default(), log.clone(), chain.spec.clone()).unwrap(); - let beacon_processor_config = BeaconProcessorConfig::default(); + let beacon_processor_config = BeaconProcessorConfig { + // The number of workers must be greater than one. Tests which use the + // builder workflow sometimes require an internal HTTP request in order + // to fulfill an already in-flight HTTP request, therefore having only + // one worker will result in a deadlock. + max_workers: 2, + ..BeaconProcessorConfig::default() + }; let BeaconProcessorChannels { beacon_processor_tx, beacon_processor_rx, @@ -196,11 +203,6 @@ pub async fn create_api_server_on_port( BeaconProcessor { network_globals: network_globals.clone(), executor: test_runtime.task_executor.clone(), - // The number of workers must be greater than one. Tests which use the - // builder workflow sometimes require an internal HTTP request in order - // to fulfill an already in-flight HTTP request, therefore having only - // one worker will result in a deadlock. - max_workers: 2, current_workers: 0, config: beacon_processor_config, log: log.clone(), diff --git a/beacon_node/network/Cargo.toml b/beacon_node/network/Cargo.toml index c37b0fa45d7..715e77bcade 100644 --- a/beacon_node/network/Cargo.toml +++ b/beacon_node/network/Cargo.toml @@ -36,7 +36,6 @@ logging = { path = "../../common/logging" } task_executor = { path = "../../common/task_executor" } igd = "0.12.1" itertools = "0.10.0" -num_cpus = "1.13.0" lru_cache = { path = "../../common/lru_cache" } if-addrs = "0.6.4" strum = "0.24.0" diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index ce5b9141172..a678edbf1ff 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -20,7 +20,6 @@ use lighthouse_network::{ Client, MessageId, NetworkGlobals, PeerId, }; use slot_clock::SlotClock; -use std::cmp; use std::iter::Iterator; use std::sync::Arc; use std::time::Duration; @@ -228,7 +227,6 @@ impl TestRig { let beacon_processor = BeaconProcessor { network_globals, executor, - max_workers: cmp::max(1, num_cpus::get()), current_workers: 0, config: beacon_processor_config, log: log.clone(), From 91f3bc274b063921e41c7a37386e86680697e875 Mon Sep 17 00:00:00 2001 From: Aoi Kurokawa Date: Mon, 21 Aug 2023 05:02:35 +0000 Subject: [PATCH 09/25] Fix the link of anvil in lighthouse book (#4641) ## Issue Addressed Wrong link for anvil ## Proposed Changes Fix the link to correct one. --- book/src/setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/setup.md b/book/src/setup.md index 533e1d463d3..1ae6e635408 100644 --- a/book/src/setup.md +++ b/book/src/setup.md @@ -9,7 +9,7 @@ particularly useful for development but still a good way to ensure you have the base dependencies. The additional requirements for developers are: -- [`anvil`](https://github.com/foundry-rs/foundry/tree/master/anvil). This is used to +- [`anvil`](https://github.com/foundry-rs/foundry/tree/master/crates/anvil). This is used to simulate the execution chain during tests. You'll get failures during tests if you don't have `anvil` available on your `PATH`. - [`cmake`](https://cmake.org/cmake/help/latest/command/install.html). Used by From f92b856cd1b25b7e51a35747e613a5f22e6973d3 Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 21 Aug 2023 05:02:36 +0000 Subject: [PATCH 10/25] Remove Antithesis docker build workflow (#4642) ## Issue Addressed This PR remove Antithesis docker build CI workflow. Confirmed with Antithesis team that this is no longer used. --- .github/workflows/docker-antithesis.yml | 35 ------------------ testing/antithesis/Dockerfile.libvoidstar | 25 ------------- testing/antithesis/libvoidstar/libvoidstar.so | Bin 348192 -> 0 bytes 3 files changed, 60 deletions(-) delete mode 100644 .github/workflows/docker-antithesis.yml delete mode 100644 testing/antithesis/Dockerfile.libvoidstar delete mode 100644 testing/antithesis/libvoidstar/libvoidstar.so diff --git a/.github/workflows/docker-antithesis.yml b/.github/workflows/docker-antithesis.yml deleted file mode 100644 index a96431fafbd..00000000000 --- a/.github/workflows/docker-antithesis.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: docker antithesis - -on: - push: - branches: - - unstable - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - ANTITHESIS_PASSWORD: ${{ secrets.ANTITHESIS_PASSWORD }} - ANTITHESIS_USERNAME: ${{ secrets.ANTITHESIS_USERNAME }} - ANTITHESIS_SERVER: ${{ secrets.ANTITHESIS_SERVER }} - REPOSITORY: ${{ secrets.ANTITHESIS_REPOSITORY }} - IMAGE_NAME: lighthouse - TAG: libvoidstar - -jobs: - build-docker: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3 - - name: Update Rust - run: rustup update stable - - name: Dockerhub login - run: | - echo "${ANTITHESIS_PASSWORD}" | docker login --username ${ANTITHESIS_USERNAME} https://${ANTITHESIS_SERVER} --password-stdin - - name: Build AMD64 dockerfile (with push) - run: | - docker build \ - --tag ${ANTITHESIS_SERVER}/${REPOSITORY}/${IMAGE_NAME}:${TAG} \ - --file ./testing/antithesis/Dockerfile.libvoidstar . - docker push ${ANTITHESIS_SERVER}/${REPOSITORY}/${IMAGE_NAME}:${TAG} diff --git a/testing/antithesis/Dockerfile.libvoidstar b/testing/antithesis/Dockerfile.libvoidstar deleted file mode 100644 index c790e248dfe..00000000000 --- a/testing/antithesis/Dockerfile.libvoidstar +++ /dev/null @@ -1,25 +0,0 @@ -FROM rust:1.68.2-bullseye AS builder -RUN apt-get update && apt-get -y upgrade && apt-get install -y cmake libclang-dev -COPY . lighthouse - -# Build lighthouse directly with a cargo build command, bypassing the Makefile. -RUN cd lighthouse && LD_LIBRARY_PATH=/lighthouse/testing/antithesis/libvoidstar/ RUSTFLAGS="-Cpasses=sancov-module -Cllvm-args=-sanitizer-coverage-level=3 -Cllvm-args=-sanitizer-coverage-trace-pc-guard -Ccodegen-units=1 -Cdebuginfo=2 -L/lighthouse/testing/antithesis/libvoidstar/ -lvoidstar" cargo build --release --manifest-path lighthouse/Cargo.toml --target x86_64-unknown-linux-gnu --features modern --verbose --bin lighthouse -# build lcli binary directly with cargo install command, bypassing the makefile -RUN cargo install --path /lighthouse/lcli --force --locked - -FROM ubuntu:latest -RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-recommends \ - libssl-dev \ - ca-certificates \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# create and move the libvoidstar file -RUN mkdir libvoidstar -COPY --from=builder /lighthouse/testing/antithesis/libvoidstar/libvoidstar.so /usr/lib/libvoidstar.so - -# set the env variable to avoid having to always set it -ENV LD_LIBRARY_PATH=/usr/lib -# move the lighthouse binary and lcli binary -COPY --from=builder /lighthouse/target/x86_64-unknown-linux-gnu/release/lighthouse /usr/local/bin/lighthouse -COPY --from=builder /lighthouse/target/release/lcli /usr/local/bin/lcli \ No newline at end of file diff --git a/testing/antithesis/libvoidstar/libvoidstar.so b/testing/antithesis/libvoidstar/libvoidstar.so deleted file mode 100644 index 0f8a0f23c3fb7a349788bc8d5532dc71bb821c68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 348192 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

-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 Date: Thu, 24 Aug 2023 05:54:36 +0000 Subject: [PATCH 11/25] Implement expected withdrawals endpoint (#4390) ## Issue Addressed [#4029](https://github.com/sigp/lighthouse/issues/4029) ## Proposed Changes implement expected_withdrawals HTTP API per the spec https://github.com/ethereum/beacon-APIs/pull/304 ## Additional Info --- beacon_node/http_api/src/builder_states.rs | 72 +++++++++++++++ beacon_node/http_api/src/lib.rs | 57 ++++++++++++ beacon_node/http_api/tests/tests.rs | 101 +++++++++++++++++++++ common/eth2/src/lib.rs | 17 ++++ common/eth2/src/types.rs | 5 + 5 files changed, 252 insertions(+) create mode 100644 beacon_node/http_api/src/builder_states.rs diff --git a/beacon_node/http_api/src/builder_states.rs b/beacon_node/http_api/src/builder_states.rs new file mode 100644 index 00000000000..90203f2d60c --- /dev/null +++ b/beacon_node/http_api/src/builder_states.rs @@ -0,0 +1,72 @@ +use crate::StateId; +use beacon_chain::{BeaconChain, BeaconChainTypes}; +use safe_arith::SafeArith; +use state_processing::per_block_processing::get_expected_withdrawals; +use state_processing::state_advance::partial_state_advance; +use std::sync::Arc; +use types::{BeaconState, EthSpec, ForkName, Slot, Withdrawals}; + +const MAX_EPOCH_LOOKAHEAD: u64 = 2; + +/// Get the withdrawals computed from the specified state, that will be included in the block +/// that gets built on the specified state. +pub fn get_next_withdrawals( + chain: &Arc>, + mut state: BeaconState, + state_id: StateId, + proposal_slot: Slot, +) -> Result, warp::Rejection> { + get_next_withdrawals_sanity_checks(chain, &state, proposal_slot)?; + + // advance the state to the epoch of the proposal slot. + let proposal_epoch = proposal_slot.epoch(T::EthSpec::slots_per_epoch()); + let (state_root, _, _) = state_id.root(chain)?; + if proposal_epoch != state.current_epoch() { + if let Err(e) = + partial_state_advance(&mut state, Some(state_root), proposal_slot, &chain.spec) + { + return Err(warp_utils::reject::custom_server_error(format!( + "failed to advance to the epoch of the proposal slot: {:?}", + e + ))); + } + } + + match get_expected_withdrawals(&state, &chain.spec) { + Ok(withdrawals) => Ok(withdrawals), + Err(e) => Err(warp_utils::reject::custom_server_error(format!( + "failed to get expected withdrawal: {:?}", + e + ))), + } +} + +fn get_next_withdrawals_sanity_checks( + chain: &BeaconChain, + state: &BeaconState, + proposal_slot: Slot, +) -> Result<(), warp::Rejection> { + if proposal_slot <= state.slot() { + return Err(warp_utils::reject::custom_bad_request( + "proposal slot must be greater than the pre-state slot".to_string(), + )); + } + + let fork = chain.spec.fork_name_at_slot::(proposal_slot); + if let ForkName::Base | ForkName::Altair | ForkName::Merge = fork { + return Err(warp_utils::reject::custom_bad_request( + "the specified state is a pre-capella state.".to_string(), + )); + } + + let look_ahead_limit = MAX_EPOCH_LOOKAHEAD + .safe_mul(T::EthSpec::slots_per_epoch()) + .map_err(warp_utils::reject::arith_error)?; + if proposal_slot >= state.slot() + look_ahead_limit { + return Err(warp_utils::reject::custom_bad_request(format!( + "proposal slot is greater than or equal to the look ahead limit: {look_ahead_limit}" + ))); + } + + Ok(()) +} diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 8e316834d0e..3aa10139b0b 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -10,6 +10,7 @@ mod attester_duties; mod block_id; mod block_packing_efficiency; mod block_rewards; +mod builder_states; mod database; mod metrics; mod proposer_duties; @@ -32,6 +33,7 @@ use beacon_chain::{ }; use beacon_processor::BeaconProcessorSend; pub use block_id::BlockId; +use builder_states::get_next_withdrawals; use bytes::Bytes; use directory::DEFAULT_ROOT_DIR; use eth2::types::{ @@ -2291,6 +2293,60 @@ pub fn serve( }, ); + /* + * builder/states + */ + + let builder_states_path = eth_v1 + .and(warp::path("builder")) + .and(warp::path("states")) + .and(chain_filter.clone()); + + // GET builder/states/{state_id}/expected_withdrawals + let get_expected_withdrawals = builder_states_path + .clone() + .and(task_spawner_filter.clone()) + .and(warp::path::param::()) + .and(warp::path("expected_withdrawals")) + .and(warp::query::()) + .and(warp::path::end()) + .and(warp::header::optional::("accept")) + .then( + |chain: Arc>, + task_spawner: TaskSpawner, + state_id: StateId, + query: api_types::ExpectedWithdrawalsQuery, + accept_header: Option| { + task_spawner.blocking_response_task(Priority::P1, move || { + let (state, execution_optimistic, finalized) = state_id.state(&chain)?; + let proposal_slot = query.proposal_slot.unwrap_or(state.slot() + 1); + let withdrawals = + get_next_withdrawals::(&chain, state, state_id, proposal_slot)?; + + match accept_header { + Some(api_types::Accept::Ssz) => Response::builder() + .status(200) + .header("Content-Type", "application/octet-stream") + .body(withdrawals.as_ssz_bytes().into()) + .map_err(|e| { + warp_utils::reject::custom_server_error(format!( + "failed to create response: {}", + e + )) + }), + _ => Ok(warp::reply::json( + &api_types::ExecutionOptimisticFinalizedResponse { + data: withdrawals, + execution_optimistic: Some(execution_optimistic), + finalized: Some(finalized), + }, + ) + .into_response()), + } + }) + }, + ); + /* * beacon/rewards */ @@ -4503,6 +4559,7 @@ pub fn serve( .uor(get_lighthouse_block_packing_efficiency) .uor(get_lighthouse_merge_readiness) .uor(get_events) + .uor(get_expected_withdrawals) .uor(lighthouse_log_events.boxed()) .recover(warp_utils::reject::handle_rejection), ) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 3c72441c02f..46cc55591c7 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -28,6 +28,7 @@ use sensitive_url::SensitiveUrl; use slot_clock::SlotClock; use state_processing::per_block_processing::get_expected_withdrawals; use state_processing::per_slot_processing; +use state_processing::state_advance::partial_state_advance; use std::convert::TryInto; use std::sync::Arc; use tokio::time::Duration; @@ -4341,6 +4342,72 @@ impl ApiTester { self } + pub async fn test_get_expected_withdrawals_invalid_state(self) -> Self { + let state_id = CoreStateId::Root(Hash256::zero()); + + let result = self.client.get_expected_withdrawals(&state_id).await; + + match result { + Err(e) => { + assert_eq!(e.status().unwrap(), 404); + } + _ => panic!("query did not fail correctly"), + } + + self + } + + pub async fn test_get_expected_withdrawals_capella(self) -> Self { + let slot = self.chain.slot().unwrap(); + let state_id = CoreStateId::Slot(slot); + + // calculate the expected withdrawals + let (mut state, _, _) = StateId(state_id).state(&self.chain).unwrap(); + let proposal_slot = state.slot() + 1; + let proposal_epoch = proposal_slot.epoch(E::slots_per_epoch()); + let (state_root, _, _) = StateId(state_id).root(&self.chain).unwrap(); + if proposal_epoch != state.current_epoch() { + let _ = partial_state_advance( + &mut state, + Some(state_root), + proposal_slot, + &self.chain.spec, + ); + } + let expected_withdrawals = get_expected_withdrawals(&state, &self.chain.spec).unwrap(); + + // fetch expected withdrawals from the client + let result = self.client.get_expected_withdrawals(&state_id).await; + match result { + Ok(withdrawal_response) => { + assert_eq!(withdrawal_response.execution_optimistic, Some(false)); + assert_eq!(withdrawal_response.finalized, Some(false)); + assert_eq!(withdrawal_response.data, expected_withdrawals.to_vec()); + } + Err(e) => { + println!("{:?}", e); + panic!("query failed incorrectly"); + } + } + + self + } + + pub async fn test_get_expected_withdrawals_pre_capella(self) -> Self { + let state_id = CoreStateId::Head; + + let result = self.client.get_expected_withdrawals(&state_id).await; + + match result { + Err(e) => { + assert_eq!(e.status().unwrap(), 400); + } + _ => panic!("query did not fail correctly"), + } + + self + } + pub async fn test_get_events_altair(self) -> Self { let topics = vec![EventTopic::ContributionAndProof]; let mut events_future = self @@ -5123,3 +5190,37 @@ async fn optimistic_responses() { .test_check_optimistic_responses() .await; } + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn expected_withdrawals_invalid_pre_capella() { + let mut config = ApiTesterConfig::default(); + config.spec.altair_fork_epoch = Some(Epoch::new(0)); + ApiTester::new_from_config(config) + .await + .test_get_expected_withdrawals_pre_capella() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn expected_withdrawals_invalid_state() { + let mut config = ApiTesterConfig::default(); + config.spec.altair_fork_epoch = Some(Epoch::new(0)); + config.spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + config.spec.capella_fork_epoch = Some(Epoch::new(0)); + ApiTester::new_from_config(config) + .await + .test_get_expected_withdrawals_invalid_state() + .await; +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn expected_withdrawals_valid_capella() { + let mut config = ApiTesterConfig::default(); + config.spec.altair_fork_epoch = Some(Epoch::new(0)); + config.spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + config.spec.capella_fork_epoch = Some(Epoch::new(0)); + ApiTester::new_from_config(config) + .await + .test_get_expected_withdrawals_capella() + .await; +} diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 146a832e388..74c2f3802fc 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -1261,6 +1261,23 @@ impl BeaconNodeHttpClient { Ok(()) } + // GET builder/states/{state_id}/expected_withdrawals + pub async fn get_expected_withdrawals( + &self, + state_id: &StateId, + ) -> Result>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("builder") + .push("states") + .push(&state_id.to_string()) + .push("expected_withdrawals"); + + self.get(path).await + } + /// `POST validator/contribution_and_proofs` pub async fn post_validator_contribution_and_proofs( &self, diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index f451d3b8f2e..28fd09c09bc 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -581,6 +581,11 @@ pub struct SyncingData { pub sync_distance: Slot, } +#[derive(Serialize, Deserialize)] +pub struct ExpectedWithdrawalsQuery { + pub proposal_slot: Option, +} + #[derive(Clone, PartialEq, Debug, Deserialize)] #[serde(try_from = "String", bound = "T: FromStr")] pub struct QueryVec { From ea43b6a53c0e24a4a1111bcbb1b27fd4b33b4322 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Thu, 24 Aug 2023 05:54:37 +0000 Subject: [PATCH 12/25] Revive mplex (#4619) ## Issue Addressed N/A ## Proposed Changes In #4431 , we seem to have removed support for mplex as it is being deprecated in libp2p. See https://github.com/libp2p/specs/issues/553 . Related rust-libp2p PR https://github.com/libp2p/rust-libp2p/pull/3920 However, since this isn't part of the official [consensus specs](https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#multiplexing), we still need to support mplex. > Clients MUST support [mplex](https://github.com/libp2p/specs/tree/master/mplex) and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). This PR adds back mplex support as before. --- Cargo.lock | 20 +++++++++++++++++++ beacon_node/lighthouse_network/Cargo.toml | 1 + .../lighthouse_network/src/service/utils.rs | 10 +++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9a8cf48336c..b1779045e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4117,6 +4117,25 @@ dependencies = [ "prometheus-client", ] +[[package]] +name = "libp2p-mplex" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93959ed08b6caf9810e067655e25f1362098797fef7c44d3103e63dcb6f0fabe" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "log", + "nohash-hasher", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec 1.11.0", + "unsigned-varint 0.7.1", +] + [[package]] name = "libp2p-noise" version = "0.43.0" @@ -4382,6 +4401,7 @@ dependencies = [ "hex", "lazy_static", "libp2p", + "libp2p-mplex", "lighthouse_metrics", "lighthouse_version", "lru 0.7.8", diff --git a/beacon_node/lighthouse_network/Cargo.toml b/beacon_node/lighthouse_network/Cargo.toml index f71845fed25..925d278ad62 100644 --- a/beacon_node/lighthouse_network/Cargo.toml +++ b/beacon_node/lighthouse_network/Cargo.toml @@ -44,6 +44,7 @@ prometheus-client = "0.21.0" unused_port = { path = "../../common/unused_port" } delay_map = "0.3.0" void = "1" +libp2p-mplex = "0.40.0" [dependencies.libp2p] version = "0.52" diff --git a/beacon_node/lighthouse_network/src/service/utils.rs b/beacon_node/lighthouse_network/src/service/utils.rs index 21fd09b6b0f..b8acc4ed6d5 100644 --- a/beacon_node/lighthouse_network/src/service/utils.rs +++ b/beacon_node/lighthouse_network/src/service/utils.rs @@ -50,13 +50,21 @@ pub fn build_transport( transport.or_transport(libp2p::websocket::WsConfig::new(trans_clone)) }; + // mplex config + let mut mplex_config = libp2p_mplex::MplexConfig::new(); + mplex_config.set_max_buffer_size(256); + mplex_config.set_max_buffer_behaviour(libp2p_mplex::MaxBufferBehaviour::Block); + // yamux config let mut yamux_config = yamux::Config::default(); yamux_config.set_window_update_mode(yamux::WindowUpdateMode::on_read()); let (transport, bandwidth) = transport .upgrade(core::upgrade::Version::V1) .authenticate(generate_noise_config(&local_private_key)) - .multiplex(yamux_config) + .multiplex(core::upgrade::SelectUpgrade::new( + yamux_config, + mplex_config, + )) .timeout(Duration::from_secs(10)) .boxed() .with_bandwidth_logging(); From fa6003bb5ccf1949297dea66f79849dbf073a918 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Thu, 24 Aug 2023 05:54:38 +0000 Subject: [PATCH 13/25] Use lockfile with cross and fix audit fail (#4656) ## Issue Addressed Temporary ignore for #4651. We are unaffected, and upstream will be patched in a few days. ## Proposed Changes - Ignore cargo audit failures (ublocks CI) - Use `--locked` when building with `cross`. We use `--locked` for regular builds, and I think excluding it from `cross` was just an oversight. I think for consistent builds it makes sense to use `--locked` while building. This is particularly relevant for release binaries, which otherwise will just use a random selection of dependencies that exist on build day (near impossible to recreate if we had to). --- Makefile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index cb447e26a51..bc84cdede09 100644 --- a/Makefile +++ b/Makefile @@ -71,13 +71,13 @@ install-lcli: # optimized CPU functions that may not be available on some systems. This # results in a more portable binary with ~20% slower BLS verification. build-x86_64: - cross build --bin lighthouse --target x86_64-unknown-linux-gnu --features "modern,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" + cross build --bin lighthouse --target x86_64-unknown-linux-gnu --features "modern,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" --locked build-x86_64-portable: - cross build --bin lighthouse --target x86_64-unknown-linux-gnu --features "portable,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" + cross build --bin lighthouse --target x86_64-unknown-linux-gnu --features "portable,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" --locked build-aarch64: - cross build --bin lighthouse --target aarch64-unknown-linux-gnu --features "$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" + cross build --bin lighthouse --target aarch64-unknown-linux-gnu --features "$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" --locked build-aarch64-portable: - cross build --bin lighthouse --target aarch64-unknown-linux-gnu --features "portable,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" + cross build --bin lighthouse --target aarch64-unknown-linux-gnu --features "portable,$(CROSS_FEATURES)" --profile "$(CROSS_PROFILE)" --locked # Create a `.tar.gz` containing a binary for a specific target. define tarball_release_binary @@ -206,8 +206,9 @@ arbitrary-fuzz: # Runs cargo audit (Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database) audit: - cargo install --force cargo-audit - cargo audit --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2022-0093 + # cargo install --force cargo-audit + cargo audit --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2022-0093 \ + --ignore RUSTSEC-2023-0052 --ignore RUSTSEC-2023-0053 # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: From 14924dbc955732a742d52eafcad32cb0f790f027 Mon Sep 17 00:00:00 2001 From: realbigsean Date: Thu, 24 Aug 2023 14:33:24 -0400 Subject: [PATCH 14/25] rust 1.72 lints (#4659) --- beacon_node/beacon_chain/src/observed_attesters.rs | 6 +++--- beacon_node/genesis/src/common.rs | 2 +- beacon_node/lighthouse_network/src/discovery/mod.rs | 2 +- .../lighthouse_network/src/peer_manager/mod.rs | 3 ++- beacon_node/store/src/chunked_vector.rs | 6 ++++-- beacon_node/store/src/leveldb_store.rs | 2 +- consensus/safe_arith/src/iter.rs | 12 ++++++------ crypto/bls/src/generic_public_key_bytes.rs | 5 +---- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/beacon_node/beacon_chain/src/observed_attesters.rs b/beacon_node/beacon_chain/src/observed_attesters.rs index 59c67bd1b95..605a1343210 100644 --- a/beacon_node/beacon_chain/src/observed_attesters.rs +++ b/beacon_node/beacon_chain/src/observed_attesters.rs @@ -841,7 +841,7 @@ mod tests { let mut store = $type::default(); let max_cap = store.max_capacity(); - let to_skip = vec![1_u64, 3, 4, 5]; + let to_skip = [1_u64, 3, 4, 5]; let periods = (0..max_cap * 3) .into_iter() .filter(|i| !to_skip.contains(i)) @@ -1012,7 +1012,7 @@ mod tests { let mut store = $type::default(); let max_cap = store.max_capacity(); - let to_skip = vec![1_u64, 3, 4, 5]; + let to_skip = [1_u64, 3, 4, 5]; let periods = (0..max_cap * 3) .into_iter() .filter(|i| !to_skip.contains(i)) @@ -1121,7 +1121,7 @@ mod tests { let mut store = $type::default(); let max_cap = store.max_capacity(); - let to_skip = vec![1_u64, 3, 4, 5]; + let to_skip = [1_u64, 3, 4, 5]; let periods = (0..max_cap * 3) .into_iter() .filter(|i| !to_skip.contains(i)) diff --git a/beacon_node/genesis/src/common.rs b/beacon_node/genesis/src/common.rs index 06bf99f9f63..e48fa362046 100644 --- a/beacon_node/genesis/src/common.rs +++ b/beacon_node/genesis/src/common.rs @@ -39,7 +39,7 @@ pub fn genesis_deposits( Ok(deposit_data .into_iter() - .zip(proofs.into_iter()) + .zip(proofs) .map(|(data, proof)| (data, proof.into())) .map(|(data, proof)| Deposit { proof, data }) .collect()) diff --git a/beacon_node/lighthouse_network/src/discovery/mod.rs b/beacon_node/lighthouse_network/src/discovery/mod.rs index 82a371d8a20..c3819f1eb94 100644 --- a/beacon_node/lighthouse_network/src/discovery/mod.rs +++ b/beacon_node/lighthouse_network/src/discovery/mod.rs @@ -647,7 +647,7 @@ impl Discovery { if subnet_queries.len() == MAX_SUBNETS_IN_QUERY || self.queued_queries.is_empty() { // This query is for searching for peers of a particular subnet // Drain subnet_queries so we can re-use it as we continue to process the queue - let grouped_queries: Vec = subnet_queries.drain(..).collect(); + let grouped_queries: Vec = std::mem::take(&mut subnet_queries); self.start_subnet_query(grouped_queries); processed = true; } diff --git a/beacon_node/lighthouse_network/src/peer_manager/mod.rs b/beacon_node/lighthouse_network/src/peer_manager/mod.rs index 4f3454f4033..f6158ed3a0c 100644 --- a/beacon_node/lighthouse_network/src/peer_manager/mod.rs +++ b/beacon_node/lighthouse_network/src/peer_manager/mod.rs @@ -969,6 +969,7 @@ impl PeerManager { macro_rules! prune_peers { ($filter: expr) => { + let filter = $filter; for (peer_id, info) in self .network_globals .peers @@ -976,7 +977,7 @@ impl PeerManager { .worst_connected_peers() .iter() .filter(|(_, info)| { - !info.has_future_duty() && !info.is_trusted() && $filter(*info) + !info.has_future_duty() && !info.is_trusted() && filter(*info) }) { if peers_to_prune.len() diff --git a/beacon_node/store/src/chunked_vector.rs b/beacon_node/store/src/chunked_vector.rs index 73edfbb0744..537614f2817 100644 --- a/beacon_node/store/src/chunked_vector.rs +++ b/beacon_node/store/src/chunked_vector.rs @@ -299,7 +299,8 @@ macro_rules! field { } fn update_pattern(spec: &ChainSpec) -> UpdatePattern { - $update_pattern(spec) + let update_pattern = $update_pattern; + update_pattern(spec) } fn get_value( @@ -307,7 +308,8 @@ macro_rules! field { vindex: u64, spec: &ChainSpec, ) -> Result { - $get_value(state, vindex, spec) + let get_value = $get_value; + get_value(state, vindex, spec) } fn is_fixed_length() -> bool { diff --git a/beacon_node/store/src/leveldb_store.rs b/beacon_node/store/src/leveldb_store.rs index 86bd4ffaccd..7aac9f72d91 100644 --- a/beacon_node/store/src/leveldb_store.rs +++ b/beacon_node/store/src/leveldb_store.rs @@ -167,7 +167,7 @@ impl KeyValueStore for LevelDB { ) }; - for (start_key, end_key) in vec![ + for (start_key, end_key) in [ endpoints(DBColumn::BeaconStateTemporary), endpoints(DBColumn::BeaconState), ] { diff --git a/consensus/safe_arith/src/iter.rs b/consensus/safe_arith/src/iter.rs index 1fc3d3a1a7a..d5ee51b588d 100644 --- a/consensus/safe_arith/src/iter.rs +++ b/consensus/safe_arith/src/iter.rs @@ -28,10 +28,10 @@ mod test { #[test] fn unsigned_sum_small() { - let v = vec![400u64, 401, 402, 403, 404, 405, 406]; + let arr = [400u64, 401, 402, 403, 404, 405, 406]; assert_eq!( - v.iter().copied().safe_sum().unwrap(), - v.iter().copied().sum() + arr.iter().copied().safe_sum().unwrap(), + arr.iter().copied().sum() ); } @@ -61,10 +61,10 @@ mod test { #[test] fn signed_sum_almost_overflow() { - let v = vec![i64::MIN, 1, -1i64, i64::MAX, i64::MAX, 1]; + let arr = [i64::MIN, 1, -1i64, i64::MAX, i64::MAX, 1]; assert_eq!( - v.iter().copied().safe_sum().unwrap(), - v.iter().copied().sum() + arr.iter().copied().safe_sum().unwrap(), + arr.iter().copied().sum() ); } } diff --git a/crypto/bls/src/generic_public_key_bytes.rs b/crypto/bls/src/generic_public_key_bytes.rs index 59b0ffc43f1..240568b4f67 100644 --- a/crypto/bls/src/generic_public_key_bytes.rs +++ b/crypto/bls/src/generic_public_key_bytes.rs @@ -27,10 +27,7 @@ impl Copy for GenericPublicKeyBytes {} impl Clone for GenericPublicKeyBytes { fn clone(&self) -> Self { - Self { - bytes: self.bytes, - _phantom: PhantomData, - } + *self } } From c258270d6a874d407e2a465b916f97dbcb2c759a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Oliveira?= Date: Mon, 28 Aug 2023 00:55:28 +0000 Subject: [PATCH 15/25] update dependencies (#4639) ## Issue Addressed updates underlying dependencies and removes the ignored `RUSTSEC`'s for `cargo audit`. Also switches `procinfo` to `procfs` on `eth2` to remove the `nom` warning, `procinfo` is unmaintained see [here](https://github.com/danburkert/procinfo-rs/issues/46). --- Cargo.lock | 1180 ++++++++++++++---------- Cargo.toml | 3 +- Makefile | 5 +- beacon_node/execution_layer/Cargo.toml | 2 +- common/eth2/Cargo.toml | 10 +- common/eth2/src/lighthouse.rs | 13 +- common/eth2_network_config/Cargo.toml | 4 +- common/logging/Cargo.toml | 4 +- common/warp_utils/src/cors.rs | 12 +- common/warp_utils/src/metrics.rs | 2 +- lighthouse/tests/beacon_node.rs | 32 +- lighthouse/tests/tls/cert.pem | 24 + lighthouse/tests/tls/key.rsa | 27 + lighthouse/tests/validator_client.rs | 15 + testing/web3signer_tests/Cargo.toml | 4 +- watch/Cargo.toml | 3 +- watch/tests/tests.rs | 38 +- 17 files changed, 871 insertions(+), 507 deletions(-) create mode 100644 lighthouse/tests/tls/cert.pem create mode 100644 lighthouse/tests/tls/key.rsa diff --git a/Cargo.lock b/Cargo.lock index b1779045e30..56d421e0390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,12 +97,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "ctr", "opaque-debug", ] +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + [[package]] name = "aes-gcm" version = "0.9.4" @@ -110,8 +121,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.7.5", + "cipher 0.3.0", "ctr", "ghash", "subtle", @@ -141,9 +152,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" dependencies = [ "memchr", ] @@ -194,9 +205,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" @@ -234,6 +245,45 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "asn1-rs" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "asn1_der" version = "0.7.6" @@ -262,9 +312,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] @@ -277,7 +327,7 @@ checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", ] [[package]] @@ -288,18 +338,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "async-trait" -version = "0.1.72" +version = "0.1.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -310,7 +360,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -323,7 +373,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", ] [[package]] @@ -378,9 +428,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a1de45611fdb535bfde7b7de4fd54f4fd2b17b1737c0a59b69bf9b92074b8c" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", @@ -395,7 +445,7 @@ dependencies = [ "memchr", "mime", "percent-encoding", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "rustversion", "serde", "serde_json", @@ -649,9 +699,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bitvec" @@ -744,11 +794,10 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.41.0" +version = "1.42.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2f2e73fffe9455141e170fb9c1feb0ac521ec7e7dcd47a7cab72a658490fb8" +checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" dependencies = [ - "chrono", "serde", "serde_with", ] @@ -794,16 +843,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "builder_client" version = "0.1.0" @@ -905,7 +944,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.18", + "semver", "serde", "serde_json", "thiserror", @@ -919,9 +958,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "jobserver", + "libc", +] [[package]] name = "cexpr" @@ -929,7 +972,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ - "nom 7.1.3", + "nom", ] [[package]] @@ -945,7 +988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -958,7 +1001,7 @@ checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -971,11 +1014,7 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", - "serde", - "time 0.1.45", - "wasm-bindgen", "winapi", ] @@ -988,6 +1027,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -1066,7 +1115,7 @@ dependencies = [ "state_processing", "store", "task_executor", - "time 0.3.24", + "time", "timer", "tokio", "types", @@ -1107,9 +1156,15 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "convert_case" @@ -1326,7 +1381,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -1336,7 +1391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" dependencies = [ "nix 0.26.2", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1354,19 +1409,32 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" dependencies = [ "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "packed_simd_2", "platforms 3.0.2", + "rustc_version", "subtle", "zeroize", ] +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "darling" version = "0.13.4" @@ -1508,20 +1576,34 @@ dependencies = [ [[package]] name = "der" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der-parser" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" [[package]] name = "derivative" @@ -1542,7 +1624,7 @@ checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1554,7 +1636,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.0", + "rustc_version", "syn 1.0.109", ] @@ -1564,7 +1646,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7a532c1f99a0f596f6960a60d1e119e91582b24b39e2d83a190e61262c3ef0c" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.0", "byteorder", "diesel_derives", "itoa", @@ -1581,7 +1663,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1601,7 +1683,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -1681,7 +1763,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98c05fa26996c6141f78ac4fafbe297a7fa69690565ba4e0d1f2e60bde5ce501" dependencies = [ - "aes", + "aes 0.7.5", "aes-gcm", "arrayvec", "delay_map", @@ -1708,6 +1790,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -1738,7 +1831,7 @@ version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" dependencies = [ - "der 0.7.7", + "der 0.7.8", "digest 0.10.7", "elliptic-curve 0.13.5", "rfc6979 0.4.0", @@ -1748,18 +1841,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - -[[package]] -name = "ed25519" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" dependencies = [ "pkcs8 0.10.2", "signature 2.1.0", @@ -1767,26 +1851,12 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519 1.5.3", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-dalek" -version = "2.0.0-pre.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd577ba9d4bcab443cac60003d8fd32c638e7024a3ec92c200d7af5d2c397ed" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ - "curve25519-dalek 4.0.0-rc.1", - "ed25519 2.2.1", + "curve25519-dalek 4.0.0", + "ed25519", "rand_core 0.6.4", "serde", "sha2 0.10.7", @@ -1906,7 +1976,7 @@ checksum = "0be7b2ac146c1f99fe245c02d16af0696450d8e06c135db75e10eeb9e642c20d" dependencies = [ "base64 0.21.2", "bytes", - "ed25519-dalek 2.0.0-pre.0", + "ed25519-dalek", "hex", "k256 0.13.1", "log", @@ -1989,7 +2059,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2075,7 +2145,7 @@ dependencies = [ "mediatype", "mime", "pretty_reqwest_error", - "procinfo", + "procfs", "proto_array", "psutil", "reqwest", @@ -2128,7 +2198,7 @@ dependencies = [ name = "eth2_keystore" version = "0.1.0" dependencies = [ - "aes", + "aes 0.7.5", "bls", "eth2_key_derivation", "hex", @@ -2637,7 +2707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset 0.9.0", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -2675,9 +2745,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" dependencies = [ "crc32fast", "libz-sys", @@ -2811,7 +2881,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "waker-fn", ] @@ -2823,18 +2893,17 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "futures-rustls" -version = "0.22.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" dependencies = [ "futures-io", - "rustls 0.20.8", - "webpki 0.22.0", + "rustls 0.21.6", ] [[package]] @@ -2879,7 +2948,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "pin-utils", "slab", ] @@ -3258,7 +3327,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", ] [[package]] @@ -3338,9 +3407,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -3364,7 +3433,7 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "socket2 0.4.9", "tokio", "tower-service", @@ -3381,7 +3450,7 @@ dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.5", + "rustls 0.21.6", "tokio", "tokio-rustls 0.24.1", ] @@ -3594,6 +3663,15 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -3632,7 +3710,7 @@ checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.2", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3643,7 +3721,7 @@ checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2 0.5.3", "widestring 1.0.2", - "windows-sys", + "windows-sys 0.48.0", "winreg 0.50.0", ] @@ -3699,6 +3777,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -3774,7 +3861,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -3880,12 +3967,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" - [[package]] name = "libm" version = "0.2.7" @@ -3909,9 +3990,9 @@ dependencies = [ [[package]] name = "libp2p" -version = "0.52.1" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38039ba2df4f3255842050845daef4a004cc1f26da03dbc645535088b51910ef" +checksum = "32d07d1502a027366d55afe187621c2d7895dc111a3df13b35fed698049681d7" dependencies = [ "bytes", "futures", @@ -3929,6 +4010,7 @@ dependencies = [ "libp2p-metrics", "libp2p-noise", "libp2p-plaintext", + "libp2p-quic", "libp2p-swarm", "libp2p-tcp", "libp2p-websocket", @@ -4006,9 +4088,9 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e378da62e8c9251f6e885ed173a561663f29b251e745586cf6ae6150b295c37" +checksum = "2d157562dba6017193e5285acf6b1054759e83540bfd79f75b69d6ce774c88da" dependencies = [ "asynchronous-codec", "base64 0.21.2", @@ -4060,13 +4142,13 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38d6012784fe4cc14e6d443eb415b11fc7c456dc15d9f0d90d9b70bc7ac3ec1" +checksum = "686e73aff5e23efbb99bc85340ea6fd8686986aa7b283a881ba182cfca535ca9" dependencies = [ "asn1_der", "bs58 0.5.0", - "ed25519-dalek 1.0.1", + "ed25519-dalek", "libsecp256k1", "log", "multihash 0.19.0", @@ -4103,9 +4185,9 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3787ea81798dcc5bf1d8b40a8e8245cf894b168d04dd70aa48cb3ff2fff141d2" +checksum = "239ba7d28f8d0b5d77760dc6619c05c7e88e74ec8fbbe97f856f20a56745e620" dependencies = [ "instant", "libp2p-core", @@ -4138,12 +4220,12 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.43.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87945db2b3f977af09b62b9aa0a5f3e4870995a577ecd845cdeba94cdf6bbca7" +checksum = "71ce70757f2c0d82e9a3ef738fb10ea0723d16cec37f078f719e2c247704c1bb" dependencies = [ "bytes", - "curve25519-dalek 3.2.0", + "curve25519-dalek 4.0.0", "futures", "libp2p-core", "libp2p-identity", @@ -4177,11 +4259,34 @@ dependencies = [ "unsigned-varint 0.7.1", ] +[[package]] +name = "libp2p-quic" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb763e88f9a043546bfebd3575f340e7dd3d6c1b2cf2629600ec8965360c63a" +dependencies = [ + "bytes", + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "log", + "parking_lot 0.12.1", + "quinn", + "rand 0.8.5", + "rustls 0.21.6", + "socket2 0.5.3", + "thiserror", + "tokio", +] + [[package]] name = "libp2p-swarm" -version = "0.43.2" +version = "0.43.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43106820057e0f65c77b01a3873593f66e676da4e40c70c3a809b239109f1d30" +checksum = "28016944851bd73526d3c146aabf0fa9bbe27c558f080f9e5447da3a1772c01a" dependencies = [ "either", "fnv", @@ -4210,7 +4315,7 @@ dependencies = [ "proc-macro-warning", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -4230,11 +4335,30 @@ dependencies = [ "tokio", ] +[[package]] +name = "libp2p-tls" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8218d1d5482b122ccae396bbf38abdcb283ecc96fa54760e1dfd251f0546ac61" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls 0.21.6", + "rustls-webpki", + "thiserror", + "x509-parser", + "yasna", +] + [[package]] name = "libp2p-websocket" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956d981ebc84abc3377e5875483c06d94ff57bc6b25f725047f9fd52592f72d4" +checksum = "3facf0691bab65f571bc97c6c65ffa836248ca631d631b7691ac91deb7fceb5f" dependencies = [ "either", "futures", @@ -4247,14 +4371,14 @@ dependencies = [ "rw-stream-sink", "soketto", "url", - "webpki-roots 0.23.1", + "webpki-roots 0.25.2", ] [[package]] name = "libp2p-yamux" -version = "0.44.0" +version = "0.44.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a9b42ab6de15c6f076d8fb11dc5f48d899a10b55a2e16b12be9012a05287b0" +checksum = "8eedcb62824c4300efb9cfd4e2a6edaf3ca097b9e68b36dabe45a44469fd6a85" dependencies = [ "futures", "libp2p-core", @@ -4452,6 +4576,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -4505,9 +4635,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "logging" @@ -4611,9 +4741,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67827e6ea8ee8a7c4a72227ef4fc08957040acffdb5f122733b24fa12daff41b" +checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" [[package]] name = "maybe-uninit" @@ -4799,7 +4929,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -4828,6 +4958,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + [[package]] name = "multiaddr" version = "0.14.0" @@ -4913,24 +5061,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand 0.8.5", - "safemem", - "tempfile", - "twoway", -] - [[package]] name = "multistream-select" version = "0.13.0" @@ -5140,12 +5270,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" -[[package]] -name = "nom" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff" - [[package]] name = "nom" version = "7.1.3" @@ -5195,7 +5319,7 @@ dependencies = [ "autocfg 0.1.8", "byteorder", "lazy_static", - "libm 0.2.7", + "libm", "num-integer", "num-iter", "num-traits", @@ -5263,6 +5387,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -5315,9 +5448,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" dependencies = [ "bitflags 1.3.2", "cfg-if", @@ -5336,7 +5469,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -5347,18 +5480,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "111.26.0+1.1.1u" +version = "111.27.0+1.1.1v" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc62c9f12b22b8f5208c23a7200a442b2e5999f8bdf80233852122b5a4f6f37" +checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" dependencies = [ "cc", "libc", @@ -5409,16 +5542,6 @@ dependencies = [ "sha2 0.10.7", ] -[[package]] -name = "packed_simd_2" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" -dependencies = [ - "cfg-if", - "libm 0.1.4", -] - [[package]] name = "parity-scale-codec" version = "2.3.1" @@ -5522,7 +5645,18 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec 1.11.0", - "windows-targets", + "windows-targets 0.48.3", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -5549,6 +5683,18 @@ dependencies = [ "crypto-mac 0.11.1", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", + "password-hash", + "sha2 0.10.7", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -5586,7 +5732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -5609,22 +5755,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -5635,9 +5781,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" [[package]] name = "pin-utils" @@ -5661,7 +5807,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.7", + "der 0.7.8", "spki 0.7.2", ] @@ -5723,8 +5869,8 @@ dependencies = [ "concurrent-queue", "libc", "log", - "pin-project-lite 0.2.10", - "windows-sys", + "pin-project-lite 0.2.12", + "windows-sys 0.48.0", ] [[package]] @@ -5886,7 +6032,7 @@ checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -5899,17 +6045,20 @@ dependencies = [ ] [[package]] -name = "procinfo" -version = "0.4.2" +name = "procfs" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab1427f3d2635891f842892dda177883dca0639e05fe66796a62c9d2f23b49c" +checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" dependencies = [ + "bitflags 1.3.2", "byteorder", - "libc", - "nom 2.2.1", - "rustc_version 0.2.3", -] - + "chrono", + "flate2", + "hex", + "lazy_static", + "rustix 0.36.15", +] + [[package]] name = "prometheus" version = "0.13.3" @@ -5939,13 +6088,13 @@ dependencies = [ [[package]] name = "prometheus-client-derive-encode" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.29", ] [[package]] @@ -6049,11 +6198,59 @@ dependencies = [ "pin-project-lite 0.1.12", ] +[[package]] +name = "quinn" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +dependencies = [ + "bytes", + "futures-io", + "pin-project-lite 0.2.12", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.21.6", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13f81c9a9d574310b8351f8666f5a93ac3b0069c45c28ad52c10291389a7cf9" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring", + "rustc-hash", + "rustls 0.21.6", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +dependencies = [ + "bytes", + "libc", + "socket2 0.5.3", + "tracing", + "windows-sys 0.48.0", +] + [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -6193,6 +6390,18 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rcgen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" +dependencies = [ + "pem", + "ring", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -6224,13 +6433,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.4", + "regex-automata 0.3.6", "regex-syntax 0.7.4", ] @@ -6245,9 +6454,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -6290,8 +6499,8 @@ dependencies = [ "native-tls", "once_cell", "percent-encoding", - "pin-project-lite 0.2.10", - "rustls 0.21.5", + "pin-project-lite 0.2.12", + "rustls 0.21.6", "rustls-pemfile", "serde", "serde_json", @@ -6350,7 +6559,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -6442,60 +6651,61 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rusticata-macros" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "semver 1.0.18", + "nom", ] [[package]] name = "rustix" -version = "0.37.23" +version = "0.36.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", ] [[package]] name = "rustix" -version = "0.38.4" +version = "0.37.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" dependencies = [ - "bitflags 2.3.3", + "bitflags 1.3.2", "errno", + "io-lifetimes", "libc", - "linux-raw-sys 0.4.5", - "windows-sys", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", ] [[package]] -name = "rustls" -version = "0.19.1" +name = "rustix" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "base64 0.13.1", - "log", - "ring", - "sct 0.6.1", - "webpki 0.21.4", + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.5", + "windows-sys 0.48.0", ] [[package]] @@ -6506,20 +6716,20 @@ checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", - "sct 0.7.0", - "webpki 0.22.0", + "sct", + "webpki", ] [[package]] name = "rustls" -version = "0.21.5" +version = "0.21.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring", - "rustls-webpki 0.101.2", - "sct 0.7.0", + "rustls-webpki", + "sct", ] [[package]] @@ -6533,19 +6743,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.100.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.2" +version = "0.101.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" dependencies = [ "ring", "untrusted", @@ -6578,19 +6778,13 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" name = "safe_arith" version = "0.1.0" -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -6632,7 +6826,7 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -6668,16 +6862,6 @@ dependencies = [ "sha2 0.9.9", ] -[[package]] -name = "sct" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -6709,7 +6893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.7", + "der 0.7.8", "generic-array", "pkcs8 0.10.2", "subtle", @@ -6739,15 +6923,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.18" @@ -6757,12 +6932,6 @@ dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -6779,9 +6948,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] @@ -6819,20 +6988,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.180" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", @@ -6857,7 +7026,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -7049,7 +7218,7 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.24", + "time", ] [[package]] @@ -7181,7 +7350,7 @@ dependencies = [ "serde", "serde_json", "slog", - "time 0.3.24", + "time", ] [[package]] @@ -7226,14 +7395,14 @@ dependencies = [ "slog", "term", "thread_local", - "time 0.3.24", + "time", ] [[package]] name = "sloggers" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e20d36cb80da75a9c5511872f15247ddad14ead8c1dd97a86b56d1be9f5d4a0e" +checksum = "7a0a4d8569a69ee56f277bffc2f6eee637b98ed468448e8a5a84fa63efe4de9d" dependencies = [ "chrono", "libc", @@ -7286,17 +7455,17 @@ checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" +checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" dependencies = [ "aes-gcm", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-rc.1", + "curve25519-dalek 4.0.0", "rand_core 0.6.4", "ring", - "rustc_version 0.4.0", + "rustc_version", "sha2 0.10.7", "subtle", ] @@ -7318,7 +7487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7342,6 +7511,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.6.0" @@ -7359,7 +7534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der 0.7.7", + "der 0.7.8", ] [[package]] @@ -7575,9 +7750,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -7691,15 +7866,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand 2.0.0", "redox_syscall 0.3.5", - "rustix 0.38.4", - "windows-sys", + "rustix 0.38.8", + "windows-sys 0.48.0", ] [[package]] @@ -7741,8 +7916,7 @@ dependencies = [ [[package]] name = "testcontainers" version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e2b1567ca8a2b819ea7b28c92be35d9f76fb9edb214321dcc86eb96023d1f87" +source = "git+https://github.com/testcontainers/testcontainers-rs/?rev=0f2c9851#0f2c985160e51a200cfc847097c15b8d85ed7df1" dependencies = [ "bollard-stubs", "futures", @@ -7766,22 +7940,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -7805,20 +7979,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.45" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ "deranged", "itoa", @@ -7910,22 +8073,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" dependencies = [ - "autocfg 1.1.0", "backtrace", "bytes", "libc", "mio", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "signal-hook-registry", - "socket2 0.4.9", + "socket2 0.5.3", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -7934,7 +8096,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "tokio", ] @@ -7946,7 +8108,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -7975,7 +8137,7 @@ dependencies = [ "parking_lot 0.12.1", "percent-encoding", "phf", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "postgres-protocol", "postgres-types", "socket2 0.5.3", @@ -7983,17 +8145,6 @@ dependencies = [ "tokio-util 0.7.8", ] -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - [[package]] name = "tokio-rustls" version = "0.23.4" @@ -8002,7 +8153,7 @@ checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls 0.20.8", "tokio", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -8011,7 +8162,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.5", + "rustls 0.21.6", "tokio", ] @@ -8022,38 +8173,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "tokio", "tokio-util 0.7.8", ] [[package]] name = "tokio-tungstenite" -version = "0.15.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" dependencies = [ "futures-util", "log", - "pin-project", + "rustls 0.20.8", "tokio", - "tungstenite 0.14.0", + "tokio-rustls 0.23.4", + "tungstenite 0.17.3", + "webpki", + "webpki-roots 0.22.6", ] [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "rustls 0.20.8", "tokio", - "tokio-rustls 0.23.4", - "tungstenite 0.17.3", - "webpki 0.22.0", - "webpki-roots 0.22.6", + "tungstenite 0.18.0", ] [[package]] @@ -8067,7 +8217,7 @@ dependencies = [ "futures-io", "futures-sink", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "slab", "tokio", ] @@ -8081,7 +8231,7 @@ dependencies = [ "bytes", "futures-core", "futures-sink", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "slab", "tokio", "tracing", @@ -8139,7 +8289,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "tokio", "tower-layer", "tower-service", @@ -8166,7 +8316,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", - "pin-project-lite 0.2.10", + "pin-project-lite 0.2.12", "tracing-attributes", "tracing-core", ] @@ -8179,7 +8329,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] @@ -8336,9 +8486,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.14.0" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64 0.13.1", "byteorder", @@ -8347,17 +8497,19 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "rustls 0.20.8", + "sha-1 0.10.1", "thiserror", "url", "utf-8", + "webpki", ] [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ "base64 0.13.1", "byteorder", @@ -8366,21 +8518,10 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls 0.20.8", - "sha-1 0.10.1", + "sha1", "thiserror", "url", "utf-8", - "webpki 0.22.0", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", ] [[package]] @@ -8731,8 +8872,8 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.2" -source = "git+https://github.com/macladson/warp?rev=7e75acc368229a46a236a8c991bf251fe7fe50ef#7e75acc368229a46a236a8c991bf251fe7fe50ef" +version = "0.3.5" +source = "git+https://github.com/seanmonstar/warp.git?rev=149913fe#149913fed948bbe2149b52b9016170bcaef950ab" dependencies = [ "bytes", "futures-channel", @@ -8743,18 +8884,19 @@ dependencies = [ "log", "mime", "mime_guess", - "multipart", + "multer", "percent-encoding", "pin-project", + "rustls-pemfile", "scoped-tls", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.22.0", + "tokio-rustls 0.23.4", "tokio-stream", - "tokio-tungstenite 0.15.0", - "tokio-util 0.6.10", + "tokio-tungstenite 0.18.0", + "tokio-util 0.7.8", "tower-service", "tracing", ] @@ -8783,12 +8925,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -8816,7 +8952,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", "wasm-bindgen-shared", ] @@ -8850,7 +8986,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -8960,16 +9096,6 @@ dependencies = [ "zip", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki" version = "0.22.0" @@ -8986,17 +9112,14 @@ version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] name = "webpki-roots" -version = "0.23.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" -dependencies = [ - "rustls-webpki 0.100.1", -] +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "widestring" @@ -9066,7 +9189,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.3", ] [[package]] @@ -9081,35 +9204,65 @@ dependencies = [ "winapi", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.3", ] [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f51fb4c64f8b770a823c043c7fad036323e1c48f55287b7bbb7987b2fcdf3b" +dependencies = [ + "windows_aarch64_gnullvm 0.48.3", + "windows_aarch64_msvc 0.48.3", + "windows_i686_gnu 0.48.3", + "windows_i686_msvc 0.48.3", + "windows_x86_64_gnu 0.48.3", + "windows_x86_64_gnullvm 0.48.3", + "windows_x86_64_msvc 0.48.3", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "fde1bb55ae4ce76a597a8566d82c57432bc69c039449d61572a7a353da28f68c" [[package]] name = "windows_aarch64_msvc" @@ -9119,9 +9272,15 @@ checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1513e8d48365a78adad7322fd6b5e4c4e99d92a69db8df2d435b25b1f1f286d4" [[package]] name = "windows_i686_gnu" @@ -9131,9 +9290,15 @@ checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "60587c0265d2b842298f5858e1a5d79d146f9ee0c37be5782e92a6eb5e1d7a83" [[package]] name = "windows_i686_msvc" @@ -9143,9 +9308,15 @@ checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "224fe0e0ffff5d2ea6a29f82026c8f43870038a0ffc247aa95a52b47df381ac4" [[package]] name = "windows_x86_64_gnu" @@ -9155,15 +9326,27 @@ checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fc52a0f50a088de499712cbc012df7ebd94e2d6eb948435449d76a6287e7ad" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2093925509d91ea3d69bcd20238f4c2ecdb1a29d3c281d026a09705d0dd35f3d" [[package]] name = "windows_x86_64_msvc" @@ -9173,15 +9356,21 @@ checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ade45bc8bf02ae2aa34a9d54ba660a1a58204da34ba793c00d83ca3730b5f1" [[package]] name = "winnow" -version = "0.5.3" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46aab759304e4d7b2075a9aecba26228bb073ee8c50db796b2c72c676b5d807" +checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" dependencies = [ "memchr", ] @@ -9202,7 +9391,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -9216,7 +9405,7 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.0", + "rustc_version", "send_wrapper", "thiserror", "wasm-bindgen", @@ -9250,6 +9439,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-parser" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror", + "time", +] + [[package]] name = "xml-rs" version = "0.8.16" @@ -9276,18 +9482,28 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +checksum = "0329ef377816896f014435162bb3711ea7a07729c23d0960e6f8048b21b8fe91" dependencies = [ "futures", "log", "nohash-hasher", "parking_lot 0.12.1", + "pin-project", "rand 0.8.5", "static_assertions", ] +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -9305,19 +9521,55 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.29", ] [[package]] name = "zip" -version = "0.5.13" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" dependencies = [ + "aes 0.8.3", "byteorder", "bzip2", + "constant_time_eq", "crc32fast", + "crossbeam-utils", "flate2", - "thiserror", - "time 0.1.45", + "hmac 0.12.1", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 15906a03065..9930658e652 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,8 @@ resolver = "2" [patch] [patch.crates-io] -warp = { git = "https://github.com/macladson/warp", rev="7e75acc368229a46a236a8c991bf251fe7fe50ef" } +# TODO: remove when 0.3.6 get's released. +warp = { git = "https://github.com/seanmonstar/warp.git", rev="149913fe" } [profile.maxperf] inherits = "release" diff --git a/Makefile b/Makefile index bc84cdede09..1e99b3dbb33 100644 --- a/Makefile +++ b/Makefile @@ -206,9 +206,8 @@ arbitrary-fuzz: # Runs cargo audit (Audit Cargo.lock files for crates with security vulnerabilities reported to the RustSec Advisory Database) audit: - # cargo install --force cargo-audit - cargo audit --ignore RUSTSEC-2020-0071 --ignore RUSTSEC-2022-0093 \ - --ignore RUSTSEC-2023-0052 --ignore RUSTSEC-2023-0053 + cargo install --force cargo-audit + cargo audit --ignore RUSTSEC-2023-0052 # Runs `cargo vendor` to make sure dependencies can be vendored for packaging, reproducibility and archival purpose. vendor: diff --git a/beacon_node/execution_layer/Cargo.toml b/beacon_node/execution_layer/Cargo.toml index 8492a5159e2..f2259a4813b 100644 --- a/beacon_node/execution_layer/Cargo.toml +++ b/beacon_node/execution_layer/Cargo.toml @@ -50,4 +50,4 @@ keccak-hash = "0.10.0" hash256-std-hasher = "0.15.2" triehash = "0.8.4" hash-db = "0.15.2" -pretty_reqwest_error = { path = "../../common/pretty_reqwest_error" } \ No newline at end of file +pretty_reqwest_error = { path = "../../common/pretty_reqwest_error" } diff --git a/common/eth2/Cargo.toml b/common/eth2/Cargo.toml index d8e1a375fd5..8cb3de3a002 100644 --- a/common/eth2/Cargo.toml +++ b/common/eth2/Cargo.toml @@ -35,14 +35,8 @@ tokio = { version = "1.14.0", features = ["full"] } [target.'cfg(target_os = "linux")'.dependencies] psutil = { version = "3.2.2", optional = true } -procinfo = { version = "0.4.2", optional = true } +procfs = { version = "0.15.1", optional = true } [features] default = ["lighthouse"] -lighthouse = [ - "proto_array", - "psutil", - "procinfo", - "store", - "slashing_protection", -] +lighthouse = ["proto_array", "psutil", "procfs", "store", "slashing_protection"] diff --git a/common/eth2/src/lighthouse.rs b/common/eth2/src/lighthouse.rs index 1b4bcc0e395..dfc19db4928 100644 --- a/common/eth2/src/lighthouse.rs +++ b/common/eth2/src/lighthouse.rs @@ -95,8 +95,8 @@ pub struct ValidatorInclusionData { #[cfg(target_os = "linux")] use { - procinfo::pid, psutil::cpu::os::linux::CpuTimesExt, - psutil::memory::os::linux::VirtualMemoryExt, psutil::process::Process, + psutil::cpu::os::linux::CpuTimesExt, psutil::memory::os::linux::VirtualMemoryExt, + psutil::process::Process, }; /// Reports on the health of the Lighthouse instance. @@ -238,7 +238,7 @@ pub struct ProcessHealth { /// The pid of this process. pub pid: u32, /// The number of threads used by this pid. - pub pid_num_threads: i32, + pub pid_num_threads: i64, /// The total resident memory used by this pid. pub pid_mem_resident_set_size: u64, /// The total virtual memory used by this pid. @@ -262,7 +262,12 @@ impl ProcessHealth { .memory_info() .map_err(|e| format!("Unable to get process memory info: {:?}", e))?; - let stat = pid::stat_self().map_err(|e| format!("Unable to get stat: {:?}", e))?; + let me = procfs::process::Process::myself() + .map_err(|e| format!("Unable to get process: {:?}", e))?; + let stat = me + .stat() + .map_err(|e| format!("Unable to get stat: {:?}", e))?; + let process_times = process .cpu_times() .map_err(|e| format!("Unable to get process cpu times : {:?}", e))?; diff --git a/common/eth2_network_config/Cargo.toml b/common/eth2_network_config/Cargo.toml index 338a2d243bc..d1fe25f1cc1 100644 --- a/common/eth2_network_config/Cargo.toml +++ b/common/eth2_network_config/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" build = "build.rs" [build-dependencies] -zip = "0.5.8" +zip = "0.6" eth2_config = { path = "../eth2_config"} [dev-dependencies] @@ -18,4 +18,4 @@ serde_yaml = "0.8.13" types = { path = "../../consensus/types"} ethereum_ssz = "0.5.0" eth2_config = { path = "../eth2_config"} -discv5 = "0.3.1" \ No newline at end of file +discv5 = "0.3.1" diff --git a/common/logging/Cargo.toml b/common/logging/Cargo.toml index b6179d9e782..0e158f58ff5 100644 --- a/common/logging/Cargo.toml +++ b/common/logging/Cargo.toml @@ -17,6 +17,6 @@ sloggers = { version = "2.1.1", features = ["json"] } slog-async = "2.7.0" take_mut = "0.2.2" parking_lot = "0.12.1" -serde = "1.0.153" +serde = "1.0.153" serde_json = "1.0.94" -chrono = "0.4.23" +chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } diff --git a/common/warp_utils/src/cors.rs b/common/warp_utils/src/cors.rs index 314ea9c8f74..55043dfd7dd 100644 --- a/common/warp_utils/src/cors.rs +++ b/common/warp_utils/src/cors.rs @@ -10,10 +10,14 @@ pub fn set_builder_origins( default_origin: (IpAddr, u16), ) -> Result { if let Some(allow_origin) = allow_origin { - let origins = allow_origin - .split(',') - .map(|s| verify_cors_origin_str(s).map(|_| s)) - .collect::, _>>()?; + let mut origins = vec![]; + for origin in allow_origin.split(',') { + verify_cors_origin_str(origin)?; + if origin == "*" { + return Ok(builder.allow_any_origin()); + } + origins.push(origin) + } Ok(builder.allow_origins(origins)) } else { let origin = match default_origin.0 { diff --git a/common/warp_utils/src/metrics.rs b/common/warp_utils/src/metrics.rs index 1b9d89db91a..d93b74ca956 100644 --- a/common/warp_utils/src/metrics.rs +++ b/common/warp_utils/src/metrics.rs @@ -87,7 +87,7 @@ pub fn scrape_process_health_metrics() { // This will silently fail if we are unable to observe the health. This is desired behaviour // since we don't support `Health` for all platforms. if let Ok(health) = ProcessHealth::observe() { - set_gauge(&PROCESS_NUM_THREADS, health.pid_num_threads as i64); + set_gauge(&PROCESS_NUM_THREADS, health.pid_num_threads); set_gauge(&PROCESS_RES_MEM, health.pid_mem_resident_set_size as i64); set_gauge(&PROCESS_VIRT_MEM, health.pid_mem_virtual_memory_size as i64); set_gauge(&PROCESS_SECONDS, health.pid_process_seconds_total as i64); diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 37d224dbc31..626be85f197 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -11,6 +11,7 @@ use lighthouse_network::PeerId; use std::fs::File; use std::io::{Read, Write}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::str::FromStr; @@ -1460,15 +1461,20 @@ fn disable_inbound_rate_limiter_flag() { #[test] fn http_allow_origin_flag() { CommandLineTest::new() - .flag("http-allow-origin", Some("127.0.0.99")) + .flag("http", None) + .flag("http-allow-origin", Some("http://127.0.0.99")) .run_with_zero_port() .with_config(|config| { - assert_eq!(config.http_api.allow_origin, Some("127.0.0.99".to_string())); + assert_eq!( + config.http_api.allow_origin, + Some("http://127.0.0.99".to_string()) + ); }); } #[test] fn http_allow_origin_all_flag() { CommandLineTest::new() + .flag("http", None) .flag("http-allow-origin", Some("*")) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.allow_origin, Some("*".to_string()))); @@ -1476,6 +1482,7 @@ fn http_allow_origin_all_flag() { #[test] fn http_allow_sync_stalled_flag() { CommandLineTest::new() + .flag("http", None) .flag("http-allow-sync-stalled", None) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.allow_sync_stalled, true)); @@ -1483,32 +1490,29 @@ fn http_allow_sync_stalled_flag() { #[test] fn http_enable_beacon_processor() { CommandLineTest::new() + .flag("http", None) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.enable_beacon_processor, true)); CommandLineTest::new() + .flag("http", None) .flag("http-enable-beacon-processor", Some("true")) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.enable_beacon_processor, true)); CommandLineTest::new() + .flag("http", None) .flag("http-enable-beacon-processor", Some("false")) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.enable_beacon_processor, false)); } #[test] fn http_tls_flags() { - let dir = TempDir::new().expect("Unable to create temporary directory"); CommandLineTest::new() + .flag("http", None) .flag("http-enable-tls", None) - .flag( - "http-tls-cert", - dir.path().join("certificate.crt").as_os_str().to_str(), - ) - .flag( - "http-tls-key", - dir.path().join("private.key").as_os_str().to_str(), - ) + .flag("http-tls-cert", Some("tests/tls/cert.pem")) + .flag("http-tls-key", Some("tests/tls/key.rsa")) .run_with_zero_port() .with_config(|config| { let tls_config = config @@ -1516,14 +1520,15 @@ fn http_tls_flags() { .tls_config .as_ref() .expect("tls_config was empty."); - assert_eq!(tls_config.cert, dir.path().join("certificate.crt")); - assert_eq!(tls_config.key, dir.path().join("private.key")); + assert_eq!(tls_config.cert, Path::new("tests/tls/cert.pem")); + assert_eq!(tls_config.key, Path::new("tests/tls/key.rsa")); }); } #[test] fn http_spec_fork_default() { CommandLineTest::new() + .flag("http", None) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.spec_fork_name, None)); } @@ -1531,6 +1536,7 @@ fn http_spec_fork_default() { #[test] fn http_spec_fork_override() { CommandLineTest::new() + .flag("http", None) .flag("http-spec-fork", Some("altair")) .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.spec_fork_name, Some(ForkName::Altair))); diff --git a/lighthouse/tests/tls/cert.pem b/lighthouse/tests/tls/cert.pem new file mode 100644 index 00000000000..03af12ff819 --- /dev/null +++ b/lighthouse/tests/tls/cert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEADCCAmigAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u +eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE2MDgxMzE2MDcwNFoX +DTIyMDIwMzE2MDcwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpVhh1/FNP2qvWenbZSghari/UThwe +dynfnHG7gc3JmygkEdErWBO/CHzHgsx7biVE5b8sZYNEDKFojyoPHGWK2bQM/FTy +niJCgNCLdn6hUqqxLAml3cxGW77hAWu94THDGB1qFe+eFiAUnDmob8gNZtAzT6Ky +b/JGJdrEU0wj+Rd7wUb4kpLInNH/Jc+oz2ii2AjNbGOZXnRz7h7Kv3sO9vABByYe +LcCj3qnhejHMqVhbAT1MD6zQ2+YKBjE52MsQKU/xhUpu9KkUyLh0cxkh3zrFiKh4 +Vuvtc+n7aeOv2jJmOl1dr0XLlSHBlmoKqH6dCTSbddQLmlK7dms8vE01AgMBAAGj +gb4wgbswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFMeUzGYV +bXwJNQVbY1+A8YXYZY8pMEIGA1UdIwQ7MDmAFJvEsUi7+D8vp8xcWvnEdVBGkpoW +oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswOwYDVR0RBDQwMoIO +dGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJbG9jYWxob3N0 +MA0GCSqGSIb3DQEBCwUAA4IBgQBsk5ivAaRAcNgjc7LEiWXFkMg703AqDDNx7kB1 +RDgLalLvrjOfOp2jsDfST7N1tKLBSQ9bMw9X4Jve+j7XXRUthcwuoYTeeo+Cy0/T +1Q78ctoX74E2nB958zwmtRykGrgE/6JAJDwGcgpY9kBPycGxTlCN926uGxHsDwVs +98cL6ZXptMLTR6T2XP36dAJZuOICSqmCSbFR8knc/gjUO36rXTxhwci8iDbmEVaf +BHpgBXGU5+SQ+QM++v6bHGf4LNQC5NZ4e4xvGax8ioYu/BRsB/T3Lx+RlItz4zdU +XuxCNcm3nhQV2ZHquRdbSdoyIxV5kJXel4wCmOhWIq7A2OBKdu5fQzIAzzLi65EN +RPAKsKB4h7hGgvciZQ7dsMrlGw0DLdJ6UrFyiR5Io7dXYT/+JP91lP5xsl6Lhg9O +FgALt7GSYRm2cZdgi9pO9rRr83Br1VjQT1vHz6yoZMXSqc4A2zcN2a2ZVq//rHvc +FZygs8miAhWPzqnpmgTj1cPiU1M= +-----END CERTIFICATE----- diff --git a/lighthouse/tests/tls/key.rsa b/lighthouse/tests/tls/key.rsa new file mode 100644 index 00000000000..b13bf5d07f9 --- /dev/null +++ b/lighthouse/tests/tls/key.rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqVYYdfxTT9qr1np22UoIWq4v1E4cHncp35xxu4HNyZsoJBHR +K1gTvwh8x4LMe24lROW/LGWDRAyhaI8qDxxlitm0DPxU8p4iQoDQi3Z+oVKqsSwJ +pd3MRlu+4QFrveExwxgdahXvnhYgFJw5qG/IDWbQM0+ism/yRiXaxFNMI/kXe8FG ++JKSyJzR/yXPqM9ootgIzWxjmV50c+4eyr97DvbwAQcmHi3Ao96p4XoxzKlYWwE9 +TA+s0NvmCgYxOdjLEClP8YVKbvSpFMi4dHMZId86xYioeFbr7XPp+2njr9oyZjpd +Xa9Fy5UhwZZqCqh+nQk0m3XUC5pSu3ZrPLxNNQIDAQABAoIBAFKtZJgGsK6md4vq +kyiYSufrcBLaaEQ/rkQtYCJKyC0NAlZKFLRy9oEpJbNLm4cQSkYPXn3Qunx5Jj2k +2MYz+SgIDy7f7KHgr52Ew020dzNQ52JFvBgt6NTZaqL1TKOS1fcJSSNIvouTBerK +NCSXHzfb4P+MfEVe/w1c4ilE+kH9SzdEo2jK/sRbzHIY8TX0JbmQ4SCLLayr22YG +usIxtIYcWt3MMP/G2luRnYzzBCje5MXdpAhlHLi4TB6x4h5PmBKYc57uOVNngKLd +YyrQKcszW4Nx5v0a4HG3A5EtUXNCco1+5asXOg2lYphQYVh2R+1wgu5WiDjDVu+6 +EYgjFSkCgYEA0NBk6FDoxE/4L/4iJ4zIhu9BptN8Je/uS5c6wRejNC/VqQyw7SHb +hRFNrXPvq5Y+2bI/DxtdzZLKAMXOMjDjj0XEgfOIn2aveOo3uE7zf1i+njxwQhPu +uSYA9AlBZiKGr2PCYSDPnViHOspVJjxRuAgyWM1Qf+CTC0D95aj0oz8CgYEAz5n4 +Cb3/WfUHxMJLljJ7PlVmlQpF5Hk3AOR9+vtqTtdxRjuxW6DH2uAHBDdC3OgppUN4 +CFj55kzc2HUuiHtmPtx8mK6G+otT7Lww+nLSFL4PvZ6CYxqcio5MPnoYd+pCxrXY +JFo2W7e4FkBOxb5PF5So5plg+d0z/QiA7aFP1osCgYEAtgi1rwC5qkm8prn4tFm6 +hkcVCIXc+IWNS0Bu693bXKdGr7RsmIynff1zpf4ntYGpEMaeymClCY0ppDrMYlzU +RBYiFNdlBvDRj6s/H+FTzHRk2DT/99rAhY9nzVY0OQFoQIXK8jlURGrkmI/CYy66 +XqBmo5t4zcHM7kaeEBOWEKkCgYAYnO6VaRtPNQfYwhhoFFAcUc+5t+AVeHGW/4AY +M5qlAlIBu64JaQSI5KqwS0T4H+ZgG6Gti68FKPO+DhaYQ9kZdtam23pRVhd7J8y+ +xMI3h1kiaBqZWVxZ6QkNFzizbui/2mtn0/JB6YQ/zxwHwcpqx0tHG8Qtm5ZAV7PB +eLCYhQKBgQDALJxU/6hMTdytEU5CLOBSMby45YD/RrfQrl2gl/vA0etPrto4RkVq +UrkDO/9W4mZORClN3knxEFSTlYi8YOboxdlynpFfhcs82wFChs+Ydp1eEsVHAqtu +T+uzn0sroycBiBfVB949LExnzGDFUkhG0i2c2InarQYLTsIyHCIDEA== +-----END RSA PRIVATE KEY----- diff --git a/lighthouse/tests/validator_client.rs b/lighthouse/tests/validator_client.rs index 9bcfe2a1d50..062b7e7786a 100644 --- a/lighthouse/tests/validator_client.rs +++ b/lighthouse/tests/validator_client.rs @@ -260,6 +260,7 @@ fn http_flag() { fn http_address_flag() { let addr = "127.0.0.99".parse::().unwrap(); CommandLineTest::new() + .flag("http", None) .flag("http-address", Some("127.0.0.99")) .flag("unencrypted-http-transport", None) .run() @@ -269,6 +270,7 @@ fn http_address_flag() { fn http_address_ipv6_flag() { let addr = "::1".parse::().unwrap(); CommandLineTest::new() + .flag("http", None) .flag("http-address", Some("::1")) .flag("unencrypted-http-transport", None) .run() @@ -279,6 +281,7 @@ fn http_address_ipv6_flag() { fn missing_unencrypted_http_transport_flag() { let addr = "127.0.0.99".parse::().unwrap(); CommandLineTest::new() + .flag("http", None) .flag("http-address", Some("127.0.0.99")) .run() .with_config(|config| assert_eq!(config.http_api.listen_addr, addr)); @@ -286,6 +289,7 @@ fn missing_unencrypted_http_transport_flag() { #[test] fn http_port_flag() { CommandLineTest::new() + .flag("http", None) .flag("http-port", Some("9090")) .run() .with_config(|config| assert_eq!(config.http_api.listen_port, 9090)); @@ -293,6 +297,7 @@ fn http_port_flag() { #[test] fn http_allow_origin_flag() { CommandLineTest::new() + .flag("http", None) .flag("http-allow-origin", Some("http://localhost:9009")) .run() .with_config(|config| { @@ -305,6 +310,7 @@ fn http_allow_origin_flag() { #[test] fn http_allow_origin_all_flag() { CommandLineTest::new() + .flag("http", None) .flag("http-allow-origin", Some("*")) .run() .with_config(|config| assert_eq!(config.http_api.allow_origin, Some("*".to_string()))); @@ -312,12 +318,14 @@ fn http_allow_origin_all_flag() { #[test] fn http_allow_keystore_export_default() { CommandLineTest::new() + .flag("http", None) .run() .with_config(|config| assert!(!config.http_api.allow_keystore_export)); } #[test] fn http_allow_keystore_export_present() { CommandLineTest::new() + .flag("http", None) .flag("http-allow-keystore-export", None) .run() .with_config(|config| assert!(config.http_api.allow_keystore_export)); @@ -325,12 +333,14 @@ fn http_allow_keystore_export_present() { #[test] fn http_store_keystore_passwords_in_secrets_dir_default() { CommandLineTest::new() + .flag("http", None) .run() .with_config(|config| assert!(!config.http_api.store_passwords_in_secrets_dir)); } #[test] fn http_store_keystore_passwords_in_secrets_dir_present() { CommandLineTest::new() + .flag("http", None) .flag("http-store-passwords-in-secrets-dir", None) .run() .with_config(|config| assert!(config.http_api.store_passwords_in_secrets_dir)); @@ -348,6 +358,7 @@ fn metrics_flag() { fn metrics_address_flag() { let addr = "127.0.0.99".parse::().unwrap(); CommandLineTest::new() + .flag("metrics", None) .flag("metrics-address", Some("127.0.0.99")) .run() .with_config(|config| assert_eq!(config.http_metrics.listen_addr, addr)); @@ -356,6 +367,7 @@ fn metrics_address_flag() { fn metrics_address_ipv6_flag() { let addr = "::1".parse::().unwrap(); CommandLineTest::new() + .flag("metrics", None) .flag("metrics-address", Some("::1")) .run() .with_config(|config| assert_eq!(config.http_metrics.listen_addr, addr)); @@ -363,6 +375,7 @@ fn metrics_address_ipv6_flag() { #[test] fn metrics_port_flag() { CommandLineTest::new() + .flag("metrics", None) .flag("metrics-port", Some("9090")) .run() .with_config(|config| assert_eq!(config.http_metrics.listen_port, 9090)); @@ -370,6 +383,7 @@ fn metrics_port_flag() { #[test] fn metrics_allow_origin_flag() { CommandLineTest::new() + .flag("metrics", None) .flag("metrics-allow-origin", Some("http://localhost:9009")) .run() .with_config(|config| { @@ -382,6 +396,7 @@ fn metrics_allow_origin_flag() { #[test] fn metrics_allow_origin_all_flag() { CommandLineTest::new() + .flag("metrics", None) .flag("metrics-allow-origin", Some("*")) .run() .with_config(|config| assert_eq!(config.http_metrics.allow_origin, Some("*".to_string()))); diff --git a/testing/web3signer_tests/Cargo.toml b/testing/web3signer_tests/Cargo.toml index c0fbf667236..faad76a19c3 100644 --- a/testing/web3signer_tests/Cargo.toml +++ b/testing/web3signer_tests/Cargo.toml @@ -26,6 +26,6 @@ serde_derive = "1.0.116" serde_yaml = "0.8.13" eth2_network_config = { path = "../../common/eth2_network_config" } serde_json = "1.0.58" -zip = "0.5.13" +zip = "0.6" lazy_static = "1.4.0" -parking_lot = "0.12.0" \ No newline at end of file +parking_lot = "0.12.0" diff --git a/watch/Cargo.toml b/watch/Cargo.toml index 23e2c566dc1..b29df14439e 100644 --- a/watch/Cargo.toml +++ b/watch/Cargo.toml @@ -41,6 +41,7 @@ tokio-postgres = "0.7.5" http_api = { path = "../beacon_node/http_api" } beacon_chain = { path = "../beacon_node/beacon_chain" } network = { path = "../beacon_node/network" } -testcontainers = "0.14.0" +# TODO: update to 0.15 when released: https://github.com/testcontainers/testcontainers-rs/issues/497 +testcontainers = { git = "https://github.com/testcontainers/testcontainers-rs/", rev = "0f2c9851" } unused_port = { path = "../common/unused_port" } task_executor = { path = "../common/task_executor" } diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs index 9032b124ab8..a54386eb753 100644 --- a/watch/tests/tests.rs +++ b/watch/tests/tests.rs @@ -23,6 +23,7 @@ use watch::{ }; use log::error; +use std::collections::HashMap; use std::env; use std::net::SocketAddr; use std::time::Duration; @@ -30,7 +31,42 @@ use tokio::{runtime, task::JoinHandle}; use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls}; use unused_port::unused_tcp4_port; -use testcontainers::{clients::Cli, images::postgres::Postgres, RunnableImage}; +use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage}; + +#[derive(Debug)] +pub struct Postgres(HashMap); + +impl Default for Postgres { + fn default() -> Self { + let mut env_vars = HashMap::new(); + env_vars.insert("POSTGRES_DB".to_owned(), "postgres".to_owned()); + env_vars.insert("POSTGRES_HOST_AUTH_METHOD".into(), "trust".into()); + + Self(env_vars) + } +} + +impl Image for Postgres { + type Args = (); + + fn name(&self) -> String { + "postgres".to_owned() + } + + fn tag(&self) -> String { + "11-alpine".to_owned() + } + + fn ready_conditions(&self) -> Vec { + vec![WaitFor::message_on_stderr( + "database system is ready to accept connections", + )] + } + + fn env_vars(&self) -> Box + '_> { + Box::new(self.0.iter()) + } +} type E = MainnetEthSpec; From 8e95b69a1a9a449f352ec7a3904c549accb75111 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 28 Aug 2023 00:55:31 +0000 Subject: [PATCH 16/25] Send success code for duplicate blocks on HTTP (#4655) ## Issue Addressed Closes #4473 (take 3) ## Proposed Changes - Send a 202 status code by default for duplicate blocks, instead of 400. This conveys to the caller that the block was published, but makes no guarantees about its validity. Block relays can count this as a success or a failure as they wish. - For users wanting finer-grained control over which status is returned for duplicates, a flag `--http-duplicate-block-status` can be used to adjust the behaviour. A 400 status can be supplied to restore the old (spec-compliant) behaviour, or a 200 status can be used to silence VCs that warn loudly for non-200 codes (e.g. Lighthouse prior to v4.4.0). - Update the Lighthouse VC to gracefully handle success codes other than 200. The info message isn't the nicest thing to read, but it covers all bases and isn't a nasty `ERRO`/`CRIT` that will wake anyone up. ## Additional Info I'm planning to raise a PR to `beacon-APIs` to specify that clients may return 202 for duplicate blocks. Really it would be nice to use some 2xx code that _isn't_ the same as the code for "published but invalid". I think unfortunately there aren't any suitable codes, and maybe the best fit is `409 CONFLICT`. Given that we need to fix this promptly for our release, I think using the 202 code temporarily with configuration strikes a nice compromise. --- beacon_node/http_api/src/lib.rs | 185 ++++++++---------- beacon_node/http_api/src/publish_blocks.rs | 47 +++-- beacon_node/http_api/src/task_spawner.rs | 40 ---- beacon_node/http_api/src/test_utils.rs | 10 +- .../tests/broadcast_validation_tests.rs | 9 +- beacon_node/http_api/tests/tests.rs | 67 ++++++- beacon_node/src/cli.rs | 9 + beacon_node/src/config.rs | 3 + common/eth2/src/types.rs | 20 ++ lighthouse/tests/beacon_node.rs | 19 ++ validator_client/src/block_service.rs | 43 ++-- 11 files changed, 271 insertions(+), 181 deletions(-) diff --git a/beacon_node/http_api/src/lib.rs b/beacon_node/http_api/src/lib.rs index 3aa10139b0b..19d62c060d9 100644 --- a/beacon_node/http_api/src/lib.rs +++ b/beacon_node/http_api/src/lib.rs @@ -139,6 +139,8 @@ pub struct Config { pub data_dir: PathBuf, pub sse_capacity_multiplier: usize, pub enable_beacon_processor: bool, + #[serde(with = "eth2::types::serde_status_code")] + pub duplicate_block_status_code: StatusCode, } impl Default for Config { @@ -154,6 +156,7 @@ impl Default for Config { data_dir: PathBuf::from(DEFAULT_ROOT_DIR), sse_capacity_multiplier: 1, enable_beacon_processor: true, + duplicate_block_status_code: StatusCode::ACCEPTED, } } } @@ -510,6 +513,8 @@ pub fn serve( let task_spawner_filter = warp::any().map(move || TaskSpawner::new(beacon_processor_send.clone())); + let duplicate_block_status_code = ctx.config.duplicate_block_status_code; + /* * * Start of HTTP method definitions. @@ -1284,11 +1289,11 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |block: Arc>, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |block: Arc>, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, @@ -1297,9 +1302,9 @@ pub fn serve( &network_tx, log, BroadcastValidation::default(), + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1314,11 +1319,11 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |block_bytes: Bytes, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |block_bytes: Bytes, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBeaconBlock::::from_ssz_bytes(&block_bytes, &chain.spec) @@ -1334,9 +1339,9 @@ pub fn serve( &network_tx, log, BroadcastValidation::default(), + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1352,12 +1357,12 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |validation_level: api_types::BroadcastValidationQuery, - block: Arc>, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |validation_level: api_types::BroadcastValidationQuery, + block: Arc>, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_block( None, @@ -1366,9 +1371,9 @@ pub fn serve( &network_tx, log, validation_level.broadcast_validation, + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1384,12 +1389,12 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |validation_level: api_types::BroadcastValidationQuery, - block_bytes: Bytes, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |validation_level: api_types::BroadcastValidationQuery, + block_bytes: Bytes, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { let block = SignedBeaconBlock::::from_ssz_bytes(&block_bytes, &chain.spec) @@ -1405,9 +1410,9 @@ pub fn serve( &network_tx, log, validation_level.broadcast_validation, + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1427,11 +1432,11 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |block: SignedBeaconBlock>, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |block: SignedBlindedBeaconBlock, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { publish_blocks::publish_blinded_block( block, @@ -1439,9 +1444,9 @@ pub fn serve( &network_tx, log, BroadcastValidation::default(), + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1457,13 +1462,13 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |block_bytes: Bytes, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { + move |block_bytes: Bytes, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { task_spawner.spawn_async_with_rejection(Priority::P0, async move { - let block = SignedBeaconBlock::>::from_ssz_bytes( + let block = SignedBlindedBeaconBlock::::from_ssz_bytes( &block_bytes, &chain.spec, ) @@ -1476,9 +1481,9 @@ pub fn serve( &network_tx, log, BroadcastValidation::default(), + duplicate_block_status_code, ) .await - .map(|()| warp::reply().into_response()) }) }, ); @@ -1494,87 +1499,63 @@ pub fn serve( .and(network_tx_filter.clone()) .and(log_filter.clone()) .then( - |validation_level: api_types::BroadcastValidationQuery, - block: SignedBeaconBlock>, - task_spawner: TaskSpawner, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| { - task_spawner.spawn_async(Priority::P0, async move { - match publish_blocks::publish_blinded_block( + move |validation_level: api_types::BroadcastValidationQuery, + block: SignedBlindedBeaconBlock, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { + task_spawner.spawn_async_with_rejection(Priority::P0, async move { + publish_blocks::publish_blinded_block( block, chain, &network_tx, log, validation_level.broadcast_validation, + duplicate_block_status_code, ) .await - { - Ok(()) => warp::reply().into_response(), - Err(e) => match warp_utils::reject::handle_rejection(e).await { - Ok(reply) => reply.into_response(), - Err(_) => warp::reply::with_status( - StatusCode::INTERNAL_SERVER_ERROR, - eth2::StatusCode::INTERNAL_SERVER_ERROR, - ) - .into_response(), - }, - } }) }, ); - let post_beacon_blinded_blocks_v2_ssz = - eth_v2 - .and(warp::path("beacon")) - .and(warp::path("blinded_blocks")) - .and(warp::query::()) - .and(warp::path::end()) - .and(warp::body::bytes()) - .and(chain_filter.clone()) - .and(network_tx_filter.clone()) - .and(log_filter.clone()) - .then( - |validation_level: api_types::BroadcastValidationQuery, - block_bytes: Bytes, - chain: Arc>, - network_tx: UnboundedSender>, - log: Logger| async move { - let block = - match SignedBeaconBlock::>::from_ssz_bytes( - &block_bytes, - &chain.spec, - ) { - Ok(data) => data, - Err(_) => { - return warp::reply::with_status( - StatusCode::BAD_REQUEST, - eth2::StatusCode::BAD_REQUEST, - ) - .into_response(); - } - }; - match publish_blocks::publish_blinded_block( + let post_beacon_blinded_blocks_v2_ssz = eth_v2 + .and(warp::path("beacon")) + .and(warp::path("blinded_blocks")) + .and(warp::query::()) + .and(warp::path::end()) + .and(warp::body::bytes()) + .and(task_spawner_filter.clone()) + .and(chain_filter.clone()) + .and(network_tx_filter.clone()) + .and(log_filter.clone()) + .then( + move |validation_level: api_types::BroadcastValidationQuery, + block_bytes: Bytes, + task_spawner: TaskSpawner, + chain: Arc>, + network_tx: UnboundedSender>, + log: Logger| { + task_spawner.spawn_async_with_rejection(Priority::P0, async move { + let block = SignedBlindedBeaconBlock::::from_ssz_bytes( + &block_bytes, + &chain.spec, + ) + .map_err(|e| { + warp_utils::reject::custom_bad_request(format!("invalid SSZ: {e:?}")) + })?; + publish_blocks::publish_blinded_block( block, chain, &network_tx, log, validation_level.broadcast_validation, + duplicate_block_status_code, ) .await - { - Ok(()) => warp::reply().into_response(), - Err(e) => match warp_utils::reject::handle_rejection(e).await { - Ok(reply) => reply.into_response(), - Err(_) => warp::reply::with_status( - StatusCode::INTERNAL_SERVER_ERROR, - eth2::StatusCode::INTERNAL_SERVER_ERROR, - ) - .into_response(), - }, - } - }, - ); + }) + }, + ); let block_id_or_err = warp::path::param::().or_else(|_| async { Err(warp_utils::reject::custom_bad_request( diff --git a/beacon_node/http_api/src/publish_blocks.rs b/beacon_node/http_api/src/publish_blocks.rs index 0f2f7b361c7..58524f06981 100644 --- a/beacon_node/http_api/src/publish_blocks.rs +++ b/beacon_node/http_api/src/publish_blocks.rs @@ -4,7 +4,7 @@ use beacon_chain::{ BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, IntoGossipVerifiedBlock, NotifyExecutionLayer, }; -use eth2::types::BroadcastValidation; +use eth2::types::{BroadcastValidation, ErrorMessage}; use execution_layer::ProvenancedPayload; use lighthouse_network::PubsubMessage; use network::NetworkMessage; @@ -19,7 +19,8 @@ use types::{ AbstractExecPayload, BeaconBlockRef, BlindedPayload, EthSpec, ExecPayload, ExecutionBlockHash, FullPayload, Hash256, SignedBeaconBlock, }; -use warp::Rejection; +use warp::http::StatusCode; +use warp::{reply::Response, Rejection, Reply}; pub enum ProvenancedBlock> { /// The payload was built using a local EE. @@ -47,7 +48,8 @@ pub async fn publish_block>( network_tx: &UnboundedSender>, log: Logger, validation_level: BroadcastValidation, -) -> Result<(), Rejection> { + duplicate_status_code: StatusCode, +) -> Result { let seen_timestamp = timestamp_now(); let (block, is_locally_built_block) = match provenanced_block { ProvenancedBlock::Local(block, _) => (block, true), @@ -75,10 +77,30 @@ pub async fn publish_block>( }; /* if we can form a `GossipVerifiedBlock`, we've passed our basic gossip checks */ - let gossip_verified_block = block.into_gossip_verified_block(&chain).map_err(|e| { - warn!(log, "Not publishing block, not gossip verified"; "slot" => beacon_block.slot(), "error" => ?e); - warp_utils::reject::custom_bad_request(e.to_string()) - })?; + let gossip_verified_block = match block.into_gossip_verified_block(&chain) { + Ok(b) => b, + Err(BlockError::BlockIsAlreadyKnown) => { + // Allow the status code for duplicate blocks to be overridden based on config. + return Ok(warp::reply::with_status( + warp::reply::json(&ErrorMessage { + code: duplicate_status_code.as_u16(), + message: "duplicate block".to_string(), + stacktraces: vec![], + }), + duplicate_status_code, + ) + .into_response()); + } + Err(e) => { + warn!( + log, + "Not publishing block - not gossip verified"; + "slot" => beacon_block.slot(), + "error" => ?e + ); + return Err(warp_utils::reject::custom_bad_request(e.to_string())); + } + }; let block_root = block_root.unwrap_or(gossip_verified_block.block_root); @@ -167,8 +189,7 @@ pub async fn publish_block>( &log, ) } - - Ok(()) + Ok(warp::reply().into_response()) } Err(BlockError::BeaconChainError(BeaconChainError::UnableToPublish)) => { Err(warp_utils::reject::custom_server_error( @@ -178,10 +199,6 @@ pub async fn publish_block>( Err(BlockError::Slashable) => Err(warp_utils::reject::custom_bad_request( "proposal for this slot and proposer has already been seen".to_string(), )), - Err(BlockError::BlockIsAlreadyKnown) => { - info!(log, "Block from HTTP API already known"; "block" => ?block_root); - Ok(()) - } Err(e) => { if let BroadcastValidation::Gossip = validation_level { Err(warp_utils::reject::broadcast_without_import(format!("{e}"))) @@ -208,7 +225,8 @@ pub async fn publish_blinded_block( network_tx: &UnboundedSender>, log: Logger, validation_level: BroadcastValidation, -) -> Result<(), Rejection> { + duplicate_status_code: StatusCode, +) -> Result { let block_root = block.canonical_root(); let full_block: ProvenancedBlock>> = reconstruct_block(chain.clone(), block_root, block, log.clone()).await?; @@ -219,6 +237,7 @@ pub async fn publish_blinded_block( network_tx, log, validation_level, + duplicate_status_code, ) .await } diff --git a/beacon_node/http_api/src/task_spawner.rs b/beacon_node/http_api/src/task_spawner.rs index 503faff717b..8768e057dac 100644 --- a/beacon_node/http_api/src/task_spawner.rs +++ b/beacon_node/http_api/src/task_spawner.rs @@ -159,46 +159,6 @@ impl TaskSpawner { .and_then(|x| x) } } - - /// Executes an async task which always returns a `Response`. - pub async fn spawn_async( - self, - priority: Priority, - func: impl Future + Send + Sync + 'static, - ) -> Response { - if let Some(beacon_processor_send) = &self.beacon_processor_send { - // Create a wrapper future that will execute `func` and send the - // result to a channel held by this thread. - let (tx, rx) = oneshot::channel(); - let process_fn = async move { - // Await the future, collect the return value. - let func_result = func.await; - // Send the result down the channel. Ignore any failures; the - // send can only fail if the receiver is dropped. - let _ = tx.send(func_result); - }; - - // Send the function to the beacon processor for execution at some arbitrary time. - let result = send_to_beacon_processor( - beacon_processor_send, - priority, - BlockingOrAsync::Async(Box::pin(process_fn)), - rx, - ) - .await; - convert_rejection(result).await - } else { - // There is no beacon processor so spawn a task directly on the - // tokio executor. - tokio::task::spawn(func).await.unwrap_or_else(|e| { - warp::reply::with_status( - warp::reply::json(&format!("Tokio did not execute task: {e:?}")), - eth2::StatusCode::INTERNAL_SERVER_ERROR, - ) - .into_response() - }) - } - } } /// Send a task to the beacon processor and await execution. diff --git a/beacon_node/http_api/src/test_utils.rs b/beacon_node/http_api/src/test_utils.rs index b1478f3f290..33834d58ca0 100644 --- a/beacon_node/http_api/src/test_utils.rs +++ b/beacon_node/http_api/src/test_utils.rs @@ -23,7 +23,7 @@ use network::{NetworkReceivers, NetworkSenders}; use sensitive_url::SensitiveUrl; use slog::Logger; use std::future::Future; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::net::SocketAddr; use std::sync::Arc; use std::time::Duration; use store::MemoryStore; @@ -220,15 +220,9 @@ pub async fn create_api_server_on_port( let ctx = Arc::new(Context { config: Config { enabled: true, - listen_addr: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), listen_port: port, - allow_origin: None, - tls_config: None, - allow_sync_stalled: false, data_dir: std::path::PathBuf::from(DEFAULT_ROOT_DIR), - spec_fork_name: None, - sse_capacity_multiplier: 1, - enable_beacon_processor: true, + ..Config::default() }, chain: Some(chain), network_senders: Some(network_senders), diff --git a/beacon_node/http_api/tests/broadcast_validation_tests.rs b/beacon_node/http_api/tests/broadcast_validation_tests.rs index 00825890009..96ff37d81af 100644 --- a/beacon_node/http_api/tests/broadcast_validation_tests.rs +++ b/beacon_node/http_api/tests/broadcast_validation_tests.rs @@ -364,13 +364,14 @@ pub async fn consensus_partial_pass_only_consensus() { /* submit `block_b` which should induce equivocation */ let channel = tokio::sync::mpsc::unbounded_channel(); - let publication_result: Result<(), Rejection> = publish_block( + let publication_result = publish_block( None, ProvenancedBlock::local(gossip_block_b.unwrap()), tester.harness.chain.clone(), &channel.0, test_logger, validation_level.unwrap(), + StatusCode::ACCEPTED, ) .await; @@ -641,13 +642,14 @@ pub async fn equivocation_consensus_late_equivocation() { let channel = tokio::sync::mpsc::unbounded_channel(); - let publication_result: Result<(), Rejection> = publish_block( + let publication_result = publish_block( None, ProvenancedBlock::local(gossip_block_b.unwrap()), tester.harness.chain, &channel.0, test_logger, validation_level.unwrap(), + StatusCode::ACCEPTED, ) .await; @@ -1294,12 +1296,13 @@ pub async fn blinded_equivocation_consensus_late_equivocation() { let channel = tokio::sync::mpsc::unbounded_channel(); - let publication_result: Result<(), Rejection> = publish_blinded_block( + let publication_result = publish_blinded_block( block_b, tester.harness.chain, &channel.0, test_logger, validation_level.unwrap(), + StatusCode::ACCEPTED, ) .await; diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 46cc55591c7..adaf1a0f2d4 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -8,7 +8,7 @@ use eth2::{ mixin::{RequestAccept, ResponseForkName, ResponseOptional}, reqwest::RequestBuilder, types::{BlockId as CoreBlockId, ForkChoiceNode, StateId as CoreStateId, *}, - BeaconNodeHttpClient, Error, Timeouts, + BeaconNodeHttpClient, Error, StatusCode, Timeouts, }; use execution_layer::test_utils::TestingBuilder; use execution_layer::test_utils::DEFAULT_BUILDER_THRESHOLD_WEI; @@ -1318,6 +1318,63 @@ impl ApiTester { self } + pub async fn test_post_beacon_blocks_duplicate(self) -> Self { + let block = self + .harness + .make_block( + self.harness.get_current_state(), + self.harness.get_current_slot(), + ) + .await + .0; + + assert!(self.client.post_beacon_blocks(&block).await.is_ok()); + + let blinded_block = block.clone_as_blinded(); + + // Test all the POST methods in sequence, they should all behave the same. + let responses = vec![ + self.client.post_beacon_blocks(&block).await.unwrap_err(), + self.client + .post_beacon_blocks_v2(&block, None) + .await + .unwrap_err(), + self.client + .post_beacon_blocks_ssz(&block) + .await + .unwrap_err(), + self.client + .post_beacon_blocks_v2_ssz(&block, None) + .await + .unwrap_err(), + self.client + .post_beacon_blinded_blocks(&blinded_block) + .await + .unwrap_err(), + self.client + .post_beacon_blinded_blocks_v2(&blinded_block, None) + .await + .unwrap_err(), + self.client + .post_beacon_blinded_blocks_ssz(&blinded_block) + .await + .unwrap_err(), + self.client + .post_beacon_blinded_blocks_v2_ssz(&blinded_block, None) + .await + .unwrap_err(), + ]; + for (i, response) in responses.into_iter().enumerate() { + assert_eq!( + response.status().unwrap(), + StatusCode::ACCEPTED, + "response {i}" + ); + } + + self + } + pub async fn test_beacon_blocks(self) -> Self { for block_id in self.interesting_block_ids() { let expected = block_id @@ -4651,6 +4708,14 @@ async fn post_beacon_blocks_invalid() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn post_beacon_blocks_duplicate() { + ApiTester::new() + .await + .test_post_beacon_blocks_duplicate() + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn beacon_pools_post_attestations_valid() { ApiTester::new() diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index 974dabbf0cf..ed25748133f 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -391,6 +391,15 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .help("Multiplier to apply to the length of HTTP server-sent-event (SSE) channels. \ Increasing this value can prevent messages from being dropped.") ) + .arg( + Arg::with_name("http-duplicate-block-status") + .long("http-duplicate-block-status") + .takes_value(true) + .default_value("202") + .value_name("STATUS_CODE") + .help("Status code to send when a block that is already known is POSTed to the \ + HTTP API.") + ) .arg( Arg::with_name("http-enable-beacon-processor") .long("http-enable-beacon-processor") diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index d2e92b48f33..b03a8cb3e5f 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -155,6 +155,9 @@ pub fn get_config( client_config.http_api.enable_beacon_processor = parse_required(cli_args, "http-enable-beacon-processor")?; + client_config.http_api.duplicate_block_status_code = + parse_required(cli_args, "http-duplicate-block-status")?; + if let Some(cache_size) = clap_utils::parse_optional(cli_args, "shuffling-cache-size")? { client_config.chain.shuffling_cache_size = cache_size; } diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 28fd09c09bc..822f8817991 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -1316,6 +1316,26 @@ pub struct BroadcastValidationQuery { pub broadcast_validation: BroadcastValidation, } +pub mod serde_status_code { + use crate::StatusCode; + use serde::{de::Error, Deserialize, Serialize}; + + pub fn serialize(status_code: &StatusCode, ser: S) -> Result + where + S: serde::Serializer, + { + status_code.as_u16().serialize(ser) + } + + pub fn deserialize<'de, D>(de: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + let status_code = u16::deserialize(de)?; + StatusCode::try_from(status_code).map_err(D::Error::custom) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 626be85f197..5069b0261f6 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2387,3 +2387,22 @@ fn http_sse_capacity_multiplier_override() { .run_with_zero_port() .with_config(|config| assert_eq!(config.http_api.sse_capacity_multiplier, 10)); } + +#[test] +fn http_duplicate_block_status_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.http_api.duplicate_block_status_code.as_u16(), 202) + }); +} + +#[test] +fn http_duplicate_block_status_override() { + CommandLineTest::new() + .flag("http-duplicate-block-status", Some("301")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.http_api.duplicate_block_status_code.as_u16(), 301) + }); +} diff --git a/validator_client/src/block_service.rs b/validator_client/src/block_service.rs index 2a09455b6ff..094b85bf810 100644 --- a/validator_client/src/block_service.rs +++ b/validator_client/src/block_service.rs @@ -10,7 +10,8 @@ use crate::{ validator_store::{Error as ValidatorStoreError, ValidatorStore}, }; use environment::RuntimeContext; -use eth2::BeaconNodeHttpClient; +use eth2::{BeaconNodeHttpClient, StatusCode}; +use slog::Logger; use slog::{crit, debug, error, info, trace, warn}; use slot_clock::SlotClock; use std::fmt::Debug; @@ -593,12 +594,7 @@ impl BlockService { beacon_node .post_beacon_blocks(&signed_block) .await - .map_err(|e| { - BlockError::Irrecoverable(format!( - "Error from beacon node when publishing block: {:?}", - e - )) - })? + .or_else(|e| handle_block_post_error(e, slot, log))? } BlockType::Blinded => { let _post_timer = metrics::start_timer_vec( @@ -608,12 +604,7 @@ impl BlockService { beacon_node .post_beacon_blinded_blocks(&signed_block) .await - .map_err(|e| { - BlockError::Irrecoverable(format!( - "Error from beacon node when publishing block: {:?}", - e - )) - })? + .or_else(|e| handle_block_post_error(e, slot, log))? } } Ok::<_, BlockError>(()) @@ -634,3 +625,29 @@ impl BlockService { Ok(()) } } + +fn handle_block_post_error(err: eth2::Error, slot: Slot, log: &Logger) -> Result<(), BlockError> { + // Handle non-200 success codes. + if let Some(status) = err.status() { + if status == StatusCode::ACCEPTED { + info!( + log, + "Block is already known to BN or might be invalid"; + "slot" => slot, + "status_code" => status.as_u16(), + ); + return Ok(()); + } else if status.is_success() { + debug!( + log, + "Block published with non-standard success code"; + "slot" => slot, + "status_code" => status.as_u16(), + ); + return Ok(()); + } + } + Err(BlockError::Irrecoverable(format!( + "Error from beacon node when publishing block: {err:?}", + ))) +} From 9c24cd4ad4ab6eaa7d5725d4a300c271a1a2c25e Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Mon, 28 Aug 2023 00:55:32 +0000 Subject: [PATCH 17/25] Do not log slot clock error prior to genesis (#4657) ## Issue Addressed #4654 ## Proposed Changes Only log error if we're unable to read slot clock after genesis. I thought about simply down grading the `error` to a `warn`, but feel like it's still unnecessary noise before genesis, and it would be good to retain error log if we're pass genesis. But I'd be ok with just downgrading the log level, too. --- .../src/subnet_service/attestation_subnets.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/beacon_node/network/src/subnet_service/attestation_subnets.rs b/beacon_node/network/src/subnet_service/attestation_subnets.rs index b4f52df39d3..1cae6299e1c 100644 --- a/beacon_node/network/src/subnet_service/attestation_subnets.rs +++ b/beacon_node/network/src/subnet_service/attestation_subnets.rs @@ -302,9 +302,16 @@ impl AttestationService { /// Gets the long lived subnets the node should be subscribed to during the current epoch and /// the remaining duration for which they remain valid. fn recompute_long_lived_subnets_inner(&mut self) -> Result { - let current_epoch = self.beacon_chain.epoch().map_err( - |e| error!(self.log, "Failed to get the current epoch from clock"; "err" => ?e), - )?; + let current_epoch = self.beacon_chain.epoch().map_err(|e| { + if !self + .beacon_chain + .slot_clock + .is_prior_to_genesis() + .unwrap_or(false) + { + error!(self.log, "Failed to get the current epoch from clock"; "err" => ?e) + } + })?; let (subnets, next_subscription_epoch) = SubnetId::compute_subnets_for_epoch::( self.node_id.raw().into(), From 55e02e7c3f92e46900b6c7df6e0eaab971718027 Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 28 Aug 2023 00:55:33 +0000 Subject: [PATCH 18/25] Show `--gui` flag in help text (#4660) ## Issue Addressed N/A ## Proposed Changes Remove the `hidden(true)` modifier on the `--gui` flag so it shows up when running `lighthouse bn --help` ## Additional Info We need to include this now that Siren has had its first stable release. --- beacon_node/src/cli.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/beacon_node/src/cli.rs b/beacon_node/src/cli.rs index ed25748133f..837625e12a7 100644 --- a/beacon_node/src/cli.rs +++ b/beacon_node/src/cli.rs @@ -1134,7 +1134,6 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> { .arg( Arg::with_name("gui") .long("gui") - .hidden(true) .help("Enable the graphical user interface and all its requirements. \ This enables --http and --validator-monitor-auto and enables SSE logging.") .takes_value(false) From e056c279aa7f476baeaad6690ac86001d42af597 Mon Sep 17 00:00:00 2001 From: Mac L Date: Mon, 28 Aug 2023 00:55:34 +0000 Subject: [PATCH 19/25] Increase `web3signer_tests` timeouts (#4662) ## Issue Addressed `web3signer_tests` can sometimes timeout. ## Proposed Changes Increase the `web3signer_tests` timeout from 20s to 30s ## Additional Info Previously I believed the consistent CI failures were due to this, but it ended up being something different. See below: --- The timing of this makes it very likely it is related to the [latest release of `web3-signer`](https://github.com/Consensys/web3signer/releases/tag/23.8.1). I now believe this is due to an out of date Java runtime on our runners. A newer version of Java became a requirement with the new `web3-signer` release. However, I was getting timeouts locally, which implies that the margin before timeout is quite small at 20s so bumping it up to 30s could be a good idea regardless. --- testing/web3signer_tests/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/web3signer_tests/src/lib.rs b/testing/web3signer_tests/src/lib.rs index dd17ae23b15..463de0c8b3a 100644 --- a/testing/web3signer_tests/src/lib.rs +++ b/testing/web3signer_tests/src/lib.rs @@ -51,7 +51,7 @@ mod tests { /// If the we are unable to reach the Web3Signer HTTP API within this time out then we will /// assume it failed to start. - const UPCHECK_TIMEOUT: Duration = Duration::from_secs(20); + const UPCHECK_TIMEOUT: Duration = Duration::from_secs(30); /// Set to `false` to send the Web3Signer logs to the console during tests. Logs are useful when /// debugging. From d61f5071849723f56c4cdd02e8a4f0c6100d1543 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 28 Aug 2023 05:34:27 +0000 Subject: [PATCH 20/25] Add Holesky (#4653) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Issue Addressed NA ## Proposed Changes Add the Holesky network config as per https://github.com/eth-clients/holesky/tree/36e4ff2d5138dcb2eb614f0f60fdb060b2adc1e2/custom_config_data. Since the genesis state is ~190MB, I've opted to *not* include it in the binary and instead download it at runtime (see #4564 for context). To download this file we have: - A hard-coded URL for a SigP-hosted S3 bucket with the Holesky genesis state. Assuming this download works correctly, users will be none the wiser that the state wasn't included in the binary (apart from some additional logs) - If the user provides a `--checkpoint-sync-url` flag, then LH will download the genesis state from that server rather than our S3 bucket. - If the user provides a `--genesis-state-url` flag, then LH will download the genesis state from that server regardless of the S3 bucket or `--checkpoint-sync-url` flag. - Whenever a genesis state is downloaded it is checked against a checksum baked into the binary. - A genesis state will never be downloaded if it's already included in the binary. - There is a `--genesis-state-url-timeout` flag to tweak the timeout for downloading the genesis state file. ## Log Output Example of log output when a state is downloaded: ```bash Aug 23 05:40:13.424 INFO Logging to file path: "/Users/paul/.lighthouse/holesky/beacon/logs/beacon.log" Aug 23 05:40:13.425 INFO Lighthouse started version: Lighthouse/v4.3.0-bd9931f+ Aug 23 05:40:13.425 INFO Configured for network name: holesky Aug 23 05:40:13.426 INFO Data directory initialised datadir: /Users/paul/.lighthouse/holesky Aug 23 05:40:13.427 INFO Deposit contract address: 0x4242424242424242424242424242424242424242, deploy_block: 0 Aug 23 05:40:13.427 INFO Downloading genesis state info: this may take some time on testnets with large validator counts, timeout: 60s, server: https://sigp-public-genesis-states.s3.ap-southeast-2.amazonaws.com/ Aug 23 05:40:29.895 INFO Starting from known genesis state service: beacon ``` Example of log output when there are no URLs specified: ``` Aug 23 06:29:51.645 INFO Logging to file path: "/Users/paul/.lighthouse/goerli/beacon/logs/beacon.log" Aug 23 06:29:51.646 INFO Lighthouse started version: Lighthouse/v4.3.0-666a39c+ Aug 23 06:29:51.646 INFO Configured for network name: goerli Aug 23 06:29:51.647 INFO Data directory initialised datadir: /Users/paul/.lighthouse/goerli Aug 23 06:29:51.647 INFO Deposit contract address: 0xff50ed3d0ec03ac01d4c79aad74928bff48a7b2b, deploy_block: 4367322 The genesis state is not present in the binary and there are no known download URLs. Please use --checkpoint-sync-url or --genesis-state-url. ``` ## Additional Info I tested the `--genesis-state-url` flag with all 9 Goerli checkpoint sync servers on https://eth-clients.github.io/checkpoint-sync-endpoints/ and they all worked 🎉 My IDE eagerly formatted some `Cargo.toml`. I've disabled it but I don't see the value in spending time reverting the changes that are already there. I also added the `GenesisStateBytes` enum to avoid an unnecessary clone on the genesis state bytes baked into the binary. This is not a huge deal on Mainnet, but will become more relevant when testing with big genesis states. When we do a fresh checkpoint sync we're downloading the genesis state to check the `genesis_validators_root` against the finalised state we receive. This is not *entirely* pointless, since we verify the checksum when we download the genesis state so we are actually guaranteeing that the finalised state is on the same network. There might be a smarter/less-download-y way to go about this, but I've run out of cycles to figure that out. Perhaps we can grab it in the next release? --- Cargo.lock | 8 + account_manager/Cargo.toml | 10 +- account_manager/src/validator/exit.rs | 19 +- .../src/validator/slashing_protection.rs | 24 +- beacon_node/Cargo.toml | 18 +- beacon_node/client/src/builder.rs | 41 ++- beacon_node/client/src/config.rs | 15 +- beacon_node/src/config.rs | 40 ++- boot_node/src/config.rs | 16 +- common/eth2_config/src/lib.rs | 90 ++++-- common/eth2_network_config/Cargo.toml | 13 +- common/eth2_network_config/build.rs | 9 +- .../holesky/boot_enr.yaml | 8 + .../holesky/config.yaml | 117 ++++++++ .../holesky/deploy_block.txt | 1 + common/eth2_network_config/src/lib.rs | 275 ++++++++++++++++-- common/pretty_reqwest_error/src/lib.rs | 6 + lcli/src/eth1_genesis.rs | 2 +- lcli/src/interop_genesis.rs | 2 +- lcli/src/new_testnet.rs | 5 +- lighthouse/src/main.rs | 24 ++ lighthouse/tests/beacon_node.rs | 25 ++ 22 files changed, 651 insertions(+), 117 deletions(-) create mode 100644 common/eth2_network_config/built_in_network_configs/holesky/boot_enr.yaml create mode 100644 common/eth2_network_config/built_in_network_configs/holesky/config.yaml create mode 100644 common/eth2_network_config/built_in_network_configs/holesky/deploy_block.txt diff --git a/Cargo.lock b/Cargo.lock index 56d421e0390..75c15ba7813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "serde", "serde_json", "slashing_protection", + "slog", "slot_clock", "tempfile", "tokio", @@ -2223,9 +2224,16 @@ dependencies = [ "discv5", "eth2_config", "ethereum_ssz", + "logging", + "pretty_reqwest_error", + "reqwest", + "sensitive_url", "serde_yaml", + "sha2 0.10.7", + "slog", "tempfile", "types", + "url", "zip", ] diff --git a/account_manager/Cargo.toml b/account_manager/Cargo.toml index 7d90cbb427d..238e4a77e08 100644 --- a/account_manager/Cargo.toml +++ b/account_manager/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "account_manager" version = "0.3.5" -authors = ["Paul Hauner ", "Luke Anderson "] +authors = [ + "Paul Hauner ", + "Luke Anderson ", +] edition = "2021" [dependencies] @@ -19,13 +22,14 @@ tokio = { version = "1.14.0", features = ["full"] } eth2_keystore = { path = "../crypto/eth2_keystore" } account_utils = { path = "../common/account_utils" } slashing_protection = { path = "../validator_client/slashing_protection" } -eth2 = {path = "../common/eth2"} -safe_arith = {path = "../consensus/safe_arith"} +eth2 = { path = "../common/eth2" } +safe_arith = { path = "../consensus/safe_arith" } slot_clock = { path = "../common/slot_clock" } filesystem = { path = "../common/filesystem" } sensitive_url = { path = "../common/sensitive_url" } serde = { version = "1.0.116", features = ["derive"] } serde_json = "1.0.58" +slog = { version = "2.5.2" } [dev-dependencies] tempfile = "3.1.0" diff --git a/account_manager/src/validator/exit.rs b/account_manager/src/validator/exit.rs index 5755a355f31..1ff61a7c01d 100644 --- a/account_manager/src/validator/exit.rs +++ b/account_manager/src/validator/exit.rs @@ -10,6 +10,7 @@ use eth2_keystore::Keystore; use eth2_network_config::Eth2NetworkConfig; use safe_arith::SafeArith; use sensitive_url::SensitiveUrl; +use slog::Logger; use slot_clock::{SlotClock, SystemTimeSlotClock}; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -78,6 +79,12 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< let password_file_path: Option = clap_utils::parse_optional(matches, PASSWORD_FILE_FLAG)?; + let genesis_state_url: Option = + clap_utils::parse_optional(matches, "genesis-state-url")?; + let genesis_state_url_timeout = + clap_utils::parse_required(matches, "genesis-state-url-timeout") + .map(Duration::from_secs)?; + let stdin_inputs = cfg!(windows) || matches.is_present(STDIN_INPUTS_FLAG); let no_wait = matches.is_present(NO_WAIT); let no_confirmation = matches.is_present(NO_CONFIRMATION); @@ -104,6 +111,9 @@ pub fn cli_run(matches: &ArgMatches, env: Environment) -> Result< ð2_network_config, no_wait, no_confirmation, + genesis_state_url, + genesis_state_url_timeout, + env.core_context().log(), ))?; Ok(()) @@ -120,13 +130,14 @@ async fn publish_voluntary_exit( eth2_network_config: &Eth2NetworkConfig, no_wait: bool, no_confirmation: bool, + genesis_state_url: Option, + genesis_state_url_timeout: Duration, + log: &Logger, ) -> Result<(), String> { let genesis_data = get_geneisis_data(client).await?; let testnet_genesis_root = eth2_network_config - .beacon_state::() - .as_ref() - .expect("network should have valid genesis state") - .genesis_validators_root(); + .genesis_validators_root::(genesis_state_url.as_deref(), genesis_state_url_timeout, log)? + .ok_or("Genesis state is unknown")?; // Verify that the beacon node and validator being exited are on the same network. if genesis_data.genesis_validators_root != testnet_genesis_root { diff --git a/account_manager/src/validator/slashing_protection.rs b/account_manager/src/validator/slashing_protection.rs index f25bbd8159f..570f29b4ad6 100644 --- a/account_manager/src/validator/slashing_protection.rs +++ b/account_manager/src/validator/slashing_protection.rs @@ -7,7 +7,8 @@ use slashing_protection::{ use std::fs::File; use std::path::PathBuf; use std::str::FromStr; -use types::{BeaconState, Epoch, EthSpec, PublicKeyBytes, Slot}; +use std::time::Duration; +use types::{Epoch, EthSpec, PublicKeyBytes, Slot}; pub const CMD: &str = "slashing-protection"; pub const IMPORT_CMD: &str = "import"; @@ -82,19 +83,24 @@ pub fn cli_run( ) -> Result<(), String> { let slashing_protection_db_path = validator_base_dir.join(SLASHING_PROTECTION_FILENAME); + let genesis_state_url: Option = + clap_utils::parse_optional(matches, "genesis-state-url")?; + let genesis_state_url_timeout = + clap_utils::parse_required(matches, "genesis-state-url-timeout") + .map(Duration::from_secs)?; + + let context = env.core_context(); let eth2_network_config = env .eth2_network_config .ok_or("Unable to get testnet configuration from the environment")?; let genesis_validators_root = eth2_network_config - .beacon_state::() - .map(|state: BeaconState| state.genesis_validators_root()) - .map_err(|e| { - format!( - "Unable to get genesis state, has genesis occurred? Detail: {:?}", - e - ) - })?; + .genesis_validators_root::( + genesis_state_url.as_deref(), + genesis_state_url_timeout, + context.log(), + )? + .ok_or_else(|| "Unable to get genesis state, has genesis occurred?".to_string())?; match matches.subcommand() { (IMPORT_CMD, Some(matches)) => { diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index f55c724dc39..a0e46705b74 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -1,7 +1,10 @@ [package] name = "beacon_node" version = "4.3.0" -authors = ["Paul Hauner ", "Age Manning ", + "Age Manning { + ClientGenesis::GenesisState => { info!( context.log(), "Starting from known genesis state"; ); - let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec) - .map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?; + let genesis_state = genesis_state(&runtime_context, &config, log)?; builder.genesis_state(genesis_state).map(|v| (v, None))? } ClientGenesis::WeakSubjSszBytes { anchor_state_bytes, anchor_block_bytes, - genesis_state_bytes, } => { info!(context.log(), "Starting checkpoint sync"); if config.chain.genesis_backfill { @@ -279,17 +276,13 @@ where .map_err(|e| format!("Unable to parse weak subj state SSZ: {:?}", e))?; let anchor_block = SignedBeaconBlock::from_ssz_bytes(&anchor_block_bytes, &spec) .map_err(|e| format!("Unable to parse weak subj block SSZ: {:?}", e))?; - let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec) - .map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?; + let genesis_state = genesis_state(&runtime_context, &config, log)?; builder .weak_subjectivity_state(anchor_state, anchor_block, genesis_state) .map(|v| (v, None))? } - ClientGenesis::CheckpointSyncUrl { - genesis_state_bytes, - url, - } => { + ClientGenesis::CheckpointSyncUrl { url } => { info!( context.log(), "Starting checkpoint sync"; @@ -384,8 +377,7 @@ where debug!(context.log(), "Downloaded finalized block"); - let genesis_state = BeaconState::from_ssz_bytes(&genesis_state_bytes, &spec) - .map_err(|e| format!("Unable to parse genesis state SSZ: {:?}", e))?; + let genesis_state = genesis_state(&runtime_context, &config, log)?; info!( context.log(), @@ -1089,3 +1081,22 @@ where Ok(self) } } + +/// Obtain the genesis state from the `eth2_network_config` in `context`. +fn genesis_state( + context: &RuntimeContext, + config: &ClientConfig, + log: &Logger, +) -> Result, String> { + let eth2_network_config = context + .eth2_network_config + .as_ref() + .ok_or("An eth2_network_config is required to obtain the genesis state")?; + eth2_network_config + .genesis_state::( + config.genesis_state_url.as_deref(), + config.genesis_state_url_timeout, + log, + )? + .ok_or_else(|| "Genesis state is unknown".to_string()) +} diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs index 283efe9c3fc..adaf0279847 100644 --- a/beacon_node/client/src/config.rs +++ b/beacon_node/client/src/config.rs @@ -7,6 +7,7 @@ use sensitive_url::SensitiveUrl; use serde_derive::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; +use std::time::Duration; use types::{Graffiti, PublicKeyBytes}; /// Default directory name for the freezer database under the top-level data dir. const DEFAULT_FREEZER_DB_DIR: &str = "freezer_db"; @@ -25,18 +26,13 @@ pub enum ClientGenesis { /// contract. #[default] DepositContract, - /// Loads the genesis state from SSZ-encoded `BeaconState` bytes. - /// - /// We include the bytes instead of the `BeaconState` because the `EthSpec` type - /// parameter would be very annoying. - SszBytes { genesis_state_bytes: Vec }, + /// Loads the genesis state from the genesis state in the `Eth2NetworkConfig`. + GenesisState, WeakSubjSszBytes { - genesis_state_bytes: Vec, anchor_state_bytes: Vec, anchor_block_bytes: Vec, }, CheckpointSyncUrl { - genesis_state_bytes: Vec, url: SensitiveUrl, }, } @@ -81,6 +77,8 @@ pub struct Config { pub slasher: Option, pub logger_config: LoggerConfig, pub beacon_processor: BeaconProcessorConfig, + pub genesis_state_url: Option, + pub genesis_state_url_timeout: Duration, } impl Default for Config { @@ -108,6 +106,9 @@ impl Default for Config { validator_monitor_individual_tracking_threshold: DEFAULT_INDIVIDUAL_TRACKING_THRESHOLD, logger_config: LoggerConfig::default(), beacon_processor: <_>::default(), + genesis_state_url: <_>::default(), + // This default value should always be overwritten by the CLI default value. + genesis_state_url_timeout: Duration::from_secs(60), } } } diff --git a/beacon_node/src/config.rs b/beacon_node/src/config.rs index b03a8cb3e5f..70495777e28 100644 --- a/beacon_node/src/config.rs +++ b/beacon_node/src/config.rs @@ -471,9 +471,30 @@ pub fn get_config( client_config.chain.checkpoint_sync_url_timeout = clap_utils::parse_required::(cli_args, "checkpoint-sync-url-timeout")?; - client_config.genesis = if let Some(genesis_state_bytes) = - eth2_network_config.genesis_state_bytes.clone() - { + client_config.genesis_state_url_timeout = + clap_utils::parse_required(cli_args, "genesis-state-url-timeout") + .map(Duration::from_secs)?; + + let genesis_state_url_opt = + clap_utils::parse_optional::(cli_args, "genesis-state-url")?; + let checkpoint_sync_url_opt = + clap_utils::parse_optional::(cli_args, "checkpoint-sync-url")?; + + // If the `--genesis-state-url` is defined, use that to download the + // genesis state bytes. If it's not defined, try `--checkpoint-sync-url`. + client_config.genesis_state_url = if let Some(genesis_state_url) = genesis_state_url_opt { + Some(genesis_state_url) + } else if let Some(checkpoint_sync_url) = checkpoint_sync_url_opt { + // If the checkpoint sync URL is going to be used to download the + // genesis state, adopt the timeout from the checkpoint sync URL too. + client_config.genesis_state_url_timeout = + Duration::from_secs(client_config.chain.checkpoint_sync_url_timeout); + Some(checkpoint_sync_url) + } else { + None + }; + + client_config.genesis = if eth2_network_config.genesis_state_is_known() { // Set up weak subjectivity sync, or start from the hardcoded genesis state. if let (Some(initial_state_path), Some(initial_block_path)) = ( cli_args.value_of("checkpoint-state"), @@ -495,7 +516,6 @@ pub fn get_config( let anchor_block_bytes = read(initial_block_path)?; ClientGenesis::WeakSubjSszBytes { - genesis_state_bytes, anchor_state_bytes, anchor_block_bytes, } @@ -503,17 +523,9 @@ pub fn get_config( let url = SensitiveUrl::parse(remote_bn_url) .map_err(|e| format!("Invalid checkpoint sync URL: {:?}", e))?; - ClientGenesis::CheckpointSyncUrl { - genesis_state_bytes, - url, - } + ClientGenesis::CheckpointSyncUrl { url } } else { - // Note: re-serializing the genesis state is not so efficient, however it avoids adding - // trait bounds to the `ClientGenesis` enum. This would have significant flow-on - // effects. - ClientGenesis::SszBytes { - genesis_state_bytes, - } + ClientGenesis::GenesisState } } else { if cli_args.is_present("checkpoint-state") || cli_args.is_present("checkpoint-sync-url") { diff --git a/boot_node/src/config.rs b/boot_node/src/config.rs index d006156bf9d..779269921a5 100644 --- a/boot_node/src/config.rs +++ b/boot_node/src/config.rs @@ -10,6 +10,7 @@ use lighthouse_network::{ use serde_derive::{Deserialize, Serialize}; use ssz::Encode; use std::net::{SocketAddrV4, SocketAddrV6}; +use std::time::Duration; use std::{marker::PhantomData, path::PathBuf}; use types::EthSpec; @@ -90,8 +91,19 @@ impl BootNodeConfig { let enr_fork = { let spec = eth2_network_config.chain_spec::()?; - if eth2_network_config.beacon_state_is_known() { - let genesis_state = eth2_network_config.beacon_state::()?; + let genesis_state_url: Option = + clap_utils::parse_optional(matches, "genesis-state-url")?; + let genesis_state_url_timeout = + clap_utils::parse_required(matches, "genesis-state-url-timeout") + .map(Duration::from_secs)?; + + if eth2_network_config.genesis_state_is_known() { + let genesis_state = eth2_network_config + .genesis_state::(genesis_state_url.as_deref(), genesis_state_url_timeout, &logger)? + .ok_or_else(|| { + "The genesis state for this network is not known, this is an unsupported mode" + .to_string() + })?; slog::info!(logger, "Genesis state found"; "root" => genesis_state.canonical_root().to_string()); let enr_fork = spec.enr_fork_id::( diff --git a/common/eth2_config/src/lib.rs b/common/eth2_config/src/lib.rs index 7e5506667ff..bb14d067569 100644 --- a/common/eth2_config/src/lib.rs +++ b/common/eth2_config/src/lib.rs @@ -23,6 +23,16 @@ pub const PREDEFINED_NETWORKS_DIR: &str = predefined_networks_dir!(); pub const GENESIS_FILE_NAME: &str = "genesis.ssz"; pub const GENESIS_ZIP_FILE_NAME: &str = "genesis.ssz.zip"; +const HOLESKY_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url { + urls: &[ + // This is an AWS S3 bucket hosted by Sigma Prime. See Paul Hauner for + // more details. + "https://sigp-public-genesis-states.s3.ap-southeast-2.amazonaws.com/holesky/", + ], + checksum: "0x76631cd0b9ddc5b2c766b496e23f16759ce1181446a4efb40e5540cd15b78a07", + genesis_validators_root: "0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1", +}; + /// The core configuration of a Lighthouse beacon node. #[derive(Debug, Clone)] pub struct Eth2Config { @@ -62,6 +72,32 @@ impl Eth2Config { } } +/// Describes how a genesis state may be obtained. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum GenesisStateSource { + /// The genesis state for this network is not yet known. + Unknown, + /// The genesis state for this network is included in the binary via + /// `include_bytes!` or by loading from a testnet dir. + IncludedBytes, + /// The genesis state for this network should be downloaded from a URL. + Url { + /// URLs to try to download the file from, in order. + urls: &'static [&'static str], + /// The SHA256 of the genesis state bytes. This is *not* a hash tree + /// root to simplify the types (i.e., to avoid getting EthSpec + /// involved). + /// + /// The format should be 0x-prefixed ASCII bytes. + checksum: &'static str, + /// The `genesis_validators_root` of the genesis state. Used to avoid + /// downloading the state for simple signing operations. + /// + /// The format should be 0x-prefixed ASCII bytes. + genesis_validators_root: &'static str, + }, +} + /// A directory that can be built by downloading files via HTTP. /// /// Used by the `eth2_network_config` crate to initialize the network directories during build and @@ -70,7 +106,7 @@ impl Eth2Config { pub struct Eth2NetArchiveAndDirectory<'a> { pub name: &'a str, pub config_dir: &'a str, - pub genesis_is_known: bool, + pub genesis_state_source: GenesisStateSource, } impl<'a> Eth2NetArchiveAndDirectory<'a> { @@ -89,15 +125,11 @@ impl<'a> Eth2NetArchiveAndDirectory<'a> { } } -/// Indicates that the `genesis.ssz.zip` file is present on the filesystem. This means that the -/// deposit ceremony has concluded and the final genesis `BeaconState` is known. -const GENESIS_STATE_IS_KNOWN: bool = true; - -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct HardcodedNet { pub name: &'static str, pub config_dir: &'static str, - pub genesis_is_known: bool, + pub genesis_state_source: GenesisStateSource, pub config: &'static [u8], pub deploy_block: &'static [u8], pub boot_enr: &'static [u8], @@ -109,7 +141,7 @@ pub struct HardcodedNet { /// It also defines a `include__file!` macro which provides a wrapper around /// `std::include_bytes`, allowing the inclusion of bytes from the specific testnet directory. macro_rules! define_archive { - ($name_ident: ident, $config_dir: tt, $genesis_is_known: ident) => { + ($name_ident: ident, $config_dir: tt, $genesis_state_source: path) => { paste! { #[macro_use] pub mod $name_ident { @@ -118,7 +150,7 @@ macro_rules! define_archive { pub const ETH2_NET_DIR: Eth2NetArchiveAndDirectory = Eth2NetArchiveAndDirectory { name: stringify!($name_ident), config_dir: $config_dir, - genesis_is_known: $genesis_is_known, + genesis_state_source: $genesis_state_source, }; /// A wrapper around `std::include_bytes` which includes a file from a specific network @@ -151,7 +183,7 @@ macro_rules! define_net { $this_crate::HardcodedNet { name: ETH2_NET_DIR.name, config_dir: ETH2_NET_DIR.config_dir, - genesis_is_known: ETH2_NET_DIR.genesis_is_known, + genesis_state_source: ETH2_NET_DIR.genesis_state_source, config: $this_crate::$include_file!($this_crate, "../", "config.yaml"), deploy_block: $this_crate::$include_file!($this_crate, "../", "deploy_block.txt"), boot_enr: $this_crate::$include_file!($this_crate, "../", "boot_enr.yaml"), @@ -199,9 +231,9 @@ macro_rules! define_nets { /// `build.rs` which will unzip the genesis states. Then, that `eth2_network_configs` crate can /// perform the final step of using `std::include_bytes` to bake the files (bytes) into the binary. macro_rules! define_hardcoded_nets { - ($(($name_ident: ident, $config_dir: tt, $genesis_is_known: ident)),+) => { + ($(($name_ident: ident, $config_dir: tt, $genesis_state_source: path)),+) => { $( - define_archive!($name_ident, $config_dir, $genesis_is_known); + define_archive!($name_ident, $config_dir, $genesis_state_source); )+ pub const ETH2_NET_DIRS: &[Eth2NetArchiveAndDirectory<'static>] = &[$($name_ident::ETH2_NET_DIR,)+]; @@ -242,9 +274,8 @@ define_hardcoded_nets!( // The name of the directory in the `eth2_network_config/built_in_network_configs` // directory where the configuration files are located for this network. "mainnet", - // Set to `true` if the genesis state can be found in the `built_in_network_configs` - // directory. - GENESIS_STATE_IS_KNOWN + // Describes how the genesis state can be obtained. + GenesisStateSource::IncludedBytes ), ( // Network name (must be unique among all networks). @@ -252,9 +283,8 @@ define_hardcoded_nets!( // The name of the directory in the `eth2_network_config/built_in_network_configs` // directory where the configuration files are located for this network. "prater", - // Set to `true` if the genesis state can be found in the `built_in_network_configs` - // directory. - GENESIS_STATE_IS_KNOWN + // Describes how the genesis state can be obtained. + GenesisStateSource::IncludedBytes ), ( // Network name (must be unique among all networks). @@ -264,9 +294,8 @@ define_hardcoded_nets!( // // The Goerli network is effectively an alias to Prater. "prater", - // Set to `true` if the genesis state can be found in the `built_in_network_configs` - // directory. - GENESIS_STATE_IS_KNOWN + // Describes how the genesis state can be obtained. + GenesisStateSource::IncludedBytes ), ( // Network name (must be unique among all networks). @@ -274,9 +303,8 @@ define_hardcoded_nets!( // The name of the directory in the `eth2_network_config/built_in_network_configs` // directory where the configuration files are located for this network. "gnosis", - // Set to `true` if the genesis state can be found in the `built_in_network_configs` - // directory. - GENESIS_STATE_IS_KNOWN + // Describes how the genesis state can be obtained. + GenesisStateSource::IncludedBytes ), ( // Network name (must be unique among all networks). @@ -284,8 +312,16 @@ define_hardcoded_nets!( // The name of the directory in the `eth2_network_config/built_in_network_configs` // directory where the configuration files are located for this network. "sepolia", - // Set to `true` if the genesis state can be found in the `built_in_network_configs` - // directory. - GENESIS_STATE_IS_KNOWN + // Describes how the genesis state can be obtained. + GenesisStateSource::IncludedBytes + ), + ( + // Network name (must be unique among all networks). + holesky, + // The name of the directory in the `eth2_network_config/built_in_network_configs` + // directory where the configuration files are located for this network. + "holesky", + // Describes how the genesis state can be obtained. + HOLESKY_GENESIS_STATE_SOURCE ) ); diff --git a/common/eth2_network_config/Cargo.toml b/common/eth2_network_config/Cargo.toml index d1fe25f1cc1..e73f64d5a83 100644 --- a/common/eth2_network_config/Cargo.toml +++ b/common/eth2_network_config/Cargo.toml @@ -8,14 +8,21 @@ build = "build.rs" [build-dependencies] zip = "0.6" -eth2_config = { path = "../eth2_config"} +eth2_config = { path = "../eth2_config" } [dev-dependencies] tempfile = "3.1.0" [dependencies] serde_yaml = "0.8.13" -types = { path = "../../consensus/types"} +types = { path = "../../consensus/types" } ethereum_ssz = "0.5.0" -eth2_config = { path = "../eth2_config"} +eth2_config = { path = "../eth2_config" } discv5 = "0.3.1" +reqwest = { version = "0.11.0", features = ["blocking"] } +pretty_reqwest_error = { path = "../pretty_reqwest_error" } +sha2 = "0.10" +url = "2.2.2" +sensitive_url = { path = "../sensitive_url" } +slog = "2.5.2" +logging = { path = "../logging" } diff --git a/common/eth2_network_config/build.rs b/common/eth2_network_config/build.rs index fa45fafa4e7..3165930f4a8 100644 --- a/common/eth2_network_config/build.rs +++ b/common/eth2_network_config/build.rs @@ -1,5 +1,7 @@ //! Extracts zipped genesis states on first run. -use eth2_config::{Eth2NetArchiveAndDirectory, ETH2_NET_DIRS, GENESIS_FILE_NAME}; +use eth2_config::{ + Eth2NetArchiveAndDirectory, GenesisStateSource, ETH2_NET_DIRS, GENESIS_FILE_NAME, +}; use std::fs::File; use std::io; use zip::ZipArchive; @@ -26,7 +28,7 @@ fn uncompress_state(network: &Eth2NetArchiveAndDirectory<'static>) -> Result<(), return Ok(()); } - if network.genesis_is_known { + if network.genesis_state_source == GenesisStateSource::IncludedBytes { // Extract genesis state from genesis.ssz.zip let archive_path = network.genesis_state_archive(); let archive_file = File::open(&archive_path) @@ -46,7 +48,8 @@ fn uncompress_state(network: &Eth2NetArchiveAndDirectory<'static>) -> Result<(), io::copy(&mut file, &mut outfile) .map_err(|e| format!("Error writing file {:?}: {}", genesis_ssz_path, e))?; } else { - // Create empty genesis.ssz if genesis is unknown + // Create empty genesis.ssz if genesis is unknown or to be downloaded via URL. + // This is a bit of a hack to make `include_bytes!` easier to deal with. File::create(genesis_ssz_path) .map_err(|e| format!("Failed to create {}: {}", GENESIS_FILE_NAME, e))?; } diff --git a/common/eth2_network_config/built_in_network_configs/holesky/boot_enr.yaml b/common/eth2_network_config/built_in_network_configs/holesky/boot_enr.yaml new file mode 100644 index 00000000000..616d41d672a --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/holesky/boot_enr.yaml @@ -0,0 +1,8 @@ +# EF +- enr:-Iq4QJk4WqRkjsX5c2CXtOra6HnxN-BMXnWhmhEQO9Bn9iABTJGdjUOurM7Btj1ouKaFkvTRoju5vz2GPmVON2dffQKGAX53x8JigmlkgnY0gmlwhLKAlv6Jc2VjcDI1NmsxoQK6S-Cii_KmfFdUJL2TANL3ksaKUnNXvTCv1tLwXs0QgIN1ZHCCIyk +- enr:-KG4QF6d6vMSboSujAXTI4vYqArccm0eIlXfcxf2Lx_VE1q6IkQo_2D5LAO3ZSBVUs0w5rrVDmABJZuMzISe_pZundADhGV0aDKQqX6DZjABcAAAAQAAAAAAAIJpZIJ2NIJpcISygIjpiXNlY3AyNTZrMaEDF3aSa7QSCvdqLpANNd8GML4PLEZVg45fKQwMWhDZjd2DdGNwgiMog3VkcIIjKA +- enr:-Ly4QJLXSSAj3ggPBIcodvBU6IyfpU_yW7E9J-5syoJorBuvcYj_Fokcjr303bQoTdWXADf8po0ssh75Mr5wVGzZZsMBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCpfoNmMAFwAAABAAAAAAAAgmlkgnY0gmlwhJK-DYCJc2VjcDI1NmsxoQJrIlXIQDvQ6t9yDySqJYDXgZgLXzTvq8W7OI51jfmxJohzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA +# Teku +- enr:-LK4QMlzEff6d-M0A1pSFG5lJ2c56i_I-ZftdojZbW3ehkGNM4pkQuHQqzVvF1BG9aDjIakjnmO23mCBFFZ2w5zOsugEh2F0dG5ldHOIAAAAAAYAAACEZXRoMpCpfoNmMAFwAAABAAAAAAAAgmlkgnY0gmlwhKyuI_mJc2VjcDI1NmsxoQIH1kQRCZW-4AIVyAeXj5o49m_IqNFKRHp6tSpfXMUrSYN0Y3CCIyiDdWRwgiMo +# Sigma Prime +- enr:-Le4QI88slOwzz66Ksq8Vnz324DPb1BzSiY-WYPvnoJIl-lceW9bmSJnwDzgNbCjp5wsBigg76x4tValvGgQPxxSjrMBhGV0aDKQqX6DZjABcAAAAQAAAAAAAIJpZIJ2NIJpcIQ5gR6Wg2lwNpAgAUHQBwEQAAAAAAAAADR-iXNlY3AyNTZrMaEDPMSNdcL92uNIyCsS177Z6KTXlbZakQqxv3aQcWawNXeDdWRwgiMohHVkcDaCI4I diff --git a/common/eth2_network_config/built_in_network_configs/holesky/config.yaml b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml new file mode 100644 index 00000000000..a6bfd87adec --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/holesky/config.yaml @@ -0,0 +1,117 @@ +# Extends the mainnet preset +PRESET_BASE: 'mainnet' +CONFIG_NAME: holesky + +# Genesis +# --------------------------------------------------------------- +# `2**14` (= 16,384) +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 +# Sep-15-2023 13:55:00 +UTC +MIN_GENESIS_TIME: 1694786100 +GENESIS_FORK_VERSION: 0x00017000 +# Genesis delay 5 mins +GENESIS_DELAY: 300 + + +# Forking +# --------------------------------------------------------------- +# Some forks are disabled for now: +# - These may be re-assigned to another fork-version later +# - Temporarily set to max uint64 value: 2**64 - 1 + +# Altair +ALTAIR_FORK_VERSION: 0x10017000 +ALTAIR_FORK_EPOCH: 0 +# Merge +BELLATRIX_FORK_VERSION: 0x20017000 +BELLATRIX_FORK_EPOCH: 0 +TERMINAL_TOTAL_DIFFICULTY: 0 +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + +# Capella +CAPELLA_FORK_VERSION: 0x30017000 +CAPELLA_FORK_EPOCH: 256 + +# DENEB +DENEB_FORK_VERSION: 0x40017000 +DENEB_FORK_EPOCH: 18446744073709551615 + +# Time parameters +# --------------------------------------------------------------- +# 12 seconds +SECONDS_PER_SLOT: 12 +# 14 (estimate from Eth1 mainnet) +SECONDS_PER_ETH1_BLOCK: 14 +# 2**8 (= 256) epochs ~27 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# 2**8 (= 256) epochs ~27 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**11 (= 2,048) Eth1 blocks ~8 hours +ETH1_FOLLOW_DISTANCE: 2048 + + +# Validator cycle +# --------------------------------------------------------------- +# 2**2 (= 4) +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 +# 28,000,000,000 Gwei to ensure quicker ejection +EJECTION_BALANCE: 28000000000 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**16 (= 65,536) +CHURN_LIMIT_QUOTIENT: 65536 + +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 + +# Deposit contract +# --------------------------------------------------------------- +DEPOSIT_CHAIN_ID: 17000 +DEPOSIT_NETWORK_ID: 17000 +DEPOSIT_CONTRACT_ADDRESS: 0x4242424242424242424242424242424242424242 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +GOSSIP_MAX_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# `10 * 2**20` (=10485760, 10 MiB) +MAX_CHUNK_SIZE: 10485760 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# `2**7` (=128) +MAX_REQUEST_BLOCKS_DENEB: 128 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK +MAX_REQUEST_BLOB_SIDECARS: 768 +# `2**12` (= 4096 epochs, ~18 days) +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# `6` +BLOB_SIDECAR_SUBNET_COUNT: 6 +# `uint64(6)` +MAX_BLOBS_PER_BLOCK: 6 diff --git a/common/eth2_network_config/built_in_network_configs/holesky/deploy_block.txt b/common/eth2_network_config/built_in_network_configs/holesky/deploy_block.txt new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/holesky/deploy_block.txt @@ -0,0 +1 @@ +0 diff --git a/common/eth2_network_config/src/lib.rs b/common/eth2_network_config/src/lib.rs index 7274bbf029b..1206b8b8caa 100644 --- a/common/eth2_network_config/src/lib.rs +++ b/common/eth2_network_config/src/lib.rs @@ -13,10 +13,20 @@ use discv5::enr::{CombinedKey, Enr}; use eth2_config::{instantiate_hardcoded_nets, HardcodedNet}; +use pretty_reqwest_error::PrettyReqwestError; +use reqwest::blocking::Client; +use sensitive_url::SensitiveUrl; +use sha2::{Digest, Sha256}; +use slog::{info, warn, Logger}; use std::fs::{create_dir_all, File}; use std::io::{Read, Write}; use std::path::PathBuf; -use types::{BeaconState, ChainSpec, Config, EthSpec, EthSpecId}; +use std::str::FromStr; +use std::time::Duration; +use types::{BeaconState, ChainSpec, Config, EthSpec, EthSpecId, Hash256}; +use url::Url; + +pub use eth2_config::GenesisStateSource; pub const DEPLOY_BLOCK_FILE: &str = "deploy_block.txt"; pub const BOOT_ENR_FILE: &str = "boot_enr.yaml"; @@ -32,6 +42,35 @@ instantiate_hardcoded_nets!(eth2_config); pub const DEFAULT_HARDCODED_NETWORK: &str = "mainnet"; +/// A simple slice-or-vec enum to avoid cloning the beacon state bytes in the +/// binary whilst also supporting loading them from a file at runtime. +#[derive(Clone, PartialEq, Debug)] +pub enum GenesisStateBytes { + Slice(&'static [u8]), + Vec(Vec<u8>), +} + +impl AsRef<[u8]> for GenesisStateBytes { + fn as_ref(&self) -> &[u8] { + match self { + GenesisStateBytes::Slice(slice) => slice, + GenesisStateBytes::Vec(vec) => vec.as_ref(), + } + } +} + +impl From<&'static [u8]> for GenesisStateBytes { + fn from(slice: &'static [u8]) -> Self { + GenesisStateBytes::Slice(slice) + } +} + +impl From<Vec<u8>> for GenesisStateBytes { + fn from(vec: Vec<u8>) -> Self { + GenesisStateBytes::Vec(vec) + } +} + /// Specifies an Eth2 network. /// /// See the crate-level documentation for more details. @@ -41,7 +80,8 @@ pub struct Eth2NetworkConfig { /// value to be the block number where the first deposit occurs. pub deposit_contract_deploy_block: u64, pub boot_enr: Option<Vec<Enr<CombinedKey>>>, - pub genesis_state_bytes: Option<Vec<u8>>, + pub genesis_state_source: GenesisStateSource, + pub genesis_state_bytes: Option<GenesisStateBytes>, pub config: Config, } @@ -65,8 +105,10 @@ impl Eth2NetworkConfig { serde_yaml::from_reader(net.boot_enr) .map_err(|e| format!("Unable to parse boot enr: {:?}", e))?, ), - genesis_state_bytes: Some(net.genesis_state_bytes.to_vec()) - .filter(|bytes| !bytes.is_empty()), + genesis_state_source: net.genesis_state_source, + genesis_state_bytes: Some(net.genesis_state_bytes) + .filter(|bytes| !bytes.is_empty()) + .map(Into::into), config: serde_yaml::from_reader(net.config) .map_err(|e| format!("Unable to parse yaml config: {:?}", e))?, }) @@ -81,8 +123,37 @@ impl Eth2NetworkConfig { } /// Returns `true` if this configuration contains a `BeaconState`. - pub fn beacon_state_is_known(&self) -> bool { - self.genesis_state_bytes.is_some() + pub fn genesis_state_is_known(&self) -> bool { + self.genesis_state_source != GenesisStateSource::Unknown + } + + /// The `genesis_validators_root` of the genesis state. May download the + /// genesis state if the value is not already available. + pub fn genesis_validators_root<E: EthSpec>( + &self, + genesis_state_url: Option<&str>, + timeout: Duration, + log: &Logger, + ) -> Result<Option<Hash256>, String> { + if let GenesisStateSource::Url { + genesis_validators_root, + .. + } = self.genesis_state_source + { + Hash256::from_str(genesis_validators_root) + .map(Option::Some) + .map_err(|e| { + format!( + "Unable to parse genesis state genesis_validators_root: {:?}", + e + ) + }) + } else { + self.genesis_state::<E>(genesis_state_url, timeout, log)? + .map(|state| state.genesis_validators_root()) + .map(Result::Ok) + .transpose() + } } /// Construct a consolidated `ChainSpec` from the YAML config. @@ -96,15 +167,65 @@ impl Eth2NetworkConfig { } /// Attempts to deserialize `self.beacon_state`, returning an error if it's missing or invalid. - pub fn beacon_state<E: EthSpec>(&self) -> Result<BeaconState<E>, String> { + /// + /// If the genesis state is configured to be downloaded from a URL, then the + /// `genesis_state_url` will override the built-in list of download URLs. + pub fn genesis_state<E: EthSpec>( + &self, + genesis_state_url: Option<&str>, + timeout: Duration, + log: &Logger, + ) -> Result<Option<BeaconState<E>>, String> { let spec = self.chain_spec::<E>()?; - let genesis_state_bytes = self - .genesis_state_bytes - .as_ref() - .ok_or("Genesis state is unknown")?; + match &self.genesis_state_source { + GenesisStateSource::Unknown => Ok(None), + GenesisStateSource::IncludedBytes => { + let state = self + .genesis_state_bytes + .as_ref() + .map(|bytes| { + BeaconState::from_ssz_bytes(bytes.as_ref(), &spec).map_err(|e| { + format!("Built-in genesis state SSZ bytes are invalid: {:?}", e) + }) + }) + .ok_or("Genesis state bytes missing from Eth2NetworkConfig")??; + Ok(Some(state)) + } + GenesisStateSource::Url { + urls: built_in_urls, + checksum, + genesis_validators_root, + } => { + let checksum = Hash256::from_str(checksum).map_err(|e| { + format!("Unable to parse genesis state bytes checksum: {:?}", e) + })?; + let bytes = if let Some(specified_url) = genesis_state_url { + download_genesis_state(&[specified_url], timeout, checksum, log) + } else { + download_genesis_state(built_in_urls, timeout, checksum, log) + }?; + let state = BeaconState::from_ssz_bytes(bytes.as_ref(), &spec).map_err(|e| { + format!("Downloaded genesis state SSZ bytes are invalid: {:?}", e) + })?; + + let genesis_validators_root = + Hash256::from_str(genesis_validators_root).map_err(|e| { + format!( + "Unable to parse genesis state genesis_validators_root: {:?}", + e + ) + })?; + if state.genesis_validators_root() != genesis_validators_root { + return Err(format!( + "Downloaded genesis validators root {:?} does not match expected {:?}", + state.genesis_validators_root(), + genesis_validators_root + )); + } - BeaconState::from_ssz_bytes(genesis_state_bytes, &spec) - .map_err(|e| format!("Genesis state SSZ bytes are invalid: {:?}", e)) + Ok(Some(state)) + } + } } /// Write the files to the directory. @@ -162,7 +283,7 @@ impl Eth2NetworkConfig { File::create(&file) .map_err(|e| format!("Unable to create {:?}: {:?}", file, e)) .and_then(|mut file| { - file.write_all(genesis_state_bytes) + file.write_all(genesis_state_bytes.as_ref()) .map_err(|e| format!("Unable to write {:?}: {:?}", file, e)) })?; } @@ -198,7 +319,7 @@ impl Eth2NetworkConfig { // The genesis state is a special case because it uses SSZ, not YAML. let genesis_file_path = base_dir.join(GENESIS_STATE_FILE); - let genesis_state_bytes = if genesis_file_path.exists() { + let (genesis_state_bytes, genesis_state_source) = if genesis_file_path.exists() { let mut bytes = vec![]; File::open(&genesis_file_path) .map_err(|e| format!("Unable to open {:?}: {:?}", genesis_file_path, e)) @@ -207,20 +328,105 @@ impl Eth2NetworkConfig { .map_err(|e| format!("Unable to read {:?}: {:?}", file, e)) })?; - Some(bytes).filter(|bytes| !bytes.is_empty()) + let state = Some(bytes).filter(|bytes| !bytes.is_empty()); + let genesis_state_source = if state.is_some() { + GenesisStateSource::IncludedBytes + } else { + GenesisStateSource::Unknown + }; + (state, genesis_state_source) } else { - None + (None, GenesisStateSource::Unknown) }; Ok(Self { deposit_contract_deploy_block, boot_enr, - genesis_state_bytes, + genesis_state_source, + genesis_state_bytes: genesis_state_bytes.map(Into::into), config, }) } } +/// Try to download a genesis state from each of the `urls` in the order they +/// are defined. Return `Ok` if any url returns a response that matches the +/// given `checksum`. +fn download_genesis_state( + urls: &[&str], + timeout: Duration, + checksum: Hash256, + log: &Logger, +) -> Result<Vec<u8>, String> { + if urls.is_empty() { + return Err( + "The genesis state is not present in the binary and there are no known download URLs. \ + Please use --checkpoint-sync-url or --genesis-state-url." + .to_string(), + ); + } + + let mut errors = vec![]; + for url in urls { + // URLs are always expected to be the base URL of a server that supports + // the beacon-API. + let url = parse_state_download_url(url)?; + let redacted_url = SensitiveUrl::new(url.clone()) + .map(|url| url.to_string()) + .unwrap_or_else(|_| "<REDACTED>".to_string()); + + info!( + log, + "Downloading genesis state"; + "server" => &redacted_url, + "timeout" => ?timeout, + "info" => "this may take some time on testnets with large validator counts" + ); + + let client = Client::new(); + let response = client + .get(url) + .header("Accept", "application/octet-stream") + .timeout(timeout) + .send() + .and_then(|r| r.error_for_status().and_then(|r| r.bytes())); + + match response { + Ok(bytes) => { + // Check the server response against our local checksum. + if Sha256::digest(bytes.as_ref())[..] == checksum[..] { + return Ok(bytes.into()); + } else { + warn!( + log, + "Genesis state download failed"; + "server" => &redacted_url, + "timeout" => ?timeout, + ); + errors.push(format!( + "Response from {} did not match local checksum", + redacted_url + )) + } + } + Err(e) => errors.push(PrettyReqwestError::from(e).to_string()), + } + } + Err(format!( + "Unable to download a genesis state from {} source(s): {}", + errors.len(), + errors.join(",") + )) +} + +/// Parses the `url` and joins the necessary state download path. +fn parse_state_download_url(url: &str) -> Result<Url, String> { + Url::parse(url) + .map_err(|e| format!("Invalid genesis state URL: {:?}", e))? + .join("eth/v2/debug/beacon/states/genesis") + .map_err(|e| format!("Failed to append genesis state path to URL: {:?}", e)) +} + #[cfg(test)] mod tests { use super::*; @@ -260,7 +466,9 @@ mod tests { #[test] fn mainnet_genesis_state() { let config = Eth2NetworkConfig::from_hardcoded_net(&MAINNET).unwrap(); - config.beacon_state::<E>().expect("beacon state can decode"); + config + .genesis_state::<E>(None, Duration::from_secs(1), &logging::test_logger()) + .expect("beacon state can decode"); } #[test] @@ -285,10 +493,25 @@ mod tests { assert_eq!( config.genesis_state_bytes.is_some(), - net.genesis_is_known, + net.genesis_state_source == GenesisStateSource::IncludedBytes, "{:?}", net.name ); + + if let GenesisStateSource::Url { + urls, + checksum, + genesis_validators_root, + } = net.genesis_state_source + { + Hash256::from_str(checksum).expect("the checksum must be a valid 32-byte value"); + Hash256::from_str(genesis_validators_root) + .expect("the GVR must be a valid 32-byte value"); + for url in urls { + parse_state_download_url(url).expect("url must be valid"); + } + } + assert_eq!(config.config.config_name, Some(net.config_dir.to_string())); } } @@ -324,10 +547,20 @@ mod tests { let base_dir = temp_dir.path().join("my_testnet"); let deposit_contract_deploy_block = 42; + let genesis_state_source = if genesis_state.is_some() { + GenesisStateSource::IncludedBytes + } else { + GenesisStateSource::Unknown + }; + let testnet: Eth2NetworkConfig = Eth2NetworkConfig { deposit_contract_deploy_block, boot_enr, - genesis_state_bytes: genesis_state.as_ref().map(Encode::as_ssz_bytes), + genesis_state_source, + genesis_state_bytes: genesis_state + .as_ref() + .map(Encode::as_ssz_bytes) + .map(Into::into), config, }; diff --git a/common/pretty_reqwest_error/src/lib.rs b/common/pretty_reqwest_error/src/lib.rs index 4c605f38aeb..0aaee5965ee 100644 --- a/common/pretty_reqwest_error/src/lib.rs +++ b/common/pretty_reqwest_error/src/lib.rs @@ -55,6 +55,12 @@ impl fmt::Debug for PrettyReqwestError { } } +impl fmt::Display for PrettyReqwestError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + impl From<reqwest::Error> for PrettyReqwestError { fn from(inner: reqwest::Error) -> Self { Self(inner) diff --git a/lcli/src/eth1_genesis.rs b/lcli/src/eth1_genesis.rs index 34144cd86db..bddd4baad8b 100644 --- a/lcli/src/eth1_genesis.rs +++ b/lcli/src/eth1_genesis.rs @@ -49,7 +49,7 @@ pub fn run<T: EthSpec>( .wait_for_genesis_state::<T>(ETH1_GENESIS_UPDATE_INTERVAL, spec) .await .map(move |genesis_state| { - eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes()); + eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes().into()); eth2_network_config.force_write_to_file(testnet_dir) }) .map_err(|e| format!("Failed to find genesis: {}", e))?; diff --git a/lcli/src/interop_genesis.rs b/lcli/src/interop_genesis.rs index 57a5ba00988..1a0b81fcb7f 100644 --- a/lcli/src/interop_genesis.rs +++ b/lcli/src/interop_genesis.rs @@ -42,7 +42,7 @@ pub fn run<T: EthSpec>(testnet_dir: PathBuf, matches: &ArgMatches) -> Result<(), &spec, )?; - eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes()); + eth2_network_config.genesis_state_bytes = Some(genesis_state.as_ssz_bytes().into()); eth2_network_config.force_write_to_file(testnet_dir)?; Ok(()) diff --git a/lcli/src/new_testnet.rs b/lcli/src/new_testnet.rs index 01a44cabef7..973993f9790 100644 --- a/lcli/src/new_testnet.rs +++ b/lcli/src/new_testnet.rs @@ -1,7 +1,7 @@ use account_utils::eth2_keystore::keypair_from_secret; use clap::ArgMatches; use clap_utils::{parse_optional, parse_required, parse_ssz_optional}; -use eth2_network_config::Eth2NetworkConfig; +use eth2_network_config::{Eth2NetworkConfig, GenesisStateSource}; use eth2_wallet::bip39::Seed; use eth2_wallet::bip39::{Language, Mnemonic}; use eth2_wallet::{recover_validator_secret_from_mnemonic, KeyType}; @@ -190,7 +190,8 @@ pub fn run<T: EthSpec>(testnet_dir_path: PathBuf, matches: &ArgMatches) -> Resul let testnet = Eth2NetworkConfig { deposit_contract_deploy_block, boot_enr: Some(vec![]), - genesis_state_bytes, + genesis_state_bytes: genesis_state_bytes.map(Into::into), + genesis_state_source: GenesisStateSource::IncludedBytes, config: Config::from_chain_spec::<T>(&spec), }; diff --git a/lighthouse/src/main.rs b/lighthouse/src/main.rs index d8b522307c4..6384fc53cd6 100644 --- a/lighthouse/src/main.rs +++ b/lighthouse/src/main.rs @@ -324,6 +324,30 @@ fn main() { .takes_value(true) .global(true) ) + .arg( + Arg::with_name("genesis-state-url") + .long("genesis-state-url") + .value_name("URL") + .help( + "A URL of a beacon-API compatible server from which to download the genesis state. \ + Checkpoint sync server URLs can generally be used with this flag. \ + If not supplied, a default URL or the --checkpoint-sync-url may be used. \ + If the genesis state is already included in this binary then this value will be ignored.", + ) + .takes_value(true) + .global(true), + ) + .arg( + Arg::with_name("genesis-state-url-timeout") + .long("genesis-state-url-timeout") + .value_name("SECONDS") + .help( + "The timeout in seconds for the request to --genesis-state-url.", + ) + .takes_value(true) + .default_value("180") + .global(true), + ) .subcommand(beacon_node::cli_app()) .subcommand(boot_node::cli_app()) .subcommand(validator_client::cli_app()) diff --git a/lighthouse/tests/beacon_node.rs b/lighthouse/tests/beacon_node.rs index 5069b0261f6..05b4358509b 100644 --- a/lighthouse/tests/beacon_node.rs +++ b/lighthouse/tests/beacon_node.rs @@ -2406,3 +2406,28 @@ fn http_duplicate_block_status_override() { assert_eq!(config.http_api.duplicate_block_status_code.as_u16(), 301) }); } + +#[test] +fn genesis_state_url_default() { + CommandLineTest::new() + .run_with_zero_port() + .with_config(|config| { + assert_eq!(config.genesis_state_url, None); + assert_eq!(config.genesis_state_url_timeout, Duration::from_secs(180)); + }); +} + +#[test] +fn genesis_state_url_value() { + CommandLineTest::new() + .flag("genesis-state-url", Some("http://genesis.com")) + .flag("genesis-state-url-timeout", Some("42")) + .run_with_zero_port() + .with_config(|config| { + assert_eq!( + config.genesis_state_url.as_deref(), + Some("http://genesis.com") + ); + assert_eq!(config.genesis_state_url_timeout, Duration::from_secs(42)); + }); +} From f284e0e26445f776e470ad694a947cb10adc5686 Mon Sep 17 00:00:00 2001 From: Michael Sproul <michael@sigmaprime.io> Date: Mon, 28 Aug 2023 05:34:28 +0000 Subject: [PATCH 21/25] Fix bug in block root storage (#4663) ## Issue Addressed Fix a bug in the storage of the linear block roots array in the freezer DB. Previously this array was always written as part of state storage (or block backfill). With state pruning enabled by #4610, these states were no longer being written and as a result neither were the block roots. The impact is quite low, we would just log an error when trying to forwards-iterate the block roots, which for validating nodes only happens when they try to look up blocks for peers: > Aug 25 03:42:36.980 ERRO Missing chunk in forwards iterator chunk index: 49726, service: freezer_db Any node checkpoint synced off `unstable` is affected and has a corrupt database. If you see the log above, you need to re-sync with the fix. Nodes that haven't checkpoint synced recently should _not_ be corrupted, even if they ran the buggy version. ## Proposed Changes - Use a `ChunkWriter` to write the block roots when states are not being stored. - Tweak the usage of `get_latest_restore_point` so that it doesn't return a nonsense value when state pruning is enabled. - Tweak the guarantee on the block roots array so that block roots are assumed available up to the split slot (exclusive). This is a bit nicer than relying on anything to do with the latest restore point, which is a nonsensical concept when there aren't any restore points. ## Additional Info I'm looking forward to deleting the chunked vector code for good when we merge tree-states :grin: --- Cargo.lock | 1 + beacon_node/beacon_chain/src/builder.rs | 15 ++++ beacon_node/beacon_chain/tests/store_tests.rs | 2 +- beacon_node/store/src/chunked_iter.rs | 8 +- beacon_node/store/src/forwards_iter.rs | 54 ++++++++++-- beacon_node/store/src/hot_cold_store.rs | 82 +++++++++++++++---- watch/Cargo.toml | 1 + watch/tests/tests.rs | 23 +++--- 8 files changed, 144 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75c15ba7813..27d7b655e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9051,6 +9051,7 @@ dependencies = [ "http_api", "hyper", "log", + "logging", "network", "r2d2", "rand 0.7.3", diff --git a/beacon_node/beacon_chain/src/builder.rs b/beacon_node/beacon_chain/src/builder.rs index bb629d66249..54739f2b8ac 100644 --- a/beacon_node/beacon_chain/src/builder.rs +++ b/beacon_node/beacon_chain/src/builder.rs @@ -481,6 +481,21 @@ where let (_, updated_builder) = self.set_genesis_state(genesis_state)?; self = updated_builder; + // Fill in the linear block roots between the checkpoint block's slot and the aligned + // state's slot. All slots less than the block's slot will be handled by block backfill, + // while states greater or equal to the checkpoint state will be handled by `migrate_db`. + let block_root_batch = store + .store_frozen_block_root_at_skip_slots( + weak_subj_block.slot(), + weak_subj_state.slot(), + weak_subj_block_root, + ) + .map_err(|e| format!("Error writing frozen block roots: {e:?}"))?; + store + .cold_db + .do_atomically(block_root_batch) + .map_err(|e| format!("Error writing frozen block roots: {e:?}"))?; + // Write the state and block non-atomically, it doesn't matter if they're forgotten // about on a crash restart. store diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index bf68657b4f5..ab54af42c78 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -421,7 +421,7 @@ async fn forwards_iter_block_and_state_roots_until() { // The last restore point slot is the point at which the hybrid forwards iterator behaviour // changes. - let last_restore_point_slot = store.get_latest_restore_point_slot(); + let last_restore_point_slot = store.get_latest_restore_point_slot().unwrap(); assert!(last_restore_point_slot > 0); let chain = &harness.chain; diff --git a/beacon_node/store/src/chunked_iter.rs b/beacon_node/store/src/chunked_iter.rs index 8ef0b6d201d..b3322b5225d 100644 --- a/beacon_node/store/src/chunked_iter.rs +++ b/beacon_node/store/src/chunked_iter.rs @@ -30,16 +30,16 @@ where /// Create a new iterator which can yield elements from `start_vindex` up to the last /// index stored by the restore point at `last_restore_point_slot`. /// - /// The `last_restore_point` slot should be the slot of a recent restore point as obtained from - /// `HotColdDB::get_latest_restore_point_slot`. We pass it as a parameter so that the caller can + /// The `freezer_upper_limit` slot should be the slot of a recent restore point as obtained from + /// `Root::freezer_upper_limit`. We pass it as a parameter so that the caller can /// maintain a stable view of the database (see `HybridForwardsBlockRootsIterator`). pub fn new( store: &'a HotColdDB<E, Hot, Cold>, start_vindex: usize, - last_restore_point_slot: Slot, + freezer_upper_limit: Slot, spec: &ChainSpec, ) -> Self { - let (_, end_vindex) = F::start_and_end_vindex(last_restore_point_slot, spec); + let (_, end_vindex) = F::start_and_end_vindex(freezer_upper_limit, spec); // Set the next chunk to the one containing `start_vindex`. let next_cindex = start_vindex / F::chunk_size(); diff --git a/beacon_node/store/src/forwards_iter.rs b/beacon_node/store/src/forwards_iter.rs index 353be6bf058..125b73a458f 100644 --- a/beacon_node/store/src/forwards_iter.rs +++ b/beacon_node/store/src/forwards_iter.rs @@ -19,6 +19,14 @@ pub trait Root<E: EthSpec>: Field<E, Value = Hash256> { end_state: BeaconState<E>, end_root: Hash256, ) -> Result<SimpleForwardsIterator>; + + /// The first slot for which this field is *no longer* stored in the freezer database. + /// + /// If `None`, then this field is not stored in the freezer database at all due to pruning + /// configuration. + fn freezer_upper_limit<Hot: ItemStore<E>, Cold: ItemStore<E>>( + store: &HotColdDB<E, Hot, Cold>, + ) -> Option<Slot>; } impl<E: EthSpec> Root<E> for BlockRoots { @@ -39,6 +47,13 @@ impl<E: EthSpec> Root<E> for BlockRoots { )?; Ok(SimpleForwardsIterator { values }) } + + fn freezer_upper_limit<Hot: ItemStore<E>, Cold: ItemStore<E>>( + store: &HotColdDB<E, Hot, Cold>, + ) -> Option<Slot> { + // Block roots are stored for all slots up to the split slot (exclusive). + Some(store.get_split_slot()) + } } impl<E: EthSpec> Root<E> for StateRoots { @@ -59,6 +74,15 @@ impl<E: EthSpec> Root<E> for StateRoots { )?; Ok(SimpleForwardsIterator { values }) } + + fn freezer_upper_limit<Hot: ItemStore<E>, Cold: ItemStore<E>>( + store: &HotColdDB<E, Hot, Cold>, + ) -> Option<Slot> { + // State roots are stored for all slots up to the latest restore point (exclusive). + // There may not be a latest restore point if state pruning is enabled, in which + // case this function will return `None`. + store.get_latest_restore_point_slot() + } } /// Forwards root iterator that makes use of a flat field table in the freezer DB. @@ -118,6 +142,7 @@ impl Iterator for SimpleForwardsIterator { pub enum HybridForwardsIterator<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> { PreFinalization { iter: Box<FrozenForwardsIterator<'a, E, F, Hot, Cold>>, + end_slot: Option<Slot>, /// Data required by the `PostFinalization` iterator when we get to it. continuation_data: Option<Box<(BeaconState<E>, Hash256)>>, }, @@ -129,6 +154,7 @@ pub enum HybridForwardsIterator<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, C PostFinalization { iter: SimpleForwardsIterator, }, + Finished, } impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> @@ -138,8 +164,8 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> /// /// The `get_state` closure should return a beacon state and final block/state root to backtrack /// from in the case where the iterated range does not lie entirely within the frozen portion of - /// the database. If an `end_slot` is provided and it is before the database's latest restore - /// point slot then the `get_state` closure will not be called at all. + /// the database. If an `end_slot` is provided and it is before the database's freezer upper + /// limit for the field then the `get_state` closure will not be called at all. /// /// It is OK for `get_state` to hold a lock while this function is evaluated, as the returned /// iterator is as lazy as possible and won't do any work apart from calling `get_state`. @@ -155,13 +181,15 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> ) -> Result<Self> { use HybridForwardsIterator::*; - let latest_restore_point_slot = store.get_latest_restore_point_slot(); + // First slot at which this field is *not* available in the freezer. i.e. all slots less + // than this slot have their data available in the freezer. + let freezer_upper_limit = F::freezer_upper_limit(store).unwrap_or(Slot::new(0)); - let result = if start_slot < latest_restore_point_slot { + let result = if start_slot < freezer_upper_limit { let iter = Box::new(FrozenForwardsIterator::new( store, start_slot, - latest_restore_point_slot, + freezer_upper_limit, spec, )); @@ -169,13 +197,14 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> // `end_slot`. If it tries to continue further a `NoContinuationData` error will be // returned. let continuation_data = - if end_slot.map_or(false, |end_slot| end_slot < latest_restore_point_slot) { + if end_slot.map_or(false, |end_slot| end_slot < freezer_upper_limit) { None } else { Some(Box::new(get_state())) }; PreFinalization { iter, + end_slot, continuation_data, } } else { @@ -195,6 +224,7 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> match self { PreFinalization { iter, + end_slot, continuation_data, } => { match iter.next() { @@ -203,10 +233,17 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> // to a post-finalization iterator beginning from the last slot // of the pre iterator. None => { + // If the iterator has an end slot (inclusive) which has already been + // covered by the (exclusive) frozen forwards iterator, then we're done! + let iter_end_slot = Slot::from(iter.inner.end_vindex); + if end_slot.map_or(false, |end_slot| iter_end_slot == end_slot + 1) { + *self = Finished; + return Ok(None); + } + let continuation_data = continuation_data.take(); let store = iter.inner.store; - let start_slot = Slot::from(iter.inner.end_vindex); - + let start_slot = iter_end_slot; *self = PostFinalizationLazy { continuation_data, store, @@ -230,6 +267,7 @@ impl<'a, E: EthSpec, F: Root<E>, Hot: ItemStore<E>, Cold: ItemStore<E>> self.do_next() } PostFinalization { iter } => iter.next().transpose(), + Finished => Ok(None), } } } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 47839ed3e98..87f8e0ffc36 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -18,7 +18,7 @@ use crate::metadata::{ }; use crate::metrics; use crate::{ - get_key_for_col, DBColumn, DatabaseBlock, Error, ItemStore, KeyValueStoreOp, + get_key_for_col, ChunkWriter, DBColumn, DatabaseBlock, Error, ItemStore, KeyValueStoreOp, PartialBeaconState, StoreItem, StoreOp, }; use itertools::process_results; @@ -963,6 +963,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold> ops.push(op); // 2. Store updated vector entries. + // Block roots need to be written here as well as by the `ChunkWriter` in `migrate_db` + // because states may require older block roots, and the writer only stores block roots + // between the previous split point and the new split point. let db = &self.cold_db; store_updated_vector(BlockRoots, db, state, &self.spec, ops)?; store_updated_vector(StateRoots, db, state, &self.spec, ops)?; @@ -1243,10 +1246,21 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold> }; } - /// Fetch the slot of the most recently stored restore point. - pub fn get_latest_restore_point_slot(&self) -> Slot { - (self.get_split_slot() - 1) / self.config.slots_per_restore_point - * self.config.slots_per_restore_point + /// Fetch the slot of the most recently stored restore point (if any). + pub fn get_latest_restore_point_slot(&self) -> Option<Slot> { + let split_slot = self.get_split_slot(); + let anchor = self.get_anchor_info(); + + // There are no restore points stored if the state upper limit lies in the hot database. + // It hasn't been reached yet, and may never be. + if anchor.map_or(false, |a| a.state_upper_limit >= split_slot) { + None + } else { + Some( + (split_slot - 1) / self.config.slots_per_restore_point + * self.config.slots_per_restore_point, + ) + } } /// Load the database schema version from disk. @@ -1585,6 +1599,25 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold> ) } + /// Update the linear array of frozen block roots with the block root for several skipped slots. + /// + /// Write the block root at all slots from `start_slot` (inclusive) to `end_slot` (exclusive). + pub fn store_frozen_block_root_at_skip_slots( + &self, + start_slot: Slot, + end_slot: Slot, + block_root: Hash256, + ) -> Result<Vec<KeyValueStoreOp>, Error> { + let mut ops = vec![]; + let mut block_root_writer = + ChunkWriter::<BlockRoots, _, _>::new(&self.cold_db, start_slot.as_usize())?; + for slot in start_slot.as_usize()..end_slot.as_usize() { + block_root_writer.set(slot, block_root, &mut ops)?; + } + block_root_writer.write(&mut ops)?; + Ok(ops) + } + /// Try to prune all execution payloads, returning early if there is no need to prune. pub fn try_prune_execution_payloads(&self, force: bool) -> Result<(), Error> { let split = self.get_split_info(); @@ -1725,7 +1758,14 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>( return Err(HotColdDBError::FreezeSlotUnaligned(finalized_state.slot()).into()); } - let mut hot_db_ops: Vec<StoreOp<E>> = Vec::new(); + let mut hot_db_ops = vec![]; + let mut cold_db_ops = vec![]; + + // Chunk writer for the linear block roots in the freezer DB. + // Start at the new upper limit because we iterate backwards. + let new_frozen_block_root_upper_limit = finalized_state.slot().as_usize().saturating_sub(1); + let mut block_root_writer = + ChunkWriter::<BlockRoots, _, _>::new(&store.cold_db, new_frozen_block_root_upper_limit)?; // 1. Copy all of the states between the new finalized state and the split slot, from the hot DB // to the cold DB. Delete the execution payloads of these now-finalized blocks. @@ -1750,6 +1790,9 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>( // Delete the old summary, and the full state if we lie on an epoch boundary. hot_db_ops.push(StoreOp::DeleteState(state_root, Some(slot))); + // Store the block root for this slot in the linear array of frozen block roots. + block_root_writer.set(slot.as_usize(), block_root, &mut cold_db_ops)?; + // Do not try to store states if a restore point is yet to be stored, or will never be // stored (see `STATE_UPPER_LIMIT_NO_RETAIN`). Make an exception for the genesis state // which always needs to be copied from the hot DB to the freezer and should not be deleted. @@ -1759,29 +1802,34 @@ pub fn migrate_database<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>>( .map_or(false, |anchor| slot < anchor.state_upper_limit) { debug!(store.log, "Pruning finalized state"; "slot" => slot); + continue; } - let mut cold_db_ops: Vec<KeyValueStoreOp> = Vec::new(); + // Store a pointer from this state root to its slot, so we can later reconstruct states + // from their state root alone. + let cold_state_summary = ColdStateSummary { slot }; + let op = cold_state_summary.as_kv_store_op(state_root); + cold_db_ops.push(op); if slot % store.config.slots_per_restore_point == 0 { let state: BeaconState<E> = get_full_state(&store.hot_db, &state_root, &store.spec)? .ok_or(HotColdDBError::MissingStateToFreeze(state_root))?; store.store_cold_state(&state_root, &state, &mut cold_db_ops)?; - } - // Store a pointer from this state root to its slot, so we can later reconstruct states - // from their state root alone. - let cold_state_summary = ColdStateSummary { slot }; - let op = cold_state_summary.as_kv_store_op(state_root); - cold_db_ops.push(op); - - // There are data dependencies between calls to `store_cold_state()` that prevent us from - // doing one big call to `store.cold_db.do_atomically()` at end of the loop. - store.cold_db.do_atomically(cold_db_ops)?; + // Commit the batch of cold DB ops whenever a full state is written. Each state stored + // may read the linear fields of previous states stored. + store + .cold_db + .do_atomically(std::mem::take(&mut cold_db_ops))?; + } } + // Finish writing the block roots and commit the remaining cold DB ops. + block_root_writer.write(&mut cold_db_ops)?; + store.cold_db.do_atomically(cold_db_ops)?; + // Warning: Critical section. We have to take care not to put any of the two databases in an // inconsistent state if the OS process dies at any point during the freezeing // procedure. diff --git a/watch/Cargo.toml b/watch/Cargo.toml index b29df14439e..3dc3b7c1905 100644 --- a/watch/Cargo.toml +++ b/watch/Cargo.toml @@ -45,3 +45,4 @@ network = { path = "../beacon_node/network" } testcontainers = { git = "https://github.com/testcontainers/testcontainers-rs/", rev = "0f2c9851" } unused_port = { path = "../common/unused_port" } task_executor = { path = "../common/task_executor" } +logging = { path = "../common/logging" } diff --git a/watch/tests/tests.rs b/watch/tests/tests.rs index a54386eb753..dc0b8af6e34 100644 --- a/watch/tests/tests.rs +++ b/watch/tests/tests.rs @@ -7,12 +7,21 @@ use beacon_chain::{ }; use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts}; use http_api::test_utils::{create_api_server, ApiServer}; +use log::error; +use logging::test_logger; use network::NetworkReceivers; - use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::env; +use std::net::SocketAddr; +use std::time::Duration; +use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage}; use tokio::sync::oneshot; +use tokio::{runtime, task::JoinHandle}; +use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls}; use types::{Hash256, MainnetEthSpec, Slot}; +use unused_port::unused_tcp4_port; use url::Url; use watch::{ client::WatchHttpClient, @@ -22,17 +31,6 @@ use watch::{ updater::{handler::*, run_updater, Config as UpdaterConfig, WatchSpec}, }; -use log::error; -use std::collections::HashMap; -use std::env; -use std::net::SocketAddr; -use std::time::Duration; -use tokio::{runtime, task::JoinHandle}; -use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls}; -use unused_port::unused_tcp4_port; - -use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage}; - #[derive(Debug)] pub struct Postgres(HashMap<String, String>); @@ -132,6 +130,7 @@ impl TesterBuilder { reconstruct_historic_states: true, ..ChainConfig::default() }) + .logger(test_logger()) .deterministic_keypairs(VALIDATOR_COUNT) .fresh_ephemeral_store() .build(); From 0c23c86849401ea38456e8b1b9a9b0ea4b826ea7 Mon Sep 17 00:00:00 2001 From: Philippe Schommers <philippe@schommers.be> Date: Tue, 29 Aug 2023 05:56:30 +0000 Subject: [PATCH 22/25] feat: add chiado (#4530) ## Issue Addressed N/A ## Proposed Changes Adds the Chiado (Gnosis testnet) network to the builtin one. ## Additional Info It's a fairly trivial change all things considered as the preset already exists, so shouldn't be hard to maintain. It compiles and seems to work, but I'm sure I missed something? Co-authored-by: Paul Hauner <paul@paulhauner.com> --- book/src/merge-migration.md | 3 +- book/src/run_a_node.md | 3 +- common/eth2_config/src/lib.rs | 17 ++ .../chiado/boot_enr.yaml | 8 + .../chiado/config.yaml | 154 ++++++++++++++++++ .../chiado/deploy_block.txt | 1 + common/eth2_network_config/src/lib.rs | 4 +- 7 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 common/eth2_network_config/built_in_network_configs/chiado/boot_enr.yaml create mode 100644 common/eth2_network_config/built_in_network_configs/chiado/config.yaml create mode 100644 common/eth2_network_config/built_in_network_configs/chiado/deploy_block.txt diff --git a/book/src/merge-migration.md b/book/src/merge-migration.md index acca0bbeb3e..bab520b569b 100644 --- a/book/src/merge-migration.md +++ b/book/src/merge-migration.md @@ -21,7 +21,7 @@ engine to a merge-ready version. ## When? -All networks (**Mainnet**, **Goerli (Prater)**, **Ropsten**, **Sepolia**, **Kiln**, **Gnosis**) have successfully undergone the Bellatrix fork and transitioned to a post-merge Network. Your node must have a merge-ready configuration to continue operating. Table below lists the date at which Bellatrix and The Merge occurred: +All networks (**Mainnet**, **Goerli (Prater)**, **Ropsten**, **Sepolia**, **Kiln**, **Chiado**, **Gnosis**) have successfully undergone the Bellatrix fork and transitioned to a post-merge Network. Your node must have a merge-ready configuration to continue operating. Table below lists the date at which Bellatrix and The Merge occurred: <div align="center"> @@ -31,6 +31,7 @@ All networks (**Mainnet**, **Goerli (Prater)**, **Ropsten**, **Sepolia**, **Kiln | Sepolia | 20<sup>th</sup> June 2022 | 6<sup>th</sup> July 2022 | | | Goerli | 4<sup>th</sup> August 2022 | 10<sup>th</sup> August 2022 | Previously named `Prater`| | Mainnet | 6<sup>th</sup> September 2022 | 15<sup>th</sup> September 2022 | +| Chiado | 10<sup>th</sup> October 2022 | 4<sup>th</sup> November 2022 | | Gnosis| 30<sup>th</sup> November 2022 | 8<sup>th</sup> December 2022 </div> diff --git a/book/src/run_a_node.md b/book/src/run_a_node.md index a31aedf785a..1ea14273357 100644 --- a/book/src/run_a_node.md +++ b/book/src/run_a_node.md @@ -58,7 +58,8 @@ Notable flags: - `lighthouse --network mainnet`: Mainnet. - `lighthouse --network goerli`: Goerli (testnet). - `lighthouse --network sepolia`: Sepolia (testnet). - - `lighthouse --network gnosis`: Gnosis chain + - `lighthouse --network chiado`: Chiado (testnet). + - `lighthouse --network gnosis`: Gnosis chain. > Note: Using the correct `--network` flag is very important; using the wrong flag can result in penalties, slashings or lost deposits. As a rule of thumb, *always* diff --git a/common/eth2_config/src/lib.rs b/common/eth2_config/src/lib.rs index bb14d067569..6487151a92c 100644 --- a/common/eth2_config/src/lib.rs +++ b/common/eth2_config/src/lib.rs @@ -33,6 +33,13 @@ const HOLESKY_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url genesis_validators_root: "0x9143aa7c615a7f7115e2b6aac319c03529df8242ae705fba9df39b79c59fa8b1", }; +const CHIADO_GENESIS_STATE_SOURCE: GenesisStateSource = GenesisStateSource::Url { + // No default checkpoint sources are provided. + urls: &[], + checksum: "0xd4a039454c7429f1dfaa7e11e397ef3d0f50d2d5e4c0e4dc04919d153aa13af1", + genesis_validators_root: "0x9d642dac73058fbf39c0ae41ab1e34e4d889043cb199851ded7095bc99eb4c1e", +}; + /// The core configuration of a Lighthouse beacon node. #[derive(Debug, Clone)] pub struct Eth2Config { @@ -306,6 +313,16 @@ define_hardcoded_nets!( // Describes how the genesis state can be obtained. GenesisStateSource::IncludedBytes ), + ( + // Network name (must be unique among all networks). + chiado, + // The name of the directory in the `eth2_network_config/built_in_network_configs` + // directory where the configuration files are located for this network. + "chiado", + // Set to `true` if the genesis state can be found in the `built_in_network_configs` + // directory. + CHIADO_GENESIS_STATE_SOURCE + ), ( // Network name (must be unique among all networks). sepolia, diff --git a/common/eth2_network_config/built_in_network_configs/chiado/boot_enr.yaml b/common/eth2_network_config/built_in_network_configs/chiado/boot_enr.yaml new file mode 100644 index 00000000000..96baffde6fb --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/chiado/boot_enr.yaml @@ -0,0 +1,8 @@ +# chiado-teku-0 +- "enr:-Ly4QLYLNqrjvSxD3lpAPBUNlxa6cIbe79JqLZLFcZZjWoCjZcw-85agLUErHiygG2weRSCLnd5V460qTbLbwJQsfZkoh2F0dG5ldHOI__________-EZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhKq7mu-Jc2VjcDI1NmsxoQP900YAYa9kdvzlSKGjVo-F3XVzATjOYp3BsjLjSophO4hzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA" +# chiado-teku-1 +- "enr:-Ly4QCGeYvTCNOGKi0mKRUd45rLj96b4pH98qG7B9TCUGXGpHZALtaL2-XfjASQyhbCqENccI4PGXVqYTIehNT9KJMQgh2F0dG5ldHOI__________-EZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhIuQrVSJc2VjcDI1NmsxoQP9iDchx2PGl3JyJ29B9fhLCvVMN6n23pPAIIeFV-sHOIhzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA" +#GnosisDAO Bootnode: 3.71.132.231 +- "enr:-Ly4QAtr21x5Ps7HYhdZkIBRBgcBkvlIfEel1YNjtFWf4cV3au2LgBGICz9PtEs9-p2HUl_eME8m1WImxTxSB3AkCMwBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhANHhOeJc2VjcDI1NmsxoQNLp1QPV8-pyMCohOtj6xGtSBM_GtVTqzlbvNsCF4ezkYhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA" +#GnosisDAO Bootnode: 3.69.35.13 +- "enr:-Ly4QLgn8Bx6faigkKUGZQvd1HDToV2FAxZIiENK-lczruzQb90qJK-4E65ADly0s4__dQOW7IkLMW7ZAyJy2vtiLy8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAxNnBDAgAAb___________gmlkgnY0gmlwhANFIw2Jc2VjcDI1NmsxoQMa-fWEy9UJHfOl_lix3wdY5qust78sHAqZnWwEiyqKgYhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA" diff --git a/common/eth2_network_config/built_in_network_configs/chiado/config.yaml b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml new file mode 100644 index 00000000000..47b285a654f --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/chiado/config.yaml @@ -0,0 +1,154 @@ +# Extends the mainnet preset +PRESET_BASE: gnosis +# needs to exist because of Prysm. Otherwise it conflicts with mainnet genesis +CONFIG_NAME: chiado + +# Genesis +MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 6000 +# 10 October 2022 10:00:00 GMT+0000 +MIN_GENESIS_TIME: 1665396000 +GENESIS_DELAY: 300 + +# Projected time: 2022-11-04T15:00:00.000Z, block: 680928 +TERMINAL_TOTAL_DIFFICULTY: 231707791542740786049188744689299064356246512 + +# Deposit contract +# --------------------------------------------------------------- +# NOTE: Don't use a value too high, or Teku rejects it (4294906129 NOK) +DEPOSIT_CHAIN_ID: 10200 +DEPOSIT_NETWORK_ID: 10200 +DEPOSIT_CONTRACT_ADDRESS: 0xb97036A26259B7147018913bD58a774cf91acf25 + +# Misc +# --------------------------------------------------------------- +# 2**6 (= 64) +MAX_COMMITTEES_PER_SLOT: 64 +# 2**7 (= 128) +TARGET_COMMITTEE_SIZE: 128 +# 2**11 (= 2,048) +MAX_VALIDATORS_PER_COMMITTEE: 2048 +# 2**2 (= 4) +MIN_PER_EPOCH_CHURN_LIMIT: 4 +# 2**12 (= 4096) +CHURN_LIMIT_QUOTIENT: 4096 +# See issue 563 +SHUFFLE_ROUND_COUNT: 90 +# 4 +HYSTERESIS_QUOTIENT: 4 +# 1 (minus 0.25) +HYSTERESIS_DOWNWARD_MULTIPLIER: 1 +# 5 (plus 1.25) +HYSTERESIS_UPWARD_MULTIPLIER: 5 +# Validator +# --------------------------------------------------------------- +# 2**10 (= 1024) ~1.4 hour +ETH1_FOLLOW_DISTANCE: 1024 +# 2**4 (= 16) +TARGET_AGGREGATORS_PER_COMMITTEE: 16 +# 2**0 (= 1) +RANDOM_SUBNETS_PER_VALIDATOR: 1 +# 2**8 (= 256) +EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256 +# 6 (estimate from xDai mainnet) +SECONDS_PER_ETH1_BLOCK: 6 + +# Gwei values +# --------------------------------------------------------------- +# 2**0 * 10**9 (= 1,000,000,000) Gwei +MIN_DEPOSIT_AMOUNT: 1000000000 +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE: 32000000000 +# 2**4 * 10**9 (= 16,000,000,000) Gwei +EJECTION_BALANCE: 16000000000 +# 2**0 * 10**9 (= 1,000,000,000) Gwei +EFFECTIVE_BALANCE_INCREMENT: 1000000000 +# Initial values +# --------------------------------------------------------------- +# GBC area code +GENESIS_FORK_VERSION: 0x0000006f +BLS_WITHDRAWAL_PREFIX: 0x00 +# Time parameters +# --------------------------------------------------------------- +# 5 seconds +SECONDS_PER_SLOT: 5 +# 2**0 (= 1) slots 12 seconds +MIN_ATTESTATION_INCLUSION_DELAY: 1 +# 2**4 (= 16) slots 1.87 minutes +SLOTS_PER_EPOCH: 16 +# 2**0 (= 1) epochs 1.87 minutes +MIN_SEED_LOOKAHEAD: 1 +# 2**2 (= 4) epochs 7.47 minutes +MAX_SEED_LOOKAHEAD: 4 +# 2**6 (= 64) epochs ~2 hours +EPOCHS_PER_ETH1_VOTING_PERIOD: 64 +# 2**13 (= 8,192) slots ~15.9 hours +SLOTS_PER_HISTORICAL_ROOT: 8192 +# 2**8 (= 256) epochs ~8 hours +MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 +# 2**8 (= 256) epochs ~8 hours +SHARD_COMMITTEE_PERIOD: 256 +# 2**2 (= 4) epochs 7.47 minutes +MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 + +# State vector lengths +# --------------------------------------------------------------- +# 2**16 (= 65,536) epochs ~85 days +EPOCHS_PER_HISTORICAL_VECTOR: 65536 +# 2**13 (= 8,192) epochs ~10.6 days +EPOCHS_PER_SLASHINGS_VECTOR: 8192 +# 2**24 (= 16,777,216) historical roots, ~15,243 years +HISTORICAL_ROOTS_LIMIT: 16777216 +# 2**40 (= 1,099,511,627,776) validator spots +VALIDATOR_REGISTRY_LIMIT: 1099511627776 +# Reward and penalty quotients +# --------------------------------------------------------------- +# 25 +BASE_REWARD_FACTOR: 25 +# 2**9 (= 512) +WHISTLEBLOWER_REWARD_QUOTIENT: 512 +# 2**3 (= 8) +PROPOSER_REWARD_QUOTIENT: 8 +# 2**26 (= 67,108,864) +INACTIVITY_PENALTY_QUOTIENT: 67108864 +# 2**7 (= 128) (lower safety margin at Phase 0 genesis) +MIN_SLASHING_PENALTY_QUOTIENT: 128 +# 1 (lower safety margin at Phase 0 genesis) +PROPORTIONAL_SLASHING_MULTIPLIER: 1 +# Max operations per block +# --------------------------------------------------------------- +# 2**4 (= 16) +MAX_PROPOSER_SLASHINGS: 16 +# 2**1 (= 2) +MAX_ATTESTER_SLASHINGS: 2 +# 2**7 (= 128) +MAX_ATTESTATIONS: 128 +# 2**4 (= 16) +MAX_DEPOSITS: 16 +# 2**4 (= 16) +MAX_VOLUNTARY_EXITS: 16 +# Signature domains +# --------------------------------------------------------------- +DOMAIN_BEACON_PROPOSER: 0x00000000 +DOMAIN_BEACON_ATTESTER: 0x01000000 +DOMAIN_RANDAO: 0x02000000 +DOMAIN_DEPOSIT: 0x03000000 +DOMAIN_VOLUNTARY_EXIT: 0x04000000 +DOMAIN_SELECTION_PROOF: 0x05000000 +DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 +DOMAIN_SYNC_COMMITTEE: 0x07000000 +DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: 0x08000000 +DOMAIN_CONTRIBUTION_AND_PROOF: 0x09000000 + +# Altair +ALTAIR_FORK_VERSION: 0x0100006f +ALTAIR_FORK_EPOCH: 90 # Mon Oct 10 2022 12:00:00 GMT+0000 +# Bellatrix +BELLATRIX_FORK_VERSION: 0x0200006f +BELLATRIX_FORK_EPOCH: 180 # Mon Oct 10 2022 14:00:00 GMT+0000 +# Capella +CAPELLA_FORK_VERSION: 0x0300006f +CAPELLA_FORK_EPOCH: 244224 # Wed May 24 2023 13:12:00 GMT+0000 + +INACTIVITY_SCORE_BIAS: 4 +# 2**4 (= 16) +INACTIVITY_SCORE_RECOVERY_RATE: 16 diff --git a/common/eth2_network_config/built_in_network_configs/chiado/deploy_block.txt b/common/eth2_network_config/built_in_network_configs/chiado/deploy_block.txt new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/common/eth2_network_config/built_in_network_configs/chiado/deploy_block.txt @@ -0,0 +1 @@ +0 diff --git a/common/eth2_network_config/src/lib.rs b/common/eth2_network_config/src/lib.rs index 1206b8b8caa..769f656e85d 100644 --- a/common/eth2_network_config/src/lib.rs +++ b/common/eth2_network_config/src/lib.rs @@ -482,10 +482,10 @@ mod tests { fn hard_coded_nets_work() { for net in HARDCODED_NETS { let config = Eth2NetworkConfig::from_hardcoded_net(net) - .unwrap_or_else(|_| panic!("{:?}", net.name)); + .unwrap_or_else(|e| panic!("{:?}: {:?}", net.name, e)); // Ensure we can parse the YAML config to a chain spec. - if net.name == types::GNOSIS { + if config.config.preset_base == types::GNOSIS { config.chain_spec::<GnosisEthSpec>().unwrap(); } else { config.chain_spec::<MainnetEthSpec>().unwrap(); From 41ac9b6a08725e66b617427caa68bc413bb7f4cd Mon Sep 17 00:00:00 2001 From: Jimmy Chen <jchen.tc@gmail.com> Date: Wed, 30 Aug 2023 21:41:35 +1000 Subject: [PATCH 23/25] Pin foundry toolchain version to fix stuck CI jobs --- .github/workflows/test-suite.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 91a0b734537..5819f804847 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -67,6 +67,8 @@ jobs: run: rustup update stable - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - name: Run tests in release run: make test-release release-tests-windows: @@ -88,6 +90,8 @@ jobs: npm config set msvs_version 2019 - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - name: Install make run: choco install -y make - uses: KyleMayes/install-llvm-action@v1 @@ -143,6 +147,8 @@ jobs: run: rustup update stable - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - name: Run tests in debug run: make test-debug state-transition-vectors-ubuntu: @@ -189,6 +195,8 @@ jobs: run: rustup update stable - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - name: Run the beacon chain sim that starts from an eth1 contract run: cargo run --release --bin simulator eth1-sim merge-transition-ubuntu: @@ -201,6 +209,8 @@ jobs: run: rustup update stable - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - 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: @@ -223,6 +233,8 @@ jobs: run: rustup update stable - name: Install Foundry (anvil) uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-ca67d15f4abd46394b324c50e21e66f306a1162d - name: Run the syncing simulator run: cargo run --release --bin simulator syncing-sim doppelganger-protection-test: From e99ba3a14e5f85011ffed081c9c4cb1dabb772fe Mon Sep 17 00:00:00 2001 From: Paul Hauner <paul@paulhauner.com> Date: Thu, 31 Aug 2023 02:12:35 +0000 Subject: [PATCH 24/25] Release v4.4.0 (#4673) ## Issue Addressed NA ## Proposed Changes Bump versions from `v4.3.0` to `v4.4.0`. ## Additional Info NA --- Cargo.lock | 8 ++++---- beacon_node/Cargo.toml | 2 +- boot_node/Cargo.toml | 2 +- common/lighthouse_version/src/lib.rs | 4 ++-- lcli/Cargo.toml | 2 +- lighthouse/Cargo.toml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27d7b655e30..9afe6fe183f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,7 +606,7 @@ dependencies = [ [[package]] name = "beacon_node" -version = "4.3.0" +version = "4.4.0" dependencies = [ "beacon_chain", "clap", @@ -805,7 +805,7 @@ dependencies = [ [[package]] name = "boot_node" -version = "4.3.0" +version = "4.4.0" dependencies = [ "beacon_node", "clap", @@ -3880,7 +3880,7 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lcli" -version = "4.3.0" +version = "4.4.0" dependencies = [ "account_utils", "beacon_chain", @@ -4467,7 +4467,7 @@ dependencies = [ [[package]] name = "lighthouse" -version = "4.3.0" +version = "4.4.0" dependencies = [ "account_manager", "account_utils", diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index a0e46705b74..e7d24e8ab46 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "beacon_node" -version = "4.3.0" +version = "4.4.0" authors = [ "Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com", diff --git a/boot_node/Cargo.toml b/boot_node/Cargo.toml index c3dd3bd1936..3db521c0e27 100644 --- a/boot_node/Cargo.toml +++ b/boot_node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "boot_node" -version = "4.3.0" +version = "4.4.0" authors = ["Sigma Prime <contact@sigmaprime.io>"] edition = "2021" diff --git a/common/lighthouse_version/src/lib.rs b/common/lighthouse_version/src/lib.rs index e874432fbca..65f57531d33 100644 --- a/common/lighthouse_version/src/lib.rs +++ b/common/lighthouse_version/src/lib.rs @@ -17,8 +17,8 @@ pub const VERSION: &str = git_version!( // NOTE: using --match instead of --exclude for compatibility with old Git "--match=thiswillnevermatchlol" ], - prefix = "Lighthouse/v4.3.0-", - fallback = "Lighthouse/v4.3.0" + prefix = "Lighthouse/v4.4.0-", + fallback = "Lighthouse/v4.4.0" ); /// Returns `VERSION`, but with platform information appended to the end. diff --git a/lcli/Cargo.toml b/lcli/Cargo.toml index f9d0a6a31c7..8b838ac6f82 100644 --- a/lcli/Cargo.toml +++ b/lcli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lcli" description = "Lighthouse CLI (modeled after zcli)" -version = "4.3.0" +version = "4.4.0" authors = ["Paul Hauner <paul@paulhauner.com>"] edition = "2021" diff --git a/lighthouse/Cargo.toml b/lighthouse/Cargo.toml index 8003236f2df..d836c4d96de 100644 --- a/lighthouse/Cargo.toml +++ b/lighthouse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lighthouse" -version = "4.3.0" +version = "4.4.0" authors = ["Sigma Prime <contact@sigmaprime.io>"] edition = "2021" autotests = false From 74eb26764371ba86d32ed7fe2b2fe6a83562b053 Mon Sep 17 00:00:00 2001 From: Michael Sproul <michael@sigmaprime.io> Date: Thu, 31 Aug 2023 11:18:00 +0000 Subject: [PATCH 25/25] Remove double-locking deadlock from HTTP API (#4687) ## Issue Addressed Fix a deadlock introduced in #4236 which was caught during the v4.4.0 release testing cycle (with thanks to @paulhauner and `gdb`). ## Proposed Changes Avoid re-locking the fork choice read lock when querying a state by root in the HTTP API. This avoids a deadlock due to the lock already being held. ## Additional Info The [RwLock docs](https://docs.rs/lock_api/latest/lock_api/struct.RwLock.html#method.read) explicitly advise against re-locking: > Note that attempts to recursively acquire a read lock on a RwLock when the current thread already holds one may result in a deadlock. --- beacon_node/http_api/src/state_id.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/beacon_node/http_api/src/state_id.rs b/beacon_node/http_api/src/state_id.rs index 5e86053771e..1a76333e2d4 100644 --- a/beacon_node/http_api/src/state_id.rs +++ b/beacon_node/http_api/src/state_id.rs @@ -89,9 +89,7 @@ impl StateId { } 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() + fork_choice .is_optimistic_or_invalid_block(&hot_summary.latest_block_root) .map_err(BeaconChainError::ForkChoiceError) .map_err(warp_utils::reject::beacon_chain_error)?