|
7 | 7 | "math"
|
8 | 8 | "math/big"
|
9 | 9 | "slices"
|
| 10 | + "strconv" |
10 | 11 | "time"
|
11 | 12 |
|
12 | 13 | "github.com/btcsuite/btcd/btcec/v2"
|
@@ -4340,3 +4341,179 @@ func testCustomChannelsDecodeAssetInvoice(ctx context.Context,
|
4340 | 4341 | const expectedUnits = 100_000_000_000
|
4341 | 4342 | require.Equal(t.t, int64(expectedUnits), int64(decodeResp.AssetAmount))
|
4342 | 4343 | }
|
| 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 | +} |
0 commit comments