Poll for resolved `UtxoFuture`s rather than resolving on the graph
What changed, and why it matters
This commit refactors how Lightning Dev Kit handles asynchronous checks of Bitcoin transaction outputs (UTXOs) used to validate gossip messages about the network graph. Previously, resolving these checks required direct circular references between components, which could cause memory leaks when LDK was unloaded. The new design uses a notification/polling pattern instead, making the code cleaner and avoiding leaked objects. It is a defensive architectural fix rather than a patch for an active exploit.
Treat as a routine hardening/refactoring commit. No immediate security response required, but downstream users should update to avoid the memory-leak-prone circular-reference design. Review follow-up commit mentioned in message for removal of manual wakeups.
Security signals we found
Eliminates circular Arc references between P2PGossipSync, GossipVerifier, and PeerManager
Prevents NetworkGraph memory leaks on LDK unload
Refactors async UTXO validation from push/callback to poll/notify pattern
Removes public resolve_without_forwarding and parameterized resolve APIs in favor of simpler resolve
Adds process_completed_checks polling in get_and_clear_pending_msg_events
Evidence from the diff
The change moves UtxoFuture resolution from a callback-style model that required P2PGossipSync -> GossipVerifier -> P2PGossipSync and PeerManager -> P2PGossipSync -> GossipVerifier -> PeerManager circular Arc references to a poll-based model. UtxoFuture::resolve now only stores the result and signals a Notifier. P2PGossipSync::get_and_clear_pending_msg_events polls PendingChecks::check_resolved_futures to convert completed futures into MessageSendEvent forwarding entries. This removes the need for UtxoFuture to hold references to NetworkGraph/P2PGossipSync during resolution and eliminates the lookup_completed cleanup path. The commit message explicitly frames this as fixing a design that leaves NetworkGraphs to leak if LDK is unloaded.
Changed components
lightning/src/routing/utxo.rslightning/src/routing/gossip.rslightning-block-sync/src/gossip.rsfuzz/src/router.rsInspect captured patch +204 / −220
diff --git a/fuzz/src/router.rs b/fuzz/src/router.rs
index e6508d0..2e5b15f 100644
--- a/fuzz/src/router.rs
+++ b/fuzz/src/router.rs
@@ -89,11 +89,10 @@ impl InputData {
}
}
-struct FuzzChainSource<'a, 'b, Out: test_logger::Output> {
+struct FuzzChainSource {
input: Arc<InputData>,
- net_graph: &'a NetworkGraph<&'b test_logger::TestLogger<Out>>,
}
-impl<Out: test_logger::Output> UtxoLookup for FuzzChainSource<'_, '_, Out> {
+impl UtxoLookup for FuzzChainSource {
fn get_utxo(&self, _chain_hash: &ChainHash, _scid: u64, notifier: Arc<Notifier>) -> UtxoResult {
let input_slice = self.input.get_slice(2);
if input_slice.is_none() {
@@ -109,12 +108,12 @@ impl<Out: test_logger::Output> UtxoLookup for FuzzChainSource<'_, '_, Out> {
&[1, _] => UtxoResult::Sync(Err(UtxoLookupError::UnknownTx)),
&[2, _] => {
let future = UtxoFuture::new(notifier);
- future.resolve_without_forwarding(self.net_graph, Ok(txo_res));
+ future.resolve(Ok(txo_res));
UtxoResult::Async(future.clone())
},
&[3, _] => {
let future = UtxoFuture::new(notifier);
- future.resolve_without_forwarding(self.net_graph, Err(UtxoLookupError::UnknownTx));
+ future.resolve(Err(UtxoLookupError::UnknownTx));
UtxoResult::Async(future.clone())
},
&[4, _] => {
@@ -198,7 +197,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
let our_pubkey = get_pubkey!();
let net_graph = NetworkGraph::new(Network::Bitcoin, &logger);
- let chain_source = FuzzChainSource { input: Arc::clone(&input), net_graph: &net_graph };
+ let chain_source = FuzzChainSource { input: Arc::clone(&input) };
let mut node_pks = new_hash_map();
let mut scid = 42;
@@ -336,9 +335,7 @@ pub fn do_test<Out: test_logger::Output>(data: &[u8], out: Out) {
node_pks.insert(get_pubkey_from_node_id!(msg.node_id_1), ());
node_pks.insert(get_pubkey_from_node_id!(msg.node_id_2), ());
let _ = net_graph
- .update_channel_from_unsigned_announcement::<&FuzzChainSource<'_, '_, Out>>(
- &msg, &None,
- );
+ .update_channel_from_unsigned_announcement::<&FuzzChainSource>(&msg, &None);
},
2 => {
let msg =
diff --git a/lightning-block-sync/src/gossip.rs b/lightning-block-sync/src/gossip.rs
index 2c5dadf..63045b6 100644
--- a/lightning-block-sync/src/gossip.rs
+++ b/lightning-block-sync/src/gossip.rs
@@ -283,7 +283,7 @@ where
let pmw = Arc::clone(&self.peer_manager_wake);
self.spawn.spawn(async move {
let res = Self::retrieve_utxo(source, block_cache, scid).await;
- fut.resolve(gossiper.network_graph(), &*gossiper, res);
+ fut.resolve(res);
(pmw)();
});
UtxoResult::Async(res)
diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs
index ae317ad..46ca332 100644
--- a/lightning/src/routing/gossip.rs
+++ b/lightning/src/routing/gossip.rs
@@ -378,39 +378,42 @@ where
}
}
- /// Used to broadcast forward gossip messages which were validated async.
- ///
- /// Note that this will ignore events other than `Broadcast*` or messages with too much excess
- /// data.
- pub(super) fn forward_gossip_msg(&self, mut ev: MessageSendEvent) {
- match &mut ev {
- MessageSendEvent::BroadcastChannelAnnouncement { msg, ref mut update_msg } => {
- if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY {
- return;
- }
- if update_msg.as_ref().map(|msg| msg.contents.excess_data.len()).unwrap_or(0)
- > MAX_EXCESS_BYTES_FOR_RELAY
- {
- *update_msg = None;
- }
- },
- MessageSendEvent::BroadcastChannelUpdate { msg, .. } => {
- if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY {
- return;
- }
- },
- MessageSendEvent::BroadcastNodeAnnouncement { msg } => {
- if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY
- || msg.contents.excess_address_data.len() > MAX_EXCESS_BYTES_FOR_RELAY
- || msg.contents.excess_data.len() + msg.contents.excess_address_data.len()
+ /// Walks the list of pending UTXO validations and removes completed ones, adding any messages
+ /// we should forward as a result to [`Self::pending_events`].
+ fn process_completed_checks(&self) {
+ let msgs = self.network_graph.pending_checks.check_resolved_futures(&*self.network_graph);
+ let mut pending_events = self.pending_events.lock().unwrap();
+ pending_events.reserve(msgs.len());
+ for mut message in msgs {
+ match &mut message {
+ MessageSendEvent::BroadcastChannelAnnouncement { msg, ref mut update_msg } => {
+ if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY {
+ continue;
+ }
+ if update_msg.as_ref().map(|msg| msg.contents.excess_data.len()).unwrap_or(0)
> MAX_EXCESS_BYTES_FOR_RELAY
- {
- return;
- }
- },
- _ => return,
+ {
+ *update_msg = None;
+ }
+ },
+ MessageSendEvent::BroadcastChannelUpdate { msg, .. } => {
+ if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY {
+ continue;
+ }
+ },
+ MessageSendEvent::BroadcastNodeAnnouncement { msg } => {
+ if msg.contents.excess_data.len() > MAX_EXCESS_BYTES_FOR_RELAY
+ || msg.contents.excess_address_data.len() > MAX_EXCESS_BYTES_FOR_RELAY
+ || msg.contents.excess_data.len() + msg.contents.excess_address_data.len()
+ > MAX_EXCESS_BYTES_FOR_RELAY
+ {
+ continue;
+ }
+ },
+ _ => continue,
+ }
+ pending_events.push(message);
}
- self.pending_events.lock().unwrap().push(ev);
}
}
@@ -884,6 +887,7 @@ where
}
fn get_and_clear_pending_msg_events(&self) -> Vec<MessageSendEvent> {
+ self.process_completed_checks();
let mut ret = Vec::new();
let mut pending_events = self.pending_events.lock().unwrap();
core::mem::swap(&mut ret, &mut pending_events);
diff --git a/lightning/src/routing/utxo.rs b/lightning/src/routing/utxo.rs
index 8e0dc11..f46160f 100644
--- a/lightning/src/routing/utxo.rs
+++ b/lightning/src/routing/utxo.rs
@@ -21,7 +21,7 @@ use bitcoin::hex::DisplayHex;
use crate::ln::chan_utils::make_funding_redeemscript_from_slices;
use crate::ln::msgs::{self, ErrorAction, LightningError, MessageSendEvent};
-use crate::routing::gossip::{NetworkGraph, NodeId, P2PGossipSync};
+use crate::routing::gossip::{NetworkGraph, NodeId};
use crate::util::logger::{Level, Logger};
use crate::util::wakers::Notifier;
@@ -157,148 +157,11 @@ impl UtxoFuture {
}
}
- /// Resolves this future against the given `graph` and with the given `result`.
- ///
- /// This is identical to calling [`UtxoFuture::resolve`] with a dummy `gossip`, disabling
- /// forwarding the validated gossip message onwards to peers.
- ///
- /// Because this may cause the [`NetworkGraph`]'s [`processing_queue_high`] to flip, in order
- /// to allow us to interact with peers again, you should call [`PeerManager::process_events`]
- /// after this.
- ///
- /// [`processing_queue_high`]: crate::ln::msgs::RoutingMessageHandler::processing_queue_high
- /// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events
- pub fn resolve_without_forwarding<L: Deref>(
- &self, graph: &NetworkGraph<L>, result: Result<TxOut, UtxoLookupError>,
- ) where
- L::Target: Logger,
- {
- self.do_resolve(graph, result);
- }
-
- /// Resolves this future against the given `graph` and with the given `result`.
- ///
- /// The given `gossip` is used to broadcast any validated messages onwards to all peers which
- /// have available buffer space.
- ///
- /// Because this may cause the [`NetworkGraph`]'s [`processing_queue_high`] to flip, in order
- /// to allow us to interact with peers again, you should call [`PeerManager::process_events`]
- /// after this.
- ///
- /// [`processing_queue_high`]: crate::ln::msgs::RoutingMessageHandler::processing_queue_high
- /// [`PeerManager::process_events`]: crate::ln::peer_handler::PeerManager::process_events
- pub fn resolve<
- L: Deref,
- G: Deref<Target = NetworkGraph<L>>,
- U: Deref,
- GS: Deref<Target = P2PGossipSync<G, U, L>>,
- >(
- &self, graph: &NetworkGraph<L>, gossip: GS, result: Result<TxOut, UtxoLookupError>,
- ) where
- L::Target: Logger,
- U::Target: UtxoLookup,
- {
- let mut res = self.do_resolve(graph, result);
- for msg_opt in res.iter_mut() {
- if let Some(msg) = msg_opt.take() {
- gossip.forward_gossip_msg(msg);
- }
- }
- }
-
- #[rustfmt::skip]
- fn do_resolve<L: Deref>(&self, graph: &NetworkGraph<L>, result: Result<TxOut, UtxoLookupError>)
- -> [Option<MessageSendEvent>; 5] where L::Target: Logger {
- let (announcement, node_a, node_b, update_a, update_b) = {
- let mut pending_checks = graph.pending_checks.internal.lock().unwrap();
- let mut async_messages = self.state.lock().unwrap();
- async_messages.notifier.notify();
-
- if async_messages.channel_announce.is_none() {
- // We raced returning to `check_channel_announcement` which hasn't updated
- // `channel_announce` yet. That's okay, we can set the `complete` field which it will
- // check once it gets control again.
- async_messages.complete = Some(result);
- return [None, None, None, None, None];
- }
-
- let announcement_msg = match async_messages.channel_announce.as_ref().unwrap() {
- ChannelAnnouncement::Full(signed_msg) => &signed_msg.contents,
- ChannelAnnouncement::Unsigned(msg) => &msg,
- };
-
- pending_checks.lookup_completed(announcement_msg, &Arc::downgrade(&self.state));
-
- (async_messages.channel_announce.take().unwrap(),
- async_messages.latest_node_announce_a.take(),
- async_messages.latest_node_announce_b.take(),
- async_messages.latest_channel_update_a.take(),
- async_messages.latest_channel_update_b.take())
- };
-
- let mut res = [None, None, None, None, None];
- let mut res_idx = 0;
-
- // Now that we've updated our internal state, pass the pending messages back through the
- // network graph with a different `UtxoLookup` which will resolve immediately.
- // Note that we ignore errors as we don't disconnect peers anyway, so there's nothing to do
- // with them.
- let resolver = UtxoResolver(result);
- let (node_id_1, node_id_2) = match &announcement {
- ChannelAnnouncement::Full(signed_msg) => (signed_msg.contents.node_id_1, signed_msg.contents.node_id_2),
- ChannelAnnouncement::Unsigned(msg) => (msg.node_id_1, msg.node_id_2),
- };
- match announcement {
- ChannelAnnouncement::Full(signed_msg) => {
- if graph.update_channel_from_announcement(&signed_msg, &Some(&resolver)).is_ok() {
- res[res_idx] = Some(MessageSendEvent::BroadcastChannelAnnouncement {
- msg: signed_msg, update_msg: None,
- });
- res_idx += 1;
- }
- },
- ChannelAnnouncement::Unsigned(msg) => {
- let _ = graph.update_channel_from_unsigned_announcement(&msg, &Some(&resolver));
- },
- }
-
- for announce in core::iter::once(node_a).chain(core::iter::once(node_b)) {
- match announce {
- Some(NodeAnnouncement::Full(signed_msg)) => {
- if graph.update_node_from_announcement(&signed_msg).is_ok() {
- res[res_idx] = Some(MessageSendEvent::BroadcastNodeAnnouncement {
- msg: signed_msg,
- });
- res_idx += 1;
- }
- },
- Some(NodeAnnouncement::Unsigned(msg)) => {
- let _ = graph.update_node_from_unsigned_announcement(&msg);
- },
- None => {},
- }
- }
-
- for update in core::iter::once(update_a).chain(core::iter::once(update_b)) {
- match update {
- Some(ChannelUpdate::Full(signed_msg)) => {
- if graph.update_channel(&signed_msg).is_ok() {
- res[res_idx] = Some(MessageSendEvent::BroadcastChannelUpdate {
- msg: signed_msg,
- node_id_1,
- node_id_2,
- });
- res_idx += 1;
- }
- },
- Some(ChannelUpdate::Unsigned(msg)) => {
- let _ = graph.update_channel_unsigned(&msg);
- },
- None => {},
- }
- }
-
- res
+ /// Resolves this future with the given result.
+ pub fn resolve(&self, result: Result<TxOut, UtxoLookupError>) {
+ let mut state = self.state.lock().unwrap();
+ state.complete = Some(result);
+ state.notifier.notify();
}
}
@@ -307,28 +170,6 @@ struct PendingChecksContext {
nodes: HashMap<NodeId, Vec<Weak<Mutex<UtxoMessages>>>>,
}
-impl PendingChecksContext {
- #[rustfmt::skip]
- fn lookup_completed(&mut self,
- msg: &msgs::UnsignedChannelAnnouncement, completed_state: &Weak<Mutex<UtxoMessages>>
- ) {
- if let hash_map::Entry::Occupied(e) = self.channels.entry(msg.short_channel_id) {
- if Weak::ptr_eq(e.get(), &completed_state) {
- e.remove();
- }
- }
-
- if let hash_map::Entry::Occupied(mut e) = self.nodes.entry(msg.node_id_1) {
- e.get_mut().retain(|elem| !Weak::ptr_eq(&elem, &completed_state));
- if e.get().is_empty() { e.remove(); }
- }
- if let hash_map::Entry::Occupied(mut e) = self.nodes.entry(msg.node_id_2) {
- e.get_mut().retain(|elem| !Weak::ptr_eq(&elem, &completed_state));
- if e.get().is_empty() { e.remove(); }
- }
- }
-}
-
/// A set of messages which are pending UTXO lookups for processing.
pub(super) struct PendingChecks {
internal: Mutex<PendingChecksContext>,
@@ -597,6 +438,142 @@ impl PendingChecks {
false
}
}
+
+ fn resolve_single_future<L: Deref>(
+ &self, graph: &NetworkGraph<L>, entry: Arc<Mutex<UtxoMessages>>,
+ new_messages: &mut Vec<MessageSendEvent>,
+ ) where
+ L::Target: Logger,
+ {
+ let (announcement, result, announce_a, announce_b, update_a, update_b);
+ {
+ let mut state = entry.lock().unwrap();
+ announcement = if let Some(announcement) = state.channel_announce.take() {
+ announcement
+ } else {
+ // We raced returning to `check_channel_announcement` which hasn't updated
+ // `channel_announce` yet. That's okay, we can set the `complete` field which it will
+ // check once it gets control again.
+ return;
+ };
+
+ result = if let Some(result) = state.complete.take() {
+ result
+ } else {
+ debug_assert!(false, "Future should have been resolved");
+ return;
+ };
+
+ announce_a = state.latest_node_announce_a.take();
+ announce_b = state.latest_node_announce_b.take();
+ update_a = state.latest_channel_update_a.take();
+ update_b = state.latest_channel_update_b.take();
+ }
+
+ // Now that we've updated our internal state, pass the pending messages back through the
+ // network graph with a different `UtxoLookup` which will resolve immediately.
+ // Note that we ignore errors as we don't disconnect peers anyway, so there's nothing to do
+ // with them.
+ let resolver = UtxoResolver(result);
+ let (node_id_1, node_id_2) = match &announcement {
+ ChannelAnnouncement::Full(signed_msg) => {
+ (signed_msg.contents.node_id_1, signed_msg.contents.node_id_2)
+ },
+ ChannelAnnouncement::Unsigned(msg) => (msg.node_id_1, msg.node_id_2),
+ };
+ match announcement {
+ ChannelAnnouncement::Full(signed_msg) => {
+ if graph.update_channel_from_announcement(&signed_msg, &Some(&resolver)).is_ok() {
+ new_messages.push(MessageSendEvent::BroadcastChannelAnnouncement {
+ msg: signed_msg,
+ update_msg: None,
+ });
+ }
+ },
+ ChannelAnnouncement::Unsigned(msg) => {
+ let _ = graph.update_channel_from_unsigned_announcement(&msg, &Some(&resolver));
+ },
+ }
+
+ for announce in [announce_a, announce_b] {
+ match announce {
+ Some(NodeAnnouncement::Full(signed_msg)) => {
+ if graph.update_node_from_announcement(&signed_msg).is_ok() {
+ new_messages
+ .push(MessageSendEvent::BroadcastNodeAnnouncement { msg: signed_msg });
+ }
+ },
+ Some(NodeAnnouncement::Unsigned(msg)) => {
+ let _ = graph.update_node_from_unsigned_announcement(&msg);
+ },
+ None => {},
+ }
+ }
+
+ for update in [update_a, update_b] {
+ match update {
+ Some(ChannelUpdate::Full(signed_msg)) => {
+ if graph.update_channel(&signed_msg).is_ok() {
+ new_messages.push(MessageSendEvent::BroadcastChannelUpdate {
+ msg: signed_msg,
+ node_id_1,
+ node_id_2,
+ });
+ }
+ },
+ Some(ChannelUpdate::Unsigned(msg)) => {
+ let _ = graph.update_channel_unsigned(&msg);
+ },
+ None => {},
+ }
+ }
+ }
+
+ pub(super) fn check_resolved_futures<L: Deref>(
+ &self, graph: &NetworkGraph<L>,
+ ) -> Vec<MessageSendEvent>
+ where
+ L::Target: Logger,
+ {
+ let mut completed_states = Vec::new();
+ {
+ let mut lck = self.internal.lock().unwrap();
+ lck.channels.retain(|_, state| {
+ if let Some(state) = state.upgrade() {
+ if state.lock().unwrap().complete.is_some() {
+ completed_states.push(state);
+ false
+ } else {
+ true
+ }
+ } else {
+ // The UtxoFuture has been dropped, drop the pending-lookup state.
+ false
+ }
+ });
+ lck.nodes.retain(|_, lookups| {
+ lookups.retain(|state| {
+ if let Some(state) = state.upgrade() {
+ if state.lock().unwrap().complete.is_some() {
+ completed_states.push(state);
+ false
+ } else {
+ true
+ }
+ } else {
+ // The UtxoFuture has been dropped, drop the pending-lookup state.
+ false
+ }
+ });
+ !lookups.is_empty()
+ });
+ }
+ let mut res = Vec::with_capacity(completed_states.len() * 5);
+ for state in completed_states {
+ self.resolve_single_future(graph, state, &mut res);
+ }
+ res
+ }
}
#[cfg(test)]
@@ -654,9 +631,10 @@ mod tests {
let notifier = Arc::new(Notifier::new());
let future = UtxoFuture::new(Arc::clone(¬ifier));
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
+ future
+ .resolve(Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
*chain_source.utxo_ret.lock().unwrap() = UtxoResult::Async(future.clone());
network_graph.update_channel_from_announcement(&valid_announcement, &Some(&chain_source)).unwrap();
@@ -679,9 +657,9 @@ mod tests {
"Channel being checked async");
assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none());
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script }));
+ future.resolve(Ok(TxOut { value: Amount::ZERO, script_pubkey: good_script }));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).unwrap();
network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).unwrap();
@@ -710,9 +688,10 @@ mod tests {
"Channel being checked async");
assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none());
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: bitcoin::ScriptBuf::new() }));
+ let value = Amount::from_sat(1_000_000);
+ future.resolve(Ok(TxOut { value, script_pubkey: bitcoin::ScriptBuf::new() }));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none());
}
@@ -731,8 +710,9 @@ mod tests {
"Channel being checked async");
assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none());
- future.resolve_without_forwarding(&network_graph, Err(UtxoLookupError::UnknownTx));
+ future.resolve(Err(UtxoLookupError::UnknownTx));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert!(network_graph.read_only().channels().get(&valid_announcement.contents.short_channel_id).is_none());
}
@@ -766,9 +746,10 @@ mod tests {
"Awaiting channel_announcement validation to accept channel_update");
assert!(!notifier.notify_pending());
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
+ future
+ .resolve(Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert!(network_graph.read_only().channels()
.get(&valid_announcement.contents.short_channel_id).unwrap().one_to_two.is_some());
@@ -806,9 +787,9 @@ mod tests {
"Awaiting channel_announcement validation to accept channel_update");
assert!(!notifier.notify_pending());
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
+ future.resolve(Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert_eq!(chan_update_a.contents.timestamp, chan_update_b.contents.timestamp);
let graph_lock = network_graph.read_only();
@@ -857,10 +838,11 @@ mod tests {
assert_eq!(chain_source.get_utxo_call_count.load(Ordering::Relaxed), 2);
// Still, if we resolve the original future, the original channel will be accepted.
- future.resolve_without_forwarding(&network_graph,
- Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
+ future
+ .resolve(Ok(TxOut { value: Amount::from_sat(1_000_000), script_pubkey: good_script }));
assert!(notifier_a.notify_pending());
assert!(!notifier_b.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert!(!network_graph.read_only().channels()
.get(&valid_announcement.contents.short_channel_id).unwrap()
.announcement_message.as_ref().unwrap()
@@ -896,8 +878,9 @@ mod tests {
assert!(network_graph.pending_checks.too_many_checks_pending());
// Once the future completes the "too many checks" flag should reset.
- future.resolve_without_forwarding(&network_graph, Err(UtxoLookupError::UnknownTx));
+ future.resolve(Err(UtxoLookupError::UnknownTx));
assert!(notifier.notify_pending());
+ network_graph.pending_checks.check_resolved_futures(&network_graph);
assert!(!network_graph.pending_checks.too_many_checks_pending());
}
Why this scored 24/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.