@@ -15,6 +15,7 @@ import (
15
15
"github.com/btcsuite/btcd/btcec/v2/schnorr"
16
16
"github.com/btcsuite/btcd/btcutil"
17
17
"github.com/btcsuite/btcd/chaincfg/chainhash"
18
+ "github.com/btcsuite/btcd/wire"
18
19
"github.com/davecgh/go-spew/spew"
19
20
"github.com/lightninglabs/taproot-assets/itest"
20
21
"github.com/lightninglabs/taproot-assets/proof"
@@ -916,10 +917,19 @@ func waitForSendEvent(t *testing.T,
916
917
}
917
918
}
918
919
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.
919
929
func closeAssetChannelAndAssert (t * harnessTest , net * NetworkHarness ,
920
930
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 ) {
923
933
924
934
t .t .Helper ()
925
935
@@ -953,6 +963,41 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
953
963
954
964
waitForSendEvent (t .t , sendEvents , tapfreighter .SendStateComplete )
955
965
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
+
956
1001
// With the channel closed, we'll now assert that the co-op close
957
1002
// transaction was inserted into the local universe.
958
1003
//
@@ -972,49 +1017,50 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
972
1017
additionalOutputs ++
973
1018
}
974
1019
975
- require .Len (t .t , closeTx .TxOut , numOutputs )
1020
+ closeTxid := closeTx .TxHash ()
1021
+ require .Len (t , closeTx .TxOut , numOutputs )
976
1022
977
1023
outIdx := 0
978
1024
dummyAmt := int64 (1000 )
979
- require .LessOrEqual (t . t , closeTx .TxOut [outIdx ].Value , dummyAmt )
1025
+ require .LessOrEqual (t , closeTx .TxOut [outIdx ].Value , dummyAmt )
980
1026
981
1027
if remoteAssetBalance {
982
1028
outIdx ++
983
- require .LessOrEqual (t . t , closeTx .TxOut [outIdx ].Value , dummyAmt )
1029
+ require .LessOrEqual (t , closeTx .TxOut [outIdx ].Value , dummyAmt )
984
1030
}
985
1031
986
1032
// We also require there to be at most two additional outputs, one for
987
1033
// each of the asset outputs with balance.
988
- require .Len (t . t , closeUpdate .AdditionalOutputs , additionalOutputs )
1034
+ require .Len (t , closeUpdate .AdditionalOutputs , additionalOutputs )
989
1035
990
1036
var remoteCloseOut * lnrpc.CloseOutput
991
1037
if remoteBtcBalance {
992
1038
// The remote node has received a couple of HTLCs with an above
993
1039
// dust value, so it should also have accumulated a non-dust
994
1040
// balance, even after subtracting 1k sats for the asset output.
995
1041
remoteCloseOut = closeUpdate .RemoteCloseOutput
996
- require .NotNil (t . t , remoteCloseOut )
1042
+ require .NotNil (t , remoteCloseOut )
997
1043
998
1044
outIdx ++
999
1045
require .EqualValues (
1000
- t . t , remoteCloseOut .AmountSat - dummyAmt ,
1046
+ t , remoteCloseOut .AmountSat - dummyAmt ,
1001
1047
closeTx .TxOut [outIdx ].Value ,
1002
1048
)
1003
1049
} else if remoteAssetBalance {
1004
1050
// The remote node has received a couple of HTLCs but not enough
1005
1051
// to go above dust. So it should still have an asset balance
1006
1052
// that we can verify.
1007
1053
remoteCloseOut = closeUpdate .RemoteCloseOutput
1008
- require .NotNil (t . t , remoteCloseOut )
1054
+ require .NotNil (t , remoteCloseOut )
1009
1055
}
1010
1056
1011
1057
// The local node should have received the local BTC balance minus the
1012
1058
// TX fees and 1k sats for the asset output.
1013
1059
localCloseOut := closeUpdate .LocalCloseOutput
1014
- require .NotNil (t . t , localCloseOut )
1060
+ require .NotNil (t , localCloseOut )
1015
1061
outIdx ++
1016
1062
require .Greater (
1017
- t . t , closeTx .TxOut [outIdx ].Value ,
1063
+ t , closeTx .TxOut [outIdx ].Value ,
1018
1064
localCloseOut .AmountSat - dummyAmt ,
1019
1065
)
1020
1066
@@ -1039,39 +1085,41 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
1039
1085
1040
1086
if remoteAuxOut != nil {
1041
1087
require .Equal (
1042
- t . t , remoteAuxOut .PkScript ,
1088
+ t , remoteAuxOut .PkScript ,
1043
1089
closeTx .TxOut [remoteAssetIndex ].PkScript ,
1044
1090
)
1045
1091
}
1046
1092
1047
1093
require .Equal (
1048
- t . t , localAuxOut .PkScript ,
1094
+ t , localAuxOut .PkScript ,
1049
1095
closeTx .TxOut [localAssetIndex ].PkScript ,
1050
1096
)
1051
1097
1052
1098
// We now verify the arrival of the local balance asset proof at the
1053
1099
// universe server.
1054
1100
var localAssetCloseOut rfqmsg.JsonCloseOutput
1055
- err = json .Unmarshal (
1101
+ err : = json .Unmarshal (
1056
1102
localCloseOut .CustomChannelData , & localAssetCloseOut ,
1057
1103
)
1058
- require .NoError (t . t , err )
1104
+ require .NoError (t , err )
1059
1105
1060
1106
for assetIDStr , scriptKeyStr := range localAssetCloseOut .ScriptKeys {
1061
1107
scriptKeyBytes , err := hex .DecodeString (scriptKeyStr )
1062
- require .NoError (t . t , err )
1108
+ require .NoError (t , err )
1063
1109
1064
- require .Equal (t . t , hex .EncodeToString (assetID ), assetIDStr )
1110
+ require .Equal (t , hex .EncodeToString (assetID ), assetIDStr )
1065
1111
1066
1112
a := assertUniverseProofExists (
1067
- t . t , universeTap , assetID , groupKey , scriptKeyBytes ,
1113
+ t , universeTap , assetID , groupKey , scriptKeyBytes ,
1068
1114
fmt .Sprintf ("%v:%v" , closeTxid , localAssetIndex ),
1069
1115
)
1070
1116
1117
+ localTapd := newTapClient (t , local )
1118
+
1071
1119
scriptKey , err := btcec .ParsePubKey (scriptKeyBytes )
1072
- require .NoError (t . t , err )
1120
+ require .NoError (t , err )
1073
1121
assertAssetExists (
1074
- t . t , localTapd , assetID , a .Amount , scriptKey , true ,
1122
+ t , localTapd , assetID , a .Amount , scriptKey , true ,
1075
1123
true , false ,
1076
1124
)
1077
1125
}
@@ -1083,38 +1131,145 @@ func closeAssetChannelAndAssert(t *harnessTest, net *NetworkHarness,
1083
1131
1084
1132
// At this point the remote close output should be defined, otherwise
1085
1133
// something went wrong.
1086
- require .NotNil (t . t , remoteCloseOut )
1134
+ require .NotNil (t , remoteCloseOut )
1087
1135
1088
1136
// And then we verify the arrival of the remote balance asset proof at
1089
1137
// the universe server as well.
1090
1138
var remoteAssetCloseOut rfqmsg.JsonCloseOutput
1091
1139
err = json .Unmarshal (
1092
1140
remoteCloseOut .CustomChannelData , & remoteAssetCloseOut ,
1093
1141
)
1094
- require .NoError (t . t , err )
1142
+ require .NoError (t , err )
1095
1143
1096
1144
for assetIDStr , scriptKeyStr := range remoteAssetCloseOut .ScriptKeys {
1097
1145
scriptKeyBytes , err := hex .DecodeString (scriptKeyStr )
1098
- require .NoError (t . t , err )
1146
+ require .NoError (t , err )
1099
1147
1100
- require .Equal (t . t , hex .EncodeToString (assetID ), assetIDStr )
1148
+ require .Equal (t , hex .EncodeToString (assetID ), assetIDStr )
1101
1149
1102
1150
a := assertUniverseProofExists (
1103
- t . t , universeTap , assetID , groupKey , scriptKeyBytes ,
1151
+ t , universeTap , assetID , groupKey , scriptKeyBytes ,
1104
1152
fmt .Sprintf ("%v:%v" , closeTxid , remoteAssetIndex ),
1105
1153
)
1106
1154
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 )
1108
1229
1109
1230
scriptKey , err := btcec .ParsePubKey (scriptKeyBytes )
1110
- require .NoError (t . t , err )
1231
+ require .NoError (t , err )
1111
1232
assertAssetExists (
1112
- t . t , remoteTapd , assetID , a .Amount , scriptKey , true ,
1233
+ t , remoteTapd , assetID , a .Amount , scriptKey , true ,
1113
1234
true , false ,
1114
1235
)
1115
1236
}
1116
1237
}
1117
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
+
1118
1273
type tapClient struct {
1119
1274
node * HarnessNode
1120
1275
lnd * rpc.HarnessRPC
0 commit comments