Skip to content

Commit d3629ea

Browse files
authored
Merge pull request #797 from lightninglabs/initiator-zero-balance-coop-close
custom channels: add more itest coverage, fix pending channel bug
2 parents 79fec8a + fb6949d commit d3629ea

File tree

5 files changed

+263
-53
lines changed

5 files changed

+263
-53
lines changed

config.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -400,19 +400,6 @@ func loadAndValidateConfig(interceptor signal.Interceptor) (*Config, error) {
400400
cfg.Lnd.RPCMiddleware.Enable = true
401401
}
402402

403-
// For the integration of tapd with lnd, we need to allow tapd to send
404-
// custom error messages to peers through the SendCustomMessage RPC in
405-
// lnd. Since the error messages aren't in the custom range, we
406-
// explicitly need to allow them. This isn't currently needed in remote
407-
// mode, because custom channels are only available if both lnd and tapd
408-
// are running in integrated mode.
409-
if !cfg.lndRemote {
410-
cfg.Lnd.ProtocolOptions.CustomMessage = append(
411-
cfg.Lnd.ProtocolOptions.CustomMessage,
412-
lnwire.MsgError,
413-
)
414-
}
415-
416403
// Validate the lightning-terminal config options.
417404
litDir := lnd.CleanAndExpandPath(preCfg.LitDir)
418405
cfg.LetsEncryptDir = lncfg.CleanAndExpandPath(cfg.LetsEncryptDir)
@@ -620,6 +607,19 @@ func loadConfigFile(preCfg *Config, interceptor signal.Interceptor) (*Config,
620607
// configuration is fully valid. This also sets up the main logger that
621608
// logs to a sub-directory in the .lnd folder.
622609
case ModeIntegrated:
610+
// For the integration of tapd with lnd, we need to allow tapd
611+
// to send custom error messages to peers through the
612+
// SendCustomMessage RPC in lnd. Since the error messages aren't
613+
// in the custom range, we explicitly need to allow them. This
614+
// isn't currently needed in remote mode, because custom
615+
// channels are only available if both lnd and tapd are running
616+
// in integrated mode. We need to set this value before we call
617+
// lnd.ValidateConfig() below, because that's what's going to
618+
// inject these values into the lnwire package.
619+
cfg.Lnd.ProtocolOptions.CustomMessage = append(
620+
cfg.Lnd.ProtocolOptions.CustomMessage, lnwire.MsgError,
621+
)
622+
623623
var err error
624624
cfg.Lnd, err = lnd.ValidateConfig(
625625
*cfg.Lnd, interceptor, fileParser, flagParser,

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.20240719071936-7035dedc860a
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.20240719071936-7035dedc860a h1:iWKqqgFcrOCJc4tviMguRNncdwtR/jATWkulQD2FBIQ=
1179+
github.com/lightninglabs/taproot-assets v0.4.0-rc4.0.20240719071936-7035dedc860a/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: 183 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1616
"github.com/btcsuite/btcd/btcutil"
1717
"github.com/btcsuite/btcd/chaincfg/chainhash"
18+
"github.com/btcsuite/btcd/wire"
1819
"github.com/davecgh/go-spew/spew"
1920
"github.com/lightninglabs/taproot-assets/itest"
2021
"github.com/lightninglabs/taproot-assets/proof"
@@ -916,10 +917,19 @@ func waitForSendEvent(t *testing.T,
916917
}
917918
}
918919

920+
// coOpCloseBalanceCheck is a function type that can be passed into
921+
// closeAssetChannelAndAsset to asset the final balance of the closing
922+
// transaction.
923+
type coOpCloseBalanceCheck func(t *testing.T, local, remote *HarnessNode,
924+
closeTx *wire.MsgTx, closeUpdate *lnrpc.ChannelCloseUpdate,
925+
assetID, groupKey []byte, universeTap *tapClient)
926+
927+
// closeAssetChannelAndAssert closes the channel between the local and remote
928+
// node and asserts the final balances of the closing transaction.
919929
func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
920930
local, remote *HarnessNode, chanPoint *lnrpc.ChannelPoint,
921-
assetID, groupKey []byte, universeTap *tapClient, remoteBtcBalance,
922-
remoteAssetBalance bool) {
931+
assetID, groupKey []byte, universeTap *tapClient,
932+
balanceCheck coOpCloseBalanceCheck) {
923933

924934
t.t.Helper()
925935

@@ -953,6 +963,41 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
953963

954964
waitForSendEvent(t.t, sendEvents, tapfreighter.SendStateComplete)
955965

966+
// Check the final balance of the closing transaction.
967+
balanceCheck(
968+
t.t, local, remote, closeTx, closeUpdate, assetID, groupKey,
969+
universeTap,
970+
)
971+
}
972+
973+
// assertDefaultCoOpCloseBalance returns a default implementation of the co-op
974+
// close balance check that can be used in tests. It assumes the initiator has
975+
// both an asset and BTC balance left, while the responder's balance can be
976+
// specified with the boolean variables.
977+
func assertDefaultCoOpCloseBalance(remoteBtcBalance,
978+
remoteAssetBalance bool) coOpCloseBalanceCheck {
979+
980+
return func(t *testing.T, local, remote *HarnessNode,
981+
closeTx *wire.MsgTx, closeUpdate *lnrpc.ChannelCloseUpdate,
982+
assetID, groupKey []byte, universeTap *tapClient) {
983+
984+
defaultCoOpCloseBalanceCheck(
985+
t, local, remote, closeTx, closeUpdate, assetID,
986+
groupKey, universeTap, remoteBtcBalance,
987+
remoteAssetBalance,
988+
)
989+
}
990+
}
991+
992+
// defaultCoOpCloseBalanceCheck is a default implementation of the co-op close
993+
// balance check that can be used in tests. It assumes the initiator has both
994+
// an asset and BTC balance left, while the responder's balance can be specified
995+
// with the boolean variables.
996+
func defaultCoOpCloseBalanceCheck(t *testing.T, local, remote *HarnessNode,
997+
closeTx *wire.MsgTx, closeUpdate *lnrpc.ChannelCloseUpdate,
998+
assetID, groupKey []byte, universeTap *tapClient, remoteBtcBalance,
999+
remoteAssetBalance bool) {
1000+
9561001
// With the channel closed, we'll now assert that the co-op close
9571002
// transaction was inserted into the local universe.
9581003
//
@@ -972,49 +1017,50 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
9721017
additionalOutputs++
9731018
}
9741019

975-
require.Len(t.t, closeTx.TxOut, numOutputs)
1020+
closeTxid := closeTx.TxHash()
1021+
require.Len(t, closeTx.TxOut, numOutputs)
9761022

9771023
outIdx := 0
9781024
dummyAmt := int64(1000)
979-
require.LessOrEqual(t.t, closeTx.TxOut[outIdx].Value, dummyAmt)
1025+
require.LessOrEqual(t, closeTx.TxOut[outIdx].Value, dummyAmt)
9801026

9811027
if remoteAssetBalance {
9821028
outIdx++
983-
require.LessOrEqual(t.t, closeTx.TxOut[outIdx].Value, dummyAmt)
1029+
require.LessOrEqual(t, closeTx.TxOut[outIdx].Value, dummyAmt)
9841030
}
9851031

9861032
// We also require there to be at most two additional outputs, one for
9871033
// each of the asset outputs with balance.
988-
require.Len(t.t, closeUpdate.AdditionalOutputs, additionalOutputs)
1034+
require.Len(t, closeUpdate.AdditionalOutputs, additionalOutputs)
9891035

9901036
var remoteCloseOut *lnrpc.CloseOutput
9911037
if remoteBtcBalance {
9921038
// The remote node has received a couple of HTLCs with an above
9931039
// dust value, so it should also have accumulated a non-dust
9941040
// balance, even after subtracting 1k sats for the asset output.
9951041
remoteCloseOut = closeUpdate.RemoteCloseOutput
996-
require.NotNil(t.t, remoteCloseOut)
1042+
require.NotNil(t, remoteCloseOut)
9971043

9981044
outIdx++
9991045
require.EqualValues(
1000-
t.t, remoteCloseOut.AmountSat-dummyAmt,
1046+
t, remoteCloseOut.AmountSat-dummyAmt,
10011047
closeTx.TxOut[outIdx].Value,
10021048
)
10031049
} else if remoteAssetBalance {
10041050
// The remote node has received a couple of HTLCs but not enough
10051051
// to go above dust. So it should still have an asset balance
10061052
// that we can verify.
10071053
remoteCloseOut = closeUpdate.RemoteCloseOutput
1008-
require.NotNil(t.t, remoteCloseOut)
1054+
require.NotNil(t, remoteCloseOut)
10091055
}
10101056

10111057
// The local node should have received the local BTC balance minus the
10121058
// TX fees and 1k sats for the asset output.
10131059
localCloseOut := closeUpdate.LocalCloseOutput
1014-
require.NotNil(t.t, localCloseOut)
1060+
require.NotNil(t, localCloseOut)
10151061
outIdx++
10161062
require.Greater(
1017-
t.t, closeTx.TxOut[outIdx].Value,
1063+
t, closeTx.TxOut[outIdx].Value,
10181064
localCloseOut.AmountSat-dummyAmt,
10191065
)
10201066

@@ -1039,39 +1085,41 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
10391085

10401086
if remoteAuxOut != nil {
10411087
require.Equal(
1042-
t.t, remoteAuxOut.PkScript,
1088+
t, remoteAuxOut.PkScript,
10431089
closeTx.TxOut[remoteAssetIndex].PkScript,
10441090
)
10451091
}
10461092

10471093
require.Equal(
1048-
t.t, localAuxOut.PkScript,
1094+
t, localAuxOut.PkScript,
10491095
closeTx.TxOut[localAssetIndex].PkScript,
10501096
)
10511097

10521098
// We now verify the arrival of the local balance asset proof at the
10531099
// universe server.
10541100
var localAssetCloseOut rfqmsg.JsonCloseOutput
1055-
err = json.Unmarshal(
1101+
err := json.Unmarshal(
10561102
localCloseOut.CustomChannelData, &localAssetCloseOut,
10571103
)
1058-
require.NoError(t.t, err)
1104+
require.NoError(t, err)
10591105

10601106
for assetIDStr, scriptKeyStr := range localAssetCloseOut.ScriptKeys {
10611107
scriptKeyBytes, err := hex.DecodeString(scriptKeyStr)
1062-
require.NoError(t.t, err)
1108+
require.NoError(t, err)
10631109

1064-
require.Equal(t.t, hex.EncodeToString(assetID), assetIDStr)
1110+
require.Equal(t, hex.EncodeToString(assetID), assetIDStr)
10651111

10661112
a := assertUniverseProofExists(
1067-
t.t, universeTap, assetID, groupKey, scriptKeyBytes,
1113+
t, universeTap, assetID, groupKey, scriptKeyBytes,
10681114
fmt.Sprintf("%v:%v", closeTxid, localAssetIndex),
10691115
)
10701116

1117+
localTapd := newTapClient(t, local)
1118+
10711119
scriptKey, err := btcec.ParsePubKey(scriptKeyBytes)
1072-
require.NoError(t.t, err)
1120+
require.NoError(t, err)
10731121
assertAssetExists(
1074-
t.t, localTapd, assetID, a.Amount, scriptKey, true,
1122+
t, localTapd, assetID, a.Amount, scriptKey, true,
10751123
true, false,
10761124
)
10771125
}
@@ -1083,38 +1131,145 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
10831131

10841132
// At this point the remote close output should be defined, otherwise
10851133
// something went wrong.
1086-
require.NotNil(t.t, remoteCloseOut)
1134+
require.NotNil(t, remoteCloseOut)
10871135

10881136
// And then we verify the arrival of the remote balance asset proof at
10891137
// the universe server as well.
10901138
var remoteAssetCloseOut rfqmsg.JsonCloseOutput
10911139
err = json.Unmarshal(
10921140
remoteCloseOut.CustomChannelData, &remoteAssetCloseOut,
10931141
)
1094-
require.NoError(t.t, err)
1142+
require.NoError(t, err)
10951143

10961144
for assetIDStr, scriptKeyStr := range remoteAssetCloseOut.ScriptKeys {
10971145
scriptKeyBytes, err := hex.DecodeString(scriptKeyStr)
1098-
require.NoError(t.t, err)
1146+
require.NoError(t, err)
10991147

1100-
require.Equal(t.t, hex.EncodeToString(assetID), assetIDStr)
1148+
require.Equal(t, hex.EncodeToString(assetID), assetIDStr)
11011149

11021150
a := assertUniverseProofExists(
1103-
t.t, universeTap, assetID, groupKey, scriptKeyBytes,
1151+
t, universeTap, assetID, groupKey, scriptKeyBytes,
11041152
fmt.Sprintf("%v:%v", closeTxid, remoteAssetIndex),
11051153
)
11061154

1107-
remoteTapd := newTapClient(t.t, remote)
1155+
remoteTapd := newTapClient(t, remote)
1156+
1157+
scriptKey, err := btcec.ParsePubKey(scriptKeyBytes)
1158+
require.NoError(t, err)
1159+
assertAssetExists(
1160+
t, remoteTapd, assetID, a.Amount, scriptKey, true,
1161+
true, false,
1162+
)
1163+
}
1164+
}
1165+
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)
11081229

11091230
scriptKey, err := btcec.ParsePubKey(scriptKeyBytes)
1110-
require.NoError(t.t, err)
1231+
require.NoError(t, err)
11111232
assertAssetExists(
1112-
t.t, remoteTapd, assetID, a.Amount, scriptKey, true,
1233+
t, remoteTapd, assetID, a.Amount, scriptKey, true,
11131234
true, false,
11141235
)
11151236
}
11161237
}
11171238

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+
11181273
type tapClient struct {
11191274
node *HarnessNode
11201275
lnd *rpc.HarnessRPC

0 commit comments

Comments
 (0)