Skip to content

Commit 895aa20

Browse files
authored
feat(pbs): stateless pbs module (#212)
1 parent c2a0512 commit 895aa20

File tree

8 files changed

+20
-118
lines changed

8 files changed

+20
-118
lines changed

crates/common/src/config/mux.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,7 @@ async fn fetch_lido_registry_keys(
216216
chain: Chain,
217217
node_operator_id: U256,
218218
) -> eyre::Result<Vec<BlsPublicKey>> {
219-
debug!(
220-
"loading operator keys from Lido registry: chain={:?}, node_operator_id={}",
221-
chain, node_operator_id
222-
);
219+
debug!(?chain, %node_operator_id, "loading operator keys from Lido registry");
223220

224221
let provider = ProviderBuilder::new().on_http(rpc_url);
225222
let registry_address = lido_registry_address(chain)?;
@@ -263,7 +260,7 @@ async fn fetch_lido_registry_keys(
263260
}
264261

265262
ensure!(keys.len() == total_keys as usize, "expected {total_keys} keys, got {}", keys.len());
266-
let unique: Vec<_> = keys.iter().collect::<HashSet<_>>().into_iter().collect();
263+
let unique = keys.iter().collect::<HashSet<_>>();
267264
ensure!(unique.len() == keys.len(), "found duplicate keys in registry");
268265

269266
Ok(keys)

crates/common/src/pbs/constants.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ pub const SUBMIT_BLOCK_PATH: &str = "/blinded_blocks";
99

1010
// https://ethereum.github.io/builder-specs/#/Builder
1111

12-
pub const HEADER_SLOT_UUID_KEY: &str = "X-MEVBoost-SlotID";
12+
// Currently unused to enable a stateless default PBS module
13+
// const HEADER_SLOT_UUID_KEY: &str = "X-MEVBoost-SlotID";
1314
pub const HEADER_VERSION_KEY: &str = "X-CommitBoost-Version";
1415
pub const HEADER_VERSION_VALUE: &str = COMMIT_BOOST_VERSION;
1516
pub const HEADER_START_TIME_UNIX_MS: &str = "X-MEVBoost-StartTimeUnixMS";

crates/common/src/pbs/event.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ pub enum BuilderEvent {
3434
MissedPayload {
3535
/// Hash for the block for which no payload was received
3636
block_hash: B256,
37-
/// Relays which delivered the header but for which no payload was
38-
/// received
39-
missing_relays: String,
4037
},
4138
RegisterValidatorRequest(Vec<ValidatorRegistration>),
4239
RegisterValidatorResponse,

crates/pbs/src/mev_boost/get_header.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use cb_common::{
1414
pbs::{
1515
error::{PbsError, ValidationError},
1616
GetHeaderParams, GetHeaderResponse, RelayClient, SignedExecutionPayloadHeader,
17-
EMPTY_TX_ROOT_HASH, HEADER_SLOT_UUID_KEY, HEADER_START_TIME_UNIX_MS,
17+
EMPTY_TX_ROOT_HASH, HEADER_START_TIME_UNIX_MS,
1818
},
1919
signature::verify_signed_message,
2020
types::Chain,
@@ -73,11 +73,8 @@ pub async fn get_header<S: BuilderApiState>(
7373
return Ok(None);
7474
}
7575

76-
let (_, slot_uuid) = state.get_slot_and_uuid();
77-
7876
// prepare headers, except for start time which is set in `send_one_get_header`
7977
let mut send_headers = HeaderMap::new();
80-
send_headers.insert(HEADER_SLOT_UUID_KEY, HeaderValue::from_str(&slot_uuid.to_string())?);
8178
send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?);
8279

8380
let mut handles = Vec::with_capacity(relays.len());
@@ -118,7 +115,9 @@ pub async fn get_header<S: BuilderApiState>(
118115
}
119116
}
120117

121-
Ok(state.add_bids(params.slot, relay_bids))
118+
let max_bid = relay_bids.into_iter().max_by_key(|bid| bid.value());
119+
120+
Ok(max_bid)
122121
}
123122

124123
/// Fetch the parent block from the RPC URL for extra validation of the header.

crates/pbs/src/mev_boost/submit_block.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use axum::http::{HeaderMap, HeaderValue};
44
use cb_common::{
55
pbs::{
66
error::{PbsError, ValidationError},
7-
RelayClient, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, HEADER_SLOT_UUID_KEY,
7+
RelayClient, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse,
88
HEADER_START_TIME_UNIX_MS,
99
},
1010
utils::{get_user_agent_with_version, utcnow_ms},
@@ -27,11 +27,8 @@ pub async fn submit_block<S: BuilderApiState>(
2727
req_headers: HeaderMap,
2828
state: PbsState<S>,
2929
) -> eyre::Result<SubmitBlindedBlockResponse> {
30-
let (_, slot_uuid) = state.get_slot_and_uuid();
31-
3230
// prepare headers
3331
let mut send_headers = HeaderMap::new();
34-
send_headers.insert(HEADER_SLOT_UUID_KEY, HeaderValue::from_str(&slot_uuid.to_string())?);
3532
send_headers.insert(HEADER_START_TIME_UNIX_MS, HeaderValue::from(utcnow_ms()));
3633
send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?);
3734

crates/pbs/src/routes/get_header.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ pub async fn handle_get_header<S: BuilderApiState, A: BuilderApi<S>>(
2727
Path(params): Path<GetHeaderParams>,
2828
) -> Result<impl IntoResponse, PbsClientError> {
2929
state.publish_event(BuilderEvent::GetHeaderRequest(params));
30-
state.get_or_update_slot_uuid(params.slot);
3130

3231
let ua = get_user_agent(&req_headers);
3332
let ms_into_slot = ms_into_slot(params.slot, state.config.chain);

crates/pbs/src/routes/submit_block.rs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cb_common::{
44
utils::{get_user_agent, timestamp_of_slot_start_millis, utcnow_ms},
55
};
66
use reqwest::StatusCode;
7-
use tracing::{error, info, trace, warn};
7+
use tracing::{error, info, trace};
88
use uuid::Uuid;
99

1010
use crate::{
@@ -29,13 +29,8 @@ pub async fn handle_submit_block<S: BuilderApiState, A: BuilderApi<S>>(
2929
let block_hash = signed_blinded_block.message.body.execution_payload_header.block_hash;
3030
let slot_start_ms = timestamp_of_slot_start_millis(slot, state.config.chain);
3131
let ua = get_user_agent(&req_headers);
32-
let (curr_slot, slot_uuid) = state.get_slot_and_uuid();
3332

34-
info!(ua, %slot_uuid, ms_into_slot=now.saturating_sub(slot_start_ms), %block_hash);
35-
36-
if curr_slot != signed_blinded_block.message.slot {
37-
warn!(expected = curr_slot, got = slot, "blinded beacon slot mismatch")
38-
}
33+
info!(ua, ms_into_slot=now.saturating_sub(slot_start_ms), %block_hash);
3934

4035
match A::submit_block(signed_blinded_block, req_headers, state.clone()).await {
4136
Ok(res) => {
@@ -48,24 +43,8 @@ pub async fn handle_submit_block<S: BuilderApiState, A: BuilderApi<S>>(
4843
}
4944

5045
Err(err) => {
51-
if let Some(fault_pubkeys) = state.get_relays_by_block_hash(slot, block_hash) {
52-
let missing_relays = state
53-
.relays()
54-
.iter()
55-
.filter(|relay| fault_pubkeys.contains(&relay.pubkey()))
56-
.map(|relay| &**relay.id)
57-
.collect::<Vec<_>>()
58-
.join(",");
59-
60-
error!(%err, %block_hash, missing_relays, "CRITICAL: no payload received from relays");
61-
state.publish_event(BuilderEvent::MissedPayload { block_hash, missing_relays });
62-
} else {
63-
error!(%err, %block_hash, "CRITICAL: no payload delivered and no relay for block hash. Was getHeader even called?");
64-
state.publish_event(BuilderEvent::MissedPayload {
65-
block_hash,
66-
missing_relays: String::default(),
67-
});
68-
};
46+
error!(%err, %block_hash, "CRITICAL: no payload received from relays. Check previous logs or use the Relay Data API");
47+
state.publish_event(BuilderEvent::MissedPayload { block_hash });
6948

7049
let err = PbsClientError::NoPayload;
7150
BEACON_NODE_STATUS

crates/pbs/src/state.rs

Lines changed: 7 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,30 @@
1-
use std::{
2-
collections::HashSet,
3-
sync::{Arc, Mutex},
4-
};
5-
6-
use alloy::{primitives::B256, rpc::types::beacon::BlsPublicKey};
1+
use alloy::rpc::types::beacon::BlsPublicKey;
72
use cb_common::{
83
config::{PbsConfig, PbsModuleConfig},
9-
pbs::{BuilderEvent, GetHeaderResponse, RelayClient},
4+
pbs::{BuilderEvent, RelayClient},
105
};
11-
use dashmap::DashMap;
12-
use uuid::Uuid;
136

147
pub trait BuilderApiState: Clone + Sync + Send + 'static {}
158
impl BuilderApiState for () {}
169

17-
/// State for the Pbs module. It can be extended by adding extra data to the
18-
/// state
10+
/// Config for the Pbs module. It can be extended by adding extra data to the
11+
/// state for modules that need it
12+
// TODO: consider remove state from the PBS module altogether
1913
#[derive(Clone)]
2014
pub struct PbsState<S: BuilderApiState = ()> {
2115
/// Config data for the Pbs service
2216
pub config: PbsModuleConfig,
2317
/// Opaque extra data for library use
2418
pub data: S,
25-
/// Info about the latest slot and its uuid
26-
current_slot_info: Arc<Mutex<(u64, Uuid)>>,
27-
/// Keeps track of which relays delivered which block for which slot
28-
bid_cache: Arc<DashMap<u64, Vec<GetHeaderResponse>>>,
2919
}
3020

3121
impl PbsState<()> {
3222
pub fn new(config: PbsModuleConfig) -> Self {
33-
Self {
34-
config,
35-
data: (),
36-
current_slot_info: Arc::new(Mutex::new((0, Uuid::new_v4()))),
37-
bid_cache: Arc::new(DashMap::new()),
38-
}
23+
Self { config, data: () }
3924
}
4025

4126
pub fn with_data<S: BuilderApiState>(self, data: S) -> PbsState<S> {
42-
PbsState {
43-
data,
44-
config: self.config,
45-
current_slot_info: self.current_slot_info,
46-
bid_cache: self.bid_cache,
47-
}
27+
PbsState { data, config: self.config }
4828
}
4929
}
5030

@@ -58,22 +38,6 @@ where
5838
}
5939
}
6040

61-
pub fn get_or_update_slot_uuid(&self, last_slot: u64) -> Uuid {
62-
let mut guard = self.current_slot_info.lock().expect("poisoned");
63-
if guard.0 < last_slot {
64-
// new slot
65-
guard.0 = last_slot;
66-
guard.1 = Uuid::new_v4();
67-
self.clear(last_slot);
68-
}
69-
guard.1
70-
}
71-
72-
pub fn get_slot_and_uuid(&self) -> (u64, Uuid) {
73-
let guard = self.current_slot_info.lock().expect("poisoned");
74-
*guard
75-
}
76-
7741
// Getters
7842
pub fn pbs_config(&self) -> &PbsConfig {
7943
&self.config.pbs_config
@@ -102,35 +66,4 @@ where
10266
pub fn extra_validation_enabled(&self) -> bool {
10367
self.config.pbs_config.extra_validation_enabled
10468
}
105-
106-
/// Add some bids to the cache, the bids are all assumed to be for the
107-
/// provided slot Returns the bid with the max value
108-
pub fn add_bids(&self, slot: u64, bids: Vec<GetHeaderResponse>) -> Option<GetHeaderResponse> {
109-
let mut slot_entry = self.bid_cache.entry(slot).or_default();
110-
slot_entry.extend(bids);
111-
slot_entry.iter().max_by_key(|bid| bid.value()).cloned()
112-
}
113-
114-
/// Retrieves a list of relays pubkeys that delivered a given block hash
115-
/// Returns None if we dont have bids for the slot or for the block hash
116-
pub fn get_relays_by_block_hash(
117-
&self,
118-
slot: u64,
119-
block_hash: B256,
120-
) -> Option<HashSet<BlsPublicKey>> {
121-
self.bid_cache.get(&slot).and_then(|bids| {
122-
let filtered: HashSet<_> = bids
123-
.iter()
124-
.filter(|&bid| (bid.block_hash() == block_hash))
125-
.map(|bid| bid.pubkey())
126-
.collect();
127-
128-
(!filtered.is_empty()).then_some(filtered)
129-
})
130-
}
131-
132-
/// Clear bids which are more than ~3 minutes old
133-
fn clear(&self, last_slot: u64) {
134-
self.bid_cache.retain(|slot, _| last_slot.saturating_sub(*slot) < 15)
135-
}
13669
}

0 commit comments

Comments
 (0)