Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions itest/litd_custom_channels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,130 @@ func testCustomChannelsBalanceConsistency(ctx context.Context,
)
}

// testCustomChannelsInvoiceQuoteExpiryMismatch ensures that we don't create
// asset invoices that outlive their RFQ quotes. If the fix is absent, the
// invoice can be settled with BTC after the quote expires and is cleaned up.
func testCustomChannelsInvoiceQuoteExpiryMismatch(ctx context.Context,
net *NetworkHarness, t *harnessTest) {

const quoteExpiry = 15 * time.Second
oracleAddr := fmt.Sprintf("localhost:%d", port.NextAvailablePort())
oracle := newOracleHarnessWithExpiry(oracleAddr, quoteExpiry)
oracle.start(t.t)
t.t.Cleanup(oracle.stop)

lndArgs := slices.Clone(lndArgsTemplate)
litdArgs := slices.Clone(litdArgsTemplateNoOracle)
litdArgs = append(litdArgs, fmt.Sprintf(
"--taproot-assets.experimental.rfq.priceoracleaddress="+
"rfqrpc://%s", oracleAddr,
))

// We use Charlie as the proof courier. But in order for Charlie to also
// use itself, we need to define its port upfront.
charliePort := port.NextAvailablePort()
litdArgs = append(litdArgs, fmt.Sprintf(
"--taproot-assets.proofcourieraddr=%s://%s",
proof.UniverseRpcCourierType,
fmt.Sprintf(node.ListenerFormat, charliePort),
))

charlie, err := net.NewNodeWithPort(
t.t, "Charlie", lndArgs, false, true, charliePort, 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, fabia, yara}
connectAllNodes(t.t, net, nodes)
fundAllNodes(t.t, net, nodes)

// Create the normal channel between Erin and Dave. Erin opens so she
// has outgoing capacity to pay Dave.
t.Logf("Opening normal channel between Erin and Dave...")
channelOp := openChannelAndAssert(
t, net, erin, dave, lntest.OpenChannelParams{
Amt: 10_000_000,
SatPerVByte: 5,
},
)
defer closeChannelAndAssert(t, net, erin, channelOp, false)

universeTap := newTapClient(t.t, charlie)
charlieTap := newTapClient(t.t, charlie)
daveTap := newTapClient(t.t, dave)
erinTap := newTapClient(t.t, erin)
fabiaTap := newTapClient(t.t, fabia)
yaraTap := newTapClient(t.t, yara)

// 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,
},
},
)
cents := mintedAssets[0]
assetID := cents.AssetGenesis.AssetId

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...")

const (
daveFundingAmount = uint64(400_000)
erinFundingAmount = uint64(200_000)
)
charlieFundingAmount := cents.Amount - uint64(2*400_000)

_, _, _ = createTestAssetNetwork(
t, net, charlieTap, daveTap, erinTap, fabiaTap, yaraTap,
universeTap, cents, 400_000, charlieFundingAmount,
daveFundingAmount, erinFundingAmount, 0,
)

// Set a price in the oracle for the minted asset.
var id asset.ID
copy(id[:], assetID)
assetPrice := rfqmath.NewBigIntFixedPoint(100_000_00, 2)
oracle.setPrice(id, assetPrice, assetPrice)

// Create an asset invoice whose expiry exceeds the RFQ quote expiry.
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()

invoiceExpiry := int64((2 * time.Minute).Seconds())
request := &tchrpc.AddInvoiceRequest{
AssetAmount: 40,
PeerPubkey: charlie.PubKey[:],
InvoiceRequest: &lnrpc.Invoice{
Memo: "asset invoice with long expiry",
Expiry: invoiceExpiry,
},
AssetId: assetID,
}

// Invoice creation should fail, since the quote is too short.
_, err = daveTap.AddInvoice(ctxt, request)
require.Error(t.t, err)
require.ErrorContains(t.t, err, "no quotes with sufficient expiry")
}

// testCustomChannelsSingleAssetMultiInput tests whether it is possible to fund
// a channel using FundChannel that uses multiple inputs from the same asset.
func testCustomChannelsSingleAssetMultiInput(ctx context.Context,
Expand Down
5 changes: 5 additions & 0 deletions itest/litd_test_list_on_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ var allTestCases = []*testCase{
test: testCustomChannelsStrictForwarding,
noAliceBob: true,
},
{
name: "custom channels invoice quote expiry mismatch",
test: testCustomChannelsInvoiceQuoteExpiryMismatch,
noAliceBob: true,
},
{
name: "custom channels decode payreq",
test: testCustomChannelsDecodeAssetInvoice,
Expand Down
10 changes: 9 additions & 1 deletion itest/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ type oracleHarness struct {

purchasePrices map[asset.ID]rfqmath.BigIntFixedPoint
salePrices map[asset.ID]rfqmath.BigIntFixedPoint
expiryDelay time.Duration
}

func newOracleHarness(listenAddr string) *oracleHarness {
return newOracleHarnessWithExpiry(listenAddr, 5*time.Minute)
}

func newOracleHarnessWithExpiry(listenAddr string,
expiryDelay time.Duration) *oracleHarness {

return &oracleHarness{
listenAddr: listenAddr,
purchasePrices: make(map[asset.ID]rfqmath.BigIntFixedPoint),
salePrices: make(map[asset.ID]rfqmath.BigIntFixedPoint),
expiryDelay: expiryDelay,
}
}

Expand Down Expand Up @@ -130,7 +138,7 @@ func (o *oracleHarness) getAssetRates(id asset.ID,
return oraclerpc.AssetRates{}, err
}

expiry := time.Now().Add(5 * time.Minute).Unix()
expiry := time.Now().Add(o.expiryDelay).Unix()
return oraclerpc.AssetRates{
SubjectAssetRate: rpcSubjectAssetToBtcRate,
PaymentAssetRate: rpcPaymentAssetToBtcRate,
Expand Down
Loading