Use bitcoin core's logic for anti-fee-sniping
[ldk-sample] / src / sweep.rs
index 2a168a7d9235bbf4af012857ef36273863dd3b80..94120f9088ba377a659faf867ac394d1981ee80a 100644 (file)
@@ -5,15 +5,18 @@ 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 rand::{thread_rng, Rng};
 
 use crate::hex_utils;
 use crate::BitcoindClient;
+use crate::ChannelManager;
 use crate::FilesystemLogger;
 use crate::FilesystemPersister;
 
@@ -28,12 +31,13 @@ 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 -
        // these outputs can just be treated as normal outputs during coin selection.
        let pending_spendables_dir =
-               format!("{}/{}", crate::PENDING_SPENDABLE_OUTPUT_DIR, ldk_data_dir);
+               format!("{}/{}", ldk_data_dir, crate::PENDING_SPENDABLE_OUTPUT_DIR);
        let processing_spendables_dir = format!("{}/processing_spendable_outputs", ldk_data_dir);
        let spendables_dir = format!("{}/spendable_outputs", ldk_data_dir);
 
@@ -106,16 +110,35 @@ 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.
+                               // Occasionally randomly pick a nLockTime even further back, so
+                               // that transactions that are delayed after signing for whatever reason,
+                               // e.g. high-latency mix networks and some CoinJoin implementations, have
+                               // better privacy.
+                               // Logic copied from core: https://github.com/bitcoin/bitcoin/blob/1d4846a8443be901b8a5deb0e357481af22838d0/src/wallet/spend.cpp#L936
+                               let mut cur_height = channel_manager.current_best_block().height();
+
+                               // 10% of the time
+                               if thread_rng().gen_range(0, 10) == 0 {
+                                       // subtract random number between 0 and 100
+                                       cur_height = cur_height.saturating_sub(thread_rng().gen_range(0, 100));
+                               }
+
+                               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,