Skip to content

simln-lib/feat: Surface send_to_route for SimGraph #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 80 additions & 3 deletions simln-lib/src/sim_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ impl SimulatedChannel {

/// SimNetwork represents a high level network coordinator that is responsible for the task of actually propagating
/// payments through the simulated network.
trait SimNetwork: Send + Sync {
pub trait SimNetwork: Send + Sync {
/// Sends payments over the route provided through the network, reporting the final payment outcome to the sender
/// channel provided.
fn dispatch_payment(
Expand All @@ -488,7 +488,7 @@ trait SimNetwork: Send + Sync {
/// all functionality through to a coordinating simulation network. This implementation contains both the [`SimNetwork`]
/// implementation that will allow us to dispatch payments and a read-only NetworkGraph that is used for pathfinding.
/// While these two could be combined, we re-use the LDK-native struct to allow re-use of their pathfinding logic.
struct SimNode<'a, T: SimNetwork> {
pub struct SimNode<'a, T: SimNetwork> {
info: NodeInfo,
/// The underlying execution network that will be responsible for dispatching payments.
network: Arc<Mutex<T>>,
Expand Down Expand Up @@ -526,6 +526,37 @@ impl<'a, T: SimNetwork> SimNode<'a, T> {
scorer,
}
}

/// Dispatches a payment to a specified route.
/// The [`lightning::routing::router::build_route_from_hops`] function can be used to build the route to be passed here.
///
/// **Note:** The payment hash passed in here should be used in track_payment to track the payment outcome.
pub async fn send_to_route(
&mut self,
route: Route,
payment_hash: PaymentHash,
) -> Result<(), LightningError> {
let (sender, receiver) = channel();

// Check for payment hash collision, failing the payment if we happen to repeat one.
match self.in_flight.entry(payment_hash) {
Entry::Occupied(_) => {
return Err(LightningError::SendPaymentError(
"payment hash exists".to_string(),
));
},
Entry::Vacant(vacant) => {
vacant.insert(receiver);
},
}

self.network
.lock()
.await
.dispatch_payment(self.info.pubkey, route, payment_hash, sender);

Ok(())
}
}

/// Produces the node info for a mocked node, filling in the features that the simulator requires.
Expand Down Expand Up @@ -642,7 +673,8 @@ impl<T: SimNetwork> LightningNode for SimNode<'_, T> {
}

/// track_payment blocks until a payment outcome is returned for the payment hash provided, or the shutdown listener
/// provided is triggered. This call will fail if the hash provided was not obtained by calling send_payment first.
/// provided is triggered. This call will fail if the hash provided was not obtained from send_payment or passed
/// into send_to_route first.
async fn track_payment(
&mut self,
hash: &PaymentHash,
Expand Down Expand Up @@ -1470,6 +1502,7 @@ mod tests {
use super::*;
use crate::clock::SystemClock;
use crate::test_utils::get_random_keypair;
use lightning::routing::router::build_route_from_hops;
use lightning::routing::router::Route;
use mockall::mock;
use ntest::assert_true;
Expand Down Expand Up @@ -2244,6 +2277,50 @@ mod tests {
test_kit.graph.tasks.wait().await;
}

#[tokio::test]
async fn test_send_and_track_payment_to_route() {
let chan_capacity = 500_000_000;
let test_kit =
DispatchPaymentTestKit::new(chan_capacity, vec![], CustomRecords::default()).await;

let mut node = SimNode::new(
test_kit.nodes[0],
Arc::new(Mutex::new(test_kit.graph)),
test_kit.routing_graph.clone(),
);

let route = build_route_from_hops(
&test_kit.nodes[0],
&[test_kit.nodes[1], test_kit.nodes[2], test_kit.nodes[3]],
&RouteParameters {
payment_params: PaymentParameters::from_node_id(*test_kit.nodes.last().unwrap(), 0)
.with_max_total_cltv_expiry_delta(u32::MAX)
// TODO: set non-zero value to support MPP.
.with_max_path_count(1)
// Allow sending htlcs up to 50% of the channel's capacity.
.with_max_channel_saturation_power_of_half(1),
final_value_msat: 20_000,
max_total_routing_fee_msat: None,
},
&test_kit.routing_graph,
&WrappedLog {},
&[0; 32],
)
.unwrap();

let preimage = PaymentPreimage(rand::random());
let payment_hash = preimage.into();
node.send_to_route(route, payment_hash).await.unwrap();

let (_, shutdown_listener) = triggered::trigger();
let result = node
.track_payment(&payment_hash, shutdown_listener)
.await
.unwrap();

assert!(matches!(result.payment_outcome, PaymentOutcome::Success));
}

fn create_intercept_request(shutdown_listener: Listener) -> InterceptRequest {
let (_, pubkey) = get_random_keypair();
InterceptRequest {
Expand Down