-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds bundle support to block builder (#8)
* WIP: adding bundle fetcher support * WIP: separating auth out into authenticator * WIP: fixing the cannot drop a runtime in a blocking context error * WIP: Fixes tokio runtime drop bug * wip: cleanup and remove imports * chore: fix dep error * chore: mark bundle poller test as it * feat: spawn bundle poller and handle exits * feat: ingest bundle transactions, drop bundle on failure This implements ingesting bundle transactions naively. It does not take into account the bundle "rules", so to say, which would make it a bit more complex. If a transaction fails to decode, the bundle is considered "invalid", and dropped. * chore: clippy * chore: comments * chore: clarify comment on signed orders * chore: dedup oauth, do not use spawn blocking * chore: remove unneeded async * chore: clean up authenticator * feat: track seen bundles to dedup * feat(`oauth`): caching, shareable authenticator (#19) * feat: caching, shareable authenticator * fix: actually spawn builder task lmao * chore: remove unnecesary mut selfs --------- Co-authored-by: evalir <[email protected]>
- Loading branch information
Showing
12 changed files
with
442 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,9 @@ aws-sdk-kms = "1.15.0" | |
hex = { package = "const-hex", version = "1", default-features = false, features = [ | ||
"alloc", | ||
] } | ||
|
||
signet-types = { git = "ssh://[email protected]/init4tech/signet-node.git" } | ||
|
||
serde = { version = "1.0.197", features = ["derive"] } | ||
tracing = "0.1.40" | ||
|
||
|
@@ -41,7 +44,7 @@ openssl = { version = "0.10", features = ["vendored"] } | |
reqwest = { version = "0.11.24", features = ["blocking", "json"] } | ||
ruint = "1.12.1" | ||
serde_json = "1.0" | ||
thiserror = "1.0.58" | ||
thiserror = "1.0.68" | ||
tokio = { version = "1.36.0", features = ["full", "macros", "rt-multi-thread"] } | ||
tracing-subscriber = "0.3.18" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
//! Bundler service responsible for polling and submitting bundles to the in-progress block. | ||
use std::time::{Duration, Instant}; | ||
|
||
pub use crate::config::BuilderConfig; | ||
use alloy_primitives::map::HashMap; | ||
use reqwest::Url; | ||
use serde::{Deserialize, Serialize}; | ||
use signet_types::SignetEthBundle; | ||
use tokio::{sync::mpsc, task::JoinHandle}; | ||
use tracing::debug; | ||
|
||
use oauth2::TokenResponse; | ||
|
||
use super::oauth::Authenticator; | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Bundle { | ||
pub id: String, | ||
pub bundle: SignetEthBundle, | ||
} | ||
|
||
/// Response from the tx-pool containing a list of bundles. | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct TxPoolBundleResponse { | ||
pub bundles: Vec<Bundle>, | ||
} | ||
|
||
pub struct BundlePoller { | ||
pub config: BuilderConfig, | ||
pub authenticator: Authenticator, | ||
pub seen_uuids: HashMap<String, Instant>, | ||
} | ||
|
||
/// Implements a poller for the block builder to pull bundles from the tx cache. | ||
impl BundlePoller { | ||
/// Creates a new BundlePoller from the provided builder config. | ||
pub async fn new(config: &BuilderConfig, authenticator: Authenticator) -> Self { | ||
Self { | ||
config: config.clone(), | ||
authenticator, | ||
seen_uuids: HashMap::new(), | ||
} | ||
} | ||
|
||
/// Fetches bundles from the transaction cache and returns the (oldest? random?) bundle in the cache. | ||
pub async fn check_bundle_cache(&mut self) -> eyre::Result<Vec<Bundle>> { | ||
let mut unique: Vec<Bundle> = Vec::new(); | ||
|
||
let bundle_url: Url = Url::parse(&self.config.tx_pool_url)?.join("bundles")?; | ||
let token = self.authenticator.fetch_oauth_token().await?; | ||
|
||
// Add the token to the request headers | ||
let result = reqwest::Client::new() | ||
.get(bundle_url) | ||
.bearer_auth(token.access_token().secret()) | ||
.send() | ||
.await? | ||
.error_for_status()?; | ||
|
||
let body = result.bytes().await?; | ||
let bundles: TxPoolBundleResponse = serde_json::from_slice(&body)?; | ||
|
||
bundles.bundles.iter().for_each(|bundle| { | ||
self.check_seen_bundles(bundle.clone(), &mut unique); | ||
}); | ||
|
||
Ok(unique) | ||
} | ||
|
||
/// Checks if the bundle has been seen before and if not, adds it to the unique bundles list. | ||
fn check_seen_bundles(&mut self, bundle: Bundle, unique: &mut Vec<Bundle>) { | ||
self.seen_uuids.entry(bundle.id.clone()).or_insert_with(|| { | ||
// add to the set of unique bundles | ||
unique.push(bundle.clone()); | ||
Instant::now() + Duration::from_secs(self.config.tx_pool_cache_duration) | ||
}); | ||
} | ||
|
||
/// Evicts expired bundles from the cache. | ||
fn evict(&mut self) { | ||
let expired_keys: Vec<String> = self | ||
.seen_uuids | ||
.iter() | ||
.filter_map(|(key, expiry)| { | ||
if expiry.elapsed().is_zero() { | ||
Some(key.clone()) | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect(); | ||
|
||
for key in expired_keys { | ||
self.seen_uuids.remove(&key); | ||
} | ||
} | ||
|
||
pub fn spawn(mut self, bundle_channel: mpsc::UnboundedSender<Bundle>) -> JoinHandle<()> { | ||
let handle: JoinHandle<()> = tokio::spawn(async move { | ||
loop { | ||
let bundle_channel = bundle_channel.clone(); | ||
let bundles = self.check_bundle_cache().await; | ||
|
||
match bundles { | ||
Ok(bundles) => { | ||
for bundle in bundles { | ||
let result = bundle_channel.send(bundle); | ||
if result.is_err() { | ||
tracing::debug!("bundle_channel failed to send bundle"); | ||
} | ||
} | ||
} | ||
Err(err) => { | ||
debug!(?err, "error fetching bundles from tx-pool"); | ||
} | ||
} | ||
|
||
// evict expired bundles once every loop | ||
self.evict(); | ||
|
||
tokio::time::sleep(Duration::from_secs(self.config.tx_pool_poll_interval)).await; | ||
} | ||
}); | ||
|
||
handle | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
pub mod block; | ||
pub mod bundler; | ||
pub mod oauth; | ||
pub mod submit; | ||
pub mod tx_poller; |
Oops, something went wrong.