Skip to content

Commit f532dca

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 f532dca

10 files changed

Lines changed: 207 additions & 110 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: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8-
use std::time::{Duration, SystemTime, UNIX_EPOCH};
9-
108
use bitcoin::secp256k1::PublicKey;
119
use bitcoin::OutPoint;
1210
use lightning::events::ClosureReason;
1311
use lightning::impl_writeable_tlv_based;
1412
use lightning::ln::types::ChannelId;
1513

16-
use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
17-
use crate::hex_utils;
14+
use crate::data_store::{StorableObject, StorableObjectUpdate};
1815
use crate::types::UserChannelId;
1916

2017
/// Details of a closed channel.
@@ -30,10 +27,7 @@ pub struct ClosedChannelDetails {
3027
/// The local identifier of the channel.
3128
pub user_channel_id: UserChannelId,
3229
/// The node ID of the channel's counterparty.
33-
///
34-
/// Will be `None` if the channel was closed before the counterparty's node ID could be
35-
/// determined (e.g., very early in the channel negotiation process).
36-
pub counterparty_node_id: Option<PublicKey>,
30+
pub counterparty_node_id: PublicKey,
3731
/// The channel's funding transaction outpoint.
3832
///
3933
/// Will be `None` if the channel was closed before a funding transaction was established.
@@ -67,58 +61,16 @@ pub struct ClosedChannelDetails {
6761
impl_writeable_tlv_based!(ClosedChannelDetails, {
6862
(0, channel_id, required),
6963
(2, user_channel_id, required),
70-
(4, counterparty_node_id, option),
64+
(4, counterparty_node_id, required),
7165
(6, funding_txo, option),
7266
(8, channel_capacity_sats, option),
7367
(10, last_local_balance_msat, option),
7468
(12, is_outbound, required),
7569
(14, closure_reason, upgradable_option),
76-
(16, closed_at, (default_value, SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_secs())),
70+
(16, closed_at, required),
7771
(18, is_announced, required),
7872
});
7973

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-
12274
pub(crate) struct ClosedChannelDetailsUpdate(pub UserChannelId);
12375

12476
impl StorableObjectUpdate<ClosedChannelDetails> for ClosedChannelDetailsUpdate {
@@ -144,9 +96,3 @@ impl StorableObject for ClosedChannelDetails {
14496
ClosedChannelDetailsUpdate(self.user_channel_id)
14597
}
14698
}
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-
}

0 commit comments

Comments
 (0)