diff --git a/lightningd/invoice.c b/lightningd/invoice.c index 19402db13..5aa4029fc 100644 --- a/lightningd/invoice.c +++ b/lightningd/invoice.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -109,16 +110,19 @@ static void json_invoice(struct command *cmd, const char *buffer, const jsmntok_t *params) { struct invoice *invoice; - jsmntok_t *msatoshi, *r, *label; + jsmntok_t *msatoshi, *r, *label, *desc; struct json_result *response = new_json_result(cmd); struct invoices *invs = cmd->ld->invoices; + struct bolt11 *b11; + char *b11enc; if (!json_get_params(buffer, params, "amount", &msatoshi, "label", &label, + "description", &desc, "?r", &r, NULL)) { - command_fail(cmd, "Need {amount} and {label}"); + command_fail(cmd, "Need {amount}, {label} and {description}"); return; } @@ -171,6 +175,25 @@ static void json_invoice(struct command *cmd, return; } + /* Construct bolt11 string. */ + b11 = new_bolt11(cmd, &invoice->msatoshi); + b11->chain = get_chainparams(cmd->ld); + b11->timestamp = time_now().ts.tv_sec; + b11->payment_hash = invoice->rhash; + b11->receiver_id = cmd->ld->id; + if (desc->end - desc->start >= BOLT11_FIELD_BYTE_LIMIT) { + b11->description_hash = tal(b11, struct sha256); + sha256(b11->description_hash, buffer + desc->start, + desc->end - desc->start); + } else + b11->description = tal_strndup(b11, buffer + desc->start, + desc->end - desc->start); + /* FIXME: add option to set this */ + b11->expiry = 3600; + + /* FIXME: add private routes if necessary! */ + b11enc = bolt11_encode(cmd, cmd->ld, b11, false); + /* OK, connect it to main state, respond with hash */ tal_steal(invs, invoice); list_add(&invs->invlist, &invoice->list); @@ -178,6 +201,9 @@ static void json_invoice(struct command *cmd, json_object_start(response, NULL); json_add_hex(response, "rhash", &invoice->rhash, sizeof(invoice->rhash)); + json_add_string(response, "bolt11", b11enc); + if (b11->description_hash) + json_add_string(response, "description", b11->description); json_object_end(response); command_success(cmd, response); @@ -186,8 +212,8 @@ static void json_invoice(struct command *cmd, static const struct json_command invoice_command = { "invoice", json_invoice, - "Create invoice for {msatoshi} with {label} (with a set {r}, otherwise generate one)", - "Returns the {rhash} on success. " + "Create invoice for {msatoshi} with {label} and {description} (with a set {r}, otherwise generate one)", + "Returns the {rhash} and {bolt11} on success, and {description} if too alrge for {bolt11}. " }; AUTODATA(json_command, &invoice_command); diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index 1ee5f785b..4f753374f 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -253,7 +253,7 @@ class LightningDTests(BaseLightningDTests): if not label: label = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(20)) - rhash = ldst.rpc.invoice(amt, label)['rhash'] + rhash = ldst.rpc.invoice(amt, label, label)['rhash'] assert ldst.rpc.listinvoice(label)[0]['complete'] == False routestep = { @@ -281,6 +281,21 @@ class LightningDTests(BaseLightningDTests): l1 = self.node_factory.get_node() l1.rpc.stop() + def test_invoice(self): + l1 = self.node_factory.get_node() + + before = int(time.time()) + inv = l1.rpc.invoice(123000, 'label', 'description') + after = int(time.time()) + b11 = l1.rpc.decodepay(inv['bolt11']) + assert b11['currency'] == 'tb' + assert b11['timestamp'] >= before + assert b11['timestamp'] <= after + assert b11['payment_hash'] == inv['rhash'] + assert b11['description'] == 'description' + assert b11['expiry'] == 3600 + assert b11['payee'] == l1.info['id'] + def test_connect(self): l1,l2 = self.connect() @@ -534,7 +549,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) amt = 200000000 - rhash = l2.rpc.invoice(amt, 'testpayment2')['rhash'] + rhash = l2.rpc.invoice(amt, 'testpayment2', 'desc')['rhash'] assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == False routestep = { @@ -596,7 +611,7 @@ class LightningDTests(BaseLightningDTests): assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == True # Overpaying by "only" a factor of 2 succeeds. - rhash = l2.rpc.invoice(amt, 'testpayment3')['rhash'] + rhash = l2.rpc.invoice(amt, 'testpayment3', 'desc')['rhash'] assert l2.rpc.listinvoice('testpayment3')[0]['complete'] == False routestep = { 'msatoshi' : amt * 2, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} l1.rpc.sendpay(to_json([routestep]), rhash) @@ -770,7 +785,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) # Must be dust! - rhash = l2.rpc.invoice(1, 'onchain_dust_out')['rhash'] + rhash = l2.rpc.invoice(1, 'onchain_dust_out', 'desc')['rhash'] routestep = { 'msatoshi' : 1, 'id' : l2.info['id'], @@ -822,7 +837,7 @@ class LightningDTests(BaseLightningDTests): l1.rpc.connect(l2.info['id'], 'localhost:{}'.format(l2.info['port'])) self.fund_channel(l1, l2, 10**6) - rhash = l2.rpc.invoice(10**8, 'onchain_timeout')['rhash'] + rhash = l2.rpc.invoice(10**8, 'onchain_timeout', 'desc')['rhash'] # We underpay, so it fails. routestep = { 'msatoshi' : 10**8 - 1, @@ -885,7 +900,7 @@ class LightningDTests(BaseLightningDTests): self.pay(l2, l1, 2 * 10**8) # Must be bigger than dust! - rhash = l3.rpc.invoice(10**8, 'middleman')['rhash'] + rhash = l3.rpc.invoice(10**8, 'middleman', 'desc')['rhash'] # Wait for route propagation. l1.bitcoin.rpc.generate(5) l1.daemon.wait_for_log('Received node_announcement for node {}' @@ -1348,7 +1363,7 @@ class LightningDTests(BaseLightningDTests): assert l2.rpc.getpeer(l1.info['id'])['channel'] == chanid1 assert l3.rpc.getpeer(l2.info['id'])['channel'] == chanid2 - rhash = l3.rpc.invoice(100000000, 'testpayment1')['rhash'] + rhash = l3.rpc.invoice(100000000, 'testpayment1', 'desc')['rhash'] assert l3.rpc.listinvoice('testpayment1')[0]['complete'] == False # Fee for node2 is 10 millionths, plus 1. @@ -1496,7 +1511,7 @@ class LightningDTests(BaseLightningDTests): assert route[1]['msatoshi'] == 4999999 assert route[1]['delay'] == 9 + shadow_route - rhash = l3.rpc.invoice(4999999, 'test_forward_different_fees_and_cltv')['rhash'] + rhash = l3.rpc.invoice(4999999, 'test_forward_different_fees_and_cltv', 'desc')['rhash'] assert l3.rpc.listinvoice('test_forward_different_fees_and_cltv')[0]['complete'] == False # This should work. @@ -1560,7 +1575,7 @@ class LightningDTests(BaseLightningDTests): route[1]['delay'] += 10 # This should work. - rhash = l3.rpc.invoice(4999999, 'test_forward_pad_fees_and_cltv')['rhash'] + rhash = l3.rpc.invoice(4999999, 'test_forward_pad_fees_and_cltv', 'desc')['rhash'] l1.rpc.sendpay(to_json(route), rhash) assert l3.rpc.listinvoice('test_forward_pad_fees_and_cltv')[0]['complete'] == True @@ -1726,7 +1741,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) amt = 200000000 - rhash = l2.rpc.invoice(amt, 'test_reconnect_sender_add1')['rhash'] + rhash = l2.rpc.invoice(amt, 'test_reconnect_sender_add1', 'desc')['rhash'] assert l2.rpc.listinvoice('test_reconnect_sender_add1')[0]['complete'] == False route = [ { 'msatoshi' : amt, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} ] @@ -1754,7 +1769,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) amt = 200000000 - rhash = l2.rpc.invoice(amt, 'testpayment')['rhash'] + rhash = l2.rpc.invoice(amt, 'testpayment', 'desc')['rhash'] assert l2.rpc.listinvoice('testpayment')[0]['complete'] == False route = [ { 'msatoshi' : amt, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} ] @@ -1780,7 +1795,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) amt = 200000000 - rhash = l2.rpc.invoice(amt, 'testpayment2')['rhash'] + rhash = l2.rpc.invoice(amt, 'testpayment2', 'desc')['rhash'] assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == False route = [ { 'msatoshi' : amt, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} ] @@ -1810,7 +1825,7 @@ class LightningDTests(BaseLightningDTests): self.fund_channel(l1, l2, 10**6) amt = 200000000 - rhash = l2.rpc.invoice(amt, 'testpayment2')['rhash'] + rhash = l2.rpc.invoice(amt, 'testpayment2', 'desc')['rhash'] assert l2.rpc.listinvoice('testpayment2')[0]['complete'] == False route = [ { 'msatoshi' : amt, 'id' : l2.info['id'], 'delay' : 5, 'channel': '1:1:1'} ]