Skip to content

Implement weak subjectivity safety checks #7347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 @@ -40,7 +40,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::{
BeaconBlock, BeaconState, BlobSidecarList, ChainSpec, Checkpoint, DataColumnSidecarList, Epoch,
EthSpec, FixedBytesExtended, Hash256, Signature, SignedBeaconBlock, Slot,
Expand Down Expand Up @@ -816,6 +816,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()
);
}

let validator_pubkey_cache = self
.validator_pubkey_cache
.map(|mut validator_pubkey_cache| {
Expand Down
4 changes: 4 additions & 0 deletions beacon_node/beacon_chain/src/chain_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ 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,
}

impl Default for ChainConfig {
Expand Down Expand Up @@ -155,6 +158,7 @@ impl Default for ChainConfig {
block_publishing_delay: None,
data_column_publishing_delay: None,
invalid_block_roots: HashSet::new(),
ignore_ws_check: false,
}
}
}
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 @@ -97,6 +97,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 @@ -12,8 +12,8 @@ use operation_pool::PersistedOperationPool;
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 @@ -35,6 +35,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 @@ -1043,3 +1064,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 @@ -1414,6 +1414,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.")
.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 @@ -834,6 +834,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 @@ -29,14 +29,9 @@ pub type ProductionClient<E> = Client<
>,
>;

/// 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