Skip to content

Commit 2c7e16f

Browse files
committed
fixup! Add persistent closed channel history and list_closed_channels()
Generalize PendingChannelInfo into ChannelRecord::Funded. Rather than shipping two separate per-channel stores, introduce a ChannelRecord enum with a single Funded variant that already carries the fields the splice PR will need (counterparty_node_id, channel_id) in addition to is_outbound/is_announced flags. This lets the splice PR add pending_splice: Option<SpliceIntent> as a new optional TLV field without touching the store infrastructure.
1 parent 00a3e59 commit 2c7e16f

8 files changed

Lines changed: 194 additions & 94 deletions

File tree

src/builder.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ use crate::io::utils::{
6464
};
6565
use crate::io::vss_store::VssStoreBuilder;
6666
use crate::io::{
67-
self, CLOSED_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
67+
self, CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE,
68+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE,
69+
CLOSED_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
6870
CLOSED_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
6971
PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
70-
PENDING_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
71-
PENDING_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
7272
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
7373
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
7474
};
@@ -83,9 +83,9 @@ use crate::peer_store::PeerStore;
8383
use crate::runtime::{Runtime, RuntimeSpawner};
8484
use crate::tx_broadcaster::TransactionBroadcaster;
8585
use crate::types::{
86-
AsyncPersister, ChainMonitor, ChannelManager, ClosedChannelStore, DynStore, DynStoreRef,
87-
DynStoreWrapper, GossipSync, Graph, HRNResolver, KeysManager, MessageRouter, OnionMessenger,
88-
PaymentStore, PeerManager, PendingChannelStore, PendingPaymentStore, SyncAndAsyncKVStore,
86+
AsyncPersister, ChainMonitor, ChannelManager, ChannelRecordStore, ClosedChannelStore, DynStore,
87+
DynStoreRef, DynStoreWrapper, GossipSync, Graph, HRNResolver, KeysManager, MessageRouter,
88+
OnionMessenger, PaymentStore, PeerManager, PendingPaymentStore, SyncAndAsyncKVStore,
8989
};
9090
use crate::wallet::persist::KVStoreWalletPersister;
9191
use crate::wallet::Wallet;
@@ -1297,7 +1297,7 @@ fn build_with_store_internal(
12971297
node_metris_res,
12981298
pending_payment_store_res,
12991299
closed_channel_store_res,
1300-
pending_channel_store_res,
1300+
channel_record_store_res,
13011301
) = runtime.block_on(async move {
13021302
tokio::join!(
13031303
read_all_objects(
@@ -1321,8 +1321,8 @@ fn build_with_store_internal(
13211321
),
13221322
read_all_objects(
13231323
&*kv_store_ref,
1324-
PENDING_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
1325-
PENDING_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
1324+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE,
1325+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE,
13261326
Arc::clone(&logger_ref),
13271327
),
13281328
)
@@ -1541,16 +1541,16 @@ fn build_with_store_internal(
15411541
},
15421542
};
15431543

1544-
let pending_channel_store = match pending_channel_store_res {
1545-
Ok(pending_channels) => Arc::new(PendingChannelStore::new(
1546-
pending_channels,
1547-
PENDING_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
1548-
PENDING_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
1544+
let channel_record_store = match channel_record_store_res {
1545+
Ok(channel_records) => Arc::new(ChannelRecordStore::new(
1546+
channel_records,
1547+
CHANNEL_RECORD_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
1548+
CHANNEL_RECORD_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
15491549
Arc::clone(&kv_store),
15501550
Arc::clone(&logger),
15511551
)),
15521552
Err(e) => {
1553-
log_error!(logger, "Failed to read pending channel data from store: {}", e);
1553+
log_error!(logger, "Failed to read channel record data from store: {}", e);
15541554
return Err(BuildError::ReadFailed);
15551555
},
15561556
};
@@ -2113,7 +2113,7 @@ fn build_with_store_internal(
21132113
peer_store,
21142114
payment_store,
21152115
closed_channel_store,
2116-
pending_channel_store,
2116+
channel_record_store,
21172117
lnurl_auth,
21182118
is_running,
21192119
node_metrics,

src/channel/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
//! Per-channel state tracking.
9+
10+
pub(crate) mod store;

src/channel/store.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use bitcoin::secp256k1::PublicKey;
9+
use lightning::impl_writeable_tlv_based_enum;
10+
use lightning::ln::types::ChannelId;
11+
12+
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
13+
use crate::hex_utils;
14+
use crate::types::UserChannelId;
15+
16+
/// Persistent per-channel state tracked by LDK Node, keyed by [`UserChannelId`].
17+
///
18+
/// Durably stores channel flags at `ChannelPending` time so they remain accessible when the
19+
/// channel closes, even after a restart or a [`ReplayEvent`]. The `Funded` variant is designed
20+
/// to be extended with a `pending_splice` field in a future PR to support splice retry across
21+
/// restarts and peer disconnects.
22+
///
23+
/// [`ReplayEvent`]: lightning::events::ReplayEvent
24+
#[derive(Clone, Debug, PartialEq, Eq)]
25+
pub(crate) enum ChannelRecord {
26+
/// State for a live channel whose funding transaction exists.
27+
Funded {
28+
user_channel_id: UserChannelId,
29+
/// The node ID of the channel counterparty.
30+
counterparty_node_id: PublicKey,
31+
/// The channel's ID at the time the `ChannelPending` event fired.
32+
channel_id: ChannelId,
33+
/// Whether we opened the channel (outbound) or the counterparty did (inbound).
34+
is_outbound: bool,
35+
/// Whether the channel was publicly announced.
36+
is_announced: bool,
37+
},
38+
}
39+
40+
impl_writeable_tlv_based_enum!(ChannelRecord,
41+
(0, Funded) => {
42+
(0, user_channel_id, required),
43+
(2, counterparty_node_id, required),
44+
(4, channel_id, required),
45+
(6, is_outbound, required),
46+
(8, is_announced, required),
47+
},
48+
);
49+
50+
#[derive(Clone, Debug, PartialEq, Eq)]
51+
pub(crate) struct ChannelRecordUpdate {
52+
pub user_channel_id: UserChannelId,
53+
}
54+
55+
impl StorableObjectUpdate<ChannelRecord> for ChannelRecordUpdate {
56+
fn id(&self) -> UserChannelId {
57+
self.user_channel_id
58+
}
59+
}
60+
61+
impl StorableObject for ChannelRecord {
62+
type Id = UserChannelId;
63+
type Update = ChannelRecordUpdate;
64+
65+
fn id(&self) -> UserChannelId {
66+
match self {
67+
ChannelRecord::Funded { user_channel_id, .. } => *user_channel_id,
68+
}
69+
}
70+
71+
fn update(&mut self, _update: Self::Update) -> bool {
72+
// ChannelRecord fields are immutable once written in this version. Returning false
73+
// makes insert_or_update a no-op when the record already exists, ensuring idempotency
74+
// on ChannelPending replay.
75+
false
76+
}
77+
78+
fn to_update(&self) -> Self::Update {
79+
ChannelRecordUpdate { user_channel_id: self.id() }
80+
}
81+
}
82+
83+
impl StorableObjectId for UserChannelId {
84+
fn encode_to_hex_str(&self) -> String {
85+
hex_utils::to_string(&self.0.to_be_bytes())
86+
}
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use lightning::ln::types::ChannelId;
92+
use lightning::util::ser::{Readable, Writeable};
93+
94+
use super::*;
95+
96+
fn make_record(is_outbound: bool, is_announced: bool) -> ChannelRecord {
97+
let user_channel_id = UserChannelId(42);
98+
// A valid compressed public key: prefix 0x02 followed by 32 bytes.
99+
let counterparty_node_id = PublicKey::from_slice(&[2u8; 33]).expect("valid pubkey");
100+
let channel_id = ChannelId([3u8; 32]);
101+
ChannelRecord::Funded {
102+
user_channel_id,
103+
counterparty_node_id,
104+
channel_id,
105+
is_outbound,
106+
is_announced,
107+
}
108+
}
109+
110+
#[test]
111+
fn channel_record_roundtrips() {
112+
for (is_outbound, is_announced) in
113+
[(true, false), (false, true), (true, true), (false, false)]
114+
{
115+
let record = make_record(is_outbound, is_announced);
116+
let encoded = record.encode();
117+
let decoded = ChannelRecord::read(&mut &encoded[..]).expect("decode succeeds");
118+
assert_eq!(record, decoded);
119+
assert_eq!(decoded.id(), UserChannelId(42));
120+
assert!(matches!(
121+
decoded,
122+
ChannelRecord::Funded {
123+
is_outbound: dec_out,
124+
is_announced: dec_ann,
125+
..
126+
} if dec_out == is_outbound && dec_ann == is_announced
127+
));
128+
}
129+
}
130+
}

src/closed_channel.rs

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ use lightning::events::ClosureReason;
1313
use lightning::impl_writeable_tlv_based;
1414
use lightning::ln::types::ChannelId;
1515

16-
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
17-
use crate::hex_utils;
16+
use crate::data_store::{StorableObject, StorableObjectUpdate};
1817
use crate::types::UserChannelId;
1918

2019
/// Details of a closed channel.
@@ -77,48 +76,6 @@ impl_writeable_tlv_based!(ClosedChannelDetails, {
7776
(18, is_announced, required),
7877
});
7978

80-
/// Channel flags persisted at channel-pending time so they remain accessible when the channel
81-
/// closes, even after a restart or when `handle_event` returns [`ReplayEvent`].
82-
///
83-
/// [`ReplayEvent`]: lightning::events::ReplayEvent
84-
#[derive(Clone, Debug)]
85-
pub(crate) struct PendingChannelInfo {
86-
pub user_channel_id: UserChannelId,
87-
pub is_outbound: bool,
88-
pub is_announced: bool,
89-
}
90-
91-
impl_writeable_tlv_based!(PendingChannelInfo, {
92-
(0, user_channel_id, required),
93-
(2, is_outbound, required),
94-
(4, is_announced, required),
95-
});
96-
97-
pub(crate) struct PendingChannelInfoUpdate(pub UserChannelId);
98-
99-
impl StorableObjectUpdate<PendingChannelInfo> for PendingChannelInfoUpdate {
100-
fn id(&self) -> UserChannelId {
101-
self.0
102-
}
103-
}
104-
105-
impl StorableObject for PendingChannelInfo {
106-
type Id = UserChannelId;
107-
type Update = PendingChannelInfoUpdate;
108-
109-
fn id(&self) -> UserChannelId {
110-
self.user_channel_id
111-
}
112-
113-
fn update(&mut self, _update: Self::Update) -> bool {
114-
false
115-
}
116-
117-
fn to_update(&self) -> Self::Update {
118-
PendingChannelInfoUpdate(self.user_channel_id)
119-
}
120-
}
121-
12279
pub(crate) struct ClosedChannelDetailsUpdate(pub UserChannelId);
12380

12481
impl StorableObjectUpdate<ClosedChannelDetails> for ClosedChannelDetailsUpdate {
@@ -144,9 +101,3 @@ impl StorableObject for ClosedChannelDetails {
144101
ClosedChannelDetailsUpdate(self.user_channel_id)
145102
}
146103
}
147-
148-
impl StorableObjectId for UserChannelId {
149-
fn encode_to_hex_str(&self) -> String {
150-
hex_utils::to_string(&self.0.to_be_bytes())
151-
}
152-
}

src/event.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
3434
use lightning_liquidity::lsps2::utils::compute_opening_fee;
3535
use lightning_types::payment::{PaymentHash, PaymentPreimage};
3636

37-
use crate::closed_channel::{ClosedChannelDetails, PendingChannelInfo};
37+
use crate::channel::store::ChannelRecord;
38+
use crate::closed_channel::ClosedChannelDetails;
3839
use crate::config::{may_announce_channel, Config};
3940
use crate::connection::ConnectionManager;
4041
use crate::data_store::DataStoreUpdateResult;
@@ -54,8 +55,8 @@ use crate::payment::store::{
5455
};
5556
use crate::runtime::Runtime;
5657
use crate::types::{
57-
ClosedChannelStore, CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore,
58-
PendingChannelStore, Sweeper, Wallet,
58+
ChannelRecordStore, ClosedChannelStore, CustomTlvRecord, DynStore, KeysManager, OnionMessenger,
59+
PaymentStore, Sweeper, Wallet,
5960
};
6061
use crate::{
6162
hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore,
@@ -551,7 +552,7 @@ where
551552
payment_store: Arc<PaymentStore>,
552553
peer_store: Arc<PeerStore<L>>,
553554
closed_channel_store: Arc<ClosedChannelStore>,
554-
pending_channel_store: Arc<PendingChannelStore>,
555+
channel_record_store: Arc<ChannelRecordStore>,
555556
// Tracks which user_channel_ids correspond to outbound channels. Populated at startup from
556557
// list_channels() and updated on ChannelPending events. Consumed on ChannelClosed events.
557558
outbound_channel_ids: Mutex<HashSet<UserChannelId>>,
@@ -579,7 +580,7 @@ where
579580
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
580581
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
581582
closed_channel_store: Arc<ClosedChannelStore>,
582-
pending_channel_store: Arc<PendingChannelStore>, keys_manager: Arc<KeysManager>,
583+
channel_record_store: Arc<ChannelRecordStore>, keys_manager: Arc<KeysManager>,
583584
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
584585
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
585586
config: Arc<Config>,
@@ -612,7 +613,7 @@ where
612613
payment_store,
613614
peer_store,
614615
closed_channel_store,
615-
pending_channel_store,
616+
channel_record_store,
616617
outbound_channel_ids,
617618
announced_channel_ids,
618619
keys_manager,
@@ -1575,15 +1576,17 @@ where
15751576
.insert(UserChannelId(user_channel_id));
15761577
}
15771578

1578-
let pending_info = PendingChannelInfo {
1579+
let record = ChannelRecord::Funded {
15791580
user_channel_id: UserChannelId(user_channel_id),
1581+
counterparty_node_id,
1582+
channel_id,
15801583
is_outbound: pending_channel.is_outbound,
15811584
is_announced: pending_channel.is_announced,
15821585
};
1583-
if let Err(e) = self.pending_channel_store.insert_or_update(pending_info) {
1586+
if let Err(e) = self.channel_record_store.insert_or_update(record) {
15841587
log_error!(
15851588
self.logger,
1586-
"Failed to persist pending channel info {}: {}",
1589+
"Failed to persist channel record for {}: {}",
15871590
channel_id,
15881591
e
15891592
);
@@ -1683,15 +1686,19 @@ where
16831686
.expect("Lock poisoned")
16841687
.remove(&user_channel_id);
16851688

1686-
// Primary: use the durably-persisted PendingChannelInfo written at
1687-
// ChannelPending time. Falls back to in-memory sets (populated at startup
1688-
// or on ChannelPending), then to any already-persisted ClosedChannelDetails
1689-
// record (for the replay case where insert_or_update already succeeded but
1690-
// add_event failed and PendingChannelInfo was already cleaned up).
1689+
// Primary: use the durably-persisted ChannelRecord written at ChannelPending
1690+
// time. Falls back to in-memory sets (populated at startup or on
1691+
// ChannelPending), then to any already-persisted ClosedChannelDetails record
1692+
// (for the replay case where insert_or_update already succeeded but
1693+
// add_event failed and the ChannelRecord was already cleaned up).
16911694
let (is_outbound, is_announced) = self
1692-
.pending_channel_store
1695+
.channel_record_store
16931696
.get(&user_channel_id)
1694-
.map(|info| (info.is_outbound, info.is_announced))
1697+
.and_then(|record| match record {
1698+
ChannelRecord::Funded { is_outbound, is_announced, .. } => {
1699+
Some((is_outbound, is_announced))
1700+
},
1701+
})
16951702
.or_else(|| {
16961703
self.closed_channel_store
16971704
.get(&user_channel_id)
@@ -1745,10 +1752,10 @@ where
17451752

17461753
match self.event_queue.add_event(event).await {
17471754
Ok(_) => {
1748-
if let Err(e) = self.pending_channel_store.remove(&user_channel_id) {
1755+
if let Err(e) = self.channel_record_store.remove(&user_channel_id) {
17491756
log_error!(
17501757
self.logger,
1751-
"Failed to remove pending channel info for {}: {}",
1758+
"Failed to remove channel record for {}: {}",
17521759
channel_id,
17531760
e
17541761
);

0 commit comments

Comments
 (0)