Skip to content

Commit 67fa5b0

Browse files
Migrate deprecated spendable outputs persistence.
1 parent 0071c38 commit 67fa5b0

File tree

2 files changed

+75
-128
lines changed

2 files changed

+75
-128
lines changed

src/main.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
6161
use std::sync::{Arc, Mutex, RwLock};
6262
use std::time::{Duration, SystemTime};
6363

64-
pub(crate) const PENDING_SPENDABLE_OUTPUT_DIR: &'static str = "pending_spendable_outputs";
65-
6664
#[derive(Copy, Clone)]
6765
pub(crate) enum HTLCStatus {
6866
Pending,
@@ -1078,14 +1076,12 @@ async fn start_ldk() {
10781076
}
10791077
});
10801078

1081-
// TODO: remove this, since the new `OutputSweeper` was added in LDK v0.0.123.
1082-
tokio::spawn(sweep::periodic_sweep(
1079+
tokio::spawn(sweep::migrate_deprecated_spendable_outputs(
10831080
ldk_data_dir.clone(),
10841081
Arc::clone(&keys_manager),
10851082
Arc::clone(&logger),
10861083
Arc::clone(&persister),
1087-
Arc::clone(&bitcoind_client),
1088-
Arc::clone(&channel_manager),
1084+
Arc::clone(&output_sweeper),
10891085
));
10901086

10911087
// Start the CLI.

src/sweep.rs

Lines changed: 73 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,102 @@
11
use std::io::{Read, Seek, SeekFrom};
2-
use std::path::PathBuf;
2+
use std::path::{Path, PathBuf};
33
use std::sync::Arc;
4-
use std::time::Duration;
54
use std::{fs, io};
65

7-
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
8-
use lightning::sign::{EntropySource, KeysManager, OutputSpender, SpendableOutputDescriptor};
6+
use lightning::sign::{EntropySource, KeysManager, SpendableOutputDescriptor};
97
use lightning::util::logger::Logger;
108
use lightning::util::persist::KVStore;
119
use lightning::util::ser::{Readable, WithoutLength, Writeable};
1210

1311
use lightning_persister::fs_store::FilesystemStore;
1412

15-
use bitcoin::blockdata::locktime::absolute::LockTime;
16-
use bitcoin::secp256k1::Secp256k1;
17-
use rand::{thread_rng, Rng};
18-
13+
use crate::disk::FilesystemLogger;
1914
use crate::hex_utils;
20-
use crate::BitcoindClient;
21-
use crate::ChannelManager;
22-
use crate::FilesystemLogger;
15+
use crate::OutputSweeper;
16+
17+
const DEPRECATED_PENDING_SPENDABLE_OUTPUT_DIR: &'static str = "pending_spendable_outputs";
2318

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(
19+
/// We updated to use LDK's OutputSweeper as part of upgrading to LDK 0.0.123, so migrate away from
20+
/// the old sweep persistence.
21+
pub(crate) async fn migrate_deprecated_spendable_outputs(
3322
ldk_data_dir: String, keys_manager: Arc<KeysManager>, logger: Arc<FilesystemLogger>,
34-
persister: Arc<FilesystemStore>, bitcoind_client: Arc<BitcoindClient>,
35-
channel_manager: Arc<ChannelManager>,
23+
persister: Arc<FilesystemStore>, sweeper: Arc<OutputSweeper>,
3624
) {
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.
25+
lightning::log_info!(&*logger, "Beginning migration of deprecated spendable outputs");
4026
let pending_spendables_dir =
41-
format!("{}/{}", ldk_data_dir, crate::PENDING_SPENDABLE_OUTPUT_DIR);
27+
format!("{}/{}", ldk_data_dir, DEPRECATED_PENDING_SPENDABLE_OUTPUT_DIR);
4228
let processing_spendables_dir = format!("{}/processing_spendable_outputs", ldk_data_dir);
4329
let spendables_dir = format!("{}/spendable_outputs", ldk_data_dir);
4430

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));
31+
if !Path::new(&pending_spendables_dir).exists()
32+
&& !Path::new(&processing_spendables_dir).exists()
33+
&& !Path::new(&spendables_dir).exists()
34+
{
35+
lightning::log_info!(&*logger, "No deprecated spendable outputs to migrate, returning");
36+
return;
37+
}
5438

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-
}
39+
if let Ok(dir_iter) = fs::read_dir(&pending_spendables_dir) {
40+
// Move any spendable descriptors from pending folder so that we don't have any
41+
// races with new files being added.
42+
for file_res in dir_iter {
43+
let file = file_res.unwrap();
44+
// Only move a file if its a 32-byte-hex'd filename, otherwise it might be a
45+
// temporary file.
46+
if file.file_name().len() == 64 {
47+
fs::create_dir_all(&processing_spendables_dir).unwrap();
48+
let mut holding_path = PathBuf::new();
49+
holding_path.push(&processing_spendables_dir);
50+
holding_path.push(&file.file_name());
51+
fs::rename(file.path(), holding_path).unwrap();
7152
}
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();
53+
}
54+
// Now concatenate all the pending files we moved into one file in the
55+
// `spendable_outputs` directory and drop the processing directory.
56+
let mut outputs = Vec::new();
57+
if let Ok(processing_iter) = fs::read_dir(&processing_spendables_dir) {
58+
for file_res in processing_iter {
59+
outputs.append(&mut fs::read(file_res.unwrap().path()).unwrap());
8660
}
8761
}
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 = bitcoind_client
113-
.get_est_sat_per_1000_weight(ConfirmationTarget::ChannelCloseMinimum);
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-
}
62+
if !outputs.is_empty() {
63+
let key = hex_utils::hex_str(&keys_manager.get_secure_random_bytes());
64+
persister
65+
.write("spendable_outputs", "", &key, &WithoutLength(&outputs).encode())
66+
.unwrap();
67+
fs::remove_dir_all(&processing_spendables_dir).unwrap();
68+
}
69+
}
12870

129-
let locktime =
130-
LockTime::from_height(cur_height).map_or(LockTime::ZERO, |l| l.into());
71+
let best_block = sweeper.current_best_block();
13172

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.");
73+
let mut outputs: Vec<SpendableOutputDescriptor> = Vec::new();
74+
if let Ok(dir_iter) = fs::read_dir(&spendables_dir) {
75+
for file_res in dir_iter {
76+
let mut file = fs::File::open(file_res.unwrap().path()).unwrap();
77+
loop {
78+
// Check if there are any bytes left to read, and if so read a descriptor.
79+
match file.read_exact(&mut [0; 1]) {
80+
Ok(_) => {
81+
file.seek(SeekFrom::Current(-1)).unwrap();
82+
},
83+
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
84+
Err(e) => Err(e).unwrap(),
14785
}
86+
outputs.push(Readable::read(&mut file).unwrap());
14887
}
14988
}
15089
}
90+
91+
let spend_delay = Some(best_block.height + 2);
92+
sweeper.track_spendable_outputs(outputs.clone(), None, false, spend_delay).unwrap();
93+
94+
fs::remove_dir_all(&spendables_dir).unwrap();
95+
fs::remove_dir_all(&pending_spendables_dir).unwrap();
96+
97+
lightning::log_info!(
98+
&*logger,
99+
"Successfully migrated {} deprecated spendable outputs",
100+
outputs.len()
101+
);
151102
}

0 commit comments

Comments
 (0)