@@ -14,6 +14,7 @@ use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
1414
1515use base64:: prelude:: BASE64_STANDARD ;
1616use base64:: Engine ;
17+ use bitcoin:: transaction:: Version ;
1718use bitcoin:: { BlockHash , FeeRate , Network , OutPoint , Transaction , Txid } ;
1819use lightning:: chain:: chaininterface:: ConfirmationTarget as LdkConfirmationTarget ;
1920use lightning:: chain:: { BlockLocator , Listen } ;
@@ -41,6 +42,7 @@ use crate::fee_estimator::{
4142} ;
4243use crate :: io:: utils:: update_and_persist_node_metrics;
4344use crate :: logger:: { log_bytes, log_debug, log_error, log_info, log_trace, LdkLogger , Logger } ;
45+ use crate :: tx_broadcaster:: SortedTransactions ;
4446use crate :: types:: { ChainMonitor , ChannelManager , DynStore , Sweeper , Wallet } ;
4547use crate :: { Error , PersistedNodeMetrics } ;
4648
@@ -119,6 +121,30 @@ impl BitcoindChainSource {
119121 self . api_client . utxo_source ( )
120122 }
121123
124+ pub ( super ) async fn validate_zero_fee_commitments_support ( & self ) -> Result < ( ) , Error > {
125+ let node_version_result = tokio:: time:: timeout (
126+ Duration :: from_secs ( CHAIN_POLLING_TIMEOUT_SECS ) ,
127+ self . api_client . get_node_version ( ) ,
128+ )
129+ . await
130+ . map_err ( |e| {
131+ log_error ! ( self . logger, "Failed to get node version: {:?}" , e) ;
132+ Error :: ConnectionFailed
133+ } ) ?;
134+
135+ let node_version = node_version_result. map_err ( |e| {
136+ log_error ! ( self . logger, "Failed to get node version: {:?}" , e) ;
137+ Error :: ConnectionFailed
138+ } ) ?;
139+
140+ // v26 first shipped the `submitpackage` RPC, but we need v29 to relay ephemeral dust
141+ if node_version < 290000 {
142+ log_error ! ( self . logger, "Bitcoin backend MUST be greater than or equal to v29" ) ;
143+ return Err ( Error :: ChainSourceNotSupported ) ;
144+ }
145+ Ok ( ( ) )
146+ }
147+
122148 pub ( super ) async fn continuously_sync_wallets (
123149 & self , mut stop_sync_receiver : tokio:: sync:: watch:: Receiver < ( ) > ,
124150 onchain_wallet : Arc < Wallet > , channel_manager : Arc < ChannelManager > ,
@@ -571,46 +597,59 @@ impl BitcoindChainSource {
571597 Ok ( ( ) )
572598 }
573599
574- pub ( crate ) async fn process_broadcast_package ( & self , package : Vec < Transaction > ) {
575- // While it's a bit unclear when we'd be able to lean on Bitcoin Core >v28
576- // features, we should eventually switch to use `submitpackage` via the
577- // `rust-bitcoind-json-rpc` crate rather than just broadcasting individual
578- // transactions.
579- for tx in & package {
580- let txid = tx. compute_txid ( ) ;
581- let timeout_fut = tokio:: time:: timeout (
582- Duration :: from_secs ( DEFAULT_TX_BROADCAST_TIMEOUT_SECS ) ,
583- self . api_client . broadcast_transaction ( tx) ,
584- ) ;
585- match timeout_fut. await {
586- Ok ( res) => match res {
587- Ok ( id) => {
588- debug_assert_eq ! ( id, txid) ;
589- log_trace ! ( self . logger, "Successfully broadcast transaction {}" , txid) ;
590- } ,
591- Err ( e) => {
592- log_error ! ( self . logger, "Failed to broadcast transaction {}: {}" , txid, e) ;
593- log_trace ! (
594- self . logger,
595- "Failed broadcast transaction bytes: {}" ,
596- log_bytes!( tx. encode( ) )
597- ) ;
600+ fn log_broadcast_error (
601+ & self , e : impl core:: fmt:: Display , txids : & [ Txid ] , txs : & SortedTransactions ,
602+ ) {
603+ log_error ! ( self . logger, "Failed to broadcast transaction(s) {:?}: {}" , txids, e) ;
604+ log_trace ! ( self . logger, "Failed broadcast transaction bytes:" ) ;
605+ for tx in txs. iter ( ) {
606+ log_trace ! ( self . logger, "{}" , log_bytes!( tx. encode( ) ) ) ;
607+ }
608+ }
609+
610+ pub ( crate ) async fn process_transaction_broadcast ( & self , txs : SortedTransactions ) {
611+ let all_txs_are_v3 = txs. iter ( ) . all ( |tx| tx. version == Version :: non_standard ( 3 ) ) ;
612+ match txs. len ( ) {
613+ 2 .. if all_txs_are_v3 => {
614+ let txids: Vec < _ > = txs. iter ( ) . map ( |tx| tx. compute_txid ( ) ) . collect ( ) ;
615+ let timeout_fut = tokio:: time:: timeout (
616+ Duration :: from_secs ( DEFAULT_TX_BROADCAST_TIMEOUT_SECS ) ,
617+ self . api_client . submit_package ( & txs) ,
618+ ) ;
619+ match timeout_fut. await {
620+ Ok ( res) => match res {
621+ Ok ( result) => {
622+ log_trace ! ( self . logger, "Successfully broadcast package {:?}" , txids) ;
623+ log_trace ! ( self . logger, "Successfully broadcast package {}" , result) ;
624+ } ,
625+ Err ( e) => self . log_broadcast_error ( e, & txids, & txs) ,
598626 } ,
599- } ,
600- Err ( e) => {
601- log_error ! (
602- self . logger,
603- "Failed to broadcast transaction due to timeout {}: {}" ,
604- txid,
605- e
606- ) ;
607- log_trace ! (
608- self . logger,
609- "Failed broadcast transaction bytes: {}" ,
610- log_bytes!( tx. encode( ) )
627+ Err ( e) => self . log_broadcast_error ( e, & txids, & txs) ,
628+ }
629+ } ,
630+ _ => {
631+ for tx in txs. iter ( ) {
632+ let txid = tx. compute_txid ( ) ;
633+ let timeout_fut = tokio:: time:: timeout (
634+ Duration :: from_secs ( DEFAULT_TX_BROADCAST_TIMEOUT_SECS ) ,
635+ self . api_client . broadcast_transaction ( tx) ,
611636 ) ;
612- } ,
613- }
637+ match timeout_fut. await {
638+ Ok ( res) => match res {
639+ Ok ( id) => {
640+ debug_assert_eq ! ( id, txid) ;
641+ log_trace ! (
642+ self . logger,
643+ "Successfully broadcast transaction {}" ,
644+ txid
645+ ) ;
646+ } ,
647+ Err ( e) => self . log_broadcast_error ( e, & [ txid] , & txs) ,
648+ } ,
649+ Err ( e) => self . log_broadcast_error ( e, & [ txid] , & txs) ,
650+ }
651+ }
652+ } ,
614653 }
615654 }
616655}
@@ -748,6 +787,31 @@ impl BitcoindClient {
748787 }
749788 }
750789
790+ pub ( crate ) async fn get_node_version ( & self ) -> Result < u64 , BitcoindClientError > {
791+ match self {
792+ BitcoindClient :: Rpc { rpc_client, .. } => {
793+ Self :: get_node_version_inner ( Arc :: clone ( rpc_client) )
794+ . await
795+ . map_err ( BitcoindClientError :: Rpc )
796+ } ,
797+ BitcoindClient :: Rest { rpc_client, .. } => {
798+ // Bitcoin Core's REST interface does not support `getnetworkinfo`
799+ // so we use the RPC client.
800+ Self :: get_node_version_inner ( Arc :: clone ( rpc_client) )
801+ . await
802+ . map_err ( BitcoindClientError :: Rpc )
803+ } ,
804+ }
805+ }
806+
807+ async fn get_node_version_inner ( rpc_client : Arc < RpcClient > ) -> Result < u64 , RpcClientError > {
808+ rpc_client. call_method :: < serde_json:: Value > ( "getnetworkinfo" , & [ ] ) . await . and_then ( |value| {
809+ value[ "version" ] . as_u64 ( ) . ok_or ( RpcClientError :: InvalidData ( String :: from (
810+ "The version field in the `getnetworkinfo` response should be a u64" ,
811+ ) ) )
812+ } )
813+ }
814+
751815 /// Broadcasts the provided transaction.
752816 pub ( crate ) async fn broadcast_transaction (
753817 & self , tx : & Transaction ,
@@ -776,6 +840,38 @@ impl BitcoindClient {
776840 rpc_client. call_method :: < Txid > ( "sendrawtransaction" , & [ tx_json] ) . await
777841 }
778842
843+ /// Submits the provided package
844+ pub ( crate ) async fn submit_package (
845+ & self , package : & SortedTransactions ,
846+ ) -> Result < String , BitcoindClientError > {
847+ match self {
848+ BitcoindClient :: Rpc { rpc_client, .. } => {
849+ Self :: submit_package_inner ( Arc :: clone ( rpc_client) , package)
850+ . await
851+ . map_err ( BitcoindClientError :: Rpc )
852+ } ,
853+ BitcoindClient :: Rest { rpc_client, .. } => {
854+ // Bitcoin Core's REST interface does not support submitting packages
855+ // so we use the RPC client.
856+ Self :: submit_package_inner ( Arc :: clone ( rpc_client) , package)
857+ . await
858+ . map_err ( BitcoindClientError :: Rpc )
859+ } ,
860+ }
861+ }
862+
863+ async fn submit_package_inner (
864+ rpc_client : Arc < RpcClient > , package : & SortedTransactions ,
865+ ) -> Result < String , RpcClientError > {
866+ let package_serialized: Vec < _ > =
867+ package. iter ( ) . map ( |tx| bitcoin:: consensus:: encode:: serialize_hex ( tx) ) . collect ( ) ;
868+ let package_json = serde_json:: json!( package_serialized) ;
869+ rpc_client
870+ . call_method :: < SubmitPackageResponse > ( "submitpackage" , & [ package_json] )
871+ . await
872+ . map ( |response| response. 0 )
873+ }
874+
779875 /// Retrieve the fee estimate needed for a transaction to begin
780876 /// confirmation within the provided `num_blocks`.
781877 pub ( crate ) async fn get_fee_estimate_for_target (
@@ -1327,6 +1423,23 @@ impl TryInto<GetMempoolEntryResponse> for JsonResponse {
13271423 }
13281424}
13291425
1426+ pub struct SubmitPackageResponse ( String ) ;
1427+
1428+ impl TryInto < SubmitPackageResponse > for JsonResponse {
1429+ type Error = String ;
1430+ fn try_into ( self ) -> Result < SubmitPackageResponse , String > {
1431+ let response = self . 0 . to_string ( ) ;
1432+ let res = self . 0 . as_object ( ) . ok_or ( "Failed to parse submitpackage response" . to_string ( ) ) ?;
1433+
1434+ match res[ "package_msg" ] . as_str ( ) {
1435+ Some ( "success" ) => Ok ( SubmitPackageResponse ( response) ) ,
1436+ Some ( _) | None => {
1437+ return Err ( response) ;
1438+ } ,
1439+ }
1440+ }
1441+ }
1442+
13301443#[ derive( Debug , Clone ) ]
13311444pub ( crate ) struct MempoolEntry {
13321445 /// The transaction id
0 commit comments