Skip to content

Commit fd23710

Browse files
authored
Merge pull request #2825 from tnull/2024-01-upstream-output-sweeper
Add `OutputSweeper` utility persisting and sweeping spendable outputs
2 parents 56a87cc + be8c0d0 commit fd23710

File tree

12 files changed

+1146
-70
lines changed

12 files changed

+1146
-70
lines changed

fuzz/src/chanmon_consistency.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ impl FeeEstimator for FuzzEstimator {
8484
// Background feerate which is <= the minimum Normal feerate.
8585
match conf_target {
8686
ConfirmationTarget::OnChainSweep => MAX_FEE,
87-
ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee => 253,
87+
ConfirmationTarget::ChannelCloseMinimum|ConfirmationTarget::AnchorChannelFee|ConfirmationTarget::MinAllowedAnchorChannelRemoteFee|ConfirmationTarget::MinAllowedNonAnchorChannelRemoteFee|ConfirmationTarget::OutputSpendingFee => 253,
8888
ConfirmationTarget::NonAnchorChannelFee => cmp::min(self.ret_val.load(atomic::Ordering::Acquire), MAX_FEE),
8989
}
9090
}

lightning-background-processor/src/lib.rs

+136-4
Original file line numberDiff line numberDiff line change
@@ -919,14 +919,16 @@ impl Drop for BackgroundProcessor {
919919

920920
#[cfg(all(feature = "std", test))]
921921
mod tests {
922+
use bitcoin::{ScriptBuf, Txid};
922923
use bitcoin::blockdata::constants::{genesis_block, ChainHash};
923924
use bitcoin::blockdata::locktime::absolute::LockTime;
924925
use bitcoin::blockdata::transaction::{Transaction, TxOut};
926+
use bitcoin::hashes::Hash;
925927
use bitcoin::network::constants::Network;
926928
use bitcoin::secp256k1::{SecretKey, PublicKey, Secp256k1};
927-
use lightning::chain::{BestBlock, Confirm, chainmonitor};
929+
use lightning::chain::{BestBlock, Confirm, chainmonitor, Filter};
928930
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
929-
use lightning::sign::{InMemorySigner, KeysManager};
931+
use lightning::sign::{InMemorySigner, KeysManager, ChangeDestinationSource};
930932
use lightning::chain::transaction::OutPoint;
931933
use lightning::events::{Event, PathFailure, MessageSendEventsProvider, MessageSendEvent};
932934
use lightning::{get_event_msg, get_event};
@@ -947,6 +949,7 @@ mod tests {
947949
CHANNEL_MANAGER_PERSISTENCE_PRIMARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_SECONDARY_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY,
948950
NETWORK_GRAPH_PERSISTENCE_PRIMARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_SECONDARY_NAMESPACE, NETWORK_GRAPH_PERSISTENCE_KEY,
949951
SCORER_PERSISTENCE_PRIMARY_NAMESPACE, SCORER_PERSISTENCE_SECONDARY_NAMESPACE, SCORER_PERSISTENCE_KEY};
952+
use lightning::util::sweep::{OutputSweeper, OutputSpendStatus};
950953
use lightning_persister::fs_store::FilesystemStore;
951954
use std::collections::VecDeque;
952955
use std::{fs, env};
@@ -1009,6 +1012,9 @@ mod tests {
10091012
logger: Arc<test_utils::TestLogger>,
10101013
best_block: BestBlock,
10111014
scorer: Arc<LockingWrapper<TestScorer>>,
1015+
sweeper: Arc<OutputSweeper<Arc<test_utils::TestBroadcaster>, Arc<TestWallet>,
1016+
Arc<test_utils::TestFeeEstimator>, Arc<dyn Filter + Sync + Send>, Arc<FilesystemStore>,
1017+
Arc<test_utils::TestLogger>, Arc<KeysManager>>>,
10121018
}
10131019

10141020
impl Node {
@@ -1247,6 +1253,14 @@ mod tests {
12471253
}
12481254
}
12491255

1256+
struct TestWallet {}
1257+
1258+
impl ChangeDestinationSource for TestWallet {
1259+
fn get_change_destination_script(&self) -> Result<ScriptBuf, ()> {
1260+
Ok(ScriptBuf::new())
1261+
}
1262+
}
1263+
12501264
fn get_full_filepath(filepath: String, filename: String) -> String {
12511265
let mut path = PathBuf::from(filepath);
12521266
path.push(filename);
@@ -1271,10 +1285,15 @@ mod tests {
12711285
let router = Arc::new(DefaultRouter::new(network_graph.clone(), logger.clone(), Arc::clone(&keys_manager), scorer.clone(), Default::default()));
12721286
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin));
12731287
let kv_store = Arc::new(FilesystemStore::new(format!("{}_persister_{}", &persist_dir, i).into()));
1288+
let now = Duration::from_secs(genesis_block.header.time as u64);
1289+
let keys_manager = Arc::new(KeysManager::new(&seed, now.as_secs(), now.subsec_nanos()));
12741290
let chain_monitor = Arc::new(chainmonitor::ChainMonitor::new(Some(chain_source.clone()), tx_broadcaster.clone(), logger.clone(), fee_estimator.clone(), kv_store.clone()));
12751291
let best_block = BestBlock::from_network(network);
12761292
let params = ChainParameters { network, best_block };
12771293
let manager = Arc::new(ChannelManager::new(fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), logger.clone(), keys_manager.clone(), keys_manager.clone(), keys_manager.clone(), UserConfig::default(), params, genesis_block.header.time));
1294+
let wallet = Arc::new(TestWallet {});
1295+
let sweeper = Arc::new(OutputSweeper::new(best_block, Arc::clone(&tx_broadcaster), Arc::clone(&fee_estimator),
1296+
None::<Arc<dyn Filter + Sync + Send>>, Arc::clone(&keys_manager), wallet, Arc::clone(&kv_store), Arc::clone(&logger)));
12781297
let p2p_gossip_sync = Arc::new(P2PGossipSync::new(network_graph.clone(), Some(chain_source.clone()), logger.clone()));
12791298
let rapid_gossip_sync = Arc::new(RapidGossipSync::new(network_graph.clone(), logger.clone()));
12801299
let msg_handler = MessageHandler {
@@ -1283,7 +1302,7 @@ mod tests {
12831302
onion_message_handler: IgnoringMessageHandler{}, custom_message_handler: IgnoringMessageHandler{}
12841303
};
12851304
let peer_manager = Arc::new(PeerManager::new(msg_handler, 0, &seed, logger.clone(), keys_manager.clone()));
1286-
let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, kv_store, tx_broadcaster, network_graph, logger, best_block, scorer };
1305+
let node = Node { node: manager, p2p_gossip_sync, rapid_gossip_sync, peer_manager, chain_monitor, kv_store, tx_broadcaster, network_graph, logger, best_block, scorer, sweeper };
12871306
nodes.push(node);
12881307
}
12891308

@@ -1352,15 +1371,40 @@ mod tests {
13521371
1 => {
13531372
node.node.transactions_confirmed(&header, &txdata, height);
13541373
node.chain_monitor.transactions_confirmed(&header, &txdata, height);
1374+
node.sweeper.transactions_confirmed(&header, &txdata, height);
13551375
},
13561376
x if x == depth => {
1377+
// We need the TestBroadcaster to know about the new height so that it doesn't think
1378+
// we're violating the time lock requirements of transactions broadcasted at that
1379+
// point.
1380+
node.tx_broadcaster.blocks.lock().unwrap().push((genesis_block(Network::Bitcoin), height));
13571381
node.node.best_block_updated(&header, height);
13581382
node.chain_monitor.best_block_updated(&header, height);
1383+
node.sweeper.best_block_updated(&header, height);
13591384
},
13601385
_ => {},
13611386
}
13621387
}
13631388
}
1389+
1390+
fn advance_chain(node: &mut Node, num_blocks: u32) {
1391+
for i in 1..=num_blocks {
1392+
let prev_blockhash = node.best_block.block_hash;
1393+
let height = node.best_block.height + 1;
1394+
let header = create_dummy_header(prev_blockhash, height);
1395+
node.best_block = BestBlock::new(header.block_hash(), height);
1396+
if i == num_blocks {
1397+
// We need the TestBroadcaster to know about the new height so that it doesn't think
1398+
// we're violating the time lock requirements of transactions broadcasted at that
1399+
// point.
1400+
node.tx_broadcaster.blocks.lock().unwrap().push((genesis_block(Network::Bitcoin), height));
1401+
node.node.best_block_updated(&header, height);
1402+
node.chain_monitor.best_block_updated(&header, height);
1403+
node.sweeper.best_block_updated(&header, height);
1404+
}
1405+
}
1406+
}
1407+
13641408
fn confirm_transaction(node: &mut Node, tx: &Transaction) {
13651409
confirm_transaction_depth(node, tx, ANTI_REORG_DELAY);
13661410
}
@@ -1592,6 +1636,9 @@ mod tests {
15921636
let _as_channel_update = get_event_msg!(nodes[0], MessageSendEvent::SendChannelUpdate, nodes[1].node.get_our_node_id());
15931637
nodes[1].node.handle_channel_ready(&nodes[0].node.get_our_node_id(), &as_funding);
15941638
let _bs_channel_update = get_event_msg!(nodes[1], MessageSendEvent::SendChannelUpdate, nodes[0].node.get_our_node_id());
1639+
let broadcast_funding = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
1640+
assert_eq!(broadcast_funding.txid(), funding_tx.txid());
1641+
assert!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
15951642

15961643
if !std::thread::panicking() {
15971644
bg_processor.stop().unwrap();
@@ -1617,10 +1664,95 @@ mod tests {
16171664
.recv_timeout(Duration::from_secs(EVENT_DEADLINE))
16181665
.expect("Events not handled within deadline");
16191666
match event {
1620-
Event::SpendableOutputs { .. } => {},
1667+
Event::SpendableOutputs { outputs, channel_id } => {
1668+
nodes[0].sweeper.track_spendable_outputs(outputs, channel_id, false, Some(153));
1669+
},
16211670
_ => panic!("Unexpected event: {:?}", event),
16221671
}
16231672

1673+
// Check we don't generate an initial sweeping tx until we reach the required height.
1674+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1675+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1676+
if let Some(sweep_tx_0) = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop() {
1677+
assert!(!tracked_output.is_spent_in(&sweep_tx_0));
1678+
match tracked_output.status {
1679+
OutputSpendStatus::PendingInitialBroadcast { delayed_until_height } => {
1680+
assert_eq!(delayed_until_height, Some(153));
1681+
}
1682+
_ => panic!("Unexpected status"),
1683+
}
1684+
}
1685+
1686+
advance_chain(&mut nodes[0], 3);
1687+
1688+
// Check we generate an initial sweeping tx.
1689+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1690+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1691+
let sweep_tx_0 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
1692+
match tracked_output.status {
1693+
OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => {
1694+
assert_eq!(sweep_tx_0.txid(), latest_spending_tx.txid());
1695+
}
1696+
_ => panic!("Unexpected status"),
1697+
}
1698+
1699+
// Check we regenerate and rebroadcast the sweeping tx each block.
1700+
advance_chain(&mut nodes[0], 1);
1701+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1702+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1703+
let sweep_tx_1 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
1704+
match tracked_output.status {
1705+
OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => {
1706+
assert_eq!(sweep_tx_1.txid(), latest_spending_tx.txid());
1707+
}
1708+
_ => panic!("Unexpected status"),
1709+
}
1710+
assert_ne!(sweep_tx_0, sweep_tx_1);
1711+
1712+
advance_chain(&mut nodes[0], 1);
1713+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1714+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1715+
let sweep_tx_2 = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().pop().unwrap();
1716+
match tracked_output.status {
1717+
OutputSpendStatus::PendingFirstConfirmation { latest_spending_tx, .. } => {
1718+
assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid());
1719+
}
1720+
_ => panic!("Unexpected status"),
1721+
}
1722+
assert_ne!(sweep_tx_0, sweep_tx_2);
1723+
assert_ne!(sweep_tx_1, sweep_tx_2);
1724+
1725+
// Check we still track the spendable outputs up to ANTI_REORG_DELAY confirmations.
1726+
confirm_transaction_depth(&mut nodes[0], &sweep_tx_2, 5);
1727+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1728+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1729+
match tracked_output.status {
1730+
OutputSpendStatus::PendingThresholdConfirmations { latest_spending_tx, .. } => {
1731+
assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid());
1732+
}
1733+
_ => panic!("Unexpected status"),
1734+
}
1735+
1736+
// Check we still see the transaction as confirmed if we unconfirm any untracked
1737+
// transaction. (We previously had a bug that would mark tracked transactions as
1738+
// unconfirmed if any transaction at an unknown block height would be unconfirmed.)
1739+
let unconf_txid = Txid::from_slice(&[0; 32]).unwrap();
1740+
nodes[0].sweeper.transaction_unconfirmed(&unconf_txid);
1741+
1742+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 1);
1743+
let tracked_output = nodes[0].sweeper.tracked_spendable_outputs().first().unwrap().clone();
1744+
match tracked_output.status {
1745+
OutputSpendStatus::PendingThresholdConfirmations { latest_spending_tx, .. } => {
1746+
assert_eq!(sweep_tx_2.txid(), latest_spending_tx.txid());
1747+
}
1748+
_ => panic!("Unexpected status"),
1749+
}
1750+
1751+
// Check we stop tracking the spendable outputs when one of the txs reaches
1752+
// ANTI_REORG_DELAY confirmations.
1753+
confirm_transaction_depth(&mut nodes[0], &sweep_tx_0, ANTI_REORG_DELAY);
1754+
assert_eq!(nodes[0].sweeper.tracked_spendable_outputs().len(), 0);
1755+
16241756
if !std::thread::panicking() {
16251757
bg_processor.stop().unwrap();
16261758
}

lightning/src/chain/chaininterface.rs

+11
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ pub enum ConfirmationTarget {
124124
///
125125
/// [`ChannelManager::close_channel_with_feerate_and_script`]: crate::ln::channelmanager::ChannelManager::close_channel_with_feerate_and_script
126126
ChannelCloseMinimum,
127+
/// The feerate [`OutputSweeper`] will use on transactions spending
128+
/// [`SpendableOutputDescriptor`]s after a channel closure.
129+
///
130+
/// Generally spending these outputs is safe as long as they eventually confirm, so a value
131+
/// (slightly above) the mempool minimum should suffice. However, as this value will influence
132+
/// how long funds will be unavailable after channel closure, [`FeeEstimator`] implementors
133+
/// might want to choose a higher feerate to regain control over funds faster.
134+
///
135+
/// [`OutputSweeper`]: crate::util::sweep::OutputSweeper
136+
/// [`SpendableOutputDescriptor`]: crate::sign::SpendableOutputDescriptor
137+
OutputSpendingFee,
127138
}
128139

129140
/// A trait which should be implemented to provide feerate information on a number of time

lightning/src/chain/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::chain::channelmonitor::{ChannelMonitor, ChannelMonitorUpdate, Monitor
2020
use crate::ln::ChannelId;
2121
use crate::sign::ecdsa::WriteableEcdsaChannelSigner;
2222
use crate::chain::transaction::{OutPoint, TransactionData};
23+
use crate::impl_writeable_tlv_based;
2324

2425
#[allow(unused_imports)]
2526
use crate::prelude::*;
@@ -56,6 +57,11 @@ impl BestBlock {
5657
}
5758
}
5859

60+
impl_writeable_tlv_based!(BestBlock, {
61+
(0, block_hash, required),
62+
(2, height, required),
63+
});
64+
5965

6066
/// The `Listen` trait is used to notify when blocks have been connected or disconnected from the
6167
/// chain.

lightning/src/events/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -886,9 +886,15 @@ pub enum Event {
886886
},
887887
/// Used to indicate that an output which you should know how to spend was confirmed on chain
888888
/// and is now spendable.
889-
/// Such an output will *not* ever be spent by rust-lightning, and are not at risk of your
889+
///
890+
/// Such an output will *never* be spent directly by LDK, and are not at risk of your
890891
/// counterparty spending them due to some kind of timeout. Thus, you need to store them
891892
/// somewhere and spend them when you create on-chain transactions.
893+
///
894+
/// You may hand them to the [`OutputSweeper`] utility which will store and (re-)generate spending
895+
/// transactions for you.
896+
///
897+
/// [`OutputSweeper`]: crate::util::sweep::OutputSweeper
892898
SpendableOutputs {
893899
/// The outputs which you should store as spendable by you.
894900
outputs: Vec<SpendableOutputDescriptor>,

lightning/src/ln/functional_tests.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::chain::chaininterface::LowerBoundedFeeEstimator;
1717
use crate::chain::channelmonitor;
1818
use crate::chain::channelmonitor::{CLOSED_CHANNEL_UPDATE_ID, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
1919
use crate::chain::transaction::OutPoint;
20-
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, SignerProvider};
20+
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider};
2121
use crate::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination, PaymentFailureReason};
2222
use crate::ln::{ChannelId, PaymentPreimage, PaymentSecret, PaymentHash};
2323
use crate::ln::channel::{commitment_tx_base_weight, COMMITMENT_TX_WEIGHT_PER_HTLC, CONCURRENT_INBOUND_HTLC_FEE_BUFFER, FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MIN_AFFORDABLE_HTLC_COUNT, get_holder_selected_channel_reserve_satoshis, OutboundV1Channel, InboundV1Channel, COINBASE_MATURITY, ChannelPhase};
@@ -9951,9 +9951,9 @@ fn do_test_max_dust_htlc_exposure(dust_outbound_balance: bool, exposure_breach_e
99519951
let dust_outbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_outbound_htlc_on_holder_tx_msat;
99529952

99539953
// Substract 3 sats for multiplier and 2 sats for fixed limit to make sure we are 50% below the dust limit.
9954-
// This is to make sure we fully use the dust limit. If we don't, we could end up with `dust_ibd_htlc_on_holder_tx` being 1
9954+
// This is to make sure we fully use the dust limit. If we don't, we could end up with `dust_ibd_htlc_on_holder_tx` being 1
99559955
// while `max_dust_htlc_exposure_msat` is not equal to `dust_outbound_htlc_on_holder_tx_msat`.
9956-
let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - if multiplier_dust_limit { 3 } else { 2 }) * 1000;
9956+
let dust_inbound_htlc_on_holder_tx_msat: u64 = (dust_buffer_feerate * htlc_success_tx_weight(&channel_type_features) / 1000 + open_channel.common_fields.dust_limit_satoshis - if multiplier_dust_limit { 3 } else { 2 }) * 1000;
99579957
let dust_inbound_htlc_on_holder_tx: u64 = max_dust_htlc_exposure_msat / dust_inbound_htlc_on_holder_tx_msat;
99589958

99599959
let dust_htlc_on_counterparty_tx: u64 = 4;

lightning/src/ln/monitor_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
//! Further functional tests which test blockchain reorganizations.
1111
12-
use crate::sign::{ecdsa::EcdsaChannelSigner, SpendableOutputDescriptor};
12+
use crate::sign::{ecdsa::EcdsaChannelSigner, OutputSpender, SpendableOutputDescriptor};
1313
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS, Balance};
1414
use crate::chain::transaction::OutPoint;
1515
use crate::chain::chaininterface::{LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};

lightning/src/ln/reorg_tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::chain::transaction::OutPoint;
1515
use crate::chain::Confirm;
1616
use crate::events::{Event, MessageSendEventsProvider, ClosureReason, HTLCDestination, MessageSendEvent};
1717
use crate::ln::msgs::{ChannelMessageHandler, Init};
18+
use crate::sign::OutputSpender;
1819
use crate::util::test_utils;
1920
use crate::util::ser::Writeable;
2021
use crate::util::string::UntrustedString;

0 commit comments

Comments
 (0)