Skip to content

Commit ab50d44

Browse files
committed
f - Make async offer readiness race-free
Return a completed future when an async receive offer is already ready. Drop redundant public wrappers now covered by lower-level APIs. Co-Authored-By: HAL 9000
1 parent 1bbfd6f commit ab50d44

5 files changed

Lines changed: 55 additions & 36 deletions

File tree

lightning/src/ln/async_payments_tests.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,32 @@ fn ignore_duplicate_invoice() {
10931093
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
10941094
}
10951095

1096+
#[test]
1097+
fn async_receive_offer_ready_future_completes_when_offer_already_ready() {
1098+
let chanmon_cfgs = create_chanmon_cfgs(3);
1099+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1100+
1101+
let mut allow_priv_chan_fwds_cfg = test_default_channel_config();
1102+
allow_priv_chan_fwds_cfg.accept_forwards_to_priv_channels = true;
1103+
let node_chanmgrs =
1104+
create_node_chanmgrs(3, &node_cfgs, &[None, Some(allow_priv_chan_fwds_cfg), None]);
1105+
1106+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1107+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1108+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1109+
1110+
let recipient_id = vec![42; 32];
1111+
let inv_server_paths =
1112+
nodes[1].node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
1113+
nodes[2].node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
1114+
expect_offer_paths_requests(&nodes[2], &[&nodes[0], &nodes[1]]);
1115+
1116+
pass_static_invoice_server_messages(&nodes[1], &nodes[2], recipient_id.clone());
1117+
1118+
assert!(nodes[2].node.get_async_receive_offer_ready_future().poll_is_complete());
1119+
assert!(nodes[2].node.get_async_receive_offer_ready_future().poll_is_complete());
1120+
}
1121+
10961122
#[test]
10971123
fn async_receive_flow_success() {
10981124
// Test that an always-online sender can successfully pay an async receiver.

lightning/src/ln/channelmanager.rs

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5911,16 +5911,6 @@ impl<
59115911
self.check_refresh_async_receive_offer_cache(false).unwrap();
59125912
}
59135913

5914-
/// Requests fresh async receive offer paths from the configured static invoice server, if any.
5915-
pub fn refresh_async_receive_offers(&self) -> Result<(), ()> {
5916-
self.check_refresh_async_receive_offer_cache(false).map_err(|()| {
5917-
log_error!(
5918-
self.logger,
5919-
"Failed to create blinded paths when requesting async receive offer paths"
5920-
);
5921-
})
5922-
}
5923-
59245914
/// Requests fresh async receive offer paths from the configured static invoice server, if any,
59255915
/// and attaches `payment_metadata` to the resulting BOLT 12 payment contexts.
59265916
///
@@ -5941,30 +5931,6 @@ impl<
59415931
})
59425932
}
59435933

5944-
/// Returns once an async receive offer is ready after the interactive static-invoice
5945-
/// protocol completes, or immediately if one is already available.
5946-
///
5947-
/// Callers that need a timeout can combine this future with their runtime's timeout
5948-
/// primitive.
5949-
#[cfg_attr(
5950-
feature = "std",
5951-
doc = "Synchronous callers should instead fetch the underlying [`Future`] via [`Self::get_async_receive_offer_ready_future`] and call [`Future::wait_timeout`] on it."
5952-
)]
5953-
///
5954-
/// [`Future`]: crate::util::wakers::Future
5955-
#[cfg_attr(
5956-
feature = "std",
5957-
doc = "[`Future::wait_timeout`]: crate::util::wakers::Future::wait_timeout"
5958-
)]
5959-
pub async fn await_async_receive_offer(&self) -> Result<Offer, ()> {
5960-
if let Ok(offer) = self.get_async_receive_offer() {
5961-
return Ok(offer);
5962-
}
5963-
5964-
self.flow.get_async_receive_offer_ready_future().await;
5965-
self.get_async_receive_offer()
5966-
}
5967-
59685934
/// Returns a [`Future`] that completes when an async receive offer is ready.
59695935
///
59705936
/// See [`OffersMessageFlow::get_async_receive_offer_ready_future`] for details.

lightning/src/offers/async_receive_offer_cache.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ impl AsyncReceiveOfferCache {
265265
.ok_or(())
266266
}
267267

268+
/// Returns whether [`Self::get_async_receive_offer`] would return an offer without marking an
269+
/// unused offer as used.
270+
pub(super) fn has_async_receive_offer(&self, duration_since_epoch: Duration) -> bool {
271+
self.offers_with_idx().any(|(_, offer)| {
272+
!offer.offer.is_expired_no_std(duration_since_epoch)
273+
&& matches!(offer.status, OfferStatus::Ready { .. } | OfferStatus::Used { .. })
274+
})
275+
}
276+
268277
/// Remove expired offers from the cache, returning the first slot number in the cache that needs
269278
/// a new offer, if any exist.
270279
pub(super) fn prune_expired_offers(

lightning/src/offers/flow.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,7 +1755,8 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
17551755
}
17561756

17571757
/// Returns a [`Future`] that completes when an async receive offer is ready, i.e., after the
1758-
/// interactive static-invoice protocol completes.
1758+
/// interactive static-invoice protocol completes. If an offer is already ready, the returned
1759+
/// [`Future`] will already be complete.
17591760
///
17601761
/// Callers can `.await` the returned [`Future`] in an async context.
17611762
#[cfg_attr(
@@ -1765,7 +1766,12 @@ impl<MR: MessageRouter, L: Logger> OffersMessageFlow<MR, L> {
17651766
///
17661767
/// After it completes, use [`Self::get_async_receive_offer`] to retrieve the offer.
17671768
pub fn get_async_receive_offer_ready_future(&self) -> Future {
1768-
self.async_receive_offer_ready_notifier.get_future()
1769+
let cache = self.async_receive_offer_cache.lock().unwrap();
1770+
if cache.has_async_receive_offer(self.duration_since_epoch()) {
1771+
Future::completed()
1772+
} else {
1773+
self.async_receive_offer_ready_notifier.get_future()
1774+
}
17691775
}
17701776

17711777
/// Get the encoded [`AsyncReceiveOfferCache`] for persistence.

lightning/src/util/wakers.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ pub struct Future {
162162
}
163163

164164
impl Future {
165+
pub(crate) fn completed() -> Self {
166+
let state = Arc::new(Mutex::new(FutureState {
167+
callbacks: Vec::new(),
168+
std_future_callbacks: Vec::new(),
169+
callbacks_with_state: Vec::new(),
170+
complete: true,
171+
callbacks_made: false,
172+
next_idx: 1,
173+
}));
174+
Future { state, self_idx: 0 }
175+
}
176+
165177
/// Registers a callback to be called upon completion of this future. If the future has already
166178
/// completed, the callback will be called immediately.
167179
///

0 commit comments

Comments
 (0)