@@ -1370,6 +1370,315 @@ func testCustomChannelsGroupTranchesForceClose(ctx context.Context,
1370
1370
)
1371
1371
}
1372
1372
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
+
1373
1682
// testCustomChannelsForceClose tests a force close scenario after both parties
1374
1683
// have an active asset balance.
1375
1684
func testCustomChannelsForceClose (ctx context.Context , net * NetworkHarness ,
0 commit comments