Browse Source

invoice: option to expose/not-expose private channels.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
plugin-timeout-inc
Rusty Russell 6 years ago
committed by Christian Decker
parent
commit
1567238dd9
  1. 2
      CHANGELOG.md
  2. 5
      contrib/pylightning/lightning/lightning.py
  3. 10
      doc/lightning-invoice.7
  4. 11
      doc/lightning-invoice.7.txt
  5. 1
      gossipd/gossip_wire.csv
  6. 29
      gossipd/gossipd.c
  7. 6
      lightningd/invoice.c
  8. 2
      lightningd/test/run-invoice-select-inchan.c
  9. 19
      tests/test_invoices.py
  10. 43
      tools/generate-wire.py

2
CHANGELOG.md

@ -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

5
contrib/pylightning/lightning/lightning.py

@ -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)

10
doc/lightning-invoice.7

@ -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\&.

11
doc/lightning-invoice.7.txt

@ -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
------------

1
gossipd/gossip_wire.csv

@ -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

Can't render this file because it has a wrong number of fields in line 6.

29
gossipd/gossipd.c

@ -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);

6
lightningd/invoice.c

@ -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);

2
lightningd/test/run-invoice-select-inchan.c

@ -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)

19
tests/test_invoices.py

@ -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)

43
tools/generate-wire.py

@ -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))
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))
s))
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))

Loading…
Cancel
Save