mod convert;
mod disk;
mod hex_utils;
+mod sweep;
use crate::bitcoind_client::BitcoindClient;
use crate::disk::FilesystemLogger;
use bitcoin::blockdata::transaction::Transaction;
use bitcoin::consensus::encode;
use bitcoin::network::constants::Network;
-use bitcoin::secp256k1::Secp256k1;
use bitcoin::BlockHash;
use bitcoin_bech32::WitnessProgram;
use lightning::chain;
-use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
-use lightning::chain::keysinterface::{EntropySource, InMemorySigner, KeysManager};
+use lightning::chain::keysinterface::{
+ EntropySource, InMemorySigner, KeysManager, SpendableOutputDescriptor,
+};
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
use lightning::chain::{Filter, Watch};
+use lightning::events::{Event, PaymentFailureReason, PaymentPurpose};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager,
use lightning::routing::gossip::{NodeId, P2PGossipSync};
use lightning::routing::router::DefaultRouter;
use lightning::util::config::UserConfig;
-use lightning::util::events::{Event, PaymentPurpose};
+use lightning::util::persist::KVStorePersister;
use lightning::util::ser::ReadableArgs;
-use lightning_background_processor::{BackgroundProcessor, GossipSync};
+use lightning_background_processor::{process_events_async, GossipSync};
use lightning_block_sync::init;
use lightning_block_sync::poll;
use lightning_block_sync::SpvClient;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
+pub(crate) const PENDING_SPENDABLE_OUTPUT_DIR: &'static str = "pending_spendable_outputs";
+
pub(crate) enum HTLCStatus {
Pending,
Succeeded,
channel_manager: &Arc<ChannelManager>, bitcoind_client: &BitcoindClient,
network_graph: &NetworkGraph, keys_manager: &KeysManager,
inbound_payments: &PaymentInfoStorage, outbound_payments: &PaymentInfoStorage,
- network: Network, event: &Event,
+ persister: &Arc<FilesystemPersister>, network: Network, event: Event,
) {
match event {
Event::FundingGenerationReady {
.expect("Lightning funding tx should always be to a SegWit output")
.to_address();
let mut outputs = vec![HashMap::with_capacity(1)];
- outputs[0].insert(addr, *channel_value_satoshis as f64 / 100_000_000.0);
+ outputs[0].insert(addr, channel_value_satoshis as f64 / 100_000_000.0);
let raw_tx = bitcoind_client.create_raw_transaction(outputs).await;
// Have your wallet put the inputs into the transaction such that the output is
if channel_manager
.funding_transaction_generated(
&temporary_channel_id,
- counterparty_node_id,
+ &counterparty_node_id,
final_tx,
)
.is_err()
receiver_node_id: _,
via_channel_id: _,
via_user_channel_id: _,
+ claim_deadline: _,
+ onion_fields: _,
} => {
println!(
"\nEVENT: received payment from payment hash {} of {} millisatoshis",
print!("> ");
io::stdout().flush().unwrap();
let payment_preimage = match purpose {
- PaymentPurpose::InvoicePayment { payment_preimage, .. } => *payment_preimage,
- PaymentPurpose::SpontaneousPayment(preimage) => Some(*preimage),
+ PaymentPurpose::InvoicePayment { payment_preimage, .. } => payment_preimage,
+ PaymentPurpose::SpontaneousPayment(preimage) => Some(preimage),
};
channel_manager.claim_funds(payment_preimage.unwrap());
}
io::stdout().flush().unwrap();
let (payment_preimage, payment_secret) = match purpose {
PaymentPurpose::InvoicePayment { payment_preimage, payment_secret, .. } => {
- (*payment_preimage, Some(*payment_secret))
+ (payment_preimage, Some(payment_secret))
}
- PaymentPurpose::SpontaneousPayment(preimage) => (Some(*preimage), None),
+ PaymentPurpose::SpontaneousPayment(preimage) => (Some(preimage), None),
};
let mut payments = inbound_payments.lock().unwrap();
- match payments.entry(*payment_hash) {
+ match payments.entry(payment_hash) {
Entry::Occupied(mut e) => {
let payment = e.get_mut();
payment.status = HTLCStatus::Succeeded;
preimage: payment_preimage,
secret: payment_secret,
status: HTLCStatus::Succeeded,
- amt_msat: MillisatAmount(Some(*amount_msat)),
+ amt_msat: MillisatAmount(Some(amount_msat)),
});
}
}
Event::PaymentSent { payment_preimage, payment_hash, fee_paid_msat, .. } => {
let mut payments = outbound_payments.lock().unwrap();
for (hash, payment) in payments.iter_mut() {
- if *hash == *payment_hash {
- payment.preimage = Some(*payment_preimage);
+ if *hash == payment_hash {
+ payment.preimage = Some(payment_preimage);
payment.status = HTLCStatus::Succeeded;
println!(
"\nEVENT: successfully sent payment of {} millisatoshis{} from \
Event::PaymentPathFailed { .. } => {}
Event::ProbeSuccessful { .. } => {}
Event::ProbeFailed { .. } => {}
- Event::PaymentFailed { payment_hash, .. } => {
+ Event::PaymentFailed { payment_hash, reason, .. } => {
print!(
- "\nEVENT: Failed to send payment to payment hash {:?}: exhausted payment retry attempts",
- hex_utils::hex_str(&payment_hash.0)
+ "\nEVENT: Failed to send payment to payment hash {:?}: {:?}",
+ hex_utils::hex_str(&payment_hash.0),
+ if let Some(r) = reason { r } else { PaymentFailureReason::RetriesExhausted }
);
print!("> ");
io::stdout().flush().unwrap();
next_channel_id,
fee_earned_msat,
claim_from_onchain_tx,
+ outbound_amount_forwarded_msat,
} => {
let read_only_network_graph = network_graph.read_only();
let nodes = read_only_network_graph.nodes();
.unwrap_or_default()
};
let from_prev_str =
- format!(" from {}{}", node_str(prev_channel_id), channel_str(prev_channel_id));
+ format!(" from {}{}", node_str(&prev_channel_id), channel_str(&prev_channel_id));
let to_next_str =
- format!(" to {}{}", node_str(next_channel_id), channel_str(next_channel_id));
+ format!(" to {}{}", node_str(&next_channel_id), channel_str(&next_channel_id));
- let from_onchain_str = if *claim_from_onchain_tx {
+ let from_onchain_str = if claim_from_onchain_tx {
"from onchain downstream claim"
} else {
"from HTLC fulfill message"
};
+ let amt_args = if let Some(v) = outbound_amount_forwarded_msat {
+ format!("{}", v)
+ } else {
+ "?".to_string()
+ };
if let Some(fee_earned) = fee_earned_msat {
println!(
- "\nEVENT: Forwarded payment{}{}, earning {} msat {}",
- from_prev_str, to_next_str, fee_earned, from_onchain_str
+ "\nEVENT: Forwarded payment for {} msat{}{}, earning {} msat {}",
+ amt_args, from_prev_str, to_next_str, fee_earned, from_onchain_str
);
} else {
println!(
- "\nEVENT: Forwarded payment{}{}, claiming onchain {}",
- from_prev_str, to_next_str, from_onchain_str
+ "\nEVENT: Forwarded payment for {} msat{}{}, claiming onchain {}",
+ amt_args, from_prev_str, to_next_str, from_onchain_str
);
}
print!("> ");
});
}
Event::SpendableOutputs { outputs } => {
- let destination_address = bitcoind_client.get_new_address().await;
- let output_descriptors = &outputs.iter().map(|a| a).collect::<Vec<_>>();
- let tx_feerate =
- bitcoind_client.get_est_sat_per_1000_weight(ConfirmationTarget::Normal);
- let spending_tx = keys_manager
- .spend_spendable_outputs(
- output_descriptors,
- Vec::new(),
- destination_address.script_pubkey(),
- tx_feerate,
- &Secp256k1::new(),
- )
- .unwrap();
- bitcoind_client.broadcast_transaction(&spending_tx);
+ // SpendableOutputDescriptors, of which outputs is a vec of, are critical to keep track
+ // of! While a `StaticOutput` descriptor is just an output to a static, well-known key,
+ // other descriptors are not currently ever regenerated for you by LDK. Once we return
+ // from this method, the descriptor will be gone, and you may lose track of some funds.
+ //
+ // Here we simply persist them to disk, with a background task running which will try
+ // to spend them regularly (possibly duplicatively/RBF'ing them). These can just be
+ // treated as normal funds where possible - they are only spendable by us and there is
+ // no rush to claim them.
+ for output in outputs {
+ let key = hex_utils::hex_str(&keys_manager.get_secure_random_bytes());
+ // Note that if the type here changes our read code needs to change as well.
+ let output: SpendableOutputDescriptor = output;
+ persister
+ .persist(&format!("{}/{}", PENDING_SPENDABLE_OUTPUT_DIR, key), &output)
+ .unwrap();
+ }
+ }
+ Event::ChannelPending { channel_id, counterparty_node_id, .. } => {
+ println!(
+ "\nEVENT: Channel {} with peer {} is pending awaiting funding lock-in!",
+ hex_utils::hex_str(&channel_id),
+ hex_utils::hex_str(&counterparty_node_id.serialize()),
+ );
+ print!("> ");
+ io::stdout().flush().unwrap();
}
Event::ChannelReady {
ref channel_id,
Event::ChannelClosed { channel_id, reason, user_channel_id: _ } => {
println!(
"\nEVENT: Channel {} closed due to: {:?}",
- hex_utils::hex_str(channel_id),
+ hex_utils::hex_str(&channel_id),
reason
);
print!("> ");
}
});
- // Step 18: Handle LDK Events
- let channel_manager_event_listener = channel_manager.clone();
- let keys_manager_listener = keys_manager.clone();
// TODO: persist payment info to disk
let inbound_payments: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new()));
let outbound_payments: PaymentInfoStorage = Arc::new(Mutex::new(HashMap::new()));
- let inbound_pmts_for_events = inbound_payments.clone();
- let outbound_pmts_for_events = outbound_payments.clone();
+
+ // Step 18: Handle LDK Events
+ let channel_manager_event_listener = Arc::clone(&channel_manager);
+ let bitcoind_client_event_listener = Arc::clone(&bitcoind_client);
+ let network_graph_event_listener = Arc::clone(&network_graph);
+ let keys_manager_event_listener = Arc::clone(&keys_manager);
+ let inbound_payments_event_listener = Arc::clone(&inbound_payments);
+ let outbound_payments_event_listener = Arc::clone(&outbound_payments);
+ let persister_event_listener = Arc::clone(&persister);
let network = args.network;
- let bitcoind_rpc = bitcoind_client.clone();
- let network_graph_events = network_graph.clone();
- let handle = tokio::runtime::Handle::current();
let event_handler = move |event: Event| {
- handle.block_on(handle_ldk_events(
- &channel_manager_event_listener,
- &bitcoind_rpc,
- &network_graph_events,
- &keys_manager_listener,
- &inbound_pmts_for_events,
- &outbound_pmts_for_events,
- network,
- &event,
- ));
+ let channel_manager_event_listener = Arc::clone(&channel_manager_event_listener);
+ let bitcoind_client_event_listener = Arc::clone(&bitcoind_client_event_listener);
+ let network_graph_event_listener = Arc::clone(&network_graph_event_listener);
+ let keys_manager_event_listener = Arc::clone(&keys_manager_event_listener);
+ let inbound_payments_event_listener = Arc::clone(&inbound_payments_event_listener);
+ let outbound_payments_event_listener = Arc::clone(&outbound_payments_event_listener);
+ let persister_event_listener = Arc::clone(&persister_event_listener);
+ async move {
+ handle_ldk_events(
+ &channel_manager_event_listener,
+ &bitcoind_client_event_listener,
+ &network_graph_event_listener,
+ &keys_manager_event_listener,
+ &inbound_payments_event_listener,
+ &outbound_payments_event_listener,
+ &persister_event_listener,
+ network,
+ event,
+ )
+ .await;
+ }
};
// Step 19: Persist ChannelManager and NetworkGraph
let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone()));
// Step 20: Background Processing
- let background_processor = BackgroundProcessor::start(
- persister,
+ let (bp_exit, bp_exit_check) = tokio::sync::watch::channel(());
+ let background_processor = tokio::spawn(process_events_async(
+ Arc::clone(&persister),
event_handler,
chain_monitor.clone(),
channel_manager.clone(),
peer_manager.clone(),
logger.clone(),
Some(scorer.clone()),
- );
+ move |t| {
+ let mut bp_exit_fut_check = bp_exit_check.clone();
+ Box::pin(async move {
+ tokio::select! {
+ _ = tokio::time::sleep(t) => false,
+ _ = bp_exit_fut_check.changed() => true,
+ }
+ })
+ },
+ false,
+ ));
// Regularly reconnect to channel peers.
let connect_cm = Arc::clone(&channel_manager);
let stop_connect = Arc::clone(&stop_listen_connect);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(1));
+ interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
loop {
interval.tick().await;
match disk::read_channel_peer_data(Path::new(&peer_data_path)) {
});
// Regularly broadcast our node_announcement. This is only required (or possible) if we have
- // some public channels, and is only useful if we have public listen address(es) to announce.
- // In a production environment, this should occur only after the announcement of new channels
- // to avoid churn in the global network graph.
+ // some public channels.
let peer_man = Arc::clone(&peer_manager);
+ let chan_man = Arc::clone(&channel_manager);
let network = args.network;
- if !args.ldk_announced_listen_addr.is_empty() {
- tokio::spawn(async move {
- let mut interval = tokio::time::interval(Duration::from_secs(60));
- loop {
- interval.tick().await;
+ tokio::spawn(async move {
+ // First wait a minute until we have some peers and maybe have opened a channel.
+ tokio::time::sleep(Duration::from_secs(60)).await;
+ // Then, update our announcement once an hour to keep it fresh but avoid unnecessary churn
+ // in the global gossip network.
+ let mut interval = tokio::time::interval(Duration::from_secs(3600));
+ loop {
+ interval.tick().await;
+ // Don't bother trying to announce if we don't have any public channls, though our
+ // peers should drop such an announcement anyway. Note that announcement may not
+ // propagate until we have a channel with 6+ confirmations.
+ if chan_man.list_channels().iter().any(|chan| chan.is_public) {
peer_man.broadcast_node_announcement(
[0; 3],
args.ldk_announced_node_name,
args.ldk_announced_listen_addr.clone(),
);
}
- });
- }
+ }
+ });
+
+ tokio::spawn(sweep::periodic_sweep(
+ ldk_data_dir.clone(),
+ Arc::clone(&keys_manager),
+ Arc::clone(&logger),
+ Arc::clone(&persister),
+ Arc::clone(&bitcoind_client),
+ ));
// Start the CLI.
cli::poll_for_user_input(
Arc::clone(&onion_messenger),
inbound_payments,
outbound_payments,
- ldk_data_dir.clone(),
+ ldk_data_dir,
network,
Arc::clone(&logger),
)
peer_manager.disconnect_all_peers();
// Stop the background processor.
- background_processor.stop().unwrap();
+ bp_exit.send(()).unwrap();
+ background_processor.await.unwrap().unwrap();
}
#[tokio::main]