Skip to content
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

enable-unsafe-lookahead flag #330

Merged
merged 6 commits into from
Oct 28, 2024
Merged
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
2 changes: 0 additions & 2 deletions bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,4 @@ async fn main() -> Result<()> {
}
}
}

Ok(())
}
8 changes: 4 additions & 4 deletions bolt-sidecar/src/builder/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,25 +122,25 @@ mod tests {
fn test_compute_builder_domain() {
let mainnet = ChainConfig::mainnet();
assert_eq!(
compute_builder_domain(mainnet.fork_version(), None),
compute_builder_domain(mainnet.chain.fork_version(), None),
mainnet.application_builder_domain()
);

let holesky = ChainConfig::holesky();
assert_eq!(
compute_builder_domain(holesky.fork_version(), None),
compute_builder_domain(holesky.chain.fork_version(), None),
holesky.application_builder_domain()
);

let kurtosis = ChainConfig::kurtosis(0, 0);
assert_eq!(
compute_builder_domain(kurtosis.fork_version(), None),
compute_builder_domain(kurtosis.chain.fork_version(), None),
kurtosis.application_builder_domain()
);

let helder = ChainConfig::helder();
assert_eq!(
compute_builder_domain(helder.fork_version(), None),
compute_builder_domain(helder.chain.fork_version(), None),
helder.application_builder_domain()
);
}
Expand Down
97 changes: 66 additions & 31 deletions bolt-sidecar/src/config/chain.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::time::Duration;
use core::fmt;
use std::{
fmt::{Display, Formatter},
time::Duration,
};

use clap::{Args, ValueEnum};
use ethereum_consensus::deneb::{compute_fork_data_root, Root};
Expand All @@ -20,38 +24,52 @@ pub const APPLICATION_BUILDER_DOMAIN_MASK: [u8; 4] = [0, 0, 0, 1];
/// The domain mask for signing commit-boost messages.
pub const COMMIT_BOOST_DOMAIN_MASK: [u8; 4] = [109, 109, 111, 67];

pub const DEFAULT_CHAIN_CONFIG: ChainConfig = ChainConfig {
chain: Chain::Mainnet,
commitment_deadline: DEFAULT_COMMITMENT_DEADLINE_IN_MILLIS,
slot_time: DEFAULT_SLOT_TIME_IN_SECONDS,
enable_unsafe_lookahead: false,
};

/// Configuration for the chain the sidecar is running on.
/// This allows to customize the slot time for custom Kurtosis devnets.
#[derive(Debug, Clone, Copy, Args, Deserialize)]
pub struct ChainConfig {
/// Chain on which the sidecar is running
#[clap(long, env = "BOLT_SIDECAR_CHAIN", default_value = "mainnet")]
chain: Chain,
#[clap(
long,
env = "BOLT_SIDECAR_CHAIN",
default_value_t = DEFAULT_CHAIN_CONFIG.chain
)]
pub(crate) chain: Chain,
/// The deadline in the slot at which the sidecar will stop accepting
/// new commitments for the next block (parsed as milliseconds).
#[clap(
long,
env = "BOLT_SIDECAR_COMMITMENT_DEADLINE",
default_value_t = DEFAULT_COMMITMENT_DEADLINE_IN_MILLIS
default_value_t = DEFAULT_CHAIN_CONFIG.commitment_deadline
)]
commitment_deadline: u64,
pub(crate) commitment_deadline: u64,
/// The slot time duration in seconds. If provided,
/// it overrides the default for the selected [Chain].
#[clap(
long,
env = "BOLT_SIDECAR_SLOT_TIME",
default_value_t = DEFAULT_SLOT_TIME_IN_SECONDS
default_value_t = DEFAULT_CHAIN_CONFIG.slot_time,
)]
pub(crate) slot_time: u64,
/// Toggle to enable unsafe lookahead for the sidecar. If `true`, commitments requests will be
/// validated against a two-epoch lookahead window.
#[clap(
long,
env = "BOLT_SIDECAR_ENABLE_UNSAFE_LOOKAHEAD",
default_value_t = DEFAULT_CHAIN_CONFIG.enable_unsafe_lookahead
)]
slot_time: u64,
pub(crate) enable_unsafe_lookahead: bool,
}

impl Default for ChainConfig {
fn default() -> Self {
Self {
chain: Chain::Mainnet,
commitment_deadline: DEFAULT_COMMITMENT_DEADLINE_IN_MILLIS,
slot_time: DEFAULT_SLOT_TIME_IN_SECONDS,
}
DEFAULT_CHAIN_CONFIG
}
}

Expand All @@ -65,6 +83,33 @@ pub enum Chain {
Kurtosis,
}

impl Chain {
pub fn name(&self) -> &'static str {
match self {
Chain::Mainnet => "mainnet",
Chain::Holesky => "holesky",
Chain::Helder => "helder",
Chain::Kurtosis => "kurtosis",
}
}

/// Get the fork version for the given chain.
pub fn fork_version(&self) -> [u8; 4] {
match self {
Chain::Mainnet => [0, 0, 0, 0],
Chain::Holesky => [1, 1, 112, 0],
Chain::Helder => [16, 0, 0, 0],
Chain::Kurtosis => [16, 0, 0, 56],
}
}
}

impl Display for Chain {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}

impl ChainConfig {
/// Get the chain ID for the given chain.
pub fn chain_id(&self) -> u64 {
Expand All @@ -78,12 +123,7 @@ impl ChainConfig {

/// Get the chain name for the given chain.
pub fn name(&self) -> &'static str {
match self.chain {
Chain::Mainnet => "mainnet",
Chain::Holesky => "holesky",
Chain::Helder => "helder",
Chain::Kurtosis => "kurtosis",
}
self.chain.name()
}

/// Get the slot time for the given chain in seconds.
Expand All @@ -101,16 +141,6 @@ impl ChainConfig {
self.compute_domain_from_mask(COMMIT_BOOST_DOMAIN_MASK)
}

/// Get the fork version for the given chain.
pub fn fork_version(&self) -> [u8; 4] {
match self.chain {
Chain::Mainnet => [0, 0, 0, 0],
Chain::Holesky => [1, 1, 112, 0],
Chain::Helder => [16, 0, 0, 0],
Chain::Kurtosis => [16, 0, 0, 56],
}
}

/// Get the commitment deadline duration for the given chain.
pub fn commitment_deadline(&self) -> Duration {
Duration::from_millis(self.commitment_deadline)
Expand All @@ -120,7 +150,7 @@ impl ChainConfig {
fn compute_domain_from_mask(&self, mask: [u8; 4]) -> [u8; 32] {
let mut domain = [0; 32];

let fork_version = self.fork_version();
let fork_version = self.chain.fork_version();

// Note: the application builder domain specs require the genesis_validators_root
// to be 0x00 for any out-of-protocol message. The commit-boost domain follows the
Expand Down Expand Up @@ -149,7 +179,12 @@ impl ChainConfig {
}

pub fn kurtosis(slot_time_in_seconds: u64, commitment_deadline: u64) -> Self {
Self { chain: Chain::Kurtosis, slot_time: slot_time_in_seconds, commitment_deadline }
Self {
chain: Chain::Kurtosis,
slot_time: slot_time_in_seconds,
commitment_deadline,
..Default::default()
}
}
}

Expand Down
1 change: 1 addition & 0 deletions bolt-sidecar/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ impl<C: StateFetcher, ECDSA: SignerECDSA> SidecarDriver<C, ECDSA> {
beacon_client,
opts.validator_indexes.clone(),
opts.chain.commitment_deadline(),
opts.chain.enable_unsafe_lookahead,
);

let (payload_requests_tx, payload_requests_rx) = mpsc::channel(16);
Expand Down
88 changes: 78 additions & 10 deletions bolt-sidecar/src/state/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{

use beacon_api_client::{mainnet::Client, ProposerDuty};
use ethereum_consensus::{crypto::PublicKey as BlsPublicKey, phase0::mainnet::SLOTS_PER_EPOCH};
use tokio::join;
use tracing::debug;

use super::CommitmentDeadline;
Expand Down Expand Up @@ -32,10 +33,15 @@ pub enum ConsensusError {

/// Represents an epoch in the beacon chain.
#[derive(Debug, Default)]
#[allow(missing_docs)]
pub struct Epoch {
struct Epoch {
/// The epoch number
pub value: u64,
/// The start slot of the epoch
pub start_slot: Slot,
/// The proposer duties of the epoch.
///
/// NOTE: if the unsafe lookhead flag is enabled, then this field represents the proposer
/// duties also for the next epoch.
pub proposer_duties: Vec<ProposerDuty>,
}

Expand All @@ -58,6 +64,8 @@ pub struct ConsensusState {
pub commitment_deadline: CommitmentDeadline,
/// The duration of the commitment deadline.
commitment_deadline_duration: Duration,
/// If commitment requests should be validated against also against the unsafe lookahead
thedevbirb marked this conversation as resolved.
Show resolved Hide resolved
pub unsafe_lookahead_enabled: bool,
}

impl fmt::Debug for ConsensusState {
Expand All @@ -77,6 +85,7 @@ impl ConsensusState {
beacon_api_client: BeaconClient,
validator_indexes: ValidatorIndexes,
commitment_deadline_duration: Duration,
unsafe_lookahead_enabled: bool,
) -> Self {
ConsensusState {
beacon_api_client,
Expand All @@ -86,6 +95,7 @@ impl ConsensusState {
latest_slot_timestamp: Instant::now(),
commitment_deadline: CommitmentDeadline::new(0, commitment_deadline_duration),
commitment_deadline_duration,
unsafe_lookahead_enabled,
}
}

Expand All @@ -101,14 +111,18 @@ impl ConsensusState {
) -> Result<BlsPublicKey, ConsensusError> {
let CommitmentRequest::Inclusion(req) = request;

// Check if the slot is in the current epoch
if req.slot < self.epoch.start_slot || req.slot >= self.epoch.start_slot + SLOTS_PER_EPOCH {
let furthest_slot = self.epoch.start_slot
mempirate marked this conversation as resolved.
Show resolved Hide resolved
+ SLOTS_PER_EPOCH
+ if self.unsafe_lookahead_enabled { SLOTS_PER_EPOCH } else { 0 };

// Check if the slot is in the current epoch or next epoch (if unsafe lookahead is enabled)
if req.slot < self.epoch.start_slot || req.slot >= furthest_slot {
return Err(ConsensusError::InvalidSlot(req.slot));
}

// If the request is for the next slot, check if it's within the commitment deadline
if req.slot == self.latest_slot + 1 &&
self.latest_slot_timestamp + self.commitment_deadline_duration < Instant::now()
if req.slot == self.latest_slot + 1
&& self.latest_slot_timestamp + self.commitment_deadline_duration < Instant::now()
{
return Err(ConsensusError::DeadlineExceeded);
}
Expand Down Expand Up @@ -151,11 +165,27 @@ impl ConsensusState {
Ok(())
}

/// Fetch proposer duties for the given epoch.
/// Fetch proposer duties for the given epoch and the next one if the unsafe lookahead flag is set
async fn fetch_proposer_duties(&mut self, epoch: u64) -> Result<(), ConsensusError> {
let duties = self.beacon_api_client.get_proposer_duties(epoch).await?;
let duties = if self.unsafe_lookahead_enabled {
let two_epoch_duties = join!(
self.beacon_api_client.get_proposer_duties(epoch),
self.beacon_api_client.get_proposer_duties(epoch + 1)
);

match two_epoch_duties {
(Ok((_, mut duties)), Ok((_, next_duties))) => {
duties.extend(next_duties);
duties
}
(Err(e), _) | (_, Err(e)) => return Err(ConsensusError::BeaconApiError(e)),
}
} else {
self.beacon_api_client.get_proposer_duties(epoch).await?.1
};

self.epoch.proposer_duties = duties;

self.epoch.proposer_duties = duties.1;
Ok(())
}

Expand All @@ -174,7 +204,7 @@ impl ConsensusState {

#[cfg(test)]
mod tests {
use beacon_api_client::ProposerDuty;
use beacon_api_client::{BlockId, ProposerDuty};
use reqwest::Url;
use tracing::warn;

Expand Down Expand Up @@ -202,6 +232,7 @@ mod tests {
validator_indexes,
commitment_deadline_duration: Duration::from_secs(1),
latest_slot: 0,
unsafe_lookahead_enabled: false,
};

// Test finding a valid slot
Expand Down Expand Up @@ -238,6 +269,7 @@ mod tests {
validator_indexes,
commitment_deadline: CommitmentDeadline::new(0, commitment_deadline_duration),
commitment_deadline_duration,
unsafe_lookahead_enabled: false,
};

// Update the slot to 32
Expand All @@ -260,4 +292,40 @@ mod tests {

Ok(())
}

#[tokio::test]
async fn test_fetch_proposer_duties() -> eyre::Result<()> {
let _ = tracing_subscriber::fmt::try_init();

let Some(url) = try_get_beacon_api_url().await else {
warn!("skipping test: beacon API URL is not reachable");
return Ok(());
};

let beacon_client = BeaconClient::new(Url::parse(url).unwrap());

let commitment_deadline_duration = Duration::from_secs(1);

// Create the initial ConsensusState
let mut state = ConsensusState {
beacon_api_client: beacon_client,
epoch: Epoch::default(),
latest_slot: Default::default(),
latest_slot_timestamp: Instant::now(),
validator_indexes: Default::default(),
commitment_deadline: CommitmentDeadline::new(0, commitment_deadline_duration),
commitment_deadline_duration,
// We test for both epochs
unsafe_lookahead_enabled: true,
};

let epoch =
state.beacon_api_client.get_beacon_header(BlockId::Head).await?.header.message.slot
/ SLOTS_PER_EPOCH;

state.fetch_proposer_duties(epoch).await?;
assert_eq!(state.epoch.proposer_duties.len(), SLOTS_PER_EPOCH as usize * 2);

Ok(())
}
}
Loading