mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 09:54:16 +01:00
invoice: add fallback address to invoice command
* Modifies invoice command to have the following format invoice <msatoshi> <label> <desc> <?expiry> <?fallbackaddr> * Adds support for Segwit bcrt1 addresses for withdraw * Add test case for fallback address in invoice creation * Create a common json_tok_address_scriptpubkey to be used by invoice and withdraw commands.
This commit is contained in:
parent
d23650d2ed
commit
b028a0a439
@ -150,7 +150,7 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
}
|
||||
return self.call("listchannels", payload)
|
||||
|
||||
def invoice(self, msatoshi, label, description, expiry=None):
|
||||
def invoice(self, msatoshi, label, description, expiry=None, fallback=None):
|
||||
"""
|
||||
Create an invoice for {msatoshi} with {label} and {description} with
|
||||
optional {expiry} seconds (default 1 hour)
|
||||
@ -159,7 +159,8 @@ class LightningRpc(UnixDomainSocketRpc):
|
||||
"msatoshi": msatoshi,
|
||||
"label": label,
|
||||
"description": description,
|
||||
"expiry": expiry
|
||||
"expiry": expiry,
|
||||
"fallback": fallback
|
||||
}
|
||||
return self.call("invoice", payload)
|
||||
|
||||
|
@ -103,14 +103,16 @@ static void json_invoice(struct command *cmd,
|
||||
const char *buffer, const jsmntok_t *params)
|
||||
{
|
||||
const struct invoice *invoice;
|
||||
jsmntok_t *msatoshi, *label, *desc, *exp;
|
||||
jsmntok_t *msatoshi, *label, *desc, *exp, *fallback;
|
||||
u64 *msatoshi_val;
|
||||
const char *label_val;
|
||||
const char *desc_val;
|
||||
const char *bip173;
|
||||
struct json_result *response = new_json_result(cmd);
|
||||
struct wallet *wallet = cmd->ld->wallet;
|
||||
struct bolt11 *b11;
|
||||
char *b11enc;
|
||||
const u8 *fallback_script;
|
||||
u64 expiry = 3600;
|
||||
|
||||
if (!json_get_params(cmd, buffer, params,
|
||||
@ -118,6 +120,7 @@ static void json_invoice(struct command *cmd,
|
||||
"label", &label,
|
||||
"description", &desc,
|
||||
"?expiry", &exp,
|
||||
"?fallback", &fallback,
|
||||
NULL)) {
|
||||
return;
|
||||
}
|
||||
@ -169,6 +172,17 @@ static void json_invoice(struct command *cmd,
|
||||
return;
|
||||
}
|
||||
|
||||
/* fallback address */
|
||||
if (fallback) {
|
||||
bip173 = json_tok_address_scriptpubkey(cmd, buffer, fallback, &fallback_script);
|
||||
if (!streq(get_chainparams(cmd->ld)->bip173_name, bip173)) {
|
||||
command_fail(cmd, "Invalid fallback address for %s does not match network %s",
|
||||
get_chainparams(cmd->ld)->network_name, bip173);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
invoice = wallet_invoice_create(cmd->ld->wallet,
|
||||
take(msatoshi_val),
|
||||
take(label_val),
|
||||
@ -188,6 +202,8 @@ static void json_invoice(struct command *cmd,
|
||||
b11->expiry = expiry;
|
||||
b11->description = tal_steal(b11, desc_val);
|
||||
b11->description_hash = NULL;
|
||||
if (fallback)
|
||||
b11->fallback = tal_steal(b11, fallback_script);
|
||||
|
||||
/* FIXME: add private routes if necessary! */
|
||||
b11enc = bolt11_encode(cmd, b11, false, hsm_sign_b11, cmd->ld);
|
||||
|
@ -1,11 +1,15 @@
|
||||
/* Code for JSON_RPC API */
|
||||
/* eg: { "method" : "dev-echo", "params" : [ "hello", "Arabella!" ], "id" : "1" } */
|
||||
#include <arpa/inet.h>
|
||||
#include <bitcoin/address.h>
|
||||
#include <bitcoin/base58.h>
|
||||
#include <bitcoin/script.h>
|
||||
#include <ccan/array_size/array_size.h>
|
||||
#include <ccan/err/err.h>
|
||||
#include <ccan/io/io.h>
|
||||
#include <ccan/str/hex/hex.h>
|
||||
#include <ccan/tal/str/str.h>
|
||||
#include <common/bech32.h>
|
||||
#include <common/json.h>
|
||||
#include <common/memleak.h>
|
||||
#include <common/version.h>
|
||||
@ -792,3 +796,97 @@ void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename)
|
||||
/* Technically this is a leak, but there's only one */
|
||||
notleak(io_new_listener(ld, fd, incoming_jcon_connected, ld));
|
||||
}
|
||||
|
||||
/**
|
||||
* segwit_addr_net_decode - Try to decode a Bech32 address and detect
|
||||
* testnet/mainnet/regtest
|
||||
*
|
||||
* This processes the address and returns a string if it is a Bech32
|
||||
* address specified by BIP173. The string is set whether it is
|
||||
* testnet ("tb"), mainnet ("bc"), or regtest ("bcrt")
|
||||
* It does not check, witness version and program size restrictions.
|
||||
*
|
||||
* Out: witness_version: Pointer to an int that will be updated to contain
|
||||
* the witness program version (between 0 and 16 inclusive).
|
||||
* witness_program: Pointer to a buffer of size 40 that will be updated
|
||||
* to contain the witness program bytes.
|
||||
* witness_program_len: Pointer to a size_t that will be updated to
|
||||
* contain the length of bytes in witness_program.
|
||||
* In: addrz: Pointer to the null-terminated address.
|
||||
* Returns string containing the human readable segment of bech32 address
|
||||
*/
|
||||
static const char* segwit_addr_net_decode(int *witness_version,
|
||||
uint8_t *witness_program,
|
||||
size_t *witness_program_len,
|
||||
const char *addrz)
|
||||
{
|
||||
const char *network[] = { "bc", "tb", "bcrt" };
|
||||
for (int i = 0; i < sizeof(network) / sizeof(*network); ++i) {
|
||||
if (segwit_addr_decode(witness_version,
|
||||
witness_program, witness_program_len,
|
||||
network[i], addrz))
|
||||
return network[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *json_tok_address_scriptpubkey(const tal_t *cxt, const char *buffer,
|
||||
const jsmntok_t *tok, const u8 **scriptpubkey)
|
||||
{
|
||||
struct bitcoin_address p2pkh_destination;
|
||||
struct ripemd160 p2sh_destination;
|
||||
int witness_version;
|
||||
/* segwit_addr_net_decode requires a buffer of size 40, and will
|
||||
* not write to the buffer if the address is too long, so a buffer
|
||||
* of fixed size 40 will not overflow. */
|
||||
uint8_t witness_program[40];
|
||||
size_t witness_program_len;
|
||||
bool witness_ok;
|
||||
|
||||
char *addrz;
|
||||
const char *bip173;
|
||||
bool testnet;
|
||||
|
||||
bip173 = NULL;
|
||||
if (bitcoin_from_base58(&testnet, &p2pkh_destination,
|
||||
buffer + tok->start, tok->end - tok->start)) {
|
||||
*scriptpubkey = scriptpubkey_p2pkh(cxt, &p2pkh_destination);
|
||||
bip173 = chainparams_for_network(testnet ? "testnet" : "bitcoin")->bip173_name;
|
||||
} else if (p2sh_from_base58(&testnet, &p2sh_destination,
|
||||
buffer + tok->start, tok->end - tok->start)) {
|
||||
*scriptpubkey = scriptpubkey_p2sh_hash(cxt, &p2sh_destination);
|
||||
bip173 = chainparams_for_network(testnet ? "testnet" : "bitcoin")->bip173_name;
|
||||
}
|
||||
/* Insert other parsers that accept pointer+len here. */
|
||||
|
||||
if (bip173) return bip173;
|
||||
|
||||
/* Generate null-terminated address. */
|
||||
addrz = tal_dup_arr(cxt, char, buffer + tok->start, tok->end - tok->start, 1);
|
||||
addrz[tok->end - tok->start] = '\0';
|
||||
|
||||
bip173 = segwit_addr_net_decode(&witness_version, witness_program,
|
||||
&witness_program_len, addrz);
|
||||
|
||||
if (bip173) {
|
||||
witness_ok = false;
|
||||
if (witness_version == 0 && (witness_program_len == 20 ||
|
||||
witness_program_len == 32)) {
|
||||
witness_ok = true;
|
||||
}
|
||||
/* Insert other witness versions here. */
|
||||
|
||||
if (witness_ok) {
|
||||
*scriptpubkey = scriptpubkey_witness_raw(cxt, witness_version,
|
||||
witness_program, witness_program_len);
|
||||
}
|
||||
else {
|
||||
bip173 = NULL;
|
||||
}
|
||||
}
|
||||
/* Insert other parsers that accept null-terminated string here. */
|
||||
|
||||
tal_free(addrz);
|
||||
return bip173;
|
||||
}
|
||||
|
@ -109,5 +109,11 @@ void json_add_address(struct json_result *response, const char *fieldname,
|
||||
/* For initialization */
|
||||
void setup_jsonrpc(struct lightningd *ld, const char *rpc_filename);
|
||||
|
||||
/* Returns NULL, or bip173 network name and fills in *scriptpubkey
|
||||
* allocated off ctx
|
||||
*/
|
||||
const char *json_tok_address_scriptpubkey(const tal_t *ctx, const char *buffer,
|
||||
const jsmntok_t *tok, const u8 **scriptpubkey);
|
||||
|
||||
AUTODATA_TYPE(json_command, struct json_command);
|
||||
#endif /* LIGHTNING_LIGHTNINGD_JSONRPC_H */
|
||||
|
@ -377,9 +377,11 @@ class LightningDTests(BaseLightningDTests):
|
||||
|
||||
def test_invoice(self):
|
||||
l1 = self.node_factory.get_node()
|
||||
l2 = self.node_factory.get_node()
|
||||
|
||||
addr = l2.rpc.newaddr('bech32')['address']
|
||||
before = int(time.time())
|
||||
inv = l1.rpc.invoice(123000, 'label', 'description')
|
||||
inv = l1.rpc.invoice(123000, 'label', 'description', '3700', addr)
|
||||
after = int(time.time())
|
||||
b11 = l1.rpc.decodepay(inv['bolt11'])
|
||||
assert b11['currency'] == 'bcrt'
|
||||
@ -387,8 +389,10 @@ class LightningDTests(BaseLightningDTests):
|
||||
assert b11['created_at'] <= after
|
||||
assert b11['payment_hash'] == inv['payment_hash']
|
||||
assert b11['description'] == 'description'
|
||||
assert b11['expiry'] == 3600
|
||||
assert b11['expiry'] == 3700
|
||||
assert b11['payee'] == l1.info['id']
|
||||
assert b11['fallback']['addr'] == addr
|
||||
assert b11['fallback']['type'] == 'P2WPKH'
|
||||
|
||||
# Check pay_index is null
|
||||
outputs = l1.db_query('SELECT pay_index IS NULL AS q FROM invoices WHERE label="label";')
|
||||
@ -2778,7 +2782,7 @@ class LightningDTests(BaseLightningDTests):
|
||||
|
||||
# Now send some money to l2.
|
||||
# lightningd uses P2SH-P2WPKH
|
||||
waddr = l2.rpc.newaddr()['address']
|
||||
waddr = l2.rpc.newaddr('bech32')['address']
|
||||
out = l1.rpc.withdraw(waddr, 2 * amount)
|
||||
l1.bitcoin.rpc.generate(1)
|
||||
|
||||
@ -2794,7 +2798,7 @@ class LightningDTests(BaseLightningDTests):
|
||||
|
||||
# Simple test for withdrawal to P2WPKH
|
||||
# Address from: https://bc-2.jp/tools/bech32demo/index.html
|
||||
waddr = 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx'
|
||||
waddr = 'bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080'
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'xx1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx', 2 * amount)
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'tb1pw508d6qejxtdg4y5r3zarvary0c5xw7kdl9fad', 2 * amount)
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxxxxxx', 2 * amount)
|
||||
@ -2807,7 +2811,7 @@ class LightningDTests(BaseLightningDTests):
|
||||
|
||||
# Simple test for withdrawal to P2WSH
|
||||
# Address from: https://bc-2.jp/tools/bech32demo/index.html
|
||||
waddr = 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7'
|
||||
waddr = 'bcrt1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qzf4jry'
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'xx1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7', 2 * amount)
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'tb1prp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qsm03tq', 2 * amount)
|
||||
self.assertRaises(ValueError, l1.rpc.withdraw, 'tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qxxxxxx', 2 * amount)
|
||||
@ -2842,7 +2846,7 @@ class LightningDTests(BaseLightningDTests):
|
||||
assert(c.fetchone()[0] == 6)
|
||||
|
||||
# Test withdrawal to self.
|
||||
out = l1.rpc.withdraw(l1.rpc.newaddr()['address'], 'all')
|
||||
out = l1.rpc.withdraw(l1.rpc.newaddr('bech32')['address'], 'all')
|
||||
bitcoind.rpc.generate(1)
|
||||
c = db.cursor()
|
||||
c.execute('SELECT COUNT(*) FROM outputs WHERE status=0')
|
||||
|
@ -75,113 +75,6 @@ static void wallet_withdrawal_broadcast(struct bitcoind *bitcoind UNUSED,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* segwit_addr_net_decode - Try to decode a Bech32 address and detect
|
||||
* testnet/mainnet
|
||||
*
|
||||
* This processes the address and returns true if it is a Bech32
|
||||
* address specified by BIP173. If it returns true, then *testnet is
|
||||
* set whether it is testnet "tb" address or false if mainnet "bc"
|
||||
* address. It does not check, witness version and program size
|
||||
* restrictions.
|
||||
*
|
||||
* Out: testnet: Pointer to a bool that will be updated to true if the
|
||||
* address is testnet, or false if mainnet.
|
||||
* witness_version: Pointer to an int that will be updated to contain
|
||||
* the witness program version (between 0 and 16 inclusive).
|
||||
* witness_program: Pointer to a buffer of size 40 that will be updated
|
||||
* to contain the witness program bytes.
|
||||
* witness_program_len: Pointer to a size_t that will be updated to
|
||||
* contain the length of bytes in witness_program.
|
||||
* In: addrz: Pointer to the null-terminated address.
|
||||
* Returns true if successful, false if fail (on fail, none of the out
|
||||
* parameters are modified).
|
||||
*/
|
||||
static bool segwit_addr_net_decode(bool *testnet, int *witness_version,
|
||||
uint8_t *witness_program,
|
||||
size_t *witness_program_len,
|
||||
const char *addrz)
|
||||
{
|
||||
/* segwit_addr_decode itself expects a prog buffer (which we pass
|
||||
* witness_program as) of size 40, so segwit_addr_net_decode
|
||||
* inherits that requirement. It will not write to that buffer
|
||||
* if the input address is too long, so no buffer overflow risk. */
|
||||
if (segwit_addr_decode(witness_version,
|
||||
witness_program, witness_program_len,
|
||||
"bc", addrz)) {
|
||||
*testnet = false;
|
||||
return true;
|
||||
} else if (segwit_addr_decode(witness_version,
|
||||
witness_program, witness_program_len,
|
||||
"tb", addrz)) {
|
||||
*testnet = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* scriptpubkey_from_address - Determine scriptpubkey from a given address
|
||||
*
|
||||
* This processes the address and returns the equivalent scriptpubkey
|
||||
* for the address. If fail to parse the address, return NULL. If can
|
||||
* parse address, also sets the testnet flag if address is a testnet
|
||||
* address or clears it if mainnet.
|
||||
*/
|
||||
static u8 *scriptpubkey_from_address(const tal_t *cxt, bool *testnet,
|
||||
const char *addr, size_t addrlen)
|
||||
{
|
||||
struct bitcoin_address p2pkh_destination;
|
||||
struct ripemd160 p2sh_destination;
|
||||
int witness_version;
|
||||
/* segwit_addr_net_decode requires a buffer of size 40, and will
|
||||
* not write to the buffer if the address is too long, so a buffer
|
||||
* of fixed size 40 will not overflow. */
|
||||
uint8_t witness_program[40];
|
||||
size_t witness_program_len;
|
||||
bool witness_ok;
|
||||
u8 *script = NULL;
|
||||
|
||||
char *addrz;
|
||||
bool my_testnet;
|
||||
|
||||
if (bitcoin_from_base58(testnet, &p2pkh_destination,
|
||||
addr, addrlen)) {
|
||||
script = scriptpubkey_p2pkh(cxt, &p2pkh_destination);
|
||||
} else if (p2sh_from_base58(testnet, &p2sh_destination,
|
||||
addr, addrlen)) {
|
||||
script = scriptpubkey_p2sh_hash(cxt, &p2sh_destination);
|
||||
}
|
||||
/* Insert other parsers that accept pointer+len here. */
|
||||
|
||||
if (script) return script;
|
||||
|
||||
/* Generate null-terminated address. */
|
||||
addrz = tal_dup_arr(cxt, char, addr, addrlen, 1);
|
||||
addrz[addrlen] = '\0';
|
||||
|
||||
if (segwit_addr_net_decode(&my_testnet, &witness_version,
|
||||
witness_program, &witness_program_len,
|
||||
addrz)) {
|
||||
witness_ok = false;
|
||||
if (witness_version == 0 && (witness_program_len == 20 ||
|
||||
witness_program_len == 32)) {
|
||||
witness_ok = true;
|
||||
}
|
||||
/* Insert other witness versions here. */
|
||||
if (witness_ok) {
|
||||
*testnet = my_testnet;
|
||||
script = scriptpubkey_witness_raw(cxt, witness_version,
|
||||
witness_program,
|
||||
witness_program_len);
|
||||
}
|
||||
}
|
||||
/* Insert other parsers that accept null-terminated string here. */
|
||||
|
||||
tal_free(addrz);
|
||||
return script;
|
||||
}
|
||||
|
||||
/**
|
||||
* json_withdraw - Entrypoint for the withdrawal flow
|
||||
*
|
||||
@ -194,11 +87,11 @@ static void json_withdraw(struct command *cmd,
|
||||
{
|
||||
jsmntok_t *desttok, *sattok;
|
||||
struct withdrawal *withdraw;
|
||||
bool testnet;
|
||||
u32 feerate_per_kw = get_feerate(cmd->ld->topology, FEERATE_NORMAL);
|
||||
u64 fee_estimate;
|
||||
struct bitcoin_tx *tx;
|
||||
bool withdraw_all = false;
|
||||
const char *bip173 = NULL;
|
||||
|
||||
if (!json_get_params(cmd, buffer, params,
|
||||
"destination", &desttok,
|
||||
@ -218,26 +111,21 @@ static void json_withdraw(struct command *cmd,
|
||||
}
|
||||
|
||||
/* Parse address. */
|
||||
withdraw->destination
|
||||
= scriptpubkey_from_address(withdraw, &testnet,
|
||||
buffer + desttok->start,
|
||||
desttok->end - desttok->start);
|
||||
bip173 = json_tok_address_scriptpubkey(cmd, buffer,
|
||||
desttok, (const u8**)(&withdraw->destination));
|
||||
|
||||
/* Check that destination address could be understood. */
|
||||
if (!withdraw->destination) {
|
||||
if (!bip173) {
|
||||
command_fail(cmd, "Could not parse destination address");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check address given is compatible with the chain we are on. */
|
||||
if (testnet != get_chainparams(cmd->ld)->testnet) {
|
||||
if (testnet) {
|
||||
command_fail(cmd,
|
||||
"Use of testnet address on mainnet");
|
||||
} else {
|
||||
command_fail(cmd,
|
||||
"Use of mainnet address on testnet");
|
||||
}
|
||||
if (!streq(bip173, get_chainparams(cmd->ld)->bip173_name)) {
|
||||
command_fail(cmd,
|
||||
"Use of %s address on %s",
|
||||
chainparams_by_bip173(bip173)->network_name,
|
||||
get_chainparams(cmd->ld)->network_name);
|
||||
return;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user