From 385023f0b751f724f58b93eb607eeef9cbaa7de4 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 23 Nov 2017 13:40:14 -0600 Subject: [PATCH] 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. --- cmd/lncli/commands.go | 203 +++++++++++++++++++++++++++++++---------- lnrpc/rpc.swagger.json | 24 ++++- 2 files changed, 177 insertions(+), 50 deletions(-) diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 3ff9bd43..eadf3e23 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -29,6 +29,8 @@ import ( // TODO(roasbeef): cli logic for supporting both positional and unix style // arguments. +// TODO(roasbeef): expose all fee conf targets + func printJSON(resp interface{}) { b, err := json.Marshal(resp) if err != nil { @@ -85,10 +87,11 @@ var newAddressCommand = cli.Command{ Name: "newaddress", Usage: "generates a new address.", ArgsUsage: "address-type", - Description: "Generate a wallet new address. Address-types has to be one of:\n" + - " - p2wkh: Push to witness key hash\n" + - " - np2wkh: Push to nested witness key hash\n" + - " - p2pkh: Push to public key hash (can't be used to fund channels)", + Description: ` + Generate a wallet new address. Address-types has to be one of: + - p2wkh: Push to witness key hash + - np2wkh: Push to nested witness key hash + - p2pkh: Push to public key hash (can't be used to fund channels)`, Action: actionDecorator(newAddress), } @@ -129,8 +132,14 @@ var sendCoinsCommand = cli.Command{ Name: "sendcoins", Usage: "send bitcoin on-chain to an address", ArgsUsage: "addr amt", - Description: "Send amt coins in satoshis to the BASE58 encoded bitcoin address addr.\n\n" + - " Positional arguments and flags can be used interchangeably but not at the same time!", + Description: ` + 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{ cli.StringFlag{ Name: "addr", @@ -141,6 +150,18 @@ var sendCoinsCommand = cli.Command{ Name: "amt", 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), } @@ -158,6 +179,11 @@ func sendCoins(ctx *cli.Context) error { 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 { case ctx.IsSet("addr"): addr = ctx.String("addr") @@ -186,8 +212,10 @@ func sendCoins(ctx *cli.Context) error { defer cleanUp() req := &lnrpc.SendCoinsRequest{ - Addr: addr, - Amount: amt, + Addr: addr, + Amount: amt, + TargetConf: int32(ctx.Int64("conf_target")), + SatPerByte: ctx.Int64("sat_per_byte"), } txid, err := client.SendCoins(ctxb, req) if err != nil { @@ -201,12 +229,27 @@ func sendCoins(ctx *cli.Context) error { var sendManyCommand = cli.Command{ Name: "sendmany", Usage: "send bitcoin on-chain to multiple addresses.", - ArgsUsage: "send-json-string", - Description: "create and broadcast a transaction paying the specified " + - "amount(s) to the passed address(es)\n" + - " 'send-json-string' decodes addresses and the amount to send " + - "respectively in the following format.\n" + - ` '{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'`, + ArgsUsage: "send-json-string [--conf_target=N] [--sat_per_byte=P]", + Description: ` + Create and broadcast a transaction paying the specified amount(s) to the passed address(es). + + The send-json-string' param decodes addresses and the amount to send + 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), } @@ -218,12 +261,19 @@ func sendMany(ctx *cli.Context) error { 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() client, cleanUp := getClient(ctx) defer cleanUp() txid, err := client.SendMany(ctxb, &lnrpc.SendManyRequest{ AddrToAmount: amountToAddr, + TargetConf: int32(ctx.Int64("conf_target")), + SatPerByte: ctx.Int64("sat_per_byte"), }) if err != nil { return err @@ -324,13 +374,18 @@ func disconnectPeer(ctx *cli.Context) error { var openChannelCommand = cli.Command{ Name: "openchannel", Usage: "Open a channel to an existing peer.", - Description: "Attempt to open a new channel to an existing peer with the key node-key, " + - "optionally blocking until the channel is 'open'. " + - "The channel will be initialized with local-amt satoshis local and push-amt " + - "satoshis for the remote node. Once the " + - "channel is open, a channelPoint (txid:vout) of the funding " + - "output is returned. NOTE: peer_id and node_key are " + - "mutually exclusive, only one should be used, not both.", + Description: ` + Attempt to open a new channel to an existing peer with the key node-key + optionally blocking until the channel is 'open'. + + The channel will be initialized with local-amt satoshis local and push-amt + satoshis for the remote node. Once the channel is open, a channelPoint (txid:vout) + 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", Flags: []cli.Flag{ cli.IntFlag{ @@ -355,6 +410,18 @@ var openChannelCommand = cli.Command{ Name: "block", 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), } @@ -379,7 +446,10 @@ func openChannel(ctx *cli.Context) error { "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 { case ctx.IsSet("peer_id"): @@ -477,8 +547,19 @@ func openChannel(ctx *cli.Context) error { var closeChannelCommand = cli.Command{ Name: "closechannel", Usage: "Close an existing channel.", - Description: "Close an existing channel. The channel can be closed either " + - "cooperatively, or uncooperatively (forced).", + Description: ` + 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]]", Flags: []cli.Flag{ cli.StringFlag{ @@ -504,6 +585,18 @@ var closeChannelCommand = cli.Command{ Name: "block", 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), } @@ -529,6 +622,8 @@ func closeChannel(ctx *cli.Context) error { req := &lnrpc.CloseChannelRequest{ ChannelPoint: &lnrpc.ChannelPoint{}, Force: ctx.Bool("force"), + TargetConf: int32(ctx.Int64("conf_target")), + SatPerByte: ctx.Int64("sat_per_byte"), } switch { @@ -1019,9 +1114,11 @@ func payInvoice(ctx *cli.Context) error { var addInvoiceCommand = cli.Command{ Name: "addinvoice", Usage: "add a new invoice.", - Description: "Add a new invoice, expressing intent for a future payment. " + - "The value of the invoice in satoshis is neccesary for the " + - "creation, the remaining parameters are optional.", + Description: ` + Add a new invoice, expressing intent for a future payment. + + The value of the invoice in satoshis is necessary for the creation, + the remaining parameters are optional.`, ArgsUsage: "value preimage", Flags: []cli.Flag{ cli.StringFlag{ @@ -1616,9 +1713,12 @@ func getNetworkInfo(ctx *cli.Context) error { } var debugLevelCommand = cli.Command{ - Name: "debuglevel", - Usage: "Set the debug level.", - Description: "Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems", + Name: "debuglevel", + Usage: "Set the debug level.", + Description: `Logging level for all subsystems {trace, debug, info, warn, error, critical} + You may also specify =,=,... to set the log level for individual subsystems + + Use show to list available subsystems`, Flags: []cli.Flag{ cli.BoolFlag{ Name: "show", @@ -1714,10 +1814,12 @@ func listChainTxns(ctx *cli.Context) error { } var stopCommand = cli.Command{ - Name: "stop", - 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.", - Action: actionDecorator(stopDaemon), + Name: "stop", + 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.`, + Action: actionDecorator(stopDaemon), } func stopDaemon(ctx *cli.Context) error { @@ -1737,8 +1839,11 @@ var signMessageCommand = cli.Command{ Name: "signmessage", Usage: "sign a message with the node's private key", ArgsUsage: "msg", - Description: "Sign msg with the resident node's private key. Returns a the signature as a zbase32 string.\n\n" + - " Positional arguments and flags can be used interchangeably but not at the same time!", + Description: ` + 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{ cli.StringFlag{ Name: "msg", @@ -1777,10 +1882,12 @@ var verifyMessageCommand = cli.Command{ Name: "verifymessage", Usage: "verify a message signed with the signature", ArgsUsage: "msg signature", - Description: "Verify that the message was signed with a properly-formed signature.\n" + - " The signature must be zbase32 encoded and signed with the private key of\n" + - " an active node in the resident node's channel database.\n\n" + - " Positional arguments and flags can be used interchangeably but not at the same time!", + Description: ` + Verify that the message was signed with a properly-formed signature + The signature must be zbase32 encoded and signed with the private key of + 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{ cli.StringFlag{ Name: "msg", @@ -1838,9 +1945,9 @@ func verifyMessage(ctx *cli.Context) error { var feeReportCommand = cli.Command{ Name: "feereport", Usage: "display the current fee policies of all active channels", - Description: "Returns the current fee policies of all active " + - "channels. Fee policies can be updated using the " + - "updateFees command. ", + Description: ` + Returns the current fee policies of all active channels. + Fee policies can be updated using the updateFees command.`, Action: actionDecorator(feeReport), } @@ -1863,11 +1970,11 @@ var updateFeesCommand = cli.Command{ Name: "updatefees", Usage: "update the fee policy for all channels, or a single channel", ArgsUsage: "base_fee_msat fee_rate [channel_point]", - Description: ` Updates the fee policy for all channels, or just a - particular channel identified by it's channel point. The - fee update will be committed, and broadcast to the rest - of the network within the next batch. Channel points are encoded - as: funding_txid:output_index`, + Description: ` + Updates the fee policy for all channels, or just a particular channel + identified by it's channel point. The fee update will be committed, and + broadcast to the rest of the network within the next batch. + Channel points are encoded as: funding_txid:output_index`, Flags: []cli.Flag{ cli.Int64Flag{ Name: "base_fee_msat", diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 2cccd97b..2bc998bd 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -147,7 +147,7 @@ }, "/v1/channels/{channel_point.funding_txid}/{channel_point.output_index}": { "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", "responses": { "200": { @@ -642,7 +642,7 @@ ] }, "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", "responses": { "200": { @@ -1591,6 +1591,16 @@ "type": "string", "format": "int64", "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", "format": "int64", "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." } } },