Skip to content

Commit 7a0f5d9

Browse files
committed
itest: add grouped asset multi tranche HTLC test
1 parent 575fd84 commit 7a0f5d9

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

itest/litd_custom_channels_test.go

+309
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,315 @@ func testCustomChannelsGroupTranchesForceClose(ctx context.Context,
13701370
)
13711371
}
13721372

1373+
// testCustomChannelsGroupTranchesHtlcForceClose tests that we can successfully
1374+
// open a custom channel with multiple pieces of a grouped asset, then force
1375+
// close it while having pending HTLCs. We then test that we can successfully
1376+
// sweep all balances from those HTLCs.
1377+
func testCustomChannelsGroupTranchesHtlcForceClose(ctx context.Context,
1378+
net *NetworkHarness, t *harnessTest) {
1379+
1380+
lndArgs := slices.Clone(lndArgsTemplate)
1381+
litdArgs := slices.Clone(litdArgsTemplate)
1382+
1383+
// We use Charlie as the proof courier. But in order for Charlie to also
1384+
// use itself, we need to define its port upfront.
1385+
charliePort := port.NextAvailablePort()
1386+
litdArgs = append(litdArgs, fmt.Sprintf(
1387+
"--taproot-assets.proofcourieraddr=%s://%s",
1388+
proof.UniverseRpcCourierType,
1389+
fmt.Sprintf(node.ListenerFormat, charliePort),
1390+
))
1391+
1392+
// The topology we are going for looks like the following:
1393+
//
1394+
// Charlie --[assets]--> Dave --[sats]--> Erin --[assets]--> Fabia
1395+
//
1396+
// With [assets] being a custom channel and [sats] being a normal, BTC
1397+
// only channel.
1398+
charlie, err := net.NewNodeWithPort(
1399+
t.t, "Charlie", lndArgs, false, true, charliePort, litdArgs...,
1400+
)
1401+
require.NoError(t.t, err)
1402+
1403+
dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...)
1404+
require.NoError(t.t, err)
1405+
erin, err := net.NewNode(t.t, "Erin", lndArgs, false, true, litdArgs...)
1406+
require.NoError(t.t, err)
1407+
fabia, err := net.NewNode(
1408+
t.t, "Fabia", lndArgs, false, true, litdArgs...,
1409+
)
1410+
require.NoError(t.t, err)
1411+
1412+
nodes := []*HarnessNode{charlie, dave, erin, fabia}
1413+
connectAllNodes(t.t, net, nodes)
1414+
fundAllNodes(t.t, net, nodes)
1415+
1416+
// Create the normal channel between Dave and Erin.
1417+
t.Logf("Opening normal channel between Dave and Erin...")
1418+
channelOp := openChannelAndAssert(
1419+
t, net, dave, erin, lntest.OpenChannelParams{
1420+
Amt: 5_000_000,
1421+
SatPerVByte: 5,
1422+
},
1423+
)
1424+
defer closeChannelAndAssert(t, net, dave, channelOp, false)
1425+
1426+
// This is the only public channel, we need everyone to be aware of it.
1427+
assertChannelKnown(t.t, charlie, channelOp)
1428+
assertChannelKnown(t.t, fabia, channelOp)
1429+
1430+
universeTap := newTapClient(t.t, charlie)
1431+
charlieTap := newTapClient(t.t, charlie)
1432+
daveTap := newTapClient(t.t, dave)
1433+
erinTap := newTapClient(t.t, erin)
1434+
fabiaTap := newTapClient(t.t, fabia)
1435+
1436+
groupAssetReq := itest.CopyRequest(&mintrpc.MintAssetRequest{
1437+
Asset: itestAsset,
1438+
})
1439+
groupAssetReq.Asset.NewGroupedAsset = true
1440+
1441+
// Mint the asset tranches 1 and 2 on Charlie and sync all nodes to
1442+
// Charlie as the universe.
1443+
mintedAssetsT1 := itest.MintAssetsConfirmBatch(
1444+
t.t, t.lndHarness.Miner.Client, charlieTap,
1445+
[]*mintrpc.MintAssetRequest{groupAssetReq},
1446+
)
1447+
centsT1 := mintedAssetsT1[0]
1448+
assetID1 := centsT1.AssetGenesis.AssetId
1449+
groupKey := centsT1.GetAssetGroup().GetTweakedGroupKey()
1450+
1451+
groupAssetReq = itest.CopyRequest(&mintrpc.MintAssetRequest{
1452+
Asset: itestAsset,
1453+
})
1454+
groupAssetReq.Asset.GroupedAsset = true
1455+
groupAssetReq.Asset.GroupKey = groupKey
1456+
groupAssetReq.Asset.Name = "itest-asset-cents-tranche-2"
1457+
1458+
mintedAssetsT2 := itest.MintAssetsConfirmBatch(
1459+
t.t, t.lndHarness.Miner.Client, charlieTap,
1460+
[]*mintrpc.MintAssetRequest{groupAssetReq},
1461+
)
1462+
centsT2 := mintedAssetsT2[0]
1463+
assetID2 := centsT2.AssetGenesis.AssetId
1464+
1465+
t.Logf("Minted lightning cents tranche 1 (%x) and 2 (%x) for group "+
1466+
"key %x, syncing universes...", assetID1, assetID2, groupKey)
1467+
syncUniverses(t.t, charlieTap, dave, erin, fabia)
1468+
t.Logf("Universes synced between all nodes, distributing assets...")
1469+
1470+
chanPointCD, chanPointEF := createTestAssetNetworkGroupKey(
1471+
ctx, t, net, charlieTap, daveTap, erinTap, fabiaTap,
1472+
universeTap, []*taprpc.Asset{centsT1, centsT2},
1473+
fundingAmount, fundingAmount, DefaultPushSat,
1474+
)
1475+
1476+
t.Logf("Created channels %v and %v", chanPointCD, chanPointEF)
1477+
1478+
// We now send some assets over the channels to test the functionality.
1479+
// Print initial channel balances.
1480+
groupIDs := [][]byte{assetID1, assetID2}
1481+
logBalanceGroup(t.t, nodes, groupIDs, "initial")
1482+
1483+
// First, we'll send over some funds from Charlie to Dave, as we want
1484+
// Dave to be able to extend HTLCs in the other direction.
1485+
const (
1486+
numPayments = 10
1487+
keySendAmount = 2_500
1488+
)
1489+
for i := 0; i < numPayments; i++ {
1490+
sendAssetKeySendPayment(
1491+
t.t, charlie, dave, keySendAmount, nil,
1492+
fn.None[int64](), withGroupKey(groupKey),
1493+
)
1494+
}
1495+
1496+
// Now that both parties have some funds, we'll move onto the main test.
1497+
//
1498+
// We'll make 2 hodl invoice for each peer, so 4 total. From Charlie's
1499+
// PoV, he'll have 6 outgoing HTLCs, and two incoming HTLCs.
1500+
var (
1501+
daveHodlInvoices []assetHodlInvoice
1502+
charlieHodlInvoices []assetHodlInvoice
1503+
1504+
// The default oracle rate is 17_180 mSat/asset unit, so 10_000
1505+
// will be equal to 171_800_000 mSat. When we use the mpp bool
1506+
// for the smallShards param of payInvoiceWithAssets, that
1507+
// means we'll split the payment into shards of 80_000_000 mSat
1508+
// max. So we'll get three shards per payment.
1509+
assetInvoiceAmt = 10_000
1510+
assetsPerMPPShard = 4656
1511+
)
1512+
for i := 0; i < 2; i++ {
1513+
daveHodlInvoices = append(
1514+
daveHodlInvoices, createAssetHodlInvoice(
1515+
t.t, charlie, dave, uint64(assetInvoiceAmt),
1516+
nil, withInvGroupKey(groupKey),
1517+
),
1518+
)
1519+
charlieHodlInvoices = append(
1520+
charlieHodlInvoices, createAssetHodlInvoice(
1521+
t.t, dave, charlie, uint64(assetInvoiceAmt),
1522+
nil, withInvGroupKey(groupKey),
1523+
),
1524+
)
1525+
}
1526+
1527+
// Now we'll have both Dave and Charlie pay each other's invoices. We
1528+
// only care that they're in flight at this point, as they won't be
1529+
// settled yet.
1530+
baseOpts := []payOpt{
1531+
withGroupKey(groupKey),
1532+
withFailure(
1533+
lnrpc.Payment_IN_FLIGHT,
1534+
lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
1535+
),
1536+
}
1537+
for _, charlieInvoice := range charlieHodlInvoices {
1538+
// For this direction, we also want to enforce MPP.
1539+
opts := append(slices.Clone(baseOpts), withSmallShards())
1540+
payInvoiceWithAssets(
1541+
t.t, dave, charlie, charlieInvoice.payReq, nil, opts...,
1542+
)
1543+
}
1544+
for _, daveInvoice := range daveHodlInvoices {
1545+
payInvoiceWithAssets(
1546+
t.t, charlie, dave, daveInvoice.payReq, nil,
1547+
baseOpts...,
1548+
)
1549+
}
1550+
1551+
// Make sure we can sweep all the HTLCs.
1552+
const charlieStartAmount = 2
1553+
charlieExpectedBalance, _ := assertForceCloseSweeps(
1554+
ctx, net, t, charlie, dave, chanPointCD, charlieStartAmount,
1555+
assetInvoiceAmt, assetsPerMPPShard, nil, groupKey,
1556+
charlieHodlInvoices, daveHodlInvoices, true,
1557+
)
1558+
1559+
// Finally, we'll assert that Charlie's balance has been incremented by
1560+
// the timeout value.
1561+
charlieExpectedBalance += uint64(assetInvoiceAmt - 1)
1562+
t.Logf("Expecting Charlie's balance to be %d", charlieExpectedBalance)
1563+
assertSpendableBalance(
1564+
t.t, charlieTap, nil, groupKey, charlieExpectedBalance,
1565+
)
1566+
1567+
t.Logf("Sending all settled funds to Fabia")
1568+
1569+
// As a final sanity check, both Charlie and Dave should be able to send
1570+
// their entire balances to Fabia, our 3rd party.
1571+
//
1572+
// We'll make two addrs for Fabia, one for Charlie, and one for Dave.
1573+
charlieSpendableBalanceAsset1, err := spendableBalance(
1574+
charlieTap, assetID1, nil,
1575+
)
1576+
require.NoError(t.t, err)
1577+
charlieSpendableBalanceAsset2, err := spendableBalance(
1578+
charlieTap, assetID2, nil,
1579+
)
1580+
require.NoError(t.t, err)
1581+
1582+
t.Logf("Charlie's spendable balance asset 1: %d, asset 2: %d",
1583+
charlieSpendableBalanceAsset1, charlieSpendableBalanceAsset2)
1584+
1585+
fabiaCourierAddr := fmt.Sprintf(
1586+
"%s://%s", proof.UniverseRpcCourierType,
1587+
fabiaTap.node.Cfg.LitAddr(),
1588+
)
1589+
charlieAddr1, err := fabiaTap.NewAddr(ctx, &taprpc.NewAddrRequest{
1590+
Amt: charlieSpendableBalanceAsset1,
1591+
AssetId: assetID1,
1592+
ProofCourierAddr: fabiaCourierAddr,
1593+
})
1594+
require.NoError(t.t, err)
1595+
charlieAddr2, err := fabiaTap.NewAddr(ctx, &taprpc.NewAddrRequest{
1596+
Amt: charlieSpendableBalanceAsset2,
1597+
AssetId: assetID2,
1598+
ProofCourierAddr: fabiaCourierAddr,
1599+
})
1600+
require.NoError(t.t, err)
1601+
1602+
daveSpendableBalanceAsset1, err := spendableBalance(
1603+
daveTap, assetID1, nil,
1604+
)
1605+
require.NoError(t.t, err)
1606+
daveSpendableBalanceAsset2, err := spendableBalance(
1607+
daveTap, assetID2, nil,
1608+
)
1609+
require.NoError(t.t, err)
1610+
1611+
t.Logf("Daves's spendable balance asset 1: %d, asset 2: %d",
1612+
daveSpendableBalanceAsset1, daveSpendableBalanceAsset2)
1613+
1614+
daveAddr1, err := fabiaTap.NewAddr(ctx, &taprpc.NewAddrRequest{
1615+
Amt: daveSpendableBalanceAsset1,
1616+
AssetId: assetID1,
1617+
ProofCourierAddr: fabiaCourierAddr,
1618+
})
1619+
require.NoError(t.t, err)
1620+
daveAddr2, err := fabiaTap.NewAddr(ctx, &taprpc.NewAddrRequest{
1621+
Amt: daveSpendableBalanceAsset2,
1622+
AssetId: assetID2,
1623+
ProofCourierAddr: fabiaCourierAddr,
1624+
})
1625+
require.NoError(t.t, err)
1626+
1627+
_, err = charlieTap.SendAsset(ctx, &taprpc.SendAssetRequest{
1628+
TapAddrs: []string{charlieAddr1.Encoded},
1629+
})
1630+
require.NoError(t.t, err)
1631+
mineBlocks(t, net, 1, 1)
1632+
1633+
itest.AssertNonInteractiveRecvComplete(t.t, fabiaTap, 1)
1634+
1635+
ctxb := context.Background()
1636+
charlieAssets, err := charlieTap.ListAssets(
1637+
ctxb, &taprpc.ListAssetRequest{
1638+
IncludeSpent: true,
1639+
},
1640+
)
1641+
require.NoError(t.t, err)
1642+
charlieTransfers, err := charlieTap.ListTransfers(
1643+
ctxb, &taprpc.ListTransfersRequest{},
1644+
)
1645+
require.NoError(t.t, err)
1646+
1647+
t.Logf("Charlie's assets: %v", toProtoJSON(t.t, charlieAssets))
1648+
t.Logf("Charlie's transfers: %v", toProtoJSON(t.t, charlieTransfers))
1649+
1650+
_, err = charlieTap.SendAsset(ctx, &taprpc.SendAssetRequest{
1651+
TapAddrs: []string{charlieAddr2.Encoded},
1652+
})
1653+
require.NoError(t.t, err)
1654+
mineBlocks(t, net, 1, 1)
1655+
1656+
itest.AssertNonInteractiveRecvComplete(t.t, fabiaTap, 2)
1657+
1658+
_, err = daveTap.SendAsset(ctx, &taprpc.SendAssetRequest{
1659+
TapAddrs: []string{daveAddr1.Encoded},
1660+
})
1661+
require.NoError(t.t, err)
1662+
mineBlocks(t, net, 1, 1)
1663+
1664+
itest.AssertNonInteractiveRecvComplete(t.t, fabiaTap, 3)
1665+
1666+
_, err = daveTap.SendAsset(ctx, &taprpc.SendAssetRequest{
1667+
TapAddrs: []string{daveAddr2.Encoded},
1668+
})
1669+
require.NoError(t.t, err)
1670+
mineBlocks(t, net, 1, 1)
1671+
1672+
itest.AssertNonInteractiveRecvComplete(t.t, fabiaTap, 4)
1673+
1674+
// Fabia's balance should now be the sum of Charlie's and Dave's
1675+
// balances.
1676+
fabiaExpectedBalance := uint64(50_002)
1677+
assertSpendableBalance(
1678+
t.t, fabiaTap, nil, groupKey, fabiaExpectedBalance,
1679+
)
1680+
}
1681+
13731682
// testCustomChannelsForceClose tests a force close scenario after both parties
13741683
// have an active asset balance.
13751684
func testCustomChannelsForceClose(ctx context.Context, net *NetworkHarness,

itest/litd_test_list_on_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ var allTestCases = []*testCase{
4444
test: testCustomChannelsGroupTranchesForceClose,
4545
noAliceBob: true,
4646
},
47+
{
48+
name: "custom channels group tranches htlc force close",
49+
test: testCustomChannelsGroupTranchesHtlcForceClose,
50+
noAliceBob: true,
51+
},
4752
{
4853
name: "custom channels force close",
4954
test: testCustomChannelsForceClose,

0 commit comments

Comments
 (0)