Skip to content

Commit 6b768d6

Browse files
committed
sim-lib/test: add mocked testing of SimNode
1 parent 67a0b9e commit 6b768d6

File tree

3 files changed

+227
-2
lines changed

3 files changed

+227
-2
lines changed

Cargo.lock

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sim-lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ hex = "0.4.3"
2929
csv = "1.2.2"
3030
serde_millis = "0.1.1"
3131
rand_distr = "0.4.3"
32+
mockall = "0.12.1"
3233

3334
[dev-dependencies]
3435
ntest = "0.9.0"

sim-lib/src/sim_node.rs

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,9 +1041,11 @@ impl UtxoLookup for UtxoValidator {
10411041

10421042
#[cfg(test)]
10431043
mod tests {
1044-
use crate::test_utils::get_random_keypair;
1045-
10461044
use super::*;
1045+
use crate::test_utils::get_random_keypair;
1046+
use bitcoin::secp256k1::PublicKey;
1047+
use lightning::routing::router::Route;
1048+
use mockall::mock;
10471049

10481050
/// Creates a test channel policy with its maximum HTLC size set to half of the in flight limit of the channel.
10491051
/// The minimum HTLC size is hardcoded to 2 so that we can fall beneath this value with a 1 msat htlc.
@@ -1061,6 +1063,58 @@ mod tests {
10611063
}
10621064
}
10631065

1066+
/// Creates a set of n simulated channels connected in a chain of channels, where the short channel ID of each
1067+
/// channel is its index in the chain of channels and all capacity is on the side of the node that opened the
1068+
/// channel.
1069+
///
1070+
/// For example if n = 3 it will produce: node_1 -- node_2 -- node_3 -- node_4, connected by channels.
1071+
fn create_simulated_channels(n: u64, capacity_msat: u64) -> Vec<SimulatedChannel> {
1072+
let mut channels: Vec<SimulatedChannel> = vec![];
1073+
let (_, first_node) = get_random_keypair();
1074+
1075+
// Create channels in a ring so that we'll get long payment paths.
1076+
let mut node_1 = first_node;
1077+
for i in 0..n {
1078+
// Generate a new random node pubkey.
1079+
let (_, node_2) = get_random_keypair();
1080+
1081+
let node_1_to_2 = ChannelPolicy {
1082+
pubkey: node_1,
1083+
max_htlc_count: 483,
1084+
max_in_flight_msat: capacity_msat / 2,
1085+
min_htlc_size_msat: 1,
1086+
max_htlc_size_msat: capacity_msat / 2,
1087+
cltv_expiry_delta: 40,
1088+
base_fee: 1000 * i,
1089+
fee_rate_prop: 1500 * i,
1090+
};
1091+
1092+
let node_2_to_1 = ChannelPolicy {
1093+
pubkey: node_2,
1094+
max_htlc_count: 483,
1095+
max_in_flight_msat: capacity_msat / 2,
1096+
min_htlc_size_msat: 1,
1097+
max_htlc_size_msat: capacity_msat / 2,
1098+
cltv_expiry_delta: 40 + 10 * i as u32,
1099+
base_fee: 2000 * i,
1100+
fee_rate_prop: i,
1101+
};
1102+
1103+
channels.push(SimulatedChannel {
1104+
capacity_msat,
1105+
// Unique channel ID per link.
1106+
short_channel_id: ShortChannelID::from(i),
1107+
node_1: ChannelState::new(node_1_to_2, capacity_msat),
1108+
node_2: ChannelState::new(node_2_to_1, 0),
1109+
});
1110+
1111+
// Progress source ID to create a chain of nodes.
1112+
node_1 = node_2;
1113+
}
1114+
1115+
channels
1116+
}
1117+
10641118
macro_rules! assert_channel_balances {
10651119
($channel_state:expr, $local_balance:expr, $in_flight_len:expr, $in_flight_total:expr) => {
10661120
assert_eq!($channel_state.local_balance_msat, $local_balance);
@@ -1354,4 +1408,102 @@ mod tests {
13541408
Err(ForwardingError::NodeNotFound(_))
13551409
));
13561410
}
1411+
1412+
mock! {
1413+
Network{}
1414+
1415+
#[async_trait]
1416+
impl SimNetwork for Network{
1417+
fn dispatch_payment(
1418+
&mut self,
1419+
source: PublicKey,
1420+
route: Route,
1421+
payment_hash: PaymentHash,
1422+
sender: Sender<Result<PaymentResult, LightningError>>,
1423+
);
1424+
1425+
async fn lookup_node(&self, node: &PublicKey) -> Result<(NodeInfo, Vec<u64>), LightningError>;
1426+
}
1427+
}
1428+
1429+
/// Tests the functionality of a `SimNode`, mocking out the `SimNetwork` that is responsible for payment
1430+
/// propagation to isolate testing to just the implementation of `LightningNode`.
1431+
#[tokio::test]
1432+
async fn test_simulated_node() {
1433+
// Mock out our network and create a routing graph with 5 hops.
1434+
let mock = MockNetwork::new();
1435+
let sim_network = Arc::new(Mutex::new(mock));
1436+
let channels = create_simulated_channels(5, 300000000);
1437+
let graph = populate_network_graph(channels.clone()).unwrap();
1438+
1439+
// Create a simulated node for the first channel in our network.
1440+
let pk = channels[0].node_1.policy.pubkey;
1441+
let mut node = SimNode::new(pk, sim_network.clone(), Arc::new(graph));
1442+
1443+
// Prime mock to return node info from lookup and assert that we get the pubkey we're expecting.
1444+
let lookup_pk = channels[3].node_1.policy.pubkey;
1445+
sim_network
1446+
.lock()
1447+
.await
1448+
.expect_lookup_node()
1449+
.returning(move |_| Ok((node_info(lookup_pk), vec![1, 2, 3])));
1450+
1451+
// Assert that we get three channels from the mock.
1452+
let node_info = node.get_node_info(&lookup_pk).await.unwrap();
1453+
assert_eq!(lookup_pk, node_info.pubkey);
1454+
assert_eq!(node.list_channels().await.unwrap().len(), 3);
1455+
1456+
// Next, we're going to test handling of in-flight payments. To do this, we'll mock out calls to our dispatch
1457+
// function to send different results depending on the destination.
1458+
let dest_1 = channels[2].node_1.policy.pubkey;
1459+
let dest_2 = channels[4].node_1.policy.pubkey;
1460+
1461+
sim_network
1462+
.lock()
1463+
.await
1464+
.expect_dispatch_payment()
1465+
.returning(
1466+
move |_, route: Route, _, sender: Sender<Result<PaymentResult, LightningError>>| {
1467+
// If we've reached dispatch, we must have at least one path, grab the last hop to match the
1468+
// receiver.
1469+
let receiver = route.paths[0].hops.last().unwrap().pubkey;
1470+
let result = if receiver == dest_1 {
1471+
PaymentResult {
1472+
htlc_count: 2,
1473+
payment_outcome: PaymentOutcome::Success,
1474+
}
1475+
} else if receiver == dest_2 {
1476+
PaymentResult {
1477+
htlc_count: 0,
1478+
payment_outcome: PaymentOutcome::InsufficientBalance,
1479+
}
1480+
} else {
1481+
panic!("unknown mocked receiver");
1482+
};
1483+
1484+
let _ = sender.send(Ok(result)).unwrap();
1485+
},
1486+
);
1487+
1488+
// Dispatch payments to different destinations and assert that our track payment results are as expected.
1489+
let hash_1 = node.send_payment(dest_1, 10_000).await.unwrap();
1490+
let hash_2 = node.send_payment(dest_2, 15_000).await.unwrap();
1491+
1492+
let (_, shutdown_listener) = triggered::trigger();
1493+
1494+
let result_1 = node
1495+
.track_payment(hash_1, shutdown_listener.clone())
1496+
.await
1497+
.unwrap();
1498+
assert!(matches!(result_1.payment_outcome, PaymentOutcome::Success));
1499+
1500+
let result_2 = node
1501+
.track_payment(hash_2, shutdown_listener.clone())
1502+
.await
1503+
.unwrap();
1504+
assert!(matches!(
1505+
result_2.payment_outcome,
1506+
PaymentOutcome::InsufficientBalance
1507+
));
1508+
}
13571509
}

0 commit comments

Comments
 (0)