@@ -11339,3 +11339,143 @@ func TestCreateCooperativeCloseTx(t *testing.T) {
11339
11339
})
11340
11340
}
11341
11341
}
11342
+
11343
+ // TestNoopAddSettle tests that adding and settling an HTLC with no-op, no
11344
+ // balances are actually affected.
11345
+ func TestNoopAddSettle (t * testing.T ) {
11346
+ t .Parallel ()
11347
+
11348
+ // Create a test channel which will be used for the duration of this
11349
+ // unittest. The channel will be funded evenly with Alice having 5 BTC,
11350
+ // and Bob having 5 BTC.
11351
+ chanType := channeldb .SimpleTaprootFeatureBit |
11352
+ channeldb .AnchorOutputsBit | channeldb .ZeroHtlcTxFeeBit |
11353
+ channeldb .SingleFunderTweaklessBit | channeldb .TapscriptRootBit
11354
+ aliceChannel , bobChannel , err := CreateTestChannels (
11355
+ t , chanType ,
11356
+ )
11357
+ require .NoError (t , err , "unable to create test channels" )
11358
+
11359
+ const htlcAmt = 10_000
11360
+ htlc , preimage := createHTLC (0 , htlcAmt )
11361
+ noopRecord := tlv.NewPrimitiveRecord [tlv.TlvType65544 , bool ](true )
11362
+
11363
+ records , err := tlv .RecordsToMap ([]tlv.Record {noopRecord .Record ()})
11364
+ require .NoError (t , err )
11365
+ htlc .CustomRecords = records
11366
+
11367
+ aliceBalance := aliceChannel .channelState .LocalCommitment .LocalBalance
11368
+ bobBalance := bobChannel .channelState .LocalCommitment .LocalBalance
11369
+
11370
+ // Have Alice add the HTLC, then lock it in with a new state transition.
11371
+ aliceHtlcIndex , err := aliceChannel .AddHTLC (htlc , nil )
11372
+ require .NoError (t , err , "alice unable to add htlc" )
11373
+ bobHtlcIndex , err := bobChannel .ReceiveHTLC (htlc )
11374
+ require .NoError (t , err , "bob unable to receive htlc" )
11375
+
11376
+ err = ForceStateTransition (aliceChannel , bobChannel )
11377
+ require .NoError (t , err )
11378
+
11379
+ // We'll have Bob settle the HTLC, then force another state transition.
11380
+ err = bobChannel .SettleHTLC (preimage , bobHtlcIndex , nil , nil , nil )
11381
+ require .NoError (t , err , "bob unable to settle inbound htlc" )
11382
+ err = aliceChannel .ReceiveHTLCSettle (preimage , aliceHtlcIndex )
11383
+ require .NoError (t , err )
11384
+
11385
+ err = ForceStateTransition (aliceChannel , bobChannel )
11386
+ require .NoError (t , err )
11387
+
11388
+ aliceBalanceFinal := aliceChannel .channelState .LocalCommitment .LocalBalance //nolint:ll
11389
+ bobBalanceFinal := bobChannel .channelState .LocalCommitment .LocalBalance
11390
+
11391
+ // The balances of Alice and Bob should be the exact same and shouldn't
11392
+ // have changed.
11393
+ require .Equal (t , aliceBalance , aliceBalanceFinal )
11394
+ require .Equal (t , bobBalance , bobBalanceFinal )
11395
+ }
11396
+
11397
+ // TestNoopAddBelowReserve tests that the noop HTLCs behave as expected when
11398
+ // added over a channel where a party is below their reserve.
11399
+ func TestNoopAddBelowReserve (t * testing.T ) {
11400
+ t .Parallel ()
11401
+
11402
+ // Create a test channel which will be used for the duration of this
11403
+ // unittest. The channel will be funded evenly with Alice having 5 BTC,
11404
+ // and Bob having 5 BTC.
11405
+ chanType := channeldb .SimpleTaprootFeatureBit |
11406
+ channeldb .AnchorOutputsBit | channeldb .ZeroHtlcTxFeeBit |
11407
+ channeldb .SingleFunderTweaklessBit | channeldb .TapscriptRootBit
11408
+ aliceChan , bobChan , err := CreateTestChannels (t , chanType )
11409
+ require .NoError (t , err , "unable to create test channels" )
11410
+
11411
+ aliceBalance := aliceChan .channelState .LocalCommitment .LocalBalance
11412
+ bobBalance := bobChan .channelState .LocalCommitment .LocalBalance
11413
+
11414
+ const (
11415
+ // htlcAmt is the default HTLC amount to be used, epxressed in
11416
+ // milli-satoshis.
11417
+ htlcAmt = lnwire .MilliSatoshi (500_000 )
11418
+
11419
+ // numHtlc is the total number of HTLCs to be added/settled over
11420
+ // the channel.
11421
+ numHtlc = 20
11422
+ )
11423
+
11424
+ // Let's create the noop add TLV record to be used in all added HTLCs
11425
+ // over the channel.
11426
+ noopRecord := tlv.NewPrimitiveRecord [NoopAddHtlcType , bool ](true )
11427
+ records , err := tlv .RecordsToMap ([]tlv.Record {noopRecord .Record ()})
11428
+ require .NoError (t , err )
11429
+
11430
+ // Let's set Bob's reserve to whatever his local balance is, minus half
11431
+ // of the total amount to be added by the total HTLCs. This way we can
11432
+ // also verify that the noop-adds will start the nullification only once
11433
+ // Bob is above reserve.
11434
+ reserveTarget := (numHtlc / 2 ) * htlcAmt
11435
+ bobReserve := bobBalance + reserveTarget
11436
+
11437
+ bobChan .channelState .LocalChanCfg .ChanReserve =
11438
+ bobReserve .ToSatoshis ()
11439
+
11440
+ aliceChan .channelState .RemoteChanCfg .ChanReserve =
11441
+ bobReserve .ToSatoshis ()
11442
+
11443
+ // Add and settle all the HTLCs over the channel.
11444
+ for i := range numHtlc {
11445
+ htlc , preimage := createHTLC (i , htlcAmt )
11446
+ htlc .CustomRecords = records
11447
+
11448
+ aliceHtlcIndex , err := aliceChan .AddHTLC (htlc , nil )
11449
+ require .NoError (t , err , "alice unable to add htlc" )
11450
+ bobHtlcIndex , err := bobChan .ReceiveHTLC (htlc )
11451
+ require .NoError (t , err , "bob unable to receive htlc" )
11452
+
11453
+ require .NoError (t , ForceStateTransition (aliceChan , bobChan ))
11454
+
11455
+ // We'll have Bob settle the HTLC, then force another state
11456
+ // transition.
11457
+ err = bobChan .SettleHTLC (preimage , bobHtlcIndex , nil , nil , nil )
11458
+ require .NoError (t , err , "bob unable to settle inbound htlc" )
11459
+ err = aliceChan .ReceiveHTLCSettle (preimage , aliceHtlcIndex )
11460
+ require .NoError (t , err )
11461
+ require .NoError (t , ForceStateTransition (aliceChan , bobChan ))
11462
+ }
11463
+
11464
+ // We need to kick the state transition one last time for the balances
11465
+ // to be updated on both commitments.
11466
+ require .NoError (t , ForceStateTransition (aliceChan , bobChan ))
11467
+
11468
+ aliceBalanceFinal := aliceChan .channelState .LocalCommitment .LocalBalance
11469
+ bobBalanceFinal := bobChan .channelState .LocalCommitment .LocalBalance
11470
+
11471
+ // The balances of Alice and Bob must have changed exactly by half the
11472
+ // total number of HTLCs we added over the channel, plus one to get Bob
11473
+ // above the reserve. Bob's final balance should be as much as his
11474
+ // reserve plus one extra default HTLC amount.
11475
+ require .Equal (t , aliceBalance - htlcAmt * (numHtlc / 2 + 1 ), aliceBalanceFinal )
11476
+ require .Equal (t , bobBalance + htlcAmt * (numHtlc / 2 + 1 ), bobBalanceFinal )
11477
+ require .Equal (
11478
+ t , bobBalanceFinal .ToSatoshis (),
11479
+ bobChan .LocalChanReserve ()+ htlcAmt .ToSatoshis (),
11480
+ )
11481
+ }
0 commit comments