lncli: add coin selection strategy option to on-chain rpc commands

In this commit we add coin selection strategy option to the
following on-chain rpc commands `fundpsbt`, `fundtemplatepsbt`,
`batchopenchannel`, `estimatefee`, `sendcoins`, and `sendmany`.
This commit is contained in:
Mohamed Awnallah 2024-03-26 19:04:27 +02:00
parent 7c2c0dcf98
commit a0b0e7aa62
No known key found for this signature in database
GPG Key ID: 5D55706029E9B87E
4 changed files with 106 additions and 26 deletions

View File

@ -817,6 +817,7 @@ var batchOpenChannelCommand = cli.Command{
"transaction when storing it to the local " + "transaction when storing it to the local " +
"wallet after publishing it", "wallet after publishing it",
}, },
coinSelectionStrategyFlag,
}, },
Action: actionDecorator(batchOpenChannel), Action: actionDecorator(batchOpenChannel),
} }
@ -845,13 +846,19 @@ func batchOpenChannel(ctx *cli.Context) error {
return nil return nil
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs")) minConfs := int32(ctx.Uint64("min_confs"))
req := &lnrpc.BatchOpenChannelRequest{ req := &lnrpc.BatchOpenChannelRequest{
TargetConf: int32(ctx.Int64("conf_target")), TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")), SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")),
MinConfs: minConfs, MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0, SpendUnconfirmed: minConfs == 0,
Label: ctx.String("label"), Label: ctx.String("label"),
CoinSelectionStrategy: coinSelectionStrategy,
} }
// Let's try and parse the JSON part of the CLI now. Fortunately we can // Let's try and parse the JSON part of the CLI now. Fortunately we can

View File

@ -200,6 +200,16 @@ func newAddress(ctx *cli.Context) error {
return nil 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{ var estimateFeeCommand = cli.Command{
Name: "estimatefee", Name: "estimatefee",
Category: "On-chain", Category: "On-chain",
@ -215,9 +225,10 @@ var estimateFeeCommand = cli.Command{
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.Int64Flag{ cli.Int64Flag{
Name: "conf_target", Name: "conf_target",
Usage: "(optional) the number of blocks that the transaction *should* " + Usage: "(optional) the number of blocks that the " +
"confirm in", "transaction *should* confirm in",
}, },
coinSelectionStrategyFlag,
}, },
Action: actionDecorator(estimateFees), Action: actionDecorator(estimateFees),
} }
@ -231,12 +242,18 @@ func estimateFees(ctx *cli.Context) error {
return err return err
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
defer cleanUp() defer cleanUp()
resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{ resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{
AddrToAmount: amountToAddr, AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")), TargetConf: int32(ctx.Int64("conf_target")),
CoinSelectionStrategy: coinSelectionStrategy,
}) })
if err != nil { if err != nil {
return err return err
@ -313,6 +330,7 @@ var sendCoinsCommand = cli.Command{
"terminal avoid breaking existing shell " + "terminal avoid breaking existing shell " +
"scripts", "scripts",
}, },
coinSelectionStrategyFlag,
txLabelFlag, txLabelFlag,
}, },
Action: actionDecorator(sendCoins), Action: actionDecorator(sendCoins),
@ -375,6 +393,11 @@ func sendCoins(ctx *cli.Context) error {
"sweep all coins out of the wallet") "sweep all coins out of the wallet")
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
defer cleanUp() defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs")) minConfs := int32(ctx.Uint64("min_confs"))
@ -409,14 +432,15 @@ func sendCoins(ctx *cli.Context) error {
} }
req := &lnrpc.SendCoinsRequest{ req := &lnrpc.SendCoinsRequest{
Addr: addr, Addr: addr,
Amount: amt, Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")), TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag), SatPerVbyte: ctx.Uint64(feeRateFlag),
SendAll: ctx.Bool("sweepall"), SendAll: ctx.Bool("sweepall"),
Label: ctx.String(txLabelFlag.Name), Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs, MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0, SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
} }
txid, err := client.SendCoins(ctxc, req) txid, err := client.SendCoins(ctxc, req)
if err != nil { if err != nil {
@ -585,6 +609,7 @@ var sendManyCommand = cli.Command{
"must satisfy", "must satisfy",
Value: defaultUtxoMinConf, Value: defaultUtxoMinConf,
}, },
coinSelectionStrategyFlag,
txLabelFlag, txLabelFlag,
}, },
Action: actionDecorator(sendMany), Action: actionDecorator(sendMany),
@ -615,17 +640,23 @@ func sendMany(ctx *cli.Context) error {
return err return err
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
defer cleanUp() defer cleanUp()
minConfs := int32(ctx.Uint64("min_confs")) minConfs := int32(ctx.Uint64("min_confs"))
txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{ txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{
AddrToAmount: amountToAddr, AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")), TargetConf: int32(ctx.Int64("conf_target")),
SatPerVbyte: ctx.Uint64(feeRateFlag), SatPerVbyte: ctx.Uint64(feeRateFlag),
Label: ctx.String(txLabelFlag.Name), Label: ctx.String(txLabelFlag.Name),
MinConfs: minConfs, MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0, SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -564,3 +564,31 @@ func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
return nil, fmt.Errorf("unknown network: %v", network) 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)
}
}

View File

@ -849,6 +849,7 @@ var fundTemplatePsbtCommand = cli.Command{
"if required", "if required",
Value: -1, Value: -1,
}, },
coinSelectionStrategyFlag,
}, },
Action: actionDecorator(fundTemplatePsbt), Action: actionDecorator(fundTemplatePsbt),
} }
@ -997,6 +998,11 @@ func fundTemplatePsbt(ctx *cli.Context) error {
"inputs/outputs flag") "inputs/outputs flag")
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs")) minConfs := int32(ctx.Uint64("min_confs"))
req := &walletrpc.FundPsbtRequest{ req := &walletrpc.FundPsbtRequest{
Account: ctx.String("account"), Account: ctx.String("account"),
@ -1005,6 +1011,7 @@ func fundTemplatePsbt(ctx *cli.Context) error {
Template: &walletrpc.FundPsbtRequest_CoinSelect{ Template: &walletrpc.FundPsbtRequest_CoinSelect{
CoinSelect: coinSelect, CoinSelect: coinSelect,
}, },
CoinSelectionStrategy: coinSelectionStrategy,
} }
// Parse fee flags. // Parse fee flags.
@ -1167,6 +1174,7 @@ var fundPsbtCommand = cli.Command{
"transaction must satisfy", "transaction must satisfy",
Value: defaultUtxoMinConf, Value: defaultUtxoMinConf,
}, },
coinSelectionStrategyFlag,
}, },
Action: actionDecorator(fundPsbt), Action: actionDecorator(fundPsbt),
} }
@ -1180,11 +1188,17 @@ func fundPsbt(ctx *cli.Context) error {
return cli.ShowCommandHelp(ctx, "fund") return cli.ShowCommandHelp(ctx, "fund")
} }
coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx)
if err != nil {
return err
}
minConfs := int32(ctx.Uint64("min_confs")) minConfs := int32(ctx.Uint64("min_confs"))
req := &walletrpc.FundPsbtRequest{ req := &walletrpc.FundPsbtRequest{
Account: ctx.String("account"), Account: ctx.String("account"),
MinConfs: minConfs, MinConfs: minConfs,
SpendUnconfirmed: minConfs == 0, SpendUnconfirmed: minConfs == 0,
CoinSelectionStrategy: coinSelectionStrategy,
} }
// Parse template flags. // Parse template flags.