11use alloy_consensus:: transaction:: Recovered ;
22use alloy_consensus:: { Transaction , transaction:: SignerRecoverable } ;
3- use alloy_primitives:: { B256 , Bytes } ;
3+ use alloy_primitives:: { Address , B256 , Bytes , TxHash } ;
44use alloy_provider:: { Provider , RootProvider , network:: eip2718:: Decodable2718 } ;
55use jsonrpsee:: {
66 core:: { RpcResult , async_trait} ,
@@ -9,13 +9,46 @@ use jsonrpsee::{
99use op_alloy_consensus:: OpTxEnvelope ;
1010use op_alloy_network:: Optimism ;
1111use reth_rpc_eth_types:: EthApiError ;
12+ use serde:: { Deserialize , Serialize } ;
1213use std:: time:: { SystemTime , UNIX_EPOCH } ;
1314use tips_core:: { Bundle , BundleHash , BundleWithMetadata , CancelBundle } ;
1415use tracing:: { info, warn} ;
1516
1617use crate :: queue:: QueuePublisher ;
1718use crate :: validation:: { AccountInfoLookup , L1BlockInfoLookup , validate_bundle, validate_tx} ;
1819
20+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
21+ #[ serde( rename_all = "camelCase" ) ]
22+ pub struct TransactionResult {
23+ pub coinbase_diff : String ,
24+ pub eth_sent_to_coinbase : String ,
25+ pub from_address : Address ,
26+ pub gas_fees : String ,
27+ pub gas_price : String ,
28+ pub gas_used : u64 ,
29+ pub to_address : Option < Address > ,
30+ pub tx_hash : TxHash ,
31+ pub value : String ,
32+ /// Resource metering: execution time for this tx in microseconds
33+ pub execution_time_us : u128 ,
34+ }
35+
36+ /// Response for base_meterBundle
37+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
38+ #[ serde( rename_all = "camelCase" ) ]
39+ pub struct MeterBundleResponse {
40+ pub bundle_gas_price : String ,
41+ pub bundle_hash : B256 ,
42+ pub coinbase_diff : String ,
43+ pub eth_sent_to_coinbase : String ,
44+ pub gas_fees : String ,
45+ pub results : Vec < TransactionResult > ,
46+ pub state_block_number : u64 ,
47+ pub total_gas_used : u64 ,
48+ /// Resource metering: total execution time in microseconds
49+ pub total_execution_time_us : u128 ,
50+ }
51+
1952#[ rpc( server, namespace = "eth" ) ]
2053pub trait IngressApi {
2154 /// `eth_sendBundle` can be used to send your bundles to the builder.
6093 Queue : QueuePublisher + Sync + Send + ' static ,
6194{
6295 async fn send_bundle ( & self , bundle : Bundle ) -> RpcResult < BundleHash > {
63- let bundle_with_metadata = self . validate_bundle ( bundle) . await ?;
96+ let bundle_with_metadata = self . validate_bundle ( & bundle) . await ?;
97+ self . meter_bundle ( & bundle) . await ?;
6498
6599 let bundle_hash = bundle_with_metadata. bundle_hash ( ) ;
66100 if let Err ( e) = self
@@ -104,6 +138,7 @@ where
104138 reverting_tx_hashes : vec ! [ transaction. tx_hash( ) ] ,
105139 ..Default :: default ( )
106140 } ;
141+ self . meter_bundle ( & bundle) . await ?;
107142
108143 let bundle_with_metadata = BundleWithMetadata :: load ( bundle)
109144 . map_err ( |e| EthApiError :: InvalidParams ( e. to_string ( ) ) . into_rpc_err ( ) ) ?;
@@ -168,7 +203,7 @@ where
168203 Ok ( transaction)
169204 }
170205
171- async fn validate_bundle ( & self , bundle : Bundle ) -> RpcResult < BundleWithMetadata > {
206+ async fn validate_bundle ( & self , bundle : & Bundle ) -> RpcResult < BundleWithMetadata > {
172207 if bundle. txs . is_empty ( ) {
173208 return Err (
174209 EthApiError :: InvalidParams ( "Bundle cannot have empty transactions" . into ( ) )
@@ -185,8 +220,25 @@ where
185220 let transaction = self . validate_tx ( tx_data) . await ?;
186221 total_gas = total_gas. saturating_add ( transaction. gas_limit ( ) ) ;
187222 }
188- validate_bundle ( & bundle, total_gas, tx_hashes) ?;
223+ validate_bundle ( bundle, total_gas, tx_hashes) ?;
189224
190225 Ok ( bundle_with_metadata)
191226 }
227+
228+ async fn meter_bundle ( & self , bundle : & Bundle ) -> RpcResult < ( ) > {
229+ let res: MeterBundleResponse = self
230+ . provider
231+ . client ( )
232+ . request ( "base_meterBundle" , ( bundle, ) )
233+ . await
234+ . map_err ( |e| EthApiError :: InvalidParams ( e. to_string ( ) ) . into_rpc_err ( ) ) ?;
235+
236+ // if simulation takes longer than 2s, we don't include and just error to user
237+ if res. total_execution_time_us > 2000000 {
238+ return Err (
239+ EthApiError :: InvalidParams ( "Bundle simulation took too long" . into ( ) ) . into_rpc_err ( ) ,
240+ ) ;
241+ }
242+ Ok ( ( ) )
243+ }
192244}
0 commit comments