Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion beacon_node/beacon_chain/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use std::sync::Arc;
use std::time::Duration;
use store::{Error as StoreError, HotColdDB, ItemStore, KeyValueStoreOp};
use task_executor::{ShutdownReason, TaskExecutor};
use tracing::{debug, error, info};
use tracing::{debug, error, info, warn};
use types::data_column_custody_group::CustodyIndex;
use types::{
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, ColumnIndex, DataColumnSidecarList,
Expand Down Expand Up @@ -845,6 +845,34 @@ where
));
}

// Check if the head snapshot is within the weak subjectivity period
let head_state = &head_snapshot.beacon_state;
let Ok(ws_period) = head_state.compute_weak_subjectivity_period(&self.spec) else {
return Err(format!(
"Unable to compute the weak subjectivity period at the head snapshot slot: {:?}",
head_state.slot()
));
};
if current_slot.epoch(E::slots_per_epoch())
> head_state.slot().epoch(E::slots_per_epoch()) + ws_period
{
if self.chain_config.ignore_ws_check {
warn!(
head_slot=%head_state.slot(),
%current_slot,
"The current head state is outside the weak subjectivity period. It is highly recommended to purge your db and \
checkpoint sync. You are currently running a node that is susceptible to long range attacks. For more information please \
read this blog post: https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity"
)
}
return Err(
"The current head state is outside the weak subjectivity period. It is highly recommended to purge your db and \
checkpoint sync. It is possible to ignore this error with the --ignore-ws-check flag, but this could make the node \
susceptible to long range attacks. For more information please read this blog post: \
https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity".to_string()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice message but I would order as:

  • "The current head state is outside the weak subjectivity period"
  • you are susceptible to attacks
  • read more about attacks here
  • if you understand the risks and want to continue: flag

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated messaging order

);
}

let validator_pubkey_cache = self
.validator_pubkey_cache
.map(|mut validator_pubkey_cache| {
Expand Down
3 changes: 3 additions & 0 deletions beacon_node/beacon_chain/src/chain_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ pub struct ChainConfig {
/// On Holesky there is a block which is added to this set by default but which can be removed
/// by using `--invalid-block-roots ""`.
pub invalid_block_roots: HashSet<Hash256>,
/// When set to true, the beacon node can be started even if the head state is outside the weak subjectivity period.
pub ignore_ws_check: bool,
/// Disable the getBlobs optimisation to fetch blobs from the EL mempool.
pub disable_get_blobs: bool,
/// The node's custody type, determining how many data columns to custody and sample.
Expand Down Expand Up @@ -160,6 +162,7 @@ impl Default for ChainConfig {
block_publishing_delay: None,
data_column_publishing_delay: None,
invalid_block_roots: HashSet::new(),
ignore_ws_check: false,
disable_get_blobs: false,
node_custody_type: NodeCustodyType::Fullnode,
}
Expand Down
1 change: 1 addition & 0 deletions beacon_node/beacon_chain/tests/store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ fn get_harness_import_all_data_columns(
) -> TestHarness {
// Most tests expect to retain historic states, so we use this as the default.
let chain_config = ChainConfig {
ignore_ws_check: true,
reconstruct_historic_states: true,
..ChainConfig::default()
};
Expand Down
49 changes: 47 additions & 2 deletions beacon_node/beacon_chain/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use state_processing::EpochProcessingError;
use state_processing::{per_slot_processing, per_slot_processing::Error as SlotProcessingError};
use std::sync::LazyLock;
use types::{
BeaconState, BeaconStateError, BlockImportSource, Checkpoint, EthSpec, Hash256, Keypair,
MinimalEthSpec, RelativeEpoch, Slot,
BeaconState, BeaconStateError, BlockImportSource, ChainSpec, Checkpoint, EthSpec, ForkName,
Hash256, Keypair, MainnetEthSpec, MinimalEthSpec, RelativeEpoch, Slot,
};

type E = MinimalEthSpec;
Expand All @@ -36,6 +36,27 @@ fn get_harness(validator_count: usize) -> BeaconChainHarness<EphemeralHarnessTyp
)
}

fn get_harness_with_spec(
validator_count: usize,
spec: &ChainSpec,
) -> BeaconChainHarness<EphemeralHarnessType<MainnetEthSpec>> {
let chain_config = ChainConfig {
reconstruct_historic_states: true,
..Default::default()
};
let harness = BeaconChainHarness::builder(MainnetEthSpec)
.spec(spec.clone().into())
.chain_config(chain_config)
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.fresh_ephemeral_store()
.mock_execution_layer()
.build();

harness.advance_slot();

harness
}

fn get_harness_with_config(
validator_count: usize,
chain_config: ChainConfig,
Expand Down Expand Up @@ -1058,3 +1079,27 @@ async fn pseudo_finalize_with_lagging_split_update() {
let expect_true_migration = false;
pseudo_finalize_test_generic(epochs_per_migration, expect_true_migration).await;
}

#[tokio::test]
async fn test_compute_weak_subjectivity_period() {
type E = MainnetEthSpec;
let expected_ws_period = 256;

// test Base variant
let spec = ForkName::Altair.make_genesis_spec(E::default_spec());
let harness = get_harness_with_spec(VALIDATOR_COUNT, &spec);
let head_state = harness.get_current_state();

let calculated_ws_period = head_state.compute_weak_subjectivity_period(&spec).unwrap();

assert_eq!(calculated_ws_period, expected_ws_period);

// test Electra variant
let spec = ForkName::Electra.make_genesis_spec(E::default_spec());
let harness = get_harness_with_spec(VALIDATOR_COUNT, &spec);
let head_state = harness.get_current_state();

let calculated_ws_period = head_state.compute_weak_subjectivity_period(&spec).unwrap();

assert_eq!(calculated_ws_period, expected_ws_period);
}
11 changes: 11 additions & 0 deletions beacon_node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1404,6 +1404,17 @@ pub fn cli_app() -> Command {
.help_heading(FLAG_HEADER)
.display_order(0)
)
.arg(
Arg::new("ignore-ws-check")
.long("ignore-ws-check")
.help("The Weak Subjectivity Period is the the maximum time a node can be offline and still \
safely sync back to the canonical chain without the risk of falling victim to long-range attacks. \
This flag disables the Weak Subjectivity check at startup, allowing users to run a node whose current head snapshot \
is outside the Weak Subjectivity Period. It is unsafe to disable the Weak Subjectivity check at startup.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use similar message to the error log and link the post. The user needs to know that this skips a defense for an attack, the details of what the WS are not super relevant and can be delegated to the post

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.action(ArgAction::SetTrue)
.help_heading(FLAG_HEADER)
.display_order(0)
)
.arg(
Arg::new("builder-fallback-skips")
.long("builder-fallback-skips")
Expand Down
2 changes: 2 additions & 0 deletions beacon_node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,8 @@ pub fn get_config<E: EthSpec>(

client_config.chain.paranoid_block_proposal = cli_args.get_flag("paranoid-block-proposal");

client_config.chain.ignore_ws_check = cli_args.get_flag("ignore-ws-check");

/*
* Builder fallback configs.
*/
Expand Down
7 changes: 1 addition & 6 deletions beacon_node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,9 @@ use types::{ChainSpec, Epoch, EthSpec, ForkName};
pub type ProductionClient<E> =
Client<Witness<SystemTimeSlotClock, E, BeaconNodeBackend<E>, BeaconNodeBackend<E>>>;

/// The beacon node `Client` that will be used in production.
/// The beacon node `Client` that is used in production.
///
/// Generic over some `EthSpec`.
///
/// ## Notes:
///
/// Despite being titled `Production...`, this code is not ready for production. The name
/// demonstrates an intention, not a promise.
pub struct ProductionBeaconNode<E: EthSpec>(ProductionClient<E>);

impl<E: EthSpec> ProductionBeaconNode<E> {
Expand Down
7 changes: 7 additions & 0 deletions book/src/help_bn.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,13 @@ Flags:
--http-enable-tls
Serves the RESTful HTTP API server over TLS. This feature is currently
experimental.
--ignore-ws-check
The Weak Subjectivity Period is the the maximum time a node can be
offline and still safely sync back to the canonical chain without the
risk of falling victim to long-range attacks. This flag disables the
Weak Subjectivity check at startup, allowing users to run a node whose
current head snapshot is outside the Weak Subjectivity Period. It is
unsafe to disable the Weak Subjectivity check at startup.
--import-all-attestations
Import and aggregate all attestations, regardless of validator
subscriptions. This will only import attestations from
Expand Down
Loading
Loading