cmd/lncli: add new --target_conf and --sat_per_byte args for relevant commands

In this commit, we expose the new fee control features to the relevant
commands on the command line. This will allow users to have a greater
degree of control of the fees they pay when: sending coins on chain,
opening a channel, or closing a channel.
This commit is contained in:
Olaoluwa Osuntokun
2017-11-23 13:40:14 -06:00
parent 530c49f12e
commit 385023f0b7
2 changed files with 177 additions and 50 deletions

View File

@@ -29,6 +29,8 @@ import (
// TODO(roasbeef): cli logic for supporting both positional and unix style // TODO(roasbeef): cli logic for supporting both positional and unix style
// arguments. // arguments.
// TODO(roasbeef): expose all fee conf targets
func printJSON(resp interface{}) { func printJSON(resp interface{}) {
b, err := json.Marshal(resp) b, err := json.Marshal(resp)
if err != nil { if err != nil {
@@ -85,10 +87,11 @@ var newAddressCommand = cli.Command{
Name: "newaddress", Name: "newaddress",
Usage: "generates a new address.", Usage: "generates a new address.",
ArgsUsage: "address-type", ArgsUsage: "address-type",
Description: "Generate a wallet new address. Address-types has to be one of:\n" + Description: `
" - p2wkh: Push to witness key hash\n" + Generate a wallet new address. Address-types has to be one of:
" - np2wkh: Push to nested witness key hash\n" + - p2wkh: Push to witness key hash
" - p2pkh: Push to public key hash (can't be used to fund channels)", - np2wkh: Push to nested witness key hash
- p2pkh: Push to public key hash (can't be used to fund channels)`,
Action: actionDecorator(newAddress), Action: actionDecorator(newAddress),
} }
@@ -129,8 +132,14 @@ var sendCoinsCommand = cli.Command{
Name: "sendcoins", Name: "sendcoins",
Usage: "send bitcoin on-chain to an address", Usage: "send bitcoin on-chain to an address",
ArgsUsage: "addr amt", ArgsUsage: "addr amt",
Description: "Send amt coins in satoshis to the BASE58 encoded bitcoin address addr.\n\n" + Description: `
" Positional arguments and flags can be used interchangeably but not at the same time!", Send amt coins in satoshis to the BASE58 encoded bitcoin address addr.
Fees used when sending the transaction can be specified via the --conf_target, or
--sat_per_byte optional flags.
Positional arguments and flags can be used interchangeably but not at the same time!
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "addr", Name: "addr",
@@ -141,6 +150,18 @@ var sendCoinsCommand = cli.Command{
Name: "amt", Name: "amt",
Usage: "the number of bitcoin denominated in satoshis to send", Usage: "the number of bitcoin denominated in satoshis to send",
}, },
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "(optional) a manual fee expressed in " +
"sat/byte that should be used when crafting " +
"the transaction",
},
}, },
Action: actionDecorator(sendCoins), Action: actionDecorator(sendCoins),
} }
@@ -158,6 +179,11 @@ func sendCoins(ctx *cli.Context) error {
return nil return nil
} }
if ctx.IsSet("conf_target") && ctx.IsSet("sat_per_byte") {
return fmt.Errorf("either conf_target or sat_per_byte should be " +
"set, but not both")
}
switch { switch {
case ctx.IsSet("addr"): case ctx.IsSet("addr"):
addr = ctx.String("addr") addr = ctx.String("addr")
@@ -186,8 +212,10 @@ func sendCoins(ctx *cli.Context) error {
defer cleanUp() defer cleanUp()
req := &lnrpc.SendCoinsRequest{ req := &lnrpc.SendCoinsRequest{
Addr: addr, Addr: addr,
Amount: amt, Amount: amt,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
} }
txid, err := client.SendCoins(ctxb, req) txid, err := client.SendCoins(ctxb, req)
if err != nil { if err != nil {
@@ -201,12 +229,27 @@ func sendCoins(ctx *cli.Context) error {
var sendManyCommand = cli.Command{ var sendManyCommand = cli.Command{
Name: "sendmany", Name: "sendmany",
Usage: "send bitcoin on-chain to multiple addresses.", Usage: "send bitcoin on-chain to multiple addresses.",
ArgsUsage: "send-json-string", ArgsUsage: "send-json-string [--conf_target=N] [--sat_per_byte=P]",
Description: "create and broadcast a transaction paying the specified " + Description: `
"amount(s) to the passed address(es)\n" + Create and broadcast a transaction paying the specified amount(s) to the passed address(es).
" 'send-json-string' decodes addresses and the amount to send " +
"respectively in the following format.\n" + The send-json-string' param decodes addresses and the amount to send
` '{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'`, respectively in the following format:
'{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'
`,
Flags: []cli.Flag{
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the transaction *should* " +
"confirm in, will be used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "(optional) a manual fee expressed in sat/byte that should be " +
"used when crafting the transaction",
},
},
Action: actionDecorator(sendMany), Action: actionDecorator(sendMany),
} }
@@ -218,12 +261,19 @@ func sendMany(ctx *cli.Context) error {
return err return err
} }
if ctx.IsSet("conf_target") && ctx.IsSet("sat_per_byte") {
return fmt.Errorf("either conf_target or sat_per_byte should be " +
"set, but not both")
}
ctxb := context.Background() ctxb := context.Background()
client, cleanUp := getClient(ctx) client, cleanUp := getClient(ctx)
defer cleanUp() defer cleanUp()
txid, err := client.SendMany(ctxb, &lnrpc.SendManyRequest{ txid, err := client.SendMany(ctxb, &lnrpc.SendManyRequest{
AddrToAmount: amountToAddr, AddrToAmount: amountToAddr,
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
}) })
if err != nil { if err != nil {
return err return err
@@ -324,13 +374,18 @@ func disconnectPeer(ctx *cli.Context) error {
var openChannelCommand = cli.Command{ var openChannelCommand = cli.Command{
Name: "openchannel", Name: "openchannel",
Usage: "Open a channel to an existing peer.", Usage: "Open a channel to an existing peer.",
Description: "Attempt to open a new channel to an existing peer with the key node-key, " + Description: `
"optionally blocking until the channel is 'open'. " + Attempt to open a new channel to an existing peer with the key node-key
"The channel will be initialized with local-amt satoshis local and push-amt " + optionally blocking until the channel is 'open'.
"satoshis for the remote node. Once the " +
"channel is open, a channelPoint (txid:vout) of the funding " + The channel will be initialized with local-amt satoshis local and push-amt
"output is returned. NOTE: peer_id and node_key are " + satoshis for the remote node. Once the channel is open, a channelPoint (txid:vout)
"mutually exclusive, only one should be used, not both.", of the funding output is returned.
One can manually set the fee to be used for the funding transaction via either
the --conf_target or --sat_per_byte arguments. This is optional.
NOTE: peer_id and node_key are mutually exclusive, only one should be used, not both.`,
ArgsUsage: "node-key local-amt push-amt", ArgsUsage: "node-key local-amt push-amt",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.IntFlag{ cli.IntFlag{
@@ -355,6 +410,18 @@ var openChannelCommand = cli.Command{
Name: "block", Name: "block",
Usage: "block and wait until the channel is fully open", Usage: "block and wait until the channel is fully open",
}, },
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "(optional) a manual fee expressed in " +
"sat/byte that should be used when crafting " +
"the transaction",
},
}, },
Action: actionDecorator(openChannel), Action: actionDecorator(openChannel),
} }
@@ -379,7 +446,10 @@ func openChannel(ctx *cli.Context) error {
"at the same time, only one can be specified") "at the same time, only one can be specified")
} }
req := &lnrpc.OpenChannelRequest{} req := &lnrpc.OpenChannelRequest{
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
}
switch { switch {
case ctx.IsSet("peer_id"): case ctx.IsSet("peer_id"):
@@ -477,8 +547,19 @@ func openChannel(ctx *cli.Context) error {
var closeChannelCommand = cli.Command{ var closeChannelCommand = cli.Command{
Name: "closechannel", Name: "closechannel",
Usage: "Close an existing channel.", Usage: "Close an existing channel.",
Description: "Close an existing channel. The channel can be closed either " + Description: `
"cooperatively, or uncooperatively (forced).", Close an existing channel. The channel can be closed either cooperatively,
or unilaterally (--force).
A unilateral channel closure means that the latest commitment
transaction will be broadcast to the network. As a result, any settled
funds will be time locked for a few blocks before they can be swept int
lnd's wallet.
In the case of a cooperative closure, One can manually set the fee to
be used for the closing transaction via either the --conf_target or
--sat_per_byte arguments. This will be the starting value used during
fee negotiation. This is optional.`,
ArgsUsage: "funding_txid [output_index [time_limit]]", ArgsUsage: "funding_txid [output_index [time_limit]]",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
@@ -504,6 +585,18 @@ var closeChannelCommand = cli.Command{
Name: "block", Name: "block",
Usage: "block until the channel is closed", Usage: "block until the channel is closed",
}, },
cli.Int64Flag{
Name: "conf_target",
Usage: "(optional) the number of blocks that the " +
"transaction *should* confirm in, will be " +
"used for fee estimation",
},
cli.Int64Flag{
Name: "sat_per_byte",
Usage: "(optional) a manual fee expressed in " +
"sat/byte that should be used when crafting " +
"the transaction",
},
}, },
Action: actionDecorator(closeChannel), Action: actionDecorator(closeChannel),
} }
@@ -529,6 +622,8 @@ func closeChannel(ctx *cli.Context) error {
req := &lnrpc.CloseChannelRequest{ req := &lnrpc.CloseChannelRequest{
ChannelPoint: &lnrpc.ChannelPoint{}, ChannelPoint: &lnrpc.ChannelPoint{},
Force: ctx.Bool("force"), Force: ctx.Bool("force"),
TargetConf: int32(ctx.Int64("conf_target")),
SatPerByte: ctx.Int64("sat_per_byte"),
} }
switch { switch {
@@ -1019,9 +1114,11 @@ func payInvoice(ctx *cli.Context) error {
var addInvoiceCommand = cli.Command{ var addInvoiceCommand = cli.Command{
Name: "addinvoice", Name: "addinvoice",
Usage: "add a new invoice.", Usage: "add a new invoice.",
Description: "Add a new invoice, expressing intent for a future payment. " + Description: `
"The value of the invoice in satoshis is neccesary for the " + Add a new invoice, expressing intent for a future payment.
"creation, the remaining parameters are optional.",
The value of the invoice in satoshis is necessary for the creation,
the remaining parameters are optional.`,
ArgsUsage: "value preimage", ArgsUsage: "value preimage",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
@@ -1616,9 +1713,12 @@ func getNetworkInfo(ctx *cli.Context) error {
} }
var debugLevelCommand = cli.Command{ var debugLevelCommand = cli.Command{
Name: "debuglevel", Name: "debuglevel",
Usage: "Set the debug level.", Usage: "Set the debug level.",
Description: "Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems", Description: `Logging level for all subsystems {trace, debug, info, warn, error, critical}
You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems
Use show to list available subsystems`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ cli.BoolFlag{
Name: "show", Name: "show",
@@ -1714,10 +1814,12 @@ func listChainTxns(ctx *cli.Context) error {
} }
var stopCommand = cli.Command{ var stopCommand = cli.Command{
Name: "stop", Name: "stop",
Usage: "Stop and shutdown the daemon.", Usage: "Stop and shutdown the daemon.",
Description: "Gracefully stop all daemon subsystems before stopping the daemon itself. This is equivalent to stopping it using CTRL-C.", Description: `
Action: actionDecorator(stopDaemon), Gracefully stop all daemon subsystems before stopping the daemon itself.
This is equivalent to stopping it using CTRL-C.`,
Action: actionDecorator(stopDaemon),
} }
func stopDaemon(ctx *cli.Context) error { func stopDaemon(ctx *cli.Context) error {
@@ -1737,8 +1839,11 @@ var signMessageCommand = cli.Command{
Name: "signmessage", Name: "signmessage",
Usage: "sign a message with the node's private key", Usage: "sign a message with the node's private key",
ArgsUsage: "msg", ArgsUsage: "msg",
Description: "Sign msg with the resident node's private key. Returns a the signature as a zbase32 string.\n\n" + Description: `
" Positional arguments and flags can be used interchangeably but not at the same time!", Sign msg with the resident node's private key.
Returns the signature as a zbase32 string.
Positional arguments and flags can be used interchangeably but not at the same time!`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "msg", Name: "msg",
@@ -1777,10 +1882,12 @@ var verifyMessageCommand = cli.Command{
Name: "verifymessage", Name: "verifymessage",
Usage: "verify a message signed with the signature", Usage: "verify a message signed with the signature",
ArgsUsage: "msg signature", ArgsUsage: "msg signature",
Description: "Verify that the message was signed with a properly-formed signature.\n" + Description: `
" The signature must be zbase32 encoded and signed with the private key of\n" + Verify that the message was signed with a properly-formed signature
" an active node in the resident node's channel database.\n\n" + The signature must be zbase32 encoded and signed with the private key of
" Positional arguments and flags can be used interchangeably but not at the same time!", an active node in the resident node's channel database.
Positional arguments and flags can be used interchangeably but not at the same time!`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "msg", Name: "msg",
@@ -1838,9 +1945,9 @@ func verifyMessage(ctx *cli.Context) error {
var feeReportCommand = cli.Command{ var feeReportCommand = cli.Command{
Name: "feereport", Name: "feereport",
Usage: "display the current fee policies of all active channels", Usage: "display the current fee policies of all active channels",
Description: "Returns the current fee policies of all active " + Description: `
"channels. Fee policies can be updated using the " + Returns the current fee policies of all active channels.
"updateFees command. ", Fee policies can be updated using the updateFees command.`,
Action: actionDecorator(feeReport), Action: actionDecorator(feeReport),
} }
@@ -1863,11 +1970,11 @@ var updateFeesCommand = cli.Command{
Name: "updatefees", Name: "updatefees",
Usage: "update the fee policy for all channels, or a single channel", Usage: "update the fee policy for all channels, or a single channel",
ArgsUsage: "base_fee_msat fee_rate [channel_point]", ArgsUsage: "base_fee_msat fee_rate [channel_point]",
Description: ` Updates the fee policy for all channels, or just a Description: `
particular channel identified by it's channel point. The Updates the fee policy for all channels, or just a particular channel
fee update will be committed, and broadcast to the rest identified by it's channel point. The fee update will be committed, and
of the network within the next batch. Channel points are encoded broadcast to the rest of the network within the next batch.
as: funding_txid:output_index`, Channel points are encoded as: funding_txid:output_index`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.Int64Flag{ cli.Int64Flag{
Name: "base_fee_msat", Name: "base_fee_msat",

View File

@@ -147,7 +147,7 @@
}, },
"/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}": { "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}": {
"delete": { "delete": {
"summary": "* lncli: `closechannel`\nCloseChannel attempts to close an active channel identified by its channel\noutpoint (ChannelPoint). The actions of this method can additionally be\naugmented to attempt a force close after a timeout period in the case of an\ninactive peer.", "summary": "* lncli: `closechannel`\nCloseChannel attempts to close an active channel identified by its channel\noutpoint (ChannelPoint). The actions of this method can additionally be\naugmented to attempt a force close after a timeout period in the case of an\ninactive peer. If a non-force close (cooperative closure) is requested,\nthen the user can specify either a target number of blocks until the\nclosure transaction is confirmed, or a manual fee rate. If neither are\nspecified, then a default lax, block confirmation target is used.",
"operationId": "CloseChannel", "operationId": "CloseChannel",
"responses": { "responses": {
"200": { "200": {
@@ -642,7 +642,7 @@
] ]
}, },
"post": { "post": {
"summary": "* lncli: `sendcoins`\nSendCoins executes a request to send coins to a particular address. Unlike\nSendMany, this RPC call only allows creating a single output at a time.", "summary": "* lncli: `sendcoins`\nSendCoins executes a request to send coins to a particular address. Unlike\nSendMany, this RPC call only allows creating a single output at a time. If\nneither target_conf, or sat_per_byte are set, then the internal wallet will\nconsult its fee model to determine a fee for the default confirmation\ntarget.",
"operationId": "SendCoins", "operationId": "SendCoins",
"responses": { "responses": {
"200": { "200": {
@@ -1591,6 +1591,16 @@
"type": "string", "type": "string",
"format": "int64", "format": "int64",
"title": "/ The number of satoshis to push to the remote side as part of the initial commitment state" "title": "/ The number of satoshis to push to the remote side as part of the initial commitment state"
},
"target_conf": {
"type": "integer",
"format": "int32",
"description": "/ The target number of blocks that the closure transaction should be confirmed by."
},
"sat_per_byte": {
"type": "string",
"format": "int64",
"description": "/ A manual fee rate set in sat/byte that should be used when crafting the closure transaction."
} }
} }
}, },
@@ -1872,6 +1882,16 @@
"type": "string", "type": "string",
"format": "int64", "format": "int64",
"title": "/ The amount in satoshis to send" "title": "/ The amount in satoshis to send"
},
"target_conf": {
"type": "integer",
"format": "int32",
"description": "/ The target number of blocks that this transaction should be confirmed by."
},
"sat_per_byte": {
"type": "string",
"format": "int64",
"description": "/ A manual fee rate set in sat/byte that should be used when crafting the transaction."
} }
} }
}, },