Repository: lightningnetwork/lnd (language: go)

Issue:
`LeaseOutput` is slow with postgres backend
### Issue

`SendCoins` takes much longer to run with postgres as the db backend, and this itest failure has been shown multiple times,
```
harness_rpc.go:97: 
        	Error Trace:	/home/runner/work/lnd/lnd/lntest/rpc/harness_rpc.go:97
        	            				/home/runner/work/lnd/lnd/lntest/rpc/lnd.go:374
        	            				/home/runner/work/lnd/lnd/itest/lnd_taproot_test.go:129
        	            				/home/runner/work/lnd/lnd/itest/lnd_taproot_test.go:52
        	            				/home/runner/work/lnd/lnd/lntest/harness.go:385
        	            				/home/runner/work/lnd/lnd/itest/lnd_test.go:139
        	Error:      	Received unexpected error:
        	            	rpc error: code = DeadlineExceeded desc = context deadline exceeded
        	Messages:   	Alice: failed to call SendCoins
```

### Observations
Turns out that when using postgres, leasing outputs take much longer, which is called [here](https://github.com/lightningnetwork/lnd/blob/master/sweep/walletsweep.go#L248) when crafting the tx for `SendCoins`.
- For kvdb backend, the leasing of 100 utxos takes about 1 second.
- For postgres backend, the leasing of 100 utxos takes about 40 seconds.

### Steps to reproduce

Running with `make itest icase=sign_psbt/fund_and_sign_psbt backend=btcd`,
```
2024-06-04 03:37:30.353 [INF] RPCS: [sendcoins] addr=2MzusuQmFdFCBgtns6LjnCnQemeAhRftiku, amt=0 BTC, sat/kw=12500, min_confs=0, send_all=true
2024-06-04 03:37:30.353 [DBG] SWPR: [WithCoinSelectLock] entered the lock
2024-06-04 03:37:30.453 [DBG] SWPR: [WithCoinSelectLock] finished fetching UTXOs
2024-06-04 03:37:30.453 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: 65734f6883a0949f4588589fda166b9339bfb77081cf77dad1ee91b5dc78c000:0
...leasing 98 more...
2024-06-04 03:37:31.616 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: 2fb2947606ac7bf141ae364e8ea8d805344f56a319b679efa4380e6839782bf8:0
2024-06-04 03:37:31.626 [DBG] SWPR: [WithCoinSelectLock] exited the lock
2024-06-04 03:37:31.656 [DBG] SWPR: Creating sweep transaction 6c4bc2d7afeecb395d8bf0654065d7f5acb52b1017e5b4a61442bbd1dd3d7207 for 100 inputs...
```

Running with `make itest icase=sign_psbt/fund_and_sign_psbt backend=bitcoind`,
```
2024-06-04 03:46:54.785 [INF] RPCS: [sendcoins] addr=2MzDETUurmNbcnx2mLWtm4tdeARCFUkdQbU, amt=0 BTC, sat/kw=12500, min_confs=0, send_all=true
2024-06-04 03:46:54.786 [DBG] SWPR: [WithCoinSelectLock] entered the lock
2024-06-04 03:46:54.856 [DBG] SWPR: [WithCoinSelectLock] finished fetching UTXOs
2024-06-04 03:46:54.856 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: f4436cede2c43faa7eb16cc2d4577e0825790f685b146eacfbe2a07a1006df00:0
...leasing 98 more...
2024-06-04 03:46:55.960 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: 8dd273ce89622e59cae55a216a33b65d581a0d9ea95a30d74138140f5a0d15ff:0
2024-06-04 03:46:55.970 [DBG] SWPR: [WithCoinSelectLock] exited the lock
2024-06-04 03:46:55.984 [DBG] SWPR: Creating sweep transaction c383561e0096a6099dfe5c9cd57a42a6f393cb7fd281e137e5d989c518db3627 for 100 inputs...
```

Running with `make itest icase=sign_psbt/fund_and_sign_psbt backend=bitcoind dbbackend=postgres`,
```
2024-06-04 03:41:21.576 [INF] RPCS: [sendcoins] addr=2N6HM91LmDZP63ViD5MPirXxLScp3gmK2zf, amt=0 BTC, sat/kw=12500, min_confs=0, send_all=true
2024-06-04 03:41:21.577 [DBG] SWPR: [WithCoinSelectLock] entered the lock
2024-06-04 03:41:23.324 [DBG] SWPR: [WithCoinSelectLock] finished fetching UTXOs
2024-06-04 03:41:23.324 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: 38fecbec097297b3e4b1e2f535e4d4707efc8d5668424f4c14e29805aa771801:0
...leasing 98 more...
2024-06-04 03:42:02.574 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: ad3297020e2b81993d2093ae239894a47ad3f67b243fc2f79c0f1fdf808384fc:0
2024-06-04 03:42:03.284 [DBG] SWPR: [WithCoinSelectLock] exited the lock
2024-06-04 03:42:03.772 [DBG] SWPR: Creating sweep transaction e50f98c1f40080ee20139d095945149a16d3334080658598bf4a5eef8ec90a7f for 100 inputs...
```

Running with `make itest icase=sign_psbt/fund_and_sign_psbt backend=bitcoind dbbackend=postgres nativesql=true`,
```
2024-06-04 03:53:42.474 [INF] RPCS: [sendcoins] addr=2MuaBevMSc9Gxdi4WGAnuEiRXH2pGAcYNaj, amt=0 BTC, sat/kw=12500, min_confs=0, send_all=true
2024-06-04 03:53:42.475 [DBG] SWPR: [WithCoinSelectLock] entered the lock
2024-06-04 03:53:44.029 [DBG] SWPR: [WithCoinSelectLock] finished fetching UTXOs
2024-06-04 03:53:44.029 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: 1deb82f888c58b2c9e86d0f8773537324a5b1fe2ddd2b3d3a243088cb9990000:0
...leasing 98 more...
2024-06-04 03:54:19.937 [DBG] SWPR: [WithCoinSelectLock] leasing utxo: a4304be189a3075d22f5cabc638af956da871b09ae46ffb48a6dd1c143d084fe:0
2024-06-04 03:54:20.671 [DBG] SWPR: [WithCoinSelectLock] exited the lock
2024-06-04 03:54:21.260 [DBG] SWPR: Creating sweep transaction a6d8019cb0cb3f5bb0603bcfc4676b6eef81f88301933e75bd67673650b4e1c5 for 100 inputs...
```


Discussion / hints:
The implementation here is pretty unglamorous: https://github.com/btcsuite/btcwallet/blob/6fe19a472a627b5f3a6ab2eda9ed7d8809d04f2e/wtxmgr/tx.go#L1184-L1204

Perhaps the culprit is that we'll create 100 distributed transactions for the 100 outpoints, instead of just 1 transaction? 


Fix patch:
diff --git a/docs/release-notes/release-notes-0.18.3.md b/docs/release-notes/release-notes-0.18.3.md
index 8ba9eefadf..d60b069ff4 100644
--- a/docs/release-notes/release-notes-0.18.3.md
+++ b/docs/release-notes/release-notes-0.18.3.md
@@ -151,6 +151,11 @@ commitment when the channel was force closed.
 * [Allow](https://github.com/lightningnetwork/lnd/pull/8845) multiple etcd hosts
   to be specified in db.etcd.host.
 
+* Improved the internal [`LeaseOutput`
+  method](https://github.com/lightningnetwork/lnd/pull/8961) to be more
+  efficient, which improves the performance of related RPC calls such as
+  `LeaseOutput`, `SendCoins`, and PSBT funding process. 
+
 ## RPC Updates
 
 * [`xImportMissionControl`](https://github.com/lightningnetwork/lnd/pull/8779) 
diff --git a/go.mod b/go.mod
index 0e628d18b0..5abb53852f 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/btcsuite/btcd/btcutil/psbt v1.1.8
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
 	github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
-	github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd
+	github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2
 	github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4
 	github.com/btcsuite/btcwallet/wallet/txrules v1.2.1
 	github.com/btcsuite/btcwallet/walletdb v1.4.2
diff --git a/go.sum b/go.sum
index d556042dd3..e77306ddb8 100644
--- a/go.sum
+++ b/go.sum
@@ -92,8 +92,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd
 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
-github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd h1:QDb8foTCRoXrfoZVEzSYgSde16MJh4gCtCin8OCS0kI=
-github.com/btcsuite/btcwallet v0.16.10-0.20240718224643-db3a4a2543bd/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A=
+github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2 h1:qa4Avm7p97JroZZyMJADbEb9u853pjleJYSeitENvLc=
+github.com/btcsuite/btcwallet v0.16.10-0.20240809133323-7d3434c65ae2/go.mod h1:X2xDre+j1QphTRo54y2TikUzeSvreL1t1aMXrD8Kc5A=
 github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4 h1:poyHFf7+5+RdxNp5r2T6IBRD7RyraUsYARYbp/7t4D8=
 github.com/btcsuite/btcwallet/wallet/txauthor v1.3.4/go.mod h1:GETGDQuyq+VFfH1S/+/7slLM/9aNa4l7P4ejX6dJfb0=
 github.com/btcsuite/btcwallet/wallet/txrules v1.2.1 h1:UZo7YRzdHbwhK7Rhv3PO9bXgTxiOH45edK5qdsdiatk=
diff --git a/lnrpc/walletrpc/psbt.go b/lnrpc/walletrpc/psbt.go
index 19b1024733..0577519647 100644
--- a/lnrpc/walletrpc/psbt.go
+++ b/lnrpc/walletrpc/psbt.go
@@ -56,7 +56,13 @@ func lockInputs(w lnwallet.WalletController,
 			},
 		}
 
-		expiration, pkScript, value, err := w.LeaseOutput(
+		// Get the details about this outpoint.
+		utxo, err := w.FetchOutpointInfo(&lock.Outpoint)
+		if err != nil {
+			return nil, fmt.Errorf("fetch outpoint info: %w", err)
+		}
+
+		expiration, err := w.LeaseOutput(
 			lock.LockID, lock.Outpoint,
 			chanfunding.DefaultLockDuration,
 		)
@@ -80,8 +86,8 @@ func lockInputs(w lnwallet.WalletController,
 		}
 
 		lock.Expiration = expiration
-		lock.PkScript = pkScript
-		lock.Value = int64(value)
+		lock.PkScript = utxo.PkScript
+		lock.Value = int64(utxo.Value)
 		locks[idx] = lock
 	}
 
diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go
index e70a1a7712..cc836e809f 100644
--- a/lnrpc/walletrpc/walletkit_server.go
+++ b/lnrpc/walletrpc/walletkit_server.go
@@ -490,7 +490,7 @@ func (w *WalletKit) LeaseOutput(ctx context.Context,
 	// other concurrent processes attempting to lease the same UTXO.
 	var expiration time.Time
 	err = w.cfg.CoinSelectionLocker.WithCoinSelectLock(func() error {
-		expiration, _, _, err = w.cfg.Wallet.LeaseOutput(
+		expiration, err = w.cfg.Wallet.LeaseOutput(
 			lockID, *op, duration,
 		)
 		return err
@@ -1142,7 +1142,7 @@ func (w *WalletKit) sweepNewInput(op *wire.OutPoint, currentHeight uint32,
 	//
 	// We'll gather all of the information required by the UtxoSweeper in
 	// order to sweep the output.
-	utxo, err := w.cfg.Wallet.FetchInputInfo(op)
+	utxo, err := w.cfg.Wallet.FetchOutpointInfo(op)
 	if err != nil {
 		return err
 	}
diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go
index b3f9b4f9b1..22204dea39 100644
--- a/lnwallet/btcwallet/btcwallet.go
+++ b/lnwallet/btcwallet/btcwallet.go
@@ -1079,36 +1079,20 @@ func (b *BtcWallet) CreateSimpleTx(inputs fn.Set[wire.OutPoint],
 //
 // NOTE: This method requires the global coin selection lock to be held.
 func (b *BtcWallet) LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
-	duration time.Duration) (time.Time, []byte, btcutil.Amount, error) {
+	duration time.Duration) (time.Time, error) {
 
 	// Make sure we don't attempt to double lock an output that's been
 	// locked by the in-memory implementation.
 	if b.wallet.LockedOutpoint(op) {
-		return time.Time{}, nil, 0, wtxmgr.ErrOutputAlreadyLocked
+		return time.Time{}, wtxmgr.ErrOutputAlreadyLocked
 	}
 
 	lockedUntil, err := b.wallet.LeaseOutput(id, op, duration)
 	if err != nil {
-		return time.Time{}, nil, 0, err
+		return time.Time{}, err
 	}
 
-	// Get the pkScript and value for this lock from the list of all leased
-	// outputs.
-	allLeases, err := b.wallet.ListLeasedOutputs()
-	if err != nil {
-		return time.Time{}, nil, 0, err
-	}
-
-	for _, lease := range allLeases {
-		if lease.Outpoint == op {
-			return lockedUntil, lease.PkScript,
-				btcutil.Amount(lease.Value), nil
-		}
-	}
-
-	// We MUST find the leased output in the loop above, otherwise something
-	// is seriously wrong.
-	return time.Time{}, nil, 0, wtxmgr.ErrUnknownOutput
+	return lockedUntil, nil
 }
 
 // ListLeasedOutputs returns a list of all currently locked outputs.
diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go
index 9864164769..97832a7890 100644
--- a/lnwallet/btcwallet/signer.go
+++ b/lnwallet/btcwallet/signer.go
@@ -8,6 +8,7 @@ import (
 	"github.com/btcsuite/btcd/btcec/v2/schnorr"
 	"github.com/btcsuite/btcd/btcutil"
 	"github.com/btcsuite/btcd/btcutil/hdkeychain"
+	"github.com/btcsuite/btcd/btcutil/psbt"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
@@ -18,18 +19,16 @@ import (
 	"github.com/lightningnetwork/lnd/lnwallet"
 )
 
-// FetchInputInfo queries for the WalletController's knowledge of the passed
+// FetchOutpointInfo queries for the WalletController's knowledge of the passed
 // outpoint. If the base wallet determines this output is under its control,
 // then the original txout should be returned. Otherwise, a non-nil error value
 // of ErrNotMine should be returned instead.
 //
 // This is a part of the WalletController interface.
-func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
+func (b *BtcWallet) FetchOutpointInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
 	error) {
 
-	prevTx, txOut, bip32, confirmations, err := b.wallet.FetchInputInfo(
-		prevOut,
-	)
+	prevTx, txOut, confirmations, err := b.wallet.FetchOutpointInfo(prevOut)
 	if err != nil {
 		return nil, err
 	}
@@ -51,11 +50,18 @@ func (b *BtcWallet) FetchInputInfo(prevOut *wire.OutPoint) (*lnwallet.Utxo,
 		PkScript:      txOut.PkScript,
 		Confirmations: confirmations,
 		OutPoint:      *prevOut,
-		Derivation:    bip32,
 		PrevTx:        prevTx,
 	}, nil
 }
 
+// FetchDerivationInfo queries for the wallet's knowledge of the passed
+// pkScript and constructs the derivation info and returns it.
+func (b *BtcWallet) FetchDerivationInfo(
+	pkScript []byte) (*psbt.Bip32Derivation, error) {
+
+	return b.wallet.FetchDerivationInfo(pkScript)
+}
+
 // ScriptForOutput returns the address, witness program and redeem script for a
 // given UTXO. An error is returned if the UTXO does not belong to our wallet or
 // it is not a managed pubKey address.
diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/assembler.go
index a694d2a2c4..2fdb87b32d 100644
--- a/lnwallet/chanfunding/assembler.go
+++ b/lnwallet/chanfunding/assembler.go
@@ -44,7 +44,7 @@ type OutputLeaser interface {
 	// LeaseOutput leases a target output, rendering it unusable for coin
 	// selection.
 	LeaseOutput(i wtxmgr.LockID, o wire.OutPoint, d time.Duration) (
-		time.Time, []byte, btcutil.Amount, error)
+		time.Time, error)
 
 	// ReleaseOutput releases a target output, allowing it to be used for
 	// coin selection once again.
diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go
index 4f2aa759b2..f824210e13 100644
--- a/lnwallet/chanfunding/wallet_assembler.go
+++ b/lnwallet/chanfunding/wallet_assembler.go
@@ -525,7 +525,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
 		for _, coin := range selectedCoins {
 			outpoint := coin.OutPoint
 
-			_, _, _, err = w.cfg.CoinLeaser.LeaseOutput(
+			_, err = w.cfg.CoinLeaser.LeaseOutput(
 				LndInternalLockID, outpoint,
 				DefaultReservationTimeout,
 			)
diff --git a/lnwallet/interface.go b/lnwallet/interface.go
index af5955d8e6..d3b123509b 100644
--- a/lnwallet/interface.go
+++ b/lnwallet/interface.go
@@ -126,8 +126,7 @@ type Utxo struct {
 	Confirmations int64
 	PkScript      []byte
 	wire.OutPoint
-	Derivation *psbt.Bip32Derivation
-	PrevTx     *wire.MsgTx
+	PrevTx *wire.MsgTx
 }
 
 // OutputDetail contains additional information on a destination address.
@@ -227,11 +226,16 @@ type TransactionSubscription interface {
 // behavior of all interface methods in order to ensure identical behavior
 // across all concrete implementations.
 type WalletController interface {
-	// FetchInputInfo queries for the WalletController's knowledge of the
-	// passed outpoint. If the base wallet determines this output is under
-	// its control, then the original txout should be returned.  Otherwise,
-	// a non-nil error value of ErrNotMine should be returned instead.
-	FetchInputInfo(prevOut *wire.OutPoint) (*Utxo, error)
+	// FetchOutpointInfo queries for the WalletController's knowledge of
+	// the passed outpoint. If the base wallet determines this output is
+	// under its control, then the original txout should be returned.
+	// Otherwise, a non-nil error value of ErrNotMine should be returned
+	// instead.
+	FetchOutpointInfo(prevOut *wire.OutPoint) (*Utxo, error)
+
+	// FetchDerivationInfo queries for the wallet's knowledge of the passed
+	// pkScript and constructs the derivation info and returns it.
+	FetchDerivationInfo(pkScript []byte) (*psbt.Bip32Derivation, error)
 
 	// ScriptForOutput returns the address, witness program and redeem
 	// script for a given UTXO. An error is returned if the UTXO does not
@@ -409,8 +413,7 @@ type WalletController interface {
 	//
 	// NOTE: This method requires the global coin selection lock to be held.
 	LeaseOutput(id wtxmgr.LockID, op wire.OutPoint,
-		duration time.Duration) (time.Time, []byte, btcutil.Amount,
-		error)
+		duration time.Duration) (time.Time, error)
 
 	// ReleaseOutput unlocks an output, allowing it to be available for coin
 	// selection if it remains unspent. The ID should match the one used to
diff --git a/lnwallet/mock.go b/lnwallet/mock.go
index e6bd0b2e41..faac5fa678 100644
--- a/lnwallet/mock.go
+++ b/lnwallet/mock.go
@@ -45,9 +45,9 @@ func (w *mockWalletController) BackEnd() string {
 	return "mock"
 }
 
-// FetchInputInfo will be called to get info about the inputs to the funding
+// FetchOutpointInfo will be called to get info about the inputs to the funding
 // transaction.
-func (w *mockWalletController) FetchInputInfo(
+func (w *mockWalletController) FetchOutpointInfo(
 	prevOut *wire.OutPoint) (*Utxo, error) {
 
 	utxo := &Utxo{
@@ -201,9 +201,9 @@ func (w *mockWalletController) ListTransactionDetails(int32, int32,
 
 // LeaseOutput returns the current time and a nil error.
 func (w *mockWalletController) LeaseOutput(wtxmgr.LockID, wire.OutPoint,
-	time.Duration) (time.Time, []byte, btcutil.Amount, error) {
+	time.Duration) (time.Time, error) {
 
-	return time.Now(), nil, 0, nil
+	return time.Now(), nil
 }
 
 // ReleaseOutput currently does nothing.
@@ -300,6 +300,14 @@ func (w *mockWalletController) RemoveDescendants(*wire.MsgTx) error {
 	return nil
 }
 
+// FetchDerivationInfo queries for the wallet's knowledge of the passed
+// pkScript and constructs the derivation info and returns it.
+func (w *mockWalletController) FetchDerivationInfo(
+	pkScript []byte) (*psbt.Bip32Derivation, error) {
+
+	return nil, nil
+}
+
 func (w *mockWalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
 	return nil
 }
diff --git a/lnwallet/rpcwallet/rpcwallet.go b/lnwallet/rpcwallet/rpcwallet.go
index c702174163..bf6aa61df3 100644
--- a/lnwallet/rpcwallet/rpcwallet.go
+++ b/lnwallet/rpcwallet/rpcwallet.go
@@ -154,7 +154,7 @@ func (r *RPCKeyRing) SendOutputs(inputs fn.Set[wire.OutPoint],
 		// watch-only wallet if it can map this outpoint into a coin we
 		// own. If not, then we can't continue because our wallet state
 		// is out of sync.
-		info, err := r.WalletController.FetchInputInfo(
+		info, err := r.WalletController.FetchOutpointInfo(
 			&txIn.PreviousOutPoint,
 		)
 		if err != nil {
@@ -289,7 +289,7 @@ func (r *RPCKeyRing) FinalizePsbt(packet *psbt.Packet, _ string) error {
 		// We can only sign this input if it's ours, so we try to map it
 		// to a coin we own. If we can't, then we'll continue as it
 		// isn't our input.
-		utxo, err := r.FetchInputInfo(&txIn.PreviousOutPoint)
+		utxo, err := r.FetchOutpointInfo(&txIn.PreviousOutPoint)
 		if err != nil {
 			continue
 		}
@@ -906,7 +906,7 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
 		}
 
 		txIn := tx.TxIn[idx]
-		info, err := r.WalletController.FetchInputInfo(
+		info, err := r.WalletController.FetchOutpointInfo(
 			&txIn.PreviousOutPoint,
 		)
 		if err != nil {
@@ -1015,19 +1015,32 @@ func (r *RPCKeyRing) remoteSign(tx *wire.MsgTx, signDesc *input.SignDescriptor,
 		signDesc.KeyDesc.PubKey = fullDesc.PubKey
 	}
 
+	var derivation *psbt.Bip32Derivation
+
 	// Make sure we actually know about the input. We either have been
 	// watching the UTXO on-chain or we have been given all the required
 	// info in the sign descriptor.
-	info, err := r.WalletController.FetchInputInfo(&txIn.PreviousOutPoint)
+	info, err := r.WalletController.FetchOutpointInfo(
+		&txIn.PreviousOutPoint,
+	)
+
+	// If the wallet is aware of this outpoint, we go ahead and fetch the
+	// derivation info.
+	if err == nil {
+		derivation, err = r.WalletController.FetchDerivationInfo(
+			info.PkScript,
+		)
+	}
+
 	switch {
-	// No error, we do have the full UTXO and derivation info available.
+	// No error, we do have the full UTXO info available.
 	case err == nil:
 		in.WitnessUtxo = &wire.TxOut{
 			Value:    int64(info.Value),
 			PkScript: info.PkScript,
 		}
 		in.NonWitnessUtxo = info.PrevTx
-		in.Bip32Derivation = []*psbt.Bip32Derivation{info.Derivation}
+		in.Bip32Derivation = []*psbt.Bip32Derivation{derivation}
 
 	// The wallet doesn't know about this UTXO, so it's probably a TX that
 	// we haven't published yet (e.g. a channel funding TX). So we need to
diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go
index 90a0bea2d3..6925446a3e 100644
--- a/lnwallet/wallet.go
+++ b/lnwallet/wallet.go
@@ -1646,7 +1646,7 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
 			[]*input.Script, 0, len(ourContribution.Inputs),
 		)
 		for _, txIn := range fundingTx.TxIn {
-			_, err := l.FetchInputInfo(&txIn.PreviousOutPoint)
+			_, err := l.FetchOutpointInfo(&txIn.PreviousOutPoint)
 			if err != nil {
 				continue
 			}
@@ -2592,7 +2592,7 @@ func (c *CoinSource) ListCoins(minConfs int32,
 // its outpoint. If the coin isn't under the control of the backing CoinSource,
 // then an error should be returned.
 func (c *CoinSource) CoinFromOutPoint(op wire.OutPoint) (*wallet.Coin, error) {
-	inputInfo, err := c.wallet.FetchInputInfo(&op)
+	inputInfo, err := c.wallet.FetchOutpointInfo(&op)
 	if err != nil {
 		return nil, err
 	}
@@ -2689,7 +2689,7 @@ func NewWalletPrevOutputFetcher(wc WalletController) *WalletPrevOutputFetcher {
 // passed outpoint. A nil value will be returned if the passed outpoint doesn't
 // exist.
 func (w *WalletPrevOutputFetcher) FetchPrevOutput(op wire.OutPoint) *wire.TxOut {
-	utxo, err := w.wc.FetchInputInfo(&op)
+	utxo, err := w.wc.FetchOutpointInfo(&op)
 	if err != nil {
 		return nil
 	}
diff --git a/rpcserver.go b/rpcserver.go
index 7fef873266..005bc38fc9 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -3488,14 +3488,7 @@ func (r *rpcServer) WalletBalance(ctx context.Context,
 		return nil, err
 	}
 	for _, leasedOutput := range leases {
-		utxoInfo, err := r.server.cc.Wallet.FetchInputInfo(
-			&leasedOutput.Outpoint,
-		)
-		if err != nil {
-			return nil, err
-		}
-
-		lockedBalance += utxoInfo.Value
+		lockedBalance += btcutil.Amount(leasedOutput.Value)
 	}
 
 	// Get the current number of non-private anchor channels.
diff --git a/sweep/walletsweep.go b/sweep/walletsweep.go
index 919e5f1d51..da5e56dee0 100644
--- a/sweep/walletsweep.go
+++ b/sweep/walletsweep.go
@@ -179,7 +179,7 @@ type OutputLeaser interface {
 	// LeaseOutput leases a target output, rendering it unusable for coin
 	// selection.
 	LeaseOutput(i wtxmgr.LockID, o wire.OutPoint, d time.Duration) (
-		time.Time, []byte, btcutil.Amount, error)
+		time.Time, error)
 
 	// ReleaseOutput releases a target output, allowing it to be used for
 	// coin selection once again.
@@ -284,7 +284,7 @@ func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
 			log.Tracef("[WithCoinSelectLock] leasing utxo: %v",
 				utxo.OutPoint)
 
-			_, _, _, err = outputLeaser.LeaseOutput(
+			_, err = outputLeaser.LeaseOutput(
 				chanfunding.LndInternalLockID, utxo.OutPoint,
 				chanfunding.DefaultLockDuration,
 			)


Test patch:
diff --git a/itest/lnd_hold_invoice_force_test.go b/itest/lnd_hold_invoice_force_test.go
index 2b251b3cb9..7c616e7a50 100644
--- a/itest/lnd_hold_invoice_force_test.go
+++ b/itest/lnd_hold_invoice_force_test.go
@@ -95,10 +95,6 @@ func testHoldInvoiceForceClose(ht *lntest.HarnessTest) {
 	// We first mine enough blocks to trigger an invoice cancelation.
 	ht.MineBlocks(int(blocksTillCancel))
 
-	// Wait for the nodes to be synced.
-	ht.WaitForBlockchainSync(alice)
-	ht.WaitForBlockchainSync(bob)
-
 	// Check that the invoice is canceled by Bob.
 	err := wait.NoError(func() error {
 		inv := bob.RPC.LookupInvoice(payHash[:])
@@ -135,10 +131,6 @@ func testHoldInvoiceForceClose(ht *lntest.HarnessTest) {
 	// invoice cancelation message was received by Alice.
 	ht.MineBlocks(int(blocksTillForce - blocksTillCancel))
 
-	// Wait for the nodes to be synced.
-	ht.WaitForBlockchainSync(alice)
-	ht.WaitForBlockchainSync(bob)
-
 	// Check that Alice has not closed the channel because there are no
 	// outgoing HTLCs in her channel as the only HTLC has already been
 	// canceled.
diff --git a/itest/lnd_psbt_test.go b/itest/lnd_psbt_test.go
index 91d67ba376..ac39bb4971 100644
--- a/itest/lnd_psbt_test.go
+++ b/itest/lnd_psbt_test.go
@@ -1578,7 +1578,6 @@ func sendAllCoinsToAddrType(ht *lntest.HarnessTest,
 	})
 
 	ht.MineBlocksAndAssertNumTxes(1, 1)
-	ht.WaitForBlockchainSync(hn)
 }
 
 // testPsbtChanFundingFailFlow tests the failing of a funding flow by the
diff --git a/itest/lnd_route_blinding_test.go b/itest/lnd_route_blinding_test.go
index 44d5879f99..fbfca0f275 100644
--- a/itest/lnd_route_blinding_test.go
+++ b/itest/lnd_route_blinding_test.go
@@ -852,10 +852,6 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
 	ht.AssertNumPendingSweeps(ht.Bob, 0)
 	ht.MineBlocksAndAssertNumTxes(1, 1)
 
-	// Assert that the HTLC has cleared.
-	ht.WaitForBlockchainSync(ht.Bob)
-	ht.WaitForBlockchainSync(ht.Alice)
-
 	ht.AssertHTLCNotActive(ht.Bob, testCase.channels[0], hash[:])
 	ht.AssertHTLCNotActive(ht.Alice, testCase.channels[0], hash[:])
 
diff --git a/lntest/mock/walletcontroller.go b/lntest/mock/walletcontroller.go
index 7af22e0383..cb7e387010 100644
--- a/lntest/mock/walletcontroller.go
+++ b/lntest/mock/walletcontroller.go
@@ -43,9 +43,9 @@ func (w *WalletController) BackEnd() string {
 	return "mock"
 }
 
-// FetchInputInfo will be called to get info about the inputs to the funding
+// FetchOutpointInfo will be called to get info about the inputs to the funding
 // transaction.
-func (w *WalletController) FetchInputInfo(
+func (w *WalletController) FetchOutpointInfo(
 	prevOut *wire.OutPoint) (*lnwallet.Utxo, error) {
 
 	utxo := &lnwallet.Utxo{
@@ -195,9 +195,9 @@ func (w *WalletController) ListTransactionDetails(int32, int32,
 
 // LeaseOutput returns the current time and a nil error.
 func (w *WalletController) LeaseOutput(wtxmgr.LockID, wire.OutPoint,
-	time.Duration) (time.Time, []byte, btcutil.Amount, error) {
+	time.Duration) (time.Time, error) {
 
-	return time.Now(), nil, 0, nil
+	return time.Now(), nil
 }
 
 // ReleaseOutput currently does nothing.
@@ -292,3 +292,11 @@ func (w *WalletController) RemoveDescendants(*wire.MsgTx) error {
 func (w *WalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
 	return nil
 }
+
+// FetchDerivationInfo queries for the wallet's knowledge of the passed
+// pkScript and constructs the derivation info and returns it.
+func (w *WalletController) FetchDerivationInfo(
+	pkScript []byte) (*psbt.Bip32Derivation, error) {
+
+	return nil, nil
+}
diff --git a/sweep/walletsweep_test.go b/sweep/walletsweep_test.go
index f5ed0283e7..968d9cb4fb 100644
--- a/sweep/walletsweep_test.go
+++ b/sweep/walletsweep_test.go
@@ -199,11 +199,11 @@ func newMockOutputLeaser() *mockOutputLeaser {
 }
 
 func (m *mockOutputLeaser) LeaseOutput(_ wtxmgr.LockID, o wire.OutPoint,
-	t time.Duration) (time.Time, []byte, btcutil.Amount, error) {
+	t time.Duration) (time.Time, error) {
 
 	m.leasedOutputs[o] = struct{}{}
 
-	return time.Now().Add(t), nil, 0, nil
+	return time.Now().Add(t), nil
 }
 
 func (m *mockOutputLeaser) ReleaseOutput(_ wtxmgr.LockID,