diff --git a/src/payment/store.rs b/src/payment/store.rs index 160890895..cf7916645 100644 --- a/src/payment/store.rs +++ b/src/payment/store.rs @@ -288,7 +288,9 @@ impl StorableObject for PaymentDetails { if let Some(tx_type_update) = update.tx_type { match self.kind { PaymentKind::Onchain { ref mut tx_type, .. } => { - update_if_necessary!(*tx_type, tx_type_update); + if tx_type.is_none() || tx_type_update.is_some() { + update_if_necessary!(*tx_type, tx_type_update); + } }, _ => {}, } @@ -921,6 +923,103 @@ mod tests { assert_eq!(kind, PaymentKind::read(&mut &*kind.encode()).unwrap()); } + #[test] + fn known_onchain_tx_type_survives_unknown_update() { + use bitcoin::hashes::Hash; + use std::str::FromStr; + + let txid = Txid::from_byte_array([8u8; 32]); + let payment_id = PaymentId(txid.to_byte_array()); + let pubkey = PublicKey::from_str( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) + .unwrap(); + let tx_type = TransactionType::CooperativeClose { + counterparty_node_id: pubkey, + channel_id: ChannelId([4u8; 32]), + }; + let mut classified = PaymentDetails::new( + payment_id, + PaymentKind::Onchain { + txid, + status: ConfirmationStatus::Unconfirmed, + tx_type: Some(tx_type.clone()), + }, + Some(1_000), + Some(100), + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + let wallet_sync_update = PaymentDetails::new( + payment_id, + PaymentKind::Onchain { + txid, + status: ConfirmationStatus::Confirmed { + block_hash: BlockHash::from_byte_array([9u8; 32]), + height: 42, + timestamp: 123, + }, + tx_type: None, + }, + Some(1_000), + Some(100), + PaymentDirection::Inbound, + PaymentStatus::Pending, + ); + + assert!(classified.update(PaymentDetailsUpdate::from(&wallet_sync_update))); + match classified.kind { + PaymentKind::Onchain { status, tx_type: Some(updated_tx_type), .. } => { + assert!(matches!(status, ConfirmationStatus::Confirmed { height: 42, .. })); + assert_eq!(updated_tx_type, tx_type); + }, + other => panic!("Unexpected payment kind: {:?}", other), + } + } + + #[test] + fn transaction_type_from_ldk_variants() { + use std::str::FromStr; + + let pubkey = PublicKey::from_str( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) + .unwrap(); + let channel_id = ChannelId([5u8; 32]); + let channel = Channel { counterparty_node_id: pubkey, channel_id }; + + let variants = vec![ + ( + LdkTransactionType::Funding { channels: vec![(pubkey, channel_id)] }, + TransactionType::Funding { channels: vec![channel.clone()] }, + ), + ( + LdkTransactionType::CooperativeClose { counterparty_node_id: pubkey, channel_id }, + TransactionType::CooperativeClose { counterparty_node_id: pubkey, channel_id }, + ), + ( + LdkTransactionType::UnilateralClose { counterparty_node_id: pubkey, channel_id }, + TransactionType::UnilateralClose { counterparty_node_id: pubkey, channel_id }, + ), + ( + LdkTransactionType::AnchorBump { counterparty_node_id: pubkey, channel_id }, + TransactionType::AnchorBump { counterparty_node_id: pubkey, channel_id }, + ), + ( + LdkTransactionType::Claim { counterparty_node_id: pubkey, channel_id }, + TransactionType::Claim { counterparty_node_id: pubkey, channel_id }, + ), + ( + LdkTransactionType::Sweep { channels: vec![(pubkey, channel_id)] }, + TransactionType::Sweep { channels: vec![channel] }, + ), + ]; + + for (ldk_type, expected_type) in variants { + assert_eq!(TransactionType::from(ldk_type), expected_type); + } + } + #[derive(Clone, Debug, PartialEq, Eq)] struct LegacyBolt11JitKind { hash: PaymentHash, diff --git a/src/tx_broadcaster.rs b/src/tx_broadcaster.rs index 5722a3ebe..ccf2298b0 100644 --- a/src/tx_broadcaster.rs +++ b/src/tx_broadcaster.rs @@ -9,7 +9,9 @@ use std::ops::Deref; use std::sync::{Mutex as StdMutex, Weak}; use bitcoin::Transaction; -use lightning::chain::chaininterface::{BroadcasterInterface, TransactionType}; +use lightning::chain::chaininterface::{ + BroadcasterInterface, TransactionType as LdkTransactionType, +}; use tokio::sync::{mpsc, Mutex, MutexGuard}; use crate::logger::{log_error, LdkLogger}; @@ -22,16 +24,21 @@ const BCAST_PACKAGE_QUEUE_SIZE: usize = 50; /// call, along with each transaction's type. Queued until the background task classifies and /// broadcasts it. Built only via [`BroadcastPackage::new`] from such a call, so unrelated /// transactions can't be grouped into one package by accident. -pub(crate) struct BroadcastPackage(Vec<(Transaction, TransactionType)>); +pub(crate) struct BroadcastPackage(Vec<(Transaction, Option)>); impl BroadcastPackage { /// Builds a package from the transactions of a single `broadcast_transactions` call. - fn new(txs: &[(&Transaction, TransactionType)]) -> Self { - Self(txs.iter().map(|(tx, tx_type)| ((*tx).clone(), tx_type.clone())).collect()) + fn new(txs: &[(&Transaction, LdkTransactionType)]) -> Self { + Self(txs.iter().map(|(tx, tx_type)| ((*tx).clone(), Some(tx_type.clone()))).collect()) + } + + /// Builds a package for wallet-originated broadcasts that have no LDK classification. + fn unclassified(txs: Vec) -> Self { + Self(txs.into_iter().map(|tx| (tx, None)).collect()) } /// The packaged transactions and their types, for classification. - fn transactions(&self) -> &[(Transaction, TransactionType)] { + fn transactions(&self) -> &[(Transaction, Option)] { &self.0 } @@ -91,18 +98,26 @@ where let wallet_opt = self.wallet.lock().expect("lock").as_ref().and_then(Weak::upgrade); if let Some(wallet) = wallet_opt { for (tx, tx_type) in package.transactions() { - wallet.classify_broadcast(tx, tx_type).await?; + if let Some(tx_type) = tx_type { + wallet.classify_broadcast(tx, tx_type).await?; + } } } Ok(package) } + + pub(crate) fn broadcast_unclassified_transactions(&self, txs: Vec) { + self.queue_sender.try_send(BroadcastPackage::unclassified(txs)).unwrap_or_else(|e| { + log_error!(self.logger, "Failed to broadcast transactions: {}", e); + }); + } } impl BroadcasterInterface for TransactionBroadcaster where L::Target: LdkLogger, { - fn broadcast_transactions(&self, txs: &[(&Transaction, TransactionType)]) { + fn broadcast_transactions(&self, txs: &[(&Transaction, LdkTransactionType)]) { self.queue_sender.try_send(BroadcastPackage::new(txs)).unwrap_or_else(|e| { log_error!(self.logger, "Failed to broadcast transactions: {}", e); }); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index ad4f8d45e..f6b72df4e 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -32,7 +32,7 @@ use bitcoin::{ WPubkeyHash, Weight, WitnessProgram, WitnessVersion, }; use lightning::chain::chaininterface::{ - BroadcasterInterface, FundingCandidate, TransactionType as LdkTransactionType, + FundingCandidate, TransactionType as LdkTransactionType, INCREMENTAL_RELAY_FEE_SAT_PER_1000_WEIGHT, }; use lightning::chain::channelmonitor::ANTI_REORG_DELAY; @@ -335,21 +335,12 @@ impl Wallet { .collect(); if !txs_to_broadcast.is_empty() { - let tx_refs: Vec<( - &Transaction, - lightning::chain::chaininterface::TransactionType, - )> = - txs_to_broadcast - .iter() - .map(|tx| { - (tx, lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }) - }) - .collect(); - self.broadcaster.broadcast_transactions(&tx_refs); + let tx_count = txs_to_broadcast.len(); + self.broadcaster.broadcast_unclassified_transactions(txs_to_broadcast); log_info!( self.logger, "Rebroadcast {} unconfirmed transactions on chain tip change", - txs_to_broadcast.len() + tx_count ); } } @@ -889,12 +880,8 @@ impl Wallet { })? }; - self.broadcaster.broadcast_transactions(&[( - &tx, - lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }, - )]); - let txid = tx.compute_txid(); + self.broadcaster.broadcast_unclassified_transactions(vec![tx]); match send_amount { OnchainSendAmount::ExactRetainingReserve { amount_sats, .. } => { @@ -1184,9 +1171,8 @@ impl Wallet { Ok(tx) } - /// Classifies a funding broadcast (channel open or splice) handed to the broadcaster by LDK, - /// recording a payment for it before it is sent. Other transaction types are left for wallet - /// sync to record normally. + /// Classifies an on-chain broadcast handed to the broadcaster by LDK, recording a payment for it + /// before it is sent when it affects this node's wallet. pub(crate) async fn classify_broadcast( &self, tx: &Transaction, tx_type: &LdkTransactionType, ) -> Result<(), Error> { @@ -1197,7 +1183,13 @@ impl Wallet { LdkTransactionType::InteractiveFunding { candidates } => { self.classify_interactive_funding(tx, candidates, tx_type.clone().into()).await }, - _ => Ok(()), + LdkTransactionType::CooperativeClose { .. } + | LdkTransactionType::UnilateralClose { .. } + | LdkTransactionType::AnchorBump { .. } + | LdkTransactionType::Claim { .. } + | LdkTransactionType::Sweep { .. } => { + self.classify_regular_broadcast(tx, tx_type.clone().into()).await + }, } } @@ -1338,6 +1330,40 @@ impl Wallet { Ok(()) } + /// Records a non-funding LDK broadcast as an on-chain payment, tagged with its transaction type. + /// Wallet sync later refreshes confirmation status while preserving the type. + async fn classify_regular_broadcast( + &self, tx: &Transaction, tx_type: TransactionType, + ) -> Result<(), Error> { + let txid = tx.compute_txid(); + let (amount_msat, fee_paid_msat, direction) = self.onchain_payment_fields(tx); + + if amount_msat == Some(0) && fee_paid_msat == Some(0) { + log_trace!( + self.logger, + "Not recording classified broadcast {} as a payment: no wallet-level activity", + txid, + ); + return Ok(()); + } + + let details = PaymentDetails::new( + PaymentId(txid.to_byte_array()), + PaymentKind::Onchain { + txid, + status: ConfirmationStatus::Unconfirmed, + tx_type: Some(tx_type), + }, + amount_msat, + fee_paid_msat, + direction, + PaymentStatus::Pending, + ); + self.payment_store.insert_or_update(details).await?; + log_debug!(self.logger, "Recorded classified on-chain broadcast {}", txid); + Ok(()) + } + /// Writes a freshly-classified funding payment to the authoritative payment store and adds a /// pending-store index entry, so wallet sync graduates it through `ANTI_REORG_DELAY`. async fn persist_funding_payment( @@ -1719,11 +1745,6 @@ impl Wallet { let new_txid = fee_bumped_tx.compute_txid(); - self.broadcaster.broadcast_transactions(&[( - &fee_bumped_tx, - lightning::chain::chaininterface::TransactionType::Sweep { channels: vec![] }, - )]); - let new_payment = self.create_payment_from_tx( &locked_wallet, new_txid, @@ -1733,6 +1754,8 @@ impl Wallet { ConfirmationStatus::Unconfirmed, ); + self.broadcaster.broadcast_unclassified_transactions(vec![fee_bumped_tx]); + let pending_payment_store = self.create_pending_payment_from_tx(new_payment.clone(), Vec::new()); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 68ace9179..9c4be0239 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -30,6 +30,7 @@ use std::time::Duration; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; +use bitcoin::secp256k1::PublicKey; use bitcoin::{ Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, Txid, Witness, }; @@ -42,7 +43,7 @@ use ldk_node::config::{ }; use ldk_node::entropy::{generate_entropy_mnemonic, NodeEntropy}; use ldk_node::io::sqlite_store::SqliteStore; -use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus}; +use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus, TransactionType}; use ldk_node::{ Builder, ChannelShutdownState, CustomTlvRecord, Event, LightningBalance, Node, NodeError, PendingSweepBalance, UserChannelId, @@ -407,6 +408,116 @@ type TestNode = Arc; #[cfg(not(feature = "uniffi"))] type TestNode = Node; +fn has_onchain_tx_type bool>(node: &TestNode, predicate: F) -> bool { + node.list_payments().into_iter().any(|payment| { + matches!( + payment.kind, + PaymentKind::Onchain { tx_type: Some(ref tx_type), .. } if predicate(tx_type) + ) + }) +} + +fn assert_any_node_has_onchain_tx_type bool + Copy>( + nodes: &[(&str, &TestNode)], tx_type_name: &str, predicate: F, +) { + if nodes.iter().any(|(_, node)| has_onchain_tx_type(node, predicate)) { + return; + } + + let observed: Vec = nodes + .iter() + .flat_map(|(name, node)| { + node.list_payments().into_iter().filter_map(move |payment| match payment.kind { + PaymentKind::Onchain { tx_type, .. } => Some(format!("{}:{:?}", name, tx_type)), + _ => None, + }) + }) + .collect(); + panic!("Expected on-chain payment with tx_type {}; observed {:?}", tx_type_name, observed); +} + +fn assert_all_nodes_have_onchain_tx_type bool + Copy>( + nodes: &[(&str, &TestNode)], tx_type_name: &str, predicate: F, +) { + if nodes.iter().all(|(_, node)| has_onchain_tx_type(node, predicate)) { + return; + } + + let observed: Vec = nodes + .iter() + .flat_map(|(name, node)| { + node.list_payments().into_iter().filter_map(move |payment| match payment.kind { + PaymentKind::Onchain { tx_type, .. } => Some(format!("{}:{:?}", name, tx_type)), + _ => None, + }) + }) + .collect(); + panic!( + "Expected all nodes to have on-chain payment with tx_type {}; observed {:?}", + tx_type_name, observed + ); +} + +async fn settle_force_close_balance( + node: &TestNode, counterparty_node_id: PublicKey, peer_node: &TestNode, + bitcoind: &BitcoindClient, electrsd: &E, +) { + let balances = node.list_balances(); + if balances.lightning_balances.len() == 1 { + match balances.lightning_balances[0] { + LightningBalance::ClaimableAwaitingConfirmations { + counterparty_node_id: actual_counterparty_node_id, + confirmation_height, + .. + } => { + assert_eq!(actual_counterparty_node_id, counterparty_node_id); + let cur_height = node.status().current_best_block.height; + let blocks_to_go = confirmation_height - cur_height; + generate_blocks_and_wait(bitcoind, electrsd, blocks_to_go as usize).await; + node.sync_wallets().unwrap(); + peer_node.sync_wallets().unwrap(); + }, + _ => panic!("Unexpected balance state!"), + } + } else { + assert!(balances.lightning_balances.is_empty(), "Unexpected balance state: {:?}", balances); + assert_eq!(balances.pending_balances_from_channel_closures.len(), 1); + } + + for _ in 0..6 { + if node.list_balances().lightning_balances.is_empty() { + break; + } + generate_blocks_and_wait(bitcoind, electrsd, 1).await; + node.sync_wallets().unwrap(); + peer_node.sync_wallets().unwrap(); + } + + let balances = node.list_balances(); + assert!(balances.lightning_balances.is_empty(), "Unexpected balance state: {:?}", balances); + assert_eq!(balances.pending_balances_from_channel_closures.len(), 1); + match balances.pending_balances_from_channel_closures[0] { + PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => { + generate_blocks_and_wait(bitcoind, electrsd, 1).await; + node.sync_wallets().unwrap(); + peer_node.sync_wallets().unwrap(); + + assert!(node.list_balances().lightning_balances.is_empty()); + assert_eq!(node.list_balances().pending_balances_from_channel_closures.len(), 1); + match node.list_balances().pending_balances_from_channel_closures[0] { + PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, + _ => panic!("Unexpected balance state!"), + } + }, + PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, + _ => panic!("Unexpected balance state!"), + } + + generate_blocks_and_wait(bitcoind, electrsd, 5).await; + node.sync_wallets().unwrap(); + peer_node.sync_wallets().unwrap(); +} + #[derive(Clone)] pub(crate) enum TestChainSource<'a> { Esplora(&'a ElectrsD), @@ -1474,84 +1585,11 @@ pub(crate) async fn do_channel_full_cycle( node_b.sync_wallets().unwrap(); if force_close { - // Check node_b properly sees all balances and sweeps them. - assert_eq!(node_b.list_balances().lightning_balances.len(), 1); - match node_b.list_balances().lightning_balances[0] { - LightningBalance::ClaimableAwaitingConfirmations { - counterparty_node_id, - confirmation_height, - .. - } => { - assert_eq!(counterparty_node_id, node_a.node_id()); - let cur_height = node_b.status().current_best_block.height; - let blocks_to_go = confirmation_height - cur_height; - generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); - }, - _ => panic!("Unexpected balance state!"), - } - - assert!(node_b.list_balances().lightning_balances.is_empty()); - assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_b.list_balances().pending_balances_from_channel_closures[0] { - PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => {}, - _ => panic!("Unexpected balance state!"), - } - generate_blocks_and_wait(&bitcoind, electrsd, 1).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); - + settle_force_close_balance(&node_b, node_a.node_id(), &node_a, &bitcoind, electrsd).await; assert!(node_b.list_balances().lightning_balances.is_empty()); assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_b.list_balances().pending_balances_from_channel_closures[0] { - PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, - _ => panic!("Unexpected balance state!"), - } - generate_blocks_and_wait(&bitcoind, electrsd, 5).await; - node_b.sync_wallets().unwrap(); - node_a.sync_wallets().unwrap(); - - assert!(node_b.list_balances().lightning_balances.is_empty()); - assert_eq!(node_b.list_balances().pending_balances_from_channel_closures.len(), 1); - - // Check node_a properly sees all balances and sweeps them. - assert_eq!(node_a.list_balances().lightning_balances.len(), 1); - match node_a.list_balances().lightning_balances[0] { - LightningBalance::ClaimableAwaitingConfirmations { - counterparty_node_id, - confirmation_height, - .. - } => { - assert_eq!(counterparty_node_id, node_b.node_id()); - let cur_height = node_a.status().current_best_block.height; - let blocks_to_go = confirmation_height - cur_height; - generate_blocks_and_wait(&bitcoind, electrsd, blocks_to_go as usize).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); - }, - _ => panic!("Unexpected balance state!"), - } - - assert!(node_a.list_balances().lightning_balances.is_empty()); - assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_a.list_balances().pending_balances_from_channel_closures[0] { - PendingSweepBalance::BroadcastAwaitingConfirmation { .. } => {}, - _ => panic!("Unexpected balance state!"), - } - generate_blocks_and_wait(&bitcoind, electrsd, 1).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); - assert!(node_a.list_balances().lightning_balances.is_empty()); - assert_eq!(node_a.list_balances().pending_balances_from_channel_closures.len(), 1); - match node_a.list_balances().pending_balances_from_channel_closures[0] { - PendingSweepBalance::AwaitingThresholdConfirmations { .. } => {}, - _ => panic!("Unexpected balance state!"), - } - generate_blocks_and_wait(&bitcoind, electrsd, 5).await; - node_a.sync_wallets().unwrap(); - node_b.sync_wallets().unwrap(); + settle_force_close_balance(&node_a, node_b.node_id(), &node_b, &bitcoind, electrsd).await; } else { assert_eq!(node_a.list_balances().lightning_balances.len(), 1); assert!(node_a.list_balances().pending_balances_from_channel_closures.is_empty()); @@ -1597,6 +1635,25 @@ pub(crate) async fn do_channel_full_cycle( assert!(node_b.list_balances().pending_balances_from_channel_closures.is_empty()); } + if force_close { + assert_any_node_has_onchain_tx_type( + &[("node_a", &node_a), ("node_b", &node_b)], + "UnilateralClose", + |tx_type| matches!(tx_type, TransactionType::UnilateralClose { .. }), + ); + assert_any_node_has_onchain_tx_type( + &[("node_a", &node_a), ("node_b", &node_b)], + "Sweep", + |tx_type| matches!(tx_type, TransactionType::Sweep { .. }), + ); + } else { + assert_all_nodes_have_onchain_tx_type( + &[("node_a", &node_a), ("node_b", &node_b)], + "CooperativeClose", + |tx_type| matches!(tx_type, TransactionType::CooperativeClose { .. }), + ); + } + let sum_of_all_payments_sat = (push_msat + invoice_amount_1_msat + overpaid_amount_msat @@ -1619,20 +1676,20 @@ pub(crate) async fn do_channel_full_cycle( 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(), - 3 - ); - assert_eq!( - node_b - .list_payments_with_filter(|p| p.direction == PaymentDirection::Inbound - && matches!(p.kind, PaymentKind::Onchain { .. })) - .len(), - 2 - ); + let node_a_inbound_onchain_count = node_a + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound + && matches!(p.kind, PaymentKind::Onchain { .. }) + }) + .len(); + let node_b_inbound_onchain_count = node_b + .list_payments_with_filter(|p| { + p.direction == PaymentDirection::Inbound + && matches!(p.kind, PaymentKind::Onchain { .. }) + }) + .len(); + assert!(node_a_inbound_onchain_count >= 3); + assert!(node_b_inbound_onchain_count >= 2); // Check we handled all events assert_eq!(node_a.next_event(), None); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 41028b662..d87542b03 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -599,17 +599,19 @@ async fn onchain_send_receive() { let payment_a = node_a.payment(&payment_id).unwrap(); assert_eq!(payment_a.status, PaymentStatus::Pending); match payment_a.kind { - PaymentKind::Onchain { status, .. } => { + PaymentKind::Onchain { status, tx_type, .. } => { assert!(matches!(status, ConfirmationStatus::Unconfirmed)); + assert_eq!(tx_type, None); }, _ => panic!("Unexpected payment kind"), } assert!(payment_a.fee_paid_msat > Some(0)); let payment_b = node_b.payment(&payment_id).unwrap(); assert_eq!(payment_b.status, PaymentStatus::Pending); - match payment_a.kind { - PaymentKind::Onchain { status, .. } => { + match payment_b.kind { + PaymentKind::Onchain { status, tx_type, .. } => { assert!(matches!(status, ConfirmationStatus::Unconfirmed)); + assert_eq!(tx_type, None); }, _ => panic!("Unexpected payment kind"), } @@ -638,18 +640,20 @@ async fn onchain_send_receive() { let payment_a = node_a.payment(&payment_id).unwrap(); match payment_a.kind { - PaymentKind::Onchain { txid: _txid, status, .. } => { + PaymentKind::Onchain { txid: _txid, status, tx_type } => { assert_eq!(_txid, txid); assert!(matches!(status, ConfirmationStatus::Confirmed { .. })); + assert_eq!(tx_type, None); }, _ => panic!("Unexpected payment kind"), } - let payment_b = node_a.payment(&payment_id).unwrap(); + let payment_b = node_b.payment(&payment_id).unwrap(); match payment_b.kind { - PaymentKind::Onchain { txid: _txid, status, .. } => { + PaymentKind::Onchain { txid: _txid, status, tx_type } => { assert_eq!(_txid, txid); assert!(matches!(status, ConfirmationStatus::Confirmed { .. })); + assert_eq!(tx_type, None); }, _ => panic!("Unexpected payment kind"), }