From d4ae971a891ffa3c6e7bec63e419bc4fdf42064e Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 22 Dec 2025 02:33:52 +0000 Subject: [PATCH 1/2] Handle internal transfers completed after shutdown Currently we only process setting `TxMetadata` for internal transfers if they complete in-line while the app is open and running the rebalance logic. If, however, an internal transfer is initiated but then does not complete the metadata is lost and instead our transaction list includes a LN payment received and no information about the trusted transaction at all. Instead, here, we track enough information in `TxType::PendingRebalance` to match the trusted transfer with an LN transaction and then do so in `list_transactions`, updating metadata as appropriate if we find a match. We handle upgrades from previous versions of Orange gracefully, though I'm not sure if we really need to do that yet. --- graduated-rebalancer/src/lib.rs | 15 +++++ orange-sdk/src/lib.rs | 102 ++++++++++++++++++++++++++++---- orange-sdk/src/rebalancer.rs | 8 ++- orange-sdk/src/store.rs | 21 +++++-- 4 files changed, 130 insertions(+), 16 deletions(-) diff --git a/graduated-rebalancer/src/lib.rs b/graduated-rebalancer/src/lib.rs index 827d0ba..72d2ee9 100644 --- a/graduated-rebalancer/src/lib.rs +++ b/graduated-rebalancer/src/lib.rs @@ -12,6 +12,7 @@ use bitcoin_payment_instructions::PaymentMethod; use lightning::bitcoin::hashes::Hash; use lightning::bitcoin::hex::DisplayHex; use lightning::bitcoin::OutPoint; +use lightning::types::payment::PaymentHash; use lightning::util::logger::Logger; use lightning::{log_debug, log_error, log_info}; use lightning_invoice::Bolt11Invoice; @@ -159,6 +160,12 @@ pub enum RebalancerEvent { trigger_id: [u8; 32], /// Trusted wallet payment ID for the rebalance trusted_rebalance_payment_id: [u8; 32], + /// The [`PaymentHash`] of this rebalance payment. + /// + /// Note that if you're using LDK only we have the information required to make a + /// payment for this hash, meaning that any payments claimable by our lightning + /// wallet are related to this rebalance. + payment_hash: PaymentHash, /// Amount being rebalanced in millisatoshis amount_msat: u64, }, @@ -170,6 +177,12 @@ pub enum RebalancerEvent { trusted_rebalance_payment_id: [u8; 32], /// Lightning payment ID for the rebalance ln_rebalance_payment_id: [u8; 32], + /// The [`PaymentHash`] of this rebalance payment. + /// + /// Note that if you're using LDK only we have the information required to make a + /// payment for this hash, meaning that any payments claimable by our lightning + /// wallet are related to this rebalance. + payment_hash: PaymentHash, /// Amount rebalanced in millisatoshis amount_msat: u64, /// Total fee paid in millisatoshis @@ -281,6 +294,7 @@ where .handle_event(RebalancerEvent::RebalanceInitiated { trigger_id: params.id, trusted_rebalance_payment_id: rebalance_id, + payment_hash: PaymentHash(expected_hash.to_byte_array()), amount_msat: transfer_amt.milli_sats(), }) .await; @@ -322,6 +336,7 @@ where trusted_rebalance_payment_id: rebalance_id, ln_rebalance_payment_id: ln_payment.id, amount_msat: transfer_amt.milli_sats(), + payment_hash: PaymentHash(expected_hash.to_byte_array()), fee_msat: ln_payment.fee_paid_msat.unwrap_or_default() + trusted_payment.fee_paid_msat.unwrap_or_default(), }) diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index 9984055..47610d7 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -679,6 +679,15 @@ impl Wallet { transaction: Option, } + let mut completed_internal_transfers = HashMap::new(); + #[derive(Debug)] + struct CompletedInternalTransfer { + trusted_transfer_id: [u8; 32], + payment_triggering_transfer: PaymentId, + time: Duration, + ln_transfer_id: Option<[u8; 32]>, + } + for payment in trusted_payments { if let Some(tx_metadata) = tx_metadata.get(&PaymentId::Trusted(payment.id)) { match &tx_metadata.ty { @@ -731,7 +740,32 @@ impl Wallet { time_since_epoch: tx_metadata.time, }); }, - TxType::PendingRebalance { .. } => { + TxType::PendingRebalance { + trusted_payment, + payment_triggering_transfer, + payment_hash, + } => { + if let Some(trusted_id) = trusted_payment { + debug_assert_eq!(*trusted_id, payment.id); + } + if payment.status == TxStatus::Completed { + if trusted_payment.is_some() + && payment_triggering_transfer.is_some() + && payment_hash.is_some() + { + let old_val = completed_internal_transfers.insert( + payment_hash.unwrap(), + CompletedInternalTransfer { + trusted_transfer_id: trusted_payment.unwrap(), + payment_triggering_transfer: payment_triggering_transfer + .unwrap(), + time: payment.time_since_epoch, + ln_transfer_id: None, + }, + ); + debug_assert!(old_val.is_none()); + } + } // Pending rebalances are not shown in the transaction list. continue; }, @@ -879,15 +913,63 @@ impl Wallet { // failed rebalances. continue; } - res.push(Transaction { - id: PaymentId::SelfCustodial(payment.id.0), - status, - outbound: payment.direction == PaymentDirection::Outbound, - amount: payment.amount_msat.map(|a| Amount::from_milli_sats(a).unwrap()), - fee, - payment_type: (&payment).into(), - time_since_epoch: Duration::from_secs(payment.latest_update_timestamp), - }) + + let payment_hash = match payment.kind { + PaymentKind::Onchain { .. } => None, + PaymentKind::Bolt11 { hash, .. } => Some(hash), + PaymentKind::Bolt11Jit { hash, .. } => Some(hash), + PaymentKind::Bolt12Offer { hash, .. } => hash, + PaymentKind::Bolt12Refund { hash, .. } => hash, + PaymentKind::Spontaneous { hash, .. } => Some(hash), + }; + + if let Some(info) = + payment_hash.map(|hash| completed_internal_transfers.get_mut(&hash)).flatten() + { + info.ln_transfer_id = Some(payment.id.0); + } else { + res.push(Transaction { + id: PaymentId::SelfCustodial(payment.id.0), + status, + outbound: payment.direction == PaymentDirection::Outbound, + amount: payment.amount_msat.map(|a| Amount::from_milli_sats(a).unwrap()), + fee, + payment_type: (&payment).into(), + time_since_epoch: Duration::from_secs(payment.latest_update_timestamp), + }) + } + } + } + + std::mem::drop(tx_metadata); + for (_, info) in completed_internal_transfers { + debug_assert!(info.ln_transfer_id.is_some()); + if let Some(lightning_payment) = info.ln_transfer_id { + log_info!( + self.inner.logger, + "Setting metadata for background-completed internal transfer with from trusted transaction {:?} to LN transaction {:?} triggered by transaction {}", + info.trusted_transfer_id, + lightning_payment, + info.payment_triggering_transfer + ); + let metadata = TxMetadata { + ty: TxType::TrustedToLightning { + trusted_payment: info.trusted_transfer_id, + lightning_payment, + payment_triggering_transfer: info.payment_triggering_transfer, + }, + time: info.time, + }; + self.inner + .tx_metadata + .set_tx_caused_rebalance(&info.payment_triggering_transfer) + .expect("Failed to write metadata for rebalance transaction"); + self.inner + .tx_metadata + .upsert(PaymentId::Trusted(info.trusted_transfer_id), metadata); + self.inner + .tx_metadata + .insert(PaymentId::SelfCustodial(lightning_payment), metadata); } } diff --git a/orange-sdk/src/rebalancer.rs b/orange-sdk/src/rebalancer.rs index 335717c..79abdb4 100644 --- a/orange-sdk/src/rebalancer.rs +++ b/orange-sdk/src/rebalancer.rs @@ -293,9 +293,14 @@ impl graduated_rebalancer::EventHandler for OrangeRebalanceEventHandler { trigger_id, trusted_rebalance_payment_id, amount_msat, + payment_hash, } => { let metadata = TxMetadata { - ty: TxType::PendingRebalance {}, + ty: TxType::PendingRebalance { + payment_triggering_transfer: Some(PaymentId::Trusted(trigger_id)), + trusted_payment: Some(trusted_rebalance_payment_id), + payment_hash: Some(payment_hash), + }, time: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(), }; self.tx_metadata @@ -317,6 +322,7 @@ impl graduated_rebalancer::EventHandler for OrangeRebalanceEventHandler { trigger_id, trusted_rebalance_payment_id: rebalance_id, ln_rebalance_payment_id: lightning_id, + payment_hash: _, amount_msat, fee_msat, } => { diff --git a/orange-sdk/src/store.rs b/orange-sdk/src/store.rs index f6eb15a..7fd8f9c 100644 --- a/orange-sdk/src/store.rs +++ b/orange-sdk/src/store.rs @@ -18,8 +18,8 @@ use ldk_node::bitcoin::Txid; use ldk_node::bitcoin::hex::{DisplayHex, FromHex}; use ldk_node::lightning::io; use ldk_node::lightning::ln::msgs::DecodeError; -use ldk_node::lightning::types::payment::PaymentPreimage; -use ldk_node::lightning::util::persist::KVStore; +use ldk_node::lightning::types::payment::{PaymentHash, PaymentPreimage}; +use ldk_node::lightning::util::persist::{KVStore, KVStoreSync}; use ldk_node::lightning::util::ser::{Readable, Writeable, Writer}; use ldk_node::lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum}; use ldk_node::payment::PaymentDetails; @@ -254,14 +254,21 @@ pub(crate) enum TxType { Payment { ty: PaymentType, }, - PendingRebalance {}, + PendingRebalance { + // Note that while all of these fields are `Option`al, they are always + // filled in by any released version of Orange. They were added after + // some initial beta testing, however. + trusted_payment: Option<[u8; 32]>, + payment_triggering_transfer: Option, + payment_hash: Option, + }, } impl TxType { pub(crate) fn is_rebalance(&self) -> bool { matches!( self, - TxType::PendingRebalance {} + TxType::PendingRebalance { .. } | TxType::TrustedToLightning { .. } | TxType::OnchainToLightning { .. } ) @@ -280,7 +287,11 @@ impl_writeable_tlv_based_enum!(TxType, }, (2, PaymentTriggeringTransferLightning) => { (0, ty, required), }, (3, Payment) => { (0, ty, required), }, - (4, PendingRebalance) => {}, + (4, PendingRebalance) => { + (1, trusted_payment, option), + (3, payment_triggering_transfer, option), + (5, payment_hash, option), + }, ); #[derive(Debug, Copy, Clone)] From 01d1f3dafa7c89c577fb7ad1ba709b2b520b86a8 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 29 Dec 2025 16:54:28 +0000 Subject: [PATCH 2/2] f now async, which makes rust think our function's future is not send --- orange-sdk/src/lib.rs | 473 ++++++++++++++++++++-------------------- orange-sdk/src/store.rs | 2 +- 2 files changed, 241 insertions(+), 234 deletions(-) diff --git a/orange-sdk/src/lib.rs b/orange-sdk/src/lib.rs index 47610d7..ef4100c 100644 --- a/orange-sdk/src/lib.rs +++ b/orange-sdk/src/lib.rs @@ -669,7 +669,6 @@ impl Wallet { let mut res = Vec::with_capacity( trusted_payments.len() + lightning_payments.len() + splice_outs.len(), ); - let tx_metadata = self.inner.tx_metadata.read(); let mut internal_transfers = HashMap::new(); #[derive(Debug, Default)] @@ -687,190 +686,204 @@ impl Wallet { time: Duration, ln_transfer_id: Option<[u8; 32]>, } - - for payment in trusted_payments { - if let Some(tx_metadata) = tx_metadata.get(&PaymentId::Trusted(payment.id)) { - match &tx_metadata.ty { - TxType::TrustedToLightning { - trusted_payment, - lightning_payment: _, - payment_triggering_transfer, - } => { - let entry = internal_transfers - .entry(*payment_triggering_transfer) - .or_insert(InternalTransfer::default()); - if payment.id == *trusted_payment { - debug_assert!(entry.send_fee.is_none()); - entry.send_fee = Some(payment.fee); - } else { - debug_assert!(false); - } - }, - TxType::OnchainToLightning { .. } => { + { + let tx_metadata = self.inner.tx_metadata.read(); + for payment in trusted_payments { + if let Some(tx_metadata) = tx_metadata.get(&PaymentId::Trusted(payment.id)) { + match &tx_metadata.ty { + TxType::TrustedToLightning { + trusted_payment, + lightning_payment: _, + payment_triggering_transfer, + } => { + let entry = internal_transfers + .entry(*payment_triggering_transfer) + .or_insert(InternalTransfer::default()); + if payment.id == *trusted_payment { + debug_assert!(entry.send_fee.is_none()); + entry.send_fee = Some(payment.fee); + } else { + debug_assert!(false); + } + }, + TxType::OnchainToLightning { .. } => { + debug_assert!( + false, + "Onchain to lightning transfer should not be in trusted payments list" + ); + }, + TxType::PaymentTriggeringTransferLightning { ty } => { + let entry = internal_transfers + .entry(PaymentId::Trusted(payment.id)) + .or_insert(InternalTransfer::default()); + debug_assert!(entry.transaction.is_none()); + entry.transaction = Some(Transaction { + id: PaymentId::Trusted(payment.id), + status: payment.status, + outbound: payment.outbound, + amount: Some(payment.amount), + fee: Some(payment.fee), + payment_type: *ty, + time_since_epoch: tx_metadata.time, + }); + }, + TxType::Payment { ty } => { + debug_assert!(!matches!(ty, PaymentType::OutgoingOnChain { .. })); + debug_assert!(!matches!(ty, PaymentType::IncomingOnChain { .. })); + res.push(Transaction { + id: PaymentId::Trusted(payment.id), + status: payment.status, + outbound: payment.outbound, + amount: Some(payment.amount), + fee: Some(payment.fee), + payment_type: *ty, + time_since_epoch: tx_metadata.time, + }); + }, + TxType::PendingRebalance { + trusted_payment, + payment_triggering_transfer, + payment_hash, + } => { + if let Some(trusted_id) = trusted_payment { + debug_assert_eq!(*trusted_id, payment.id); + } + if payment.status == TxStatus::Completed { + if trusted_payment.is_some() + && payment_triggering_transfer.is_some() + && payment_hash.is_some() + { + let old_val = completed_internal_transfers.insert( + payment_hash.unwrap(), + CompletedInternalTransfer { + trusted_transfer_id: trusted_payment.unwrap(), + payment_triggering_transfer: + payment_triggering_transfer.unwrap(), + time: payment.time_since_epoch, + ln_transfer_id: None, + }, + ); + debug_assert!(old_val.is_none()); + } + } + // Pending rebalances are not shown in the transaction list. + continue; + }, + } + } else { + if payment.outbound { + log_warn!( + self.inner.logger, + "Missing outbound trusted payment metadata entry on {:?}", + payment.id + ); + #[cfg(feature = "_test-utils")] debug_assert!( false, - "Onchain to lightning transfer should not be in trusted payments list" + "Missing outbound trusted payment metadata entry on {:?}", + payment.id ); - }, - TxType::PaymentTriggeringTransferLightning { ty } => { - let entry = internal_transfers - .entry(PaymentId::Trusted(payment.id)) - .or_insert(InternalTransfer::default()); - debug_assert!(entry.transaction.is_none()); - entry.transaction = Some(Transaction { - id: PaymentId::Trusted(payment.id), - status: payment.status, - outbound: payment.outbound, - amount: Some(payment.amount), - fee: Some(payment.fee), - payment_type: *ty, - time_since_epoch: tx_metadata.time, - }); - }, - TxType::Payment { ty } => { - debug_assert!(!matches!(ty, PaymentType::OutgoingOnChain { .. })); - debug_assert!(!matches!(ty, PaymentType::IncomingOnChain { .. })); - res.push(Transaction { - id: PaymentId::Trusted(payment.id), - status: payment.status, - outbound: payment.outbound, - amount: Some(payment.amount), - fee: Some(payment.fee), - payment_type: *ty, - time_since_epoch: tx_metadata.time, - }); - }, - TxType::PendingRebalance { - trusted_payment, - payment_triggering_transfer, - payment_hash, - } => { - if let Some(trusted_id) = trusted_payment { - debug_assert_eq!(*trusted_id, payment.id); - } - if payment.status == TxStatus::Completed { - if trusted_payment.is_some() - && payment_triggering_transfer.is_some() - && payment_hash.is_some() - { - let old_val = completed_internal_transfers.insert( - payment_hash.unwrap(), - CompletedInternalTransfer { - trusted_transfer_id: trusted_payment.unwrap(), - payment_triggering_transfer: payment_triggering_transfer - .unwrap(), - time: payment.time_since_epoch, - ln_transfer_id: None, - }, - ); - debug_assert!(old_val.is_none()); - } - } - // Pending rebalances are not shown in the transaction list. - continue; - }, - } - } else { - if payment.outbound { - log_warn!( - self.inner.logger, - "Missing outbound trusted payment metadata entry on {:?}", - payment.id - ); - #[cfg(feature = "_test-utils")] - debug_assert!( - false, - "Missing outbound trusted payment metadata entry on {:?}", - payment.id - ); - } + } - if payment.status != TxStatus::Completed { - // We don't bother to surface pending inbound transactions (i.e. issued but - // unpaid invoices) in our transaction list. - continue; - } + if payment.status != TxStatus::Completed { + // We don't bother to surface pending inbound transactions (i.e. issued but + // unpaid invoices) in our transaction list. + continue; + } - let payment_type = if payment.outbound { - PaymentType::OutgoingLightningBolt11 { payment_preimage: None } - } else { - PaymentType::IncomingLightning {} - }; + let payment_type = if payment.outbound { + PaymentType::OutgoingLightningBolt11 { payment_preimage: None } + } else { + PaymentType::IncomingLightning {} + }; - res.push(Transaction { - id: PaymentId::Trusted(payment.id), - status: payment.status, - outbound: payment.outbound, - amount: Some(payment.amount), - fee: Some(payment.fee), - payment_type, - time_since_epoch: payment.time_since_epoch, - }); - } - } - for payment in lightning_payments { - use ldk_node::payment::PaymentDirection; - let lightning_receive_fee = match payment.kind { - PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => { - let msats = counterparty_skimmed_fee_msat.unwrap_or(0); - debug_assert_eq!(payment.direction, PaymentDirection::Inbound); - Some(Amount::from_milli_sats(msats).expect("Must be valid")) - }, - _ => None, - }; - let fee = if payment.direction == PaymentDirection::Outbound { - match payment.fee_paid_msat { - None => Some(lightning_receive_fee.unwrap_or(Amount::ZERO)), - Some(fee) => Some( - Amount::from_milli_sats(fee) - .unwrap() - .saturating_add(lightning_receive_fee.unwrap_or(Amount::ZERO)), - ), + res.push(Transaction { + id: PaymentId::Trusted(payment.id), + status: payment.status, + outbound: payment.outbound, + amount: Some(payment.amount), + fee: Some(payment.fee), + payment_type, + time_since_epoch: payment.time_since_epoch, + }); } - } else { - Some(lightning_receive_fee.unwrap_or(Amount::ZERO)) - }; - if let Some(tx_metadata) = tx_metadata.get(&PaymentId::SelfCustodial(payment.id.0)) { - match &tx_metadata.ty { - TxType::TrustedToLightning { - trusted_payment: _, - lightning_payment, - payment_triggering_transfer, - } => { - let entry = internal_transfers - .entry(*payment_triggering_transfer) - .or_insert(InternalTransfer::default()); - if payment.id.0 == *lightning_payment { - debug_assert!(entry.receive_fee.is_none()); - entry.receive_fee = lightning_receive_fee.or(Some(Amount::ZERO)); - } else { - debug_assert!(false); - } - }, - TxType::OnchainToLightning { channel_txid, triggering_txid } => { - let entry = internal_transfers - .entry(PaymentId::SelfCustodial(triggering_txid.to_byte_array())) - .or_insert(InternalTransfer::default()); - if &payment.id.0 == channel_txid.as_byte_array() { - debug_assert!(entry.send_fee.is_none()); - entry.send_fee = payment - .fee_paid_msat - .map(|fee| Amount::from_milli_sats(fee).expect("Must be valid")); - } else { - debug_assert!(false); - } + } + for payment in lightning_payments { + use ldk_node::payment::PaymentDirection; + let lightning_receive_fee = match payment.kind { + PaymentKind::Bolt11Jit { counterparty_skimmed_fee_msat, .. } => { + let msats = counterparty_skimmed_fee_msat.unwrap_or(0); + debug_assert_eq!(payment.direction, PaymentDirection::Inbound); + Some(Amount::from_milli_sats(msats).expect("Must be valid")) }, - TxType::PaymentTriggeringTransferLightning { ty: _ } => { - let entry = internal_transfers - .entry(PaymentId::SelfCustodial(payment.id.0)) - .or_insert(InternalTransfer { - receive_fee: lightning_receive_fee, - send_fee: None, - transaction: None, + _ => None, + }; + let fee = if payment.direction == PaymentDirection::Outbound { + match payment.fee_paid_msat { + None => Some(lightning_receive_fee.unwrap_or(Amount::ZERO)), + Some(fee) => Some( + Amount::from_milli_sats(fee) + .unwrap() + .saturating_add(lightning_receive_fee.unwrap_or(Amount::ZERO)), + ), + } + } else { + Some(lightning_receive_fee.unwrap_or(Amount::ZERO)) + }; + if let Some(tx_metadata) = tx_metadata.get(&PaymentId::SelfCustodial(payment.id.0)) + { + match &tx_metadata.ty { + TxType::TrustedToLightning { + trusted_payment: _, + lightning_payment, + payment_triggering_transfer, + } => { + let entry = internal_transfers + .entry(*payment_triggering_transfer) + .or_insert(InternalTransfer::default()); + if payment.id.0 == *lightning_payment { + debug_assert!(entry.receive_fee.is_none()); + entry.receive_fee = lightning_receive_fee.or(Some(Amount::ZERO)); + } else { + debug_assert!(false); + } + }, + TxType::OnchainToLightning { channel_txid, triggering_txid } => { + let entry = internal_transfers + .entry(PaymentId::SelfCustodial(triggering_txid.to_byte_array())) + .or_insert(InternalTransfer::default()); + if &payment.id.0 == channel_txid.as_byte_array() { + debug_assert!(entry.send_fee.is_none()); + entry.send_fee = payment.fee_paid_msat.map(|fee| { + Amount::from_milli_sats(fee).expect("Must be valid") + }); + } else { + debug_assert!(false); + } + }, + TxType::PaymentTriggeringTransferLightning { ty: _ } => { + let entry = internal_transfers + .entry(PaymentId::SelfCustodial(payment.id.0)) + .or_insert(InternalTransfer { + receive_fee: lightning_receive_fee, + send_fee: None, + transaction: None, + }); + debug_assert!(entry.transaction.is_none()); + + entry.transaction = Some(Transaction { + id: PaymentId::SelfCustodial(payment.id.0), + status: payment.status.into(), + outbound: payment.direction == PaymentDirection::Outbound, + amount: payment + .amount_msat + .map(|a| Amount::from_milli_sats(a).expect("Must be valid")), + fee, + payment_type: (&payment).into(), + time_since_epoch: tx_metadata.time, }); - debug_assert!(entry.transaction.is_none()); - - entry.transaction = Some(Transaction { + }, + TxType::Payment { ty: _ } => res.push(Transaction { id: PaymentId::SelfCustodial(payment.id.0), status: payment.status.into(), outbound: payment.direction == PaymentDirection::Outbound, @@ -880,68 +893,59 @@ impl Wallet { fee, payment_type: (&payment).into(), time_since_epoch: tx_metadata.time, - }); - }, - TxType::Payment { ty: _ } => res.push(Transaction { - id: PaymentId::SelfCustodial(payment.id.0), - status: payment.status.into(), - outbound: payment.direction == PaymentDirection::Outbound, - amount: payment - .amount_msat - .map(|a| Amount::from_milli_sats(a).expect("Must be valid")), - fee, - payment_type: (&payment).into(), - time_since_epoch: tx_metadata.time, - }), - TxType::PendingRebalance { .. } => { - // Pending rebalances are not shown in the transaction list. - continue; - }, - } - } else { - debug_assert_ne!( - payment.direction, - PaymentDirection::Outbound, - "Missing outbound lightning payment metadata entry on {}", - payment.id - ); + }), + TxType::PendingRebalance { .. } => { + // Pending rebalances are not shown in the transaction list. + continue; + }, + } + } else { + debug_assert_ne!( + payment.direction, + PaymentDirection::Outbound, + "Missing outbound lightning payment metadata entry on {}", + payment.id + ); - let status = payment.status.into(); - if status != TxStatus::Completed { - // We don't bother to surface pending inbound transactions (i.e. issued but - // unpaid invoices) in our transaction list, in part because these may be - // failed rebalances. - continue; - } + let status = payment.status.into(); + if status != TxStatus::Completed { + // We don't bother to surface pending inbound transactions (i.e. issued but + // unpaid invoices) in our transaction list, in part because these may be + // failed rebalances. + continue; + } - let payment_hash = match payment.kind { - PaymentKind::Onchain { .. } => None, - PaymentKind::Bolt11 { hash, .. } => Some(hash), - PaymentKind::Bolt11Jit { hash, .. } => Some(hash), - PaymentKind::Bolt12Offer { hash, .. } => hash, - PaymentKind::Bolt12Refund { hash, .. } => hash, - PaymentKind::Spontaneous { hash, .. } => Some(hash), - }; + let payment_hash = match payment.kind { + PaymentKind::Onchain { .. } => None, + PaymentKind::Bolt11 { hash, .. } => Some(hash), + PaymentKind::Bolt11Jit { hash, .. } => Some(hash), + PaymentKind::Bolt12Offer { hash, .. } => hash, + PaymentKind::Bolt12Refund { hash, .. } => hash, + PaymentKind::Spontaneous { hash, .. } => Some(hash), + }; - if let Some(info) = - payment_hash.map(|hash| completed_internal_transfers.get_mut(&hash)).flatten() - { - info.ln_transfer_id = Some(payment.id.0); - } else { - res.push(Transaction { - id: PaymentId::SelfCustodial(payment.id.0), - status, - outbound: payment.direction == PaymentDirection::Outbound, - amount: payment.amount_msat.map(|a| Amount::from_milli_sats(a).unwrap()), - fee, - payment_type: (&payment).into(), - time_since_epoch: Duration::from_secs(payment.latest_update_timestamp), - }) + if let Some(info) = payment_hash + .map(|hash| completed_internal_transfers.get_mut(&hash)) + .flatten() + { + info.ln_transfer_id = Some(payment.id.0); + } else { + res.push(Transaction { + id: PaymentId::SelfCustodial(payment.id.0), + status, + outbound: payment.direction == PaymentDirection::Outbound, + amount: payment + .amount_msat + .map(|a| Amount::from_milli_sats(a).unwrap()), + fee, + payment_type: (&payment).into(), + time_since_epoch: Duration::from_secs(payment.latest_update_timestamp), + }) + } } } } - std::mem::drop(tx_metadata); for (_, info) in completed_internal_transfers { debug_assert!(info.ln_transfer_id.is_some()); if let Some(lightning_payment) = info.ln_transfer_id { @@ -963,13 +967,16 @@ impl Wallet { self.inner .tx_metadata .set_tx_caused_rebalance(&info.payment_triggering_transfer) + .await .expect("Failed to write metadata for rebalance transaction"); self.inner .tx_metadata - .upsert(PaymentId::Trusted(info.trusted_transfer_id), metadata); + .upsert(PaymentId::Trusted(info.trusted_transfer_id), metadata) + .await; self.inner .tx_metadata - .insert(PaymentId::SelfCustodial(lightning_payment), metadata); + .insert(PaymentId::SelfCustodial(lightning_payment), metadata) + .await; } } diff --git a/orange-sdk/src/store.rs b/orange-sdk/src/store.rs index 7fd8f9c..373b55a 100644 --- a/orange-sdk/src/store.rs +++ b/orange-sdk/src/store.rs @@ -19,7 +19,7 @@ use ldk_node::bitcoin::hex::{DisplayHex, FromHex}; use ldk_node::lightning::io; use ldk_node::lightning::ln::msgs::DecodeError; use ldk_node::lightning::types::payment::{PaymentHash, PaymentPreimage}; -use ldk_node::lightning::util::persist::{KVStore, KVStoreSync}; +use ldk_node::lightning::util::persist::KVStore; use ldk_node::lightning::util::ser::{Readable, Writeable, Writer}; use ldk_node::lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum}; use ldk_node::payment::PaymentDetails;