Skip to content

Commit 13d66da

Browse files
committed
itest: add grouped asset multi tranche test
1 parent 2badf1d commit 13d66da

File tree

3 files changed

+305
-8
lines changed

3 files changed

+305
-8
lines changed

itest/assets_test.go

Lines changed: 174 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,142 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap,
290290
return chanPointCD, chanPointDY, chanPointEF
291291
}
292292

293+
func createTestAssetNetworkGroupKey(ctx context.Context, t *harnessTest,
294+
net *NetworkHarness, charlieTap, daveTap, erinTap, fabiaTap,
295+
universeTap *tapClient, mintedAssets []*taprpc.Asset, assetSendAmount,
296+
charlieFundingAmount, erinFundingAmount uint64,
297+
pushSat int64) (*lnrpc.ChannelPoint, *lnrpc.ChannelPoint) {
298+
299+
var groupKey []byte
300+
for _, mintedAsset := range mintedAssets {
301+
require.NotNil(t.t, mintedAsset.AssetGroup)
302+
303+
if groupKey == nil {
304+
groupKey = mintedAsset.AssetGroup.TweakedGroupKey
305+
306+
continue
307+
}
308+
309+
require.Equal(
310+
t.t, groupKey, mintedAsset.AssetGroup.TweakedGroupKey,
311+
)
312+
}
313+
314+
// We need to send some assets to Erin, so he can fund an asset channel
315+
// with Fabia.
316+
sendAssetsEqualAmounts(
317+
ctx, t, erinTap, charlieTap, universeTap, mintedAssets,
318+
assetSendAmount/2, 0,
319+
)
320+
321+
t.Logf("Opening asset channels...")
322+
323+
// The first channel we create has a push amount, so Charlie can receive
324+
// payments immediately and not run into the channel reserve issue.
325+
fundRespCD, err := charlieTap.FundChannel(
326+
ctx, &tchrpc.FundChannelRequest{
327+
AssetAmount: charlieFundingAmount,
328+
GroupKey: groupKey,
329+
PeerPubkey: daveTap.node.PubKey[:],
330+
FeeRateSatPerVbyte: 5,
331+
PushSat: pushSat,
332+
},
333+
)
334+
require.NoError(t.t, err)
335+
t.Logf("Funded channel between Charlie and Dave: %v", fundRespCD)
336+
337+
fundRespEF, err := erinTap.FundChannel(
338+
ctx, &tchrpc.FundChannelRequest{
339+
AssetAmount: erinFundingAmount,
340+
GroupKey: groupKey,
341+
PeerPubkey: fabiaTap.node.PubKey[:],
342+
FeeRateSatPerVbyte: 5,
343+
PushSat: pushSat,
344+
},
345+
)
346+
require.NoError(t.t, err)
347+
t.Logf("Funded channel between Erin and Fabia: %v", fundRespEF)
348+
349+
// Make sure the pending channel shows up in the list and has the
350+
// custom records set as JSON.
351+
assertPendingChannels(
352+
t.t, charlieTap.node, mintedAssets[1], 1, charlieFundingAmount,
353+
0,
354+
)
355+
assertPendingChannels(
356+
t.t, erinTap.node, mintedAssets[0], 1, erinFundingAmount/2, 0,
357+
)
358+
assertPendingChannels(
359+
t.t, erinTap.node, mintedAssets[1], 1, erinFundingAmount/2, 0,
360+
)
361+
362+
// Now that we've looked at the pending channels, let's actually confirm
363+
// all three of them.
364+
mineBlocks(t, net, 6, 2)
365+
366+
chanPointCD := &lnrpc.ChannelPoint{
367+
OutputIndex: uint32(fundRespCD.OutputIndex),
368+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
369+
FundingTxidStr: fundRespCD.Txid,
370+
},
371+
}
372+
chanPointEF := &lnrpc.ChannelPoint{
373+
OutputIndex: uint32(fundRespEF.OutputIndex),
374+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
375+
FundingTxidStr: fundRespEF.Txid,
376+
},
377+
}
378+
379+
return chanPointCD, chanPointEF
380+
}
381+
382+
func sendAssetsEqualAmounts(ctx context.Context, t *harnessTest,
383+
recipient, sender, universe *tapClient, mintedAssets []*taprpc.Asset,
384+
assetSendAmount uint64, previousSends uint64) {
385+
386+
numTranches := uint64(len(mintedAssets))
387+
for idx, mintedAsset := range mintedAssets {
388+
assetID := mintedAsset.AssetGenesis.AssetId
389+
trancheAmount := assetSendAmount / numTranches
390+
recipientAddr, err := recipient.NewAddr(
391+
ctx, &taprpc.NewAddrRequest{
392+
Amt: trancheAmount,
393+
AssetId: assetID,
394+
ProofCourierAddr: fmt.Sprintf(
395+
"%s://%s", proof.UniverseRpcCourierType,
396+
universe.node.Cfg.LitAddr(),
397+
),
398+
},
399+
)
400+
require.NoError(t.t, err)
401+
402+
t.Logf("Sending %v asset units to %s...", trancheAmount,
403+
recipient.node.Cfg.Name)
404+
405+
// We assume that we sent the same size in a previous send.
406+
totalSent := trancheAmount + (previousSends * trancheAmount)
407+
408+
// Send the assets to recipient.
409+
itest.AssertAddrCreated(
410+
t.t, recipient, mintedAsset, recipientAddr,
411+
)
412+
sendResp, err := sender.SendAsset(ctx, &taprpc.SendAssetRequest{
413+
TapAddrs: []string{recipientAddr.Encoded},
414+
})
415+
require.NoError(t.t, err)
416+
itest.ConfirmAndAssertOutboundTransfer(
417+
t.t, t.lndHarness.Miner.Client, sender, sendResp,
418+
assetID,
419+
[]uint64{mintedAsset.Amount - totalSent, trancheAmount},
420+
int(previousSends*numTranches)+idx,
421+
int(previousSends*numTranches)+idx+1,
422+
)
423+
itest.AssertNonInteractiveRecvComplete(
424+
t.t, recipient, int(previousSends*numTranches)+idx+1,
425+
)
426+
}
427+
}
428+
293429
func assertNumAssetUTXOs(t *testing.T, tapdClient *tapClient,
294430
numUTXOs int) *taprpc.ListUtxosResponse {
295431

@@ -472,10 +608,13 @@ func assertPendingChannels(t *testing.T, node *HarnessNode,
472608
pendingChan.Channel.CustomChannelData, &pendingJSON,
473609
)
474610
require.NoError(t, err)
475-
require.Len(t, pendingJSON.Assets, 1)
611+
require.GreaterOrEqual(t, len(pendingJSON.Assets), 1)
476612

477613
require.NotZero(t, pendingJSON.Assets[0].Capacity)
478614

615+
pendingFormatted, _ := json.MarshalIndent(pendingJSON, "", " ")
616+
t.Logf("Pending channel: %v", string(pendingFormatted))
617+
479618
// Check the decimal display of the channel funding blob. If no explicit
480619
// value was set, we assume and expect the value of 0.
481620
var expectedDecimalDisplay uint8
@@ -493,9 +632,7 @@ func assertPendingChannels(t *testing.T, node *HarnessNode,
493632
// Check the balance of the pending channel.
494633
assetID := mintedAsset.AssetGenesis.AssetId
495634
pendingLocalBalance, pendingRemoteBalance, _, _ :=
496-
getAssetChannelBalance(
497-
t, node, assetID, true,
498-
)
635+
getAssetChannelBalance(t, node, [][]byte{assetID}, true)
499636
require.EqualValues(t, localSum, pendingLocalBalance)
500637
require.EqualValues(t, remoteSum, pendingRemoteBalance)
501638
}
@@ -621,7 +758,7 @@ func getChannelCustomData(src, dst *HarnessNode) (*rfqmsg.JsonAssetChanInfo,
621758
return &assetData.Assets[0], nil
622759
}
623760

624-
func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
761+
func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetIDs [][]byte,
625762
pending bool) (uint64, uint64, uint64, uint64) {
626763

627764
ctxb := context.Background()
@@ -642,9 +779,19 @@ func getAssetChannelBalance(t *testing.T, node *HarnessNode, assetID []byte,
642779
balances = assetBalance.PendingChannels
643780
}
644781

782+
idMatch := func(assetIDString string) bool {
783+
for _, groupedID := range assetIDs {
784+
if assetIDString == hex.EncodeToString(groupedID) {
785+
return true
786+
}
787+
}
788+
789+
return false
790+
}
791+
645792
var localSum, remoteSum uint64
646793
for assetIDString := range balances {
647-
if assetIDString != hex.EncodeToString(assetID) {
794+
if !idMatch(assetIDString) {
648795
continue
649796
}
650797

@@ -2017,8 +2164,27 @@ func logBalance(t *testing.T, nodes []*HarnessNode, assetID []byte,
20172164
time.Sleep(time.Millisecond * 250)
20182165

20192166
for _, node := range nodes {
2020-
local, remote, localSat, remoteSat :=
2021-
getAssetChannelBalance(t, node, assetID, false)
2167+
local, remote, localSat, remoteSat := getAssetChannelBalance(
2168+
t, node, [][]byte{assetID}, false,
2169+
)
2170+
2171+
t.Logf("%-7s balance: local=%-9d remote=%-9d, localSat=%-9d, "+
2172+
"remoteSat=%-9d (%v)", node.Cfg.Name, local, remote,
2173+
localSat, remoteSat, occasion)
2174+
}
2175+
}
2176+
2177+
func logBalanceGroup(t *testing.T, nodes []*HarnessNode, assetIDs [][]byte,
2178+
occasion string) {
2179+
2180+
t.Helper()
2181+
2182+
time.Sleep(time.Millisecond * 250)
2183+
2184+
for _, node := range nodes {
2185+
local, remote, localSat, remoteSat := getAssetChannelBalance(
2186+
t, node, assetIDs, false,
2187+
)
20222188

20232189
t.Logf("%-7s balance: local=%-9d remote=%-9d, localSat=%-9d, "+
20242190
"remoteSat=%-9d (%v)", node.Cfg.Name, local, remote,

itest/litd_custom_channels_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,133 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness,
11401140
assertAssetBalance(t.t, fabiaTap, assetID, fabiaAssetBalance)
11411141
}
11421142

1143+
func testCustomChannelsGroupedAssetTranches(ctx context.Context,
1144+
net *NetworkHarness, t *harnessTest) {
1145+
1146+
lndArgs := slices.Clone(lndArgsTemplate)
1147+
litdArgs := slices.Clone(litdArgsTemplate)
1148+
1149+
// Explicitly set the proof courier as Zane (now has no other role
1150+
// other than proof shuffling), otherwise a hashmail courier will be
1151+
// used. For the funding transaction, we're just posting it and don't
1152+
// expect a true receiver.
1153+
zane, err := net.NewNode(
1154+
t.t, "Zane", lndArgs, false, true, litdArgs...,
1155+
)
1156+
require.NoError(t.t, err)
1157+
1158+
litdArgs = append(litdArgs, fmt.Sprintf(
1159+
"--taproot-assets.proofcourieraddr=%s://%s",
1160+
proof.UniverseRpcCourierType, zane.Cfg.LitAddr(),
1161+
))
1162+
1163+
// The topology we are going for looks like the following:
1164+
//
1165+
// Charlie --[assets]--> Dave --[sats]--> Erin --[assets]--> Fabia
1166+
//
1167+
// With [assets] being a custom channel and [sats] being a normal, BTC
1168+
// only channel.
1169+
charlie, err := net.NewNode(
1170+
t.t, "Charlie", lndArgs, false, true, litdArgs...,
1171+
)
1172+
require.NoError(t.t, err)
1173+
1174+
dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...)
1175+
require.NoError(t.t, err)
1176+
erin, err := net.NewNode(t.t, "Erin", lndArgs, false, true, litdArgs...)
1177+
require.NoError(t.t, err)
1178+
fabia, err := net.NewNode(
1179+
t.t, "Fabia", lndArgs, false, true, litdArgs...,
1180+
)
1181+
require.NoError(t.t, err)
1182+
1183+
nodes := []*HarnessNode{charlie, dave, erin, fabia}
1184+
connectAllNodes(t.t, net, nodes)
1185+
fundAllNodes(t.t, net, nodes)
1186+
1187+
// Create the normal channel between Dave and Erin.
1188+
t.Logf("Opening normal channel between Dave and Erin...")
1189+
channelOp := openChannelAndAssert(
1190+
t, net, dave, erin, lntest.OpenChannelParams{
1191+
Amt: 5_000_000,
1192+
SatPerVByte: 5,
1193+
},
1194+
)
1195+
defer closeChannelAndAssert(t, net, dave, channelOp, false)
1196+
1197+
// This is the only public channel, we need everyone to be aware of it.
1198+
assertChannelKnown(t.t, charlie, channelOp)
1199+
assertChannelKnown(t.t, fabia, channelOp)
1200+
1201+
universeTap := newTapClient(t.t, zane)
1202+
charlieTap := newTapClient(t.t, charlie)
1203+
daveTap := newTapClient(t.t, dave)
1204+
erinTap := newTapClient(t.t, erin)
1205+
fabiaTap := newTapClient(t.t, fabia)
1206+
1207+
groupAssetReq := itest.CopyRequest(&mintrpc.MintAssetRequest{
1208+
Asset: itestAsset,
1209+
})
1210+
groupAssetReq.Asset.NewGroupedAsset = true
1211+
1212+
// Mint the asset tranches 1 and 2 on Charlie and sync all nodes to
1213+
// Charlie as the universe.
1214+
mintedAssetsT1 := itest.MintAssetsConfirmBatch(
1215+
t.t, t.lndHarness.Miner.Client, charlieTap,
1216+
[]*mintrpc.MintAssetRequest{groupAssetReq},
1217+
)
1218+
centsT1 := mintedAssetsT1[0]
1219+
assetID1 := centsT1.AssetGenesis.AssetId
1220+
groupKey := centsT1.GetAssetGroup().GetTweakedGroupKey()
1221+
1222+
groupAssetReq = itest.CopyRequest(&mintrpc.MintAssetRequest{
1223+
Asset: itestAsset,
1224+
})
1225+
groupAssetReq.Asset.GroupedAsset = true
1226+
groupAssetReq.Asset.GroupKey = groupKey
1227+
groupAssetReq.Asset.Name = "itest-asset-cents-tranche-2"
1228+
1229+
mintedAssetsT2 := itest.MintAssetsConfirmBatch(
1230+
t.t, t.lndHarness.Miner.Client, charlieTap,
1231+
[]*mintrpc.MintAssetRequest{groupAssetReq},
1232+
)
1233+
centsT2 := mintedAssetsT2[0]
1234+
assetID2 := centsT2.AssetGenesis.AssetId
1235+
1236+
t.Logf("Minted lightning cents tranche 1 (%x) and 2 (%x) for group "+
1237+
"key %x, syncing universes...", assetID1, assetID2, groupKey)
1238+
syncUniverses(t.t, charlieTap, dave, erin, fabia)
1239+
t.Logf("Universes synced between all nodes, distributing assets...")
1240+
1241+
chanPointCD, chanPointEF := createTestAssetNetworkGroupKey(
1242+
ctx, t, net, charlieTap, daveTap, erinTap, fabiaTap,
1243+
universeTap, []*taprpc.Asset{centsT1, centsT2}, startAmount,
1244+
fundingAmount, fundingAmount, DefaultPushSat,
1245+
)
1246+
1247+
t.Logf("Created channels %v and %v", chanPointCD, chanPointEF)
1248+
1249+
// We now send some assets over the channels to test the functionality.
1250+
// Print initial channel balances.
1251+
groupIDs := [][]byte{assetID1, assetID2}
1252+
logBalanceGroup(t.t, nodes, groupIDs, "initial")
1253+
1254+
// ------------
1255+
// Test case 1: Send a direct keysend payment from Charlie to Dave.
1256+
// ------------
1257+
const keySendAmount = 100
1258+
sendAssetKeySendPayment(
1259+
t.t, charlie, dave, keySendAmount, assetID1, fn.None[int64](),
1260+
)
1261+
logBalanceGroup(t.t, nodes, groupIDs, "after keysend")
1262+
1263+
t.Logf("Closing Charlie -> Dave channel")
1264+
closeAssetChannelAndAssert(
1265+
t, net, charlie, dave, chanPointCD, assetID1, groupKey,
1266+
universeTap, noOpCoOpCloseBalanceCheck,
1267+
)
1268+
}
1269+
11431270
// testCustomChannelsForceClose tests a force close scenario after both parties
11441271
// have an active asset balance.
11451272
func testCustomChannelsForceClose(ctx context.Context, net *NetworkHarness,

itest/litd_test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ var allTestCases = []*testCase{
3636
name: "test custom channels grouped asset",
3737
test: testCustomChannelsGroupedAsset,
3838
},
39+
{
40+
name: "test custom channels grouped asset tranches",
41+
test: testCustomChannelsGroupedAssetTranches,
42+
},
3943
{
4044
name: "test custom channels force close",
4145
test: testCustomChannelsForceClose,

0 commit comments

Comments
 (0)