Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
448f36d
Add initial weak subjectivity check
eserilev Apr 22, 2025
48ee262
Add ignore-ws-check flag and changes based on feedback
eserilev Apr 23, 2025
6ac8d99
Add pre-electra calculations
eserilev Apr 23, 2025
c2b9f9f
fix ci
eserilev Apr 23, 2025
1727167
Added tests
eserilev Apr 24, 2025
e6fdd4d
added better test coverage
eserilev Apr 24, 2025
1086b50
update comment
eserilev Apr 24, 2025
c995e6a
update comment
eserilev Apr 24, 2025
54228e0
Resolve merge conflicts
eserilev Oct 14, 2025
b5adc67
Merge branch 'unstable' into weak-subjectivity-startup-check
michaelsproul Nov 19, 2025
8cb1a78
Merge conflicts
eserilev Nov 26, 2025
c09954a
Fix messaging
eserilev Dec 1, 2025
83f6ac4
simlplify cli text
eserilev Dec 1, 2025
3d919f0
Remove pre-electra calc
eserilev Dec 2, 2025
c524e98
Merge branch 'unstable' into weak-subjectivity-startup-check
eserilev Dec 2, 2025
5db3205
Use test spec in InvalidPayloadRig
eserilev Dec 3, 2025
a2886a4
Merge branch 'weak-subjectivity-startup-check' of https://github.com/…
eserilev Dec 3, 2025
bc60964
WS pre-electra set to 5
eserilev Dec 3, 2025
5bb7d46
Fix test
eserilev Dec 3, 2025
d808c07
Fi
eserilev Dec 3, 2025
73e6eb6
Merge conflicts
eserilev Dec 8, 2025
5243aaf
fix
eserilev Dec 8, 2025
ccfbb8a
Fix merge conflicts
eserilev Jan 14, 2026
490ce0c
Merge branch 'unstable' of https://github.com/sigp/lighthouse into we…
eserilev Jan 14, 2026
2ba18d3
Fix
eserilev Jan 14, 2026
d4b3d34
merge sigp/unstable into weak-subjectivity-startup-check
dapplion Feb 10, 2026
0b25b1d
remove accidentally committed .claude/plans file
dapplion Feb 10, 2026
8a62893
Merge branch 'unstable' into weak-subjectivity-startup-check
eserilev Feb 10, 2026
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()
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
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.")
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 @@ -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