Skip to content

Commit 91529f4

Browse files
authored
Merge pull request #1055 from lightninglabs/btc-to-asset-re-balance
itest: add circular btc-to-asset re-balance test
2 parents 5c59312 + 613a3a2 commit 91529f4

File tree

3 files changed

+211
-6
lines changed

3 files changed

+211
-6
lines changed

itest/assets_test.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,14 @@ func assertChannelSatBalance(t *testing.T, node *HarnessNode,
984984
func assertChannelAssetBalance(t *testing.T, node *HarnessNode,
985985
chanPoint *lnrpc.ChannelPoint, local, remote uint64) {
986986

987+
assertChannelAssetBalanceWithDelta(
988+
t, node, chanPoint, local, remote, 1,
989+
)
990+
}
991+
992+
func assertChannelAssetBalanceWithDelta(t *testing.T, node *HarnessNode,
993+
chanPoint *lnrpc.ChannelPoint, local, remote uint64, delta float64) {
994+
987995
targetChan := fetchChannel(t, node, chanPoint)
988996

989997
var assetBalance rfqmsg.JsonAssetChannel
@@ -992,8 +1000,8 @@ func assertChannelAssetBalance(t *testing.T, node *HarnessNode,
9921000

9931001
require.Len(t, assetBalance.FundingAssets, 1)
9941002

995-
require.InDelta(t, local, assetBalance.LocalBalance, 1)
996-
require.InDelta(t, remote, assetBalance.RemoteBalance, 1)
1003+
require.InDelta(t, local, assetBalance.LocalBalance, delta)
1004+
require.InDelta(t, remote, assetBalance.RemoteBalance, delta)
9971005
}
9981006

9991007
// addRoutingFee adds the default routing fee (1 part per million fee rate plus
@@ -1153,10 +1161,12 @@ func payPayReqWithSatoshi(t *testing.T, payer *HarnessNode, payReq string,
11531161
defer cancel()
11541162

11551163
sendReq := &routerrpc.SendPaymentRequest{
1156-
PaymentRequest: payReq,
1157-
TimeoutSeconds: int32(PaymentTimeout.Seconds()),
1158-
FeeLimitMsat: 1_000_000,
1159-
MaxParts: cfg.maxShards,
1164+
PaymentRequest: payReq,
1165+
TimeoutSeconds: int32(PaymentTimeout.Seconds()),
1166+
FeeLimitMsat: 1_000_000,
1167+
MaxParts: cfg.maxShards,
1168+
OutgoingChanIds: cfg.outgoingChanIDs,
1169+
AllowSelfPayment: cfg.allowSelfPayment,
11601170
}
11611171

11621172
if cfg.smallShards {
@@ -1244,6 +1254,8 @@ type payConfig struct {
12441254
failureReason lnrpc.PaymentFailureReason
12451255
rfq fn.Option[rfqmsg.ID]
12461256
groupKey []byte
1257+
outgoingChanIDs []uint64
1258+
allowSelfPayment bool
12471259
}
12481260

12491261
func defaultPayConfig() *payConfig {
@@ -1315,6 +1327,18 @@ func withGroupKey(groupKey []byte) payOpt {
13151327
}
13161328
}
13171329

1330+
func withOutgoingChanIDs(ids []uint64) payOpt {
1331+
return func(c *payConfig) {
1332+
c.outgoingChanIDs = ids
1333+
}
1334+
}
1335+
1336+
func withAllowSelfPayment() payOpt {
1337+
return func(c *payConfig) {
1338+
c.allowSelfPayment = true
1339+
}
1340+
}
1341+
13181342
func payInvoiceWithAssets(t *testing.T, payer, rfqPeer *HarnessNode,
13191343
payReq string, assetID []byte,
13201344
opts ...payOpt) (uint64, rfqmath.BigIntFixedPoint) {

itest/litd_custom_channels_test.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"math"
88
"math/big"
99
"slices"
10+
"strconv"
1011
"time"
1112

1213
"github.com/btcsuite/btcd/btcec/v2"
@@ -4340,3 +4341,179 @@ func testCustomChannelsDecodeAssetInvoice(ctx context.Context,
43404341
const expectedUnits = 100_000_000_000
43414342
require.Equal(t.t, int64(expectedUnits), int64(decodeResp.AssetAmount))
43424343
}
4344+
4345+
// testCustomChannelsSelfPayment tests that circular self-payments can be made
4346+
// to re-balance between BTC and assets.
4347+
func testCustomChannelsSelfPayment(ctx context.Context, net *NetworkHarness,
4348+
t *harnessTest) {
4349+
4350+
lndArgs := slices.Clone(lndArgsTemplate)
4351+
litdArgs := slices.Clone(litdArgsTemplate)
4352+
4353+
// We use Alice as the proof courier. But in order for Alice to also
4354+
// use itself, we need to define its port upfront.
4355+
alicePort := port.NextAvailablePort()
4356+
litdArgs = append(litdArgs, fmt.Sprintf(
4357+
"--taproot-assets.proofcourieraddr=%s://%s",
4358+
proof.UniverseRpcCourierType,
4359+
fmt.Sprintf(node.ListenerFormat, alicePort),
4360+
))
4361+
4362+
// Next, we'll make Alice and Bob, who will be the main nodes under
4363+
// test.
4364+
alice, err := net.NewNodeWithPort(
4365+
t.t, "Alice", lndArgs, false, true, alicePort, litdArgs...,
4366+
)
4367+
require.NoError(t.t, err)
4368+
bob, err := net.NewNode(
4369+
t.t, "Bob", lndArgs, false, true, litdArgs...,
4370+
)
4371+
require.NoError(t.t, err)
4372+
4373+
// Now we'll connect all nodes, and also fund them with some coins.
4374+
nodes := []*HarnessNode{alice, bob}
4375+
connectAllNodes(t.t, net, nodes)
4376+
fundAllNodes(t.t, net, nodes)
4377+
4378+
aliceTap := newTapClient(t.t, alice)
4379+
4380+
// Next, we'll mint an asset for Alice, who will be the node that opens
4381+
// the channel outbound.
4382+
mintedAssets := itest.MintAssetsConfirmBatch(
4383+
t.t, t.lndHarness.Miner.Client, aliceTap,
4384+
[]*mintrpc.MintAssetRequest{
4385+
{
4386+
Asset: itestAsset,
4387+
},
4388+
},
4389+
)
4390+
cents := mintedAssets[0]
4391+
assetID := cents.AssetGenesis.AssetId
4392+
4393+
t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount)
4394+
syncUniverses(t.t, aliceTap, bob)
4395+
t.Logf("Universes synced between all nodes, distributing assets...")
4396+
4397+
// With the assets created, and synced -- we'll now open the channel
4398+
// between Alice and Bob.
4399+
t.Logf("Opening asset channel...")
4400+
assetFundResp, err := aliceTap.FundChannel(
4401+
ctx, &tchrpc.FundChannelRequest{
4402+
AssetAmount: fundingAmount,
4403+
AssetId: assetID,
4404+
PeerPubkey: bob.PubKey[:],
4405+
FeeRateSatPerVbyte: 5,
4406+
},
4407+
)
4408+
require.NoError(t.t, err)
4409+
t.Logf("Funded asset channel between Alice and Bob: %v", assetFundResp)
4410+
4411+
assetChanPoint := &lnrpc.ChannelPoint{
4412+
OutputIndex: uint32(assetFundResp.OutputIndex),
4413+
FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{
4414+
FundingTxidStr: assetFundResp.Txid,
4415+
},
4416+
}
4417+
4418+
// With the channel open, mine a block to confirm it.
4419+
mineBlocks(t, net, 6, 1)
4420+
4421+
// Before we start sending out payments, let's make sure each node can
4422+
// see the other one in the graph and has all required features.
4423+
require.NoError(t.t, t.lndHarness.AssertNodeKnown(alice, bob))
4424+
require.NoError(t.t, t.lndHarness.AssertNodeKnown(bob, alice))
4425+
4426+
t.Logf("Opening normal channel between Alice and Bob...")
4427+
satChanPoint := openChannelAndAssert(
4428+
t, net, alice, bob, lntest.OpenChannelParams{
4429+
Amt: 10_000_000,
4430+
SatPerVByte: 5,
4431+
},
4432+
)
4433+
defer closeChannelAndAssert(t, net, alice, satChanPoint, false)
4434+
4435+
satChan := fetchChannel(t.t, alice, satChanPoint)
4436+
satChanSCID := satChan.ChanId
4437+
4438+
t.Logf("Alice pubkey: %x", alice.PubKey[:])
4439+
t.Logf("Bob pubkey: %x", bob.PubKey[:])
4440+
t.Logf("Outgoing channel SCID: %d", satChanSCID)
4441+
logBalance(t.t, nodes, assetID, "initial")
4442+
4443+
t.Logf("Key sending 15k assets from Alice to Bob...")
4444+
const (
4445+
assetKeySendAmount = 15_000
4446+
numInvoicePayments = 10
4447+
assetInvoiceAmount = 1_234
4448+
btcKeySendAmount = 200_000
4449+
btcReserveAmount = 2000
4450+
btcHtlcCost = numInvoicePayments * 354
4451+
)
4452+
sendAssetKeySendPayment(
4453+
t.t, alice, bob, assetKeySendAmount, assetID,
4454+
fn.Some[int64](btcReserveAmount+btcHtlcCost),
4455+
)
4456+
4457+
// We also send 200k sats from Alice to Bob, to make sure the BTC
4458+
// channel has liquidity in both directions.
4459+
sendKeySendPayment(t.t, alice, bob, btcKeySendAmount)
4460+
logBalance(t.t, nodes, assetID, "after keysend")
4461+
4462+
// We now do a series of small payments. They should all succeed and the
4463+
// balances should be updated accordingly.
4464+
aliceAssetBalance := uint64(fundingAmount - assetKeySendAmount)
4465+
bobAssetBalance := uint64(assetKeySendAmount)
4466+
for i := 0; i < numInvoicePayments; i++ {
4467+
// The BTC balance of Alice before we start the payment. We
4468+
// expect that to go down by at least the invoice amount.
4469+
btcBalanceAliceBefore := fetchChannel(
4470+
t.t, alice, satChanPoint,
4471+
).LocalBalance
4472+
4473+
invoiceResp := createAssetInvoice(
4474+
t.t, bob, alice, assetInvoiceAmount, assetID,
4475+
)
4476+
payInvoiceWithSatoshi(
4477+
t.t, alice, invoiceResp, withOutgoingChanIDs(
4478+
[]uint64{satChanSCID},
4479+
), withAllowSelfPayment(),
4480+
)
4481+
4482+
logBalance(
4483+
t.t, nodes, assetID,
4484+
"after paying invoice "+strconv.Itoa(i),
4485+
)
4486+
4487+
// The accumulated delta from the rounding of multiple sends.
4488+
// We basically allow the balance to be off by one unit for each
4489+
// payment.
4490+
delta := float64(i + 1)
4491+
4492+
// We now expect the channel balance to have decreased in the
4493+
// BTC channel and increased in the assets channel.
4494+
assertChannelAssetBalanceWithDelta(
4495+
t.t, alice, assetChanPoint,
4496+
aliceAssetBalance+assetInvoiceAmount,
4497+
bobAssetBalance-assetInvoiceAmount, delta,
4498+
)
4499+
aliceAssetBalance += assetInvoiceAmount
4500+
bobAssetBalance -= assetInvoiceAmount
4501+
4502+
btcBalanceAliceAfter := fetchChannel(
4503+
t.t, alice, satChanPoint,
4504+
).LocalBalance
4505+
4506+
// The difference between the two balances should be at least
4507+
// the invoice amount.
4508+
decodedInvoice, err := alice.DecodePayReq(
4509+
context.Background(), &lnrpc.PayReqString{
4510+
PayReq: invoiceResp.PaymentRequest,
4511+
},
4512+
)
4513+
require.NoError(t.t, err)
4514+
require.GreaterOrEqual(
4515+
t.t, btcBalanceAliceBefore-btcBalanceAliceAfter,
4516+
decodedInvoice.NumSatoshis,
4517+
)
4518+
}
4519+
}

itest/litd_test_list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,8 @@ var allTestCases = []*testCase{
114114
test: testCustomChannelsDecodeAssetInvoice,
115115
noAliceBob: true,
116116
},
117+
{
118+
name: "test custom channels self-payment",
119+
test: testCustomChannelsSelfPayment,
120+
},
117121
}

0 commit comments

Comments
 (0)