@@ -11,6 +11,7 @@ use ethers_core::abi::Function;
1111use ethers_core:: types:: Eip1559TransactionRequest ;
1212use eyre:: eyre;
1313use futures_util:: future;
14+ use hyperlane_core:: ChainResult ;
1415use tokio:: sync:: Mutex ;
1516use tokio:: try_join;
1617use tracing:: { debug, error, info, instrument, warn} ;
@@ -33,6 +34,9 @@ use hyperlane_ethereum::{
3334 multicall, EthereumReorgPeriod , EvmProviderForLander , LanderProviderBuilder ,
3435} ;
3536
37+ use crate :: adapter:: chains:: ethereum:: metrics:: {
38+ LABEL_BATCHED_TRANSACTION_FAILED , LABEL_BATCHED_TRANSACTION_SUCCESS ,
39+ } ;
3640use crate :: {
3741 adapter:: { core:: TxBuildingResult , AdaptsChain , GasLimit } ,
3842 dispatcher:: { PayloadDb , PostInclusionMetricsSource , TransactionDb } ,
@@ -66,6 +70,7 @@ pub struct EthereumAdapter {
6670 pub payload_db : Arc < dyn PayloadDb > ,
6771 pub signer : H160 ,
6872 pub minimum_time_between_resubmissions : Duration ,
73+ pub metrics : EthereumAdapterMetrics ,
6974}
7075
7176impl EthereumAdapter {
@@ -97,14 +102,16 @@ impl EthereumAdapter {
97102 . ok_or_else ( || eyre ! ( "No signer found in provider for domain {}" , domain) ) ?;
98103
99104 let metrics = EthereumAdapterMetrics :: new (
105+ conf. domain . clone ( ) ,
106+ dispatcher_metrics. get_batched_transactions ( ) ,
100107 dispatcher_metrics. get_finalized_nonce ( domain, & signer. to_string ( ) ) ,
101108 dispatcher_metrics. get_upper_nonce ( domain, & signer. to_string ( ) ) ,
102109 ) ;
103110
104111 let payload_db = db. clone ( ) as Arc < dyn PayloadDb > ;
105112
106113 let reorg_period = EthereumReorgPeriod :: try_from ( & conf. reorg_period ) ?;
107- let nonce_manager = NonceManager :: new ( & conf, db, provider. clone ( ) , metrics) . await ?;
114+ let nonce_manager = NonceManager :: new ( & conf, db, provider. clone ( ) , metrics. clone ( ) ) . await ?;
108115
109116 let adapter = Self {
110117 estimated_block_time : conf. estimated_block_time ,
@@ -119,6 +126,7 @@ impl EthereumAdapter {
119126 payload_db,
120127 signer,
121128 minimum_time_between_resubmissions : DEFAULT_MINIMUM_TIME_BETWEEN_RESUBMISSIONS ,
129+ metrics,
122130 } ;
123131
124132 Ok ( adapter)
@@ -338,7 +346,7 @@ impl EthereumAdapter {
338346 & self ,
339347 precursors : Vec < ( TypedTransaction , Function ) > ,
340348 payload_details : Vec < PayloadDetails > ,
341- ) -> Vec < TxBuildingResult > {
349+ ) -> ChainResult < Vec < TxBuildingResult > > {
342350 use super :: transaction:: TransactionFactory ;
343351
344352 let multi_precursor = self
@@ -350,24 +358,19 @@ impl EthereumAdapter {
350358 self . signer ,
351359 )
352360 . await
353- . map ( |( tx, f) | EthereumTxPrecursor :: new ( tx, f) ) ;
354-
355- let multi_precursor = match multi_precursor {
356- Ok ( precursor) => precursor,
357- Err ( e) => {
358- error ! ( error = ?e, "Failed to batch payloads" ) ;
359- return vec ! [ ] ;
360- }
361- } ;
361+ . map ( |( tx, f) | EthereumTxPrecursor :: new ( tx, f) ) ?;
362362
363363 let transaction = TransactionFactory :: build ( multi_precursor, payload_details. clone ( ) ) ;
364364
365365 let tx_building_result = TxBuildingResult {
366366 payloads : payload_details,
367367 maybe_tx : Some ( transaction) ,
368368 } ;
369+ Ok ( vec ! [ tx_building_result] )
370+ }
369371
370- vec ! [ tx_building_result]
372+ pub fn metrics ( & self ) -> & EthereumAdapterMetrics {
373+ & self . metrics
371374 }
372375}
373376
@@ -403,14 +406,16 @@ impl AdaptsChain for EthereumAdapter {
403406 elapsed > * ready_time
404407 }
405408
406- /// Builds a transaction for the given payloads.
409+ /// Builds transactions for the given payloads.
407410 ///
408411 /// If there is only one payload, it builds a transaction without batching.
409412 /// If there are multiple payloads, it batches them into a single transaction.
410413 /// The order of individual calls in the batched transaction is determined
411414 /// by the order of payloads.
412415 /// The order should not change since the simulation and estimation of the batched transaction
413416 /// depend on the order of payloads.
417+ /// If batching fails, it will fallback to building multiple transactions with a
418+ /// single payload for each of them.
414419 async fn build_transactions ( & self , payloads : & [ FullPayload ] ) -> Vec < TxBuildingResult > {
415420 use super :: transaction:: TransactionFactory ;
416421
@@ -445,12 +450,48 @@ impl AdaptsChain for EthereumAdapter {
445450 return results;
446451 }
447452
448- // Batched transaction
449- let results = self
450- . build_batched_transaction ( precursors, payload_details)
451- . await ;
453+ match self
454+ . build_batched_transaction ( precursors. clone ( ) , payload_details. clone ( ) )
455+ . await
456+ {
457+ Ok ( results) => {
458+ self . metrics ( ) . increment_batched_transactions (
459+ LABEL_BATCHED_TRANSACTION_SUCCESS ,
460+ payloads. len ( ) as u64 ,
461+ ) ;
462+ info ! (
463+ ?payloads,
464+ ?results,
465+ "built batched transaction for payloads"
466+ ) ;
467+ // Batched transaction
468+ return results;
469+ }
470+ Err ( err) => {
471+ self . metrics ( ) . increment_batched_transactions (
472+ LABEL_BATCHED_TRANSACTION_FAILED ,
473+ payloads. len ( ) as u64 ,
474+ ) ;
475+ warn ! (
476+ domain = self . domain. name( ) ,
477+ ?err,
478+ "Failed to build batch transaction. Fallback to single tx submission"
479+ ) ;
480+ }
481+ }
452482
453- info ! ( ?payloads, ?results, "built transaction for payloads" ) ;
483+ let results: Vec < _ > = payloads
484+ . iter ( )
485+ . map ( |payload| {
486+ let precursor = EthereumTxPrecursor :: from_payload ( payload, self . signer ) ;
487+ self . build_single_transaction ( precursor, vec ! [ payload. details. clone( ) ] )
488+ } )
489+ . collect ( ) ;
490+ info ! (
491+ ?payloads,
492+ ?results,
493+ "built multiple transactions for multiple payloads"
494+ ) ;
454495 results
455496 }
456497
0 commit comments