diff --git a/docs/release-notes/release-notes-0.13.4.md b/docs/release-notes/release-notes-0.13.4.md index dce0cd78f..04230daaa 100644 --- a/docs/release-notes/release-notes-0.13.4.md +++ b/docs/release-notes/release-notes-0.13.4.md @@ -4,9 +4,9 @@ ### Lightning Terminal -- [Fixed a bug where REST calls for the `WalletUnlocker` service weren't allowed +* [Fixed a bug where REST calls for the `WalletUnlocker` service weren't allowed on startup](https://github.com/lightninglabs/lightning-terminal/pull/806). -- [Added build flag 'litd_no_ui' for building litd without the ui, accessible +* [Added build flag 'litd_no_ui' for building litd without the ui, accessible with 'make go-build-noui' and 'make go-install-noui'](https://github.com/lightninglabs/lightning-terminal/pull/500). ### LND @@ -19,8 +19,14 @@ with 'make go-build-noui' and 'make go-install-noui'](https://github.com/lightni ### Taproot Assets +* [Inconsistent balance + reporting](https://github.com/lightninglabs/lightning-terminal/pull/871) has + been fixed: on-channel balances are now exclusively reported through channel + balances and will not show up in asset balances reported by tapd. + # Autopilot # Contributors (Alphabetical Order) -* Oliver Gugger +* Gijs van Dam +* Oliver Gugger \ No newline at end of file diff --git a/go.mod b/go.mod index 5450ea26c..f664d9ac9 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/lightninglabs/loop/swapserverrpc v1.0.8 github.com/lightninglabs/pool v0.6.5-beta.0.20240604070222-e121aadb3289 github.com/lightninglabs/pool/auctioneerrpc v1.1.2 - github.com/lightninglabs/taproot-assets v0.4.2-0.20240923062224-3b92c82bb332 + github.com/lightninglabs/taproot-assets v0.4.2-0.20241018073747-bacd6589ee90 github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240919091721-70580403898e github.com/lightningnetwork/lnd/cert v1.2.2 github.com/lightningnetwork/lnd/fn v1.1.0 diff --git a/go.sum b/go.sum index 57b021c3b..07f095f87 100644 --- a/go.sum +++ b/go.sum @@ -1175,8 +1175,8 @@ github.com/lightninglabs/pool/auctioneerrpc v1.1.2 h1:Dbg+9Z9jXnhimR27EN37foc4aB github.com/lightninglabs/pool/auctioneerrpc v1.1.2/go.mod h1:1wKDzN2zEP8srOi0B9iySlEsPdoPhw6oo3Vbm1v4Mhw= github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY= github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -github.com/lightninglabs/taproot-assets v0.4.2-0.20240923062224-3b92c82bb332 h1:AVh0Ks5erEqC3RDY6gsycbf9dcYXv308j/kxi47WPyM= -github.com/lightninglabs/taproot-assets v0.4.2-0.20240923062224-3b92c82bb332/go.mod h1:B6wbs1rSTBTJwTilsKt7p/WravtKqRvJI0ICDwvcdNQ= +github.com/lightninglabs/taproot-assets v0.4.2-0.20241018073747-bacd6589ee90 h1:67sqOcoog67BcKzRuNMPhkPfG50vBLYQ617tUahoPWE= +github.com/lightninglabs/taproot-assets v0.4.2-0.20241018073747-bacd6589ee90/go.mod h1:B6wbs1rSTBTJwTilsKt7p/WravtKqRvJI0ICDwvcdNQ= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s= github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI= github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240919091721-70580403898e h1:Weu9TWNEIpC4XLbcUoSFK3Pv2aUSwn7NlYZKdsm8wUU= diff --git a/itest/assets_test.go b/itest/assets_test.go index 9a9cf244e..160c43bbc 100644 --- a/itest/assets_test.go +++ b/itest/assets_test.go @@ -21,7 +21,6 @@ import ( "github.com/lightninglabs/taproot-assets/proof" "github.com/lightninglabs/taproot-assets/rfq" "github.com/lightninglabs/taproot-assets/rfqmsg" - "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/tapfreighter" "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/assetwalletrpc" @@ -30,6 +29,7 @@ import ( tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/tapscript" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -70,7 +70,7 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, groupKey = mintedAsset.AssetGroup.TweakedGroupKey } - fundingScriptTree := tapchannel.NewFundingScriptTree() + fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() @@ -88,7 +88,7 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, t.Logf("Sending %v asset units to Dave...", assetSendAmount) - // Send the assets to Erin. + // Send the assets to Dave. itest.AssertAddrCreated(t.t, daveTap, mintedAsset, daveAddr) sendResp, err := charlieTap.SendAsset(ctxb, &taprpc.SendAssetRequest{ TapAddrs: []string{daveAddr.Encoded}, @@ -187,14 +187,15 @@ func createTestAssetNetwork(t *harnessTest, net *NetworkHarness, charlieTap, // We'll be tracking the expected asset balances throughout the test, so // we can assert it after each action. - charlieAssetBalance := charlieFundingAmount - daveAssetBalance := assetSendAmount - erinAssetBalance := assetSendAmount + charlieAssetBalance := mintedAsset.Amount - 2*assetSendAmount - + charlieFundingAmount + daveAssetBalance := assetSendAmount - daveFundingAmount + erinAssetBalance := assetSendAmount - erinFundingAmount // After opening the channels, the asset balance of the funding nodes - // shouldn't have been decreased, since the asset with the funding - // output was imported into the asset DB and should count toward the - // balance. + // should have been decreased with the funding amount. The asset with + // the funding output was imported into the asset DB but are kept out of + // the balance reporting by tapd. assertAssetBalance(t.t, charlieTap, assetID, charlieAssetBalance) assertAssetBalance(t.t, daveTap, assetID, daveAssetBalance) assertAssetBalance(t.t, erinTap, assetID, erinAssetBalance) @@ -1312,17 +1313,23 @@ func assertAssetBalance(t *testing.T, client *tapClient, assetID []byte, return err } + assetIDFound := false for _, balance := range assetIDBalances.AssetBalances { if !bytes.Equal(balance.AssetGenesis.AssetId, assetID) { continue } + assetIDFound = true if expectedBalance != balance.Balance { return fmt.Errorf("expected balance %d, got %d", expectedBalance, balance.Balance) } } + if expectedBalance > 0 && !assetIDFound { + return fmt.Errorf("expected balance %d, got 0", + expectedBalance) + } return nil }, shortTimeout) if err != nil { diff --git a/itest/litd_custom_channels_test.go b/itest/litd_custom_channels_test.go index 883e5834c..b949f99f3 100644 --- a/itest/litd_custom_channels_test.go +++ b/itest/litd_custom_channels_test.go @@ -6,15 +6,16 @@ import ( "slices" "time" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightninglabs/taproot-assets/itest" "github.com/lightninglabs/taproot-assets/proof" - "github.com/lightninglabs/taproot-assets/tapchannel" "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" tchrpc "github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/tapscript" "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntest" @@ -353,7 +354,7 @@ func testCustomChannels(_ context.Context, net *NetworkHarness, ) cents := mintedAssets[0] assetID := cents.AssetGenesis.AssetId - fundingScriptTree := tapchannel.NewFundingScriptTree() + fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() @@ -812,7 +813,7 @@ func testCustomChannelsGroupedAsset(_ context.Context, net *NetworkHarness, cents := mintedAssets[0] assetID := cents.AssetGenesis.AssetId groupID := cents.GetAssetGroup().GetTweakedGroupKey() - fundingScriptTree := tapchannel.NewFundingScriptTree() + fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() @@ -1224,13 +1225,15 @@ func testCustomChannelsForceClose(_ context.Context, net *NetworkHarness, t.Logf("Channel funding transfer: %v", toProtoJSON(t.t, assetFundingTransfer)) - // Charlie's balance should reflect that the funding asset was added to - // the DB. - assertAssetBalance(t.t, charlieTap, assetID, itestAsset.Amount) + // Charlie's balance should reflect that the funding asset is now + // excluded from balance reporting by tapd. + assertAssetBalance( + t.t, charlieTap, assetID, itestAsset.Amount-fundingAmount, + ) // Make sure that Charlie properly uploaded funding proof to the // Universe server. - fundingScriptTree := tapchannel.NewFundingScriptTree() + fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() assertUniverseProofExists( @@ -1562,13 +1565,15 @@ func testCustomChannelsBreach(_ context.Context, net *NetworkHarness, t.Logf("Channel funding transfer: %v", toProtoJSON(t.t, assetFundingTransfer)) - // Charlie's balance should reflect that the funding asset was added to - // the DB. - assertAssetBalance(t.t, charlieTap, assetID, itestAsset.Amount) + // Charlie's balance should reflect that the funding asset is now + // excluded from balance reporting by tapd. + assertAssetBalance( + t.t, charlieTap, assetID, itestAsset.Amount-fundingAmount, + ) // Make sure that Charlie properly uploaded funding proof to the // Universe server. - fundingScriptTree := tapchannel.NewFundingScriptTree() + fundingScriptTree := tapscript.NewChannelFundingScriptTree() fundingScriptKey := fundingScriptTree.TaprootKey fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() assertUniverseProofExists( @@ -1941,3 +1946,202 @@ func testCustomChannelsLiquidityEdgeCases(_ context.Context, logBalance(t.t, nodes, assetID, "after big asset payment (asset "+ "invoice, multi-hop)") } + +// testCustomChannelsBalanceConsistency is a test that test the balance . +func testCustomChannelsBalanceConsistency(_ context.Context, + net *NetworkHarness, t *harnessTest) { + + ctxb := context.Background() + lndArgs := slices.Clone(lndArgsTemplate) + litdArgs := slices.Clone(litdArgsTemplate) + + zane, err := net.NewNode( + t.t, "Zane", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + + litdArgs = append(litdArgs, fmt.Sprintf( + "--taproot-assets.proofcourieraddr=%s://%s", + proof.UniverseRpcCourierType, zane.Cfg.LitAddr(), + )) + + charlie, err := net.NewNode( + t.t, "Charlie", lndArgs, false, true, litdArgs..., + ) + require.NoError(t.t, err) + dave, err := net.NewNode(t.t, "Dave", lndArgs, false, true, litdArgs...) + require.NoError(t.t, err) + + nodes := []*HarnessNode{charlie, dave} + connectAllNodes(t.t, net, nodes) + fundAllNodes(t.t, net, nodes) + + charlieTap := newTapClient(t.t, charlie) + daveTap := newTapClient(t.t, dave) + universeTap := newTapClient(t.t, zane) + + // Mint an asset on Charlie and sync Dave 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 + var groupKey []byte + if cents.AssetGroup != nil { + groupKey = cents.AssetGroup.TweakedGroupKey + } + + t.Logf("Minted %d lightning cents, syncing universes...", cents.Amount) + syncUniverses(t.t, charlieTap, dave) + t.Logf("Universes synced between all nodes, distributing assets...") + + charlieBalance := cents.Amount + + // Charlie should have a single balance output with the full balance. + assertAssetBalance(t.t, charlieTap, assetID, cents.Amount) + + // The script key should be local to charlie, and the script key should + // be known. It is after all the asset he just minted himself. + scriptKeyLocal := true + scriptKeyKnown := false + scriptKeyHasScriptPath := false + + scriptKey, err := schnorr.ParsePubKey(cents.ScriptKey[1:]) + require.NoError(t.t, err) + assertAssetExists( + t.t, charlieTap, assetID, charlieBalance, + scriptKey, scriptKeyLocal, scriptKeyKnown, + scriptKeyHasScriptPath, + ) + + fundingScriptTree := tapscript.NewChannelFundingScriptTree() + fundingScriptKey := fundingScriptTree.TaprootKey + fundingScriptTreeBytes := fundingScriptKey.SerializeCompressed() + + fundRespCD, err := charlieTap.FundChannel( + ctxb, &tchrpc.FundChannelRequest{ + AssetAmount: charlieBalance, + AssetId: assetID, + PeerPubkey: daveTap.node.PubKey[:], + FeeRateSatPerVbyte: 5, + PushSat: 0, + }, + ) + require.NoError(t.t, err) + t.Logf("Funded channel between Charlie and Dave: %v", fundRespCD) + + // Make sure the pending channel shows up in the list and has the + // custom records set as JSON. + assertPendingChannels( + t.t, charlieTap.node, assetID, 1, charlieBalance, 0, + ) + + // Let's confirm the channel. + mineBlocks(t, net, 6, 1) + + // Tapd should not report any balance for Charlie, since the asset is + // used in a funding transaction. It should also not report any balance + // for Dave. All those balances are reported through channel balances. + assertAssetBalance(t.t, charlieTap, assetID, 0) + assertAssetBalance(t.t, daveTap, assetID, 0) + + // There should only be a single asset piece for Charlie, the one in the + // channel. + assertNumAssetOutputs(t.t, charlieTap, assetID, 1) + + // The script key should now not be local anymore, since he funded a + // channel with it. Charlie does still know the script key though. + scriptKeyLocal = false + scriptKeyKnown = true + scriptKeyHasScriptPath = true + assertAssetExists( + t.t, charlieTap, assetID, charlieBalance, + fundingScriptKey, scriptKeyLocal, scriptKeyKnown, + scriptKeyHasScriptPath, + ) + + // Assert that the proofs for both channels has been uploaded to the + // designated Universe server. + assertUniverseProofExists( + t.t, universeTap, assetID, groupKey, fundingScriptTreeBytes, + fmt.Sprintf("%v:%v", fundRespCD.Txid, fundRespCD.OutputIndex), + ) + + // Make sure the channel shows the correct asset information. + assertAssetChan( + t.t, charlieTap.node, daveTap.node, charlieBalance, assetID, + ) + + logBalance(t.t, nodes, assetID, "initial") + + // Normal case. + // Send 500 assets from Charlie to Dave. + sendAssetKeySendPayment( + t.t, charlie, dave, 500, assetID, + fn.None[int64](), lnrpc.Payment_SUCCEEDED, + fn.None[lnrpc.PaymentFailureReason](), + ) + + logBalance(t.t, nodes, assetID, "after 500 assets") + + // Tapd stould still not report balances for Charlie and Dave, since + // they are still locked up in the funding transaction. + assertAssetBalance(t.t, charlieTap, assetID, 0) + assertAssetBalance(t.t, daveTap, assetID, 0) + + // Send 10k sats from Charlie to Dave. Dave needs the sats to be able to + // send assets. + sendKeySendPayment(t.t, charlie, dave, 10000) + + // Now Dave tries to send 250 assets. + sendAssetKeySendPayment( + t.t, dave, charlie, 250, assetID, + fn.None[int64](), lnrpc.Payment_SUCCEEDED, + fn.None[lnrpc.PaymentFailureReason](), + ) + + logBalance(t.t, nodes, assetID, "after 250 sats backwards") + + // Tapd stould still not report balances for Charlie and Dave, since + // they are still locked up in the funding transaction. + assertAssetBalance(t.t, charlieTap, assetID, 0) + assertAssetBalance(t.t, daveTap, assetID, 0) + + // We will now close the channel. + t.Logf("Close the channel between Charlie and Dave...") + charlieChanPoint := &lnrpc.ChannelPoint{ + OutputIndex: uint32(fundRespCD.OutputIndex), + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: fundRespCD.Txid, + }, + } + + closeChannelAndAssert(t, net, charlie, charlieChanPoint, false) + + // Charlie should have a single balance output with the balance 250 less + // than the total amount minted. + assertAssetBalance(t.t, charlieTap, assetID, charlieBalance-250) + assertAssetBalance(t.t, daveTap, assetID, 250) + + // The script key should now be local to both Charlie and Dave, since + // the channel was closed. + scriptKeyLocal = true + scriptKeyKnown = true + scriptKeyHasScriptPath = false + assertAssetExists( + t.t, charlieTap, assetID, charlieBalance-250, + nil, scriptKeyLocal, scriptKeyKnown, scriptKeyHasScriptPath, + ) + assertAssetExists( + t.t, daveTap, assetID, 250, + nil, scriptKeyLocal, scriptKeyKnown, scriptKeyHasScriptPath, + ) + + assertNumAssetOutputs(t.t, charlieTap, assetID, 1) + assertNumAssetOutputs(t.t, daveTap, assetID, 1) +} diff --git a/itest/litd_test_list_on_test.go b/itest/litd_test_list_on_test.go index 31b9a2a37..fb75f2812 100644 --- a/itest/litd_test_list_on_test.go +++ b/itest/litd_test_list_on_test.go @@ -44,4 +44,8 @@ var allTestCases = []*testCase{ name: "test custom channels liquidity", test: testCustomChannelsLiquidityEdgeCases, }, + { + name: "test custom channels balance consistency", + test: testCustomChannelsBalanceConsistency, + }, }