lnwallet: add noop case to retransmit test

To make sure we don't cause force-closures because of commit sig
mismatches, we add a test case to verify that the retransmitted HTLC
matches the original HTLC.
This commit is contained in:
George Tsagkarelis committed 2025-08-08 20:03:30 +02:00
1 parent e0486697df
commit 4a34f78fba
1 file changed
+55 -27
+55 -27
View File
@@ -3232,7 +3232,9 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
// he receives Alice's CommitSig message, then Alice concludes that she needs // he receives Alice's CommitSig message, then Alice concludes that she needs
// to re-send the CommitDiff. After the diff has been sent, both nodes should // to re-send the CommitDiff. After the diff has been sent, both nodes should
// resynchronize and be able to complete the dangling commit. // resynchronize and be able to complete the dangling commit.
func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) { func testChanSyncOweCommitment(t *testing.T,
chanType channeldb.ChannelType, noop bool) {
// Create a test channel which will be used for the duration of this // Create a test channel which will be used for the duration of this
// unittest. The channel will be funded evenly with Alice having 5 BTC, // unittest. The channel will be funded evenly with Alice having 5 BTC,
// and Bob having 5 BTC. // and Bob having 5 BTC.
@@ -3242,6 +3244,17 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
var fakeOnionBlob [lnwire.OnionPacketSize]byte var fakeOnionBlob [lnwire.OnionPacketSize]byte
copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize)) copy(fakeOnionBlob[:], bytes.Repeat([]byte{0x05}, lnwire.OnionPacketSize))
// Let's create the noop add TLV record. This will only be
// effective for channels that have a tapscript root.
noopRecord := tlv.NewPrimitiveRecord[NoOpHtlcTLVType, bool](true)
records, err := tlv.RecordsToMap([]tlv.Record{noopRecord.Record()})
require.NoError(t, err)
// If the noop flag is not set for this test, nullify the records.
if !noop {
records = nil
}
// We'll start off the scenario with Bob sending 3 HTLC's to Alice in a // We'll start off the scenario with Bob sending 3 HTLC's to Alice in a
// single state update. // single state update.
htlcAmt := lnwire.NewMSatFromSatoshis(20000) htlcAmt := lnwire.NewMSatFromSatoshis(20000)
@@ -3251,10 +3264,11 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
rHash := sha256.Sum256(bobPreimage[:]) rHash := sha256.Sum256(bobPreimage[:])
h := &lnwire.UpdateAddHTLC{ h := &lnwire.UpdateAddHTLC{
PaymentHash: rHash, PaymentHash: rHash,
Amount: htlcAmt, Amount: htlcAmt,
Expiry: uint32(10), Expiry: uint32(10),
OnionBlob: fakeOnionBlob, OnionBlob: fakeOnionBlob,
CustomRecords: records,
} }
htlcIndex, err := bobChannel.AddHTLC(h, nil) htlcIndex, err := bobChannel.AddHTLC(h, nil)
@@ -3290,15 +3304,17 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
t.Fatalf("unable to settle htlc: %v", err) t.Fatalf("unable to settle htlc: %v", err)
} }
} }
var alicePreimage [32]byte var alicePreimage [32]byte
copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32)) copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32))
rHash := sha256.Sum256(alicePreimage[:]) rHash := sha256.Sum256(alicePreimage[:])
aliceHtlc := &lnwire.UpdateAddHTLC{ aliceHtlc := &lnwire.UpdateAddHTLC{
ChanID: chanID, ChanID: chanID,
PaymentHash: rHash, PaymentHash: rHash,
Amount: htlcAmt, Amount: htlcAmt,
Expiry: uint32(10), Expiry: uint32(10),
OnionBlob: fakeOnionBlob, OnionBlob: fakeOnionBlob,
CustomRecords: records,
} }
aliceHtlcIndex, err := aliceChannel.AddHTLC(aliceHtlc, nil) aliceHtlcIndex, err := aliceChannel.AddHTLC(aliceHtlc, nil)
require.NoError(t, err, "unable to add alice's htlc") require.NoError(t, err, "unable to add alice's htlc")
@@ -3519,22 +3535,25 @@ func testChanSyncOweCommitment(t *testing.T, chanType channeldb.ChannelType) {
// At this point, the final balances of both parties should properly // At this point, the final balances of both parties should properly
// reflect the amount of HTLC's sent. // reflect the amount of HTLC's sent.
bobMsatSent := numBobHtlcs * htlcAmt if noop {
if aliceChannel.channelState.TotalMSatSent != htlcAmt { // If this test-case includes noop HTLCs, then we don't expect
t.Fatalf("wrong value for msat sent: expected %v, got %v", // any balance changes.
htlcAmt, aliceChannel.channelState.TotalMSatSent) require.Zero(t, aliceChannel.channelState.TotalMSatSent)
} require.Zero(t, aliceChannel.channelState.TotalMSatReceived)
if aliceChannel.channelState.TotalMSatReceived != bobMsatSent { require.Zero(t, bobChannel.channelState.TotalMSatSent)
t.Fatalf("wrong value for msat recv: expected %v, got %v", require.Zero(t, bobChannel.channelState.TotalMSatReceived)
bobMsatSent, aliceChannel.channelState.TotalMSatReceived) } else {
} // Otherwise, calculate the expected changes and assert them.
if bobChannel.channelState.TotalMSatSent != bobMsatSent { bobMsatSent := numBobHtlcs * htlcAmt
t.Fatalf("wrong value for msat sent: expected %v, got %v",
bobMsatSent, bobChannel.channelState.TotalMSatSent) aliceChan := aliceChannel.channelState
} bobChan := bobChannel.channelState
if bobChannel.channelState.TotalMSatReceived != htlcAmt {
t.Fatalf("wrong value for msat recv: expected %v, got %v", require.Equal(t, aliceChan.TotalMSatSent, htlcAmt)
htlcAmt, bobChannel.channelState.TotalMSatReceived) require.Equal(t, aliceChan.TotalMSatReceived, bobMsatSent)
require.Equal(t, bobChan.TotalMSatSent, bobMsatSent)
require.Equal(t, bobChan.TotalMSatReceived, htlcAmt)
} }
} }
@@ -3548,6 +3567,7 @@ func TestChanSyncOweCommitment(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
chanType channeldb.ChannelType chanType channeldb.ChannelType
noop bool
}{ }{
{ {
name: "tweakless", name: "tweakless",
@@ -3571,10 +3591,18 @@ func TestChanSyncOweCommitment(t *testing.T) {
channeldb.SimpleTaprootFeatureBit | channeldb.SimpleTaprootFeatureBit |
channeldb.TapscriptRootBit, channeldb.TapscriptRootBit,
}, },
{
name: "tapscript root with noop",
chanType: channeldb.SingleFunderTweaklessBit |
channeldb.AnchorOutputsBit |
channeldb.SimpleTaprootFeatureBit |
channeldb.TapscriptRootBit,
noop: true,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
testChanSyncOweCommitment(t, tc.chanType) testChanSyncOweCommitment(t, tc.chanType, tc.noop)
}) })
} }
} }