Merge pull request #8515 from mohamedawnallah/add-coin-selection-strategy-on-chain-rpc

coin select: add coin selection strategy option to all on-chain RPCs
This commit is contained in:
Oliver Gugger authored and GitHub committed 2024-04-02 11:20:08 +00:00
commit b1175514f9
23 files changed
+4298 -3882

No files matched your search

+12 -5
View File
@@ -817,6 +817,7 @@ var batchOpenChannelCommand = cli.Command{
"transaction when storing it to the local " +
"wallet after publishing it",
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(batchOpenChannel),
}
@@ -845,13 +846,19 @@ func batchOpenChannel(ctx *cli.Context) error {
return nil
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs"))
req := &lnrpc.BatchOpenChannelRequest{
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
Label: ctx.String("label"),
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
Label: ctx.String("label"),
CoinSelectionStrategy: coinSelectionStrategy,
}
// Let's try and parse the JSON part of the CLI now. Fortunately we can
+49 -18
View File
@@ -200,6 +200,16 @@ func newAddress(ctx *cli.Context) error {
return nil
}
var coinSelectionStrategyFlag = cli.StringFlag{
Name: "coin_selection_strategy",
Usage: "(optional) the strategy to use for selecting " +
"coins. Possible values are 'largest', 'random', or " +
"'global-config'. If either 'largest' or 'random' is " +
"specified, it will override the globally configured " +
"strategy in lnd.conf",
Value: "global-config",
}
var estimateFeeCommand = cli.Command{
Name: "estimatefee",
Category: "On-chain",
@@ -215,9 +225,10 @@ var estimateFeeCommand = cli.Command{
Flags: []cli.Flag{
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the transaction *should* " +
"confirm in",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in",
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(estimateFees),
}
@@ -231,12 +242,18 @@ func estimateFees(ctx *cli.Context) error {
return err
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
CoinSelectionStrategy: coinSelectionStrategy,
})
if err != nil {
return err
@@ -313,6 +330,7 @@ var sendCoinsCommand = cli.Command{
"terminal avoid breaking existing shell " +
"scripts",
},
coinSelectionStrategyFlag,
txLabelFlag,
},
Action: actionDecorator(sendCoins),
@@ -375,6 +393,11 @@ func sendCoins(ctx *cli.Context) error {
"sweep all coins out of the wallet")
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs"))
@@ -409,14 +432,15 @@ func sendCoins(ctx *cli.Context) error {
}
req := &lnrpc.SendCoinsRequest{
Addr: addr,
Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
SendAll: ctx.Bool("sweepall"),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
Addr: addr,
Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
SendAll: ctx.Bool("sweepall"),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
}
txid, err := client.SendCoins(ctxc, req)
if err != nil {
@@ -585,6 +609,7 @@ var sendManyCommand = cli.Command{
"must satisfy",
Value: defaultUtxoMinConf,
},
coinSelectionStrategyFlag,
txLabelFlag,
},
Action: actionDecorator(sendMany),
@@ -615,17 +640,23 @@ func sendMany(ctx *cli.Context) error {
return err
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx)
defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs"))
txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag),
Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
})
if err != nil {
return err
+28
View File
@@ -564,3 +564,31 @@ func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
return nil, fmt.Errorf("unknown network: %v", network)
}
}
// parseCoinSelectionStrategy parses a coin selection strategy string
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
func parseCoinSelectionStrategy(ctx *cli.Context) (
lnrpc.CoinSelectionStrategy, error) {
strategy := ctx.String(coinSelectionStrategyFlag.Name)
if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
nil
}
switch strategy {
case "global-config":
return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
nil
case "largest":
return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
case "random":
return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
default:
return 0, fmt.Errorf("unknown coin selection strategy "+
"%v", strategy)
}
}
+17 -3
View File
@@ -849,6 +849,7 @@ var fundTemplatePsbtCommand = cli.Command{
"if required",
Value: -1,
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(fundTemplatePsbt),
}
@@ -997,6 +998,11 @@ func fundTemplatePsbt(ctx *cli.Context) error {
"inputs/outputs flag")
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs"))
req := &walletrpc.FundPsbtRequest{
Account: ctx.String("account"),
@@ -1005,6 +1011,7 @@ func fundTemplatePsbt(ctx *cli.Context) error {
Template: &walletrpc.FundPsbtRequest_CoinSelect{
CoinSelect: coinSelect,
},
CoinSelectionStrategy: coinSelectionStrategy,
}
// Parse fee flags.
@@ -1167,6 +1174,7 @@ var fundPsbtCommand = cli.Command{
"transaction must satisfy",
Value: defaultUtxoMinConf,
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(fundPsbt),
}
@@ -1180,11 +1188,17 @@ func fundPsbt(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, "fund")
}
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs"))
req := &walletrpc.FundPsbtRequest{
Account: ctx.String("account"),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
Account: ctx.String("account"),
MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
}
// Parse template flags.
@@ -303,6 +303,10 @@ bitcoin peers' feefilter values into account](https://github.com/lightningnetwor
* [Allow callers of `ListSweeps` to specify the start
height](https://github.com/lightningnetwork/lnd/pull/7372).
* [Coin Selection Strategy](https://github.com/lightningnetwork/lnd/pull/8515)
add coin selection strategy option to the following on-chain RPC calls
`EstimateFee`, `SendMany`, `SendCoins`, `BatchOpenChannel`, `SendOutputs`, and `FundPsbt`.
## lncli Updates
* [Documented all available `lncli`
+4 -3
View File
@@ -340,9 +340,10 @@ func (b *Batcher) BatchFund(ctx context.Context,
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: uint64(feeRateSatPerVByte),
},
MinConfs: firstReq.MinConfs,
SpendUnconfirmed: firstReq.MinConfs == 0,
ChangeType: changeType,
MinConfs: firstReq.MinConfs,
SpendUnconfirmed: firstReq.MinConfs == 0,
ChangeType: changeType,
CoinSelectionStrategy: req.CoinSelectionStrategy,
}
fundPsbtResp, err := b.cfg.WalletKitServer.FundPsbt(ctx, fundPsbtReq)
if err != nil {
+3447 -3325
View File
File diff suppressed because it is too large. Load diff
+24
View File
@@ -1091,6 +1091,18 @@ message LightningAddress {
string host = 2;
}
enum CoinSelectionStrategy {
// Use the coin selection strategy defined in the global configuration
// (lnd.conf).
STRATEGY_USE_GLOBAL_CONFIG = 0;
// Select the largest available coins first during coin selection.
STRATEGY_LARGEST = 1;
// Randomly select the available coins during coin selection.
STRATEGY_RANDOM = 2;
}
message EstimateFeeRequest {
// The map from addresses to amounts for the transaction.
map<string, int64> AddrToAmount = 1;
@@ -1105,6 +1117,9 @@ message EstimateFeeRequest {
// Whether unconfirmed outputs should be used as inputs for the transaction.
bool spend_unconfirmed = 4;
// The strategy to use for selecting coins during fees estimation.
CoinSelectionStrategy coin_selection_strategy = 5;
}
message EstimateFeeResponse {
@@ -1145,6 +1160,9 @@ message SendManyRequest {
// Whether unconfirmed outputs should be used as inputs for the transaction.
bool spend_unconfirmed = 8;
// The strategy to use for selecting coins during sending many requests.
CoinSelectionStrategy coin_selection_strategy = 9;
}
message SendManyResponse {
// The id of the transaction
@@ -1187,6 +1205,9 @@ message SendCoinsRequest {
// Whether unconfirmed outputs should be used as inputs for the transaction.
bool spend_unconfirmed = 9;
// The strategy to use for selecting coins.
CoinSelectionStrategy coin_selection_strategy = 10;
}
message SendCoinsResponse {
// The transaction ID of the transaction
@@ -2115,6 +2136,9 @@ message BatchOpenChannelRequest {
// An optional label for the batch transaction, limited to 500 characters.
string label = 6;
// The strategy to use for selecting coins during batch opening channels.
CoinSelectionStrategy coin_selection_strategy = 7;
}
message BatchOpenChannel {
+35
View File
@@ -2655,6 +2655,19 @@
"in": "query",
"required": false,
"type": "boolean"
},
{
"name": "coin_selection_strategy",
"description": "The strategy to use for selecting coins during fees estimation.\n\n - STRATEGY_USE_GLOBAL_CONFIG: Use the coin selection strategy defined in the global configuration\n(lnd.conf).\n - STRATEGY_LARGEST: Select the largest available coins first during coin selection.\n - STRATEGY_RANDOM: Randomly select the available coins during coin selection.",
"in": "query",
"required": false,
"type": "string",
"enum": [
"STRATEGY_USE_GLOBAL_CONFIG",
"STRATEGY_LARGEST",
"STRATEGY_RANDOM"
],
"default": "STRATEGY_USE_GLOBAL_CONFIG"
}
],
"tags": [
@@ -3460,6 +3473,10 @@
"label": {
"type": "string",
"description": "An optional label for the batch transaction, limited to 500 characters."
},
"coin_selection_strategy": {
"$ref": "#/definitions/lnrpcCoinSelectionStrategy",
"description": "The strategy to use for selecting coins during batch opening channels."
}
}
},
@@ -4435,6 +4452,16 @@
}
}
},
"lnrpcCoinSelectionStrategy": {
"type": "string",
"enum": [
"STRATEGY_USE_GLOBAL_CONFIG",
"STRATEGY_LARGEST",
"STRATEGY_RANDOM"
],
"default": "STRATEGY_USE_GLOBAL_CONFIG",
"description": " - STRATEGY_USE_GLOBAL_CONFIG: Use the coin selection strategy defined in the global configuration\n(lnd.conf).\n - STRATEGY_LARGEST: Select the largest available coins first during coin selection.\n - STRATEGY_RANDOM: Randomly select the available coins during coin selection."
},
"lnrpcCommitmentType": {
"type": "string",
"enum": [
@@ -6912,6 +6939,10 @@
"spend_unconfirmed": {
"type": "boolean",
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
},
"coin_selection_strategy": {
"$ref": "#/definitions/lnrpcCoinSelectionStrategy",
"description": "The strategy to use for selecting coins."
}
}
},
@@ -6985,6 +7016,10 @@
"spend_unconfirmed": {
"type": "boolean",
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
},
"coin_selection_strategy": {
"$ref": "#/definitions/lnrpcCoinSelectionStrategy",
"description": "The strategy to use for selecting coins during sending many requests."
}
}
},
+28
View File
@@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
@@ -179,3 +180,30 @@ func MarshalOutPoint(op *wire.OutPoint) *OutPoint {
OutputIndex: op.Index,
}
}
// UnmarshallCoinSelectionStrategy converts a lnrpc.CoinSelectionStrategy proto
// type to its wallet.CoinSelectionStrategy counterpart type, considering
// a global default strategy if necessary.
//
// The globalStrategy parameter specifies the default coin selection strategy
// to use if the strategy is set to STRATEGY_USE_GLOBAL_CONFIG. This allows
// flexibility in defining a default strategy at a global level.
func UnmarshallCoinSelectionStrategy(strategy CoinSelectionStrategy,
globalStrategy wallet.CoinSelectionStrategy) (
wallet.CoinSelectionStrategy, error) {
switch strategy {
case CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG:
return globalStrategy, nil
case CoinSelectionStrategy_STRATEGY_LARGEST:
return wallet.CoinSelectionLargest, nil
case CoinSelectionStrategy_STRATEGY_RANDOM:
return wallet.CoinSelectionRandom, nil
default:
return nil, fmt.Errorf("unknown coin selection strategy "+
"%v", strategy)
}
}
File diff suppressed because it is too large. Load diff
+6
View File
@@ -813,6 +813,9 @@ message SendOutputsRequest {
// Whether unconfirmed outputs should be used as inputs for the transaction.
bool spend_unconfirmed = 5;
// The strategy to use for selecting coins during sending the outputs.
lnrpc.CoinSelectionStrategy coin_selection_strategy = 6;
}
message SendOutputsResponse {
/*
@@ -1308,6 +1311,9 @@ message FundPsbtRequest {
// accounts, no change type should be provided as the coin selection key
// scope will always be used to generate the change address.
ChangeAddressType change_type = 8;
// The strategy to use for selecting coins during funding the PSBT.
lnrpc.CoinSelectionStrategy coin_selection_strategy = 10;
}
message FundPsbtResponse {
/*
+18
View File
@@ -949,6 +949,16 @@
"description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)\n- `p2tr`: Pay to taproot pubkey (`TAPROOT_PUBKEY` = 4)",
"title": "`AddressType` has to be one of:"
},
"lnrpcCoinSelectionStrategy": {
"type": "string",
"enum": [
"STRATEGY_USE_GLOBAL_CONFIG",
"STRATEGY_LARGEST",
"STRATEGY_RANDOM"
],
"default": "STRATEGY_USE_GLOBAL_CONFIG",
"description": " - STRATEGY_USE_GLOBAL_CONFIG: Use the coin selection strategy defined in the global configuration\n(lnd.conf).\n - STRATEGY_LARGEST: Select the largest available coins first during coin selection.\n - STRATEGY_RANDOM: Randomly select the available coins during coin selection."
},
"lnrpcOutPoint": {
"type": "object",
"properties": {
@@ -1467,6 +1477,10 @@
"change_type": {
"$ref": "#/definitions/walletrpcChangeAddressType",
"description": "The address type for the change. If empty, P2WPKH addresses will be used\nfor default accounts and single imported public keys. For custom\naccounts, no change type should be provided as the coin selection key\nscope will always be used to generate the change address."
},
"coin_selection_strategy": {
"$ref": "#/definitions/lnrpcCoinSelectionStrategy",
"description": "The strategy to use for selecting coins during funding the PSBT."
}
}
},
@@ -1909,6 +1923,10 @@
"spend_unconfirmed": {
"type": "boolean",
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
},
"coin_selection_strategy": {
"$ref": "#/definitions/lnrpcCoinSelectionStrategy",
"description": "The strategy to use for selecting coins during sending the outputs."
}
}
},
+26 -11
View File
@@ -799,12 +799,19 @@ func (w *WalletKit) SendOutputs(ctx context.Context,
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
// Now that we have the outputs mapped and checked for the reserve
// requirement, we can request that the wallet attempts to create this
// transaction.
tx, err := w.cfg.Wallet.SendOutputs(
outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw), minConfs,
label,
label, coinSelectionStrategy,
)
if err != nil {
return nil, err
@@ -1173,12 +1180,15 @@ func (w *WalletKit) LabelTransaction(ctx context.Context,
func (w *WalletKit) FundPsbt(_ context.Context,
req *FundPsbtRequest) (*FundPsbtResponse, error) {
var (
err error
feeSatPerKW chainfee.SatPerKWeight
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
// Determine the desired transaction fee.
var feeSatPerKW chainfee.SatPerKWeight
switch {
// Estimate the fee by the target number of blocks to confirmation.
case req.GetTargetConf() != 0:
@@ -1241,7 +1251,7 @@ func (w *WalletKit) FundPsbt(_ context.Context,
// wallet.
return w.fundPsbtInternalWallet(
account, keyScopeFromChangeAddressType(req.ChangeType),
packet, minConfs, feeSatPerKW,
packet, minConfs, feeSatPerKW, coinSelectionStrategy,
)
// The template is specified as a PSBT with the intention to perform
@@ -1317,7 +1327,7 @@ func (w *WalletKit) FundPsbt(_ context.Context,
// coin selection algorithm.
return w.fundPsbtCoinSelect(
account, changeIndex, packet, minConfs, changeType,
feeSatPerKW,
feeSatPerKW, coinSelectionStrategy,
)
// The template is specified as a RPC message. We need to create a new
@@ -1374,7 +1384,7 @@ func (w *WalletKit) FundPsbt(_ context.Context,
// wallet.
return w.fundPsbtInternalWallet(
account, keyScopeFromChangeAddressType(req.ChangeType),
packet, minConfs, feeSatPerKW,
packet, minConfs, feeSatPerKW, coinSelectionStrategy,
)
default:
@@ -1387,7 +1397,8 @@ func (w *WalletKit) FundPsbt(_ context.Context,
// wallet that does not allow specifying custom inputs while selecting coins.
func (w *WalletKit) fundPsbtInternalWallet(account string,
keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
feeSatPerKW chainfee.SatPerKWeight) (*FundPsbtResponse, error) {
feeSatPerKW chainfee.SatPerKWeight,
strategy base.CoinSelectionStrategy) (*FundPsbtResponse, error) {
// The RPC parsing part is now over. Several of the following operations
// require us to hold the global coin selection lock, so we do the rest
@@ -1419,7 +1430,8 @@ func (w *WalletKit) fundPsbtInternalWallet(account string,
// lock any coins but might still change the wallet DB by
// generating a new change address.
changeIndex, err := w.cfg.Wallet.FundPsbt(
packet, minConfs, feeSatPerKW, account, keyScope,
packet, minConfs, feeSatPerKW, account,
keyScope, strategy,
)
if err != nil {
return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
@@ -1454,10 +1466,13 @@ func (w *WalletKit) fundPsbtInternalWallet(account string,
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
// funding coin selection algorithm that allows specifying custom inputs while
// selecting coins.
//
//nolint:funlen
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
packet *psbt.Packet, minConfs int32,
changeType chanfunding.ChangeAddressType,
feeRate chainfee.SatPerKWeight) (*FundPsbtResponse, error) {
feeRate chainfee.SatPerKWeight, strategy base.CoinSelectionStrategy) (
*FundPsbtResponse, error) {
// We want to make sure we don't select any inputs that are already
// specified in the template. To do that, we require those inputs to
@@ -1603,7 +1618,7 @@ func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
selectedCoins, changeAmount, err := chanfunding.CoinSelect(
feeRate, fundingAmount, changeDustLimit, coins,
w.cfg.CoinSelectionStrategy, estimator, changeType,
strategy, estimator, changeType,
)
if err != nil {
return fmt.Errorf("error selecting coins: %w", err)
+1
View File
@@ -574,6 +574,7 @@ func TestFundPsbtCoinSelect(t *testing.T) {
resp, err := rpcServer.fundPsbtCoinSelect(
"", tc.changeIndex, copiedPacket, 0,
tc.changeType, tc.feeRate,
rpcServer.cfg.CoinSelectionStrategy,
)
switch {
+5 -3
View File
@@ -141,14 +141,16 @@ func (w *WalletController) ImportTaprootScript(waddrmgr.KeyScope,
// SendOutputs currently returns dummy values.
func (w *WalletController) SendOutputs([]*wire.TxOut,
chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) {
chainfee.SatPerKWeight, int32, string,
base.CoinSelectionStrategy) (*wire.MsgTx, error) {
return nil, nil
}
// CreateSimpleTx currently returns dummy values.
func (w *WalletController) CreateSimpleTx([]*wire.TxOut,
chainfee.SatPerKWeight, int32, bool) (*txauthor.AuthoredTx, error) {
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy,
bool) (*txauthor.AuthoredTx, error) {
return nil, nil
}
@@ -206,7 +208,7 @@ func (w *WalletController) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
// FundPsbt currently does nothing.
func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
string, *waddrmgr.KeyScope) (int32, error) {
string, *waddrmgr.KeyScope, base.CoinSelectionStrategy) (int32, error) {
return 0, nil
}
+5 -4
View File
@@ -978,8 +978,8 @@ func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
//
// This is a part of the WalletController interface.
func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32,
label string) (*wire.MsgTx, error) {
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error) {
// Convert our fee rate from sat/kw to sat/kb since it's required by
// SendOutputs.
@@ -997,7 +997,7 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
return b.wallet.SendOutputs(
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
b.cfg.CoinSelectionStrategy, label,
strategy, label,
)
}
@@ -1016,6 +1016,7 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
// This is a part of the WalletController interface.
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32,
strategy base.CoinSelectionStrategy,
dryRun bool) (*txauthor.AuthoredTx, error) {
// The fee rate is passed in using units of sat/kw, so we'll convert
@@ -1048,7 +1049,7 @@ func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
return b.wallet.CreateSimpleTx(
nil, defaultAccount, outputs, minConfs, feeSatPerKB,
b.cfg.CoinSelectionStrategy, dryRun,
strategy, dryRun,
)
}
+3 -2
View File
@@ -73,7 +73,8 @@ var (
// This is a part of the WalletController interface.
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, accountName string,
changeScope *waddrmgr.KeyScope) (int32, error) {
changeScope *waddrmgr.KeyScope,
strategy wallet.CoinSelectionStrategy) (int32, error) {
// 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.
@@ -134,7 +135,7 @@ func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
// the partial TX information in the packet.
return b.wallet.FundPsbt(
packet, keyScope, minConfs, accountNum, feeSatPerKB,
b.cfg.CoinSelectionStrategy, opts...,
strategy, opts...,
)
}
+6 -3
View File
@@ -345,7 +345,8 @@ type WalletController interface {
//
// NOTE: This method requires the global coin selection lock to be held.
SendOutputs(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, label string) (*wire.MsgTx, error)
minConfs int32, label string,
strategy base.CoinSelectionStrategy) (*wire.MsgTx, error)
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
// outputs. The transaction is not broadcasted to the network. In the
@@ -360,7 +361,8 @@ type WalletController interface {
//
// NOTE: This method requires the global coin selection lock to be held.
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, dryRun bool) (*txauthor.AuthoredTx, error)
minConfs int32, strategy base.CoinSelectionStrategy,
dryRun bool) (*txauthor.AuthoredTx, error)
// GetTransactionDetails returns a detailed description of a transaction
// given its transaction hash.
@@ -465,7 +467,8 @@ type WalletController interface {
// to lock the inputs before handing them out.
FundPsbt(packet *psbt.Packet, minConfs int32,
feeRate chainfee.SatPerKWeight, account string,
changeScope *waddrmgr.KeyScope) (int32, error)
changeScope *waddrmgr.KeyScope,
strategy base.CoinSelectionStrategy) (int32, error)
// SignPsbt expects a partial transaction with all inputs and outputs
// fully declared and tries to sign all unsigned inputs that have all
+6 -3
View File
@@ -146,14 +146,16 @@ func (w *mockWalletController) ImportTaprootScript(waddrmgr.KeyScope,
// SendOutputs currently returns dummy values.
func (w *mockWalletController) SendOutputs([]*wire.TxOut,
chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) {
chainfee.SatPerKWeight, int32, string,
base.CoinSelectionStrategy) (*wire.MsgTx, error) {
return nil, nil
}
// CreateSimpleTx currently returns dummy values.
func (w *mockWalletController) CreateSimpleTx([]*wire.TxOut,
chainfee.SatPerKWeight, int32, bool) (*txauthor.AuthoredTx, error) {
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy,
bool) (*txauthor.AuthoredTx, error) {
return nil, nil
}
@@ -214,7 +216,8 @@ func (w *mockWalletController) ListLeasedOutputs() (
// FundPsbt currently does nothing.
func (w *mockWalletController) FundPsbt(*psbt.Packet, int32,
chainfee.SatPerKWeight, string, *waddrmgr.KeyScope) (int32, error) {
chainfee.SatPerKWeight, string, *waddrmgr.KeyScope,
base.CoinSelectionStrategy) (int32, error) {
return 0, nil
}
+3 -3
View File
@@ -121,11 +121,11 @@ func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
//
// NOTE: This method only signs with BIP49/84 keys.
func (r *RPCKeyRing) SendOutputs(outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight, minConfs int32,
label string) (*wire.MsgTx, error) {
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) {
tx, err := r.WalletController.SendOutputs(
outputs, feeRate, minConfs, label,
outputs, feeRate, minConfs, label, strategy,
)
if err != nil && err != basewallet.ErrTxUnsigned {
return nil, err
+11 -1
View File
@@ -170,6 +170,7 @@ func sendCoins(t *testing.T, miner *rpctest.Harness,
tx, err := sender.SendOutputs(
[]*wire.TxOut{output}, feeRate, minConf, labels.External,
sender.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to send transaction")
@@ -1189,6 +1190,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
burnOutput := wire.NewTxOut(outputAmt, outputScript)
burnTX, err := alice.SendOutputs(
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create burn tx")
burnTXID := burnTX.TxHash()
@@ -1448,6 +1450,7 @@ func testTransactionSubscriptions(miner *rpctest.Harness,
burnOutput := wire.NewTxOut(outputAmt, outputScript)
tx, err := alice.SendOutputs(
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create tx")
txid := tx.TxHash()
@@ -1636,6 +1639,7 @@ func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
}
tx, err := alice.SendOutputs(
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to create output")
@@ -1951,6 +1955,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
}
tx, err := alice.SendOutputs(
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
alice.Cfg.CoinSelectionStrategy,
)
if err != nil {
t.Fatalf("unable to create output: %v", err)
@@ -2069,6 +2074,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
}
tx, err := w.SendOutputs(
[]*wire.TxOut{output}, 2500, 1, labels.External,
w.Cfg.CoinSelectionStrategy,
)
require.NoError(t, err, "unable to send outputs")
txid := tx.TxHash()
@@ -2293,6 +2299,7 @@ func testSpendUnconfirmed(miner *rpctest.Harness,
}
_, err = bob.SendOutputs(
[]*wire.TxOut{output}, txFeeRate, 0, labels.External,
bob.Cfg.CoinSelectionStrategy,
)
if err == nil {
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
@@ -2319,6 +2326,7 @@ func testSpendUnconfirmed(miner *rpctest.Harness,
// using confirmed outputs only.
_, err = bob.SendOutputs(
[]*wire.TxOut{output}, txFeeRate, 1, labels.External,
bob.Cfg.CoinSelectionStrategy,
)
if err == nil {
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
@@ -2559,7 +2567,8 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// Now try creating a tx spending to these outputs.
createTx, createErr := w.CreateSimpleTx(
outputs, feeRate, minConfs, true,
outputs, feeRate, minConfs,
w.Cfg.CoinSelectionStrategy, true,
)
switch {
case test.valid && createErr != nil:
@@ -2578,6 +2587,7 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
// that the change output position might be different.
tx, sendErr := w.SendOutputs(
outputs, feeRate, minConfs, labels.External,
w.Cfg.CoinSelectionStrategy,
)
switch {
case test.valid && sendErr != nil:
+37 -7
View File
@@ -30,6 +30,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/davecgh/go-spew/spew"
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
@@ -1067,8 +1068,8 @@ func allowCORS(handler http.Handler, origins []string) http.Handler {
// more addresses specified in the passed payment map. The payment map maps an
// address to a specified output value to be sent to that address.
func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
feeRate chainfee.SatPerKWeight, minConfs int32,
label string) (*chainhash.Hash, error) {
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
strategy wallet.CoinSelectionStrategy) (*chainhash.Hash, error) {
outputs, err := addrPairsToOutputs(paymentMap, r.cfg.ActiveNetParams.Params)
if err != nil {
@@ -1078,7 +1079,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
// We first do a dry run, to sanity check we won't spend our wallet
// balance below the reserved amount.
authoredTx, err := r.server.cc.Wallet.CreateSimpleTx(
outputs, feeRate, minConfs, true,
outputs, feeRate, minConfs, strategy, true,
)
if err != nil {
return nil, err
@@ -1099,7 +1100,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
// If that checks out, we're fairly confident that creating sending to
// these outputs will keep the wallet balance above the reserve.
tx, err := r.server.cc.Wallet.SendOutputs(
outputs, feeRate, minConfs, label,
outputs, feeRate, minConfs, label, strategy,
)
if err != nil {
return nil, err
@@ -1192,12 +1193,23 @@ func (r *rpcServer) EstimateFee(ctx context.Context,
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
// We will ask the wallet to create a tx using this fee rate. We set
// dryRun=true to avoid inflating the change addresses in the db.
var tx *txauthor.AuthoredTx
wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error {
tx, err = wallet.CreateSimpleTx(outputs, feePerKw, minConfs, true)
tx, err = wallet.CreateSimpleTx(
outputs, feePerKw, minConfs, coinSelectionStrategy,
true,
)
return err
})
if err != nil {
@@ -1281,6 +1293,14 @@ func (r *rpcServer) SendCoins(ctx context.Context,
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
var txid *chainhash.Hash
wallet := r.server.cc.Wallet
@@ -1417,7 +1437,8 @@ func (r *rpcServer) SendCoins(ctx context.Context,
paymentMap := map[string]int64{targetAddr.String(): in.Amount}
err := wallet.WithCoinSelectLock(func() error {
newTXID, err := r.sendCoinsOnChain(
paymentMap, feePerKw, minConfs, label,
paymentMap, feePerKw, minConfs,
label, coinSelectionStrategy,
)
if err != nil {
return err
@@ -1463,6 +1484,14 @@ func (r *rpcServer) SendMany(ctx context.Context,
return nil, err
}
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
in.CoinSelectionStrategy,
r.server.cc.Wallet.Cfg.CoinSelectionStrategy,
)
if err != nil {
return nil, err
}
rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v",
spew.Sdump(in.AddrToAmount), int64(feePerKw))
@@ -1474,7 +1503,8 @@ func (r *rpcServer) SendMany(ctx context.Context,
wallet := r.server.cc.Wallet
err = wallet.WithCoinSelectLock(func() error {
sendManyTXID, err := r.sendCoinsOnChain(
in.AddrToAmount, feePerKw, minConfs, label,
in.AddrToAmount, feePerKw, minConfs,
label, coinSelectionStrategy,
)
if err != nil {
return err