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

feat(sidecar): consensus state validation #98

Merged
merged 3 commits into from
Jul 1, 2024
Merged
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
9 changes: 9 additions & 0 deletions bolt-sidecar/bin/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bolt_sidecar::{
spec::ConstraintsApi,
start_builder_proxy,
state::{
consensus,
fetcher::{StateClient, StateFetcher},
ExecutionState,
},
Expand Down Expand Up @@ -41,6 +42,8 @@ async fn main() -> eyre::Result<()> {
let head = state_client.get_head().await?;
let mut execution_state = ExecutionState::new(state_client, ChainHead::new(0, head)).await?;

let mut consensus_state = consensus::ConsensusState::new(&config.beacon_client_url);

let shutdown_tx = start_server(config, api_events).await?;

let builder_proxy_config = BuilderProxyConfig::default();
Expand All @@ -66,6 +69,12 @@ async fn main() -> eyre::Result<()> {
tracing::info!("Received commitment request: {:?}", event.request);
let request = event.request;

if let Err (e) = consensus_state.validate_request(&CommitmentRequest::Inclusion(request.clone())) {
tracing::error!("Failed to validate request: {:?}", e);
let _ = event.response.send(Err(ApiError::Custom(e.to_string())));
continue;
}

if let Err(e) = execution_state
.try_commit(&CommitmentRequest::Inclusion(request.clone()))
.await
Expand Down
81 changes: 78 additions & 3 deletions bolt-sidecar/src/state/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,38 @@
#![allow(unused_variables)]
#![allow(missing_debug_implementations)]

use beacon_api_client::{mainnet::Client, BlockId};
use beacon_api_client::{mainnet::Client, BlockId, ProposerDuty};
use ethereum_consensus::deneb::BeaconBlockHeader;
use reqwest::Url;
use std::time::{SystemTime, UNIX_EPOCH};

use crate::primitives::ChainHead;
use crate::primitives::{ChainHead, CommitmentRequest, Slot};

// The slot inclusion deadline in seconds
const INCLUSION_DEADLINE: u64 = 6;

#[derive(Debug, thiserror::Error)]
pub enum ConsensusError {
#[error("beacon API error: {0}")]
#[error("Beacon API error: {0}")]
BeaconApiError(#[from] beacon_api_client::Error),
#[error("Invalid slot: {0}")]
InvalidSlot(Slot),
#[error("Inclusion deadline exceeded")]
DeadlineExceeded,
}

pub struct Epoch {
pub value: u64,
pub start_slot: Slot,
pub proposer_duties: Vec<ProposerDuty>,
}

pub struct ConsensusState {
beacon_api_client: Client,
header: BeaconBlockHeader,
epoch: Epoch,
// Timestamp when the current slot is received
timestamp: u64,
}

impl ConsensusState {
Expand All @@ -28,9 +45,36 @@ impl ConsensusState {
Self {
beacon_api_client,
header: BeaconBlockHeader::default(),
epoch: Epoch {
value: 0,
start_slot: 0,
proposer_duties: vec![],
},
timestamp: 0,
}
}

/// This function validates the state of the chain against a block. It checks 2 things:
/// 1. The target slot is one of our proposer slots. (TODO)
/// 2. The request hasn't passed the slot deadline.
///
/// TODO: Integrate with the registry to check if we are registered.
pub fn validate_request(&self, request: &CommitmentRequest) -> Result<(), 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 + 32 {
return Err(ConsensusError::InvalidSlot(req.slot));
}

// Check if the request is within the slot inclusion deadline
if self.timestamp + INCLUSION_DEADLINE < current_timestamp() {
return Err(ConsensusError::DeadlineExceeded);
}

Ok(())
}

/// Update the latest head and fetch the relevant data from the beacon chain.
pub async fn update_head(&mut self, head: ChainHead) -> Result<(), ConsensusError> {
let update = self
Expand All @@ -40,6 +84,37 @@ impl ConsensusState {

self.header = update.header.message;

// Update the timestamp with current time
self.timestamp = current_timestamp();

// Get the current value of slot and epoch
let slot = self.header.slot;
let epoch = slot / 32;

// If the epoch has changed, update the proposer duties
if epoch != self.epoch.value {
self.epoch.value = epoch;
self.epoch.start_slot = epoch * 32;

self.fetch_proposer_duties(epoch).await?;
}

Ok(())
}

/// Fetch proposer duties for the given epoch.
async fn fetch_proposer_duties(&mut self, epoch: u64) -> Result<(), ConsensusError> {
let duties = self.beacon_api_client.get_proposer_duties(epoch).await?;

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

/// Get the current timestamp.
fn current_timestamp() -> u64 {
return SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
Comment on lines +114 to +119
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a util function, ideally should be in utils.rs (not sure of the file structure in rust)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NOTE - the precision of the deadline is in seconds. We might want to do it in mili seconds or smaller

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seconds is fine for now!

}