01cea9d32bbc7c042d4a7b457015b23a01409d4d
[ldk-sample] / src / sweep.rs
1 use std::io::{Read, Seek, SeekFrom};
2 use std::path::PathBuf;
3 use std::sync::Arc;
4 use std::time::Duration;
5 use std::{fs, io};
6
7 use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
8 use lightning::sign::{EntropySource, KeysManager, SpendableOutputDescriptor};
9 use lightning::util::logger::Logger;
10 use lightning::util::persist::KVStore;
11 use lightning::util::ser::{Readable, WithoutLength, Writeable};
12
13 use lightning_persister::fs_store::FilesystemStore;
14
15 use bitcoin::secp256k1::Secp256k1;
16 use bitcoin::{LockTime, PackedLockTime};
17 use rand::{thread_rng, Rng};
18
19 use crate::hex_utils;
20 use crate::BitcoindClient;
21 use crate::ChannelManager;
22 use crate::FilesystemLogger;
23
24 /// If we have any pending claimable outputs, we should slowly sweep them to our Bitcoin Core
25 /// wallet. We technically don't need to do this - they're ours to spend when we want and can just
26 /// use them to build new transactions instead, but we cannot feed them direclty into Bitcoin
27 /// Core's wallet so we have to sweep.
28 ///
29 /// Note that this is unececssary for [`SpendableOutputDescriptor::StaticOutput`]s, which *do* have
30 /// an associated secret key we could simply import into Bitcoin Core's wallet, but for consistency
31 /// we don't do that here either.
32 pub(crate) async fn periodic_sweep(
33         ldk_data_dir: String, keys_manager: Arc<KeysManager>, logger: Arc<FilesystemLogger>,
34         persister: Arc<FilesystemStore>, bitcoind_client: Arc<BitcoindClient>,
35         channel_manager: Arc<ChannelManager>,
36 ) {
37         // Regularly claim outputs which are exclusively spendable by us and send them to Bitcoin Core.
38         // Note that if you more tightly integrate your wallet with LDK you may not need to do this -
39         // these outputs can just be treated as normal outputs during coin selection.
40         let pending_spendables_dir =
41                 format!("{}/{}", ldk_data_dir, crate::PENDING_SPENDABLE_OUTPUT_DIR);
42         let processing_spendables_dir = format!("{}/processing_spendable_outputs", ldk_data_dir);
43         let spendables_dir = format!("{}/spendable_outputs", ldk_data_dir);
44
45         // We batch together claims of all spendable outputs generated each day, however only after
46         // batching any claims of spendable outputs which were generated prior to restart. On a mobile
47         // device we likely won't ever be online for more than a minute, so we have to ensure we sweep
48         // any pending claims on startup, but for an always-online node you may wish to sweep even less
49         // frequently than this (or move the interval await to the top of the loop)!
50         //
51         // There is no particular rush here, we just have to ensure funds are availably by the time we
52         // need to send funds.
53         let mut interval = tokio::time::interval(Duration::from_secs(60 * 60 * 24));
54
55         loop {
56                 interval.tick().await; // Note that the first tick completes immediately
57                 if let Ok(dir_iter) = fs::read_dir(&pending_spendables_dir) {
58                         // Move any spendable descriptors from pending folder so that we don't have any
59                         // races with new files being added.
60                         for file_res in dir_iter {
61                                 let file = file_res.unwrap();
62                                 // Only move a file if its a 32-byte-hex'd filename, otherwise it might be a
63                                 // temporary file.
64                                 if file.file_name().len() == 64 {
65                                         fs::create_dir_all(&processing_spendables_dir).unwrap();
66                                         let mut holding_path = PathBuf::new();
67                                         holding_path.push(&processing_spendables_dir);
68                                         holding_path.push(&file.file_name());
69                                         fs::rename(file.path(), holding_path).unwrap();
70                                 }
71                         }
72                         // Now concatenate all the pending files we moved into one file in the
73                         // `spendable_outputs` directory and drop the processing directory.
74                         let mut outputs = Vec::new();
75                         if let Ok(processing_iter) = fs::read_dir(&processing_spendables_dir) {
76                                 for file_res in processing_iter {
77                                         outputs.append(&mut fs::read(file_res.unwrap().path()).unwrap());
78                                 }
79                         }
80                         if !outputs.is_empty() {
81                                 let key = hex_utils::hex_str(&keys_manager.get_secure_random_bytes());
82                                 persister
83                                         .write("spendable_outputs", "", &key, &WithoutLength(&outputs).encode())
84                                         .unwrap();
85                                 fs::remove_dir_all(&processing_spendables_dir).unwrap();
86                         }
87                 }
88                 // Iterate over all the sets of spendable outputs in `spendables_dir` and try to claim
89                 // them.
90                 // Note that here we try to claim each set of spendable outputs over and over again
91                 // forever, even long after its been claimed. While this isn't an issue per se, in practice
92                 // you may wish to track when the claiming transaction has confirmed and remove the
93                 // spendable outputs set. You may also wish to merge groups of unspent spendable outputs to
94                 // combine batches.
95                 if let Ok(dir_iter) = fs::read_dir(&spendables_dir) {
96                         for file_res in dir_iter {
97                                 let mut outputs: Vec<SpendableOutputDescriptor> = Vec::new();
98                                 let mut file = fs::File::open(file_res.unwrap().path()).unwrap();
99                                 loop {
100                                         // Check if there are any bytes left to read, and if so read a descriptor.
101                                         match file.read_exact(&mut [0; 1]) {
102                                                 Ok(_) => {
103                                                         file.seek(SeekFrom::Current(-1)).unwrap();
104                                                 }
105                                                 Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
106                                                 Err(e) => Err(e).unwrap(),
107                                         }
108                                         outputs.push(Readable::read(&mut file).unwrap());
109                                 }
110                                 let destination_address = bitcoind_client.get_new_address().await;
111                                 let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
112                                 let tx_feerate =
113                                         bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
114
115                                 // We set nLockTime to the current height to discourage fee sniping.
116                                 // Occasionally randomly pick a nLockTime even further back, so
117                                 // that transactions that are delayed after signing for whatever reason,
118                                 // e.g. high-latency mix networks and some CoinJoin implementations, have
119                                 // better privacy.
120                                 // Logic copied from core: https://github.com/bitcoin/bitcoin/blob/1d4846a8443be901b8a5deb0e357481af22838d0/src/wallet/spend.cpp#L936
121                                 let mut cur_height = channel_manager.current_best_block().height();
122
123                                 // 10% of the time
124                                 if thread_rng().gen_range(0, 10) == 0 {
125                                         // subtract random number between 0 and 100
126                                         cur_height = cur_height.saturating_sub(thread_rng().gen_range(0, 100));
127                                 }
128
129                                 let locktime: PackedLockTime =
130                                         LockTime::from_height(cur_height).map_or(PackedLockTime::ZERO, |l| l.into());
131
132                                 if let Ok(spending_tx) = keys_manager.spend_spendable_outputs(
133                                         output_descriptors,
134                                         Vec::new(),
135                                         destination_address.script_pubkey(),
136                                         tx_feerate,
137                                         Some(locktime),
138                                         &Secp256k1::new(),
139                                 ) {
140                                         // Note that, most likely, we've already sweeped this set of outputs
141                                         // and they're already confirmed on-chain, so this broadcast will fail.
142                                         bitcoind_client.broadcast_transactions(&[&spending_tx]);
143                                 } else {
144                                         lightning::log_error!(
145                                                 logger,
146                                                 "Failed to sweep spendable outputs! This may indicate the outputs are dust. Will try again in a day.");
147                                 }
148                         }
149                 }
150         }
151 }