Skip to content

Commit 613a3a2

Browse files
committed
itest: add circular btc-to-asset re-balance test
1 parent 7e0b198 commit 613a3a2

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
@@ -983,6 +983,14 @@ func assertChannelSatBalance(t *testing.T, node *HarnessNode,
983983
func assertChannelAssetBalance(t *testing.T, node *HarnessNode,
984984
chanPoint *lnrpc.ChannelPoint, local, remote uint64) {
985985

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

988996
var assetBalance rfqmsg.JsonAssetChannel
@@ -991,8 +999,8 @@ func assertChannelAssetBalance(t *testing.T, node *HarnessNode,
991999

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

994-
require.InDelta(t, local, assetBalance.LocalBalance, 1)
995-
require.InDelta(t, remote, assetBalance.RemoteBalance, 1)
1002+
require.InDelta(t, local, assetBalance.LocalBalance, delta)
1003+
require.InDelta(t, remote, assetBalance.RemoteBalance, delta)
9961004
}
9971005

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

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

11611171
if cfg.smallShards {
@@ -1243,6 +1253,8 @@ type payConfig struct {
12431253
failureReason lnrpc.PaymentFailureReason
12441254
rfq fn.Option[rfqmsg.ID]
12451255
groupKey []byte
1256+
outgoingChanIDs []uint64
1257+
allowSelfPayment bool
12461258
}
12471259

12481260
func defaultPayConfig() *payConfig {
@@ -1314,6 +1326,18 @@ func withGroupKey(groupKey []byte) payOpt {
13141326
}
13151327
}
13161328

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

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)