Skip to content
Draft
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
10 changes: 9 additions & 1 deletion bindings/ldk_node.udl
Original file line number Diff line number Diff line change
@@ -291,6 +291,8 @@ interface Event {
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
OnchainPaymentSuccessful(PaymentId payment_id, Txid txid, u64 amount_msat, BlockHash block_hash, u32 block_height);
OnchainPaymentReceived(PaymentId payment_id, Txid txid, u64 amount_msat, BlockHash block_hash, u32 block_height);
};

enum PaymentFailureReason {
@@ -326,7 +328,7 @@ interface ClosureReason {

[Enum]
interface PaymentKind {
Onchain();
Onchain(Txid txid, ConfirmationStatus status);
Bolt11(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret);
Bolt11Jit(PaymentHash hash, PaymentPreimage? preimage, PaymentSecret? secret, LSPFeeLimits lsp_fee_limits);
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id, UntrustedString? payer_note, u64? quantity);
@@ -357,6 +359,12 @@ dictionary LSPFeeLimits {
u64? max_proportional_opening_fee_ppm_msat;
};

[Enum]
interface ConfirmationStatus {
Confirmed (BlockHash block_hash, u32 height, u64 timestamp);
Unconfirmed ();
};

dictionary PaymentDetails {
PaymentId id;
PaymentKind kind;
47 changes: 25 additions & 22 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -782,11 +782,35 @@ fn build_with_store_internal(

let tx_broadcaster = Arc::new(TransactionBroadcaster::new(Arc::clone(&logger)));
let fee_estimator = Arc::new(OnchainFeeEstimator::new());

let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) {
Ok(payments) => {
Arc::new(PaymentStore::new(payments, Arc::clone(&kv_store), Arc::clone(&logger)))
},
Err(_) => {
return Err(BuildError::ReadFailed);
},
};

let event_queue = match io::utils::read_event_queue(Arc::clone(&kv_store), Arc::clone(&logger))
{
Ok(event_queue) => Arc::new(event_queue),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Arc::new(EventQueue::new(Arc::clone(&kv_store), Arc::clone(&logger)))
} else {
return Err(BuildError::ReadFailed);
}
},
};

let wallet = Arc::new(Wallet::new(
bdk_wallet,
wallet_persister,
Arc::clone(&tx_broadcaster),
Arc::clone(&fee_estimator),
Arc::clone(&payment_store),
Arc::clone(&event_queue),
Arc::clone(&logger),
));

@@ -995,6 +1019,7 @@ fn build_with_store_internal(
};

let channel_manager = Arc::new(channel_manager);
wallet.set_channel_manager(Arc::clone(&channel_manager));

// Give ChannelMonitors to ChainMonitor
for (_blockhash, channel_monitor) in channel_monitors.into_iter() {
@@ -1176,28 +1201,6 @@ fn build_with_store_internal(
},
}

// Init payment info storage
let payment_store = match io::utils::read_payments(Arc::clone(&kv_store), Arc::clone(&logger)) {
Ok(payments) => {
Arc::new(PaymentStore::new(payments, Arc::clone(&kv_store), Arc::clone(&logger)))
},
Err(_) => {
return Err(BuildError::ReadFailed);
},
};

let event_queue = match io::utils::read_event_queue(Arc::clone(&kv_store), Arc::clone(&logger))
{
Ok(event_queue) => Arc::new(event_queue),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Arc::new(EventQueue::new(Arc::clone(&kv_store), Arc::clone(&logger)))
} else {
return Err(BuildError::ReadFailed);
}
},
};

let peer_store = match io::utils::read_peer_info(Arc::clone(&kv_store), Arc::clone(&logger)) {
Ok(peer_store) => Arc::new(peer_store),
Err(e) => {
53 changes: 51 additions & 2 deletions src/event.rs
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ use lightning_liquidity::lsps2::utils::compute_opening_fee;

use bitcoin::blockdata::locktime::absolute::LockTime;
use bitcoin::secp256k1::PublicKey;
use bitcoin::{Amount, OutPoint};
use bitcoin::{Amount, BlockHash, OutPoint, Txid};

use rand::{thread_rng, Rng};

@@ -221,6 +221,41 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A sent onchain payment was successful.
///
/// It's guaranteed to have reached at least [`ANTI_REORG_DELAY`] delay confirmations.
///
///
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
OnchainPaymentSuccessful {
/// A local identifier used to track the payment.
payment_id: PaymentId,
/// The transaction identifier.
txid: Txid,
/// The value, in thousandths of a satoshi, that has been received.
amount_msat: u64,
/// The hash of the block in which the transaction was confirmed.
block_hash: BlockHash,
/// The height under which the block was confirmed.
block_height: u32,
},
/// An onchain payment has been received.
///
/// It's guaranteed to have reached at least [`ANTI_REORG_DELAY`] delay confirmations.
///
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
OnchainPaymentReceived {
/// A local identifier used to track the payment.
payment_id: PaymentId,
/// The transaction identifier.
txid: Txid,
/// The value, in thousandths of a satoshi, that has been received.
amount_msat: u64,
/// The hash of the block in which the transaction was confirmed.
block_hash: BlockHash,
/// The height under which the block was confirmed.
block_height: u32,
},
}

impl_writeable_tlv_based_enum!(Event,
@@ -277,7 +312,21 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, OnchainPaymentSuccessful) => {
(0, payment_id, required),
(2, txid, required),
(4, amount_msat, required),
(6, block_hash, required),
(8, block_height, required),
},
(9, OnchainPaymentReceived) => {
(0, payment_id, required),
(2, txid, required),
(4, amount_msat, required),
(6, block_hash, required),
(8, block_height, required),
},
);

pub struct EventQueue<L: Deref>
262 changes: 206 additions & 56 deletions src/payment/store.rs
Original file line number Diff line number Diff line change
@@ -25,8 +25,10 @@ use lightning::{

use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};

use bitcoin::{BlockHash, Txid};

use std::collections::hash_map;
use std::collections::HashMap;
use std::iter::FromIterator;
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -59,6 +61,113 @@ impl PaymentDetails {
.as_secs();
Self { id, kind, amount_msat, direction, status, latest_update_timestamp }
}

pub(crate) fn update(&mut self, update: &PaymentDetailsUpdate) -> bool {
debug_assert_eq!(
self.id, update.id,
"We should only ever override payment data for the same payment id"
);

let mut updated = false;

macro_rules! update_if_necessary {
($val: expr, $update: expr) => {
if $val != $update {
$val = $update;
updated = true;
}
};
}

if let Some(hash_opt) = update.hash {
match self.kind {
PaymentKind::Bolt12Offer { ref mut hash, .. } => {
debug_assert_eq!(
self.direction,
PaymentDirection::Outbound,
"We should only ever override payment hash for outbound BOLT 12 payments"
);
update_if_necessary!(*hash, hash_opt);
},
PaymentKind::Bolt12Refund { ref mut hash, .. } => {
debug_assert_eq!(
self.direction,
PaymentDirection::Outbound,
"We should only ever override payment hash for outbound BOLT 12 payments"
);
update_if_necessary!(*hash, hash_opt);
},
_ => {
// We can omit updating the hash for BOLT11 payments as the payment hash
// will always be known from the beginning.
},
}
}
if let Some(preimage_opt) = update.preimage {
match self.kind {
PaymentKind::Bolt11 { ref mut preimage, .. } => {
update_if_necessary!(*preimage, preimage_opt)
},
PaymentKind::Bolt11Jit { ref mut preimage, .. } => {
update_if_necessary!(*preimage, preimage_opt)
},
PaymentKind::Bolt12Offer { ref mut preimage, .. } => {
update_if_necessary!(*preimage, preimage_opt)
},
PaymentKind::Bolt12Refund { ref mut preimage, .. } => {
update_if_necessary!(*preimage, preimage_opt)
},
PaymentKind::Spontaneous { ref mut preimage, .. } => {
update_if_necessary!(*preimage, preimage_opt)
},
_ => {},
}
}

if let Some(secret_opt) = update.secret {
match self.kind {
PaymentKind::Bolt11 { ref mut secret, .. } => {
update_if_necessary!(*secret, secret_opt)
},
PaymentKind::Bolt11Jit { ref mut secret, .. } => {
update_if_necessary!(*secret, secret_opt)
},
PaymentKind::Bolt12Offer { ref mut secret, .. } => {
update_if_necessary!(*secret, secret_opt)
},
PaymentKind::Bolt12Refund { ref mut secret, .. } => {
update_if_necessary!(*secret, secret_opt)
},
_ => {},
}
}

if let Some(amount_opt) = update.amount_msat {
update_if_necessary!(self.amount_msat, amount_opt);
}

if let Some(status) = update.status {
update_if_necessary!(self.status, status);
}

if let Some(confirmation_status) = update.confirmation_status {
match self.kind {
PaymentKind::Onchain { ref mut status, .. } => {
update_if_necessary!(*status, confirmation_status);
},
_ => {},
}
}

if updated {
self.latest_update_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();
}

updated
}
}

impl Writeable for PaymentDetails {
@@ -175,7 +284,17 @@ impl_writeable_tlv_based_enum!(PaymentStatus,
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentKind {
/// An on-chain payment.
Onchain,
///
/// Payments of this kind will be considered pending until the respective transaction has
/// reached [`ANTI_REORG_DELAY`] confirmations on-chain.
///
/// [`ANTI_REORG_DELAY`]: lightning::chain::channelmonitor::ANTI_REORG_DELAY
Onchain {
/// The transaction identifier of this payment.
txid: Txid,
/// The confirmation status of this payment.
status: ConfirmationStatus,
},
/// A [BOLT 11] payment.
///
/// [BOLT 11]: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md
@@ -264,7 +383,10 @@ pub enum PaymentKind {
}

impl_writeable_tlv_based_enum!(PaymentKind,
(0, Onchain) => {},
(0, Onchain) => {
(0, txid, required),
(2, status, required),
},
(2, Bolt11) => {
(0, hash, required),
(2, preimage, option),
@@ -297,6 +419,31 @@ impl_writeable_tlv_based_enum!(PaymentKind,
}
);

/// Represents the confirmation status of a transaction.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ConfirmationStatus {
/// The transaction is confirmed in the best chain.
Confirmed {
/// The hash of the block in which the transaction was confirmed.
block_hash: BlockHash,
/// The height under which the block was confirmed.
height: u32,
/// The time at which the block was confirmed.
timestamp: u64,
},
/// The transaction is unconfirmed.
Unconfirmed,
}

impl_writeable_tlv_based_enum!(ConfirmationStatus,
(0, Confirmed) => {
(0, block_hash, required),
(2, height, required),
(4, timestamp, required),
},
(2, Unconfirmed) => {},
);

/// Limits applying to how much fee we allow an LSP to deduct from the payment amount.
///
/// See [`LdkChannelConfig::accept_underpaying_htlcs`] for more information.
@@ -326,6 +473,7 @@ pub(crate) struct PaymentDetailsUpdate {
pub amount_msat: Option<Option<u64>>,
pub direction: Option<PaymentDirection>,
pub status: Option<PaymentStatus>,
pub confirmation_status: Option<ConfirmationStatus>,
}

impl PaymentDetailsUpdate {
@@ -338,6 +486,36 @@ impl PaymentDetailsUpdate {
amount_msat: None,
direction: None,
status: None,
confirmation_status: None,
}
}
}

impl From<&PaymentDetails> for PaymentDetailsUpdate {
fn from(value: &PaymentDetails) -> Self {
let (hash, preimage, secret) = match value.kind {
PaymentKind::Bolt11 { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
PaymentKind::Bolt11Jit { hash, preimage, secret, .. } => (Some(hash), preimage, secret),
PaymentKind::Bolt12Offer { hash, preimage, secret, .. } => (hash, preimage, secret),
PaymentKind::Bolt12Refund { hash, preimage, secret, .. } => (hash, preimage, secret),
PaymentKind::Spontaneous { hash, preimage, .. } => (Some(hash), preimage, None),
_ => (None, None, None),
};

let confirmation_status = match value.kind {
PaymentKind::Onchain { status, .. } => Some(status),
_ => None,
};

Self {
id: value.id,
hash: Some(hash),
preimage: Some(preimage),
secret: Some(secret),
amount_msat: Some(value.amount_msat),
direction: Some(value.direction),
status: Some(value.status),
confirmation_status,
}
}
}
@@ -370,6 +548,28 @@ where
Ok(updated)
}

pub(crate) fn insert_or_update(&self, payment: &PaymentDetails) -> Result<bool, Error> {
let mut locked_payments = self.payments.lock().unwrap();

let updated;
match locked_payments.entry(payment.id) {
hash_map::Entry::Occupied(mut e) => {
let update = payment.into();
updated = e.get_mut().update(&update);
if updated {
self.persist_info(&payment.id, e.get())?;
}
},
hash_map::Entry::Vacant(e) => {
e.insert(payment.clone());
self.persist_info(&payment.id, payment)?;
updated = true;
},
}

Ok(updated)
}

pub(crate) fn remove(&self, id: &PaymentId) -> Result<(), Error> {
let store_key = hex_utils::to_string(&id.0);
self.kv_store
@@ -401,60 +601,10 @@ where
let mut locked_payments = self.payments.lock().unwrap();

if let Some(payment) = locked_payments.get_mut(&update.id) {
if let Some(hash_opt) = update.hash {
match payment.kind {
PaymentKind::Bolt12Offer { ref mut hash, .. } => {
debug_assert_eq!(payment.direction, PaymentDirection::Outbound,
"We should only ever override payment hash for outbound BOLT 12 payments");
*hash = hash_opt
},
PaymentKind::Bolt12Refund { ref mut hash, .. } => {
debug_assert_eq!(payment.direction, PaymentDirection::Outbound,
"We should only ever override payment hash for outbound BOLT 12 payments");
*hash = hash_opt
},
_ => {
// We can omit updating the hash for BOLT11 payments as the payment hash
// will always be known from the beginning.
},
}
}
if let Some(preimage_opt) = update.preimage {
match payment.kind {
PaymentKind::Bolt11 { ref mut preimage, .. } => *preimage = preimage_opt,
PaymentKind::Bolt11Jit { ref mut preimage, .. } => *preimage = preimage_opt,
PaymentKind::Bolt12Offer { ref mut preimage, .. } => *preimage = preimage_opt,
PaymentKind::Bolt12Refund { ref mut preimage, .. } => *preimage = preimage_opt,
PaymentKind::Spontaneous { ref mut preimage, .. } => *preimage = preimage_opt,
_ => {},
}
updated = payment.update(update);
if updated {
self.persist_info(&update.id, payment)?;
}

if let Some(secret_opt) = update.secret {
match payment.kind {
PaymentKind::Bolt11 { ref mut secret, .. } => *secret = secret_opt,
PaymentKind::Bolt11Jit { ref mut secret, .. } => *secret = secret_opt,
PaymentKind::Bolt12Offer { ref mut secret, .. } => *secret = secret_opt,
PaymentKind::Bolt12Refund { ref mut secret, .. } => *secret = secret_opt,
_ => {},
}
}

if let Some(amount_opt) = update.amount_msat {
payment.amount_msat = amount_opt;
}

if let Some(status) = update.status {
payment.status = status;
}

payment.latest_update_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();

self.persist_info(&update.id, payment)?;
updated = true;
}
Ok(updated)
}
4 changes: 3 additions & 1 deletion src/uniffi_types.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,9 @@ pub use crate::config::{
default_config, AnchorChannelsConfig, EsploraSyncConfig, MaxDustHTLCExposure,
};
pub use crate::graph::{ChannelInfo, ChannelUpdateInfo, NodeAnnouncementInfo, NodeInfo};
pub use crate::payment::store::{LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus};
pub use crate::payment::store::{
ConfirmationStatus, LSPFeeLimits, PaymentDirection, PaymentKind, PaymentStatus,
};
pub use crate::payment::{MaxTotalRoutingFeeLimit, QrPaymentResult, SendingParameters};

pub use lightning::chain::channelmonitor::BalanceSource;
148 changes: 144 additions & 4 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
@@ -7,15 +7,21 @@

use persist::KVStoreWalletPersister;

use crate::logger::{log_debug, log_error, log_info, log_trace, Logger};
use crate::logger::{log_debug, log_error, log_info, log_trace, FilesystemLogger, Logger};

use crate::event::{Event, EventQueue};
use crate::fee_estimator::{ConfirmationTarget, FeeEstimator};
use crate::payment::store::{ConfirmationStatus, PaymentStore};
use crate::payment::{PaymentDetails, PaymentDirection, PaymentStatus};
use crate::types::ChannelManager;
use crate::Error;

use lightning::chain::chaininterface::BroadcasterInterface;
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
use lightning::chain::{BestBlock, Listen};

use lightning::events::bump_transaction::{Utxo, WalletSource};
use lightning::ln::channelmanager::PaymentId;
use lightning::ln::inbound_payment::ExpandedKey;
use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage};
use lightning::ln::script::ShutdownScript;
@@ -44,6 +50,7 @@ use bitcoin::{

use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

pub(crate) enum OnchainSendAmount {
ExactRetainingReserve { amount_sats: u64, cur_anchor_reserve_sats: u64 },
@@ -63,8 +70,11 @@ where
// A BDK on-chain wallet.
inner: Mutex<PersistedWallet<KVStoreWalletPersister>>,
persister: Mutex<KVStoreWalletPersister>,
channel_manager: Mutex<Option<Arc<ChannelManager>>>,
broadcaster: B,
fee_estimator: E,
payment_store: Arc<PaymentStore<Arc<FilesystemLogger>>>,
event_queue: Arc<EventQueue<Arc<FilesystemLogger>>>,
logger: L,
}

@@ -76,13 +86,28 @@ where
{
pub(crate) fn new(
wallet: bdk_wallet::PersistedWallet<KVStoreWalletPersister>,
wallet_persister: KVStoreWalletPersister, broadcaster: B, fee_estimator: E, logger: L,
wallet_persister: KVStoreWalletPersister, broadcaster: B, fee_estimator: E,
payment_store: Arc<PaymentStore<Arc<FilesystemLogger>>>,
event_queue: Arc<EventQueue<Arc<FilesystemLogger>>>, logger: L,
) -> Self {
let inner = Mutex::new(wallet);
let persister = Mutex::new(wallet_persister);
Self { inner, persister, broadcaster, fee_estimator, logger }
let channel_manager = Mutex::new(None);
Self {
inner,
persister,
channel_manager,
broadcaster,
fee_estimator,
payment_store,
event_queue,
logger,
}
}

pub(crate) fn set_channel_manager(&self, channel_manager: Arc<ChannelManager>) {
*self.channel_manager.lock().unwrap() = Some(channel_manager);
}
pub(crate) fn get_full_scan_request(&self) -> FullScanRequest<KeychainKind> {
self.inner.lock().unwrap().start_full_scan().build()
}
@@ -106,6 +131,11 @@ where
Error::PersistenceFailed
})?;

self.update_payment_store(&mut *locked_wallet).map_err(|e| {
log_error!(self.logger, "Failed to update payment store: {}", e);
Error::PersistenceFailed
})?;

Ok(())
},
Err(e) => {
@@ -130,6 +160,111 @@ where
Ok(())
}

fn update_payment_store<'a>(
&self, locked_wallet: &'a mut PersistedWallet<KVStoreWalletPersister>,
) -> Result<(), Error> {
let latest_update_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::from_secs(0))
.as_secs();

for wtx in locked_wallet.transactions() {
let id = PaymentId(wtx.tx_node.txid.to_byte_array());
let txid = wtx.tx_node.txid;
let (payment_status, confirmation_status) = match wtx.chain_position {
bdk_chain::ChainPosition::Confirmed { anchor, .. } => {
let confirmation_height = anchor.block_id.height;
let cur_height = locked_wallet.latest_checkpoint().height();
let payment_status = if cur_height >= confirmation_height + ANTI_REORG_DELAY - 1
{
PaymentStatus::Succeeded
} else {
PaymentStatus::Pending
};
let confirmation_status = ConfirmationStatus::Confirmed {
block_hash: anchor.block_id.hash,
height: confirmation_height,
timestamp: anchor.confirmation_time,
};
(payment_status, confirmation_status)
},
bdk_chain::ChainPosition::Unconfirmed { .. } => {
(PaymentStatus::Pending, ConfirmationStatus::Unconfirmed)
},
};
// TODO: It would be great to introduce additional variants for
// `ChannelFunding` and `ChannelClosing`. For the former, we could just
// take a reference to `ChannelManager` here and check against
// `list_channels`. But for the latter the best approach is much less
// clear: for force-closes/HTLC spends we should be good querying
// `OutputSweeper::tracked_spendable_outputs`, but regular channel closes
// (i.e., `SpendableOutputDescriptor::StaticOutput` variants) are directly
// spent to a wallet address. The only solution I can come up with is to
// create and persist a list of 'static pending outputs' that we could use
// here to determine the `PaymentKind`, but that's not really satisfactory, so
// we're punting on it until we can come up with a better solution.
let kind = crate::payment::PaymentKind::Onchain { txid, status: confirmation_status };
let (sent, received) = locked_wallet.sent_and_received(&wtx.tx_node.tx);
let (direction, amount_msat) = if sent > received {
let direction = PaymentDirection::Outbound;
let amount_msat = sent.to_sat().saturating_sub(received.to_sat()) * 1000;
(direction, amount_msat)
} else {
let direction = PaymentDirection::Inbound;
let amount_msat = received.to_sat().saturating_sub(sent.to_sat()) * 1000;
(direction, amount_msat)
};

let payment = PaymentDetails {
id,
kind,
amount_msat: Some(amount_msat),
direction,
status: payment_status,
latest_update_timestamp,
};

let updated = self.payment_store.insert_or_update(&payment)?;

// If we just updated the entry and we deem the transaction successful (i.e., has seen
// at least ANTI_REORG_DELAY confirmations), issue an event.
if updated && payment_status == PaymentStatus::Succeeded {
match confirmation_status {
ConfirmationStatus::Confirmed { block_hash, height, .. } => {
let event;
if direction == PaymentDirection::Outbound {
event = Event::OnchainPaymentSuccessful {
payment_id: id,
txid,
block_hash,
block_height: height,
amount_msat,
};
} else {
event = Event::OnchainPaymentReceived {
payment_id: id,
txid,
block_hash,
block_height: height,
amount_msat,
};
}
self.event_queue.add_event(event).map_err(|e| {
log_error!(self.logger, "Failed to push to event queue: {}", e);
Error::PersistenceFailed
})?;
},
_ => {
// We only issue events for transactions that have seen ANTI_REORG_DELAY
// confirmations.
},
}
}
}

Ok(())
}

pub(crate) fn create_funding_transaction(
&self, output_script: ScriptBuf, amount: Amount, confirmation_target: ConfirmationTarget,
locktime: LockTime,
@@ -474,7 +609,12 @@ where
}

match locked_wallet.apply_block(block, height) {
Ok(()) => (),
Ok(()) => {
if let Err(e) = self.update_payment_store(&mut *locked_wallet) {
log_error!(self.logger, "Failed to update payment store: {}", e);
return;
}
},
Err(e) => {
log_error!(
self.logger,
99 changes: 88 additions & 11 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -483,6 +483,36 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);

// Check we saw the node funding transactions.
assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
1
);
assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
0
);
assert_eq!(
node_b
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
1
);
assert_eq!(
node_b
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
0
);

// Check we haven't got any events yet
assert_eq!(node_a.next_event(), None);
assert_eq!(node_b.next_event(), None);
@@ -515,6 +545,15 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
node_a.sync_wallets().unwrap();
node_b.sync_wallets().unwrap();

// Check we now see the channel funding transaction as outbound.
assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
1
);

let onchain_fee_buffer_sat = 5000;
let node_a_anchor_reserve_sat = if expect_anchor_channel { 25_000 } else { 0 };
let node_a_upper_bound_sat =
@@ -564,22 +603,26 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
let payment_id = node_a.bolt11_payment().send(&invoice, None).unwrap();
assert_eq!(node_a.bolt11_payment().send(&invoice, None), Err(NodeError::DuplicatePayment));

assert_eq!(node_a.list_payments().first().unwrap().id, payment_id);
assert!(!node_a.list_payments_with_filter(|p| p.id == payment_id).is_empty());

let outbound_payments_a =
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
let outbound_payments_a = node_a.list_payments_with_filter(|p| {
p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
});
assert_eq!(outbound_payments_a.len(), 1);

let inbound_payments_a =
node_a.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound);
let inbound_payments_a = node_a.list_payments_with_filter(|p| {
p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
});
assert_eq!(inbound_payments_a.len(), 0);

let outbound_payments_b =
node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Outbound);
let outbound_payments_b = node_b.list_payments_with_filter(|p| {
p.direction == PaymentDirection::Outbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
});
assert_eq!(outbound_payments_b.len(), 0);

let inbound_payments_b =
node_b.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound);
let inbound_payments_b = node_b.list_payments_with_filter(|p| {
p.direction == PaymentDirection::Inbound && matches!(p.kind, PaymentKind::Bolt11 { .. })
});
assert_eq!(inbound_payments_b.len(), 1);

expect_event!(node_a, PaymentSuccessful);
@@ -813,8 +856,26 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
node_b.payment(&keysend_payment_id).unwrap().kind,
PaymentKind::Spontaneous { .. }
));
assert_eq!(node_a.list_payments().len(), 6);
assert_eq!(node_b.list_payments().len(), 7);
assert_eq!(
node_a.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
5
);
assert_eq!(
node_b.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Bolt11 { .. })).len(),
6
);
assert_eq!(
node_a
.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. }))
.len(),
1
);
assert_eq!(
node_b
.list_payments_with_filter(|p| matches!(p.kind, PaymentKind::Spontaneous { .. }))
.len(),
1
);

println!("\nB close_channel (force: {})", force_close);
if force_close {
@@ -935,6 +996,22 @@ pub(crate) fn do_channel_full_cycle<E: ElectrumApi>(
assert_eq!(node_a.list_balances().total_anchor_channels_reserve_sats, 0);
assert_eq!(node_b.list_balances().total_anchor_channels_reserve_sats, 0);

// Now we should have seen the channel closing transaction on-chain.
assert_eq!(
node_a
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
2
);
assert_eq!(
node_b
.list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound
&& matches!(p.kind, PaymentKind::Onchain { .. }))
.len(),
2
);

// Check we handled all events
assert_eq!(node_a.next_event(), None);
assert_eq!(node_b.next_event(), None);