@@ -1038,3 +1038,257 @@ impl UtxoLookup for UtxoValidator {
1038
1038
} ) )
1039
1039
}
1040
1040
}
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