@@ -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.
13751684func testCustomChannelsForceClose (ctx context.Context , net * NetworkHarness ,
0 commit comments