Skip to content

Commit 0c2a5d0

Browse files
committed
feat: adds bundle submission call
1 parent 54f4cef commit 0c2a5d0

File tree

5 files changed

+112
-69
lines changed

5 files changed

+112
-69
lines changed

Cargo.toml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,20 @@ name = "transaction-submitter"
2222
path = "bin/submit_transaction.rs"
2323

2424
[dependencies]
25-
zenith-types = { git = "https://github.com/init4tech/zenith-rs", tag = "v0.10.1" }
25+
zenith-types = { path = "../zenith-rs" }
2626

27-
alloy-primitives = { version = "=0.8.8", features = ["serde", "tiny-keccak"] }
28-
alloy-sol-types = { version = "=0.8.8", features = ["json"] }
27+
alloy = { version = "0.7.3", features = ["full", "json-rpc", "signer-aws", "rpc-types-mev"] }
28+
alloy-primitives = { version = "=0.8.16", features = ["serde", "tiny-keccak"] }
29+
alloy-sol-types = { version = "=0.8.16", features = ["json"] }
2930
alloy-rlp = { version = "0.3.4" }
3031

31-
alloy = { version = "0.5.4", features = ["full", "json-rpc", "signer-aws"] }
32-
3332
aws-config = "1.1.7"
3433
aws-sdk-kms = "1.15.0"
3534

3635
hex = { package = "const-hex", version = "1", default-features = false, features = [
3736
"alloc",
3837
] }
3938

40-
signet-types = { git = "ssh://[email protected]/init4tech/signet-node.git" }
41-
4239
serde = { version = "1.0.197", features = ["derive"] }
4340
tracing = "0.1.40"
4441

src/config.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ pub type WalletlessProvider = FillProvider<
140140
BoxTransport,
141141
Ethereum,
142142
>;
143-
144-
pub type ZenithInstance = Zenith::ZenithInstance<BoxTransport, Provider>;
143+
pub type ZenithInstance = Zenith::ZenithInstance<BoxTransport, Provider, alloy::network::Ethereum>;
145144

146145
impl BuilderConfig {
147146
/// Load the builder configuration from environment variables.

src/tasks/block.rs

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1+
use alloy::eips::eip2718::Eip2718Error;
2+
use std::time::{SystemTime, UNIX_EPOCH};
3+
use std::{sync::OnceLock, time::Duration};
4+
use tokio::{sync::mpsc, task::JoinHandle};
5+
use tracing::Instrument;
6+
17
use super::bundler::{Bundle, BundlePoller};
28
use super::oauth::Authenticator;
39
use super::tx_poller::TxPoller;
10+
411
use crate::config::{BuilderConfig, WalletlessProvider};
12+
513
use alloy::providers::Provider;
14+
use alloy::rpc::types::mev::EthSendBundle;
615
use alloy::{
716
consensus::{SidecarBuilder, SidecarCoder, TxEnvelope},
817
eips::eip2718::Decodable2718,
918
};
1019
use alloy_primitives::{keccak256, Bytes, B256};
1120
use alloy_rlp::Buf;
12-
use std::time::{SystemTime, UNIX_EPOCH};
13-
use std::{sync::OnceLock, time::Duration};
14-
use tokio::{sync::mpsc, task::JoinHandle};
15-
use tracing::Instrument;
21+
22+
use zenith_types::BundleHelper::FillPermit2;
1623
use zenith_types::{encode_txns, Alloy2718Coder};
1724

1825
/// Ethereum's slot time in seconds.
@@ -29,7 +36,11 @@ pub struct InProgressBlock {
2936
impl InProgressBlock {
3037
/// Create a new `InProgressBlock`
3138
pub fn new() -> Self {
32-
Self { transactions: Vec::new(), raw_encoding: OnceLock::new(), hash: OnceLock::new() }
39+
Self {
40+
transactions: Vec::new(),
41+
raw_encoding: OnceLock::new(),
42+
hash: OnceLock::new(),
43+
}
3344
}
3445

3546
/// Get the number of transactions in the block.
@@ -73,15 +84,28 @@ impl InProgressBlock {
7384
pub fn ingest_bundle(&mut self, bundle: Bundle) {
7485
tracing::trace!(bundle = %bundle.id, "ingesting bundle");
7586

76-
let txs = bundle
77-
.bundle
78-
.bundle
79-
.txs
80-
.into_iter()
81-
.map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
82-
.collect::<Result<Vec<_>, _>>();
87+
// If host fills exists, then it's a Signet bundle
88+
if let Some(_) = bundle.bundle.host_fills {
89+
self.process_signet_bundle(bundle);
90+
} else {
91+
// otherwise, treat the bundle as a regular eth bundle and ingest it's tx list in order to maintain compatibility
92+
self.process_eth_bundle(bundle);
93+
}
94+
}
95+
96+
/// Process a Signet bundle by checking outputs for corresponding host fills and appending the txs in order
97+
fn process_signet_bundle(&mut self, bundle: Bundle) {
98+
let host_fills = bundle.bundle.host_fills.unwrap(); // safe because we already checked it above
99+
let host_fill_list = host_fills.clone().outputs;
100+
for output in host_fill_list.iter() {
101+
tracing::debug!(?output, "found host fill");
102+
// Parse the output as a FillPermit2
103+
}
104+
}
83105

84-
if let Ok(txs) = txs {
106+
/// Processes a typical bundle of transactions, appending them in order to the in progress block.
107+
fn process_eth_bundle(&mut self, bundle: Bundle) {
108+
if let Ok(txs) = self.decode_2718_txs(bundle.bundle.bundle) {
85109
self.unseal();
86110
// extend the transactions with the decoded transactions.
87111
// As this builder does not provide bundles landing "top of block", its fine to just extend.
@@ -91,6 +115,15 @@ impl InProgressBlock {
91115
}
92116
}
93117

118+
/// Extracts and decodes a list of 2718 transactions from a EthSendBundle
119+
fn decode_2718_txs(&mut self, bundle: EthSendBundle) -> Result<Vec<TxEnvelope>, Eip2718Error> {
120+
bundle
121+
.txs
122+
.into_iter()
123+
.map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
124+
.collect::<Result<Vec<_>, _>>()
125+
}
126+
94127
/// Encode the in-progress block
95128
fn encode_raw(&self) -> &Bytes {
96129
self.seal();
@@ -125,7 +158,7 @@ pub struct BlockBuilder {
125158
}
126159

127160
impl BlockBuilder {
128-
// create a new block builder with the given config.
161+
// Creates a new block builder with the given config
129162
pub fn new(
130163
config: &BuilderConfig,
131164
authenticator: Authenticator,
@@ -139,9 +172,10 @@ impl BlockBuilder {
139172
}
140173
}
141174

175+
/// Fetches transactions from the cache and ingests them into the current block
142176
async fn get_transactions(&mut self, in_progress: &mut InProgressBlock) {
143177
tracing::trace!("query transactions from cache");
144-
let txns = self.tx_poller.check_tx_cache().await;
178+
let txns: Result<Vec<TxEnvelope>, eyre::Error> = self.tx_poller.check_tx_cache().await;
145179
match txns {
146180
Ok(txns) => {
147181
tracing::trace!("got transactions response");
@@ -155,12 +189,16 @@ impl BlockBuilder {
155189
}
156190
}
157191

192+
/// Returns bundles from the cache and ingests them into the current block
158193
async fn _get_bundles(&mut self, in_progress: &mut InProgressBlock) {
159194
tracing::trace!("query bundles from cache");
195+
196+
// fetch latest bundles from the cache
160197
let bundles = self.bundle_poller.check_bundle_cache().await;
161198
match bundles {
162199
Ok(bundles) => {
163-
tracing::trace!("got bundles response");
200+
tracing::trace!(?bundles, "got bundles response");
201+
// QUESTION: When does this need to stop attempting to ingest for the current block?
164202
for bundle in bundles {
165203
in_progress.ingest_bundle(bundle);
166204
}
@@ -172,6 +210,7 @@ impl BlockBuilder {
172210
self.bundle_poller.evict();
173211
}
174212

213+
// Filters confirmed transactions from the block
175214
async fn filter_transactions(&self, in_progress: &mut InProgressBlock) {
176215
// query the rollup node to see which transaction(s) have been included
177216
let mut confirmed_transactions = Vec::new();
@@ -193,14 +232,14 @@ impl BlockBuilder {
193232
}
194233
}
195234

196-
// calculate the duration in seconds until the beginning of the next block slot.
235+
// Calculate the duration in seconds until the beginning of the next block slot.
197236
fn secs_to_next_slot(&self) -> u64 {
198237
let curr_timestamp: u64 = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
199238
let current_slot_time = (curr_timestamp - self.config.chain_offset) % ETHEREUM_SLOT_TIME;
200239
(ETHEREUM_SLOT_TIME - current_slot_time) % ETHEREUM_SLOT_TIME
201240
}
202241

203-
// add a buffer to the beginning of the block slot.
242+
// Add a buffer to the beginning of the block slot.
204243
fn secs_to_next_target(&self) -> u64 {
205244
self.secs_to_next_slot() + self.config.target_slot_time
206245
}
@@ -219,8 +258,8 @@ impl BlockBuilder {
219258
let mut in_progress = InProgressBlock::default();
220259
self.get_transactions(&mut in_progress).await;
221260

222-
// TODO: Implement bundle ingestion #later
223-
// self.get_bundles(&mut in_progress).await;
261+
// Ingest bundles
262+
self._get_bundles(&mut in_progress).await;
224263

225264
// Filter confirmed transactions from the block
226265
self.filter_transactions(&mut in_progress).await;

src/tasks/bundler.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
//! Bundler service responsible for polling and submitting bundles to the in-progress block.
2-
use std::time::{Duration, Instant};
1+
//! Bundler service responsible for managing bundles.
2+
use super::oauth::Authenticator;
33

44
pub use crate::config::BuilderConfig;
5-
use alloy_primitives::map::HashMap;
6-
use reqwest::Url;
7-
use serde::{Deserialize, Serialize};
8-
use signet_types::SignetEthBundle;
95

6+
use zenith_types::ZenithEthBundle;
107
use oauth2::TokenResponse;
11-
12-
use super::oauth::Authenticator;
8+
use reqwest::Url;
9+
use serde::{Deserialize, Serialize};
10+
use std::collections::HashMap;
11+
use std::time::{Duration, Instant};
1312

1413
#[derive(Debug, Clone, Serialize, Deserialize)]
1514
pub struct Bundle {
1615
pub id: String,
17-
pub bundle: SignetEthBundle,
16+
pub bundle: ZenithEthBundle,
1817
}
1918

2019
/// Response from the tx-pool containing a list of bundles.

src/tasks/submit.rs

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use crate::{
44
tasks::block::InProgressBlock,
55
};
66
use alloy::{
7-
consensus::{constants::GWEI_TO_WEI, SimpleCoder},
7+
consensus::constants::GWEI_TO_WEI,
88
eips::BlockNumberOrTag,
9-
network::{TransactionBuilder, TransactionBuilder4844},
9+
network::TransactionBuilder,
10+
providers::Provider as _,
1011
providers::SendableTx,
11-
providers::{Provider as _, WalletProvider},
1212
rpc::types::eth::TransactionRequest,
1313
signers::Signer,
1414
sol_types::SolCall,
@@ -23,8 +23,9 @@ use std::time::Instant;
2323
use tokio::{sync::mpsc, task::JoinHandle};
2424
use tracing::{debug, error, instrument, trace};
2525
use zenith_types::{
26+
BundleHelper::{submitCall, BlockHeader, FillPermit2},
2627
SignRequest, SignResponse,
27-
Zenith::{self, IncorrectHostBlock},
28+
Zenith::IncorrectHostBlock,
2829
};
2930

3031
macro_rules! spawn_provider_send {
@@ -109,23 +110,6 @@ impl SubmitTask {
109110
})
110111
}
111112

112-
/// Builds blob transaction from the provided header and signature values
113-
fn build_blob_tx(
114-
&self,
115-
header: Zenith::BlockHeader,
116-
v: u8,
117-
r: FixedBytes<32>,
118-
s: FixedBytes<32>,
119-
in_progress: &InProgressBlock,
120-
) -> eyre::Result<TransactionRequest> {
121-
let data = Zenith::submitBlockCall { header, v, r, s, _4: Default::default() }.abi_encode();
122-
let sidecar = in_progress.encode_blob::<SimpleCoder>().build()?;
123-
Ok(TransactionRequest::default()
124-
.with_blob_sidecar(sidecar)
125-
.with_input(data)
126-
.with_max_priority_fee_per_gas((GWEI_TO_WEI * 16) as u128))
127-
}
128-
129113
async fn next_host_block_height(&self) -> eyre::Result<u64> {
130114
let result = self.host_provider.get_block_number().await?;
131115
let next = result.checked_add(1).ok_or_else(|| eyre!("next host block height overflow"))?;
@@ -138,23 +122,48 @@ impl SubmitTask {
138122
resp: &SignResponse,
139123
in_progress: &InProgressBlock,
140124
) -> eyre::Result<ControlFlow> {
141-
let v: u8 = resp.sig.v().y_parity_byte() + 27;
142-
let r: FixedBytes<32> = resp.sig.r().into();
143-
let s: FixedBytes<32> = resp.sig.s().into();
144-
145-
let header = Zenith::BlockHeader {
125+
// There are two types of bundles:
126+
// - Bundles with permit2s that require checking and enforcing host fills
127+
// - Bundles that are just groups of transactions and can be executed without sorting host fills
128+
//
129+
// Block building depends on the type of bundles being built, and so our builder
130+
// needs to be able to detect and handle both types of bundles or else we will go down.
131+
// This means all builders that build blocks containing bundles on our network need to handle multiple bundle types.
132+
//
133+
// If a bundle has a permit2 fill, it must be checked if it's one of _our_ permit2 fills.
134+
// If it is, then we can begin the host fills check process.
135+
136+
let fills: Vec<FillPermit2> = vec![];
137+
138+
let header = BlockHeader {
146139
hostBlockNumber: resp.req.host_block_number,
147140
rollupChainId: U256::from(self.config.ru_chain_id),
148141
gasLimit: resp.req.gas_limit,
149142
rewardAddress: resp.req.ru_reward_address,
150143
blockDataHash: in_progress.contents_hash(),
151144
};
152145

153-
let tx = self
154-
.build_blob_tx(header, v, r, s, in_progress)?
155-
.with_from(self.host_provider.default_signer_address())
156-
.with_to(self.config.zenith_address)
157-
.with_gas_limit(1_000_000);
146+
// get sig details for signing the tx
147+
let v = resp.sig.v().into();
148+
let r: FixedBytes<32> = resp.sig.r().into();
149+
let s: FixedBytes<32> = resp.sig.s().into();
150+
151+
let submit_call = submitCall { fills, header, v, r, s }.abi_encode();
152+
153+
let tx = TransactionRequest::default()
154+
.with_input(submit_call)
155+
.with_max_priority_fee_per_gas((GWEI_TO_WEI * 16) as u128);
156+
157+
if let Err(TransportError::ErrorResp(e)) =
158+
self.host_provider.call(&tx).block(BlockNumberOrTag::Pending.into()).await
159+
{
160+
error!(
161+
code = e.code,
162+
message = %e.message,
163+
data = ?e.data,
164+
"error in transaction submission"
165+
);
166+
}
158167

159168
if let Err(TransportError::ErrorResp(e)) =
160169
self.host_provider.call(&tx).block(BlockNumberOrTag::Pending.into()).await

0 commit comments

Comments
 (0)