Skip to content

Commit 340f5c0

Browse files
committed
mod+itest: assert co-op close when initiator has zero balance
This PR bumps the version of tapd to fix the bug of panicking when an HTLC that pays the full balance to the recipient of a channel is being created. We assert this no longer is a problem and we also assert that the final balance when co-op closing in that state (where the initiator has zero balance) is correct.
1 parent 20d928f commit 340f5c0

File tree

4 files changed

+135
-4
lines changed

4 files changed

+135
-4
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/lightninglabs/loop/swapserverrpc v1.0.8
2121
github.com/lightninglabs/pool v0.6.5-beta.0.20240604070222-e121aadb3289
2222
github.com/lightninglabs/pool/auctioneerrpc v1.1.2
23-
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240716183814-7647ba8f48d5
23+
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240717143749-a4e894a32beb
2424
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240712025014-90f997c908d0
2525
github.com/lightningnetwork/lnd/cert v1.2.2
2626
github.com/lightningnetwork/lnd/fn v1.1.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,8 +1175,8 @@ github.com/lightninglabs/pool/auctioneerrpc v1.1.2 h1:Dbg+9Z9jXnhimR27EN37foc4aB
11751175
github.com/lightninglabs/pool/auctioneerrpc v1.1.2/go.mod h1:1wKDzN2zEP8srOi0B9iySlEsPdoPhw6oo3Vbm1v4Mhw=
11761176
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
11771177
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
1178-
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240716183814-7647ba8f48d5 h1:+d5HMOuBUWMsUVx6tAzy7+g8m4A76tec5/FOb1w5iVs=
1179-
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240716183814-7647ba8f48d5/go.mod h1:oAiEnRj2sCbPHAURot+tmKbyDhIoxnvkmag0JqlF1bs=
1178+
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240717143749-a4e894a32beb h1:+Y+MFz5DIcXjeyy18l+XeH03CBxPNmSysA4cEY2pilU=
1179+
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240717143749-a4e894a32beb/go.mod h1:oAiEnRj2sCbPHAURot+tmKbyDhIoxnvkmag0JqlF1bs=
11801180
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
11811181
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
11821182
github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240712025014-90f997c908d0 h1:V+PoltFSxN5oijkErYe+QbnVz5WJjBsAzaMNRrhmz3Q=

itest/assets_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,113 @@ func defaultCoOpCloseBalanceCheck(t *testing.T, local, remote *HarnessNode,
11631163
}
11641164
}
11651165

1166+
// initiatorZeroAssetBalanceCoOpBalanceCheck is a co-op close balance check
1167+
// function that can be used when the initiator has a zero asset balance.
1168+
func initiatorZeroAssetBalanceCoOpBalanceCheck(t *testing.T, _,
1169+
remote *HarnessNode, closeTx *wire.MsgTx,
1170+
closeUpdate *lnrpc.ChannelCloseUpdate, assetID, groupKey []byte,
1171+
universeTap *tapClient) {
1172+
1173+
// With the channel closed, we'll now assert that the co-op close
1174+
// transaction was inserted into the local universe.
1175+
//
1176+
// Since the initiator has a zero asset balance, we expect that at most
1177+
// three outputs exist: one for the remote asset output, one for the
1178+
// remote BTC channel balance and one for the initiator's BTC channel
1179+
// balance (which cannot be zero or below dust due to the mandatory
1180+
// channel reserve).
1181+
numOutputs := 3
1182+
1183+
closeTxid := closeTx.TxHash()
1184+
require.Len(t, closeTx.TxOut, numOutputs)
1185+
1186+
// We assume that the local node has a non-zero BTC balance left.
1187+
localOut, _ := closeTxOut(t, closeTx, closeUpdate, true)
1188+
require.Greater(t, localOut.Value, int64(1000))
1189+
1190+
// We also require there to be exactly one additional output, which is
1191+
// the remote asset output.
1192+
require.Len(t, closeUpdate.AdditionalOutputs, 1)
1193+
assetTxOut, assetOutputIndex := findTxOut(
1194+
t, closeTx, closeUpdate.AdditionalOutputs[0].PkScript,
1195+
)
1196+
require.LessOrEqual(t, assetTxOut.Value, int64(1000))
1197+
1198+
// The remote node has received a couple of HTLCs with an above
1199+
// dust value, so it should also have accumulated a non-dust
1200+
// balance, even after subtracting 1k sats for the asset output.
1201+
remoteCloseOut := closeUpdate.RemoteCloseOutput
1202+
require.NotNil(t, remoteCloseOut)
1203+
1204+
// Find out which of the additional outputs is the local one and which
1205+
// is the remote.
1206+
remoteAuxOut := closeUpdate.AdditionalOutputs[0]
1207+
require.False(t, remoteAuxOut.IsLocal)
1208+
1209+
// And then we verify the arrival of the remote balance asset proof at
1210+
// the universe server as well.
1211+
var remoteAssetCloseOut rfqmsg.JsonCloseOutput
1212+
err := json.Unmarshal(
1213+
remoteCloseOut.CustomChannelData, &remoteAssetCloseOut,
1214+
)
1215+
require.NoError(t, err)
1216+
1217+
for assetIDStr, scriptKeyStr := range remoteAssetCloseOut.ScriptKeys {
1218+
scriptKeyBytes, err := hex.DecodeString(scriptKeyStr)
1219+
require.NoError(t, err)
1220+
1221+
require.Equal(t, hex.EncodeToString(assetID), assetIDStr)
1222+
1223+
a := assertUniverseProofExists(
1224+
t, universeTap, assetID, groupKey, scriptKeyBytes,
1225+
fmt.Sprintf("%v:%v", closeTxid, assetOutputIndex),
1226+
)
1227+
1228+
remoteTapd := newTapClient(t, remote)
1229+
1230+
scriptKey, err := btcec.ParsePubKey(scriptKeyBytes)
1231+
require.NoError(t, err)
1232+
assertAssetExists(
1233+
t, remoteTapd, assetID, a.Amount, scriptKey, true,
1234+
true, false,
1235+
)
1236+
}
1237+
}
1238+
1239+
// closeTxOut returns either the local or remote output from the close
1240+
// transaction, based on the information given in the close update.
1241+
func closeTxOut(t *testing.T, closeTx *wire.MsgTx,
1242+
closeUpdate *lnrpc.ChannelCloseUpdate, local bool) (*wire.TxOut, int) {
1243+
1244+
var targetPkScript []byte
1245+
if local {
1246+
require.NotNil(t, closeUpdate.LocalCloseOutput)
1247+
targetPkScript = closeUpdate.LocalCloseOutput.PkScript
1248+
} else {
1249+
require.NotNil(t, closeUpdate.RemoteCloseOutput)
1250+
targetPkScript = closeUpdate.RemoteCloseOutput.PkScript
1251+
}
1252+
1253+
return findTxOut(t, closeTx, targetPkScript)
1254+
}
1255+
1256+
// findTxOut returns the transaction output with the target pk script from the
1257+
// given transaction.
1258+
func findTxOut(t *testing.T, tx *wire.MsgTx, targetPkScript []byte) (
1259+
*wire.TxOut, int) {
1260+
1261+
for i, txOut := range tx.TxOut {
1262+
if bytes.Equal(txOut.PkScript, targetPkScript) {
1263+
return txOut, i
1264+
}
1265+
}
1266+
1267+
t.Fatalf("close output (targetPkScript=%x) not found in close "+
1268+
"transaction", targetPkScript)
1269+
1270+
return &wire.TxOut{}, 0
1271+
}
1272+
11661273
type tapClient struct {
11671274
node *HarnessNode
11681275
lnd *rpc.HarnessRPC

itest/litd_custom_channels_test.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness,
184184
)
185185
charlieFundingAmount := cents.Amount - uint64(2*400_000)
186186

187-
createTestAssetNetwork(
187+
fundRespCD, _, _ := createTestAssetNetwork(
188188
t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap,
189189
universeTap, cents, 400_000, charlieFundingAmount,
190190
daveFundingAmount, erinFundingAmount,
@@ -231,6 +231,30 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness,
231231
)
232232
payInvoiceWithAssets(t.t, charlie, dave, invoiceResp3, assetID, false)
233233
logBalance(t.t, nodes, assetID, "after invoice 3")
234+
235+
// We keysend the rest, so that all the balance is on Dave's side.
236+
charlieRemainingBalance := charlieFundingAmount - largeInvoiceAmount -
237+
fabiaInvoiceAssetAmount/2
238+
sendAssetKeySendPayment(
239+
t.t, charlie, dave, charlieRemainingBalance,
240+
assetID, fn.None[int64](),
241+
)
242+
logBalance(t.t, nodes, assetID, "after keysend")
243+
244+
// And now we close the channel to test how things look if all the
245+
// balance is on the non-initiator (recipient) side.
246+
charlieChanPoint := &lnrpc.ChannelPoint{
247+
OutputIndex: uint32(fundRespCD.OutputIndex),
248+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
249+
FundingTxidStr: fundRespCD.Txid,
250+
},
251+
}
252+
253+
t.Logf("Closing Charlie -> Dave channel")
254+
closeAssetChannelAndAssert(
255+
t, net, charlie, dave, charlieChanPoint, assetID, nil,
256+
universeTap, initiatorZeroAssetBalanceCoOpBalanceCheck,
257+
)
234258
}
235259

236260
// testCustomChannels tests that we can create a network with custom channels

0 commit comments

Comments
 (0)