Skip to content

Commit

Permalink
Update PaymentStore after each Wallet sync
Browse files Browse the repository at this point in the history
We update the payment store whenever syncing the wallet state finished.
  • Loading branch information
tnull committed Jan 16, 2025
1 parent e96fbfb commit 3c8bf49
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 13 deletions.
5 changes: 5 additions & 0 deletions src/payment/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ impl_writeable_tlv_based_enum!(PaymentStatus,
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaymentKind {
/// An on-chain payment.
///
/// 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,
Expand Down
92 changes: 90 additions & 2 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ use persist::KVStoreWalletPersister;
use crate::logger::{log_debug, log_error, log_info, log_trace, FilesystemLogger, Logger};

use crate::fee_estimator::{ConfirmationTarget, FeeEstimator};
use crate::payment::store::PaymentStore;
use crate::payment::store::{ConfirmationStatus, PaymentStore};
use crate::payment::{PaymentDetails, PaymentDirection, PaymentStatus};
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;
Expand Down Expand Up @@ -45,6 +48,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 },
Expand Down Expand Up @@ -109,6 +113,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) => {
Expand All @@ -133,6 +142,80 @@ 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();

let payments = locked_wallet
.transactions()
.map(|wtx| {
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 = Some(sent.to_sat().saturating_sub(received.to_sat()) * 1000);
(direction, amount_msat)
} else {
let direction = PaymentDirection::Inbound;
let amount_msat = Some(received.to_sat().saturating_sub(sent.to_sat()) * 1000);
(direction, amount_msat)
};

PaymentDetails {
id,
kind,
amount_msat,
direction,
status: payment_status,
latest_update_timestamp,
}
})
.collect();

self.payment_store.batch_update(payments)?;

Ok(())
}

pub(crate) fn create_funding_transaction(
&self, output_script: ScriptBuf, amount: Amount, confirmation_target: ConfirmationTarget,
locktime: LockTime,
Expand Down Expand Up @@ -477,7 +560,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,
Expand Down
99 changes: 88 additions & 11 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,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);
Expand Down Expand Up @@ -514,6 +544,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 =
Expand Down Expand Up @@ -558,22 +597,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);
Expand Down Expand Up @@ -789,8 +832,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 {
Expand Down Expand Up @@ -911,6 +972,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);
Expand Down

0 comments on commit 3c8bf49

Please sign in to comment.