From 666d85e76ab1de738a61b8836a59350100ba4627 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 26 Mar 2025 18:22:39 +0100 Subject: [PATCH 1/2] build: bump tapd --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 73765a756..deec0042e 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/lightninglabs/pool v0.6.5-beta.0.20250305125211-4e860ec4e77f github.com/lightninglabs/pool/auctioneerrpc v1.1.3-0.20250305125211-4e860ec4e77f github.com/lightninglabs/pool/poolrpc v1.0.1-0.20250305125211-4e860ec4e77f - github.com/lightninglabs/taproot-assets v0.5.2-0.20250401150538-a9ea76a9ed3c + github.com/lightninglabs/taproot-assets v0.5.2-0.20250416114205-2da076df4b4e github.com/lightningnetwork/lnd v0.19.0-beta.rc1.0.20250327183348-eb822a5e117f github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/clock v1.1.1 diff --git a/go.sum b/go.sum index e7489a642..72e9df9c7 100644 --- a/go.sum +++ b/go.sum @@ -1181,8 +1181,8 @@ github.com/lightninglabs/pool/poolrpc v1.0.1-0.20250305125211-4e860ec4e77f h1:5p github.com/lightninglabs/pool/poolrpc v1.0.1-0.20250305125211-4e860ec4e77f/go.mod h1:lGs2hSVZ+GFpdv3btaIl9icG5/gz7BBRfvmD2iqqNl0= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display h1:w7FM5LH9Z6CpKxl13mS48idsu6F+cEZf0lkyiV+Dq9g= github.com/lightninglabs/protobuf-go-hex-display v1.34.2-hex-display/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -github.com/lightninglabs/taproot-assets v0.5.2-0.20250401150538-a9ea76a9ed3c h1:Rebx5DVZx3u327vKRrueFjZNlei1RzdGzFmOZmenkiQ= -github.com/lightninglabs/taproot-assets v0.5.2-0.20250401150538-a9ea76a9ed3c/go.mod h1:e3SjXbbi4xKhOzq54c672Z/j9UTRq5DLJGx/URgVTJo= +github.com/lightninglabs/taproot-assets v0.5.2-0.20250416114205-2da076df4b4e h1:37sk9Wmkh9QFjnqR8eHIhCi8x0uIQL0F2fpcQI25I9g= +github.com/lightninglabs/taproot-assets v0.5.2-0.20250416114205-2da076df4b4e/go.mod h1:e3SjXbbi4xKhOzq54c672Z/j9UTRq5DLJGx/URgVTJo= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb h1:yfM05S8DXKhuCBp5qSMZdtSwvJ+GFzl94KbXMNB1JDY= github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= github.com/lightningnetwork/lnd v0.19.0-beta.rc1.0.20250327183348-eb822a5e117f h1:+Bejv2Ij/ryUjLacBd5au0acMH0AYs0lhb7ki5rx9ms= From 91e5c851d2edb77a96148a76c5f9e94d746a5cb7 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Wed, 5 Mar 2025 20:13:31 +0100 Subject: [PATCH 2/2] itest: use groupkey on grouped asset & liquidity edge cases itests We add support for group keys on payments and invoices across the itests. This includes all the required helpers and optional arguments to make adding invoices and sending payments group-key enabled. In order to run the liquidity edge cases twice, once using only asset IDs and once using only group keys, we need to extract the main logic into a re-usable function that has the group key option on the top level. If the group key flag is set, then we append the respective group key opt to the payment & invoice related calls. --- itest/assets_test.go | 72 ++++++++++- itest/litd_custom_channels_test.go | 189 ++++++++++++++++++++++------- itest/litd_test_list_on_test.go | 4 + 3 files changed, 214 insertions(+), 51 deletions(-) diff --git a/itest/assets_test.go b/itest/assets_test.go index 06f199ac1..63d59335c 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -741,6 +741,12 @@ func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64, opt(cfg) } + // Nullify assetID if group key is set. RPC methods won't accept both so + // let's prioritize the group key if set. + if len(cfg.groupKey) > 0 { + assetID = nil + } + ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() @@ -770,6 +776,7 @@ func sendAssetKeySendPayment(t *testing.T, src, dst *HarnessNode, amt uint64, stream, err := srcTapd.SendPayment(ctxt, &tchrpc.SendPaymentRequest{ AssetId: assetID, AssetAmount: amt, + GroupKey: cfg.groupKey, PaymentRequest: sendReq, }) require.NoError(t, err) @@ -942,6 +949,7 @@ type payConfig struct { payStatus lnrpc.Payment_PaymentStatus failureReason lnrpc.PaymentFailureReason rfq fn.Option[rfqmsg.ID] + groupKey []byte } func defaultPayConfig() *payConfig { @@ -956,6 +964,12 @@ func defaultPayConfig() *payConfig { type payOpt func(*payConfig) +func withGroupKey(groupKey []byte) payOpt { + return func(c *payConfig) { + c.groupKey = groupKey + } +} + func withSmallShards() payOpt { return func(c *payConfig) { c.smallShards = true @@ -1010,6 +1024,12 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode, opt(cfg) } + // Nullify assetID if group key is set. RPC methods won't accept both so + // let's prioritize the group key if set. + if len(cfg.groupKey) > 0 { + assetID = []byte{} + } + ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() @@ -1041,6 +1061,7 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode, stream, err := payerTapd.SendPayment(ctxt, &tchrpc.SendPaymentRequest{ AssetId: assetID, PeerPubkey: rfqPeer.PubKey[:], + GroupKey: cfg.groupKey, PaymentRequest: sendReq, RfqId: rfqBytes, AllowOverpay: cfg.allowOverpay, @@ -1102,6 +1123,7 @@ func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode, type invoiceConfig struct { errSubStr string + groupKey []byte } func defaultInvoiceConfig() *invoiceConfig { @@ -1118,6 +1140,12 @@ func withInvoiceErrSubStr(errSubStr string) invoiceOpt { } } +func withInvGroupKey(groupKey []byte) invoiceOpt { + return func(c *invoiceConfig) { + c.groupKey = groupKey + } +} + func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, assetAmount uint64, assetID []byte, opts ...invoiceOpt) *lnrpc.AddInvoiceResponse { @@ -1127,6 +1155,12 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, opt(cfg) } + // Nullify assetID if group key is set. RPC methods won't accept both so + // let's prioritize the group key if set. + if len(cfg.groupKey) > 0 { + assetID = []byte{} + } + ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) defer cancel() @@ -1141,6 +1175,7 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, resp, err := dstTapd.AddInvoice(ctxt, &tchrpc.AddInvoiceRequest{ AssetId: assetID, + GroupKey: cfg.groupKey, AssetAmount: assetAmount, PeerPubkey: dstRfqPeer.PubKey[:], InvoiceRequest: &lnrpc.Invoice{ @@ -1185,7 +1220,7 @@ func createAssetInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, // individual HTLCs that arrived for it and that they show the correct asset // amounts for the given ID when decoded. func assertInvoiceHtlcAssets(t *testing.T, node *HarnessNode, - addedInvoice *lnrpc.AddInvoiceResponse, assetID []byte, + addedInvoice *lnrpc.AddInvoiceResponse, assetID []byte, groupID []byte, assetAmount uint64) { ctxb := context.Background() @@ -1204,7 +1239,14 @@ func assertInvoiceHtlcAssets(t *testing.T, node *HarnessNode, t.Logf("Asset invoice: %v", toProtoJSON(t, invoice)) - targetID := hex.EncodeToString(assetID) + var targetID string + switch { + case len(groupID) > 0: + targetID = hex.EncodeToString(groupID) + + case len(assetID) > 0: + targetID = hex.EncodeToString(assetID) + } var totalAssetAmount uint64 for _, htlc := range invoice.Htlcs { @@ -1231,7 +1273,7 @@ func assertInvoiceHtlcAssets(t *testing.T, node *HarnessNode, // individual HTLCs that arrived for it and that they show the correct asset // amounts for the given ID when decoded. func assertPaymentHtlcAssets(t *testing.T, node *HarnessNode, payHash []byte, - assetID []byte, assetAmount uint64) { + assetID []byte, groupID []byte, assetAmount uint64) { ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -1252,7 +1294,14 @@ func assertPaymentHtlcAssets(t *testing.T, node *HarnessNode, payHash []byte, t.Logf("Asset payment: %v", toProtoJSON(t, payment)) - targetID := hex.EncodeToString(assetID) + var targetID string + switch { + case len(groupID) > 0: + targetID = hex.EncodeToString(groupID) + + case len(assetID) > 0: + targetID = hex.EncodeToString(assetID) + } var totalAssetAmount uint64 for _, htlc := range payment.Htlcs { @@ -1282,7 +1331,19 @@ type assetHodlInvoice struct { } func createAssetHodlInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, - assetAmount uint64, assetID []byte) assetHodlInvoice { + assetAmount uint64, assetID []byte, + opts ...invoiceOpt) assetHodlInvoice { + + cfg := defaultInvoiceConfig() + for _, opt := range opts { + opt(cfg) + } + + // Nullify assetID if group key is set. RPC methods won't accept both so + // let's prioritize the group key if set. + if len(cfg.groupKey) > 0 { + assetID = []byte{} + } ctxb := context.Background() ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) @@ -1306,6 +1367,7 @@ func createAssetHodlInvoice(t *testing.T, dstRfqPeer, dst *HarnessNode, resp, err := dstTapd.AddInvoice(ctxt, &tchrpc.AddInvoiceRequest{ AssetId: assetID, + GroupKey: cfg.groupKey, AssetAmount: assetAmount, PeerPubkey: dstRfqPeer.PubKey[:], InvoiceRequest: &lnrpc.Invoice{ diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index 71aeb4518..80013c486 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -10,6 +10,7 @@ import ( "slices" "time" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -262,10 +263,11 @@ func testCustomChannelsLarge(_ context.Context, net *NetworkHarness, // sender side show the individual HTLCs that arrived for it and that // they show the correct asset amounts when decoded. assertInvoiceHtlcAssets( - t.t, dave, invoiceResp3, assetID, largeInvoiceAmount, + t.t, dave, invoiceResp3, assetID, nil, largeInvoiceAmount, ) assertPaymentHtlcAssets( - t.t, charlie, invoiceResp3.RHash, assetID, largeInvoiceAmount, + t.t, charlie, invoiceResp3.RHash, assetID, nil, + largeInvoiceAmount, ) // We keysend the rest, so that all the balance is on Dave's side. @@ -451,10 +453,11 @@ func testCustomChannels(ctx context.Context, net *NetworkHarness, // sender side show the individual HTLCs that arrived for it and that // they show the correct asset amounts when decoded. assertInvoiceHtlcAssets( - t.t, charlie, invoiceResp, assetID, charlieInvoiceAmount, + t.t, charlie, invoiceResp, assetID, nil, charlieInvoiceAmount, ) assertPaymentHtlcAssets( - t.t, dave, invoiceResp.RHash, assetID, charlieInvoiceAmount, + t.t, dave, invoiceResp.RHash, assetID, nil, + charlieInvoiceAmount, ) charlieAssetBalance += charlieInvoiceAmount @@ -846,6 +849,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, cents := mintedAssets[0] assetID := cents.AssetGenesis.AssetId groupID := cents.GetAssetGroup().GetTweakedGroupKey() + groupKey, err := btcec.ParsePubKey(groupID) + require.NoError(t.t, err) fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() @@ -892,7 +897,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, // ------------ const keySendAmount = 100 sendAssetKeySendPayment( - t.t, charlie, dave, keySendAmount, assetID, fn.None[int64](), + t.t, charlie, dave, keySendAmount, nil, fn.None[int64](), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after keysend") @@ -920,10 +926,11 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, // invoice. // ------------ createAndPayNormalInvoice( - t.t, charlie, dave, dave, 20_000, assetID, withSmallShards(), + t.t, charlie, dave, dave, 20_000, nil, withSmallShards(), withFailure(lnrpc.Payment_FAILED, failureIncorrectDetails), + withGroupKey(groupID), ) - logBalance(t.t, nodes, assetID, "after invoice") + logBalance(t.t, nodes, assetID, "after failed invoice") // We should also be able to do a multi-hop BTC only payment, paying an // invoice from Erin by Charlie. @@ -937,22 +944,25 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, // ------------ const daveInvoiceAssetAmount = 2_000 invoiceResp := createAssetInvoice( - t.t, charlie, dave, daveInvoiceAssetAmount, assetID, + t.t, charlie, dave, daveInvoiceAssetAmount, nil, + withInvGroupKey(groupID), ) payInvoiceWithAssets( - t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, - withSmallShards(), + t.t, charlie, dave, invoiceResp.PaymentRequest, nil, + withSmallShards(), withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after invoice") + groupBytes := schnorr.SerializePubKey(groupKey) + // Make sure the invoice on the receiver side and the payment on the // sender side show the individual HTLCs that arrived for it and that // they show the correct asset amounts when decoded. assertInvoiceHtlcAssets( - t.t, dave, invoiceResp, assetID, daveInvoiceAssetAmount, + t.t, dave, invoiceResp, nil, groupBytes, daveInvoiceAssetAmount, ) assertPaymentHtlcAssets( - t.t, charlie, invoiceResp.RHash, assetID, + t.t, charlie, invoiceResp.RHash, nil, groupBytes, daveInvoiceAssetAmount, ) @@ -963,7 +973,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, // Test case 4: Pay a normal invoice from Erin by Charlie. // ------------ paidAssetAmount := createAndPayNormalInvoice( - t.t, charlie, dave, erin, 20_000, assetID, withSmallShards(), + t.t, charlie, dave, erin, 20_000, nil, withSmallShards(), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after invoice") @@ -976,7 +987,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, // ------------ const fabiaInvoiceAssetAmount1 = 1000 invoiceResp = createAssetInvoice( - t.t, erin, fabia, fabiaInvoiceAssetAmount1, assetID, + t.t, erin, fabia, fabiaInvoiceAssetAmount1, nil, + withInvGroupKey(groupID), ) payInvoiceWithAssets( t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, @@ -1016,8 +1028,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, t.t, erin, fabia, fabiaInvoiceAssetAmount3, assetID, ) payInvoiceWithAssets( - t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, - withSmallShards(), + t.t, charlie, dave, invoiceResp.PaymentRequest, nil, + withSmallShards(), withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after invoice") @@ -1034,7 +1046,8 @@ func testCustomChannelsGroupedAsset(ctx context.Context, net *NetworkHarness, const yaraInvoiceAssetAmount1 = 1000 invoiceResp = createAssetInvoice( - t.t, dave, yara, yaraInvoiceAssetAmount1, assetID, + t.t, dave, yara, yaraInvoiceAssetAmount1, nil, + withInvGroupKey(groupID), ) payInvoiceWithAssets( t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, @@ -1716,10 +1729,13 @@ func testCustomChannelsBreach(ctx context.Context, net *NetworkHarness, t.Logf("Charlie UTXOs after breach: %v", toProtoJSON(t.t, charlieUTXOs)) } -// testCustomChannelsLiquidityEdgeCases is a test that runs through some -// taproot asset channel liquidity related edge cases. -func testCustomChannelsLiquidityEdgeCases(ctx context.Context, - net *NetworkHarness, t *harnessTest) { +// testCustomChannelsLiquidtyEdgeCasesCore is the core logic of the liquidity +// edge cases. This test goes through certain scenarios that expose edge cases +// and behaviors that proved to be buggy in the past and have been directly +// addressed. It accepts an extra parameter which dictates whether it should use +// group keys or asset IDs. +func testCustomChannelsLiquidtyEdgeCasesCore(ctx context.Context, + net *NetworkHarness, t *harnessTest, groupMode bool) { lndArgs := slices.Clone(lndArgsTemplate) litdArgs := slices.Clone(litdArgsTemplate) @@ -1799,19 +1815,39 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, fabiaTap := newTapClient(t.t, fabia) yaraTap := newTapClient(t.t, yara) + assetReq := itest.CopyRequest(&mintrpc.MintAssetRequest{ + Asset: itestAsset, + }) + + // In order to use group keys in this test, the asset must belong to a + // group. + if groupMode { + assetReq.Asset.NewGroupedAsset = true + } + // 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{ - { - Asset: itestAsset, - }, - }, + []*mintrpc.MintAssetRequest{assetReq}, ) cents := mintedAssets[0] assetID := cents.AssetGenesis.AssetId + // If groupMode is enabled, treat the asset as part of a group by + // assigning its tweaked group key. Otherwise, treat it as an ungrouped + // asset using only its asset ID. + var ( + groupID []byte + groupKey *btcec.PublicKey + ) + if groupMode { + groupID = cents.GetAssetGroup().GetTweakedGroupKey() + + groupKey, err = btcec.ParsePubKey(groupID) + require.NoError(t.t, err) + } + t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount) syncUniverses(t.t, charlieTap, dave, erin, fabia, yara) t.Logf("Universes synced between all nodes, distributing assets...") @@ -1844,6 +1880,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // Send 50 assets from Charlie to Dave. sendAssetKeySendPayment( t.t, charlie, dave, 50, assetID, fn.None[int64](), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after 50 assets") @@ -1870,6 +1907,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, sendAssetKeySendPayment( t.t, dave, charlie, 50, assetID, fn.None[int64](), withFailure(lnrpc.Payment_FAILED, failureNoRoute), + withGroupKey(groupID), ) done <- true @@ -1892,6 +1930,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // enough sats. sendAssetKeySendPayment( t.t, dave, charlie, 50, assetID, fn.None[int64](), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after 50 sats backwards") @@ -1908,6 +1947,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // Pay a normal bolt11 invoice involving RFQ flow. _ = createAndPayNormalInvoice( t.t, charlie, dave, erin, 20_000, assetID, withSmallShards(), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after 20k sat asset payment") @@ -1920,6 +1960,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // channel btc capacity. _ = createAndPayNormalInvoice( t.t, charlie, dave, erin, 1_000_000, assetID, withSmallShards(), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after big asset payment (btc "+ @@ -1927,30 +1968,40 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // Edge case: Big asset invoice paid by direct peer with assets. const bigAssetAmount = 100_000 + invoiceResp := createAssetInvoice( t.t, charlie, dave, bigAssetAmount, assetID, + withInvGroupKey(groupID), ) payInvoiceWithAssets( t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after big asset payment (asset "+ "invoice, direct)") + var groupBytes []byte + if groupMode { + groupBytes = schnorr.SerializePubKey(groupKey) + } + // Make sure the invoice on the receiver side and the payment on the // sender side show the individual HTLCs that arrived for it and that // they show the correct asset amounts when decoded. assertInvoiceHtlcAssets( - t.t, dave, invoiceResp, assetID, bigAssetAmount, + t.t, dave, invoiceResp, assetID, groupBytes, bigAssetAmount, ) assertPaymentHtlcAssets( - t.t, charlie, invoiceResp.RHash, assetID, bigAssetAmount, + t.t, charlie, invoiceResp.RHash, assetID, groupBytes, + bigAssetAmount, ) // Dave sends 200k assets and 5k sats to Yara. sendAssetKeySendPayment( t.t, dave, yara, 2*bigAssetAmount, assetID, fn.None[int64](), + withGroupKey(groupID), ) sendKeySendPayment(t.t, dave, yara, 5_000) @@ -1962,10 +2013,12 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // channels. invoiceResp = createAssetInvoice( t.t, dave, charlie, bigAssetAmount, assetID, + withInvGroupKey(groupID), ) payInvoiceWithAssets( t.t, yara, dave, invoiceResp.PaymentRequest, assetID, + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after big asset payment (asset "+ @@ -1975,9 +2028,12 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // Yara with satoshi. This is a multi-hop payment going over 2 asset // channels, where the total asset value is less than the default anchor // amount of 354 sats. - createAssetInvoice(t.t, dave, charlie, 1, assetID, withInvoiceErrSubStr( - "1 asset units, as the minimal transportable amount", - )) + createAssetInvoice( + t.t, dave, charlie, 1, assetID, withInvoiceErrSubStr( + "1 asset units, as the minimal transportable amount", + ), + withInvGroupKey(groupID), + ) logBalance(t.t, nodes, assetID, "after small payment (asset "+ "invoice, <354sats)") @@ -1992,17 +2048,19 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, ValueMsat: 18_000, }) require.NoError(t.t, err) + payInvoiceWithAssets( t.t, charlie, dave, btcInvoiceResp.PaymentRequest, assetID, withFeeLimit(2_000), withPayErrSubStr( "rejecting payment of 20000 mSAT", - ), + ), withGroupKey(groupID), ) // When we override the uneconomical payment, it should succeed. payInvoiceWithAssets( t.t, charlie, dave, btcInvoiceResp.PaymentRequest, assetID, withFeeLimit(2_000), withAllowOverpay(), + withGroupKey(groupID), ) logBalance( t.t, nodes, assetID, "after small payment (BTC invoice 1 sat)", @@ -2016,11 +2074,12 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, ValueMsat: 1_000, }) require.NoError(t.t, err) + payInvoiceWithAssets( t.t, charlie, dave, btcInvoiceResp.PaymentRequest, assetID, withFeeLimit(1_000), withAllowOverpay(), withPayErrSubStr( "rejecting payment of 2000 mSAT", - ), + ), withGroupKey(groupID), ) // Edge case: Check if the RFQ HTLC tracking accounts for cancelled @@ -2031,20 +2090,34 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // We start by sloshing some funds in the Erin<->Fabia. sendAssetKeySendPayment( t.t, erin, fabia, 100_000, assetID, fn.Some[int64](20_000), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "balance after 1st slosh") + // If we are running this test in group mode, then the manual rfq + // negotiation needs to also happen on the group key. + var assetSpecifier rfqrpc.AssetSpecifier + if groupMode { + assetSpecifier = rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_GroupKey{ + GroupKey: groupID, + }, + } + } else { + assetSpecifier = rfqrpc.AssetSpecifier{ + Id: &rfqrpc.AssetSpecifier_AssetId{ + AssetId: assetID, + }, + } + } + // We create the RFQ order. We set the max amt to ~180k sats which is // going to evaluate to about 10k assets. inOneHour := time.Now().Add(time.Hour) resQ, err := charlieTap.RfqClient.AddAssetSellOrder( ctx, &rfqrpc.AddAssetSellOrderRequest{ - AssetSpecifier: &rfqrpc.AssetSpecifier{ - Id: &rfqrpc.AssetSpecifier_AssetId{ - AssetId: assetID, - }, - }, + AssetSpecifier: &assetSpecifier, PaymentMaxAmt: 180_000_000, Expiry: uint64(inOneHour.Unix()), PeerPubKey: dave.PubKey[:], @@ -2054,16 +2127,20 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, require.NoError(t.t, err) // We now create a hodl invoice on Fabia, for 10k assets. - hodlInv := createAssetHodlInvoice(t.t, erin, fabia, 10_000, assetID) + hodlInv := createAssetHodlInvoice( + t.t, erin, fabia, 10_000, assetID, + withInvGroupKey(groupID), + ) // Charlie tries to pay via Dave, by providing the RFQ quote ID that was // manually created above. var quoteID rfqmsg.ID copy(quoteID[:], resQ.GetAcceptedQuote().Id) + payInvoiceWithAssets( t.t, charlie, dave, hodlInv.payReq, assetID, withSmallShards(), withFailure(lnrpc.Payment_IN_FLIGHT, failureNone), - withRFQ(quoteID), + withRFQ(quoteID), withGroupKey(groupID), ) // We now assert that the expected numbers of HTLCs are present on each @@ -2097,6 +2174,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, // Now Fabia creates the normal invoice. invoiceResp = createAssetInvoice( t.t, erin, fabia, 10_000, assetID, + withInvGroupKey(groupID), ) // Now Charlie pays the invoice, again by using the manually specified @@ -2104,6 +2182,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, payInvoiceWithAssets( t.t, charlie, dave, invoiceResp.PaymentRequest, assetID, withSmallShards(), withRFQ(quoteID), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after manual rfq hodl") @@ -2120,11 +2199,7 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, inOneHour = time.Now().Add(time.Hour) res, err := charlieTap.RfqClient.AddAssetBuyOrder( ctx, &rfqrpc.AddAssetBuyOrderRequest{ - AssetSpecifier: &rfqrpc.AssetSpecifier{ - Id: &rfqrpc.AssetSpecifier_AssetId{ - AssetId: assetID, - }, - }, + AssetSpecifier: &assetSpecifier, AssetMaxAmt: 10_000, Expiry: uint64(inOneHour.Unix()), PeerPubKey: dave.PubKey[:], @@ -2158,11 +2233,32 @@ func testCustomChannelsLiquidityEdgeCases(ctx context.Context, payInvoiceWithSatoshi( t.t, erin, iResp, withPayErrSubStr("context deadline exceeded"), withFailure(lnrpc.Payment_FAILED, failureNone), + withGroupKey(groupID), ) logBalance(t.t, nodes, assetID, "after small manual rfq") } +// testCustomChannelsLiquidityEdgeCases is a test that runs through some +// taproot asset channel liquidity related edge cases. +func testCustomChannelsLiquidityEdgeCases(ctx context.Context, + net *NetworkHarness, t *harnessTest) { + + // Run liquidity edge cases and only use single asset IDs for invoices + // and payments. + testCustomChannelsLiquidtyEdgeCasesCore(ctx, net, t, false) +} + +// testCustomChannelsLiquidityEdgeCasesGroup is a test that runs through some +// taproot asset channel liquidity related edge cases using group keys. +func testCustomChannelsLiquidityEdgeCasesGroup(ctx context.Context, + net *NetworkHarness, t *harnessTest) { + + // Run liquidity edge cases and only use group keys for invoices and + // payments. + testCustomChannelsLiquidtyEdgeCasesCore(ctx, net, t, true) +} + // testCustomChannelsStrictForwarding is a test that tests the strict forwarding // behavior of a node when it comes to paying asset invoices with assets and // BTC invoices with satoshis. @@ -2903,7 +2999,8 @@ func testCustomChannelsOraclePricing(ctx context.Context, net *NetworkHarness, charliePaidMSat, rate, ).ScaleTo(0).ToUint64() assertPaymentHtlcAssets( - t.t, charlie, invoiceResp.RHash, assetID, charliePaidAmount, + t.t, charlie, invoiceResp.RHash, assetID, nil, + charliePaidAmount, ) // We now make sure the asset and satoshi channel balances are exactly diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index cb2496495..04c3f020c 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -48,6 +48,10 @@ var allTestCases = []*testCase{ name: "test custom channels liquidity", test: testCustomChannelsLiquidityEdgeCases, }, + { + name: "test custom channels liquidity group", + test: testCustomChannelsLiquidityEdgeCasesGroup, + }, { name: "test custom channels htlc force close", test: testCustomChannelsHtlcForceClose,