]> git.bitcoin.ninja Git - ldk-sample/commitdiff
Upgrade to LDK 0.0.116
authorElias Rohrer <dev@tnull.de>
Fri, 7 Jul 2023 12:44:07 +0000 (14:44 +0200)
committerElias Rohrer <dev@tnull.de>
Tue, 25 Jul 2023 18:46:23 +0000 (20:46 +0200)
Cargo.lock
Cargo.toml
src/bitcoind_client.rs
src/cli.rs
src/convert.rs
src/disk.rs
src/main.rs
src/sweep.rs

index 86335ed326bd9bc2bb150efe8463d73fbc5826fc..cb154eee5477d47f55e0c65825a1a553f597da0c 100644 (file)
@@ -257,18 +257,18 @@ checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
 
 [[package]]
 name = "lightning"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e009e1c0c21f66378b491bb40f548682138c63e09db6f3a05af59f8804bb9f4a"
+checksum = "90a0f2155316f1570446a0447c993480673f840748c8ed25bbc59dfc442ac770"
 dependencies = [
  "bitcoin",
 ]
 
 [[package]]
 name = "lightning-background-processor"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721b05b9848a09d5b943915449b5ffb31e24708007763640cf9d79b124a17e19"
+checksum = "398b68a96cceb3c1227504bd5faeb74f26c3233447bc10cc1cb2c67e01b51556"
 dependencies = [
  "bitcoin",
  "lightning",
@@ -277,9 +277,9 @@ dependencies = [
 
 [[package]]
 name = "lightning-block-sync"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c60cf241b3c219ee865aad91eab85a879b23c1756a335a5a311790ad6c1c3d2"
+checksum = "d94c276dbe2a777d58ed6ececca96006247a4717c00ac4cdfff62d76852be783"
 dependencies = [
  "bitcoin",
  "chunked_transfer",
@@ -289,9 +289,9 @@ dependencies = [
 
 [[package]]
 name = "lightning-invoice"
-version = "0.23.0"
+version = "0.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4e44b0e2822c8811470137d2339fdfe67a699b3248bb1606d1d02eb6a1e9f0a"
+checksum = "1788c0158526ec27a502043c2911ea6ea58fdc656bdf8749484942c07b790d23"
 dependencies = [
  "bech32 0.9.1",
  "bitcoin",
@@ -303,9 +303,9 @@ dependencies = [
 
 [[package]]
 name = "lightning-net-tokio"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4561ec5d4df2dd410a8b80955791fcfb007ef9210395db6e914b9527397b868c"
+checksum = "366c0ae225736cbc03555bd5fb4b44b2e8fe2ca3c868ec53a4b325c38b2ab2bd"
 dependencies = [
  "bitcoin",
  "lightning",
@@ -314,9 +314,9 @@ dependencies = [
 
 [[package]]
 name = "lightning-persister"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c52ed57ec33fb945f464b7e91b5df49f49fec649e1b44909f3ce517e96b0449a"
+checksum = "93caaafeb42115b70119619c2420e362cce776670427fc4ced3e6df77b41c0b6"
 dependencies = [
  "bitcoin",
  "libc",
@@ -326,9 +326,9 @@ dependencies = [
 
 [[package]]
 name = "lightning-rapid-gossip-sync"
-version = "0.0.115"
+version = "0.0.116"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd84d74a9b3892db22a60ac11dfc12e76b257b3174db6743e818ecc24834f3be"
+checksum = "8a07af5814234924e623bca499e003fca1864024d5bd984e752230f73a131584"
 dependencies = [
  "bitcoin",
  "lightning",
index aa842bf012af299010707b65bd4ea7757919fb4e..0e95be6a569cf1f5fa3bf1c6dd022aec3681afd1 100644 (file)
@@ -8,13 +8,13 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-lightning = { version = "0.0.115", features = ["max_level_trace"] }
-lightning-block-sync = { version = "0.0.115", features = [ "rpc-client" ] }
-lightning-invoice = { version = "0.23" }
-lightning-net-tokio = { version = "0.0.115" }
-lightning-persister = { version = "0.0.115" }
-lightning-background-processor = { version = "0.0.115", features = [ "futures" ] }
-lightning-rapid-gossip-sync = { version = "0.0.115" }
+lightning = { version = "0.0.116", features = ["max_level_trace"] }
+lightning-block-sync = { version = "0.0.116", features = [ "rpc-client" ] }
+lightning-invoice = { version = "0.24.0" }
+lightning-net-tokio = { version = "0.0.116" }
+lightning-persister = { version = "0.0.116" }
+lightning-background-processor = { version = "0.0.116", features = [ "futures" ] }
+lightning-rapid-gossip-sync = { version = "0.0.116" }
 
 base64 = "0.13.0"
 bitcoin = "0.29.0"
index e7a3cec4cd441eda4cf9765149d522840f6d24ba..228f27a2318406b1a2380b73ba78669eb2bd55b8 100644 (file)
@@ -1,4 +1,6 @@
-use crate::convert::{BlockchainInfo, FeeResponse, FundedTx, NewAddress, RawTx, SignedTx};
+use crate::convert::{
+       BlockchainInfo, FeeResponse, FundedTx, MempoolMinFeeResponse, NewAddress, RawTx, SignedTx,
+};
 use crate::disk::FilesystemLogger;
 use base64;
 use bitcoin::blockdata::transaction::Transaction;
@@ -32,6 +34,7 @@ pub struct BitcoindClient {
 
 #[derive(Clone, Eq, Hash, PartialEq)]
 pub enum Target {
+       MempoolMinimum,
        Background,
        Normal,
        HighPriority,
@@ -75,6 +78,7 @@ impl BitcoindClient {
                                "Failed to make initial call to bitcoind - please check your RPC user/password and access settings")
                        })?;
                let mut fees: HashMap<Target, AtomicU32> = HashMap::new();
+               fees.insert(Target::MempoolMinimum, AtomicU32::new(MIN_FEERATE));
                fees.insert(Target::Background, AtomicU32::new(MIN_FEERATE));
                fees.insert(Target::Normal, AtomicU32::new(2000));
                fees.insert(Target::HighPriority, AtomicU32::new(5000));
@@ -102,6 +106,16 @@ impl BitcoindClient {
        ) {
                handle.spawn(async move {
                        loop {
+                               let mempoolmin_estimate = {
+                                       let resp = rpc_client
+                                               .call_method::<MempoolMinFeeResponse>("getmempoolinfo", &vec![])
+                                               .await
+                                               .unwrap();
+                                       match resp.feerate_sat_per_kw {
+                                               Some(feerate) => std::cmp::max(feerate, MIN_FEERATE),
+                                               None => MIN_FEERATE,
+                                       }
+                               };
                                let background_estimate = {
                                        let background_conf_target = serde_json::json!(144);
                                        let background_estimate_mode = serde_json::json!("ECONOMICAL");
@@ -151,6 +165,9 @@ impl BitcoindClient {
                                        }
                                };
 
+                               fees.get(&Target::MempoolMinimum)
+                                       .unwrap()
+                                       .store(mempoolmin_estimate, Ordering::Release);
                                fees.get(&Target::Background)
                                        .unwrap()
                                        .store(background_estimate, Ordering::Release);
@@ -238,6 +255,9 @@ impl BitcoindClient {
 impl FeeEstimator for BitcoindClient {
        fn get_est_sat_per_1000_weight(&self, confirmation_target: ConfirmationTarget) -> u32 {
                match confirmation_target {
+                       ConfirmationTarget::MempoolMinimum => {
+                               self.fees.get(&Target::MempoolMinimum).unwrap().load(Ordering::Acquire)
+                       }
                        ConfirmationTarget::Background => {
                                self.fees.get(&Target::Background).unwrap().load(Ordering::Acquire)
                        }
@@ -252,29 +272,33 @@ impl FeeEstimator for BitcoindClient {
 }
 
 impl BroadcasterInterface for BitcoindClient {
-       fn broadcast_transaction(&self, tx: &Transaction) {
-               let bitcoind_rpc_client = self.bitcoind_rpc_client.clone();
-               let tx_serialized = encode::serialize_hex(tx);
-               let tx_json = serde_json::json!(tx_serialized);
-               let logger = Arc::clone(&self.logger);
-               self.handle.spawn(async move {
-                       // This may error due to RL calling `broadcast_transaction` with the same transaction
-                       // multiple times, but the error is safe to ignore.
-                       match bitcoind_rpc_client
-                               .call_method::<Txid>("sendrawtransaction", &vec![tx_json])
-                               .await
-                       {
-                               Ok(_) => {}
-                               Err(e) => {
-                                       let err_str = e.get_ref().unwrap().to_string();
-                                       log_error!(logger,
-                                               "Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\nTransaction: {}",
-                                               err_str,
-                                               tx_serialized);
-                                       print!("Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\n> ", err_str);
-                               }
-                       }
-               });
+       fn broadcast_transactions(&self, txs: &[&Transaction]) {
+               // TODO: Rather than calling `sendrawtransaction` in a a loop, we should probably use
+               // `submitpackage` once it becomes available.
+               for tx in txs {
+                       let bitcoind_rpc_client = Arc::clone(&self.bitcoind_rpc_client);
+                       let tx_serialized = encode::serialize_hex(tx);
+                       let tx_json = serde_json::json!(tx_serialized);
+                       let logger = Arc::clone(&self.logger);
+                       self.handle.spawn(async move {
+                               // This may error due to RL calling `broadcast_transactions` with the same transaction
+                               // multiple times, but the error is safe to ignore.
+                               match bitcoind_rpc_client
+                                       .call_method::<Txid>("sendrawtransaction", &vec![tx_json])
+                                       .await
+                                       {
+                                               Ok(_) => {}
+                                               Err(e) => {
+                                                       let err_str = e.get_ref().unwrap().to_string();
+                                                       log_error!(logger,
+                                                                          "Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\nTransaction: {}",
+                                                                          err_str,
+                                                                          tx_serialized);
+                                                       print!("Warning, failed to broadcast a transaction, this is likely okay but may indicate an error: {}\n> ", err_str);
+                                               }
+                                       }
+                       });
+               }
        }
 }
 
index f896721cdfef415d06600a34ac38695bfc4ee007..8e96fd8c26ac95a98ea04bcf07e8075754db7aaa 100644 (file)
@@ -8,18 +8,19 @@ use bitcoin::hashes::sha256::Hash as Sha256;
 use bitcoin::hashes::Hash;
 use bitcoin::network::constants::Network;
 use bitcoin::secp256k1::PublicKey;
-use lightning::chain::keysinterface::{EntropySource, KeysManager};
 use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry};
 use lightning::ln::msgs::NetAddress;
 use lightning::ln::{PaymentHash, PaymentPreimage};
+use lightning::onion_message::OnionMessagePath;
 use lightning::onion_message::{CustomOnionMessageContents, Destination, OnionMessageContents};
 use lightning::routing::gossip::NodeId;
 use lightning::routing::router::{PaymentParameters, RouteParameters};
+use lightning::sign::{EntropySource, KeysManager};
 use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig};
 use lightning::util::persist::KVStorePersister;
 use lightning::util::ser::{Writeable, Writer};
 use lightning_invoice::payment::pay_invoice;
-use lightning_invoice::{utils, Currency, Invoice};
+use lightning_invoice::{utils, Bolt11Invoice, Currency};
 use lightning_persister::FilesystemPersister;
 use std::env;
 use std::io;
@@ -150,7 +151,7 @@ pub(crate) async fn poll_for_user_input(
                                                continue;
                                        }
 
-                                       let invoice = match Invoice::from_str(invoice_str.unwrap()) {
+                                       let invoice = match Bolt11Invoice::from_str(invoice_str.unwrap()) {
                                                Ok(inv) => inv,
                                                Err(e) => {
                                                        println!("ERROR: invalid invoice: {:?}", e);
@@ -387,7 +388,7 @@ pub(crate) async fn poll_for_user_input(
                                                );
                                                continue;
                                        }
-                                       let mut node_pks = Vec::new();
+                                       let mut intermediate_nodes = Vec::new();
                                        let mut errored = false;
                                        for pk_str in path_pks_str.unwrap().split(",") {
                                                let node_pubkey_vec = match hex_utils::to_vec(pk_str) {
@@ -406,7 +407,7 @@ pub(crate) async fn poll_for_user_input(
                                                                break;
                                                        }
                                                };
-                                               node_pks.push(node_pubkey);
+                                               intermediate_nodes.push(node_pubkey);
                                        }
                                        if errored {
                                                continue;
@@ -425,10 +426,10 @@ pub(crate) async fn poll_for_user_input(
                                                        continue;
                                                }
                                        };
-                                       let destination_pk = node_pks.pop().unwrap();
+                                       let destination = Destination::Node(intermediate_nodes.pop().unwrap());
+                                       let message_path = OnionMessagePath { intermediate_nodes, destination };
                                        match onion_messenger.send_onion_message(
-                                               &node_pks,
-                                               Destination::Node(destination_pk),
+                                               message_path,
                                                OnionMessageContents::Custom(UserOnionMessageContents { tlv_type, data }),
                                                None,
                                        ) {
@@ -666,7 +667,7 @@ fn open_channel(
 }
 
 fn send_payment(
-       channel_manager: &ChannelManager, invoice: &Invoice,
+       channel_manager: &ChannelManager, invoice: &Bolt11Invoice,
        outbound_payments: &mut PaymentInfoStorage, persister: Arc<FilesystemPersister>,
 ) {
        let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner());
@@ -705,7 +706,7 @@ fn keysend<E: EntropySource>(
        let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner());
 
        let route_params = RouteParameters {
-               payment_params: PaymentParameters::for_keysend(payee_pubkey, 40),
+               payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false),
                final_value_msat: amt_msat,
        };
        outbound_payments.payments.insert(
index 64039233e7d5800fc9c1c147c2e800c328ced0fd..84b033e3a0820fc911f5d4dec42a32f0f8ba05df 100644 (file)
@@ -74,6 +74,31 @@ impl TryInto<FeeResponse> for JsonResponse {
        }
 }
 
+pub struct MempoolMinFeeResponse {
+       pub feerate_sat_per_kw: Option<u32>,
+       pub errored: bool,
+}
+
+impl TryInto<MempoolMinFeeResponse> for JsonResponse {
+       type Error = std::io::Error;
+       fn try_into(self) -> std::io::Result<MempoolMinFeeResponse> {
+               let errored = !self.0["errors"].is_null();
+               assert_eq!(self.0["maxmempool"].as_u64(), Some(300000000));
+               Ok(MempoolMinFeeResponse {
+                       errored,
+                       feerate_sat_per_kw: match self.0["mempoolminfee"].as_f64() {
+                               // Bitcoin Core gives us a feerate in BTC/KvB, which we need to convert to
+                               // satoshis/KW. Thus, we first multiply by 10^8 to get satoshis, then divide by 4
+                               // to convert virtual-bytes into weight units.
+                               Some(feerate_btc_per_kvbyte) => {
+                                       Some((feerate_btc_per_kvbyte * 100_000_000.0 / 4.0).round() as u32)
+                               }
+                               None => None,
+                       },
+               })
+       }
+}
+
 pub struct BlockchainInfo {
        pub latest_height: usize,
        pub latest_blockhash: BlockHash,
index 77cefd6d59a91bc25f26c855d9119235c62df78c..a69fdef710b99ceaaf3762ecc99c318f76b6ed8b 100644 (file)
@@ -2,7 +2,7 @@ use crate::{cli, NetworkGraph, PaymentInfoStorage};
 use bitcoin::secp256k1::PublicKey;
 use bitcoin::Network;
 use chrono::Utc;
-use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringParameters};
+use lightning::routing::scoring::{ProbabilisticScorer, ProbabilisticScoringDecayParameters};
 use lightning::util::logger::{Logger, Record};
 use lightning::util::ser::{Readable, ReadableArgs, Writer};
 use std::collections::HashMap;
@@ -98,7 +98,7 @@ pub(crate) fn read_payment_info(path: &Path) -> PaymentInfoStorage {
 pub(crate) fn read_scorer(
        path: &Path, graph: Arc<NetworkGraph>, logger: Arc<FilesystemLogger>,
 ) -> ProbabilisticScorer<Arc<NetworkGraph>, Arc<FilesystemLogger>> {
-       let params = ProbabilisticScoringParameters::default();
+       let params = ProbabilisticScoringDecayParameters::default();
        if let Ok(file) = File::open(path) {
                let args = (params.clone(), Arc::clone(&graph), Arc::clone(&logger));
                if let Ok(scorer) = ProbabilisticScorer::read(&mut BufReader::new(file), args) {
index a526efc1866a1fbe1919d2440cc6240bbbe3679c..2012921c334f6a6930693899c11a7549574df601 100644 (file)
@@ -14,9 +14,6 @@ use bitcoin::network::constants::Network;
 use bitcoin::BlockHash;
 use bitcoin_bech32::WitnessProgram;
 use disk::{INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME};
-use lightning::chain::keysinterface::{
-       EntropySource, InMemorySigner, KeysManager, SpendableOutputDescriptor,
-};
 use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
 use lightning::chain::{Filter, Watch};
 use lightning::events::{Event, PaymentFailureReason, PaymentPurpose};
@@ -27,10 +24,12 @@ use lightning::ln::channelmanager::{
 use lightning::ln::msgs::DecodeError;
 use lightning::ln::peer_handler::{IgnoringMessageHandler, MessageHandler, SimpleArcPeerManager};
 use lightning::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
-use lightning::onion_message::SimpleArcOnionMessenger;
+use lightning::onion_message::{DefaultMessageRouter, SimpleArcOnionMessenger};
 use lightning::routing::gossip;
 use lightning::routing::gossip::{NodeId, P2PGossipSync};
 use lightning::routing::router::DefaultRouter;
+use lightning::routing::scoring::ProbabilisticScoringFeeParameters;
+use lightning::sign::{EntropySource, InMemorySigner, KeysManager, SpendableOutputDescriptor};
 use lightning::util::config::UserConfig;
 use lightning::util::persist::KVStorePersister;
 use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer};
@@ -207,6 +206,7 @@ async fn handle_ldk_events(
                        via_user_channel_id: _,
                        claim_deadline: _,
                        onion_fields: _,
+                       counterparty_skimmed_fee_msat: _,
                } => {
                        println!(
                                "\nEVENT: received payment from payment hash {} of {} millisatoshis",
@@ -429,6 +429,7 @@ async fn handle_ldk_events(
                        // the funding transaction either confirms, or this event is generated.
                }
                Event::HTLCIntercepted { .. } => {}
+               Event::BumpTransaction(_) => {}
        }
 }
 
@@ -553,11 +554,13 @@ async fn start_ldk() {
        )));
 
        // Step 10: Create Router
+       let scoring_fee_params = ProbabilisticScoringFeeParameters::default();
        let router = Arc::new(DefaultRouter::new(
                network_graph.clone(),
                logger.clone(),
                keys_manager.get_secure_random_bytes(),
                scorer.clone(),
+               scoring_fee_params,
        ));
 
        // Step 11: Initialize the ChannelManager
@@ -602,6 +605,7 @@ async fn start_ldk() {
                                keys_manager.clone(),
                                user_config,
                                chain_params,
+                               cur.as_secs() as u32,
                        );
                        (polled_best_block_hash, fresh_channel_manager)
                }
@@ -667,6 +671,8 @@ async fn start_ldk() {
                Arc::clone(&keys_manager),
                Arc::clone(&keys_manager),
                Arc::clone(&logger),
+               Arc::new(DefaultMessageRouter {}),
+               IgnoringMessageHandler {},
                IgnoringMessageHandler {},
        ));
        let mut ephemeral_bytes = [0; 32];
@@ -676,13 +682,13 @@ async fn start_ldk() {
                chan_handler: channel_manager.clone(),
                route_handler: gossip_sync.clone(),
                onion_message_handler: onion_messenger.clone(),
+               custom_message_handler: IgnoringMessageHandler {},
        };
        let peer_manager: Arc<PeerManager> = Arc::new(PeerManager::new(
                lightning_msg_handler,
                current_time.try_into().unwrap(),
                &ephemeral_bytes,
                logger.clone(),
-               IgnoringMessageHandler {},
                Arc::clone(&keys_manager),
        ));
 
@@ -888,6 +894,7 @@ async fn start_ldk() {
                Arc::clone(&logger),
                Arc::clone(&persister),
                Arc::clone(&bitcoind_client),
+               Arc::clone(&channel_manager),
        ));
 
        // Start the CLI.
index 2a168a7d9235bbf4af012857ef36273863dd3b80..a9ad6bdd8e3c0561959f251325b068703822551c 100644 (file)
@@ -5,15 +5,17 @@ use std::time::Duration;
 use std::{fs, io};
 
 use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
-use lightning::chain::keysinterface::{EntropySource, KeysManager, SpendableOutputDescriptor};
+use lightning::sign::{EntropySource, KeysManager, SpendableOutputDescriptor};
 use lightning::util::logger::Logger;
 use lightning::util::persist::KVStorePersister;
 use lightning::util::ser::{Readable, WithoutLength};
 
 use bitcoin::secp256k1::Secp256k1;
+use bitcoin::{LockTime, PackedLockTime};
 
 use crate::hex_utils;
 use crate::BitcoindClient;
+use crate::ChannelManager;
 use crate::FilesystemLogger;
 use crate::FilesystemPersister;
 
@@ -28,6 +30,7 @@ use crate::FilesystemPersister;
 pub(crate) async fn periodic_sweep(
        ldk_data_dir: String, keys_manager: Arc<KeysManager>, logger: Arc<FilesystemLogger>,
        persister: Arc<FilesystemPersister>, bitcoind_client: Arc<BitcoindClient>,
+       channel_manager: Arc<ChannelManager>,
 ) {
        // Regularly claim outputs which are exclusively spendable by us and send them to Bitcoin Core.
        // Note that if you more tightly integrate your wallet with LDK you may not need to do this -
@@ -106,16 +109,23 @@ pub(crate) async fn periodic_sweep(
                                let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
                                let tx_feerate =
                                        bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
+
+                               // We set nLockTime to the current height to discourage fee sniping.
+                               let cur_height = channel_manager.current_best_block().height();
+                               let locktime: PackedLockTime =
+                                       LockTime::from_height(cur_height).map_or(PackedLockTime::ZERO, |l| l.into());
+
                                if let Ok(spending_tx) = keys_manager.spend_spendable_outputs(
                                        output_descriptors,
                                        Vec::new(),
                                        destination_address.script_pubkey(),
                                        tx_feerate,
+                                       Some(locktime),
                                        &Secp256k1::new(),
                                ) {
                                        // Note that, most likely, we've already sweeped this set of outputs
                                        // and they're already confirmed on-chain, so this broadcast will fail.
-                                       bitcoind_client.broadcast_transaction(&spending_tx);
+                                       bitcoind_client.broadcast_transactions(&[&spending_tx]);
                                } else {
                                        lightning::log_error!(
                                                logger,