mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-19 07:04:22 +01:00
invoice: option to expose/not-expose private channels.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
committed by
Christian Decker
parent
fe4a600bc7
commit
1567238dd9
@@ -40,7 +40,7 @@ This release named by @molxyz and [@ctrlbreak](https://twitter.com/ctrlbreak).
|
||||
- JSON API: use `\n\n` to terminate responses, for simplified parsing (pylightning now relies on this)
|
||||
- JSON API: `fundchannel` now includes an `announce` option, when false it will keep channel private. Defaults to true.
|
||||
- JSON API: `listpeers`'s `channels` now includes a `private` flag to indicate if channel is announced or not.
|
||||
- JSON API: `invoice` route hints may now include private channels if you have no public ones.
|
||||
- JSON API: `invoice` route hints may now include private channels if you have no public ones, unless new option `exposeprivatechannels` is false.
|
||||
- Plugins: experimental plugin support for `lightningd`, including option passthrough and JSON-RPC passthrough.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -175,7 +175,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
}
|
||||
return self.call("listchannels", payload)
|
||||
|
||||
def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None, preimage=None):
|
||||
def invoice(self, msatoshi, label, description, expiry=None, fallbacks=None, preimage=None, exposeprivatechannels=None):
|
||||
"""
|
||||
Create an invoice for {msatoshi} with {label} and {description} with
|
||||
optional {expiry} seconds (default 1 hour)
|
||||
@@ -186,7 +186,8 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
"description": description,
|
||||
"expiry": expiry,
|
||||
"fallbacks": fallbacks,
|
||||
"preimage": preimage
|
||||
"preimage": preimage,
|
||||
"exposeprivatechannels": exposeprivatechannels
|
||||
}
|
||||
return self.call("invoice", payload)
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
.\" Title: lightning-invoice
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.79.1 <http://docbook.sf.net/>
|
||||
.\" Date: 09/27/2018
|
||||
.\" Date: 12/17/2018
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "LIGHTNING\-INVOICE" "7" "09/27/2018" "\ \&" "\ \&"
|
||||
.TH "LIGHTNING\-INVOICE" "7" "12/17/2018" "\ \&" "\ \&"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
@@ -31,10 +31,10 @@
|
||||
lightning-invoice \- Command for accepting payments\&.
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
\fBinvoice\fR \fImsatoshi\fR \fIlabel\fR \fIdescription\fR [\fIexpiry\fR] [\fIfallbacks\fR] [\fIpreimage\fR]
|
||||
\fBinvoice\fR \fImsatoshi\fR \fIlabel\fR \fIdescription\fR [\fIexpiry\fR] [\fIfallbacks\fR] [\fIpreimage\fR] [\fIexposeprivatechannels\fR]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
The \fBinvoice\fR RPC command creates the expectation of a payment of a given amount of milli\-satoshi: it returns a unique token which another lightning daemon can use to pay this invoice\&.
|
||||
The \fBinvoice\fR RPC command creates the expectation of a payment of a given amount of milli\-satoshi: it returns a unique token which another lightning daemon can use to pay this invoice\&. This token includes a \fIroute hint\fR description of an incoming channel with capacity to pay the invoice, if any exists\&.
|
||||
.sp
|
||||
The \fImsatoshi\fR can be the string "any", which creates an invoice that can be paid with any amount\&.
|
||||
.sp
|
||||
@@ -47,6 +47,8 @@ The \fIexpiry\fR is optionally the number of seconds the invoice is valid for\&.
|
||||
The \fIfallbacks\fR array is one or more fallback addresses to include in the invoice (in order from most\-preferred to least): note that these arrays are not currently tracked to fulfill the invoice\&.
|
||||
.sp
|
||||
The \fIpreimage\fR is a 64\-digit hex string to be used as payment preimage for the created invoice\&. By default, if unspecified, lightningd will generate a secure pseudorandom preimage seeded from an appropriate entropy source on your system\&. \fBIMPORTANT\fR: if you specify the \fIpreimage\fR, you are responsible, to ensure appropriate care for generating using a secure pseudorandom generator seeded with sufficient entropy, and keeping the preimage secret\&. This parameter is an advanced feature intended for use with cutting\-edge cryptographic protocols and should not be used unless explicitly needed\&.
|
||||
.sp
|
||||
The \fIexposeprivatechannels\fR includes unpublished channels in the selection of channels for the route hint; by default they are excluded\&.
|
||||
.SH "RETURN VALUE"
|
||||
.sp
|
||||
On success, a hash is returned as \fIpayment_hash\fR to be given to the payer, and the \fIexpiry_time\fR as a UNIX timestamp\&. It also returns a BOLT11 invoice as \fIbolt11\fR to be given to the payer\&.
|
||||
|
||||
@@ -8,13 +8,15 @@ lightning-invoice - Command for accepting payments.
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
*invoice* 'msatoshi' 'label' 'description' ['expiry'] ['fallbacks'] ['preimage']
|
||||
*invoice* 'msatoshi' 'label' 'description' ['expiry'] ['fallbacks'] ['preimage'] ['exposeprivatechannels']
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
The *invoice* RPC command creates the expectation of a payment of a
|
||||
given amount of milli-satoshi: it returns a unique token which another
|
||||
lightning daemon can use to pay this invoice.
|
||||
lightning daemon can use to pay this invoice. This token includes a
|
||||
'route hint' description of an incoming channel with capacity to pay
|
||||
the invoice, if any exists.
|
||||
|
||||
The 'msatoshi' can be the string "any", which creates an invoice
|
||||
that can be paid with any amount.
|
||||
@@ -48,6 +50,11 @@ secret.
|
||||
This parameter is an advanced feature intended for use with cutting-edge
|
||||
cryptographic protocols and should not be used unless explicitly needed.
|
||||
|
||||
If specified, 'exposeprivatechannels' overrides the default route hint
|
||||
logic, which will use unpublished channels only if there are no
|
||||
published channels. If 'true' unpublished channels are always
|
||||
considered as a route hint candidate; if 'false', never.
|
||||
|
||||
RETURN VALUE
|
||||
------------
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ gossip_dev_memleak_reply,,leak,bool
|
||||
|
||||
# master -> gossipd: get route_info for our incoming channels
|
||||
gossip_get_incoming_channels,3025
|
||||
gossip_get_incoming_channels,,private_too,?bool
|
||||
|
||||
# gossipd -> master: here they are.
|
||||
gossip_get_incoming_channels_reply,3125
|
||||
|
||||
|
@@ -2119,6 +2119,18 @@ static bool node_has_public_channels(const struct node *peer,
|
||||
return false;
|
||||
}
|
||||
|
||||
/*~ The `exposeprivate` flag is a trinary: NULL == dynamic, otherwise
|
||||
* value decides. Thus, we provide two wrappers for clarity: */
|
||||
static bool never_expose(bool *exposeprivate)
|
||||
{
|
||||
return exposeprivate && !*exposeprivate;
|
||||
}
|
||||
|
||||
static bool always_expose(bool *exposeprivate)
|
||||
{
|
||||
return exposeprivate && *exposeprivate;
|
||||
}
|
||||
|
||||
/*~ For routeboost, we offer payers a hint of what incoming channels might
|
||||
* have capacity for their payment. To do this, lightningd asks for the
|
||||
* information about all channels to this node; but gossipd doesn't know about
|
||||
@@ -2130,11 +2142,20 @@ static struct io_plan *get_incoming_channels(struct io_conn *conn,
|
||||
struct node *node;
|
||||
struct route_info *public = tal_arr(tmpctx, struct route_info, 0);
|
||||
struct route_info *private = tal_arr(tmpctx, struct route_info, 0);
|
||||
bool has_public = false;
|
||||
bool has_public;
|
||||
bool *exposeprivate;
|
||||
|
||||
if (!fromwire_gossip_get_incoming_channels(msg))
|
||||
if (!fromwire_gossip_get_incoming_channels(tmpctx, msg, &exposeprivate))
|
||||
master_badmsg(WIRE_GOSSIP_GET_INCOMING_CHANNELS, msg);
|
||||
|
||||
status_trace("exposeprivate = %s",
|
||||
exposeprivate ? (*exposeprivate ? "TRUE" : "FALSE") : "NULL");
|
||||
status_trace("msg = %s", tal_hex(tmpctx, msg));
|
||||
status_trace("always_expose = %u, never_expose = %u",
|
||||
always_expose(exposeprivate), never_expose(exposeprivate));
|
||||
|
||||
has_public = always_expose(exposeprivate);
|
||||
|
||||
node = get_node(daemon->rstate, &daemon->rstate->local_id);
|
||||
if (node) {
|
||||
for (size_t i = 0; i < tal_count(node->chans); i++) {
|
||||
@@ -2160,7 +2181,7 @@ static struct io_plan *get_incoming_channels(struct io_conn *conn,
|
||||
if (!node_has_public_channels(other_node(node, c), c))
|
||||
continue;
|
||||
|
||||
if (is_chan_public(c))
|
||||
if (always_expose(exposeprivate) || is_chan_public(c))
|
||||
tal_arr_expand(&public, ri);
|
||||
else
|
||||
tal_arr_expand(&private, ri);
|
||||
@@ -2168,7 +2189,7 @@ static struct io_plan *get_incoming_channels(struct io_conn *conn,
|
||||
}
|
||||
|
||||
/* If no public channels (even deadend ones!), share private ones. */
|
||||
if (!has_public)
|
||||
if (!has_public && !never_expose(exposeprivate))
|
||||
msg = towire_gossip_get_incoming_channels_reply(NULL, private);
|
||||
else
|
||||
msg = towire_gossip_get_incoming_channels_reply(NULL, public);
|
||||
|
||||
@@ -308,6 +308,7 @@ static struct command_result *json_invoice(struct command *cmd,
|
||||
const u8 **fallback_scripts = NULL;
|
||||
u64 *expiry;
|
||||
struct sha256 rhash;
|
||||
bool *exposeprivate;
|
||||
|
||||
info = tal(cmd, struct invoice_info);
|
||||
info->cmd = cmd;
|
||||
@@ -319,6 +320,7 @@ static struct command_result *json_invoice(struct command *cmd,
|
||||
p_opt_def("expiry", param_u64, &expiry, 3600),
|
||||
p_opt("fallbacks", param_array, &fallbacks),
|
||||
p_opt("preimage", param_tok, &preimagetok),
|
||||
p_opt("exposeprivatechannels", param_bool, &exposeprivate),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
@@ -381,8 +383,10 @@ static struct command_result *json_invoice(struct command *cmd,
|
||||
if (fallback_scripts)
|
||||
info->b11->fallbacks = tal_steal(info->b11, fallback_scripts);
|
||||
|
||||
log_debug(cmd->ld->log, "exposeprivate = %s",
|
||||
exposeprivate ? (*exposeprivate ? "TRUE" : "FALSE") : "NULL");
|
||||
subd_req(cmd, cmd->ld->gossip,
|
||||
take(towire_gossip_get_incoming_channels(NULL)),
|
||||
take(towire_gossip_get_incoming_channels(NULL, exposeprivate)),
|
||||
-1, 0, gossipd_incoming_channels_reply, info);
|
||||
|
||||
return command_still_pending(cmd);
|
||||
|
||||
@@ -397,7 +397,7 @@ u8 *towire_errorfmt(const tal_t *ctx UNNEEDED,
|
||||
const char *fmt UNNEEDED, ...)
|
||||
{ fprintf(stderr, "towire_errorfmt called!\n"); abort(); }
|
||||
/* Generated stub for towire_gossip_get_incoming_channels */
|
||||
u8 *towire_gossip_get_incoming_channels(const tal_t *ctx UNNEEDED)
|
||||
u8 *towire_gossip_get_incoming_channels(const tal_t *ctx UNNEEDED, const bool *private_too UNNEEDED)
|
||||
{ fprintf(stderr, "towire_gossip_get_incoming_channels called!\n"); abort(); }
|
||||
/* Generated stub for towire_hsm_get_channel_basepoints */
|
||||
u8 *towire_hsm_get_channel_basepoints(const tal_t *ctx UNNEEDED, const struct pubkey *peerid UNNEEDED, u64 dbid UNNEEDED)
|
||||
|
||||
@@ -194,6 +194,11 @@ def test_invoice_routeboost_private(node_factory, bitcoind):
|
||||
assert r['fee_proportional_millionths'] == 10
|
||||
assert r['cltv_expiry_delta'] == 6
|
||||
|
||||
# If we explicitly say not to, it won't expose.
|
||||
inv = l2.rpc.invoice(msatoshi=123456, label="inv1", description="?", exposeprivatechannels=False)
|
||||
assert 'warning_capacity' in inv
|
||||
assert 'routes' not in l1.rpc.decodepay(inv['bolt11'])
|
||||
|
||||
# The existence of a public channel, even without capacity, will suppress
|
||||
# the exposure of private channels.
|
||||
l3 = node_factory.get_node()
|
||||
@@ -204,9 +209,21 @@ def test_invoice_routeboost_private(node_factory, bitcoind):
|
||||
# Make sure channel is totally public.
|
||||
wait_for(lambda: [c['public'] for c in l3.rpc.listchannels(scid)['channels']] == [True, True])
|
||||
|
||||
inv = l2.rpc.invoice(msatoshi=10**7, label="inv1", description="?")
|
||||
inv = l2.rpc.invoice(msatoshi=10**7, label="inv2", description="?")
|
||||
assert 'warning_capacity' in inv
|
||||
|
||||
# Unless we tell it to include it.
|
||||
inv = l2.rpc.invoice(msatoshi=10**7, label="inv3", description="?", exposeprivatechannels=True)
|
||||
assert 'warning_capacity' not in inv
|
||||
assert 'warning_offline' not in inv
|
||||
# Route array has single route with single element.
|
||||
r = only_one(only_one(l1.rpc.decodepay(inv['bolt11'])['routes']))
|
||||
assert r['pubkey'] == l1.info['id']
|
||||
assert r['short_channel_id'] == l1.rpc.listchannels()['channels'][0]['short_channel_id']
|
||||
assert r['fee_base_msat'] == 1
|
||||
assert r['fee_proportional_millionths'] == 10
|
||||
assert r['cltv_expiry_delta'] == 6
|
||||
|
||||
|
||||
def test_invoice_expiry(node_factory, executor):
|
||||
l1, l2 = node_factory.line_graph(2, fundchannel=True)
|
||||
|
||||
@@ -392,29 +392,34 @@ class Message(object):
|
||||
self.print_fromwire_array('*' + f.name, subcalls, basetype, f,
|
||||
'*' + f.name, f.lenvar)
|
||||
else:
|
||||
if f.optional:
|
||||
assignable = f.fieldtype.is_assignable()
|
||||
deref = '*'
|
||||
else:
|
||||
deref = ''
|
||||
assignable = f.is_assignable()
|
||||
|
||||
if assignable:
|
||||
if f.is_len_var:
|
||||
s = '{} = fromwire_{}(&cursor, &plen);'.format(f.name, basetype)
|
||||
else:
|
||||
s = '{}*{} = fromwire_{}(&cursor, &plen);'.format(deref, f.name, basetype)
|
||||
elif basetype in varlen_structs:
|
||||
s = '{}*{} = fromwire_{}(ctx, &cursor, &plen);'.format(deref, f.name, basetype)
|
||||
else:
|
||||
s = 'fromwire_{}(&cursor, &plen, {}{});'.format(basetype, deref, f.name)
|
||||
|
||||
if f.optional:
|
||||
subcalls.append("if (!fromwire_bool(&cursor, &plen))\n"
|
||||
"*{} = NULL;\n"
|
||||
"else {{\n"
|
||||
"*{} = tal(ctx, {});\n"
|
||||
"fromwire_{}(&cursor, &plen, *{});\n"
|
||||
"{}\n"
|
||||
"}}"
|
||||
.format(f.name, f.name, f.fieldtype.name,
|
||||
basetype, f.name))
|
||||
elif f.is_assignable():
|
||||
subcalls.append("//3rd case {name}".format(name=f.name))
|
||||
if f.is_len_var:
|
||||
subcalls.append('{} = fromwire_{}(&cursor, &plen);'
|
||||
.format(f.name, basetype))
|
||||
s))
|
||||
else:
|
||||
subcalls.append('*{} = fromwire_{}(&cursor, &plen);'
|
||||
.format(f.name, basetype))
|
||||
elif basetype in varlen_structs:
|
||||
subcalls.append('*{} = fromwire_{}(ctx, &cursor, &plen);'
|
||||
.format(f.name, basetype))
|
||||
else:
|
||||
subcalls.append('fromwire_{}(&cursor, &plen, {});'
|
||||
.format(basetype, f.name))
|
||||
subcalls.append(s)
|
||||
|
||||
return template.format(
|
||||
name=self.name,
|
||||
@@ -481,12 +486,16 @@ class Message(object):
|
||||
self.print_towire_array(subcalls, basetype, f, f.lenvar)
|
||||
else:
|
||||
if f.optional:
|
||||
if f.fieldtype.is_assignable():
|
||||
deref = '*'
|
||||
else:
|
||||
deref = ''
|
||||
subcalls.append("if (!{})\n"
|
||||
"towire_bool(&p, false);\n"
|
||||
"else {{\n"
|
||||
"towire_bool(&p, true);\n"
|
||||
"towire_{}(&p, {});\n"
|
||||
"}}".format(f.name, basetype, f.name))
|
||||
"towire_{}(&p, {}{});\n"
|
||||
"}}".format(f.name, basetype, deref, f.name))
|
||||
else:
|
||||
subcalls.append('towire_{}(&p, {});'
|
||||
.format(basetype, f.name))
|
||||
|
||||
Reference in New Issue
Block a user