Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
27 changes: 26 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,31 @@ 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."
)
}
return Err(
"The current head state is outside the weak subjectivity period. It is highly recommended to purge your db and \
checkpoint sync. Alternatively you can accept the risks and ignore this error with the --ignore-ws-check flag.".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.

We should either summarize "the risks" or link to some resource of our own. Otherwise it's quite opaque to the users.

Copy link
Member Author

Choose a reason for hiding this comment

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

i've summarized the risks and linked to a blog post by vitalik

);
}

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
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
103 changes: 103 additions & 0 deletions consensus/types/src/beacon_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2505,6 +2505,109 @@ impl<E: EthSpec> BeaconState<E> {

Ok(())
}

/// Returns the weak subjectivity period for `self`. This computation takes into
/// account the effect of validator set churn (bounded by `get_balance_churn_limit()` per epoch)
/// A detailed calculation can be found at:
/// https://notes.ethereum.org/@CarlBeek/electra_weak_subjectivity
pub fn compute_weak_subjectivity_period(&self, spec: &ChainSpec) -> Result<Epoch, Error> {
// `SAFETY_DECAY` is defined as the maximum percentage tolerable loss in the one-third
// safety margin of FFG finality. Thus, any attack exploiting the Weak Subjectivity Period has
// a safety margin of at least `1/3 - SAFETY_DECAY/100`.
// Spec: https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/phase0/weak-subjectivity.md?plain=1#L50-L71
const SAFETY_DECAY: u64 = 10;
let total_active_balance = self.get_total_active_balance()?;
let fork_name = self.fork_name_unchecked();

if fork_name.electra_enabled() {
// spec: https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/electra/weak-subjectivity.md?plain=1#L30
// labeled delta in the spec
let balance_churn_limit = self.get_balance_churn_limit(spec)?;
let epochs_for_validator_set_churn = SAFETY_DECAY
.safe_mul(total_active_balance)?
.safe_div(balance_churn_limit.safe_mul(200)?)?;
let ws_period = spec
.min_validator_withdrawability_delay
.safe_mul(epochs_for_validator_set_churn)?;

Ok(ws_period)
} else {
// spec: https://github.com/ethereum/consensus-specs/blob/1937aff86b41b5171a9bc3972515986f1bbbf303/specs/phase0/weak-subjectivity.md?plain=1#L82
let mut ws_period = spec.min_validator_withdrawability_delay;
// labeled N in the spec
let active_validator_count = self
.get_active_validator_indices(self.slot().epoch(E::slots_per_epoch()), spec)?
.len() as u64;
// labeled t in the spec
let total_active_balance_per_validator =
total_active_balance.safe_div(active_validator_count)?;
// labeled T in the spec
let max_effective_balance = spec.max_effective_balance_for_fork(fork_name);
// labled delta in the spec
let validator_churn_limit = self.get_validator_churn_limit(spec)?;
// labeled Delta in the spec
let max_deposits_per_epoch = E::MaxDeposits::to_u64().safe_mul(E::slots_per_epoch())?;

// T * (200 + 3 * D) < t * (200 + 12 * D)
if max_effective_balance.safe_mul(200.safe_add(SAFETY_DECAY.safe_mul(3)?)?)?
< total_active_balance_per_validator
.safe_mul(SAFETY_DECAY.safe_mul(12)?.safe_add(200)?)?
{
// N * (t * (200 + 12 * D) - T * (200 + 3 * D))
let epochs_for_validator_set_churn_numerator = active_validator_count
.safe_mul(total_active_balance_per_validator)?
.safe_mul(200.safe_add(SAFETY_DECAY.safe_mul(12)?)?)?
.safe_sub(
max_effective_balance.safe_mul(SAFETY_DECAY.safe_mul(3)?.safe_add(200)?)?,
)?;

// (600 * delta * (2 * t + T))
let epochs_for_validator_set_churn_denominator =
validator_churn_limit.safe_mul(600)?.safe_mul(
total_active_balance_per_validator
.safe_mul(2)?
.safe_add(max_effective_balance)?,
)?;

// N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T))
let epochs_for_validator_set_churn = epochs_for_validator_set_churn_numerator
.safe_div(epochs_for_validator_set_churn_denominator)?;

// N * (200 + 3 * D)
let epochs_for_balance_top_ups_numerator =
active_validator_count.safe_mul(SAFETY_DECAY.safe_mul(3)?.safe_add(200)?)?;

// (600 * Delta)
let epochs_for_balance_top_ups_denominator =
max_deposits_per_epoch.safe_mul(600)?;

// N * (200 + 3 * D) // (600 * Delta)
let epochs_for_balance_top_ups = epochs_for_balance_top_ups_numerator
.safe_div(epochs_for_balance_top_ups_denominator)?;

ws_period.safe_add_assign(std::cmp::max(
epochs_for_validator_set_churn,
epochs_for_balance_top_ups,
))?;
} else {
// 3 * N * D * t
let numerator = active_validator_count
.safe_mul(3)?
.safe_mul(SAFETY_DECAY)?
.safe_mul(total_active_balance_per_validator)?;

// 200 * (Delta * (T - t))
let denomenator = max_deposits_per_epoch
.safe_mul(max_effective_balance.safe_sub(total_active_balance_per_validator)?)?
.safe_mul(200)?;

// 3 * N * D * t // (200 * Delta * (T - t))
ws_period.safe_add_assign(numerator.safe_div(denomenator)?)?;
}

Ok(ws_period)
}
}
}

impl<E: EthSpec> BeaconState<E> {
Expand Down
15 changes: 15 additions & 0 deletions lighthouse/tests/beacon_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,21 @@ fn paranoid_block_proposal_on() {
.with_config(|config| assert!(config.chain.paranoid_block_proposal));
}

#[test]
fn ignore_ws_check_enabled() {
CommandLineTest::new()
.flag("ignore-ws-check", None)
.run_with_zero_port()
.with_config(|config| assert!(config.chain.ignore_ws_check));
}

#[test]
fn ignore_ws_check_default() {
CommandLineTest::new()
.run_with_zero_port()
.with_config(|config| assert!(!config.chain.ignore_ws_check));
}

#[test]
fn reset_payload_statuses_default() {
CommandLineTest::new()
Expand Down
Loading