@@ -941,6 +941,227 @@ func testCustomChannelsForceClose(_ context.Context, net *NetworkHarness,
941
941
t .Logf ("Dave UTXOs: %v" , toProtoJSON (t .t , daveUTXOs ))
942
942
}
943
943
944
+ func testCustomChannelsBreach (_ context.Context , net * NetworkHarness ,
945
+ t * harnessTest ) {
946
+
947
+ lndArgs := slices .Clone (lndArgsTemplate )
948
+ litdArgs := slices .Clone (litdArgsTemplate )
949
+
950
+ // Zane will act as our Universe server for the duration of the test.
951
+ zane , err := net .NewNode (
952
+ t .t , "Zane" , lndArgs , false , true , litdArgs ... ,
953
+ )
954
+ require .NoError (t .t , err )
955
+
956
+ // For our litd args, make sure that they all seen Zane as the main
957
+ // Universe server.
958
+ litdArgs = append (litdArgs , fmt .Sprintf (
959
+ "--taproot-assets.proofcourieraddr=%s://%s" ,
960
+ proof .UniverseRpcCourierType , zane .Cfg .LitAddr (),
961
+ ))
962
+
963
+ // Charlie will be the breached party. We set --nolisten to ensure Dave
964
+ // won't be able to connect to him and trigger the channel protection
965
+ // logic automatically. We also can't have Charlie automatically
966
+ // reconnect too early, otherwise DLP would be initiated instead of the
967
+ // breach we want to provoke.
968
+ charlieFlags := append (
969
+ slices .Clone (lndArgs ), "--nolisten" , "--minbackoff=1h" ,
970
+ )
971
+
972
+ // For this simple test, we'll just have Carol -> Dave as an assets
973
+ // channel.
974
+ charlie , err := net .NewNode (
975
+ t .t , "Charlie" , charlieFlags , false , true , litdArgs ... ,
976
+ )
977
+ require .NoError (t .t , err )
978
+
979
+ dave , err := net .NewNode (t .t , "Dave" , lndArgs , false , true , litdArgs ... )
980
+ require .NoError (t .t , err )
981
+
982
+ // Next we'll connect all the nodes and also fund them with some coins.
983
+ nodes := []* HarnessNode {charlie , dave }
984
+ connectAllNodes (t .t , net , nodes )
985
+ fundAllNodes (t .t , net , nodes )
986
+
987
+ charlieTap := newTapClient (t .t , charlie )
988
+ daveTap := newTapClient (t .t , dave )
989
+
990
+ ctxb := context .Background ()
991
+
992
+ // Now we'll make an asset for Charlie that we'll use in the test to
993
+ // open a channel.
994
+ mintedAssets := itest .MintAssetsConfirmBatch (
995
+ t .t , t .lndHarness .Miner .Client , charlieTap ,
996
+ []* mintrpc.MintAssetRequest {
997
+ {
998
+ Asset : itestAsset ,
999
+ },
1000
+ },
1001
+ )
1002
+ cents := mintedAssets [0 ]
1003
+ assetID := cents .AssetGenesis .AssetId
1004
+
1005
+ t .Logf ("Minted %d lightning cents, syncing universes..." , cents .Amount )
1006
+ syncUniverses (t .t , charlieTap , dave )
1007
+ t .Logf ("Universes synced between all nodes, distributing assets..." )
1008
+
1009
+ // TODO(roasbeef): consolidate w/ the other test
1010
+
1011
+ // Next we can open an asset channel from Charlie -> Dave, then kick
1012
+ // off the main scenario.
1013
+ t .Logf ("Opening asset channels..." )
1014
+ assetFundResp , err := charlieTap .FundChannel (
1015
+ ctxb , & tchrpc.FundChannelRequest {
1016
+ AssetAmount : fundingAmount ,
1017
+ AssetId : assetID ,
1018
+ PeerPubkey : dave .PubKey [:],
1019
+ FeeRateSatPerVbyte : 5 ,
1020
+ },
1021
+ )
1022
+ require .NoError (t .t , err )
1023
+ t .Logf ("Funded channel between Charlie and Dave: %v" , assetFundResp )
1024
+
1025
+ // With the channel open, mine a block to confirm it.
1026
+ mineBlocks (t , net , 6 , 1 )
1027
+
1028
+ time .Sleep (time .Second * 1 )
1029
+
1030
+ // Next, we'll make keysend payments from Charlie to Dave. we'll use
1031
+ // this to reach a state where both parties have funds in the channel.
1032
+ const (
1033
+ numPayments = 5
1034
+ keySendAmount = 100
1035
+ btcAmt = int64 (5_000 )
1036
+ )
1037
+ for i := 0 ; i < numPayments ; i ++ {
1038
+ sendAssetKeySendPayment (
1039
+ t .t , charlie , dave , keySendAmount , assetID ,
1040
+ fn .Some (btcAmt ),
1041
+ )
1042
+ }
1043
+
1044
+ logBalance (t .t , nodes , assetID , "after keysend -- breach state" )
1045
+
1046
+ // Now we'll create an on disk snapshot that we'll use to restore back
1047
+ // to as our breached state.
1048
+ require .NoError (t .t , net .StopAndBackupDB (dave ))
1049
+ connectAllNodes (t .t , net , nodes )
1050
+
1051
+ // We'll send one more keysend payment now to revoke the state we were
1052
+ // just at above.
1053
+ sendAssetKeySendPayment (
1054
+ t .t , charlie , dave , keySendAmount , assetID , fn .Some (btcAmt ),
1055
+ )
1056
+ logBalance (t .t , nodes , assetID , "after keysend -- final state" )
1057
+
1058
+ // With the final state achieved, we'll now restore Dave (who will be
1059
+ // force closing) to that old state, the breach state.
1060
+ require .NoError (t .t , net .StopAndRestoreDB (dave ))
1061
+
1062
+ // With Dave restored, we'll now execute the force close.
1063
+ t .Logf ("Force close by Dave to breach..." )
1064
+ daveChanPoint := & lnrpc.ChannelPoint {
1065
+ OutputIndex : uint32 (assetFundResp .OutputIndex ),
1066
+ FundingTxid : & lnrpc.ChannelPoint_FundingTxidStr {
1067
+ FundingTxidStr : assetFundResp .Txid ,
1068
+ },
1069
+ }
1070
+ _ , breachTxid , err := net .CloseChannel (dave , daveChanPoint , true )
1071
+ require .NoError (t .t , err )
1072
+
1073
+ t .Logf ("Channel closed! Mining blocks, close_txid=%v" , breachTxid )
1074
+
1075
+ // Next, we'll mine a block to confirm the breach transaction.
1076
+ mineBlocks (t , net , 1 , 1 )
1077
+
1078
+ // We should be able to find the transfer of the breach for both
1079
+ // parties.
1080
+ charlieBreachTransfer := locateAssetTransfers (
1081
+ t .t , charlieTap , * breachTxid ,
1082
+ )
1083
+ daveBreachTransfer := locateAssetTransfers (
1084
+ t .t , daveTap , * breachTxid ,
1085
+ )
1086
+
1087
+ t .Logf ("Charlie breach transfer: %v" ,
1088
+ toProtoJSON (t .t , charlieBreachTransfer ))
1089
+ t .Logf ("Dave breach transfer: %v" ,
1090
+ toProtoJSON (t .t , daveBreachTransfer ))
1091
+
1092
+ // With the breach transaction mined, Charlie should now have a
1093
+ // transaction in the mempool sweeping the *both* commitment outputs.
1094
+ charlieJusticeTxid , err := waitForNTxsInMempool (
1095
+ net .Miner .Client , 1 , time .Second * 5 ,
1096
+ )
1097
+ require .NoError (t .t , err )
1098
+
1099
+ t .Logf ("Charlie justice txid: %v" , charlieJusticeTxid )
1100
+
1101
+ // Next, we'll mine a block to confirm Charlie's justice transaction.
1102
+ mineBlocks (t , net , 1 , 1 )
1103
+
1104
+ // Charlie should now have a transfer for his justice transaction.
1105
+ charlieJusticeTransfer := locateAssetTransfers (
1106
+ t .t , charlieTap , * charlieJusticeTxid [0 ],
1107
+ )
1108
+
1109
+ t .Logf ("Charlie justice transfer: %v" ,
1110
+ toProtoJSON (t .t , charlieJusticeTransfer ))
1111
+
1112
+ // Charlie's balance should now be the same as before the breach
1113
+ // attempt: the amount he minted at the very start.
1114
+ charlieBalance := itestAsset .Amount
1115
+ assertAssetBalance (t .t , charlieTap , assetID , charlieBalance )
1116
+
1117
+ t .Logf ("Charlie balance after breach: %d" , charlieBalance )
1118
+
1119
+ // Charlie should now have 2 total UTXOs: the change from the funding
1120
+ // output, and now the sweep output from the justice transaction.
1121
+ charlieUTXOs := assertNumAssetUTXOs (t .t , charlieTap , 2 )
1122
+
1123
+ t .Logf ("Charlie UTXOs after breach: %v" , toProtoJSON (t .t , charlieUTXOs ))
1124
+ }
1125
+
1126
+ func assertNumAssetUTXOs (t * testing.T , tapdClient * tapClient ,
1127
+ numUTXOs int ) * taprpc.ListUtxosResponse {
1128
+
1129
+ ctxb := context .Background ()
1130
+
1131
+ err := wait .NoError (func () error {
1132
+ clientUTXOs , err := tapdClient .ListUtxos (
1133
+ ctxb , & taprpc.ListUtxosRequest {},
1134
+ )
1135
+ if err != nil {
1136
+ return err
1137
+ }
1138
+
1139
+ if len (clientUTXOs .ManagedUtxos ) != numUTXOs {
1140
+ return fmt .Errorf ("expected %v UTXO, got %d" , numUTXOs ,
1141
+ len (clientUTXOs .ManagedUtxos ))
1142
+ }
1143
+
1144
+ return nil
1145
+ }, defaultTimeout )
1146
+
1147
+ clientUTXOs , err2 := tapdClient .ListUtxos (
1148
+ ctxb , & taprpc.ListUtxosRequest {},
1149
+ )
1150
+ require .NoError (t , err2 )
1151
+
1152
+ if err != nil {
1153
+ t .Logf ("wrong amount of UTXOs, got %d, expected %d: %v" ,
1154
+ len (clientUTXOs .ManagedUtxos ), numUTXOs ,
1155
+ toProtoJSON (t , clientUTXOs ))
1156
+
1157
+ t .Fatalf ("failed to assert UTXOs: %v" , err )
1158
+
1159
+ return nil
1160
+ }
1161
+
1162
+ return clientUTXOs
1163
+ }
1164
+
944
1165
func locateAssetTransfers (t * testing.T , tapdClient * tapClient ,
945
1166
txid chainhash.Hash ) * taprpc.AssetTransfer {
946
1167
@@ -953,12 +1174,12 @@ func locateAssetTransfers(t *testing.T, tapdClient *tapClient,
953
1174
},
954
1175
)
955
1176
if err != nil {
956
- return fmt .Errorf ("unable to list charlie " +
957
- "transfers: %w" , err )
1177
+ return fmt .Errorf ("unable to list %v transfers: %w" ,
1178
+ tapdClient . node . Name () , err )
958
1179
}
959
1180
if len (forceCloseTransfer .Transfers ) != 1 {
960
- return fmt .Errorf ("charlie is missing force close " +
961
- "transfer" )
1181
+ return fmt .Errorf ("%v is missing force close " +
1182
+ "transfer" , tapdClient . node . Name () )
962
1183
}
963
1184
964
1185
transfer = forceCloseTransfer .Transfers [0 ]
0 commit comments