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:
23 files changed
+4298
-3882
No files matched your search
@@ -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
|
||||||
|
|||||||
+49
-18
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -303,6 +303,10 @@ bitcoin peers' feefilter values into account](https://github.com/lightningnetwor
|
|||||||
* [Allow callers of `ListSweeps` to specify the start
|
* [Allow callers of `ListSweeps` to specify the start
|
||||||
height](https://github.com/lightningnetwork/lnd/pull/7372).
|
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
|
## lncli Updates
|
||||||
|
|
||||||
* [Documented all available `lncli`
|
* [Documented all available `lncli`
|
||||||
|
|||||||
+4
-3
@@ -340,9 +340,10 @@ func (b *Batcher) BatchFund(ctx context.Context,
|
|||||||
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
|
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
|
||||||
SatPerVbyte: uint64(feeRateSatPerVByte),
|
SatPerVbyte: uint64(feeRateSatPerVByte),
|
||||||
},
|
},
|
||||||
MinConfs: firstReq.MinConfs,
|
MinConfs: firstReq.MinConfs,
|
||||||
SpendUnconfirmed: firstReq.MinConfs == 0,
|
SpendUnconfirmed: firstReq.MinConfs == 0,
|
||||||
ChangeType: changeType,
|
ChangeType: changeType,
|
||||||
|
CoinSelectionStrategy: req.CoinSelectionStrategy,
|
||||||
}
|
}
|
||||||
fundPsbtResp, err := b.cfg.WalletKitServer.FundPsbt(ctx, fundPsbtReq)
|
fundPsbtResp, err := b.cfg.WalletKitServer.FundPsbt(ctx, fundPsbtReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+3447
-3325
File diff suppressed because it is too large.
Load diff
@@ -1091,6 +1091,18 @@ message LightningAddress {
|
|||||||
string host = 2;
|
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 {
|
message EstimateFeeRequest {
|
||||||
// The map from addresses to amounts for the transaction.
|
// The map from addresses to amounts for the transaction.
|
||||||
map<string, int64> AddrToAmount = 1;
|
map<string, int64> AddrToAmount = 1;
|
||||||
@@ -1105,6 +1117,9 @@ message EstimateFeeRequest {
|
|||||||
|
|
||||||
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
||||||
bool spend_unconfirmed = 4;
|
bool spend_unconfirmed = 4;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins during fees estimation.
|
||||||
|
CoinSelectionStrategy coin_selection_strategy = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EstimateFeeResponse {
|
message EstimateFeeResponse {
|
||||||
@@ -1145,6 +1160,9 @@ message SendManyRequest {
|
|||||||
|
|
||||||
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
||||||
bool spend_unconfirmed = 8;
|
bool spend_unconfirmed = 8;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins during sending many requests.
|
||||||
|
CoinSelectionStrategy coin_selection_strategy = 9;
|
||||||
}
|
}
|
||||||
message SendManyResponse {
|
message SendManyResponse {
|
||||||
// The id of the transaction
|
// The id of the transaction
|
||||||
@@ -1187,6 +1205,9 @@ message SendCoinsRequest {
|
|||||||
|
|
||||||
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
||||||
bool spend_unconfirmed = 9;
|
bool spend_unconfirmed = 9;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins.
|
||||||
|
CoinSelectionStrategy coin_selection_strategy = 10;
|
||||||
}
|
}
|
||||||
message SendCoinsResponse {
|
message SendCoinsResponse {
|
||||||
// The transaction ID of the transaction
|
// The transaction ID of the transaction
|
||||||
@@ -2115,6 +2136,9 @@ message BatchOpenChannelRequest {
|
|||||||
|
|
||||||
// An optional label for the batch transaction, limited to 500 characters.
|
// An optional label for the batch transaction, limited to 500 characters.
|
||||||
string label = 6;
|
string label = 6;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins during batch opening channels.
|
||||||
|
CoinSelectionStrategy coin_selection_strategy = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BatchOpenChannel {
|
message BatchOpenChannel {
|
||||||
|
|||||||
@@ -2655,6 +2655,19 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"required": false,
|
"required": false,
|
||||||
"type": "boolean"
|
"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": [
|
"tags": [
|
||||||
@@ -3460,6 +3473,10 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "An optional label for the batch transaction, limited to 500 characters."
|
"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": {
|
"lnrpcCommitmentType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
@@ -6912,6 +6939,10 @@
|
|||||||
"spend_unconfirmed": {
|
"spend_unconfirmed": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
|
"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": {
|
"spend_unconfirmed": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
@@ -179,3 +180,30 @@ func MarshalOutPoint(op *wire.OutPoint) *OutPoint {
|
|||||||
OutputIndex: op.Index,
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+523
-491
File diff suppressed because it is too large.
Load diff
@@ -813,6 +813,9 @@ message SendOutputsRequest {
|
|||||||
|
|
||||||
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
// Whether unconfirmed outputs should be used as inputs for the transaction.
|
||||||
bool spend_unconfirmed = 5;
|
bool spend_unconfirmed = 5;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins during sending the outputs.
|
||||||
|
lnrpc.CoinSelectionStrategy coin_selection_strategy = 6;
|
||||||
}
|
}
|
||||||
message SendOutputsResponse {
|
message SendOutputsResponse {
|
||||||
/*
|
/*
|
||||||
@@ -1308,6 +1311,9 @@ message FundPsbtRequest {
|
|||||||
// accounts, no change type should be provided as the coin selection key
|
// accounts, no change type should be provided as the coin selection key
|
||||||
// scope will always be used to generate the change address.
|
// scope will always be used to generate the change address.
|
||||||
ChangeAddressType change_type = 8;
|
ChangeAddressType change_type = 8;
|
||||||
|
|
||||||
|
// The strategy to use for selecting coins during funding the PSBT.
|
||||||
|
lnrpc.CoinSelectionStrategy coin_selection_strategy = 10;
|
||||||
}
|
}
|
||||||
message FundPsbtResponse {
|
message FundPsbtResponse {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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)",
|
"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:"
|
"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": {
|
"lnrpcOutPoint": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1467,6 +1477,10 @@
|
|||||||
"change_type": {
|
"change_type": {
|
||||||
"$ref": "#/definitions/walletrpcChangeAddressType",
|
"$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."
|
"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": {
|
"spend_unconfirmed": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether unconfirmed outputs should be used as inputs for the transaction."
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -799,12 +799,19 @@ func (w *WalletKit) SendOutputs(ctx context.Context,
|
|||||||
return nil, err
|
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
|
// Now that we have the outputs mapped and checked for the reserve
|
||||||
// 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,
|
outputsToCreate, chainfee.SatPerKWeight(req.SatPerKw), minConfs,
|
||||||
label,
|
label, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1173,12 +1180,15 @@ func (w *WalletKit) LabelTransaction(ctx context.Context,
|
|||||||
func (w *WalletKit) FundPsbt(_ context.Context,
|
func (w *WalletKit) FundPsbt(_ context.Context,
|
||||||
req *FundPsbtRequest) (*FundPsbtResponse, error) {
|
req *FundPsbtRequest) (*FundPsbtResponse, error) {
|
||||||
|
|
||||||
var (
|
coinSelectionStrategy, err := lnrpc.UnmarshallCoinSelectionStrategy(
|
||||||
err error
|
req.CoinSelectionStrategy, w.cfg.CoinSelectionStrategy,
|
||||||
feeSatPerKW chainfee.SatPerKWeight
|
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the desired transaction fee.
|
// Determine the desired transaction fee.
|
||||||
|
var feeSatPerKW chainfee.SatPerKWeight
|
||||||
switch {
|
switch {
|
||||||
// Estimate the fee by the target number of blocks to confirmation.
|
// Estimate the fee by the target number of blocks to confirmation.
|
||||||
case req.GetTargetConf() != 0:
|
case req.GetTargetConf() != 0:
|
||||||
@@ -1241,7 +1251,7 @@ func (w *WalletKit) FundPsbt(_ context.Context,
|
|||||||
// wallet.
|
// wallet.
|
||||||
return w.fundPsbtInternalWallet(
|
return w.fundPsbtInternalWallet(
|
||||||
account, keyScopeFromChangeAddressType(req.ChangeType),
|
account, keyScopeFromChangeAddressType(req.ChangeType),
|
||||||
packet, minConfs, feeSatPerKW,
|
packet, minConfs, feeSatPerKW, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The template is specified as a PSBT with the intention to perform
|
// 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.
|
// coin selection algorithm.
|
||||||
return w.fundPsbtCoinSelect(
|
return w.fundPsbtCoinSelect(
|
||||||
account, changeIndex, packet, minConfs, changeType,
|
account, changeIndex, packet, minConfs, changeType,
|
||||||
feeSatPerKW,
|
feeSatPerKW, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The template is specified as a RPC message. We need to create a new
|
// 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.
|
// wallet.
|
||||||
return w.fundPsbtInternalWallet(
|
return w.fundPsbtInternalWallet(
|
||||||
account, keyScopeFromChangeAddressType(req.ChangeType),
|
account, keyScopeFromChangeAddressType(req.ChangeType),
|
||||||
packet, minConfs, feeSatPerKW,
|
packet, minConfs, feeSatPerKW, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1387,7 +1397,8 @@ func (w *WalletKit) FundPsbt(_ context.Context,
|
|||||||
// wallet that does not allow specifying custom inputs while selecting coins.
|
// wallet that does not allow specifying custom inputs while selecting coins.
|
||||||
func (w *WalletKit) fundPsbtInternalWallet(account string,
|
func (w *WalletKit) fundPsbtInternalWallet(account string,
|
||||||
keyScope *waddrmgr.KeyScope, packet *psbt.Packet, minConfs int32,
|
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
|
// 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
|
// 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
|
// lock any coins but might still change the wallet DB by
|
||||||
// generating a new change address.
|
// generating a new change address.
|
||||||
changeIndex, err := w.cfg.Wallet.FundPsbt(
|
changeIndex, err := w.cfg.Wallet.FundPsbt(
|
||||||
packet, minConfs, feeSatPerKW, account, keyScope,
|
packet, minConfs, feeSatPerKW, account,
|
||||||
|
keyScope, strategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wallet couldn't fund PSBT: %w", err)
|
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
|
// fundPsbtCoinSelect uses the "new" PSBT funding method using the channel
|
||||||
// funding coin selection algorithm that allows specifying custom inputs while
|
// funding coin selection algorithm that allows specifying custom inputs while
|
||||||
// selecting coins.
|
// selecting coins.
|
||||||
|
//
|
||||||
|
//nolint:funlen
|
||||||
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
|
func (w *WalletKit) fundPsbtCoinSelect(account string, changeIndex int32,
|
||||||
packet *psbt.Packet, minConfs int32,
|
packet *psbt.Packet, minConfs int32,
|
||||||
changeType chanfunding.ChangeAddressType,
|
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
|
// 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
|
// 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(
|
selectedCoins, changeAmount, err := chanfunding.CoinSelect(
|
||||||
feeRate, fundingAmount, changeDustLimit, coins,
|
feeRate, fundingAmount, changeDustLimit, coins,
|
||||||
w.cfg.CoinSelectionStrategy, estimator, changeType,
|
strategy, estimator, changeType,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error selecting coins: %w", err)
|
return fmt.Errorf("error selecting coins: %w", err)
|
||||||
|
|||||||
@@ -574,6 +574,7 @@ func TestFundPsbtCoinSelect(t *testing.T) {
|
|||||||
resp, err := rpcServer.fundPsbtCoinSelect(
|
resp, err := rpcServer.fundPsbtCoinSelect(
|
||||||
"", tc.changeIndex, copiedPacket, 0,
|
"", tc.changeIndex, copiedPacket, 0,
|
||||||
tc.changeType, tc.feeRate,
|
tc.changeType, tc.feeRate,
|
||||||
|
rpcServer.cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -141,14 +141,16 @@ 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([]*wire.TxOut,
|
||||||
chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) {
|
chainfee.SatPerKWeight, int32, string,
|
||||||
|
base.CoinSelectionStrategy) (*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([]*wire.TxOut,
|
||||||
chainfee.SatPerKWeight, int32, bool) (*txauthor.AuthoredTx, error) {
|
chainfee.SatPerKWeight, int32, base.CoinSelectionStrategy,
|
||||||
|
bool) (*txauthor.AuthoredTx, error) {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -206,7 +208,7 @@ func (w *WalletController) ListLeasedOutputs() ([]*base.ListLeasedOutputResult,
|
|||||||
|
|
||||||
// FundPsbt currently does nothing.
|
// FundPsbt currently does nothing.
|
||||||
func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
|
func (w *WalletController) FundPsbt(*psbt.Packet, int32, chainfee.SatPerKWeight,
|
||||||
string, *waddrmgr.KeyScope) (int32, error) {
|
string, *waddrmgr.KeyScope, base.CoinSelectionStrategy) (int32, error) {
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -978,8 +978,8 @@ func (b *BtcWallet) ImportTaprootScript(scope waddrmgr.KeyScope,
|
|||||||
//
|
//
|
||||||
// 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(outputs []*wire.TxOut,
|
||||||
feeRate chainfee.SatPerKWeight, minConfs int32,
|
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
|
||||||
label string) (*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
|
||||||
// SendOutputs.
|
// SendOutputs.
|
||||||
@@ -997,7 +997,7 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
|
|||||||
|
|
||||||
return b.wallet.SendOutputs(
|
return b.wallet.SendOutputs(
|
||||||
outputs, nil, defaultAccount, minConfs, feeSatPerKB,
|
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.
|
// This is a part of the WalletController interface.
|
||||||
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
|
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
|
||||||
feeRate chainfee.SatPerKWeight, minConfs int32,
|
feeRate chainfee.SatPerKWeight, minConfs int32,
|
||||||
|
strategy base.CoinSelectionStrategy,
|
||||||
dryRun bool) (*txauthor.AuthoredTx, error) {
|
dryRun bool) (*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
|
||||||
@@ -1048,7 +1049,7 @@ func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
|
|||||||
|
|
||||||
return b.wallet.CreateSimpleTx(
|
return b.wallet.CreateSimpleTx(
|
||||||
nil, defaultAccount, outputs, minConfs, feeSatPerKB,
|
nil, defaultAccount, outputs, minConfs, feeSatPerKB,
|
||||||
b.cfg.CoinSelectionStrategy, dryRun,
|
strategy, dryRun,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ var (
|
|||||||
// This is a part of the WalletController interface.
|
// This is a part of the WalletController interface.
|
||||||
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
|
func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
|
||||||
feeRate chainfee.SatPerKWeight, accountName string,
|
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
|
// 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.
|
||||||
@@ -134,7 +135,7 @@ func (b *BtcWallet) FundPsbt(packet *psbt.Packet, minConfs int32,
|
|||||||
// the partial TX information in the packet.
|
// the partial TX information in the packet.
|
||||||
return b.wallet.FundPsbt(
|
return b.wallet.FundPsbt(
|
||||||
packet, keyScope, minConfs, accountNum, feeSatPerKB,
|
packet, keyScope, minConfs, accountNum, feeSatPerKB,
|
||||||
b.cfg.CoinSelectionStrategy, opts...,
|
strategy, opts...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -345,7 +345,8 @@ type WalletController interface {
|
|||||||
//
|
//
|
||||||
// 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(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
|
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
|
||||||
// outputs. The transaction is not broadcasted to the network. In the
|
// 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.
|
// NOTE: This method requires the global coin selection lock to be held.
|
||||||
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
|
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
|
// GetTransactionDetails returns a detailed description of a transaction
|
||||||
// given its transaction hash.
|
// given its transaction hash.
|
||||||
@@ -465,7 +467,8 @@ type WalletController interface {
|
|||||||
// to lock the inputs before handing them out.
|
// to lock the inputs before handing them out.
|
||||||
FundPsbt(packet *psbt.Packet, minConfs int32,
|
FundPsbt(packet *psbt.Packet, minConfs int32,
|
||||||
feeRate chainfee.SatPerKWeight, account string,
|
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
|
// SignPsbt expects a partial transaction with all inputs and outputs
|
||||||
// fully declared and tries to sign all unsigned inputs that have all
|
// fully declared and tries to sign all unsigned inputs that have all
|
||||||
|
|||||||
+6
-3
@@ -146,14 +146,16 @@ 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([]*wire.TxOut,
|
||||||
chainfee.SatPerKWeight, int32, string) (*wire.MsgTx, error) {
|
chainfee.SatPerKWeight, int32, string,
|
||||||
|
base.CoinSelectionStrategy) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSimpleTx currently returns dummy values.
|
// CreateSimpleTx currently returns dummy values.
|
||||||
func (w *mockWalletController) CreateSimpleTx([]*wire.TxOut,
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -214,7 +216,8 @@ func (w *mockWalletController) ListLeasedOutputs() (
|
|||||||
|
|
||||||
// FundPsbt currently does nothing.
|
// FundPsbt currently does nothing.
|
||||||
func (w *mockWalletController) FundPsbt(*psbt.Packet, int32,
|
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
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,11 +121,11 @@ func (r *RPCKeyRing) NewAddress(addrType lnwallet.AddressType, change bool,
|
|||||||
//
|
//
|
||||||
// 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(outputs []*wire.TxOut,
|
||||||
feeRate chainfee.SatPerKWeight, minConfs int32,
|
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
|
||||||
label string) (*wire.MsgTx, error) {
|
strategy basewallet.CoinSelectionStrategy) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
tx, err := r.WalletController.SendOutputs(
|
tx, err := r.WalletController.SendOutputs(
|
||||||
outputs, feeRate, minConfs, label,
|
outputs, feeRate, minConfs, label, strategy,
|
||||||
)
|
)
|
||||||
if err != nil && err != basewallet.ErrTxUnsigned {
|
if err != nil && err != basewallet.ErrTxUnsigned {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ func sendCoins(t *testing.T, miner *rpctest.Harness,
|
|||||||
|
|
||||||
tx, err := sender.SendOutputs(
|
tx, err := sender.SendOutputs(
|
||||||
[]*wire.TxOut{output}, feeRate, minConf, labels.External,
|
[]*wire.TxOut{output}, feeRate, minConf, labels.External,
|
||||||
|
sender.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to send transaction")
|
require.NoError(t, err, "unable to send transaction")
|
||||||
|
|
||||||
@@ -1189,6 +1190,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
|
|||||||
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,
|
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External,
|
||||||
|
alice.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create burn tx")
|
require.NoError(t, err, "unable to create burn tx")
|
||||||
burnTXID := burnTX.TxHash()
|
burnTXID := burnTX.TxHash()
|
||||||
@@ -1448,6 +1450,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,
|
[]*wire.TxOut{burnOutput}, 2500, 1, labels.External,
|
||||||
|
alice.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create tx")
|
require.NoError(t, err, "unable to create tx")
|
||||||
txid := tx.TxHash()
|
txid := tx.TxHash()
|
||||||
@@ -1636,6 +1639,7 @@ func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
|
|||||||
}
|
}
|
||||||
tx, err := alice.SendOutputs(
|
tx, err := alice.SendOutputs(
|
||||||
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
|
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
|
||||||
|
alice.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to create output")
|
require.NoError(t, err, "unable to create output")
|
||||||
|
|
||||||
@@ -1951,6 +1955,7 @@ func testSignOutputUsingTweaks(r *rpctest.Harness,
|
|||||||
}
|
}
|
||||||
tx, err := alice.SendOutputs(
|
tx, err := alice.SendOutputs(
|
||||||
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
|
[]*wire.TxOut{newOutput}, 2500, 1, labels.External,
|
||||||
|
alice.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create output: %v", err)
|
t.Fatalf("unable to create output: %v", err)
|
||||||
@@ -2069,6 +2074,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
|||||||
}
|
}
|
||||||
tx, err := w.SendOutputs(
|
tx, err := w.SendOutputs(
|
||||||
[]*wire.TxOut{output}, 2500, 1, labels.External,
|
[]*wire.TxOut{output}, 2500, 1, labels.External,
|
||||||
|
w.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
require.NoError(t, err, "unable to send outputs")
|
require.NoError(t, err, "unable to send outputs")
|
||||||
txid := tx.TxHash()
|
txid := tx.TxHash()
|
||||||
@@ -2293,6 +2299,7 @@ func testSpendUnconfirmed(miner *rpctest.Harness,
|
|||||||
}
|
}
|
||||||
_, err = bob.SendOutputs(
|
_, err = bob.SendOutputs(
|
||||||
[]*wire.TxOut{output}, txFeeRate, 0, labels.External,
|
[]*wire.TxOut{output}, txFeeRate, 0, labels.External,
|
||||||
|
bob.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
|
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.
|
// using confirmed outputs only.
|
||||||
_, err = bob.SendOutputs(
|
_, err = bob.SendOutputs(
|
||||||
[]*wire.TxOut{output}, txFeeRate, 1, labels.External,
|
[]*wire.TxOut{output}, txFeeRate, 1, labels.External,
|
||||||
|
bob.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should have not been able to pay due to insufficient balance: %v", err)
|
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.
|
// Now try creating a tx spending to these outputs.
|
||||||
createTx, createErr := w.CreateSimpleTx(
|
createTx, createErr := w.CreateSimpleTx(
|
||||||
outputs, feeRate, minConfs, true,
|
outputs, feeRate, minConfs,
|
||||||
|
w.Cfg.CoinSelectionStrategy, true,
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case test.valid && createErr != nil:
|
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.
|
// that the change output position might be different.
|
||||||
tx, sendErr := w.SendOutputs(
|
tx, sendErr := w.SendOutputs(
|
||||||
outputs, feeRate, minConfs, labels.External,
|
outputs, feeRate, minConfs, labels.External,
|
||||||
|
w.Cfg.CoinSelectionStrategy,
|
||||||
)
|
)
|
||||||
switch {
|
switch {
|
||||||
case test.valid && sendErr != nil:
|
case test.valid && sendErr != nil:
|
||||||
|
|||||||
+37
-7
@@ -30,6 +30,7 @@ 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/wallet"
|
||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
proxy "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
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
|
// 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.
|
// address to a specified output value to be sent to that address.
|
||||||
func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
|
func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64,
|
||||||
feeRate chainfee.SatPerKWeight, minConfs int32,
|
feeRate chainfee.SatPerKWeight, minConfs int32, label string,
|
||||||
label string) (*chainhash.Hash, error) {
|
strategy wallet.CoinSelectionStrategy) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
outputs, err := addrPairsToOutputs(paymentMap, r.cfg.ActiveNetParams.Params)
|
outputs, err := addrPairsToOutputs(paymentMap, r.cfg.ActiveNetParams.Params)
|
||||||
if err != nil {
|
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
|
// 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, true,
|
outputs, feeRate, minConfs, strategy, true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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,
|
outputs, feeRate, minConfs, label, strategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1192,12 +1193,23 @@ func (r *rpcServer) EstimateFee(ctx context.Context,
|
|||||||
return nil, err
|
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
|
// 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.
|
// dryRun=true to avoid inflating the change addresses in the db.
|
||||||
var tx *txauthor.AuthoredTx
|
var tx *txauthor.AuthoredTx
|
||||||
wallet := r.server.cc.Wallet
|
wallet := r.server.cc.Wallet
|
||||||
err = wallet.WithCoinSelectLock(func() error {
|
err = wallet.WithCoinSelectLock(func() error {
|
||||||
tx, err = wallet.CreateSimpleTx(outputs, feePerKw, minConfs, true)
|
tx, err = wallet.CreateSimpleTx(
|
||||||
|
outputs, feePerKw, minConfs, coinSelectionStrategy,
|
||||||
|
true,
|
||||||
|
)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1281,6 +1293,14 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
|||||||
return nil, err
|
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
|
var txid *chainhash.Hash
|
||||||
|
|
||||||
wallet := r.server.cc.Wallet
|
wallet := r.server.cc.Wallet
|
||||||
@@ -1417,7 +1437,8 @@ func (r *rpcServer) SendCoins(ctx context.Context,
|
|||||||
paymentMap := map[string]int64{targetAddr.String(): in.Amount}
|
paymentMap := map[string]int64{targetAddr.String(): in.Amount}
|
||||||
err := wallet.WithCoinSelectLock(func() error {
|
err := wallet.WithCoinSelectLock(func() error {
|
||||||
newTXID, err := r.sendCoinsOnChain(
|
newTXID, err := r.sendCoinsOnChain(
|
||||||
paymentMap, feePerKw, minConfs, label,
|
paymentMap, feePerKw, minConfs,
|
||||||
|
label, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1463,6 +1484,14 @@ func (r *rpcServer) SendMany(ctx context.Context,
|
|||||||
return nil, err
|
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",
|
rpcsLog.Infof("[sendmany] outputs=%v, sat/kw=%v",
|
||||||
spew.Sdump(in.AddrToAmount), int64(feePerKw))
|
spew.Sdump(in.AddrToAmount), int64(feePerKw))
|
||||||
|
|
||||||
@@ -1474,7 +1503,8 @@ func (r *rpcServer) SendMany(ctx context.Context,
|
|||||||
wallet := r.server.cc.Wallet
|
wallet := r.server.cc.Wallet
|
||||||
err = wallet.WithCoinSelectLock(func() error {
|
err = wallet.WithCoinSelectLock(func() error {
|
||||||
sendManyTXID, err := r.sendCoinsOnChain(
|
sendManyTXID, err := r.sendCoinsOnChain(
|
||||||
in.AddrToAmount, feePerKw, minConfs, label,
|
in.AddrToAmount, feePerKw, minConfs,
|
||||||
|
label, coinSelectionStrategy,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in new issue
Block a user