@@ -11339,3 +11339,143 @@ func TestCreateCooperativeCloseTx(t *testing.T) {
1133911339 })
1134011340 }
1134111341}
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 [NoOpHtlcTLVType , 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