Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 27 additions & 9 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ use crate::fee_estimator::OnchainFeeEstimator;
use crate::gossip::GossipSource;
use crate::io::sqlite_store::SqliteStore;
use crate::io::utils::{
read_event_queue, read_external_pathfinding_scores_from_cache, read_network_graph,
read_node_metrics, read_output_sweeper, read_payments, read_peer_info, read_pending_payments,
read_scorer, write_node_metrics,
read_closed_channels, read_event_queue, read_external_pathfinding_scores_from_cache,
read_network_graph, read_node_metrics, read_output_sweeper, read_payments, read_peer_info,
read_pending_payments, read_scorer, write_node_metrics,
};
use crate::io::vss_store::VssStoreBuilder;
use crate::io::{
self, PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
self, CLOSED_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
CLOSED_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
PENDING_PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE,
PENDING_PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE,
};
Expand All @@ -76,9 +78,9 @@ use crate::peer_store::PeerStore;
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, SyncAndAsyncKVStore,
AsyncPersister, ChainMonitor, ChannelManager, ClosedChannelStore, DynStore, DynStoreRef,
DynStoreWrapper, GossipSync, Graph, KeysManager, MessageRouter, OnionMessenger, PaymentStore,
PeerManager, PendingPaymentStore, SyncAndAsyncKVStore,
};
use crate::wallet::persist::KVStoreWalletPersister;
use crate::wallet::Wallet;
Expand Down Expand Up @@ -1267,12 +1269,13 @@ fn build_with_store_internal(

let kv_store_ref = Arc::clone(&kv_store);
let logger_ref = Arc::clone(&logger);
let (payment_store_res, node_metris_res, pending_payment_store_res) =
let (payment_store_res, node_metris_res, pending_payment_store_res, closed_channel_store_res) =
runtime.block_on(async move {
tokio::join!(
read_payments(&*kv_store_ref, Arc::clone(&logger_ref)),
read_node_metrics(&*kv_store_ref, Arc::clone(&logger_ref)),
read_pending_payments(&*kv_store_ref, Arc::clone(&logger_ref))
read_pending_payments(&*kv_store_ref, Arc::clone(&logger_ref)),
read_closed_channels(&*kv_store_ref, Arc::clone(&logger_ref)),
)
});

Expand Down Expand Up @@ -1303,6 +1306,20 @@ fn build_with_store_internal(
},
};

let closed_channel_store = match closed_channel_store_res {
Ok(channels) => Arc::new(ClosedChannelStore::new(
channels,
CLOSED_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE.to_string(),
CLOSED_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE.to_string(),
Arc::clone(&kv_store),
Arc::clone(&logger),
)),
Err(e) => {
log_error!(logger, "Failed to read closed channel data from store: {}", e);
return Err(BuildError::ReadFailed);
},
};

let (chain_source, chain_tip_opt) = match chain_data_source_config {
Some(ChainDataSourceConfig::Esplora { server_url, headers, sync_config }) => {
let sync_config = sync_config.unwrap_or(EsploraSyncConfig::default());
Expand Down Expand Up @@ -1996,6 +2013,7 @@ fn build_with_store_internal(
scorer,
peer_store,
payment_store,
closed_channel_store,
lnurl_auth,
is_running,
node_metrics,
Expand Down
131 changes: 131 additions & 0 deletions src/closed_channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// This file is Copyright its original authors, visible in version control history.
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
// accordance with one or both of these licenses.

use std::time::{Duration, SystemTime, UNIX_EPOCH};

use bitcoin::secp256k1::PublicKey;
use bitcoin::OutPoint;
use lightning::events::ClosureReason;
use lightning::ln::msgs::DecodeError;
use lightning::ln::types::ChannelId;
use lightning::util::ser::{Readable, Writeable, Writer};
use lightning::{_init_and_read_len_prefixed_tlv_fields, write_tlv_fields};

use crate::data_store::{StorableObject, StorableObjectId, StorableObjectUpdate};
use crate::hex_utils;
use crate::types::UserChannelId;

/// Details of a closed channel.
///
/// Returned by [`Node::list_closed_channels`].
///
/// [`Node::list_closed_channels`]: crate::Node::list_closed_channels
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
pub struct ClosedChannelDetails {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClosedChannelDetails isn't exposed in bindings

/// The channel's ID at the time it was closed.
pub channel_id: ChannelId,
/// The local identifier of the channel.
pub user_channel_id: UserChannelId,
/// The node ID of the channel's counterparty.
pub counterparty_node_id: Option<PublicKey>,
/// The channel's funding transaction outpoint.
pub funding_txo: Option<OutPoint>,
/// The channel's capacity in satoshis.
pub channel_capacity_sats: Option<u64>,
/// Our local balance in millisatoshis at the time of channel closure.
pub last_local_balance_msat: Option<u64>,
/// Indicates whether we initiated the channel opening.
///
/// `true` if the channel was opened by us (outbound), `false` if opened by the counterparty
/// (inbound). This will be `false` for channels opened prior to this field being tracked.
pub is_outbound: bool,
/// The reason for the channel closure.
pub closure_reason: Option<ClosureReason>,
/// The timestamp, in seconds since start of the UNIX epoch, when the channel was closed.
pub closed_at: u64,
}

impl Writeable for ClosedChannelDetails {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), lightning::io::Error> {
write_tlv_fields!(writer, {
(0, self.channel_id, required),
(2, self.user_channel_id, required),
(4, self.counterparty_node_id, option),
(6, self.funding_txo, option),
(8, self.channel_capacity_sats, option),
(10, self.last_local_balance_msat, option),
(12, self.is_outbound, required),
(14, self.closure_reason, upgradable_option),
(16, self.closed_at, required),
});
Ok(())
}
}

impl Readable for ClosedChannelDetails {
fn read<R: lightning::io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
let unix_time_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();
_init_and_read_len_prefixed_tlv_fields!(reader, {
(0, channel_id, required),
(2, user_channel_id, required),
(4, counterparty_node_id, option),
(6, funding_txo, option),
(8, channel_capacity_sats, option),
(10, last_local_balance_msat, option),
(12, is_outbound, required),
(14, closure_reason, upgradable_option),
(16, closed_at, (default_value, unix_time_secs)),
});
Ok(ClosedChannelDetails {
channel_id: channel_id.0.ok_or(DecodeError::InvalidValue)?,
user_channel_id: user_channel_id.0.ok_or(DecodeError::InvalidValue)?,
counterparty_node_id,
funding_txo,
channel_capacity_sats,
last_local_balance_msat,
is_outbound: is_outbound.0.ok_or(DecodeError::InvalidValue)?,
closure_reason,
closed_at: closed_at.0.ok_or(DecodeError::InvalidValue)?,
})
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use impl_writeable_tlv_based! here instead? Matches the rest of the StorableObject types in the codebase


pub(crate) struct ClosedChannelDetailsUpdate(pub UserChannelId);

impl StorableObjectUpdate<ClosedChannelDetails> for ClosedChannelDetailsUpdate {
fn id(&self) -> UserChannelId {
self.0
}
}

impl StorableObject for ClosedChannelDetails {
type Id = UserChannelId;
type Update = ClosedChannelDetailsUpdate;

fn id(&self) -> UserChannelId {
self.user_channel_id
}

fn update(&mut self, _update: Self::Update) -> bool {
// Closed channel records are immutable once written.
false
}

fn to_update(&self) -> Self::Update {
ClosedChannelDetailsUpdate(self.user_channel_id)
}
}

impl StorableObjectId for UserChannelId {
fn encode_to_hex_str(&self) -> String {
hex_utils::to_string(&self.0.to_be_bytes())
}
}
96 changes: 89 additions & 7 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

use core::future::Future;
use core::task::{Poll, Waker};
use std::collections::VecDeque;
use std::collections::{HashSet, VecDeque};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

use bitcoin::blockdata::locktime::absolute::LockTime;
use bitcoin::secp256k1::PublicKey;
Expand All @@ -35,6 +36,7 @@ use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer};
use lightning_liquidity::lsps2::utils::compute_opening_fee;
use lightning_types::payment::{PaymentHash, PaymentPreimage};

use crate::closed_channel::ClosedChannelDetails;
use crate::config::{may_announce_channel, Config};
use crate::connection::ConnectionManager;
use crate::data_store::DataStoreUpdateResult;
Expand All @@ -54,7 +56,8 @@ use crate::payment::store::{
};
use crate::runtime::Runtime;
use crate::types::{
CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore, Sweeper, Wallet,
ClosedChannelStore, CustomTlvRecord, DynStore, KeysManager, OnionMessenger, PaymentStore,
Sweeper, Wallet,
};
use crate::{
hex_utils, BumpTransactionEventHandler, ChannelManager, Error, Graph, PeerInfo, PeerStore,
Expand Down Expand Up @@ -252,6 +255,18 @@ pub enum Event {
counterparty_node_id: Option<PublicKey>,
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
/// The channel's capacity in satoshis.
///
/// This will be `None` for events serialized by LDK Node v0.8.0 and prior.
channel_capacity_sats: Option<u64>,
/// The channel's funding transaction outpoint.
///
/// This will be `None` for events serialized by LDK Node v0.8.0 and prior.
channel_funding_txo: Option<OutPoint>,
/// Our local balance in millisatoshis at the time of channel closure.
///
/// This will be `None` for events serialized by LDK Node v0.8.0 and prior.
last_local_balance_msat: Option<u64>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Expand Down Expand Up @@ -314,6 +329,9 @@ impl_writeable_tlv_based_enum!(Event,
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, reason, upgradable_option),
(5, channel_capacity_sats, option),
(7, channel_funding_txo, option),
(9, last_local_balance_msat, option),
},
(6, PaymentClaimable) => {
(0, payment_hash, required),
Expand Down Expand Up @@ -508,6 +526,10 @@ where
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
payment_store: Arc<PaymentStore>,
peer_store: Arc<PeerStore<L>>,
closed_channel_store: Arc<ClosedChannelStore>,
// Tracks which user_channel_ids correspond to outbound channels. Populated at startup from
// list_channels() and updated on ChannelPending events. Consumed on ChannelClosed events.
outbound_channel_ids: Mutex<HashSet<UserChannelId>>,
keys_manager: Arc<KeysManager>,
runtime: Arc<Runtime>,
logger: L,
Expand All @@ -528,10 +550,23 @@ where
output_sweeper: Arc<Sweeper>, network_graph: Arc<Graph>,
liquidity_source: Option<Arc<LiquiditySource<Arc<Logger>>>>,
payment_store: Arc<PaymentStore>, peer_store: Arc<PeerStore<L>>,
keys_manager: Arc<KeysManager>, static_invoice_store: Option<StaticInvoiceStore>,
onion_messenger: Arc<OnionMessenger>, om_mailbox: Option<Arc<OnionMessageMailbox>>,
runtime: Arc<Runtime>, logger: L, config: Arc<Config>,
closed_channel_store: Arc<ClosedChannelStore>, keys_manager: Arc<KeysManager>,
static_invoice_store: Option<StaticInvoiceStore>, onion_messenger: Arc<OnionMessenger>,
om_mailbox: Option<Arc<OnionMessageMailbox>>, runtime: Arc<Runtime>, logger: L,
config: Arc<Config>,
) -> Self {
// Seed outbound_channel_ids from currently open channels so we correctly classify channels
// that were already open when this node started.
let outbound_channel_ids = {
let mut set = HashSet::new();
for chan in channel_manager.list_channels() {
if chan.is_outbound {
set.insert(UserChannelId(chan.user_channel_id));
}
}
Mutex::new(set)
};

Self {
event_queue,
wallet,
Expand All @@ -543,6 +578,8 @@ where
liquidity_source,
payment_store,
peer_store,
closed_channel_store,
outbound_channel_ids,
keys_manager,
logger,
runtime,
Expand Down Expand Up @@ -1477,6 +1514,13 @@ where
if let Some(pending_channel) =
channels.into_iter().find(|c| c.channel_id == channel_id)
{
if pending_channel.is_outbound {
self.outbound_channel_ids
.lock()
.unwrap()
.insert(UserChannelId(user_channel_id));
}

if !pending_channel.is_outbound
&& self.peer_store.get_peer(&counterparty_node_id).is_none()
{
Expand Down Expand Up @@ -1552,15 +1596,53 @@ where
reason,
user_channel_id,
counterparty_node_id,
..
channel_capacity_sats,
channel_funding_txo,
last_local_balance_msat,
} => {
log_info!(self.logger, "Channel {} closed due to: {}", channel_id, reason);

let user_channel_id = UserChannelId(user_channel_id);
let is_outbound =
self.outbound_channel_ids.lock().unwrap().remove(&user_channel_id);

let closed_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();

let funding_txo =
channel_funding_txo.map(|op| OutPoint { txid: op.txid, vout: op.index as u32 });

let record = ClosedChannelDetails {
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
channel_capacity_sats,
last_local_balance_msat,
is_outbound,
closure_reason: Some(reason.clone()),
closed_at,
};

if let Err(e) = self.closed_channel_store.insert(record) {
log_error!(
self.logger,
"Failed to persist closed channel {}: {}",
channel_id,
e
);
}

let event = Event::ChannelClosed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
user_channel_id,
counterparty_node_id,
reason: Some(reason),
channel_capacity_sats,
channel_funding_txo: funding_txo,
last_local_balance_msat,
};

match self.event_queue.add_event(event).await {
Expand Down
4 changes: 4 additions & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub(crate) const PEER_INFO_PERSISTENCE_KEY: &str = "peers";
pub(crate) const PAYMENT_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "payments";
pub(crate) const PAYMENT_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";

/// The closed channel information will be persisted under this prefix.
pub(crate) const CLOSED_CHANNEL_INFO_PERSISTENCE_PRIMARY_NAMESPACE: &str = "closed_channels";
pub(crate) const CLOSED_CHANNEL_INFO_PERSISTENCE_SECONDARY_NAMESPACE: &str = "";

/// The node metrics will be persisted under this key.
pub(crate) const NODE_METRICS_PRIMARY_NAMESPACE: &str = "";
pub(crate) const NODE_METRICS_SECONDARY_NAMESPACE: &str = "";
Expand Down
Loading
Loading