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,6 +9,7 @@ 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_audit:: { BundleEvent , BundleEventPublisher } ;
1415use tips_core:: { Bundle , BundleHash , BundleWithMetadata , CancelBundle } ;
@@ -17,6 +18,38 @@ use tracing::{info, warn};
1718use crate :: queue:: QueuePublisher ;
1819use crate :: validation:: { AccountInfoLookup , L1BlockInfoLookup , validate_bundle, validate_tx} ;
1920
21+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
22+ #[ serde( rename_all = "camelCase" ) ]
23+ pub struct TransactionResult {
24+ pub coinbase_diff : String ,
25+ pub eth_sent_to_coinbase : String ,
26+ pub from_address : Address ,
27+ pub gas_fees : String ,
28+ pub gas_price : String ,
29+ pub gas_used : u64 ,
30+ pub to_address : Option < Address > ,
31+ pub tx_hash : TxHash ,
32+ pub value : String ,
33+ /// Resource metering: execution time for this tx in microseconds
34+ pub execution_time_us : u128 ,
35+ }
36+
37+ /// Response for base_meterBundle
38+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
39+ #[ serde( rename_all = "camelCase" ) ]
40+ pub struct MeterBundleResponse {
41+ pub bundle_gas_price : String ,
42+ pub bundle_hash : B256 ,
43+ pub coinbase_diff : String ,
44+ pub eth_sent_to_coinbase : String ,
45+ pub gas_fees : String ,
46+ pub results : Vec < TransactionResult > ,
47+ pub state_block_number : u64 ,
48+ pub total_gas_used : u64 ,
49+ /// Resource metering: total execution time in microseconds
50+ pub total_execution_time_us : u128 ,
51+ }
52+
2053#[ rpc( server, namespace = "eth" ) ]
2154pub trait IngressApi {
2255 /// `eth_sendBundle` can be used to send your bundles to the builder.
6598 Audit : BundleEventPublisher + Sync + Send + ' static ,
6699{
67100 async fn send_bundle ( & self , bundle : Bundle ) -> RpcResult < BundleHash > {
68- let bundle_with_metadata = self . validate_bundle ( bundle) . await ?;
101+ let bundle_with_metadata = self . validate_bundle ( & bundle) . await ?;
102+ self . meter_bundle ( & bundle) . await ?;
69103
70104 let bundle_hash = bundle_with_metadata. bundle_hash ( ) ;
71105 if let Err ( e) = self
@@ -117,6 +151,7 @@ where
117151 reverting_tx_hashes : vec ! [ transaction. tx_hash( ) ] ,
118152 ..Default :: default ( )
119153 } ;
154+ self . meter_bundle ( & bundle) . await ?;
120155
121156 let bundle_with_metadata = BundleWithMetadata :: load ( bundle)
122157 . map_err ( |e| EthApiError :: InvalidParams ( e. to_string ( ) ) . into_rpc_err ( ) ) ?;
@@ -191,7 +226,7 @@ where
191226 Ok ( transaction)
192227 }
193228
194- async fn validate_bundle ( & self , bundle : Bundle ) -> RpcResult < BundleWithMetadata > {
229+ async fn validate_bundle ( & self , bundle : & Bundle ) -> RpcResult < BundleWithMetadata > {
195230 if bundle. txs . is_empty ( ) {
196231 return Err (
197232 EthApiError :: InvalidParams ( "Bundle cannot have empty transactions" . into ( ) )
@@ -208,8 +243,25 @@ where
208243 let transaction = self . validate_tx ( tx_data) . await ?;
209244 total_gas = total_gas. saturating_add ( transaction. gas_limit ( ) ) ;
210245 }
211- validate_bundle ( & bundle, total_gas, tx_hashes) ?;
246+ validate_bundle ( bundle, total_gas, tx_hashes) ?;
212247
213248 Ok ( bundle_with_metadata)
214249 }
250+
251+ async fn meter_bundle ( & self , bundle : & Bundle ) -> RpcResult < ( ) > {
252+ let res: MeterBundleResponse = self
253+ . provider
254+ . client ( )
255+ . request ( "base_meterBundle" , ( bundle, ) )
256+ . await
257+ . map_err ( |e| EthApiError :: InvalidParams ( e. to_string ( ) ) . into_rpc_err ( ) ) ?;
258+
259+ // if simulation takes longer than 2s, we don't include and just error to user
260+ if res. total_execution_time_us > 2000000 {
261+ return Err (
262+ EthApiError :: InvalidParams ( "Bundle simulation took too long" . into ( ) ) . into_rpc_err ( ) ,
263+ ) ;
264+ }
265+ Ok ( ( ) )
266+ }
215267}
0 commit comments