11use super :: bundler:: { Bundle , BundlePoller } ;
22use super :: oauth:: Authenticator ;
33use super :: tx_poller:: TxPoller ;
4- use crate :: config:: { BuilderConfig , WalletlessProvider } ;
5- use alloy:: primitives:: { keccak256, Bytes , B256 } ;
6- use alloy:: providers:: Provider ;
4+
5+ use crate :: config:: { BuilderConfig , Provider , WalletlessProvider } ;
6+
7+ use alloy:: primitives:: { keccak256, Bytes , FixedBytes , B256 } ;
8+ use alloy:: providers:: Provider as _;
9+ use alloy:: rpc:: types:: TransactionRequest ;
710use alloy:: {
811 consensus:: { SidecarBuilder , SidecarCoder , TxEnvelope } ,
912 eips:: eip2718:: Decodable2718 ,
1013} ;
1114use alloy_rlp:: Buf ;
15+ use eyre:: { bail, eyre} ;
1216use std:: time:: { SystemTime , UNIX_EPOCH } ;
1317use std:: { sync:: OnceLock , time:: Duration } ;
1418use tokio:: { sync:: mpsc, task:: JoinHandle } ;
1519use tracing:: { debug, error, Instrument } ;
16- use zenith_types:: { encode_txns, Alloy2718Coder } ;
20+ use zenith_types:: { encode_txns, Alloy2718Coder , ZenithEthBundle } ;
1721
1822/// Ethereum's slot time in seconds.
1923pub const ETHEREUM_SLOT_TIME : u64 = 12 ;
@@ -153,14 +157,17 @@ impl BlockBuilder {
153157 }
154158 }
155159
156- async fn get_bundles ( & mut self , in_progress : & mut InProgressBlock ) {
160+ async fn get_bundles ( & mut self , host_provider : & Provider , in_progress : & mut InProgressBlock ) {
157161 tracing:: trace!( "query bundles from cache" ) ;
158162 let bundles = self . bundle_poller . check_bundle_cache ( ) . await ;
159163 // OPTIMIZE: Sort bundles received from cache
160164 match bundles {
161165 Ok ( bundles) => {
162166 for bundle in bundles {
163- in_progress. ingest_bundle ( bundle) ;
167+ let result = self . simulate_bundle ( & bundle. bundle , host_provider) . await ;
168+ if result. is_ok ( ) {
169+ in_progress. ingest_bundle ( bundle. clone ( ) ) ;
170+ }
164171 }
165172 }
166173 Err ( e) => {
@@ -170,6 +177,54 @@ impl BlockBuilder {
170177 self . bundle_poller . evict ( ) ;
171178 }
172179
180+ /// Simulates a Flashbots-style ZenithEthBundle, simualating each transaction in it's bundle
181+ /// by calling it against the host provider at the current height against default storage (no state overrides)
182+ /// and failing the whole bundle if any transaction not listed in the reverts list fails that call.
183+ async fn simulate_bundle (
184+ & mut self ,
185+ bundle : & ZenithEthBundle ,
186+ host_provider : & Provider ,
187+ ) -> eyre:: Result < ( ) > {
188+ tracing:: info!( "simulating bundle" ) ;
189+
190+ let reverts = & bundle. bundle . reverting_tx_hashes ;
191+ tracing:: debug!( reverts = ?reverts, "processing bundle with reverts" ) ;
192+
193+ for tx in & bundle. bundle . txs {
194+ let ( tx_env, hash) = self . parse_from_bundle ( tx) ?;
195+
196+ // Simulate and check for reversion allowance
197+ match self . simulate_transaction ( host_provider, tx_env) . await {
198+ Ok ( _) => {
199+ // Passed, log trace and continue
200+ tracing:: debug!( tx = %hash, "tx passed simulation" ) ;
201+ continue ;
202+ }
203+ Err ( sim_err) => {
204+ // Failed, only continfue if tx is marked in revert list
205+ tracing:: debug!( "tx failed simulation: {}" , sim_err) ;
206+ if reverts. contains ( & hash) {
207+ continue ;
208+ } else {
209+ bail ! ( "tx {hash} failed simulation but was not marked as allowed to revert" )
210+ }
211+ }
212+ }
213+ }
214+ Ok ( ( ) )
215+ }
216+
217+ /// Simulates a transaction by calling it on the host provider at the current height with the current state.
218+ async fn simulate_transaction (
219+ & self ,
220+ host_provider : & Provider ,
221+ tx_env : TxEnvelope ,
222+ ) -> eyre:: Result < ( ) > {
223+ let tx = TransactionRequest :: from_transaction ( tx_env) ;
224+ host_provider. call ( & tx) . await ?;
225+ Ok ( ( ) )
226+ }
227+
173228 async fn filter_transactions ( & self , in_progress : & mut InProgressBlock ) {
174229 // query the rollup node to see which transaction(s) have been included
175230 let mut confirmed_transactions = Vec :: new ( ) ;
@@ -203,9 +258,25 @@ impl BlockBuilder {
203258 self . secs_to_next_slot ( ) + self . config . target_slot_time
204259 }
205260
261+ /// Parses bytes into a transaction envelope that is compatible with Flashbots-style bundles
262+ fn parse_from_bundle ( & self , tx : & Bytes ) -> Result < ( TxEnvelope , FixedBytes < 32 > ) , eyre:: Error > {
263+ let tx_env = TxEnvelope :: decode_2718 ( & mut tx. chunk ( ) ) ?;
264+ let hash = tx_env. tx_hash ( ) . to_owned ( ) ;
265+ tracing:: debug!( hash = %hash, "decoded bundle tx" ) ;
266+ if tx_env. is_eip4844 ( ) {
267+ tracing:: error!( "eip-4844 disallowed" ) ;
268+ return Err ( eyre ! ( "EIP-4844 transactions are not allowed in bundles" ) ) ;
269+ }
270+ Ok ( ( tx_env, hash) )
271+ }
272+
206273 /// Spawn the block builder task, returning the inbound channel to it, and
207274 /// a handle to the running task.
208- pub fn spawn ( mut self , outbound : mpsc:: UnboundedSender < InProgressBlock > ) -> JoinHandle < ( ) > {
275+ pub fn spawn (
276+ mut self ,
277+ outbound : mpsc:: UnboundedSender < InProgressBlock > ,
278+ host_provider : Provider ,
279+ ) -> JoinHandle < ( ) > {
209280 tokio:: spawn (
210281 async move {
211282 loop {
@@ -216,7 +287,7 @@ impl BlockBuilder {
216287 // Build a block
217288 let mut in_progress = InProgressBlock :: default ( ) ;
218289 self . get_transactions ( & mut in_progress) . await ;
219- self . get_bundles ( & mut in_progress) . await ;
290+ self . get_bundles ( & host_provider , & mut in_progress) . await ;
220291
221292 // Filter confirmed transactions from the block
222293 self . filter_transactions ( & mut in_progress) . await ;
0 commit comments