Skip to content

Commit 30a9cb7

Browse files
authored
Merge pull request #103 from TheBlueMatt/main
Make SpendableOutput claims more robust
2 parents 7aceee9 + b0987a4 commit 30a9cb7

File tree

2 files changed

+181
-30
lines changed

2 files changed

+181
-30
lines changed

src/main.rs

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ mod cli;
44
mod convert;
55
mod disk;
66
mod hex_utils;
7+
mod sweep;
78

89
use crate::bitcoind_client::BitcoindClient;
910
use crate::disk::FilesystemLogger;
1011
use bitcoin::blockdata::transaction::Transaction;
1112
use bitcoin::consensus::encode;
1213
use bitcoin::network::constants::Network;
13-
use bitcoin::secp256k1::Secp256k1;
1414
use bitcoin::BlockHash;
1515
use bitcoin_bech32::WitnessProgram;
1616
use lightning::chain;
17-
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
18-
use lightning::chain::keysinterface::{EntropySource, InMemorySigner, KeysManager};
17+
use lightning::chain::keysinterface::{
18+
EntropySource, InMemorySigner, KeysManager, SpendableOutputDescriptor,
19+
};
1920
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
2021
use lightning::chain::{Filter, Watch};
2122
use lightning::events::{Event, PaymentFailureReason, PaymentPurpose};
@@ -30,6 +31,7 @@ use lightning::routing::gossip;
3031
use lightning::routing::gossip::{NodeId, P2PGossipSync};
3132
use lightning::routing::router::DefaultRouter;
3233
use lightning::util::config::UserConfig;
34+
use lightning::util::persist::KVStorePersister;
3335
use lightning::util::ser::ReadableArgs;
3436
use lightning_background_processor::{process_events_async, GossipSync};
3537
use lightning_block_sync::init;
@@ -52,6 +54,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
5254
use std::sync::{Arc, Mutex};
5355
use std::time::{Duration, SystemTime};
5456

57+
pub(crate) const PENDING_SPENDABLE_OUTPUT_DIR: &'static str = "pending_spendable_outputs";
58+
5559
pub(crate) enum HTLCStatus {
5660
Pending,
5761
Succeeded,
@@ -107,7 +111,7 @@ async fn handle_ldk_events(
107111
channel_manager: &Arc<ChannelManager>, bitcoind_client: &BitcoindClient,
108112
network_graph: &NetworkGraph, keys_manager: &KeysManager,
109113
inbound_payments: &PaymentInfoStorage, outbound_payments: &PaymentInfoStorage,
110-
network: Network, event: Event,
114+
persister: &Arc<FilesystemPersister>, network: Network, event: Event,
111115
) {
112116
match event {
113117
Event::FundingGenerationReady {
@@ -331,20 +335,23 @@ async fn handle_ldk_events(
331335
});
332336
}
333337
Event::SpendableOutputs { outputs } => {
334-
let destination_address = bitcoind_client.get_new_address().await;
335-
let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
336-
let tx_feerate =
337-
bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
338-
let spending_tx = keys_manager
339-
.spend_spendable_outputs(
340-
output_descriptors,
341-
Vec::new(),
342-
destination_address.script_pubkey(),
343-
tx_feerate,
344-
&Secp256k1::new(),
345-
)
346-
.unwrap();
347-
bitcoind_client.broadcast_transaction(&spending_tx);
338+
// SpendableOutputDescriptors, of which outputs is a vec of, are critical to keep track
339+
// of! While a `StaticOutput` descriptor is just an output to a static, well-known key,
340+
// other descriptors are not currently ever regenerated for you by LDK. Once we return
341+
// from this method, the descriptor will be gone, and you may lose track of some funds.
342+
//
343+
// Here we simply persist them to disk, with a background task running which will try
344+
// to spend them regularly (possibly duplicatively/RBF'ing them). These can just be
345+
// treated as normal funds where possible - they are only spendable by us and there is
346+
// no rush to claim them.
347+
for output in outputs {
348+
let key = hex_utils::hex_str(&keys_manager.get_secure_random_bytes());
349+
// Note that if the type here changes our read code needs to change as well.
350+
let output: SpendableOutputDescriptor = output;
351+
persister
352+
.persist(&format!("{}/{}", PENDING_SPENDABLE_OUTPUT_DIR, key), &output)
353+
.unwrap();
354+
}
348355
}
349356
Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
350357
println!(
@@ -693,6 +700,7 @@ async fn start_ldk() {
693700
let keys_manager_event_listener = Arc::clone(&keys_manager);
694701
let inbound_payments_event_listener = Arc::clone(&inbound_payments);
695702
let outbound_payments_event_listener = Arc::clone(&outbound_payments);
703+
let persister_event_listener = Arc::clone(&persister);
696704
let network = args.network;
697705
let event_handler = move |event: Event| {
698706
let channel_manager_event_listener = Arc::clone(&channel_manager_event_listener);
@@ -701,6 +709,7 @@ async fn start_ldk() {
701709
let keys_manager_event_listener = Arc::clone(&keys_manager_event_listener);
702710
let inbound_payments_event_listener = Arc::clone(&inbound_payments_event_listener);
703711
let outbound_payments_event_listener = Arc::clone(&outbound_payments_event_listener);
712+
let persister_event_listener = Arc::clone(&persister_event_listener);
704713
async move {
705714
handle_ldk_events(
706715
&channel_manager_event_listener,
@@ -709,6 +718,7 @@ async fn start_ldk() {
709718
&keys_manager_event_listener,
710719
&inbound_payments_event_listener,
711720
&outbound_payments_event_listener,
721+
&persister_event_listener,
712722
network,
713723
event,
714724
)
@@ -722,7 +732,7 @@ async fn start_ldk() {
722732
// Step 20: Background Processing
723733
let (bp_exit, bp_exit_check) = tokio::sync::watch::channel(());
724734
let background_processor = tokio::spawn(process_events_async(
725-
persister,
735+
Arc::clone(&persister),
726736
event_handler,
727737
chain_monitor.clone(),
728738
channel_manager.clone(),
@@ -781,24 +791,38 @@ async fn start_ldk() {
781791
});
782792

783793
// Regularly broadcast our node_announcement. This is only required (or possible) if we have
784-
// some public channels, and is only useful if we have public listen address(es) to announce.
785-
// In a production environment, this should occur only after the announcement of new channels
786-
// to avoid churn in the global network graph.
794+
// some public channels.
787795
let peer_man = Arc::clone(&peer_manager);
796+
let chan_man = Arc::clone(&channel_manager);
788797
let network = args.network;
789-
if !args.ldk_announced_listen_addr.is_empty() {
790-
tokio::spawn(async move {
791-
let mut interval = tokio::time::interval(Duration::from_secs(60));
792-
loop {
793-
interval.tick().await;
798+
tokio::spawn(async move {
799+
// First wait a minute until we have some peers and maybe have opened a channel.
800+
tokio::time::sleep(Duration::from_secs(60)).await;
801+
// Then, update our announcement once an hour to keep it fresh but avoid unnecessary churn
802+
// in the global gossip network.
803+
let mut interval = tokio::time::interval(Duration::from_secs(3600));
804+
loop {
805+
interval.tick().await;
806+
// Don't bother trying to announce if we don't have any public channls, though our
807+
// peers should drop such an announcement anyway. Note that announcement may not
808+
// propagate until we have a channel with 6+ confirmations.
809+
if chan_man.list_channels().iter().any(|chan| chan.is_public) {
794810
peer_man.broadcast_node_announcement(
795811
[0; 3],
796812
args.ldk_announced_node_name,
797813
args.ldk_announced_listen_addr.clone(),
798814
);
799815
}
800-
});
801-
}
816+
}
817+
});
818+
819+
tokio::spawn(sweep::periodic_sweep(
820+
ldk_data_dir.clone(),
821+
Arc::clone(&keys_manager),
822+
Arc::clone(&logger),
823+
Arc::clone(&persister),
824+
Arc::clone(&bitcoind_client),
825+
));
802826

803827
// Start the CLI.
804828
cli::poll_for_user_input(
@@ -809,7 +833,7 @@ async fn start_ldk() {
809833
Arc::clone(&onion_messenger),
810834
inbound_payments,
811835
outbound_payments,
812-
ldk_data_dir.clone(),
836+
ldk_data_dir,
813837
network,
814838
Arc::clone(&logger),
815839
)

src/sweep.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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::chain::keysinterface::{EntropySource, KeysManager, SpendableOutputDescriptor};
9+
use lightning::util::logger::Logger;
10+
use lightning::util::persist::KVStorePersister;
11+
use lightning::util::ser::{Readable, WithoutLength};
12+
13+
use bitcoin::secp256k1::Secp256k1;
14+
15+
use crate::hex_utils;
16+
use crate::BitcoindClient;
17+
use crate::FilesystemLogger;
18+
use crate::FilesystemPersister;
19+
20+
/// If we have any pending claimable outputs, we should slowly sweep them to our Bitcoin Core
21+
/// wallet. We technically don't need to do this - they're ours to spend when we want and can just
22+
/// use them to build new transactions instead, but we cannot feed them direclty into Bitcoin
23+
/// Core's wallet so we have to sweep.
24+
///
25+
/// Note that this is unececssary for [`SpendableOutputDescriptor::StaticOutput`]s, which *do* have
26+
/// an associated secret key we could simply import into Bitcoin Core's wallet, but for consistency
27+
/// we don't do that here either.
28+
pub(crate) async fn periodic_sweep(
29+
ldk_data_dir: String, keys_manager: Arc<KeysManager>, logger: Arc<FilesystemLogger>,
30+
persister: Arc<FilesystemPersister>, bitcoind_client: Arc<BitcoindClient>,
31+
) {
32+
// Regularly claim outputs which are exclusively spendable by us and send them to Bitcoin Core.
33+
// Note that if you more tightly integrate your wallet with LDK you may not need to do this -
34+
// these outputs can just be treated as normal outputs during coin selection.
35+
let pending_spendables_dir =
36+
format!("{}/{}", crate::PENDING_SPENDABLE_OUTPUT_DIR, ldk_data_dir);
37+
let processing_spendables_dir = format!("{}/processing_spendable_outputs", ldk_data_dir);
38+
let spendables_dir = format!("{}/spendable_outputs", ldk_data_dir);
39+
40+
// We batch together claims of all spendable outputs generated each day, however only after
41+
// batching any claims of spendable outputs which were generated prior to restart. On a mobile
42+
// device we likely won't ever be online for more than a minute, so we have to ensure we sweep
43+
// any pending claims on startup, but for an always-online node you may wish to sweep even less
44+
// frequently than this (or move the interval await to the top of the loop)!
45+
//
46+
// There is no particular rush here, we just have to ensure funds are availably by the time we
47+
// need to send funds.
48+
let mut interval = tokio::time::interval(Duration::from_secs(60 * 60 * 24));
49+
50+
loop {
51+
interval.tick().await; // Note that the first tick completes immediately
52+
if let Ok(dir_iter) = fs::read_dir(&pending_spendables_dir) {
53+
// Move any spendable descriptors from pending folder so that we don't have any
54+
// races with new files being added.
55+
for file_res in dir_iter {
56+
let file = file_res.unwrap();
57+
// Only move a file if its a 32-byte-hex'd filename, otherwise it might be a
58+
// temporary file.
59+
if file.file_name().len() == 64 {
60+
fs::create_dir_all(&processing_spendables_dir).unwrap();
61+
let mut holding_path = PathBuf::new();
62+
holding_path.push(&processing_spendables_dir);
63+
holding_path.push(&file.file_name());
64+
fs::rename(file.path(), holding_path).unwrap();
65+
}
66+
}
67+
// Now concatenate all the pending files we moved into one file in the
68+
// `spendable_outputs` directory and drop the processing directory.
69+
let mut outputs = Vec::new();
70+
if let Ok(processing_iter) = fs::read_dir(&processing_spendables_dir) {
71+
for file_res in processing_iter {
72+
outputs.append(&mut fs::read(file_res.unwrap().path()).unwrap());
73+
}
74+
}
75+
if !outputs.is_empty() {
76+
let key = hex_utils::hex_str(&keys_manager.get_secure_random_bytes());
77+
persister
78+
.persist(&format!("spendable_outputs/{}", key), &WithoutLength(&outputs))
79+
.unwrap();
80+
fs::remove_dir_all(&processing_spendables_dir).unwrap();
81+
}
82+
}
83+
// Iterate over all the sets of spendable outputs in `spendables_dir` and try to claim
84+
// them.
85+
// Note that here we try to claim each set of spendable outputs over and over again
86+
// forever, even long after its been claimed. While this isn't an issue per se, in practice
87+
// you may wish to track when the claiming transaction has confirmed and remove the
88+
// spendable outputs set. You may also wish to merge groups of unspent spendable outputs to
89+
// combine batches.
90+
if let Ok(dir_iter) = fs::read_dir(&spendables_dir) {
91+
for file_res in dir_iter {
92+
let mut outputs: Vec<SpendableOutputDescriptor> = Vec::new();
93+
let mut file = fs::File::open(file_res.unwrap().path()).unwrap();
94+
loop {
95+
// Check if there are any bytes left to read, and if so read a descriptor.
96+
match file.read_exact(&mut [0; 1]) {
97+
Ok(_) => {
98+
file.seek(SeekFrom::Current(-1)).unwrap();
99+
}
100+
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
101+
Err(e) => Err(e).unwrap(),
102+
}
103+
outputs.push(Readable::read(&mut file).unwrap());
104+
}
105+
let destination_address = bitcoind_client.get_new_address().await;
106+
let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
107+
let tx_feerate =
108+
bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Background);
109+
if let Ok(spending_tx) = keys_manager.spend_spendable_outputs(
110+
output_descriptors,
111+
Vec::new(),
112+
destination_address.script_pubkey(),
113+
tx_feerate,
114+
&Secp256k1::new(),
115+
) {
116+
// Note that, most likely, we've already sweeped this set of outputs
117+
// and they're already confirmed on-chain, so this broadcast will fail.
118+
bitcoind_client.broadcast_transaction(&spending_tx);
119+
} else {
120+
lightning::log_error!(
121+
logger,
122+
"Failed to sweep spendable outputs! This may indicate the outputs are dust. Will try again in a day.");
123+
}
124+
}
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)