Skip to content

Commit 57e6804

Browse files
benthecarmanclaude
andcommitted
Show backend settle time for trusted receives
A trusted receive is timestamped from the per-payment time the backend reports, but the rebalancer also records discovery-time metadata with SystemTime::now() the first time it observes a payment. list_transactions preferred that metadata time whenever it existed, so with rebalancing on the backend's settle time was always discarded. For a payment that settled while the wallet was offline, the discovery stamp is the next launch rather than the real settle time. Since history is sorted by timestamp, such a receive also floated to the top as if it were brand new. Prefer the backend's settle time for inbound receives, falling back to the metadata time only for outbound sends, which we always observe as they happen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent eea8178 commit 57e6804

2 files changed

Lines changed: 73 additions & 2 deletions

File tree

orange-sdk/src/lib.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,17 @@ impl Wallet {
844844
amount: Some(payment.amount),
845845
fee: Some(payment.fee),
846846
payment_type: *ty,
847-
time_since_epoch: tx_metadata.time,
847+
// Inbound receives use the backend's settle time. The rebalancer
848+
// stamps `tx_metadata.time` with `now()` when it first observes a
849+
// payment, so a receive that settled while we were offline would
850+
// otherwise be dated to the next launch and float to the top of
851+
// the time-sorted history. Outbound sends, which we observe as
852+
// they happen, keep the metadata time.
853+
time_since_epoch: if payment.outbound {
854+
tx_metadata.time
855+
} else {
856+
payment.time_since_epoch
857+
},
848858
});
849859
},
850860
TxType::PendingRebalance { .. } => {

orange-sdk/tests/integration_tests.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use ldk_node::payment::{ConfirmationStatus, PaymentDirection, PaymentStatus};
1111
use log::info;
1212
use orange_sdk::{Event, PaymentInfo, PaymentType, TxStatus, WalletError};
1313
use std::sync::Arc;
14-
use std::time::Duration;
14+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1515

1616
mod test_utils;
1717

@@ -85,6 +85,67 @@ async fn test_receive_to_trusted() {
8585
.await;
8686
}
8787

88+
#[tokio::test(flavor = "multi_thread")]
89+
#[test_log::test]
90+
async fn test_trusted_receive_keeps_backend_settle_time() {
91+
test_utils::run_test(|params| async move {
92+
let wallet = Arc::clone(&params.wallet);
93+
let third_party = Arc::clone(&params.third_party);
94+
95+
// Disable rebalancing before the payment exists so the rebalancer does not
96+
// stamp a discovery time when it first observes the receive.
97+
wallet.set_rebalance_enabled(false).await;
98+
99+
let recv_amt = Amount::from_sats(100).unwrap();
100+
assert!(recv_amt < wallet.get_tunables().trusted_balance_limit);
101+
102+
let uri = wallet.get_single_use_receive_uri(Some(recv_amt)).await.unwrap();
103+
assert!(uri.from_trusted);
104+
let payment_id = third_party.bolt11_payment().send(&uri.invoice, None).unwrap();
105+
106+
let p = Arc::clone(&third_party);
107+
test_utils::wait_for_condition("payer payment success", || {
108+
let res = p.payment(&payment_id).is_some_and(|p| p.status == PaymentStatus::Succeeded);
109+
async move { res }
110+
})
111+
.await;
112+
113+
test_utils::wait_for_condition("wallet balance update after receive", || async {
114+
wallet.get_balance().await.unwrap().available_balance() > Amount::ZERO
115+
})
116+
.await;
117+
118+
// The backend has recorded the settle time ~now. Sleep so any later
119+
// discovery stamp lands a clearly later wall-clock time, then mark that
120+
// boundary just before we let the rebalancer run.
121+
tokio::time::sleep(Duration::from_secs(3)).await;
122+
let enabled_at = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
123+
124+
// Re-enable rebalancing and drain the PaymentReceived event. Handling the
125+
// event triggers the rebalancer, which observes the payment for the first
126+
// time and stamps its discovery time (>= enabled_at). Give the spawned
127+
// rebalance check a moment to record it.
128+
wallet.set_rebalance_enabled(true).await;
129+
let event = test_utils::wait_next_event(&wallet).await;
130+
assert!(matches!(event, Event::PaymentReceived { .. }));
131+
tokio::time::sleep(Duration::from_secs(3)).await;
132+
133+
let txs = wallet.list_transactions().await.unwrap();
134+
assert_eq!(txs.len(), 1);
135+
let tx = txs.into_iter().next().unwrap();
136+
137+
// Must be the backend settle time (recorded ~3s before we re-enabled),
138+
// strictly earlier than the discovery stamp written at/after `enabled_at`.
139+
assert!(
140+
tx.time_since_epoch < enabled_at,
141+
"expected backend settle time, got discovery stamp: {:?} >= {:?}",
142+
tx.time_since_epoch,
143+
enabled_at
144+
);
145+
})
146+
.await;
147+
}
148+
88149
#[tokio::test(flavor = "multi_thread")]
89150
#[test_log::test]
90151
async fn test_pay_from_trusted() {

0 commit comments

Comments
 (0)