multi: expand SendOutputs and CreateSimpleTx to take utxos

This commit updates the interface methods from
`lnwallet.WalletController` to take optional input set which can be used
to create the tx.
This commit is contained in:
Ononiwu Maureen 2024-03-01 20:25:20 +01:00 committed by yyforyongyu
parent 468ca87499
commit 99339f706f
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
8 changed files with 66 additions and 38 deletions

View File

@ -811,8 +811,8 @@ func (w *WalletKit) SendOutputs(ctx context.Context,
// requirement, we can request that the wallet attempts to create this // requirement, we can request that the wallet attempts to create this
// transaction. // transaction.
tx, err := w.cfg.Wallet.SendOutputs( tx, err := w.cfg.Wallet.SendOutputs(
outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw), minConfs, nil, outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw),
label, coinSelectionStrategy, minConfs, label, coinSelectionStrategy,
) )
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -16,6 +16,7 @@ import (
base "github.com/btcsuite/btcwallet/wallet" base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@ -33,6 +34,10 @@ type WalletController struct {
Utxos []*lnwallet.Utxo Utxos []*lnwallet.Utxo
} }
// A compile time check to ensure this mocked WalletController implements the
// WalletController.
var _ lnwallet.WalletController = (*WalletController)(nil)
// BackEnd returns "mock" to signify a mock wallet controller. // BackEnd returns "mock" to signify a mock wallet controller.
func (w *WalletController) BackEnd() string { func (w *WalletController) BackEnd() string {
return "mock" return "mock"
@ -140,15 +145,15 @@ func (w *WalletController) ImportTaprootScript(waddrmgr.KeyScope,
} }
// SendOutputs currently returns dummy values. // SendOutputs currently returns dummy values.
func (w *WalletController) SendOutputs([]*wire.TxOut, func (w *WalletController) SendOutputs(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, string, chainfee.SatPerKWeight, int32, string, base.CoinSelectionStrategy) (
base.CoinSelectionStrategy) (*wire.MsgTx, error) { *wire.MsgTx, error) {
return nil, nil return nil, nil
} }
// CreateSimpleTx currently returns dummy values. // CreateSimpleTx currently returns dummy values.
func (w *WalletController) CreateSimpleTx([]*wire.TxOut, func (w *WalletController) CreateSimpleTx(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy, chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy,
bool) (*txauthor.AuthoredTx, error) { bool) (*txauthor.AuthoredTx, error) {

View File

@ -19,6 +19,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
base "github.com/btcsuite/btcwallet/wallet" base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/wallet/txrules"
@ -26,6 +27,7 @@ import (
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/blockcache"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
@ -977,8 +979,9 @@ func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut, func (b *BtcWallet) SendOutputs(inputs fn.Set[wire.OutPoint],
feeRate chainfee.SatPerKWeight, minConfs int32, label string, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) { strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) {
// Convert our fee rate from sat/kw to sat/kb since it's required by // Convert our fee rate from sat/kw to sat/kb since it's required by
@ -995,6 +998,14 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
return nil, lnwallet.ErrInvalidMinconf return nil, lnwallet.ErrInvalidMinconf
} }
// Use selected UTXOs if specified, otherwise default selection.
if len(inputs) != 0 {
return b.wallet.SendOutputsWithInput(
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
strategy, label, inputs.ToSlice(),
)
}
return b.wallet.SendOutputs( return b.wallet.SendOutputs(
outputs, nil, defaultAccount, minConfs, feeSatPerKB, outputs, nil, defaultAccount, minConfs, feeSatPerKB,
strategy, label, strategy, label,
@ -1014,10 +1025,10 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
// //
// This is a part of the WalletController interface. // This is a part of the WalletController interface.
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut, func (b *BtcWallet) CreateSimpleTx(inputs fn.Set[wire.OutPoint],
feeRate chainfee.SatPerKWeight, minConfs int32, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, minConfs int32,
strategy base.CoinSelectionStrategy, strategy base.CoinSelectionStrategy, dryRun bool) (
dryRun bool) (*txauthor.AuthoredTx, error) { *txauthor.AuthoredTx, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert // The fee rate is passed in using units of sat/kw, so we'll convert
// this to sat/KB as the CreateSimpleTx method requires this unit. // this to sat/KB as the CreateSimpleTx method requires this unit.
@ -1047,9 +1058,12 @@ func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
} }
} }
// Add the optional inputs to the transaction.
optFunc := wallet.WithCustomSelectUtxos(inputs.ToSlice())
return b.wallet.CreateSimpleTx( return b.wallet.CreateSimpleTx(
nil, defaultAccount, outputs, minConfs, feeSatPerKB, nil, defaultAccount, outputs, minConfs, feeSatPerKB,
strategy, dryRun, strategy, dryRun, []wallet.TxCreateOption{optFunc}...,
) )
} }

View File

@ -18,6 +18,7 @@ import (
base "github.com/btcsuite/btcwallet/wallet" base "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@ -344,8 +345,8 @@ type WalletController interface {
// be used when crafting the transaction. // be used when crafting the transaction.
// //
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
SendOutputs(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, SendOutputs(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
minConfs int32, label string, feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) strategy base.CoinSelectionStrategy) (*wire.MsgTx, error)
// CreateSimpleTx creates a Bitcoin transaction paying to the specified // CreateSimpleTx creates a Bitcoin transaction paying to the specified
@ -360,9 +361,10 @@ type WalletController interface {
// SHOULD NOT be broadcasted. // SHOULD NOT be broadcasted.
// //
// NOTE: This method requires the global coin selection lock to be held. // NOTE: This method requires the global coin selection lock to be held.
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, CreateSimpleTx(inputs fn.Set[wire.OutPoint], outputs []*wire.TxOut,
minConfs int32, strategy base.CoinSelectionStrategy, feeRate chainfee.SatPerKWeight, minConfs int32,
dryRun bool) (*txauthor.AuthoredTx, error) strategy base.CoinSelectionStrategy, dryRun bool) (
*txauthor.AuthoredTx, error)
// GetTransactionDetails returns a detailed description of a transaction // GetTransactionDetails returns a detailed description of a transaction
// given its transaction hash. // given its transaction hash.

View File

@ -17,6 +17,7 @@ import (
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwallet/chainfee"
) )
@ -35,6 +36,10 @@ type mockWalletController struct {
Utxos []*Utxo Utxos []*Utxo
} }
// A compile time check to ensure that mockWalletController implements the
// WalletController.
var _ WalletController = (*mockWalletController)(nil)
// BackEnd returns "mock" to signify a mock wallet controller. // BackEnd returns "mock" to signify a mock wallet controller.
func (w *mockWalletController) BackEnd() string { func (w *mockWalletController) BackEnd() string {
return "mock" return "mock"
@ -145,7 +150,7 @@ func (w *mockWalletController) ImportTaprootScript(waddrmgr.KeyScope,
} }
// SendOutputs currently returns dummy values. // SendOutputs currently returns dummy values.
func (w *mockWalletController) SendOutputs([]*wire.TxOut, func (w *mockWalletController) SendOutputs(fn.Set[wire.OutPoint], []*wire.TxOut,
chainfee.SatPerKWeight, int32, string, chainfee.SatPerKWeight, int32, string,
base.CoinSelectionStrategy) (*wire.MsgTx, error) { base.CoinSelectionStrategy) (*wire.MsgTx, error) {
@ -153,9 +158,9 @@ func (w *mockWalletController) SendOutputs([]*wire.TxOut,
} }
// CreateSimpleTx currently returns dummy values. // CreateSimpleTx currently returns dummy values.
func (w *mockWalletController) CreateSimpleTx([]*wire.TxOut, func (w *mockWalletController) CreateSimpleTx(fn.Set[wire.OutPoint],
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy, []*wire.TxOut, chainfee.SatPerKWeight, int32,
bool) (*txauthor.AuthoredTx, error) { base.CoinSelectionStrategy, bool) (*txauthor.AuthoredTx, error) {
return nil, nil return nil, nil
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
basewallet "github.com/btcsuite/btcwallet/wallet" basewallet "github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
@ -120,12 +121,13 @@ func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
// NOTE: This is a part of the WalletController interface. // NOTE: This is a part of the WalletController interface.
// //
// NOTE: This method only signs with BIP49/84 keys. // NOTE: This method only signs with BIP49/84 keys.
func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut, func (r *RPCKeyRing) SendOutputs(inputs fn.Set[wire.OutPoint],
feeRate chainfee.SatPerKWeight, minConfs int32, label string, outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, label string,
strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) { strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) {
tx, err := r.WalletController.SendOutputs( tx, err := r.WalletController.SendOutputs(
outputs, feeRate, minConfs, label, strategy, inputs, outputs, feeRate, minConfs, label, strategy,
) )
if err != nil && err != basewallet.ErrTxUnsigned { if err != nil && err != basewallet.ErrTxUnsigned {
return nil, err return nil, err

View File

@ -169,7 +169,7 @@ func sendCoins(t *testing.T, miner *rpctest.Harness,
t.Helper() t.Helper()
tx, err := sender.SendOutputs( tx, err := sender.SendOutputs(
[]*wire.TxOut{output}, feeRate, minConf, labels.External, nil, []*wire.TxOut{output}, feeRate, minConf, labels.External,
sender.Cfg.CoinSelectionStrategy, sender.Cfg.CoinSelectionStrategy,
) )
require.NoError(t, err, "unable to send transaction") require.NoError(t, err, "unable to send transaction")
@ -1193,7 +1193,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
require.NoError(t, err, "unable to make output script") require.NoError(t, err, "unable to make output script")
burnOutput := wire.NewTxOut(outputAmt, outputScript) burnOutput := wire.NewTxOut(outputAmt, outputScript)
burnTX, err := alice.SendOutputs( burnTX, err := alice.SendOutputs(
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External, nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy, alice.Cfg.CoinSelectionStrategy,
) )
require.NoError(t, err, "unable to create burn tx") require.NoError(t, err, "unable to create burn tx")
@ -1453,7 +1453,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
burnOutput := wire.NewTxOut(outputAmt, outputScript) burnOutput := wire.NewTxOut(outputAmt, outputScript)
tx, err := alice.SendOutputs( tx, err := alice.SendOutputs(
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External, nil, []*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy, alice.Cfg.CoinSelectionStrategy,
) )
require.NoError(t, err, "unable to create tx") require.NoError(t, err, "unable to create tx")
@ -1642,7 +1642,7 @@ func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
PkScript: keyScript, PkScript: keyScript,
} }
tx, err := alice.SendOutputs( tx, err := alice.SendOutputs(
[]*wire.TxOut{newOutput}, 2500, 1, labels.External, nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy, alice.Cfg.CoinSelectionStrategy,
) )
require.NoError(t, err, "unable to create output") require.NoError(t, err, "unable to create output")
@ -1958,7 +1958,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
PkScript: keyScript, PkScript: keyScript,
} }
tx, err := alice.SendOutputs( tx, err := alice.SendOutputs(
[]*wire.TxOut{newOutput}, 2500, 1, labels.External, nil, []*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy, alice.Cfg.CoinSelectionStrategy,
) )
if err != nil { if err != nil {
@ -2077,7 +2077,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
PkScript: script, PkScript: script,
} }
tx, err := w.SendOutputs( tx, err := w.SendOutputs(
[]*wire.TxOut{output}, 2500, 1, labels.External, nil, []*wire.TxOut{output}, 2500, 1, labels.External,
w.Cfg.CoinSelectionStrategy, w.Cfg.CoinSelectionStrategy,
) )
require.NoError(t, err, "unable to send outputs") require.NoError(t, err, "unable to send outputs")
@ -2302,7 +2302,7 @@ func testSpendUnconfirmed(miner *rpctest.Harness,
PkScript: alicePkScript, PkScript: alicePkScript,
} }
_, err = bob.SendOutputs( _, err = bob.SendOutputs(
[]*wire.TxOut{output}, txFeeRate, 0, labels.External, nil, []*wire.TxOut{output}, txFeeRate, 0, labels.External,
bob.Cfg.CoinSelectionStrategy, bob.Cfg.CoinSelectionStrategy,
) )
if err == nil { if err == nil {
@ -2329,7 +2329,7 @@ func testSpendUnconfirmed(miner *rpctest.Harness,
// First, verify that we don't have enough balance to send the coins // First, verify that we don't have enough balance to send the coins
// using confirmed outputs only. // using confirmed outputs only.
_, err = bob.SendOutputs( _, err = bob.SendOutputs(
[]*wire.TxOut{output}, txFeeRate, 1, labels.External, nil, []*wire.TxOut{output}, txFeeRate, 1, labels.External,
bob.Cfg.CoinSelectionStrategy, bob.Cfg.CoinSelectionStrategy,
) )
if err == nil { if err == nil {
@ -2571,7 +2571,7 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// Now try creating a tx spending to these outputs. // Now try creating a tx spending to these outputs.
createTx, createErr := w.CreateSimpleTx( createTx, createErr := w.CreateSimpleTx(
outputs, feeRate, minConfs, nil, outputs, feeRate, minConfs,
w.Cfg.CoinSelectionStrategy, true, w.Cfg.CoinSelectionStrategy, true,
) )
switch { switch {
@ -2590,7 +2590,7 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// only difference is that the dry run tx is not signed, and // only difference is that the dry run tx is not signed, and
// that the change output position might be different. // that the change output position might be different.
tx, sendErr := w.SendOutputs( tx, sendErr := w.SendOutputs(
outputs, feeRate, minConfs, labels.External, nil, outputs, feeRate, minConfs, labels.External,
w.Cfg.CoinSelectionStrategy, w.Cfg.CoinSelectionStrategy,
) )
switch { switch {

View File

@ -1077,7 +1077,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
// We first do a dry run, to sanity check we won't spend our wallet // We first do a dry run, to sanity check we won't spend our wallet
// balance below the reserved amount. // balance below the reserved amount.
authoredTx, err := r.server.cc.Wallet.CreateSimpleTx( authoredTx, err := r.server.cc.Wallet.CreateSimpleTx(
outputs, feeRate, minConfs, strategy, true, nil, outputs, feeRate, minConfs, strategy, true,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1098,7 +1098,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
// If that checks out, we're fairly confident that creating sending to // If that checks out, we're fairly confident that creating sending to
// these outputs will keep the wallet balance above the reserve. // these outputs will keep the wallet balance above the reserve.
tx, err := r.server.cc.Wallet.SendOutputs( tx, err := r.server.cc.Wallet.SendOutputs(
outputs, feeRate, minConfs, label, strategy, nil, outputs, feeRate, minConfs, label, strategy,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1207,7 +1207,7 @@ func (r *rpcServer) EstimateFee(ctx context.Context,
wallet := r.server.cc.Wallet wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error { err = wallet.WithCoinSelectLock(func() error {
tx, err = wallet.CreateSimpleTx( tx, err = wallet.CreateSimpleTx(
outputs, feePerKw, minConfs, coinSelectionStrategy, nil, outputs, feePerKw, minConfs, coinSelectionStrategy,
true, true,
) )
return err return err