Skip to content

Commit 53dd65e

Browse files
committed
Try to sweep SpendableOutputs regularly, and in bulk
Rather than trying to sweep `SpendableOutputs` every time we see one, try to sweep them in bulk once an hour. Then, try to sweep them repeatedly every hour forever, even after they're claimed. Fixes #102.
1 parent 76b89c6 commit 53dd65e

File tree

1 file changed

+113
-21
lines changed

1 file changed

+113
-21
lines changed

src/main.rs

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ use bitcoin::BlockHash;
1515
use bitcoin_bech32::WitnessProgram;
1616
use lightning::chain;
1717
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
18-
use lightning::chain::keysinterface::{EntropySource, InMemorySigner, KeysManager};
18+
use lightning::chain::keysinterface::{
19+
EntropySource, InMemorySigner, KeysManager, SpendableOutputDescriptor
20+
};
1921
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
2022
use lightning::chain::{Filter, Watch};
2123
use lightning::events::{Event, PaymentFailureReason, PaymentPurpose};
@@ -30,7 +32,9 @@ use lightning::routing::gossip;
3032
use lightning::routing::gossip::{NodeId, P2PGossipSync};
3133
use lightning::routing::router::DefaultRouter;
3234
use lightning::util::config::UserConfig;
33-
use lightning::util::ser::ReadableArgs;
35+
use lightning::util::logger::Logger;
36+
use lightning::util::persist::KVStorePersister;
37+
use lightning::util::ser::{Readable, ReadableArgs, WithoutLength};
3438
use lightning_background_processor::{process_events_async, GossipSync};
3539
use lightning_block_sync::init;
3640
use lightning_block_sync::poll;
@@ -46,8 +50,8 @@ use std::fmt;
4650
use std::fs;
4751
use std::fs::File;
4852
use std::io;
49-
use std::io::Write;
50-
use std::path::Path;
53+
use std::io::{Read, Seek, SeekFrom, Write};
54+
use std::path::{Path, PathBuf};
5155
use std::sync::atomic::{AtomicBool, Ordering};
5256
use std::sync::{Arc, Mutex};
5357
use std::time::{Duration, SystemTime};
@@ -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,21 @@ 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.persist(&format!("pending_spendable_outputs/{}", key), &output).unwrap();
352+
}
348353
}
349354
Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
350355
println!(
@@ -693,6 +698,7 @@ async fn start_ldk() {
693698
let keys_manager_event_listener = Arc::clone(&keys_manager);
694699
let inbound_payments_event_listener = Arc::clone(&inbound_payments);
695700
let outbound_payments_event_listener = Arc::clone(&outbound_payments);
701+
let persister_event_listener = Arc::clone(&persister);
696702
let network = args.network;
697703
let event_handler = move |event: Event| {
698704
let channel_manager_event_listener = Arc::clone(&channel_manager_event_listener);
@@ -701,6 +707,7 @@ async fn start_ldk() {
701707
let keys_manager_event_listener = Arc::clone(&keys_manager_event_listener);
702708
let inbound_payments_event_listener = Arc::clone(&inbound_payments_event_listener);
703709
let outbound_payments_event_listener = Arc::clone(&outbound_payments_event_listener);
710+
let persister_event_listener = Arc::clone(&persister_event_listener);
704711
async move {
705712
handle_ldk_events(
706713
&channel_manager_event_listener,
@@ -709,6 +716,7 @@ async fn start_ldk() {
709716
&keys_manager_event_listener,
710717
&inbound_payments_event_listener,
711718
&outbound_payments_event_listener,
719+
&persister_event_listener,
712720
network,
713721
event,
714722
)
@@ -722,7 +730,7 @@ async fn start_ldk() {
722730
// Step 20: Background Processing
723731
let (bp_exit, bp_exit_check) = tokio::sync::watch::channel(());
724732
let background_processor = tokio::spawn(process_events_async(
725-
persister,
733+
Arc::clone(&persister),
726734
event_handler,
727735
chain_monitor.clone(),
728736
channel_manager.clone(),
@@ -800,6 +808,90 @@ async fn start_ldk() {
800808
}
801809
});
802810

811+
// Regularly claim outputs which are exclusively spendable by us and send them to Bitcoin Core.
812+
// Note that if you more tightly integrate your wallet with LDK you may not need to do this -
813+
// these outputs can just be treated as normal outputs during coin selection.
814+
let pending_spendables_dir = format!("{}/pending_spendable_outputs", ldk_data_dir);
815+
let processing_spendables_dir = format!("{}/processing_spendable_outputs", ldk_data_dir);
816+
let spendables_dir = format!("{}/spendable_outputs", ldk_data_dir);
817+
let spending_keys_manager = Arc::clone(&keys_manager);
818+
let spending_logger = Arc::clone(&logger);
819+
tokio::spawn(async move {
820+
let mut interval = tokio::time::interval(Duration::from_secs(3600));
821+
loop {
822+
interval.tick().await;
823+
if let Ok(dir_iter) = fs::read_dir(&pending_spendables_dir) {
824+
// Move any spendable descriptors from pending folder so that we don't have any
825+
// races with new files being added.
826+
for file_res in dir_iter {
827+
let file = file_res.unwrap();
828+
// Only move a file if its a 32-byte-hex'd filename, otherwise it might be a
829+
// temporary file.
830+
if file.file_name().len() == 64 {
831+
fs::create_dir_all(&processing_spendables_dir).unwrap();
832+
let mut holding_path = PathBuf::new();
833+
holding_path.push(&processing_spendables_dir);
834+
holding_path.push(&file.file_name());
835+
fs::rename(file.path(), holding_path).unwrap();
836+
}
837+
}
838+
// Now concatenate all the pending files we moved into one file in the
839+
// `spendable_outputs` directory and drop the processing directory.
840+
let mut outputs = Vec::new();
841+
if let Ok(processing_iter) = fs::read_dir(&processing_spendables_dir) {
842+
for file_res in processing_iter {
843+
outputs.append(&mut fs::read(file_res.unwrap().path()).unwrap());
844+
}
845+
}
846+
if !outputs.is_empty() {
847+
let key = hex_utils::hex_str(&spending_keys_manager.get_secure_random_bytes());
848+
persister
849+
.persist(&format!("spendable_outputs/{}", key), &WithoutLength(&outputs))
850+
.unwrap();
851+
fs::remove_dir_all(&processing_spendables_dir).unwrap();
852+
}
853+
}
854+
// Iterate over all the sets of spendable outputs in `spendables_dir` and try to claim
855+
// them.
856+
if let Ok(dir_iter) = fs::read_dir(&spendables_dir) {
857+
for file_res in dir_iter {
858+
let mut outputs: Vec<SpendableOutputDescriptor> = Vec::new();
859+
let mut file = File::open(file_res.unwrap().path()).unwrap();
860+
loop {
861+
// Check if there are any bytes left to read, and if so read a descriptor.
862+
match file.read_exact(&mut [0; 1]) {
863+
Ok(_) => {
864+
file.seek(SeekFrom::Current(-1)).unwrap();
865+
},
866+
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break,
867+
Err(e) => Err(e).unwrap(),
868+
}
869+
outputs.push(Readable::read(&mut file).unwrap());
870+
}
871+
let destination_address = bitcoind_client.get_new_address().await;
872+
let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
873+
let tx_feerate =
874+
bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
875+
if let Ok(spending_tx) = spending_keys_manager.spend_spendable_outputs(
876+
output_descriptors,
877+
Vec::new(),
878+
destination_address.script_pubkey(),
879+
tx_feerate,
880+
&Secp256k1::new(),
881+
) {
882+
// Note that, most likely, we've already sweeped this set of outputs
883+
// and they're already confirmed on-chain, so this broadcast will fail.
884+
bitcoind_client.broadcast_transaction(&spending_tx);
885+
} else {
886+
lightning::log_error!(
887+
spending_logger,
888+
"Failed to sweep spendable outputs! This may indicate the outputs are dust. Will try again in an hour.");
889+
}
890+
}
891+
}
892+
}
893+
});
894+
803895
// Start the CLI.
804896
cli::poll_for_user_input(
805897
Arc::clone(&peer_manager),
@@ -809,7 +901,7 @@ async fn start_ldk() {
809901
Arc::clone(&onion_messenger),
810902
inbound_payments,
811903
outbound_payments,
812-
ldk_data_dir.clone(),
904+
ldk_data_dir,
813905
network,
814906
Arc::clone(&logger),
815907
)

0 commit comments

Comments
 (0)