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

Resolve conflicting MEV-Boost relay slot data. #460

Merged
merged 1 commit into from
Mar 5, 2025
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
1 change: 0 additions & 1 deletion crates/rbuilder/src/backtest/fetch/mev_boost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ impl Default for PayloadDeliveredFetcher {
MevBoostRelaySlotInfoProvider::new(
RelayClient::from_known_relay(r.clone()),
r.name(),
0,
)
})
.collect::<Vec<_>>();
Expand Down
2 changes: 1 addition & 1 deletion crates/rbuilder/src/bin/dummy-builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async fn main() -> eyre::Result<()> {

let flashbots_relay_url = "https://0xac6e77dfe25ecd6110b8e780608cce0dab71fdd5ebea22a16c0205200f2f8e2e3ad3b71d3499c54ad14d6c21b41a37ae@boost-relay.flashbots.net";
let relay_client = RelayClient::from_url(flashbots_relay_url.parse()?, None, None, None);
let relay = MevBoostRelaySlotInfoProvider::new(relay_client, "flashbots".to_string(), 0);
let relay = MevBoostRelaySlotInfoProvider::new(relay_client, "flashbots".to_string());
let blocklist_provider = Arc::new(NullBlockListProvider::new());
let payload_event = MevBoostSlotDataGenerator::new(
vec![Client::default()],
Expand Down
25 changes: 11 additions & 14 deletions crates/rbuilder/src/live_builder/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ impl L1Config {
submitters: &mut Vec<MevBoostRelayBidSubmitter>,
slot_info_providers: &mut Vec<MevBoostRelaySlotInfoProvider>,
) -> eyre::Result<()> {
if relay_config.priority.is_some() {
warn!(
relay = relay_config.name,
"Deprecated: relay priority set, ignoring"
);
}

if relay_config.mode.submits_bids() {
if let Some(submit_config) = &relay_config.submit_config {
submitters.push(MevBoostRelayBidSubmitter::new(
Expand All @@ -184,19 +191,10 @@ impl L1Config {
}
}
if relay_config.mode.gets_slot_info() {
if let Some(priority) = &relay_config.priority {
slot_info_providers.push(MevBoostRelaySlotInfoProvider::new(
client.clone(),
relay_config.name.clone(),
*priority,
));
} else {
eyre::bail!(
"Relay {} in mode {:?} has no priority",
relay_config.name,
relay_config.mode
);
}
slot_info_providers.push(MevBoostRelaySlotInfoProvider::new(
client.clone(),
relay_config.name.clone(),
));
}
Ok(())
}
Expand Down Expand Up @@ -791,7 +789,6 @@ mod test {
let (_, slot_info_providers) = config.l1_config.create_relays().unwrap();
assert_eq!(slot_info_providers.len(), 1);
assert_eq!(slot_info_providers[0].id(), "playground");
assert_eq!(slot_info_providers[0].priority(), 10);
}

#[test]
Expand Down
157 changes: 127 additions & 30 deletions crates/rbuilder/src/live_builder/payload_events/relay_epoch_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ use crate::{
primitives::mev_boost::{MevBoostRelayID, MevBoostRelaySlotInfoProvider},
telemetry::{inc_conn_relay_errors, inc_other_relay_errors, inc_too_many_req_relay_errors},
};
use ahash::HashMap;
use alloy_primitives::Address;
use futures::stream::FuturesOrdered;
use primitive_types::H384;
use tokio_stream::StreamExt;
use tracing::{info_span, trace, warn};
use tracing::{info, info_span, trace, trace_span, warn};

/// Info about a slot obtained from a relay.
#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
Expand Down Expand Up @@ -72,15 +73,9 @@ pub struct RelaysForSlotData {

impl RelaysForSlotData {
pub fn new(relays: &[MevBoostRelaySlotInfoProvider]) -> Self {
// we sort relays so the relay with the highest priority will determine what is "correct" version of the epoch data.
let sorted_relays = {
let mut relays = relays.to_vec();
relays.sort_by_key(|r| r.priority());
relays
};
Self {
relay: sorted_relays
.into_iter()
relay: relays
.iter()
.map(|relay| (relay.id().clone(), RelayEpochCache::new(relay.clone())))
.collect(),
}
Expand All @@ -99,8 +94,7 @@ impl RelaysForSlotData {
.collect::<Vec<_>>()
.await;

let mut slot_data = None;
let mut relays = Vec::new();
let mut relay_ok_res = Vec::new();
for (relay, res) in relay_res {
let span = info_span!("relay", relay, slot);
let _span_guard = span.enter();
Expand Down Expand Up @@ -131,25 +125,128 @@ impl RelaysForSlotData {
}
};
assert_eq!(relay_data.slot, slot);
let relay_slot_data = SlotData {
fee_recipient: relay_data.entry.message.fee_recipient,
gas_limit: relay_data.entry.message.gas_limit,
pubkey: relay_data.entry.message.pubkey,
};
if let Some(slot_data) = &slot_data {
if slot_data != &relay_slot_data {
warn!(
relay_slot_data = ?relay_slot_data, slot_data = ?slot_data,
"Relay returned slot data that is different from returned from other relay",
);
continue;
}
} else {
// since relays are sorted the relay with the highest priority will determine the value of slot_data
slot_data = Some(relay_slot_data);
}
relays.push(relay);
relay_ok_res.push((relay, relay_data));
}
slot_data.map(|d| (d, relays))
resolve_relay_slot_data(relay_ok_res)
}
}

fn resolve_relay_slot_data(
fetched_data: Vec<(MevBoostRelayID, ValidatorSlotData)>,
) -> Option<(SlotData, Vec<MevBoostRelayID>)> {
if fetched_data.is_empty() {
return None;
}

let mut slot_relays: HashMap<SlotData, Vec<MevBoostRelayID>> = HashMap::default();
let mut slot_raw_data: HashMap<SlotData, Vec<ValidatorSlotData>> = HashMap::default();

for (relay, raw_data) in fetched_data {
let slot_data = SlotData {
fee_recipient: raw_data.entry.message.fee_recipient,
gas_limit: raw_data.entry.message.gas_limit,
pubkey: raw_data.entry.message.pubkey,
};
slot_relays
.entry(slot_data.clone())
.or_default()
.push(relay);
slot_raw_data.entry(slot_data).or_default().push(raw_data);
}

// all relays returned the same data
if slot_relays.len() == 1 {
let (slot_data, relays) = slot_relays.into_iter().next().unwrap();
return Some((slot_data, relays));
}

let (latest_slot_data, _) = slot_raw_data
.iter()
.max_by_key(|(_, v)| {
v.iter()
.map(|r| r.entry.message.timestamp)
.max()
.unwrap_or_default()
})
.unwrap();
let selected_relays = slot_relays.get(latest_slot_data).unwrap();

let span = trace_span!("raw_relay_data", ?slot_raw_data);
let _span_guard = span.enter();
info!(all_data = ?slot_relays, ?selected_relays, "Relays returned different slot data");

Some(slot_relays.remove_entry(latest_slot_data).unwrap())
}

#[cfg(test)]
mod test {
use crate::{
mev_boost::{ValidatorRegistration, ValidatorRegistrationMessage},
utils::set_test_debug_tracing_subscriber,
};

use super::*;
use alloy_primitives::{address, Bytes};

fn make_test_data(fee_recipient: Address, timestamp: u64) -> ValidatorSlotData {
ValidatorSlotData {
entry: ValidatorRegistration {
message: ValidatorRegistrationMessage {
fee_recipient,
gas_limit: 30000000,
timestamp,
pubkey: H384::zero(),
},
signature: Bytes::new(),
},
validator_index: 1,
slot: 2,
}
}

#[test]
fn test_resolve_slot_data() {
set_test_debug_tracing_subscriber();

// Test when all relays return same data
let relay1 = MevBoostRelayID::from("relay1");
let relay2 = MevBoostRelayID::from("relay2");
let data = make_test_data(address!("1111111111111111111111111111111111111111"), 100);

let fetched = vec![
(relay1.clone(), data.clone()),
(relay2.clone(), data.clone()),
];

let result = resolve_relay_slot_data(fetched);
let (slot_data, relays) = result.unwrap();
assert_eq!(
SlotData {
fee_recipient: address!("1111111111111111111111111111111111111111"),
gas_limit: 30000000,
pubkey: H384::zero(),
},
slot_data
);
assert_eq!(relays, vec![relay1.clone(), relay2.clone()]);

// Test when relays return different data (should pick latest timestamp)
let data2 = make_test_data(address!("2222222222222222222222222222222222222222"), 200);
let fetched = vec![
(relay1.clone(), data.clone()),
(relay2.clone(), data2.clone()),
];

let result = resolve_relay_slot_data(fetched);
let (slot_data, relays) = result.unwrap();
assert_eq!(
SlotData {
fee_recipient: address!("2222222222222222222222222222222222222222"),
gas_limit: 30000000,
pubkey: H384::zero(),
},
slot_data
);
assert_eq!(relays, vec![relay2.clone()]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@ enabled_relays = ["playground"]
[[relays]]
name = "playground"
url = "http://example.com"
priority = 10
mode = "full"
21 changes: 5 additions & 16 deletions crates/rbuilder/src/primitives/mev_boost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,14 @@ pub struct RelayConfig {
pub builder_id_header: Option<String>,
#[serde(default, deserialize_with = "deserialize_env_var")]
pub api_token_header: Option<String>,
/// mode defines the need of submit_config/priority
/// mode defines the need of submit_config
#[serde(default)]
pub mode: RelayMode,
#[serde(flatten)]
/// Submit specific info.
/// Used only for Full and Fake mode.
pub submit_config: Option<RelaySubmitConfig>,
/// priority when getting slot data so solve unconsistencies. Lower number -> higher priority.
/// Used only for Full and GetSlotInfoOnly mode.
/// Deprecated field that is not used
pub priority: Option<usize>,
}

Expand Down Expand Up @@ -182,24 +181,15 @@ pub struct MevBoostRelaySlotInfoProvider {
/// Id for UI
id: MevBoostRelayID,
client: RelayClient,
/// Lower priority -> more important.
priority: usize,
}

impl MevBoostRelaySlotInfoProvider {
pub fn new(client: RelayClient, id: String, priority: usize) -> Self {
Self {
client,
id,
priority,
}
pub fn new(client: RelayClient, id: String) -> Self {
Self { client, id }
}
pub fn id(&self) -> &MevBoostRelayID {
&self.id
}
pub fn priority(&self) -> usize {
self.priority
}

/// A little ugly, needed for backtest payload fetcher.
pub fn client(&self) -> RelayClient {
Expand Down Expand Up @@ -234,7 +224,6 @@ mod test {
let example = "
name = 'relay1'
url = 'url'
priority = 0
authorization_header = 'env:XXX'
builder_id_header = 'env:YYY'
api_token_header = 'env:ZZZ'
Expand All @@ -248,7 +237,7 @@ mod test {
let config: RelayConfig = toml::from_str(example).unwrap();
assert_eq!(config.name, "relay1");
assert_eq!(config.url, "url");
assert_eq!(config.priority, Some(0));
assert_eq!(config.priority, None);
assert_eq!(config.authorization_header.unwrap(), "AAA");
assert_eq!(config.builder_id_header.unwrap(), "BBB");
assert_eq!(config.api_token_header.unwrap(), "CCC");
Expand Down
2 changes: 1 addition & 1 deletion crates/test-relay/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async fn main() -> eyre::Result<()> {
let relay = {
let url: Url = cli.relay.parse()?;
let client = RelayClient::from_url(url, None, None, None);
MevBoostRelaySlotInfoProvider::new(client, "relay".to_string(), 1)
MevBoostRelaySlotInfoProvider::new(client, "relay".to_string())
};

let validation_client = if let Some(url) = cli.validation_url {
Expand Down
Loading