mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 23:24:27 +01:00
fundchannel / withdraw: allow explicit feerate setting.
These are the two cases where we'll refuse without a fee estimate. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
committed by
Christian Decker
parent
e2d4b7cc8d
commit
14dc1c37ab
@@ -22,6 +22,7 @@ This release named by ZmnSCPxj.
|
|||||||
- JSON API: `ping` command to send a ping to a connected peer.
|
- JSON API: `ping` command to send a ping to a connected peer.
|
||||||
- JSON API: `feerates` command to inject fee estimates manually, and retrieve
|
- JSON API: `feerates` command to inject fee estimates manually, and retrieve
|
||||||
current estimates.
|
current estimates.
|
||||||
|
- JSON API: `withdraw` and `fundchannel` can be given manual feerate.
|
||||||
- Config: `--conf` option to set config file.
|
- Config: `--conf` option to set config file.
|
||||||
- Documentation: Added CHANGELOG.md
|
- Documentation: Added CHANGELOG.md
|
||||||
- pylightning: RpcError now has `method` and `payload` fields.
|
- pylightning: RpcError now has `method` and `payload` fields.
|
||||||
|
|||||||
@@ -331,13 +331,15 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
}
|
}
|
||||||
return self.call("listpeers", payload)
|
return self.call("listpeers", payload)
|
||||||
|
|
||||||
def fundchannel(self, node_id, satoshi):
|
def fundchannel(self, node_id, satoshi, feerate=None, feeratestyle=None):
|
||||||
"""
|
"""
|
||||||
Fund channel with {id} using {satoshi} satoshis"
|
Fund channel with {id} using {satoshi} satoshis"
|
||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
"id": node_id,
|
"id": node_id,
|
||||||
"satoshi": satoshi
|
"satoshi": satoshi,
|
||||||
|
"feerate": feerate,
|
||||||
|
"feeratestyle": feeratestyle
|
||||||
}
|
}
|
||||||
return self.call("fundchannel", payload)
|
return self.call("fundchannel", payload)
|
||||||
|
|
||||||
@@ -404,14 +406,16 @@ class LightningRpc(UnixDomainSocketRpc):
|
|||||||
"""
|
"""
|
||||||
return self.call("dev-memleak")
|
return self.call("dev-memleak")
|
||||||
|
|
||||||
def withdraw(self, destination, satoshi):
|
def withdraw(self, destination, satoshi, feerate=None, feeratestyle=None):
|
||||||
"""
|
"""
|
||||||
Send to {destination} address {satoshi} (or "all")
|
Send to {destination} address {satoshi} (or "all")
|
||||||
amount via Bitcoin transaction
|
amount via Bitcoin transaction
|
||||||
"""
|
"""
|
||||||
payload = {
|
payload = {
|
||||||
"destination": destination,
|
"destination": destination,
|
||||||
"satoshi": satoshi
|
"satoshi": satoshi,
|
||||||
|
"feerate": feerate,
|
||||||
|
"feeratestyle": feeratestyle
|
||||||
}
|
}
|
||||||
return self.call("withdraw", payload)
|
return self.call("withdraw", payload)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ lightning-fundchannel - Command for establishing a lightning channel.
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
*fundchannel* 'id' 'satoshi'
|
*fundchannel* 'id' 'satoshi' ['feerate' 'feeratestyle']
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@@ -27,6 +27,11 @@ The string 'all' can be used to specify all available funds (or 16777215 satoshi
|
|||||||
The value cannot be less than the dust limit, currently set to 546, nor more
|
The value cannot be less than the dust limit, currently set to 546, nor more
|
||||||
than 16777215 satoshi.
|
than 16777215 satoshi.
|
||||||
|
|
||||||
|
'feerate' is an optional feerate to use, overriding lightningd's
|
||||||
|
internal estimate. If specified, 'feeratestyle' must be either
|
||||||
|
'"perkw"' for if 'feerate' is in satoshi-per-kilosipa (weight),
|
||||||
|
or '"perkb"' for if 'feerate' is in bitcoind-style satoshi-per-kilobyte.
|
||||||
|
|
||||||
RETURN VALUE
|
RETURN VALUE
|
||||||
------------
|
------------
|
||||||
On success, the 'tx' and 'txid' of the transaction is returned, as well as the
|
On success, the 'tx' and 'txid' of the transaction is returned, as well as the
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ internal wallet.
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
*withdraw* 'destination' 'satoshi'
|
*withdraw* 'destination' 'satoshi' ['feerate' 'feeratestyle']
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
@@ -24,6 +24,10 @@ wallet (expressed, as name suggests, in satoshi).
|
|||||||
The string 'all' can be used to specify withdrawal of all
|
The string 'all' can be used to specify withdrawal of all
|
||||||
available funds.
|
available funds.
|
||||||
|
|
||||||
|
'feerate' is an optional feerate to use, overriding lightningd's
|
||||||
|
internal estimate. If specified, 'feeratestyle' must be either
|
||||||
|
'"perkw"' for if 'feerate' is in satoshi-per-kilosipa (weight),
|
||||||
|
or '"perkb"' for if 'feerate' is in bitcoind-style satoshi-per-kilobyte.
|
||||||
|
|
||||||
RETURN VALUE
|
RETURN VALUE
|
||||||
------------
|
------------
|
||||||
|
|||||||
@@ -447,6 +447,36 @@ u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style)
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If we have both feerate and style, use that, otherwise use inbuilt if avail.
|
||||||
|
* Return false if we failed command, otherwise fills in feerate_perkw. */
|
||||||
|
bool json_feerate_and_style(struct command *cmd,
|
||||||
|
const u32 *feerate, enum feerate_style *style,
|
||||||
|
u32 fallback_feerate_per_kw,
|
||||||
|
u32 *feerate_per_kw)
|
||||||
|
{
|
||||||
|
if (feerate) {
|
||||||
|
if (!style) {
|
||||||
|
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||||
|
"'feerate' requires 'feeratestyle'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*feerate_per_kw = feerate_from_style(*feerate, *style);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (style) {
|
||||||
|
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||||
|
"'feeratestyle' requires 'feerate'");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*feerate_per_kw = fallback_feerate_per_kw;
|
||||||
|
if (!*feerate_per_kw) {
|
||||||
|
command_fail(cmd, LIGHTNINGD, "Cannot estimate fees");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void json_feerates(struct command *cmd,
|
static void json_feerates(struct command *cmd,
|
||||||
const char *buffer, const jsmntok_t *params)
|
const char *buffer, const jsmntok_t *params)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ u32 unilateral_feerate(struct chain_topology *topo);
|
|||||||
u32 feerate_from_style(u32 feerate, enum feerate_style style);
|
u32 feerate_from_style(u32 feerate, enum feerate_style style);
|
||||||
u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style);
|
u32 feerate_to_style(u32 feerate_perkw, enum feerate_style style);
|
||||||
|
|
||||||
|
/* If we have both feerate and style, use that, otherwise use fallback
|
||||||
|
* if nonzero. Return false if we failed command, otherwise fills in
|
||||||
|
* feerate_per_kw. */
|
||||||
|
bool json_feerate_and_style(struct command *cmd,
|
||||||
|
const u32 *feerate, enum feerate_style *style,
|
||||||
|
u32 fallback_feerate_per_kw,
|
||||||
|
u32 *feerate_per_kw);
|
||||||
|
|
||||||
/* Broadcast a single tx, and rebroadcast as reqd (copies tx).
|
/* Broadcast a single tx, and rebroadcast as reqd (copies tx).
|
||||||
* If failed is non-NULL, call that and don't rebroadcast. */
|
* If failed is non-NULL, call that and don't rebroadcast. */
|
||||||
void broadcast_tx(struct chain_topology *topo,
|
void broadcast_tx(struct chain_topology *topo,
|
||||||
|
|||||||
@@ -764,7 +764,9 @@ static void json_fund_channel(struct command *cmd,
|
|||||||
struct pubkey *id;
|
struct pubkey *id;
|
||||||
struct peer *peer;
|
struct peer *peer;
|
||||||
struct channel *channel;
|
struct channel *channel;
|
||||||
u32 feerate_per_kw = opening_feerate(cmd->ld->topology);
|
unsigned int *feerate;
|
||||||
|
enum feerate_style *style;
|
||||||
|
u32 feerate_per_kw;
|
||||||
u8 *msg;
|
u8 *msg;
|
||||||
|
|
||||||
fc->cmd = cmd;
|
fc->cmd = cmd;
|
||||||
@@ -773,16 +775,18 @@ static void json_fund_channel(struct command *cmd,
|
|||||||
if (!param(fc->cmd, buffer, params,
|
if (!param(fc->cmd, buffer, params,
|
||||||
p_req("id", json_tok_pubkey, &id),
|
p_req("id", json_tok_pubkey, &id),
|
||||||
p_req("satoshi", json_tok_tok, &sattok),
|
p_req("satoshi", json_tok_tok, &sattok),
|
||||||
|
p_opt("feerate", json_tok_number, &feerate),
|
||||||
|
p_opt("feeratestyle", json_tok_feerate_style, &style),
|
||||||
NULL))
|
NULL))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!json_tok_wtx(&fc->wtx, buffer, sattok, MAX_FUNDING_SATOSHI))
|
if (!json_tok_wtx(&fc->wtx, buffer, sattok, MAX_FUNDING_SATOSHI))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!feerate_per_kw) {
|
if (!json_feerate_and_style(cmd, feerate, style,
|
||||||
command_fail(cmd, LIGHTNINGD, "Cannot estimate fees");
|
opening_feerate(cmd->ld->topology),
|
||||||
|
&feerate_per_kw))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
peer = peer_by_id(cmd->ld, id);
|
peer = peer_by_id(cmd->ld, id);
|
||||||
if (!peer) {
|
if (!peer) {
|
||||||
@@ -839,6 +843,6 @@ static void json_fund_channel(struct command *cmd,
|
|||||||
static const struct json_command fund_channel_command = {
|
static const struct json_command fund_channel_command = {
|
||||||
"fundchannel",
|
"fundchannel",
|
||||||
json_fund_channel,
|
json_fund_channel,
|
||||||
"Fund channel with {id} using {satoshi} (or 'all') satoshis"
|
"Fund channel with {id} using {satoshi} (or 'all') satoshis, at optional {feerate}"
|
||||||
};
|
};
|
||||||
AUTODATA(json_command, &fund_channel_command);
|
AUTODATA(json_command, &fund_channel_command);
|
||||||
|
|||||||
@@ -1124,7 +1124,20 @@ def test_no_fee_estimate(node_factory, bitcoind, executor):
|
|||||||
with pytest.raises(RpcError, match=r'Cannot estimate fees'):
|
with pytest.raises(RpcError, match=r'Cannot estimate fees'):
|
||||||
l1.rpc.withdraw(l2.rpc.newaddr()['address'], 'all')
|
l1.rpc.withdraw(l2.rpc.newaddr()['address'], 'all')
|
||||||
|
|
||||||
|
# Can with manual feerate.
|
||||||
|
l1.rpc.withdraw(l2.rpc.newaddr()['address'], 10000, 1500, 'perkb')
|
||||||
|
l1.rpc.fundchannel(l2.info['id'], 10**6, 2000, 'perkw')
|
||||||
|
|
||||||
|
# Make sure we clean up cahnnel for later attempt.
|
||||||
|
l1.daemon.wait_for_log('sendrawtx exit 0')
|
||||||
|
l1.rpc.dev_fail(l2.info['id'])
|
||||||
|
l1.daemon.wait_for_log('sendrawtx exit 0')
|
||||||
|
bitcoind.generate_block(6)
|
||||||
|
wait_for(lambda: only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['state'] == 'ONCHAIN')
|
||||||
|
wait_for(lambda: only_one(l2.rpc.getpeer(l1.info['id'])['channels'])['state'] == 'ONCHAIN')
|
||||||
|
|
||||||
# But can accept incoming connections.
|
# But can accept incoming connections.
|
||||||
|
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
|
||||||
l2.fund_channel(l1, 10**6)
|
l2.fund_channel(l1, 10**6)
|
||||||
|
|
||||||
# Can do HTLCs.
|
# Can do HTLCs.
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ static void json_withdraw(struct command *cmd,
|
|||||||
{
|
{
|
||||||
const jsmntok_t *desttok, *sattok;
|
const jsmntok_t *desttok, *sattok;
|
||||||
struct withdrawal *withdraw = tal(cmd, struct withdrawal);
|
struct withdrawal *withdraw = tal(cmd, struct withdrawal);
|
||||||
|
u32 feerate_per_kw;
|
||||||
u32 feerate_per_kw = try_get_feerate(cmd->ld->topology, FEERATE_NORMAL);
|
unsigned int *feerate;
|
||||||
|
enum feerate_style *style;
|
||||||
struct bitcoin_tx *tx;
|
struct bitcoin_tx *tx;
|
||||||
|
|
||||||
enum address_parse_result addr_parse;
|
enum address_parse_result addr_parse;
|
||||||
|
|
||||||
withdraw->cmd = cmd;
|
withdraw->cmd = cmd;
|
||||||
@@ -101,16 +101,19 @@ static void json_withdraw(struct command *cmd,
|
|||||||
if (!param(cmd, buffer, params,
|
if (!param(cmd, buffer, params,
|
||||||
p_req("destination", json_tok_tok, &desttok),
|
p_req("destination", json_tok_tok, &desttok),
|
||||||
p_req("satoshi", json_tok_tok, &sattok),
|
p_req("satoshi", json_tok_tok, &sattok),
|
||||||
|
p_opt("feerate", json_tok_number, &feerate),
|
||||||
|
p_opt("feeratestyle", json_tok_feerate_style, &style),
|
||||||
NULL))
|
NULL))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!json_tok_wtx(&withdraw->wtx, buffer, sattok, -1ULL))
|
if (!json_tok_wtx(&withdraw->wtx, buffer, sattok, -1ULL))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!feerate_per_kw) {
|
if (!json_feerate_and_style(cmd, feerate, style,
|
||||||
command_fail(cmd, LIGHTNINGD, "Cannot estimate fees");
|
try_get_feerate(cmd->ld->topology,
|
||||||
|
FEERATE_NORMAL),
|
||||||
|
&feerate_per_kw))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse address. */
|
/* Parse address. */
|
||||||
addr_parse = json_tok_address_scriptpubkey(cmd,
|
addr_parse = json_tok_address_scriptpubkey(cmd,
|
||||||
@@ -163,7 +166,7 @@ static void json_withdraw(struct command *cmd,
|
|||||||
static const struct json_command withdraw_command = {
|
static const struct json_command withdraw_command = {
|
||||||
"withdraw",
|
"withdraw",
|
||||||
json_withdraw,
|
json_withdraw,
|
||||||
"Send to {destination} address {satoshi} (or 'all') amount via Bitcoin transaction",
|
"Send to {destination} address {satoshi} (or 'all') amount via Bitcoin transaction, at optional {feerate}",
|
||||||
false, "Send funds from the internal wallet to the specified address. Either specify a number of satoshis to send or 'all' to sweep all funds in the internal wallet to the address."
|
false, "Send funds from the internal wallet to the specified address. Either specify a number of satoshis to send or 'all' to sweep all funds in the internal wallet to the address."
|
||||||
};
|
};
|
||||||
AUTODATA(json_command, &withdraw_command);
|
AUTODATA(json_command, &withdraw_command);
|
||||||
|
|||||||
Reference in New Issue
Block a user