@@ -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.
919929func 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+
11181273type tapClient struct {
11191274 node * HarnessNode
11201275 lnd * rpc.HarnessRPC
0 commit comments