Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5172d16
DROP ME: Only adopt a funding payment's own transactions from wallet …
jkczyz Aug 18, 2026
696c7c4
DROP ME: Retry funding-broadcast classification instead of dropping it
jkczyz Aug 18, 2026
0ba0e63
DROP ME: f - Retry funding-broadcast classification instead of droppi…
jkczyz Sep 25, 2026
706ee38
DROP ME: Fail funding payments lost to a confirmed conflict
jkczyz Sep 2, 2026
061f4a9
DROP ME: Record splice funding payments when signing
jkczyz Sep 5, 2026
c63c1d6
DROP ME: Resolve funding payments when LDK discards a splice round
jkczyz Sep 7, 2026
9371f04
DROP ME: f - Resolve funding payments when LDK discards a splice round
jkczyz Sep 25, 2026
2fe57c3
DROP ME: f - Resolve funding payments when LDK discards a splice round
jkczyz Sep 25, 2026
81a62f0
DROP ME: Resolve a transaction to the record that owns it before one …
jkczyz Sep 22, 2026
1fded27
DROP ME: Take the funding re-broadcast trace from the write's own read
jkczyz Sep 22, 2026
8d7b7f4
DROP ME: Have funding-record writers take a guard only the funding lo…
jkczyz Sep 28, 2026
233d110
DROP ME: Put the wallet's payment stores behind an API that locks the…
jkczyz Sep 28, 2026
dc6b42a
DROP ME: Assign random PaymentIds to funding records
jkczyz Aug 18, 2026
d2e35a4
DROP ME: f - Assign random PaymentIds to funding records
jkczyz Sep 17, 2026
7547e8c
DROP ME: f - Assign random PaymentIds to funding records
jkczyz Sep 17, 2026
8a5db4a
DROP ME: Model pending payments as an enum for pre-broadcast splices
jkczyz Aug 3, 2026
e83a13e
DROP ME: f - Model pending payments as an enum for pre-broadcast splices
jkczyz Sep 21, 2026
c13a390
DROP ME: Adopt the splice-time PaymentId when recording a splice
jkczyz Aug 3, 2026
d1d868c
DROP ME: Merge sync-created duplicates when recording a funding round
jkczyz Aug 18, 2026
5f57014
DROP ME: Persist splice intents until the splice locks
jkczyz Sep 2, 2026
f02dc55
DROP ME: f - Persist splice intents until the splice locks
jkczyz Sep 11, 2026
5c745f2
DROP ME: f - Persist splice intents until the splice locks
jkczyz Sep 21, 2026
f97e5c5
DROP ME: f - Persist splice intents until the splice locks
jkczyz Sep 22, 2026
10a742b
DROP ME: Abort a splice when funding signing fails
jkczyz Sep 5, 2026
2f1a321
DROP ME: Add reason and splice parameters to splice failure events
jkczyz Sep 2, 2026
0731f25
DROP ME: Unlock lost splice inputs at startup
jkczyz Sep 7, 2026
54d59e5
DROP ME: f - Unlock lost splice inputs at startup
jkczyz Sep 11, 2026
52e83f2
DROP ME: f - Unlock lost splice inputs at startup
jkczyz Sep 21, 2026
298f84d
DROP ME: Test splice failure surfacing and recovery
jkczyz Sep 2, 2026
ad3d629
DROP ME: f - Test splice failure surfacing and recovery
jkczyz Sep 21, 2026
641475f
Add a channel transaction provenance store
tnull Sep 29, 2026
fb501d1
Record channel transaction provenance from events
tnull Sep 29, 2026
71f0edc
Classify transactions from recorded provenance
tnull Sep 29, 2026
5a592d2
Classify on-chain payments during wallet sync
tnull Sep 29, 2026
f73da84
Let wallet sync create funding payment records
tnull Sep 29, 2026
9fc719b
f Let wallet sync create funding payment records
tnull Sep 29, 2026
a9117e6
Delete broadcast-time payment classification
tnull Sep 29, 2026
5dec914
Gate on-chain RBF positively on a wallet-only tx
tnull Sep 29, 2026
f7c3e23
Patch LDK to the tx-only broadcasting branch
tnull Sep 29, 2026
1695e1c
Stop using LDK's broadcast classification types
tnull Sep 29, 2026
1c4d9c4
Drop recorded channel facts nothing needs anymore
tnull Sep 29, 2026
c43a71f
Bound what the channel facts store may hold
tnull Sep 29, 2026
ca2db6b
Take back a splice intent its payment left behind
tnull Sep 29, 2026
7815b75
Skip a replacement whose payment record is gone
tnull Sep 29, 2026
364621c
Refuse to sign a round this node cannot measure
tnull Sep 29, 2026
baebb5b
f Refuse to sign a round this node cannot measure
tnull Sep 29, 2026
b518e9b
Let the funding output alone name a funding
tnull Sep 29, 2026
ed85bd1
Drop test scaffolding nothing exercises anymore
tnull Sep 29, 2026
8fe08d6
Fix two references that point at the wrong thing
tnull Sep 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,27 @@ harness = false
#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity" }
#lightning-macros = { path = "../rust-lightning/lightning-macros" }
#lightning-dns-resolver = { path = "../rust-lightning/lightning-dns-resolver" }

# TEMPORARY - this section must not reach a proposed branch.
#
# Overrides the revision pinned above with a local checkout of the rust-lightning branch
# `2026-09-restore-tx-only-broadcasting-0.3`, which restores
# `BroadcasterInterface::broadcast_transactions` to taking `&[&Transaction]`. The paths below
# exist only on the machine this was developed on, so the tree builds nowhere else while they
# are in place.
#
# Before this work is proposed, delete this whole section and move the `rev` of every crate
# above to an accessible, reviewed revision carrying the same revert.
[patch."https://github.com/lightningdevkit/rust-lightning"]
lightning = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning" }
lightning-types = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-types" }
lightning-invoice = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-invoice" }
lightning-net-tokio = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-net-tokio" }
lightning-persister = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-persister" }
lightning-background-processor = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-background-processor" }
lightning-rapid-gossip-sync = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-rapid-gossip-sync" }
lightning-block-sync = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-block-sync" }
lightning-transaction-sync = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-transaction-sync" }
lightning-liquidity = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-liquidity" }
lightning-macros = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-macros" }
lightning-dns-resolver = { path = "/home/tnull/worktrees/rust-lightning/2026-09-restore-tx-only-broadcasting-0.3/lightning-dns-resolver" }
56 changes: 51 additions & 5 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ use lightning_dns_resolver::OMDomainResolver;
use vss_client::headers::VssHeaderProvider;

use crate::chain::ChainSource;
use crate::channel::SpliceTracker;
#[cfg(feature = "chain-bitcoind")]
use crate::config::BitcoindRestClientConfig;
use crate::config::{
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole, Config,
ElectrumSyncConfig, EsploraSyncConfig, HRNResolverConfig, TorConfig,
CHANNEL_TX_FACTS_CACHE_CAPACITY, CHANNEL_TX_FACTS_CACHE_WARMUP_COUNT,
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
DEFAULT_MAX_PROBE_AMOUNT_MSAT, DEFAULT_MIN_PROBE_AMOUNT_MSAT, PAYMENT_CACHE_CAPACITY,
PAYMENT_CACHE_WARMUP_COUNT,
Expand All @@ -83,6 +85,8 @@ use crate::io::utils::{
use crate::io::vss_store::VssStoreBuilder;
use crate::io::{
self, CHANNEL_FORWARDING_STATS_PERSISTENCE_SECONDARY_NAMESPACE,
CHANNEL_TX_FACTS_PERSISTENCE_PRIMARY_NAMESPACE,
CHANNEL_TX_FACTS_PERSISTENCE_SECONDARY_NAMESPACE,
FORWARDED_PAYMENT_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
Expand All @@ -104,11 +108,12 @@ use crate::probing::{
use crate::runtime::{Runtime, RuntimeSpawner};
use crate::tx_broadcaster::TransactionBroadcaster;
use crate::types::{
AsyncPersister, ChainMonitor, ChannelManager, DynStore, DynStoreRef, DynStoreWrapper,
GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore, PeerManager,
PendingPaymentStore,
AsyncPersister, ChainMonitor, ChannelManager, ChannelTxFactsStore, DynStore, DynStoreRef,
DynStoreWrapper, GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
PeerManager, PendingPaymentStore,
};
use crate::wallet::persist::{read_address_pool, KVStoreWalletPersister};
use crate::wallet::provenance::NodeChannelLiveness;
use crate::wallet::Wallet;
use crate::{Node, NodeMetrics, PersistedNodeMetrics};

Expand Down Expand Up @@ -1552,6 +1557,7 @@ fn build_with_store_internal(
channel_forwarding_stats_res,
node_metris_res,
pending_payment_store_res,
channel_tx_facts_store_res,
address_pool_res,
) = runtime.block_on(async move {
tokio::join!(
Expand All @@ -1575,6 +1581,13 @@ fn build_with_store_internal(
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
Arc::clone(&logger_ref),
),
read_n_objects(
&*kv_store_ref,
CHANNEL_TX_FACTS_PERSISTENCE_PRIMARY_NAMESPACE,
CHANNEL_TX_FACTS_PERSISTENCE_SECONDARY_NAMESPACE,
CHANNEL_TX_FACTS_CACHE_WARMUP_COUNT,
Arc::clone(&logger_ref),
),
read_address_pool(&*kv_store_ref, &*logger_ref),
)
});
Expand Down Expand Up @@ -1906,6 +1919,24 @@ fn build_with_store_internal(
},
};

let channel_tx_facts_store = match channel_tx_facts_store_res {
Ok(channel_tx_facts) => Arc::new(ChannelTxFactsStore::new(
// The read hands us the newest records first, while the cache treats the objects it
// is seeded with as increasingly recently used. Reverse them, so that the newest
// record is the last one to be evicted rather than the first.
channel_tx_facts.into_iter().rev().collect(),
KeepLeastRecentlyUsed::new(CHANNEL_TX_FACTS_CACHE_CAPACITY),
CHANNEL_TX_FACTS_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
CHANNEL_TX_FACTS_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
Arc::clone(&kv_store),
Arc::clone(&logger),
)),
Err(e) => {
log_error!(logger, "Failed to read channel transaction facts from store: {}", e);
return Err(BuildError::ReadFailed);
},
};

let persisted_pool_indices = match address_pool_res {
Ok(indices) => indices,
Err(e) => {
Expand All @@ -1926,6 +1957,7 @@ fn build_with_store_internal(
Arc::clone(&config),
Arc::clone(&logger),
Arc::clone(&pending_payment_store),
Arc::clone(&channel_tx_facts_store),
));

// Fill the address pool up front so LDK's sync `SignerProvider` callbacks can hand out
Expand All @@ -1935,8 +1967,6 @@ fn build_with_store_internal(
BuildError::WalletSetupFailed
})?;

tx_broadcaster.set_wallet(Arc::downgrade(&wallet));

// Initialize the KeysManager
let cur_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).map_err(|e| {
log_error!(logger, "Failed to get current time: {}", e);
Expand Down Expand Up @@ -2417,6 +2447,14 @@ fn build_with_store_internal(
},
};

// The wallet drops the facts it recorded for a channel once nothing holds that channel
// anymore, which it can only ask now that the node's channel state exists.
wallet.set_channel_liveness(Arc::new(NodeChannelLiveness::new(
&channel_manager,
&chain_monitor,
&output_sweeper,
)));

let event_queue = match event_queue_res {
Ok(event_queue) => Arc::new(event_queue),
Err(e) => {
Expand Down Expand Up @@ -2500,6 +2538,13 @@ fn build_with_store_internal(
})
});

let splice_tracker = Arc::new(SpliceTracker::new(
Arc::clone(&channel_manager),
Arc::clone(&wallet),
Arc::clone(&pending_payment_store),
Arc::clone(&logger),
));

#[cfg(cycle_tests)]
let mut _leak_checker = crate::LeakChecker(Vec::new());
#[cfg(cycle_tests)]
Expand Down Expand Up @@ -2551,6 +2596,7 @@ fn build_with_store_internal(
payment_store,
forwarding_store,
forwarded_payment_aggregation_retention_secs,
splice_tracker,
lnurl_auth,
is_running,
node_metrics,
Expand Down
67 changes: 30 additions & 37 deletions src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ use crate::config::ElectrumSyncConfig;
use crate::config::EsploraSyncConfig;
use crate::config::{BackgroundSyncConfig, Config, WALLET_SYNC_INTERVAL_MINIMUM_SECS};
use crate::fee_estimator::OnchainFeeEstimator;
use crate::logger::{log_debug, log_error, log_info, log_trace, LdkLogger, Logger};
use crate::logger::{log_debug, log_info, log_trace, LdkLogger, Logger};
use crate::runtime::Runtime;
use crate::tx_broadcaster::BroadcastPackage;
use crate::types::{Broadcaster, ChainMonitor, ChannelManager, DynStore, Sweeper, Wallet};
use crate::{Error, PersistedNodeMetrics};

Expand Down Expand Up @@ -562,52 +563,44 @@ impl ChainSource {
}
}

/// Hands the package to the configured chain source, parents before their child so a CPFP
/// package a chain source submits one transaction at a time is still accepted.
async fn broadcast(&self, package: BroadcastPackage) {
let package = package.into_sorted_transactions();
match &self.kind {
#[cfg(feature = "chain-esplora")]
ChainSourceKind::Esplora(esplora_chain_source) => {
esplora_chain_source.process_transaction_broadcast(package).await
},
#[cfg(feature = "chain-electrum")]
ChainSourceKind::Electrum(electrum_chain_source) => {
electrum_chain_source.process_transaction_broadcast(package).await
},
#[cfg(feature = "chain-bitcoind")]
ChainSourceKind::Bitcoind(bitcoind_chain_source) => {
bitcoind_chain_source.process_transaction_broadcast(package).await
},
}
}

pub(crate) async fn continuously_process_broadcast_queue(
&self, mut stop_tx_bcast_receiver: tokio::sync::watch::Receiver<()>,
) {
let mut receiver = self.tx_broadcaster.get_broadcast_queue().await;
loop {
let tx_bcast_logger = Arc::clone(&self.logger);
tokio::select! {
let package = tokio::select! {
// A stop request is polled first, so a queue that always has a package ready
// cannot starve it.
biased;
_ = stop_tx_bcast_receiver.changed() => {
log_debug!(
tx_bcast_logger,
self.logger,
"Stopping broadcasting transactions.",
);
return;
}
Some(next_package) = receiver.recv() => {
// Classify funding broadcasts into payment records before sending. If
// classification fails we skip the broadcast, since broadcasting a tx we
// failed to record would leave it on-chain without a payment.
let package = match self.tx_broadcaster.classify_package(next_package).await {
Ok(package) => package,
Err(e) => {
log_error!(
tx_bcast_logger,
"Skipping broadcast: failed to persist payment records: {:?}",
e,
);
continue;
},
};
let package = package.into_sorted_transactions();
match &self.kind {
#[cfg(feature = "chain-esplora")]
ChainSourceKind::Esplora(esplora_chain_source) => {
esplora_chain_source.process_transaction_broadcast(package).await
},
#[cfg(feature = "chain-electrum")]
ChainSourceKind::Electrum(electrum_chain_source) => {
electrum_chain_source.process_transaction_broadcast(package).await
},
#[cfg(feature = "chain-bitcoind")]
ChainSourceKind::Bitcoind(bitcoind_chain_source) => {
bitcoind_chain_source.process_transaction_broadcast(package).await
},
}
}
}
package = self.tx_broadcaster.next_package() => package,
};
self.broadcast(package).await;
}
}
}
Expand Down
Loading
Loading