Skip to content

Commit eba9fba

Browse files
committed
sim-node/test: add test coverage for ChannelState
1 parent 6fa81f1 commit eba9fba

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed

sim-lib/src/sim_node.rs

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,3 +1038,257 @@ impl UtxoLookup for UtxoValidator {
10381038
}))
10391039
}
10401040
}
1041+
1042+
#[cfg(test)]
1043+
mod tests {
1044+
use crate::test_utils::get_random_keypair;
1045+
1046+
use super::*;
1047+
1048+
/// Creates a test channel policy with its maximum HTLC size set to half of the in flight limit of the channel.
1049+
/// The minimum HTLC size is hardcoded to 2 so that we can fall beneath this value with a 1 msat htlc.
1050+
fn create_test_policy(max_in_flight_msat: u64) -> ChannelPolicy {
1051+
let (_, pk) = get_random_keypair();
1052+
ChannelPolicy {
1053+
pubkey: pk,
1054+
max_htlc_count: 10,
1055+
max_in_flight_msat,
1056+
min_htlc_size_msat: 2,
1057+
max_htlc_size_msat: max_in_flight_msat / 2,
1058+
cltv_expiry_delta: 10,
1059+
base_fee: 1000,
1060+
fee_rate_prop: 5000,
1061+
}
1062+
}
1063+
1064+
macro_rules! assert_channel_balances {
1065+
($channel_state:expr, $local_balance:expr, $in_flight_len:expr, $in_flight_total:expr) => {
1066+
assert_eq!($channel_state.local_balance_msat, $local_balance);
1067+
assert_eq!($channel_state.in_flight.len(), $in_flight_len);
1068+
assert_eq!($channel_state.in_flight_total(), $in_flight_total);
1069+
};
1070+
}
1071+
1072+
/// Tests state updates related to adding and removing HTLCs to a channel.
1073+
#[test]
1074+
fn test_channel_state_transitions() {
1075+
let local_balance = 100_000_000;
1076+
let mut channel_state =
1077+
ChannelState::new(create_test_policy(local_balance / 2), local_balance);
1078+
1079+
// Basic sanity check that we Initialize the channel correctly.
1080+
assert_channel_balances!(channel_state, local_balance, 0, 0);
1081+
1082+
// Add a few HTLCs to our internal state and assert that balances are as expected. We'll test
1083+
// `check_outgoing_addition` in more detail in another test, so we just assert that we can add the htlc in
1084+
// this test.
1085+
let hash_1 = PaymentHash { 0: [1; 32] };
1086+
let htlc_1 = Htlc {
1087+
amount_msat: 1000,
1088+
cltv_expiry: 40,
1089+
};
1090+
1091+
assert!(channel_state.add_outgoing_htlc(hash_1, htlc_1).is_ok());
1092+
assert_channel_balances!(
1093+
channel_state,
1094+
local_balance - htlc_1.amount_msat,
1095+
1,
1096+
htlc_1.amount_msat
1097+
);
1098+
1099+
// Try to add a htlc with the same payment hash and assert that we fail because we enforce one htlc per hash
1100+
// at present.
1101+
assert!(matches!(
1102+
channel_state.add_outgoing_htlc(hash_1, htlc_1),
1103+
Err(ForwardingError::PaymentHashExists(_))
1104+
));
1105+
1106+
// Add a second, distinct htlc to our in-flight state.
1107+
let hash_2 = PaymentHash { 0: [2; 32] };
1108+
let htlc_2 = Htlc {
1109+
amount_msat: 1000,
1110+
cltv_expiry: 40,
1111+
};
1112+
1113+
assert!(channel_state.add_outgoing_htlc(hash_2, htlc_2).is_ok());
1114+
assert_channel_balances!(
1115+
channel_state,
1116+
local_balance - htlc_1.amount_msat - htlc_2.amount_msat,
1117+
2,
1118+
htlc_1.amount_msat + htlc_2.amount_msat
1119+
);
1120+
1121+
// Remove our second htlc with a failure so that our in-flight drops and we return the balance.
1122+
assert!(channel_state.remove_outgoing_htlc(&hash_2).is_ok());
1123+
channel_state.settle_outgoing_htlc(htlc_2.amount_msat, false);
1124+
assert_channel_balances!(
1125+
channel_state,
1126+
local_balance - htlc_1.amount_msat,
1127+
1,
1128+
htlc_1.amount_msat
1129+
);
1130+
1131+
// Try to remove the same htlc and assert that we fail because the htlc can't be found.
1132+
assert!(matches!(
1133+
channel_state.remove_outgoing_htlc(&hash_2),
1134+
Err(ForwardingError::PaymentHashNotFound(_))
1135+
));
1136+
1137+
// Finally, remove our original htlc with success and assert that our local balance is accordingly updated.
1138+
assert!(channel_state.remove_outgoing_htlc(&hash_1).is_ok());
1139+
channel_state.settle_outgoing_htlc(htlc_1.amount_msat, true);
1140+
assert_channel_balances!(channel_state, local_balance - htlc_1.amount_msat, 0, 0);
1141+
}
1142+
1143+
/// Tests policy checks applied when forwarding a htlc over a channel.
1144+
#[test]
1145+
fn test_htlc_forward() {
1146+
let local_balance = 140_000;
1147+
let channel_state = ChannelState::new(create_test_policy(local_balance / 2), local_balance);
1148+
1149+
// CLTV delta insufficient (one less than required).
1150+
assert!(matches!(
1151+
channel_state.check_htlc_forward(channel_state.policy.cltv_expiry_delta - 1, 0, 0),
1152+
Err(ForwardingError::InsufficientCltvDelta(_, _))
1153+
));
1154+
1155+
// Test insufficient fee.
1156+
let htlc_amount = 1000;
1157+
let htlc_fee = channel_state.policy.base_fee
1158+
+ (channel_state.policy.fee_rate_prop * htlc_amount) / 1e6 as u64;
1159+
1160+
assert!(matches!(
1161+
channel_state.check_htlc_forward(
1162+
channel_state.policy.cltv_expiry_delta,
1163+
htlc_amount,
1164+
htlc_fee - 1
1165+
),
1166+
Err(ForwardingError::InsufficientFee(_, _, _, _))
1167+
));
1168+
1169+
// Test exact and over-estimation of required policy.
1170+
assert!(channel_state
1171+
.check_htlc_forward(
1172+
channel_state.policy.cltv_expiry_delta,
1173+
htlc_amount,
1174+
htlc_fee,
1175+
)
1176+
.is_ok());
1177+
1178+
assert!(channel_state
1179+
.check_htlc_forward(
1180+
channel_state.policy.cltv_expiry_delta * 2,
1181+
htlc_amount,
1182+
htlc_fee * 3
1183+
)
1184+
.is_ok());
1185+
}
1186+
1187+
/// Test addition of outgoing htlc to local state.
1188+
#[test]
1189+
fn test_check_outgoing_addition() {
1190+
// Create test channel with low local liquidity so that we run into failures.
1191+
let local_balance = 100_000;
1192+
let mut channel_state =
1193+
ChannelState::new(create_test_policy(local_balance / 2), local_balance);
1194+
1195+
let mut htlc = Htlc {
1196+
amount_msat: channel_state.policy.max_htlc_size_msat + 1,
1197+
cltv_expiry: channel_state.policy.cltv_expiry_delta,
1198+
};
1199+
// HTLC maximum size exceeded.
1200+
assert!(matches!(
1201+
channel_state.check_outgoing_addition(&htlc),
1202+
Err(ForwardingError::MoreThanMaximum(_, _))
1203+
));
1204+
1205+
// Beneath HTLC minimum size.
1206+
htlc.amount_msat = channel_state.policy.min_htlc_size_msat - 1;
1207+
assert!(matches!(
1208+
channel_state.check_outgoing_addition(&htlc),
1209+
Err(ForwardingError::LessThanMinimum(_, _))
1210+
));
1211+
1212+
// Add two large htlcs so that we will start to run into our in-flight total amount limit.
1213+
let hash_1 = PaymentHash { 0: [1; 32] };
1214+
let htlc_1 = Htlc {
1215+
amount_msat: channel_state.policy.max_in_flight_msat / 2,
1216+
cltv_expiry: channel_state.policy.cltv_expiry_delta,
1217+
};
1218+
1219+
assert!(channel_state.check_outgoing_addition(&htlc_1).is_ok());
1220+
assert!(channel_state.add_outgoing_htlc(hash_1, htlc_1).is_ok());
1221+
1222+
let hash_2 = PaymentHash { 0: [2; 32] };
1223+
let htlc_2 = Htlc {
1224+
amount_msat: channel_state.policy.max_in_flight_msat / 2,
1225+
cltv_expiry: channel_state.policy.cltv_expiry_delta,
1226+
};
1227+
1228+
assert!(channel_state.check_outgoing_addition(&htlc_2).is_ok());
1229+
assert!(channel_state.add_outgoing_htlc(hash_2, htlc_2).is_ok());
1230+
1231+
// Now, assert that we can't add even our smallest htlc size, because we're hit our in-flight amount limit.
1232+
htlc.amount_msat = channel_state.policy.min_htlc_size_msat;
1233+
assert!(matches!(
1234+
channel_state.check_outgoing_addition(&htlc),
1235+
Err(ForwardingError::ExceedsInFlightTotal(_, _))
1236+
));
1237+
1238+
// Resolve both of the htlcs successfully so that the local liquidity is no longer available.
1239+
assert!(channel_state.remove_outgoing_htlc(&hash_1).is_ok());
1240+
channel_state.settle_outgoing_htlc(htlc_1.amount_msat, true);
1241+
1242+
assert!(channel_state.remove_outgoing_htlc(&hash_2).is_ok());
1243+
channel_state.settle_outgoing_htlc(htlc_2.amount_msat, true);
1244+
1245+
// Now we're going to add many htlcs so that we hit our in-flight count limit (unique payment hash per htlc).
1246+
for i in 0..channel_state.policy.max_htlc_count {
1247+
let hash = PaymentHash {
1248+
0: [i.try_into().unwrap(); 32],
1249+
};
1250+
assert!(channel_state.check_outgoing_addition(&htlc).is_ok());
1251+
assert!(channel_state.add_outgoing_htlc(hash, htlc).is_ok());
1252+
}
1253+
1254+
// Try to add one more htlc and we should be rejected.
1255+
let htlc_3 = Htlc {
1256+
amount_msat: channel_state.policy.min_htlc_size_msat,
1257+
cltv_expiry: channel_state.policy.cltv_expiry_delta,
1258+
};
1259+
1260+
assert!(matches!(
1261+
channel_state.check_outgoing_addition(&htlc_3),
1262+
Err(ForwardingError::ExceedsInFlightCount(_, _))
1263+
));
1264+
1265+
// Resolve all in-flight htlcs.
1266+
for i in 0..channel_state.policy.max_htlc_count {
1267+
let hash = PaymentHash {
1268+
0: [i.try_into().unwrap(); 32],
1269+
};
1270+
assert!(channel_state.remove_outgoing_htlc(&hash).is_ok());
1271+
channel_state.settle_outgoing_htlc(htlc.amount_msat, true)
1272+
}
1273+
1274+
// Add and settle another htlc to move more liquidity away from our local balance.
1275+
let hash_4 = PaymentHash { 0: [1; 32] };
1276+
let htlc_4 = Htlc {
1277+
amount_msat: channel_state.policy.max_htlc_size_msat,
1278+
cltv_expiry: channel_state.policy.cltv_expiry_delta,
1279+
};
1280+
assert!(channel_state.check_outgoing_addition(&htlc_4).is_ok());
1281+
assert!(channel_state.add_outgoing_htlc(hash_4, htlc_4).is_ok());
1282+
assert!(channel_state.remove_outgoing_htlc(&hash_4).is_ok());
1283+
channel_state.settle_outgoing_htlc(htlc_4.amount_msat, true);
1284+
1285+
// Finally, assert that we don't have enough balance to forward our largest possible htlc (because of all the
1286+
// htlcs that we've settled) and assert that we fail to a large htlc. The balance assertion here is just a
1287+
// sanity check for the test, which will fail if we change the amounts settled/failed in the test.
1288+
assert!(channel_state.local_balance_msat < channel_state.policy.max_htlc_size_msat);
1289+
assert!(matches!(
1290+
channel_state.check_outgoing_addition(&htlc_4),
1291+
Err(ForwardingError::InsufficientBalance(_, _))
1292+
));
1293+
}
1294+
}

0 commit comments

Comments
 (0)