Skip to content

Commit aef9b98

Browse files
committed
debugging and auth updates
1 parent 3b62fe0 commit aef9b98

File tree

8 files changed

+576
-521
lines changed

8 files changed

+576
-521
lines changed

Cargo.lock

Lines changed: 483 additions & 414 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ alloy = { version = "1.0.19", features = [
3737
"json-rpc",
3838
"signer-aws",
3939
"rpc-types-mev",
40+
"rpc-client",
41+
"rpc-types-debug",
4042
"rlp",
4143
"node-bindings",
4244
"serde",
@@ -58,3 +60,6 @@ tokio-stream = "0.1.17"
5860
url = "2.5.4"
5961
tracing = "0.1.41"
6062
async-trait = "0.1.88"
63+
tower = "0.4"
64+
bytes = "1"
65+
http = "0.2"

src/tasks/submit/flashbots/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/// implements a bundle provider API for building Flashbots
44
/// compatible MEV bundles
55
pub mod provider;
6-
pub use provider::FlashbotsProvider;
6+
pub use provider::Flashbots;
77

88
/// handles the lifecyle of receiving, preparing, and submitting
99
/// a rollup block to the Flashbots network.
Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,109 @@
11
//! A generic Flashbots bundle API wrapper.
2+
use std::io::Read;
3+
24
use crate::config::BuilderConfig;
35
use alloy::{
4-
primitives::BlockNumber,
6+
primitives::{BlockNumber, keccak256, ruint::aliases::B256},
57
rpc::types::mev::{EthBundleHash, MevSendBundle},
8+
signers::Signer,
69
};
10+
use axum::body;
711
use eyre::Context as _;
8-
use eyre::eyre;
9-
use init4_bin_base::deps::tracing::debug;
10-
use reqwest::Client as HttpClient;
12+
13+
use reqwest::header::CONTENT_TYPE;
1114
use serde_json::json;
1215

16+
use init4_bin_base::utils::signer::LocalOrAws;
17+
1318
/// A wrapper over a `Provider` that adds Flashbots MEV bundle helpers.
1419
#[derive(Debug)]
15-
pub struct FlashbotsProvider {
20+
pub struct Flashbots {
1621
/// The base URL for the Flashbots API.
1722
pub relay_url: url::Url,
18-
/// Inner HTTP client used for JSON-RPC requests to the relay.
19-
pub inner: HttpClient,
2023
/// Builder configuration for the task.
2124
pub config: BuilderConfig,
25+
/// Signer is loaded once at startup.
26+
signer: LocalOrAws,
2227
}
2328

24-
impl FlashbotsProvider {
29+
impl Flashbots {
2530
/// Wraps a provider with the URL and returns a new `FlashbotsProvider`.
26-
pub fn new(config: &BuilderConfig) -> Self {
31+
pub async fn new(config: &BuilderConfig) -> Self {
2732
let relay_url =
2833
config.flashbots_endpoint.as_ref().expect("Flashbots endpoint must be set").clone();
29-
Self { relay_url, inner: HttpClient::new(), config: config.clone() }
34+
35+
let signer =
36+
config.connect_builder_signer().await.expect("Local or AWS signer must be set");
37+
38+
Self { relay_url, config: config.clone(), signer }
3039
}
3140

32-
/// Submit the prepared Flashbots bundle to the relay via `mev_sendBundle`.
41+
/// Sends a bundle via `mev_sendBundle`.
3342
pub async fn send_bundle(&self, bundle: MevSendBundle) -> eyre::Result<EthBundleHash> {
34-
// NB: The Flashbots relay expects a single parameter which is the bundle object.
35-
// Alloy's `raw_request` accepts any serializable params; wrapping in a 1-tuple is fine.
36-
// We POST a JSON-RPC request to the relay URL using our inner HTTP client.
37-
let body =
38-
json!({ "jsonrpc": "2.0", "id": 1, "method": "mev_sendBundle", "params": [bundle] });
39-
let resp = self
40-
.inner
41-
.post(self.relay_url.as_str())
42-
.json(&body)
43-
.send()
44-
.await
45-
.wrap_err("mev_sendBundle HTTP request failed")?;
46-
47-
let v: serde_json::Value =
48-
resp.json().await.wrap_err("failed to parse mev_sendBundle response")?;
49-
if let Some(err) = v.get("error") {
50-
return Err(eyre!("mev_sendBundle error: {}", err));
51-
}
52-
let result = v.get("result").ok_or_else(|| eyre!("mev_sendBundle missing result"))?;
53-
let hash: EthBundleHash = serde_json::from_value(result.clone())
54-
.wrap_err("failed to deserialize mev_sendBundle result")?;
55-
debug!(?hash, "mev_sendBundle response");
43+
let params = serde_json::to_value(bundle)?;
44+
let v = self.raw_call("mev_sendBundle", params).await?;
45+
let hash: EthBundleHash =
46+
serde_json::from_value(v.get("result").cloned().unwrap_or(serde_json::Value::Null))?;
5647
Ok(hash)
5748
}
5849

5950
/// Simulate a bundle via `mev_simBundle`.
6051
pub async fn simulate_bundle(&self, bundle: MevSendBundle) -> eyre::Result<()> {
61-
let body =
62-
json!({ "jsonrpc": "2.0", "id": 1, "method": "mev_simBundle", "params": [bundle] });
63-
let resp = self
64-
.inner
65-
.post(self.relay_url.as_str())
66-
.json(&body)
67-
.send()
68-
.await
69-
.wrap_err("mev_simBundle HTTP request failed")?;
70-
71-
let v: serde_json::Value =
72-
resp.json().await.wrap_err("failed to parse mev_simBundle response")?;
73-
if let Some(err) = v.get("error") {
74-
return Err(eyre!("mev_simBundle error: {}", err));
75-
}
76-
debug!(?v, "mev_simBundle response");
52+
let params = serde_json::to_value(bundle)?;
53+
let _ = self.raw_call("mev_simBundle", params).await?;
7754
Ok(())
7855
}
7956

80-
/// Check that status of a bundle
57+
/// Fetches the bundle status by hash
8158
pub async fn bundle_status(
8259
&self,
8360
_hash: EthBundleHash,
8461
block_number: BlockNumber,
8562
) -> eyre::Result<()> {
8663
let params = json!({ "bundleHash": _hash, "blockNumber": block_number });
87-
let body = json!({ "jsonrpc": "2.0", "id": 1, "method": "flashbots_getBundleStatsV2", "params": [params] });
88-
let resp = self
89-
.inner
64+
let _ = self.raw_call("flashbots_getBundleStatsV2", params).await?;
65+
Ok(())
66+
}
67+
68+
/// Makes a raw JSON-RPC call with the Flashbots signature header to the method with the given params.
69+
async fn raw_call(
70+
&self,
71+
method: &str,
72+
params: serde_json::Value,
73+
) -> eyre::Result<serde_json::Value> {
74+
let params = match params {
75+
serde_json::Value::Array(_) => params,
76+
other => serde_json::Value::Array(vec![other]),
77+
};
78+
79+
let body = json!({"jsonrpc":"2.0","id":1,"method":method,"params":params});
80+
let body_bz = serde_json::to_vec(&body)?;
81+
82+
let payload = format!("0x{:x}", keccak256(body_bz.clone()));
83+
let signature = self.signer.sign_message(payload.as_ref()).await?;
84+
dbg!(signature.to_string());
85+
86+
let address = self.signer.address();
87+
let value = format!("{}:{}", address, signature);
88+
dbg!(value.clone());
89+
90+
let client = reqwest::Client::new();
91+
let resp = client
9092
.post(self.relay_url.as_str())
91-
.json(&body)
93+
.header(CONTENT_TYPE, "application/json")
94+
.header("X-Flashbots-Signature", value)
95+
.body(body_bz)
9296
.send()
93-
.await
94-
.wrap_err("flashbots_getBundleStatsV2 HTTP request failed")?;
95-
97+
.await?;
98+
let text = resp.text().await?;
9699
let v: serde_json::Value =
97-
resp.json().await.wrap_err("failed to parse flashbots_getBundleStatsV2 response")?;
100+
serde_json::from_str(&text).wrap_err("failed to parse flashbots JSON")?;
98101
if let Some(err) = v.get("error") {
99-
return Err(eyre!("flashbots_getBundleStatsV2 error: {}", err));
102+
eyre::bail!("flashbots error: {err}");
100103
}
101-
debug!(?v, "flashbots_getBundleStatsV2 response");
102-
Ok(())
104+
Ok(v)
103105
}
104106
}
105107

106-
// (no additional helpers)
108+
// Raw Flashbots JSON-RPC call with header signing via reqwest.
109+
impl Flashbots {}

src/tasks/submit/flashbots/task.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
quincey::Quincey,
66
tasks::{
77
block::sim::SimResult,
8-
submit::{SubmitPrep, flashbots::FlashbotsProvider},
8+
submit::{SubmitPrep, flashbots::Flashbots},
99
},
1010
utils,
1111
};
@@ -216,7 +216,7 @@ impl FlashbotsTask {
216216
async fn task_future(self, mut inbound: mpsc::UnboundedReceiver<SimResult>) {
217217
debug!("starting flashbots task");
218218

219-
let flashbots = FlashbotsProvider::new(&self.config);
219+
let flashbots = Flashbots::new(&self.config).await;
220220

221221
loop {
222222
// Wait for a sim result to come in

src/tasks/submit/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ pub use builder_helper::{BuilderHelperTask, ControlFlow};
1010

1111
/// Submission logic for Flashbots
1212
pub mod flashbots;
13-
pub use flashbots::{FlashbotsProvider, FlashbotsTask};
13+
pub use flashbots::{Flashbots, FlashbotsTask};

src/test_utils.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
//! Test utilities for testing builder tasks
22
use crate::config::BuilderConfig;
33
use alloy::{
4-
consensus::{SignableTransaction, TxEip1559, TxEnvelope},
5-
primitives::{Address, B256, TxKind, U256},
6-
rpc::client::BuiltInConnectionString,
7-
signers::{SignerSync, local::PrivateKeySigner},
4+
consensus::{SignableTransaction, TxEip1559, TxEnvelope}, primitives::{Address, TxKind, B256, U256}, rpc::client::BuiltInConnectionString, signers::{local::PrivateKeySigner, SignerSync}
85
};
96
use eyre::Result;
107
use init4_bin_base::{
@@ -32,12 +29,13 @@ pub fn setup_test_config() -> Result<BuilderConfig> {
3229
.try_into()
3330
.unwrap(),
3431
tx_broadcast_urls: vec!["http://localhost:9000".into()],
35-
flashbots_endpoint: Some("http://localhost:9062".parse().unwrap()), // NB: Flashbots API default
32+
flashbots_endpoint: Some("https://relay-sepolia.flashbots.net:443".parse().unwrap()), // NB: Flashbots API default
33+
// flashbots_endpoint: Some("https://relay.flashbots.net:443".parse().unwrap()), // NB: Flashbots API default
3634
zenith_address: Address::default(),
3735
quincey_url: "http://localhost:8080".into(),
3836
builder_port: 8080,
3937
sequencer_key: None,
40-
builder_key: "0000000000000000000000000000000000000000000000000000000000000000".into(),
38+
builder_key: PrivateKeySigner::random().to_bytes().to_string(),
4139
builder_rewards_address: Address::default(),
4240
rollup_block_gas_limit: 3_000_000_000,
4341
tx_pool_url: "http://localhost:9000/".parse().unwrap(),

tests/flashbots_provider_test.rs

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,52 +2,32 @@
22
//! These tests require the `FLASHBOTS_ENDPOINT` env var to be set.
33
44
use alloy::{
5-
primitives::FixedBytes,
6-
rpc::types::mev::{EthBundleHash, MevSendBundle},
5+
eips::Encodable2718,
6+
primitives::U256,
7+
rpc::types::mev::{BundleItem, MevSendBundle, ProtocolVersion},
8+
signers::local::PrivateKeySigner,
79
};
8-
use builder::tasks::submit::flashbots::FlashbotsProvider;
9-
use builder::test_utils::{setup_logging, setup_test_config};
10+
use builder::tasks::submit::flashbots::Flashbots;
11+
use builder::test_utils::{new_signed_tx, setup_logging, setup_test_config};
1012

1113
#[tokio::test]
1214
#[ignore = "integration test"]
13-
async fn smoke_root_provider() {
15+
async fn simulate_valid_bundle() {
1416
setup_logging();
15-
let flashbots = get_test_provider().await;
16-
assert_eq!(flashbots.relay_url.as_str(), "http://localhost:9062/");
17-
18-
let status =
19-
flashbots.bundle_status(EthBundleHash { bundle_hash: FixedBytes::default() }, 0).await;
20-
assert!(status.is_err());
21-
}
2217

23-
#[tokio::test]
24-
#[ignore = "integration test"]
25-
async fn smoke_simulate_bundle() {
2618
let flashbots = get_test_provider().await;
2719

28-
let res = flashbots.simulate_bundle(MevSendBundle::default()).await;
20+
let wallet = PrivateKeySigner::random();
21+
let tx = new_signed_tx(&wallet, 0, U256::from(1u64), 51_000).unwrap();
22+
let tx_bytes = tx.encoded_2718().into();
2923

30-
if let Err(err) = &res {
31-
let msg = format!("{err}");
32-
assert!(msg.contains("mev_simBundle"));
33-
}
34-
assert!(res.is_err());
35-
}
36-
37-
#[tokio::test]
38-
#[ignore = "integration test"]
39-
async fn smoke_send_bundle() {
40-
let flashbots = get_test_provider().await;
41-
let res = flashbots.send_bundle(MevSendBundle::default()).await;
24+
let bundle_body = vec![BundleItem::Tx { tx: tx_bytes, can_revert: false }];
25+
let bundle = MevSendBundle::new(0, Some(0), ProtocolVersion::V0_1, bundle_body);
4226

43-
if let Err(err) = &res {
44-
let msg = format!("{err}");
45-
assert!(msg.contains("mev_sendBundle"));
46-
}
47-
assert!(res.is_ok() || res.is_err());
27+
let _ = flashbots.simulate_bundle(bundle).await.expect("failed to simulate bundle");
4828
}
4929

50-
async fn get_test_provider() -> FlashbotsProvider {
30+
async fn get_test_provider() -> Flashbots {
5131
let config = setup_test_config().unwrap();
52-
FlashbotsProvider::new(&config.clone())
32+
Flashbots::new(&config.clone()).await
5333
}

0 commit comments

Comments
 (0)