@@ -190,3 +190,151 @@ impl ChannelState {
190
190
}
191
191
}
192
192
}
193
+
194
+ /// Represents a simulated channel, and is responsible for managing addition and removal of HTLCs from the channel and
195
+ /// sanity checks. Channel state is tracked *unidirectionally* for each participant in the channel.
196
+ ///
197
+ /// Each node represented in the channel tracks only its outgoing HTLCs, and balance is transferred between the two
198
+ /// nodes as they settle or fail. Given some channel: node_1 <----> node_2:
199
+ /// * HTLC sent node_1 -> node_2: added to in-flight outgoing htlcs on node_1.
200
+ /// * HTLC sent node_2 -> node_1: added to in-flight outgoing htlcs on node_2.
201
+ ///
202
+ /// Rules for managing balance are as follows:
203
+ /// * When an HTLC is in flight, the channel's local outgoing liquidity decreases (as it's locked up).
204
+ /// * When an HTLC fails, the balance is returned to the local node (the one that it was in-flight / outgoing on).
205
+ /// * When an HTLC succeeds, the balance is sent to the remote node (the one that did not track it as in-flight).
206
+ ///
207
+ /// With each state transition, the simulated channel checks that the sum of its local balances and in-flight equal the
208
+ /// total channel capacity. Failure of this sanity check represents a critical failure in the state machine.
209
+ #[ derive( Clone ) ]
210
+ pub struct SimulatedChannel {
211
+ capacity_msat : u64 ,
212
+ short_channel_id : ShortChannelID ,
213
+ node_1 : ChannelState ,
214
+ node_2 : ChannelState ,
215
+ }
216
+
217
+ impl SimulatedChannel {
218
+ /// Creates a new channel with the capacity and policies provided. The total capacity of the channel is evenly split
219
+ /// between the channel participants (this is an arbitrary decision).
220
+ pub fn new (
221
+ capacity_msat : u64 ,
222
+ short_channel_id : ShortChannelID ,
223
+ node_1 : ChannelPolicy ,
224
+ node_2 : ChannelPolicy ,
225
+ ) -> Self {
226
+ SimulatedChannel {
227
+ capacity_msat,
228
+ short_channel_id,
229
+ node_1 : ChannelState :: new ( node_1, capacity_msat / 2 ) ,
230
+ node_2 : ChannelState :: new ( node_2, capacity_msat / 2 ) ,
231
+ }
232
+ }
233
+
234
+ fn get_node_mut ( & mut self , pubkey : & PublicKey ) -> Result < & mut ChannelState , ForwardingError > {
235
+ if pubkey == & self . node_1 . policy . pubkey {
236
+ Ok ( & mut self . node_1 )
237
+ } else if pubkey == & self . node_2 . policy . pubkey {
238
+ Ok ( & mut self . node_2 )
239
+ } else {
240
+ Err ( ForwardingError :: NodeNotFound ( * pubkey) )
241
+ }
242
+ }
243
+
244
+ fn get_node ( & self , pubkey : & PublicKey ) -> Result < & ChannelState , ForwardingError > {
245
+ if pubkey == & self . node_1 . policy . pubkey {
246
+ Ok ( & self . node_1 )
247
+ } else if pubkey == & self . node_2 . policy . pubkey {
248
+ Ok ( & self . node_2 )
249
+ } else {
250
+ Err ( ForwardingError :: NodeNotFound ( * pubkey) )
251
+ }
252
+ }
253
+
254
+ /// Adds an htlc to the appropriate side of the simulated channel, checking its policy and balance are okay. The
255
+ /// public key of the node sending the HTLC (ie, the party that would send update_add_htlc in the protocol)
256
+ /// must be provided to add the outgoing htlc to its side of the channel.
257
+ fn add_htlc (
258
+ & mut self ,
259
+ sending_node : & PublicKey ,
260
+ hash : PaymentHash ,
261
+ htlc : Htlc ,
262
+ ) -> Result < ( ) , ForwardingError > {
263
+ if htlc. amount_msat == 0 {
264
+ return Err ( ForwardingError :: ZeroAmountHtlc ) ;
265
+ }
266
+
267
+ self . get_node_mut ( sending_node) ?
268
+ . add_outgoing_htlc ( hash, htlc) ?;
269
+ self . sanity_check ( )
270
+ }
271
+
272
+ /// Performs a sanity check on the total balances in a channel. Note that we do not currently include on-chain
273
+ /// fees or reserve so these values should exactly match.
274
+ fn sanity_check ( & self ) -> Result < ( ) , ForwardingError > {
275
+ let node_1_total = self . node_1 . local_balance_msat + self . node_1 . in_flight_total ( ) ;
276
+ let node_2_total = self . node_2 . local_balance_msat + self . node_2 . in_flight_total ( ) ;
277
+
278
+ fail_forwarding_inequality ! ( node_1_total + node_2_total, !=, self . capacity_msat, SanityCheckFailed ) ;
279
+
280
+ Ok ( ( ) )
281
+ }
282
+
283
+ /// Removes an htlc from the appropriate side of the simulated channel, settling balances across channel sides
284
+ /// based on the success of the htlc. The public key of the node that originally sent the HTLC (ie, the party
285
+ /// that would send update_add_htlc in the protocol) must be provided to remove the htlc from its side of the
286
+ /// channel.
287
+ fn remove_htlc (
288
+ & mut self ,
289
+ sending_node : & PublicKey ,
290
+ hash : & PaymentHash ,
291
+ success : bool ,
292
+ ) -> Result < ( ) , ForwardingError > {
293
+ let htlc = self
294
+ . get_node_mut ( sending_node) ?
295
+ . remove_outgoing_htlc ( hash, success) ?;
296
+ self . settle_htlc ( sending_node, htlc. amount_msat , success) ?;
297
+ self . sanity_check ( )
298
+ }
299
+
300
+ /// Updates the local balance of each node in the channel once a htlc has been resolved, pushing funds to the
301
+ /// receiving nodes in the case of a successful payment and returning balance to the sender in the case of a
302
+ /// failure.
303
+ fn settle_htlc (
304
+ & mut self ,
305
+ sending_node : & PublicKey ,
306
+ amount_msat : u64 ,
307
+ success : bool ,
308
+ ) -> Result < ( ) , ForwardingError > {
309
+ // Successful payments push balance to the receiver, failures return it to the sender.
310
+ let ( sender_delta_msat, receiver_delta_msat) = if success {
311
+ ( 0 , amount_msat)
312
+ } else {
313
+ ( amount_msat, 0 )
314
+ } ;
315
+
316
+ if sending_node == & self . node_1 . policy . pubkey {
317
+ self . node_1 . local_balance_msat += sender_delta_msat;
318
+ self . node_2 . local_balance_msat += receiver_delta_msat;
319
+ Ok ( ( ) )
320
+ } else if sending_node == & self . node_2 . policy . pubkey {
321
+ self . node_2 . local_balance_msat += sender_delta_msat;
322
+ self . node_1 . local_balance_msat += receiver_delta_msat;
323
+ Ok ( ( ) )
324
+ } else {
325
+ Err ( ForwardingError :: NodeNotFound ( * sending_node) )
326
+ }
327
+ }
328
+
329
+ /// Checks an htlc forward against the outgoing policy of the node provided.
330
+ fn check_htlc_forward (
331
+ & self ,
332
+ forwarding_node : & PublicKey ,
333
+ cltv_delta : u32 ,
334
+ amount_msat : u64 ,
335
+ fee_msat : u64 ,
336
+ ) -> Result < ( ) , ForwardingError > {
337
+ self . get_node ( forwarding_node) ?
338
+ . check_htlc_forward ( cltv_delta, amount_msat, fee_msat)
339
+ }
340
+ }
0 commit comments