diff --git a/itest/assets_test.go b/itest/assets_test.go index 09a2101bb..9a9cf244e 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -47,7 +47,10 @@ import ( ) // PaymentTimeout is the default payment timeout we use in our tests. -const PaymentTimeout = 6 * time.Second +const ( + PaymentTimeout = 6 * time.Second + DefaultPushSat int64 = 1062 +) // createTestAssetNetwork sends asset funds from Charlie to Dave and Erin, so // they can fund asset channels with Yara and Fabia, respectively. So the asset @@ -57,7 +60,7 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, universeTap *tapClient, mintedAsset *taprpc.Asset, assetSendAmount, charlieFundingAmount, daveFundingAmount, - erinFundingAmount uint64) (*tchrpc.FundChannelResponse, + erinFundingAmount uint64, pushSat int64) (*tchrpc.FundChannelResponse, *tchrpc.FundChannelResponse, *tchrpc.FundChannelResponse) { ctxb := context.Background() @@ -136,7 +139,7 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, AssetId: assetID, PeerPubkey: daveTap.node.PubKey[:], FeeRateSatPerVbyte: 5, - PushSat: 1065, + PushSat: pushSat, }, ) require.NoError(t.t, err) @@ -159,7 +162,7 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, AssetId: assetID, PeerPubkey: fabiaTap.node.PubKey[:], FeeRateSatPerVbyte: 5, - PushSat: 1065, + PushSat: pushSat, }, ) require.NoError(t.t, err) diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index fa54d15d1..883e5834c 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -188,7 +188,7 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness, fundRespCD, _, _ := createTestAssetNetwork( t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, universeTap, cents, 400_000, charlieFundingAmount, - daveFundingAmount, erinFundingAmount, + daveFundingAmount, erinFundingAmount, DefaultPushSat, ) // Before we start sending out payments, let's make sure each node can @@ -370,7 +370,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, fundRespCD, fundRespDY, fundRespEF := createTestAssetNetwork( t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, universeTap, cents, startAmount, charlieFundingAmount, - daveFundingAmount, erinFundingAmount, + daveFundingAmount, erinFundingAmount, DefaultPushSat, ) // We'll be tracking the expected asset balances throughout the test, so @@ -829,7 +829,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness, fundRespCD, fundRespDY, fundRespEF := createTestAssetNetwork( t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, universeTap, cents, startAmount, charlieFundingAmount, - daveFundingAmount, erinFundingAmount, + daveFundingAmount, erinFundingAmount, DefaultPushSat, ) // We'll be tracking the expected asset balances throughout the test, so @@ -1690,10 +1690,13 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness, func testCustomChannelsLiquidityEdgeCases(_ context.Context, net *NetworkHarness, t *harnessTest) { - ctxb := context.Background() lndArgs := slices.Clone(lndArgsTemplate) litdArgs := slices.Clone(litdArgsTemplate) + // Explicitly set the proof courier as Alice (how has no other role + // other than proof shuffling), otherwise a hashmail courier will be + // used. For the funding transaction, we're just posting it and don't + // expect a true receiver. zane, err := net.NewNode( t.t, "Zane", lndArgs, false, true, litdArgs..., ) @@ -1704,16 +1707,41 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, proof.UniverseRpcCourierType, zane.Cfg.LitAddr(), )) + // The topology we are going for looks like the following: + // + // Charlie --[assets]--> Dave --[sats]--> Erin --[assets]--> Fabia + // | + // | + // [assets] + // | + // v + // Yara + // + // With [assets] being a custom channel and [sats] being a normal, BTC + // only channel. + // All 5 nodes need to be full litd nodes running in integrated mode + // with tapd included. We also need specific flags to be enabled, so we + // create 5 completely new nodes, ignoring the two default nodes that + // are created by the harness. charlie, err := net.NewNode( t.t, "Charlie", lndArgs, false, true, litdArgs..., ) require.NoError(t.t, err) + dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...) require.NoError(t.t, err) erin, err := net.NewNode(t.t, "Erin", lndArgs, false, true, litdArgs...) require.NoError(t.t, err) + fabia, err := net.NewNode( + t.t, "Fabia", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + yara, err := net.NewNode( + t.t, "Yara", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) - nodes := []*HarnessNode{charlie, dave, erin} + nodes := []*HarnessNode{charlie, dave, erin, fabia, yara} connectAllNodes(t.t, net, nodes) fundAllNodes(t.t, net, nodes) @@ -1721,19 +1749,25 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, t.Logf("Opening normal channel between Dave and Erin...") channelOp := openChannelAndAssert( t, net, dave, erin, lntest.OpenChannelParams{ - Amt: 5_000_000, + Amt: 10_000_000, SatPerVByte: 5, }, ) defer closeChannelAndAssert(t, net, dave, channelOp, false) + // This is the only public channel, we need everyone to be aware of it. assertChannelKnown(t.t, charlie, channelOp) + assertChannelKnown(t.t, fabia, channelOp) + universeTap := newTapClient(t.t, zane) charlieTap := newTapClient(t.t, charlie) daveTap := newTapClient(t.t, dave) - universeTap := newTapClient(t.t, zane) + erinTap := newTapClient(t.t, erin) + fabiaTap := newTapClient(t.t, fabia) + yaraTap := newTapClient(t.t, yara) - // Mint an asset on Charlie and sync Dave to Charlie as the universe. + // Mint an asset on Charlie and sync all nodes to Charlie as the + // universe. mintedAssets := itest.MintAssetsConfirmBatch( t.t, t.lndHarness.Miner.Client, charlieTap, []*mintrpc.MintAssetRequest{ @@ -1744,62 +1778,32 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, ) cents := mintedAssets[0] assetID := cents.AssetGenesis.AssetId - var groupKey []byte - if cents.AssetGroup != nil { - groupKey = cents.AssetGroup.TweakedGroupKey - } - fundingScriptTree := tapchannel.NewFundingScriptTree() - fundingScriptKey := fundingScriptTree.TaprootKey - fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount) - syncUniverses(t.t, charlieTap, dave) + syncUniverses(t.t, charlieTap, dave, erin, fabia, yara) t.Logf("Universes synced between all nodes, distributing assets...") - charlieBalance := cents.Amount - - fundRespCD, err := charlieTap.FundChannel( - ctxb, &tchrpc.FundChannelRequest{ - AssetAmount: charlieBalance, - AssetId: assetID, - PeerPubkey: daveTap.node.PubKey[:], - FeeRateSatPerVbyte: 5, - PushSat: 0, - }, - ) - require.NoError(t.t, err) - t.Logf("Funded channel between Charlie and Dave: %v", fundRespCD) - - // Make sure the pending channel shows up in the list and has the - // custom records set as JSON. - assertPendingChannels( - t.t, charlieTap.node, assetID, 1, charlieBalance, 0, - ) - - // Let's confirm the channel. - mineBlocks(t, net, 6, 1) - - assertAssetBalance(t.t, charlieTap, assetID, cents.Amount) - - // There should only be a single asset piece for Charlie, the one in the - // channel. - assertNumAssetOutputs(t.t, charlieTap, assetID, 1) - assertAssetExists( - t.t, charlieTap, assetID, charlieBalance, - fundingScriptKey, false, true, true, + const ( + daveFundingAmount = uint64(400_000) + erinFundingAmount = uint64(200_000) ) + charlieFundingAmount := cents.Amount - uint64(2*400_000) - // Assert that the proofs for both channels has been uploaded to the - // designated Universe server. - assertUniverseProofExists( - t.t, universeTap, assetID, groupKey, fundingScriptTreeBytes, - fmt.Sprintf("%v:%v", fundRespCD.Txid, fundRespCD.OutputIndex), + _, _, _ = createTestAssetNetwork( + t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap, + universeTap, cents, 400_000, charlieFundingAmount, + daveFundingAmount, erinFundingAmount, 0, ) - // Make sure the channel shows the correct asset information. - assertAssetChan( - t.t, charlieTap.node, daveTap.node, charlieBalance, assetID, - ) + // Before we start sending out payments, let's make sure each node can + // see the other one in the graph and has all required features. + require.NoError(t.t, t.lndHarness.AssertNodeKnown(charlie, dave)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(dave, charlie)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(dave, yara)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(yara, dave)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(erin, fabia)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(fabia, erin)) + require.NoError(t.t, t.lndHarness.AssertNodeKnown(charlie, erin)) logBalance(t.t, nodes, assetID, "initial") @@ -1889,8 +1893,51 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, // Pay a bolt11 invoice with assets, which evaluates to more than the // channel btc capacity. _ = createAndPayNormalInvoice( - t.t, charlie, dave, erin, 220_000, assetID, true, + t.t, charlie, dave, erin, 1_000_000, assetID, true, + ) + + logBalance(t.t, nodes, assetID, "after big asset payment (btc "+ + "invoice, multi-hop)") + + // Edge case: Big asset invoice paid by direct peer with assets. + invoiceResp := createAssetInvoice( + t.t, charlie, dave, 100_000, assetID, + ) + + payInvoiceWithAssets(t.t, charlie, dave, invoiceResp, assetID, false) + + logBalance(t.t, nodes, assetID, "after big asset payment (asset "+ + "invoice, direct)") + + // Edge case: Big normal invoice, paid by direct channel peer with + // assets. + _ = createAndPayNormalInvoice( + t.t, dave, charlie, charlie, 1_000_000, assetID, true, + ) + + logBalance(t.t, nodes, assetID, "after big asset payment (btc "+ + "invoice, direct)") + + // Dave sends 200k assets and 2k sats to Yara. + sendAssetKeySendPayment( + t.t, dave, yara, 200_000, assetID, + fn.None[int64](), lnrpc.Payment_SUCCEEDED, + fn.None[lnrpc.PaymentFailureReason](), ) + sendKeySendPayment(t.t, dave, yara, 2000) + + logBalance(t.t, nodes, assetID, "after 200k assets to Yara") + + // Edge case: Now Charlie creates a big asset invoice to be paid for by + // Yara with assets. This is a multi-hop payment going over 2 asset + // channels, where the total asset value exceeds the btc capacity of the + // channels. + invoiceResp = createAssetInvoice( + t.t, dave, charlie, 100_000, assetID, + ) + + payInvoiceWithAssets(t.t, yara, dave, invoiceResp, assetID, false) - logBalance(t.t, nodes, assetID, "after giant asset payment") + logBalance(t.t, nodes, assetID, "after big asset payment (asset "+ + "invoice, multi-hop)") }