Merge pull request #9609 from yyforyongyu/fix-listunspent
Fix inaccurate `listunspent` result
This commit is contained in:
15 files changed
+85
-257
No files matched your search
@@ -91,6 +91,9 @@
|
|||||||
* [Pass through](https://github.com/lightningnetwork/lnd/pull/9601) the unused
|
* [Pass through](https://github.com/lightningnetwork/lnd/pull/9601) the unused
|
||||||
`MaxPeers` configuration variable for neutrino mode.
|
`MaxPeers` configuration variable for neutrino mode.
|
||||||
|
|
||||||
|
* [Fixed](https://github.com/lightningnetwork/lnd/pull/9609) a bug that may
|
||||||
|
cause `listunspent` to give inaccurate wallet UTXOs.
|
||||||
|
|
||||||
# New Features
|
# New Features
|
||||||
|
|
||||||
* Add support for [archiving channel backup](https://github.com/lightningnetwork/lnd/pull/9232)
|
* Add support for [archiving channel backup](https://github.com/lightningnetwork/lnd/pull/9232)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ require (
|
|||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
|
||||||
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
|
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
|
||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
|
||||||
github.com/btcsuite/btcwallet v0.16.10
|
github.com/btcsuite/btcwallet v0.16.11
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
|
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
|
||||||
github.com/btcsuite/btcwallet/walletdb v1.4.4
|
github.com/btcsuite/btcwallet/walletdb v1.4.4
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
|
|||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
|
||||||
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
|
github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
github.com/btcsuite/btcwallet v0.16.10 h1:SDMS0Gp7oEJVvyZNQ6gDYkpkvp/cSMRcAAy3fvO3vTk=
|
github.com/btcsuite/btcwallet v0.16.11 h1:c8RgW/HO79if8P+KFQLQE00ITmFnLPKAzIy9FwxE37A=
|
||||||
github.com/btcsuite/btcwallet v0.16.10/go.mod h1:1HJXYbjJzgumlnxOC2+ViR1U+gnHWoOn7WeK5OfY1eU=
|
github.com/btcsuite/btcwallet v0.16.11/go.mod h1:1HJXYbjJzgumlnxOC2+ViR1U+gnHWoOn7WeK5OfY1eU=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
|
||||||
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
|
||||||
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
|
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
|
||||||
|
|||||||
+25
-15
@@ -3,9 +3,7 @@ package itest
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lntest/node"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// flakePreimageSettlement documents a flake found when testing the preimage
|
// flakePreimageSettlement documents a flake found when testing the preimage
|
||||||
@@ -34,19 +32,6 @@ func flakePreimageSettlement(ht *lntest.HarnessTest) {
|
|||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// flakeFundExtraUTXO documents a flake found when testing the sweeping behavior
|
|
||||||
// of the given node, which would fail due to no wallet UTXO available, while
|
|
||||||
// there should be enough.
|
|
||||||
//
|
|
||||||
// TODO(yy): remove it once the issue is resolved.
|
|
||||||
func flakeFundExtraUTXO(ht *lntest.HarnessTest, node *node.HarnessNode) {
|
|
||||||
// The node should have enough wallet UTXOs here to sweep the HTLC in
|
|
||||||
// the end of this test. However, due to a known issue, the node's
|
|
||||||
// wallet may report there's no UTXO available. For details,
|
|
||||||
// - https://github.com/lightningnetwork/lnd/issues/8786
|
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// flakeTxNotifierNeutrino documents a flake found when running force close
|
// flakeTxNotifierNeutrino documents a flake found when running force close
|
||||||
// tests using neutrino backend, which is a race between two notifications - one
|
// tests using neutrino backend, which is a race between two notifications - one
|
||||||
// for the spending notification, the other for the block which contains the
|
// for the spending notification, the other for the block which contains the
|
||||||
@@ -127,3 +112,28 @@ func flakePaymentStreamReturnEarly() {
|
|||||||
// commitment.
|
// commitment.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flakeRaceInBitcoinClientNotifications documents a bug found that the
|
||||||
|
// `ListUnspent` gives inaccurate results. In specific,
|
||||||
|
// - an output is confirmed in block X, which is under the process of being
|
||||||
|
// credited to our wallet.
|
||||||
|
// - `ListUnspent` is called between the above process, returning an
|
||||||
|
// inaccurate result, causing the sweeper to think there's no wallet utxo.
|
||||||
|
// - the sweeping will fail at block X due to not enough inputs.
|
||||||
|
//
|
||||||
|
// Under the hood, the RPC client created for handling wallet txns and handling
|
||||||
|
// block notifications are independent. For the block notification, which is
|
||||||
|
// registered via `RegisterBlockEpochNtfn`, is managed by the `chainntnfs`,
|
||||||
|
// which is hooked to a bitcoind client created at startup. For the wallet, it
|
||||||
|
// uses another bitcoind client to receive online events. Although they share
|
||||||
|
// the same bitcoind RPC conn, these two clients are acting independently.
|
||||||
|
// With this setup, it means there's no coordination between the two system -
|
||||||
|
// `lnwallet` and `chainntnfs` can disagree on the latest onchain state for a
|
||||||
|
// short period, causing an inconsistent state which leads to the failed
|
||||||
|
// sweeping attempt.
|
||||||
|
//
|
||||||
|
// TODO(yy): We need to adhere to the SSOT principle, and make the effort to
|
||||||
|
// ensure the whole system only uses one bitcoind client.
|
||||||
|
func flakeRaceInBitcoinClientNotifications(ht *lntest.HarnessTest) {
|
||||||
|
ht.MineEmptyBlocks(1)
|
||||||
|
}
|
||||||
@@ -69,6 +69,13 @@ var excludedTestsWindows = []string{
|
|||||||
"query blinded route",
|
"query blinded route",
|
||||||
|
|
||||||
"data loss protection",
|
"data loss protection",
|
||||||
|
|
||||||
|
// The following restart cases will fail in windows due to aggregation
|
||||||
|
// can sometimes miss grouping one or two inputs in the same sweeping
|
||||||
|
// tx, which is likely caused by how the blocks are notified in windows,
|
||||||
|
// more investigation is needed.
|
||||||
|
"channel force close-anchor restart",
|
||||||
|
"channel force close-simple taproot restart",
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterWindowsFlakyTests filters out the flaky tests that are excluded from
|
// filterWindowsFlakyTests filters out the flaky tests that are excluded from
|
||||||
|
|||||||
@@ -341,8 +341,6 @@ func runLocalClaimOutgoingHTLC(ht *lntest.HarnessTest,
|
|||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
}
|
}
|
||||||
|
|
||||||
flakeFundExtraUTXO(ht, bob)
|
|
||||||
|
|
||||||
// Now that our channels are set up, we'll send two HTLC's from Alice
|
// Now that our channels are set up, we'll send two HTLC's from Alice
|
||||||
// to Carol. The first HTLC will be universally considered "dust",
|
// to Carol. The first HTLC will be universally considered "dust",
|
||||||
// while the second will be a proper fully valued HTLC.
|
// while the second will be a proper fully valued HTLC.
|
||||||
@@ -685,7 +683,6 @@ func runMultiHopReceiverPreimageClaim(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Fund Carol one UTXO so she can sweep outputs.
|
// Fund Carol one UTXO so she can sweep outputs.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||||
flakeFundExtraUTXO(ht, carol)
|
|
||||||
|
|
||||||
// If this is a taproot channel, then we'll need to make some manual
|
// If this is a taproot channel, then we'll need to make some manual
|
||||||
// route hints so Alice can actually find a route.
|
// route hints so Alice can actually find a route.
|
||||||
@@ -1601,8 +1598,6 @@ func runLocalClaimIncomingHTLC(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Fund Carol one UTXO so she can sweep outputs.
|
// Fund Carol one UTXO so she can sweep outputs.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||||
flakeFundExtraUTXO(ht, carol)
|
|
||||||
flakeFundExtraUTXO(ht, bob)
|
|
||||||
|
|
||||||
// If this is a taproot channel, then we'll need to make some manual
|
// If this is a taproot channel, then we'll need to make some manual
|
||||||
// route hints so Alice can actually find a route.
|
// route hints so Alice can actually find a route.
|
||||||
@@ -1897,7 +1892,6 @@ func runLocalClaimIncomingHTLCLeased(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Fund Carol one UTXO so she can sweep outputs.
|
// Fund Carol one UTXO so she can sweep outputs.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||||
flakeFundExtraUTXO(ht, carol)
|
|
||||||
|
|
||||||
// With the network active, we'll now add a new hodl invoice at Carol's
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
||||||
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
||||||
@@ -2230,7 +2224,6 @@ func runLocalPreimageClaim(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Fund Carol one UTXO so she can sweep outputs.
|
// Fund Carol one UTXO so she can sweep outputs.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||||
flakeFundExtraUTXO(ht, carol)
|
|
||||||
|
|
||||||
// If this is a taproot channel, then we'll need to make some manual
|
// If this is a taproot channel, then we'll need to make some manual
|
||||||
// route hints so Alice can actually find a route.
|
// route hints so Alice can actually find a route.
|
||||||
@@ -2490,7 +2483,6 @@ func runLocalPreimageClaimLeased(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Fund Carol one UTXO so she can sweep outputs.
|
// Fund Carol one UTXO so she can sweep outputs.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||||
flakeFundExtraUTXO(ht, carol)
|
|
||||||
|
|
||||||
// With the network active, we'll now add a new hodl invoice at Carol's
|
// With the network active, we'll now add a new hodl invoice at Carol's
|
||||||
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
// end. Make sure the cltv expiry delta is large enough, otherwise Bob
|
||||||
@@ -2842,7 +2834,6 @@ func runHtlcAggregation(ht *lntest.HarnessTest,
|
|||||||
// We need one additional UTXO to create the sweeping tx for the
|
// We need one additional UTXO to create the sweeping tx for the
|
||||||
// second-level success txes.
|
// second-level success txes.
|
||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
flakeFundExtraUTXO(ht, bob)
|
|
||||||
|
|
||||||
// If this is a taproot channel, then we'll need to make some manual
|
// If this is a taproot channel, then we'll need to make some manual
|
||||||
// route hints so Alice+Carol can actually find a route.
|
// route hints so Alice+Carol can actually find a route.
|
||||||
|
|||||||
@@ -115,8 +115,6 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) {
|
|||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
}
|
}
|
||||||
|
|
||||||
flakeFundExtraUTXO(ht, bob)
|
|
||||||
|
|
||||||
// Subscribe the invoice.
|
// Subscribe the invoice.
|
||||||
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||||
|
|
||||||
@@ -342,6 +340,8 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) {
|
|||||||
// needed to clean up the mempool.
|
// needed to clean up the mempool.
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
|
||||||
|
flakeRaceInBitcoinClientNotifications(ht)
|
||||||
|
|
||||||
// The above mined block should confirm Bob's force close tx, and his
|
// The above mined block should confirm Bob's force close tx, and his
|
||||||
// contractcourt will offer the HTLC to his sweeper. We are not testing
|
// contractcourt will offer the HTLC to his sweeper. We are not testing
|
||||||
// the HTLC sweeping behaviors so we just perform a simple check and
|
// the HTLC sweeping behaviors so we just perform a simple check and
|
||||||
@@ -435,8 +435,6 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) {
|
|||||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||||
}
|
}
|
||||||
|
|
||||||
flakeFundExtraUTXO(ht, bob)
|
|
||||||
|
|
||||||
// Subscribe the invoice.
|
// Subscribe the invoice.
|
||||||
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||||
|
|
||||||
|
|||||||
+20
-131
@@ -57,14 +57,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// waddrmgrNamespaceKey is the namespace key that the waddrmgr state is
|
|
||||||
// stored within the top-level walletdb buckets of btcwallet.
|
|
||||||
waddrmgrNamespaceKey = []byte("waddrmgr")
|
|
||||||
|
|
||||||
// wtxmgrNamespaceKey is the namespace key that the wtxmgr state is
|
|
||||||
// stored within the top-level waleltdb buckets of btcwallet.
|
|
||||||
wtxmgrNamespaceKey = []byte("wtxmgr")
|
|
||||||
|
|
||||||
// lightningAddrSchema is the scope addr schema for all keys that we
|
// lightningAddrSchema is the scope addr schema for all keys that we
|
||||||
// derive. We'll treat them all as p2wkh addresses, as atm we must
|
// derive. We'll treat them all as p2wkh addresses, as atm we must
|
||||||
// specify a particular type.
|
// specify a particular type.
|
||||||
@@ -350,18 +342,7 @@ func (b *BtcWallet) Start() error {
|
|||||||
// it was added recently and older wallets don't know it
|
// it was added recently and older wallets don't know it
|
||||||
// yet. Let's add it now.
|
// yet. Let's add it now.
|
||||||
addrSchema := waddrmgr.ScopeAddrMap[scope]
|
addrSchema := waddrmgr.ScopeAddrMap[scope]
|
||||||
err := walletdb.Update(
|
_, err := b.wallet.AddScopeManager(scope, addrSchema)
|
||||||
b.db, func(tx walletdb.ReadWriteTx) error {
|
|
||||||
addrmgrNs := tx.ReadWriteBucket(
|
|
||||||
waddrmgrNamespaceKey,
|
|
||||||
)
|
|
||||||
|
|
||||||
_, err := b.wallet.Manager.NewScopedKeyManager(
|
|
||||||
addrmgrNs, scope, addrSchema,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -373,65 +354,27 @@ func (b *BtcWallet) Start() error {
|
|||||||
// If the scope hasn't yet been created (it wouldn't been
|
// If the scope hasn't yet been created (it wouldn't been
|
||||||
// loaded by default if it was), then we'll manually create the
|
// loaded by default if it was), then we'll manually create the
|
||||||
// scope for the first time ourselves.
|
// scope for the first time ourselves.
|
||||||
err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
manager, err := b.wallet.AddScopeManager(
|
||||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
b.chainKeyScope, lightningAddrSchema,
|
||||||
|
)
|
||||||
scope, err = b.wallet.Manager.NewScopedKeyManager(
|
|
||||||
addrmgrNs, b.chainKeyScope, lightningAddrSchema,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope = manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the wallet is not watch-only atm, and the user wants to migrate it
|
||||||
|
// to watch-only, we will set `convertToWatchOnly` to true so the wallet
|
||||||
|
// accounts are created and converted.
|
||||||
|
convertToWatchOnly := !walletIsWatchOnly && b.cfg.WatchOnly &&
|
||||||
|
b.cfg.MigrateWatchOnly
|
||||||
|
|
||||||
// Now that the wallet is unlocked, we'll go ahead and make sure we
|
// Now that the wallet is unlocked, we'll go ahead and make sure we
|
||||||
// create accounts for all the key families we're going to use. This
|
// create accounts for all the key families we're going to use. This
|
||||||
// will make it possible to list all the account/family xpubs in the
|
// will make it possible to list all the account/family xpubs in the
|
||||||
// wallet list RPC.
|
// wallet list RPC.
|
||||||
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
err = b.wallet.InitAccounts(scope, convertToWatchOnly, 255)
|
||||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
||||||
|
|
||||||
// Generate all accounts that we could ever need. This includes
|
|
||||||
// all lnd key families as well as some key families used in
|
|
||||||
// external liquidity tools.
|
|
||||||
for keyFam := uint32(1); keyFam <= 255; keyFam++ {
|
|
||||||
// Otherwise, we'll check if the account already exists,
|
|
||||||
// if so, we can once again bail early.
|
|
||||||
_, err := scope.AccountName(addrmgrNs, keyFam)
|
|
||||||
if err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we reach this point, then the account hasn't yet
|
|
||||||
// been created, so we'll need to create it before we
|
|
||||||
// can proceed.
|
|
||||||
err = scope.NewRawAccount(addrmgrNs, keyFam)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is the first startup with remote signing and wallet
|
|
||||||
// migration turned on and the wallet wasn't previously
|
|
||||||
// migrated, we can do that now that we made sure all accounts
|
|
||||||
// that we need were derived correctly.
|
|
||||||
if !walletIsWatchOnly && b.cfg.WatchOnly &&
|
|
||||||
b.cfg.MigrateWatchOnly {
|
|
||||||
|
|
||||||
log.Infof("Migrating wallet to watch-only mode, " +
|
|
||||||
"purging all private key material")
|
|
||||||
|
|
||||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
||||||
err = b.wallet.Manager.ConvertToWatchingOnly(ns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -769,29 +712,8 @@ func (b *BtcWallet) ListAddresses(name string,
|
|||||||
|
|
||||||
for _, accntDetails := range accounts {
|
for _, accntDetails := range accounts {
|
||||||
accntScope := accntDetails.KeyScope
|
accntScope := accntDetails.KeyScope
|
||||||
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
|
managedAddrs, err := b.wallet.AccountManagedAddresses(
|
||||||
accntScope,
|
accntDetails.KeyScope, accntDetails.AccountNumber,
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var managedAddrs []waddrmgr.ManagedAddress
|
|
||||||
err = walletdb.View(
|
|
||||||
b.wallet.Database(), func(tx walletdb.ReadTx) error {
|
|
||||||
managedAddrs = nil
|
|
||||||
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
|
||||||
return scopedMgr.ForEachAccountAddress(
|
|
||||||
addrmgrNs, accntDetails.AccountNumber,
|
|
||||||
func(a waddrmgr.ManagedAddress) error {
|
|
||||||
managedAddrs = append(
|
|
||||||
managedAddrs, a,
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1817,18 +1739,8 @@ func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
|
|||||||
return isRecoveryMode, progress, nil
|
return isRecoveryMode, progress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the wallet's birthday block height from db.
|
// Query the wallet's birthday block from db.
|
||||||
var birthdayBlock waddrmgr.BlockStamp
|
birthdayBlock, err := b.wallet.BirthdayBlock()
|
||||||
err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
||||||
var err error
|
|
||||||
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
|
||||||
birthdayBlock, _, err = b.wallet.Manager.BirthdayBlock(addrmgrNs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The wallet won't start until the backend is synced, thus the birthday
|
// The wallet won't start until the backend is synced, thus the birthday
|
||||||
// block won't be set and this particular error will be returned. We'll
|
// block won't be set and this particular error will be returned. We'll
|
||||||
@@ -1886,27 +1798,12 @@ func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
|
|||||||
// by the passed transaction hash. If the transaction can't be found, then a
|
// by the passed transaction hash. If the transaction can't be found, then a
|
||||||
// nil pointer is returned.
|
// nil pointer is returned.
|
||||||
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
|
func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
|
||||||
var targetTx *wtxmgr.TxDetails
|
tx, err := b.wallet.GetTransaction(txHash)
|
||||||
err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
||||||
wtxmgrNs := tx.ReadBucket(wtxmgrNamespaceKey)
|
|
||||||
txDetails, err := b.wallet.TxStore.TxDetails(wtxmgrNs, &txHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetTx = txDetails
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetTx == nil {
|
return tx.Summary.Tx, nil
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &targetTx.TxRecord.MsgTx, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDescendants attempts to remove any transaction from the wallet's tx
|
// RemoveDescendants attempts to remove any transaction from the wallet's tx
|
||||||
@@ -1914,15 +1811,7 @@ func (b *BtcWallet) FetchTx(txHash chainhash.Hash) (*wire.MsgTx, error) {
|
|||||||
// transaction. This remove propagates recursively down the chain of descendent
|
// transaction. This remove propagates recursively down the chain of descendent
|
||||||
// transactions.
|
// transactions.
|
||||||
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
|
func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
|
||||||
txRecord, err := wtxmgr.NewTxRecordFromMsgTx(tx, time.Now())
|
return b.wallet.RemoveDescendants(tx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
||||||
wtxmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey)
|
|
||||||
return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
|
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package btcwallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btclog/v2"
|
"github.com/btcsuite/btclog/v2"
|
||||||
|
"github.com/btcsuite/btcwallet/chain"
|
||||||
|
btcwallet "github.com/btcsuite/btcwallet/wallet"
|
||||||
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,4 +32,8 @@ func DisableLog() {
|
|||||||
// btclog.
|
// btclog.
|
||||||
func UseLogger(logger btclog.Logger) {
|
func UseLogger(logger btclog.Logger) {
|
||||||
log = logger
|
log = logger
|
||||||
|
|
||||||
|
btcwallet.UseLogger(logger)
|
||||||
|
wtxmgr.UseLogger(logger)
|
||||||
|
chain.UseLogger(logger)
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
"github.com/btcsuite/btcwallet/walletdb"
|
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
@@ -71,26 +70,6 @@ func (b *BtcWallet) ScriptForOutput(output *wire.TxOut) (
|
|||||||
return b.wallet.ScriptForOutput(output)
|
return b.wallet.ScriptForOutput(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveFromKeyLoc attempts to derive a private key using a fully specified
|
|
||||||
// KeyLocator.
|
|
||||||
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
|
|
||||||
addrmgrNs walletdb.ReadWriteBucket,
|
|
||||||
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
|
|
||||||
|
|
||||||
path := waddrmgr.DerivationPath{
|
|
||||||
InternalAccount: uint32(keyLoc.Family),
|
|
||||||
Account: uint32(keyLoc.Family),
|
|
||||||
Branch: 0,
|
|
||||||
Index: keyLoc.Index,
|
|
||||||
}
|
|
||||||
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
|
// deriveKeyByBIP32Path derives a key described by a BIP32 path. We expect the
|
||||||
// first three elements of the path to be hardened according to BIP44, so they
|
// first three elements of the path to be hardened according to BIP44, so they
|
||||||
// must be a number >= 2^31.
|
// must be a number >= 2^31.
|
||||||
@@ -169,36 +148,14 @@ func (b *BtcWallet) deriveKeyByBIP32Path(path []uint32) (*btcec.PrivateKey,
|
|||||||
Purpose: purpose,
|
Purpose: purpose,
|
||||||
Coin: coinType,
|
Coin: coinType,
|
||||||
}
|
}
|
||||||
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(scope)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error fetching manager for scope %v: "+
|
|
||||||
"%w", scope, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's see if we can hit the private key cache.
|
|
||||||
keyPath := waddrmgr.DerivationPath{
|
keyPath := waddrmgr.DerivationPath{
|
||||||
InternalAccount: account,
|
InternalAccount: account,
|
||||||
Account: account,
|
Account: account,
|
||||||
Branch: change,
|
Branch: change,
|
||||||
Index: index,
|
Index: index,
|
||||||
}
|
}
|
||||||
privKey, err := scopedMgr.DeriveFromKeyPathCache(keyPath)
|
|
||||||
if err == nil {
|
|
||||||
return privKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The key wasn't in the cache, let's fully derive it now.
|
privKey, err := b.wallet.DeriveFromKeyPath(scope, keyPath)
|
||||||
err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
|
||||||
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
|
||||||
|
|
||||||
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error deriving private key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error deriving key from path %#v: %w",
|
return nil, fmt.Errorf("error deriving key from path %#v: %w",
|
||||||
keyPath, err)
|
keyPath, err)
|
||||||
@@ -225,55 +182,15 @@ func (b *BtcWallet) deriveKeyByLocator(
|
|||||||
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
|
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
|
||||||
|
|
||||||
// We'll assume the special lightning key scope in this case.
|
// We'll assume the special lightning key scope in this case.
|
||||||
scopedMgr, err := b.wallet.Manager.FetchScopedKeyManager(
|
scope := b.chainKeyScope
|
||||||
b.chainKeyScope,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// First try to read the key from the cached store, if this fails, then
|
|
||||||
// we'll fall through to the method below that requires a database
|
|
||||||
// transaction.
|
|
||||||
path := waddrmgr.DerivationPath{
|
path := waddrmgr.DerivationPath{
|
||||||
InternalAccount: uint32(keyLoc.Family),
|
InternalAccount: uint32(keyLoc.Family),
|
||||||
Account: uint32(keyLoc.Family),
|
Account: uint32(keyLoc.Family),
|
||||||
Branch: 0,
|
Branch: 0,
|
||||||
Index: keyLoc.Index,
|
Index: keyLoc.Index,
|
||||||
}
|
}
|
||||||
privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
|
|
||||||
if err == nil {
|
|
||||||
return privKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var key *btcec.PrivateKey
|
key, err := b.wallet.DeriveFromKeyPathAddAccount(scope, path)
|
||||||
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
|
||||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
||||||
|
|
||||||
key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
|
|
||||||
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
|
|
||||||
// If we've reached this point, then the account
|
|
||||||
// doesn't yet exist, so we'll create it now to ensure
|
|
||||||
// we can sign.
|
|
||||||
acctErr := scopedMgr.NewRawAccount(
|
|
||||||
addrmgrNs, uint32(keyLoc.Family),
|
|
||||||
)
|
|
||||||
if acctErr != nil {
|
|
||||||
return acctErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we know the account exists, we'll attempt
|
|
||||||
// to re-derive the private key.
|
|
||||||
key, err = deriveFromKeyLoc(
|
|
||||||
scopedMgr, addrmgrNs, keyLoc,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package lnwallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btclog/v2"
|
"github.com/btcsuite/btclog/v2"
|
||||||
"github.com/btcsuite/btcwallet/chain"
|
|
||||||
btcwallet "github.com/btcsuite/btcwallet/wallet"
|
|
||||||
"github.com/btcsuite/btcwallet/wtxmgr"
|
|
||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
)
|
)
|
||||||
@@ -31,8 +28,5 @@ func DisableLog() {
|
|||||||
func UseLogger(logger btclog.Logger) {
|
func UseLogger(logger btclog.Logger) {
|
||||||
walletLog = logger
|
walletLog = logger
|
||||||
|
|
||||||
btcwallet.UseLogger(logger)
|
|
||||||
wtxmgr.UseLogger(logger)
|
|
||||||
chain.UseLogger(logger)
|
|
||||||
chainfee.UseLogger(logger)
|
chainfee.UseLogger(logger)
|
||||||
}
|
}
|
||||||
@@ -54,6 +54,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/routing/localchans"
|
"github.com/lightningnetwork/lnd/routing/localchans"
|
||||||
"github.com/lightningnetwork/lnd/rpcperms"
|
"github.com/lightningnetwork/lnd/rpcperms"
|
||||||
"github.com/lightningnetwork/lnd/signal"
|
"github.com/lightningnetwork/lnd/signal"
|
||||||
|
"github.com/lightningnetwork/lnd/sqldb"
|
||||||
"github.com/lightningnetwork/lnd/sweep"
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
"github.com/lightningnetwork/lnd/tor"
|
"github.com/lightningnetwork/lnd/tor"
|
||||||
"github.com/lightningnetwork/lnd/watchtower"
|
"github.com/lightningnetwork/lnd/watchtower"
|
||||||
@@ -204,6 +205,7 @@ func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor)
|
|||||||
AddV1SubLogger(root, graphdb.Subsystem, interceptor, graphdb.UseLogger)
|
AddV1SubLogger(root, graphdb.Subsystem, interceptor, graphdb.UseLogger)
|
||||||
AddSubLogger(root, chainio.Subsystem, interceptor, chainio.UseLogger)
|
AddSubLogger(root, chainio.Subsystem, interceptor, chainio.UseLogger)
|
||||||
AddSubLogger(root, msgmux.Subsystem, interceptor, msgmux.UseLogger)
|
AddSubLogger(root, msgmux.Subsystem, interceptor, msgmux.UseLogger)
|
||||||
|
AddSubLogger(root, sqldb.Subsystem, interceptor, sqldb.UseLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSubLogger is a helper method to conveniently create and register the
|
// AddSubLogger is a helper method to conveniently create and register the
|
||||||
|
|||||||
@@ -247,6 +247,9 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx,
|
|||||||
tx, err := makeTx()
|
tx, err := makeTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dbErr := MapSQLError(err)
|
dbErr := MapSQLError(err)
|
||||||
|
log.Tracef("Failed to makeTx: err=%v, dbErr=%v", err,
|
||||||
|
dbErr)
|
||||||
|
|
||||||
if IsSerializationError(dbErr) {
|
if IsSerializationError(dbErr) {
|
||||||
// Nothing to roll back here, since we haven't
|
// Nothing to roll back here, since we haven't
|
||||||
// even get a transaction yet. We'll just wait
|
// even get a transaction yet. We'll just wait
|
||||||
@@ -266,6 +269,8 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx,
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if bodyErr := txBody(tx); bodyErr != nil {
|
if bodyErr := txBody(tx); bodyErr != nil {
|
||||||
|
log.Tracef("Error in txBody: %v", bodyErr)
|
||||||
|
|
||||||
// Roll back the transaction, then attempt a random
|
// Roll back the transaction, then attempt a random
|
||||||
// backoff and try again if the error was a
|
// backoff and try again if the error was a
|
||||||
// serialization error.
|
// serialization error.
|
||||||
@@ -285,6 +290,8 @@ func ExecuteSQLTransactionWithRetry(ctx context.Context, makeTx MakeTx,
|
|||||||
|
|
||||||
// Commit transaction.
|
// Commit transaction.
|
||||||
if commitErr := tx.Commit(); commitErr != nil {
|
if commitErr := tx.Commit(); commitErr != nil {
|
||||||
|
log.Tracef("Failed to commit tx: %v", commitErr)
|
||||||
|
|
||||||
// Roll back the transaction, then attempt a random
|
// Roll back the transaction, then attempt a random
|
||||||
// backoff and try again if the error was a
|
// backoff and try again if the error was a
|
||||||
// serialization error.
|
// serialization error.
|
||||||
|
|||||||
+1
-1
@@ -1371,7 +1371,7 @@ func (t *TxPublisher) createAndPublishTx(
|
|||||||
return fn.Some(*result)
|
return fn.Some(*result)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Replaced tx=%v with new tx=%v", oldTx.TxHash(),
|
log.Debugf("Replaced tx=%v with new tx=%v", oldTx.TxHash(),
|
||||||
sweepCtx.tx.TxHash())
|
sweepCtx.tx.TxHash())
|
||||||
|
|
||||||
// Otherwise, it's a successful RBF, set the event and return.
|
// Otherwise, it's a successful RBF, set the event and return.
|
||||||
|
|||||||
+5
-2
@@ -824,6 +824,9 @@ func (s *UtxoSweeper) sweep(set InputSet) error {
|
|||||||
return fmt.Errorf("gen sweep script: %w", err)
|
return fmt.Errorf("gen sweep script: %w", err)
|
||||||
}
|
}
|
||||||
s.currentOutputScript = fn.Some(addr)
|
s.currentOutputScript = fn.Some(addr)
|
||||||
|
|
||||||
|
log.Debugf("Created sweep DeliveryAddress %x",
|
||||||
|
addr.DeliveryAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
sweepAddr, err := s.currentOutputScript.UnwrapOrErr(
|
sweepAddr, err := s.currentOutputScript.UnwrapOrErr(
|
||||||
@@ -1725,8 +1728,8 @@ func (s *UtxoSweeper) handleBumpEventTxReplaced(resp *bumpResp) error {
|
|||||||
s.cfg.Wallet.CancelRebroadcast(oldTxid)
|
s.cfg.Wallet.CancelRebroadcast(oldTxid)
|
||||||
|
|
||||||
log.Infof("RBFed tx=%v(fee=%v sats, feerate=%v sats/kw) with new "+
|
log.Infof("RBFed tx=%v(fee=%v sats, feerate=%v sats/kw) with new "+
|
||||||
"tx=%v(fee=%v, "+"feerate=%v)", record.Txid, record.Fee,
|
"tx=%v(fee=%v sats, feerate=%v sats/kw)", record.Txid,
|
||||||
record.FeeRate, tr.Txid, tr.Fee, tr.FeeRate)
|
record.Fee, record.FeeRate, tr.Txid, tr.Fee, tr.FeeRate)
|
||||||
|
|
||||||
// The old sweeping tx has been replaced by a new one, we will update
|
// The old sweeping tx has been replaced by a new one, we will update
|
||||||
// the tx record in the sweeper db.
|
// the tx record in the sweeper db.
|
||||||
|
|||||||
Reference in new issue
Block a user