11use super :: { config:: EthereumL1Config , tools, transaction_error:: TransactionError } ;
2- use crate :: metrics:: Metrics ;
2+ use crate :: {
3+ metrics:: Metrics ,
4+ shared:: { alloy_tools, signer:: Signer } ,
5+ } ;
36use alloy:: {
47 consensus:: TxType ,
58 network:: { Network , ReceiptResponse , TransactionBuilder , TransactionBuilder4844 } ,
6- primitives:: B256 ,
9+ primitives:: { Address , B256 } ,
710 providers:: {
811 DynProvider , PendingTransactionBuilder , PendingTransactionError , Provider , RootProvider ,
912 WatchTxError ,
@@ -34,6 +37,9 @@ pub struct TransactionMonitorConfig {
3437 max_attempts_to_send_tx : u64 ,
3538 max_attempts_to_wait_tx : u64 ,
3639 delay_between_tx_attempts : Duration ,
40+ execution_rpc_urls : Vec < String > ,
41+ preconfer_address : Option < Address > ,
42+ signer : Arc < Signer > ,
3743}
3844
3945pub struct TransactionMonitorThread {
@@ -74,6 +80,9 @@ impl TransactionMonitor {
7480 delay_between_tx_attempts : Duration :: from_secs (
7581 config. delay_between_tx_attempts_sec ,
7682 ) ,
83+ execution_rpc_urls : config. execution_rpc_urls . clone ( ) ,
84+ preconfer_address : config. preconfer_address ,
85+ signer : config. signer . clone ( ) ,
7786 } ,
7887 join_handle : Mutex :: new ( None ) ,
7988 error_notification_channel,
@@ -229,7 +238,7 @@ impl TransactionMonitorThread {
229238 root_provider = Some ( pending_tx. provider ( ) . clone ( ) ) ;
230239 }
231240
232- debug ! (
241+ info ! (
233242 "{} tx nonce: {}, attempt: {}, l1_block: {}, hash: {}, max_fee_per_gas: {}, max_priority_fee_per_gas: {}, max_fee_per_blob_gas: {:?}" ,
234243 if sending_attempt == 0 {
235244 "🟢 Send"
@@ -379,8 +388,11 @@ impl TransactionMonitorThread {
379388 previous_tx_hashes : & Vec < B256 > ,
380389 sending_attempt : u64 ,
381390 ) -> Option < PendingTransactionBuilder < alloy:: network:: Ethereum > > {
382- match self . provider . send_transaction ( tx) . await {
383- Ok ( tx) => Some ( tx) ,
391+ match self . provider . send_transaction ( tx. clone ( ) ) . await {
392+ Ok ( pending_tx) => {
393+ self . propagate_transaction_to_other_backup_nodes ( tx) . await ;
394+ Some ( pending_tx)
395+ }
384396 Err ( e) => {
385397 self . handle_rpc_error ( e, previous_tx_hashes, sending_attempt)
386398 . await ;
@@ -389,6 +401,41 @@ impl TransactionMonitorThread {
389401 }
390402 }
391403
404+ /// Recreates each backup node every time to avoid connection issues
405+ async fn propagate_transaction_to_other_backup_nodes ( & self , tx : TransactionRequest ) {
406+ // Skip the first RPC URL since it is the main one
407+ for url in self . config . execution_rpc_urls . iter ( ) . skip ( 1 ) {
408+ let provider = alloy_tools:: construct_alloy_provider (
409+ & self . config . signer ,
410+ url,
411+ self . config . preconfer_address ,
412+ )
413+ . await ;
414+ match provider {
415+ Ok ( provider) => {
416+ let tx = provider. 0 . send_transaction ( tx. clone ( ) ) . await ;
417+ if let Err ( e) = tx {
418+ if e. to_string ( ) . contains ( "AlreadyKnown" )
419+ || e. to_string ( ) . to_lowercase ( ) . contains ( "already known" )
420+ {
421+ debug ! ( "Transaction already known to backup node {}" , url) ;
422+ } else {
423+ warn ! ( "Failed to send transaction to backup node {}: {}" , url, e) ;
424+ }
425+ } else {
426+ info ! ( "Transaction sent to backup node {}" , url) ;
427+ }
428+ }
429+ Err ( e) => {
430+ warn ! (
431+ "Failed to construct alloy provider for backup node {}: {}" ,
432+ url, e
433+ ) ;
434+ }
435+ }
436+ }
437+ }
438+
392439 async fn handle_rpc_error (
393440 & self ,
394441 e : RpcError < TransportErrorKind > ,
0 commit comments