wire: remove towire_double()

Before this patch we used to send `double`s over the wire by just
copying them. This is not portable because the internal represenation
of a `double` is implementation specific.

Instead of this, multiply any floating-point numbers that come from
the outside (e.g. JSONs) by 1 million and round them to integers when
handling them.

* Introduce a new param_millionths() that expects a floating-point
  number and returns it multipled by 1000000 as an integer.

* Replace param_double() and param_percent() with param_millionths()

* Previously the riskfactor would be allowed to be negative, which must
  have been unintentional. This patch changes that to require a
  non-negative number.

Changelog-None
This commit is contained in:
Vasil Dimov 2020-01-29 12:30:00 +01:00 committed by Rusty Russell
parent 6b7db1ea7c
commit 89ceb273f5
13 changed files with 124 additions and 137 deletions

View File

@ -35,17 +35,21 @@ struct command_result *param_bool(struct command *cmd, const char *name,
name, tok->end - tok->start, buffer + tok->start);
}
struct command_result *param_double(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
double **num)
struct command_result *param_millionths(struct command *cmd, const char *name,
const char *buffer,
const jsmntok_t *tok, uint64_t **num)
{
*num = tal(cmd, double);
if (json_to_double(buffer, tok, *num))
double d;
if (json_to_double(buffer, tok, &d) && d >= 0.0) {
*num = tal(cmd, uint64_t);
**num = (uint64_t)(d * 1000000);
return NULL;
}
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"'%s' should be a double, not '%.*s'",
name, tok->end - tok->start, buffer + tok->start);
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"'%s' should be a non-negative floating-point number, not '%.*s'",
name, tok->end - tok->start, buffer + tok->start);
}
struct command_result *param_escaped_string(struct command *cmd,
@ -128,19 +132,6 @@ struct command_result *param_sha256(struct command *cmd, const char *name,
name, tok->end - tok->start, buffer + tok->start);
}
struct command_result *param_percent(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
double **num)
{
*num = tal(cmd, double);
if (json_to_double(buffer, tok, *num) && **num >= 0.0)
return NULL;
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"'%s' should be a positive double, not '%.*s'",
name, tok->end - tok->start, buffer + tok->start);
}
struct command_result *param_u64(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
uint64_t **num)

View File

@ -25,10 +25,14 @@ struct command_result *param_bool(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
bool **b);
/* Extract double from this (must be a number literal) */
struct command_result *param_double(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
double **num);
/*
* Extract a non-negative (either 0 or positive) floating-point number from this
* (must be a number literal), multiply it by 1 million and return it as an
* integer.
*/
struct command_result *param_millionths(struct command *cmd, const char *name,
const char *buffer,
const jsmntok_t *tok, uint64_t **num);
/* Extract an escaped string (and unescape it) */
struct command_result *param_escaped_string(struct command *cmd,
@ -57,11 +61,6 @@ struct command_result *param_sha256(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
struct sha256 **hash);
/* Extract double in range [0.0, 100.0] */
struct command_result *param_percent(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
double **num);
/* Extract number from this (may be a string, or a number literal) */
struct command_result *param_u64(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,

View File

@ -131,17 +131,17 @@ struct sanity {
char *str;
bool failed;
int ival;
double dval;
u64 fpval; /* floating-point, multiplied by 1000000 */
char *fail_str;
};
struct sanity buffers[] = {
// pass
{"['42', '3.15']", false, 42, 3.15, NULL},
{"{ 'u64' : '42', 'double' : '3.15' }", false, 42, 3.15, NULL},
{"['42', '3.15']", false, 42, 3150000, NULL},
{"{ 'u64' : '42', 'fp' : '3.15' }", false, 42, 3150000, NULL},
// fail
{"{'u64':'42', 'double':'3.15', 'extra':'stuff'}", true, 0, 0,
{"{'u64':'42', 'fp':'3.15', 'extra':'stuff'}", true, 0, 0,
"unknown parameter"},
{"['42', '3.15', 'stuff']", true, 0, 0, "too many"},
{"['42', '3.15', 'null']", true, 0, 0, "too many"},
@ -151,17 +151,17 @@ struct sanity buffers[] = {
{"['42']", true, 0, 0, "missing required"},
// fail wrong type
{"{'u64':'hello', 'double':'3.15'}", true, 0, 0, "be an unsigned 64"},
{"{'u64':'hello', 'fp':'3.15'}", true, 0, 0, "be an unsigned 64"},
{"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"},
};
static void stest(const struct json *j, struct sanity *b)
{
u64 *ival;
double *dval;
u64 *fpval;
if (!param(cmd, j->buffer, j->toks,
p_req("u64", param_u64, &ival),
p_req("double", param_double, &dval), NULL)) {
p_req("fp", param_millionths, &fpval), NULL)) {
assert(check_fail());
assert(b->failed == true);
if (!strstr(fail_msg, b->fail_str)) {
@ -172,7 +172,7 @@ static void stest(const struct json *j, struct sanity *b)
assert(!check_fail());
assert(b->failed == false);
assert(*ival == 42);
assert(*dval > 3.1499 && b->dval < 3.1501);
assert(*fpval > 3149900 && b->fpval < 3150100);
}
}
@ -222,13 +222,13 @@ static void dup_names(void)
{
struct json *j =
json_parse(cmd,
"{ 'u64' : '42', 'u64' : '43', 'double' : '3.15' }");
"{ 'u64' : '42', 'u64' : '43', 'fp' : '3.15' }");
u64 *i;
double *d;
u64 *fp;
assert(!param(cmd, j->buffer, j->toks,
p_req("u64", param_u64, &i),
p_req("double", param_double, &d), NULL));
p_req("fp", param_millionths, &fp), NULL));
}
static void null_params(void)
@ -290,28 +290,28 @@ static void bad_programmer(void)
{
u64 *ival;
u64 *ival2;
double *dval;
u64 *fpval;
struct json *j = json_parse(cmd, "[ '25', '546', '26' ]");
/* check for repeated names */
assert(!param(cmd, j->buffer, j->toks,
p_req("repeat", param_u64, &ival),
p_req("double", param_double, &dval),
p_req("fp", param_millionths, &fpval),
p_req("repeat", param_u64, &ival2), NULL));
assert(check_fail());
assert(strstr(fail_msg, "developer error"));
assert(!param(cmd, j->buffer, j->toks,
p_req("repeat", param_u64, &ival),
p_req("double", param_double, &dval),
p_req("fp", param_millionths, &fpval),
p_req("repeat", param_u64, &ival), NULL));
assert(check_fail());
assert(strstr(fail_msg, "developer error"));
assert(!param(cmd, j->buffer, j->toks,
p_req("u64", param_u64, &ival),
p_req("repeat", param_double, &dval),
p_req("repeat", param_double, &dval), NULL));
p_req("repeat", param_millionths, &fpval),
p_req("repeat", param_millionths, &fpval), NULL));
assert(check_fail());
assert(strstr(fail_msg, "developer error"));
@ -330,12 +330,13 @@ static void bad_programmer(void)
/* Add required param after optional */
j = json_parse(cmd, "[ '25', '546', '26', '1.1' ]");
unsigned int *msatoshi;
double *riskfactor;
assert(!param(cmd, j->buffer, j->toks,
p_req("u64", param_u64, &ival),
p_req("double", param_double, &dval),
p_opt_def("msatoshi", param_number, &msatoshi, 100),
p_req("riskfactor", param_double, &riskfactor), NULL));
u64 *riskfactor_millionths;
assert(!param(
cmd, j->buffer, j->toks, p_req("u64", param_u64, &ival),
p_req("fp", param_millionths, &fpval),
p_opt_def("msatoshi", param_number, &msatoshi, 100),
p_req("riskfactor", param_millionths, &riskfactor_millionths),
NULL));
assert(*msatoshi);
assert(*msatoshi == 100);
assert(check_fail());
@ -525,16 +526,16 @@ static void param_tests(void)
test_cb(param_bool, bool, "[ tru ]", false, false);
test_cb(param_bool, bool, "[ 1 ]", false, false);
test_cb(param_percent, double, "[ -0.01 ]", 0, false);
test_cb(param_percent, double, "[ 0.00 ]", 0, true);
test_cb(param_percent, double, "[ 1 ]", 1, true);
test_cb(param_percent, double, "[ 1.1 ]", 1.1, true);
test_cb(param_percent, double, "[ 1.01 ]", 1.01, true);
test_cb(param_percent, double, "[ 99.99 ]", 99.99, true);
test_cb(param_percent, double, "[ 100.0 ]", 100, true);
test_cb(param_percent, double, "[ 100.001 ]", 100.001, true);
test_cb(param_percent, double, "[ 1000 ]", 1000, true);
test_cb(param_percent, double, "[ 'wow' ]", 0, false);
test_cb(param_millionths, u64, "[ -0.01 ]", 0, false);
test_cb(param_millionths, u64, "[ 0.00 ]", 0, true);
test_cb(param_millionths, u64, "[ 1 ]", 1000000, true);
test_cb(param_millionths, u64, "[ 1.1 ]", 1100000, true);
test_cb(param_millionths, u64, "[ 1.01 ]", 1010000, true);
test_cb(param_millionths, u64, "[ 99.99 ]", 99990000, true);
test_cb(param_millionths, u64, "[ 100.0 ]", 100000000, true);
test_cb(param_millionths, u64, "[ 100.001 ]", 100001000, true);
test_cb(param_millionths, u64, "[ 1000 ]", 1000000000, true);
test_cb(param_millionths, u64, "[ 'wow' ]", 0, false);
}
static void test_invoice(struct command *cmd,

View File

@ -21,9 +21,9 @@ in \fIbtc\fR\.
There are two considerations for how good a route is: how low the fees
are, and how long your payment will get stuck in a delayed output if a
node goes down during the process\. The \fIriskfactor\fR floating-point field
controls this tradeoff; it is the annual cost of your funds being stuck
(as a percentage)\.
node goes down during the process\. The \fIriskfactor\fR non-negative
floating-point field controls this tradeoff; it is the annual cost of
your funds being stuck (as a percentage)\.
For example, if you thought the convenience of keeping your funds liquid
@ -36,7 +36,7 @@ If you didnt care about risk, \fIriskfactor\fR would be zero\.
\fIfromid\fR is the node to start the route from: default is this node\.
The \fIfuzzpercent\fR is a positive floating-point number, representing a
The \fIfuzzpercent\fR is a non-negative floating-point number, representing a
percentage of the actual fee\. The \fIfuzzpercent\fR is used to distort
computed fees along each channel, to provide some randomization to the
route generated\. 0\.0 means the exact fee of that channel is used, while

View File

@ -21,9 +21,9 @@ in *btc*.
There are two considerations for how good a route is: how low the fees
are, and how long your payment will get stuck in a delayed output if a
node goes down during the process. The *riskfactor* floating-point field
controls this tradeoff; it is the annual cost of your funds being stuck
(as a percentage).
node goes down during the process. The *riskfactor* non-negative
floating-point field controls this tradeoff; it is the annual cost of
your funds being stuck (as a percentage).
For example, if you thought the convenience of keeping your funds liquid
(not stuck) was worth 20% per annum interest, *riskfactor* would be 20.
@ -32,7 +32,7 @@ If you didnt care about risk, *riskfactor* would be zero.
*fromid* is the node to start the route from: default is this node.
The *fuzzpercent* is a positive floating-point number, representing a
The *fuzzpercent* is a non-negative floating-point number, representing a
percentage of the actual fee. The *fuzzpercent* is used to distort
computed fees along each channel, to provide some randomization to the
route generated. 0.0 means the exact fee of that channel is used, while

View File

@ -35,10 +35,9 @@ msgtype,gossip_getroute_request,3006
msgdata,gossip_getroute_request,source,?node_id,
msgdata,gossip_getroute_request,destination,node_id,
msgdata,gossip_getroute_request,msatoshi,amount_msat,
# We don't pass doubles, so pass riskfactor 1000000.
msgdata,gossip_getroute_request,riskfactor_by_million,u64,
msgdata,gossip_getroute_request,riskfactor_millionths,u64,
msgdata,gossip_getroute_request,final_cltv,u32,
msgdata,gossip_getroute_request,fuzz,double,
msgdata,gossip_getroute_request,fuzz_millionths,u64,
msgdata,gossip_getroute_request,num_excluded,u16,
msgdata,gossip_getroute_request,excluded,exclude_entry,num_excluded
msgdata,gossip_getroute_request,max_hops,u32,

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

View File

@ -883,11 +883,13 @@ static struct io_plan *getroute_req(struct io_conn *conn, struct daemon *daemon,
struct node_id *source, destination;
struct amount_msat msat;
u32 final_cltv;
u64 riskfactor_by_million;
/* risk factor 12.345% -> riskfactor_millionths = 12345000 */
u64 riskfactor_millionths;
u32 max_hops;
u8 *out;
struct route_hop *hops;
double fuzz;
/* fuzz 12.345% -> fuzz_millionths = 12345000 */
u64 fuzz_millionths;
struct exclude_entry **excluded;
/* To choose between variations, we need to know how much we're
@ -901,12 +903,9 @@ static struct io_plan *getroute_req(struct io_conn *conn, struct daemon *daemon,
* for a route from ourselves (the usual case): in that case,
* we don't have to consider fees on our own outgoing channels.
*/
if (!fromwire_gossip_getroute_request(msg, msg,
&source, &destination,
&msat, &riskfactor_by_million,
&final_cltv, &fuzz,
&excluded,
&max_hops))
if (!fromwire_gossip_getroute_request(
msg, msg, &source, &destination, &msat, &riskfactor_millionths,
&final_cltv, &fuzz_millionths, &excluded, &max_hops))
master_badmsg(WIRE_GOSSIP_GETROUTE_REQUEST, msg);
status_debug("Trying to find a route from %s to %s for %s",
@ -916,9 +915,10 @@ static struct io_plan *getroute_req(struct io_conn *conn, struct daemon *daemon,
type_to_string(tmpctx, struct amount_msat, &msat));
/* routing.c does all the hard work; can return NULL. */
hops = get_route(tmpctx, daemon->rstate, source, &destination,
msat, riskfactor_by_million / 1000000.0, final_cltv,
fuzz, pseudorand_u64(), excluded, max_hops);
hops = get_route(tmpctx, daemon->rstate, source, &destination, msat,
riskfactor_millionths / 1000000.0, final_cltv,
fuzz_millionths / 1000000.0, pseudorand_u64(),
excluded, max_hops);
out = towire_gossip_getroute_reply(NULL, hops);
daemon_conn_send(daemon->master, take(out));

View File

@ -381,7 +381,8 @@ static struct command_result *json_getroute(struct command *cmd,
const jsmntok_t *excludetok;
struct amount_msat *msat;
u32 *cltv;
double *riskfactor;
/* risk factor 12.345% -> riskfactor_millionths = 12345000 */
u64 *riskfactor_millionths;
const struct exclude_entry **excluded;
u32 *max_hops;
@ -390,23 +391,23 @@ static struct command_result *json_getroute(struct command *cmd,
* randomization (the higher-fee paths become more likely to
* be selected) at the cost of increasing the probability of
* selecting the higher-fee paths. */
double *fuzz;
u64 *fuzz_millionths; /* fuzz 12.345% -> fuzz_millionths = 12345000 */
if (!param(cmd, buffer, params,
p_req("id", param_node_id, &destination),
p_req("msatoshi", param_msat, &msat),
p_req("riskfactor", param_double, &riskfactor),
p_opt_def("cltv", param_number, &cltv, 9),
p_opt("fromid", param_node_id, &source),
p_opt_def("fuzzpercent", param_percent, &fuzz, 5.0),
p_opt("exclude", param_array, &excludetok),
p_opt_def("maxhops", param_number, &max_hops,
ROUTING_MAX_HOPS),
NULL))
if (!param(
cmd, buffer, params, p_req("id", param_node_id, &destination),
p_req("msatoshi", param_msat, &msat),
p_req("riskfactor", param_millionths, &riskfactor_millionths),
p_opt_def("cltv", param_number, &cltv, 9),
p_opt("fromid", param_node_id, &source),
p_opt_def("fuzzpercent", param_millionths, &fuzz_millionths,
5000000),
p_opt("exclude", param_array, &excludetok),
p_opt_def("maxhops", param_number, &max_hops, ROUTING_MAX_HOPS),
NULL))
return command_param_failed();
/* Convert from percentage */
*fuzz = *fuzz / 100.0;
*fuzz_millionths /= 100;
if (excludetok) {
const jsmntok_t *t;
@ -442,12 +443,9 @@ static struct command_result *json_getroute(struct command *cmd,
excluded = NULL;
}
u8 *req = towire_gossip_getroute_request(cmd, source, destination,
*msat,
*riskfactor * 1000000.0,
*cltv, fuzz,
excluded,
*max_hops);
u8 *req = towire_gossip_getroute_request(
cmd, source, destination, *msat, *riskfactor_millionths, *cltv,
*fuzz_millionths, excluded, *max_hops);
subd_req(ld->gossip, ld->gossip, req, -1, 0, json_getroute_reply, cmd);
return command_still_pending(cmd);
}

View File

@ -13,7 +13,9 @@
#include <common/json_stream.h>
#include <common/pseudorand.h>
#include <common/type_to_string.h>
#include <inttypes.h>
#include <plugins/libplugin.h>
#include <stdint.h>
#include <stdio.h>
#include <wire/onion_defs.h>
#include <wire/wire.h>
@ -83,11 +85,13 @@ struct pay_command {
/* How much we're paying, and what riskfactor for routing. */
struct amount_msat msat;
double riskfactor;
/* riskfactor 12.345% -> riskfactor_millionths = 12345000 */
u64 riskfactor_millionths;
unsigned int final_cltv;
/* Limits on what routes we'll accept. */
double maxfeepercent;
/* 12.345% -> maxfee_pct_millionths = 12345000 */
u64 maxfee_pct_millionths;
unsigned int maxdelay;
struct amount_msat exemptfee;
@ -719,8 +723,8 @@ static struct command_result *getroute_done(struct command *cmd,
struct pay_attempt *attempt = current_attempt(pc);
const jsmntok_t *t = json_get_member(buf, result, "route");
struct amount_msat fee;
struct amount_msat max_fee;
u32 delay;
double feepercent;
struct out_req *req;
if (!t)
@ -752,14 +756,19 @@ static struct command_result *getroute_done(struct command *cmd,
plugin_err(cmd->plugin, "getroute with invalid delay? %.*s",
result->end - result->start, buf);
/* Casting u64 to double will lose some precision. The loss of precision
* in feepercent will be like 3.0000..(some dots)..1 % - 3.0 %.
* That loss will not be representable in double. So, it's Okay to
* cast u64 to double for feepercent calculation. */
feepercent = ((double)fee.millisatoshis) * 100.0 / ((double) pc->msat.millisatoshis); /* Raw: fee double manipulation */
if (pc->maxfee_pct_millionths / 100 > UINT32_MAX)
plugin_err(cmd->plugin, "max fee percent too large: %lf",
pc->maxfee_pct_millionths / 1000000.0);
if (amount_msat_greater(fee, pc->exemptfee)
&& feepercent > pc->maxfeepercent) {
if (!amount_msat_fee(&max_fee, pc->msat, 0,
(u32)(pc->maxfee_pct_millionths / 100)))
plugin_err(
cmd->plugin, "max fee too large: %s * %lf%%",
type_to_string(tmpctx, struct amount_msat, &pc->msat),
pc->maxfee_pct_millionths / 1000000.0);
if (amount_msat_greater(fee, pc->exemptfee) &&
amount_msat_greater(fee, max_fee)) {
const jsmntok_t *charger;
struct json_out *failed;
char *feemsg;
@ -916,7 +925,8 @@ static struct command_result *execute_getroute(struct command *cmd,
type_to_string(tmpctx, struct amount_msat, &msat));
json_add_u32(req->js, "cltv", cltv);
json_add_u32(req->js, "maxhops", max_hops);
json_add_member(req->js, "riskfactor", false, "%f", pc->riskfactor);
json_add_member(req->js, "riskfactor", false, "%lf",
pc->riskfactor_millionths / 1000000.0);
if (tal_count(pc->excludes) != 0) {
json_array_start(req->js, "exclude");
for (size_t i = 0; i < tal_count(pc->excludes); i++)
@ -1245,10 +1255,10 @@ static struct command_result *json_pay(struct command *cmd,
struct bolt11 *b11;
const char *b11str;
char *fail;
double *riskfactor;
u64 *riskfactor_millionths;
unsigned int *retryfor;
struct pay_command *pc = tal(cmd, struct pay_command);
double *maxfeepercent;
u64 *maxfee_pct_millionths;
unsigned int *maxdelay;
struct amount_msat *exemptfee;
struct out_req *req;
@ -1256,16 +1266,18 @@ static struct command_result *json_pay(struct command *cmd,
bool *use_shadow;
#endif
if (!param(cmd, buf, params,
p_req("bolt11", param_string, &b11str),
if (!param(cmd, buf, params, p_req("bolt11", param_string, &b11str),
p_opt("msatoshi", param_msat, &msat),
p_opt("label", param_string, &pc->label),
p_opt_def("riskfactor", param_double, &riskfactor, 10),
p_opt_def("maxfeepercent", param_percent, &maxfeepercent, 0.5),
p_opt_def("riskfactor", param_millionths,
&riskfactor_millionths, 10000000),
p_opt_def("maxfeepercent", param_millionths,
&maxfee_pct_millionths, 500000),
p_opt_def("retry_for", param_number, &retryfor, 60),
p_opt_def("maxdelay", param_number, &maxdelay,
maxdelay_default),
p_opt_def("exemptfee", param_msat, &exemptfee, AMOUNT_MSAT(5000)),
p_opt_def("exemptfee", param_msat, &exemptfee,
AMOUNT_MSAT(5000)),
#if DEVELOPER
p_opt_def("use_shadow", param_bool, &use_shadow, true),
#endif
@ -1312,10 +1324,10 @@ static struct command_result *json_pay(struct command *cmd,
" sets feature var_onion with no secret");
}
pc->maxfeepercent = *maxfeepercent;
pc->maxfee_pct_millionths = *maxfee_pct_millionths;
pc->maxdelay = *maxdelay;
pc->exemptfee = *exemptfee;
pc->riskfactor = *riskfactor;
pc->riskfactor_millionths = *riskfactor_millionths;
pc->final_cltv = b11->min_final_cltv_expiry;
pc->dest = type_to_string(cmd, struct node_id, &b11->receiver_id);
pc->shadow_dest = tal_strdup(pc, pc->dest);

View File

@ -199,7 +199,6 @@ class Type(FieldSet):
'secp256k1_ecdsa_signature',
'secp256k1_ecdsa_recoverable_signature',
'wirestring',
'double',
'errcode_t',
'bigsize',
'varint',

View File

@ -151,11 +151,6 @@ u64 fromwire_tu64(const u8 **cursor, size_t *max)
return fromwire_tlv_uint(cursor, max, 8);
}
void fromwire_double(const u8 **cursor, size_t *max, double *ret)
{
fromwire(cursor, max, ret, sizeof(*ret));
}
bool fromwire_bool(const u8 **cursor, size_t *max)
{
u8 ret;

View File

@ -77,11 +77,6 @@ void towire_tu64(u8 **pptr, u64 v)
return towire_tlv_uint(pptr, v);
}
void towire_double(u8 **pptr, const double *v)
{
towire(pptr, v, sizeof(*v));
}
void towire_bool(u8 **pptr, bool v)
{
u8 val = v;

View File

@ -78,7 +78,6 @@ void towire_u64(u8 **pptr, u64 v);
void towire_tu16(u8 **pptr, u16 v);
void towire_tu32(u8 **pptr, u32 v);
void towire_tu64(u8 **pptr, u64 v);
void towire_double(u8 **pptr, const double *v);
void towire_pad(u8 **pptr, size_t num);
void towire_bool(u8 **pptr, bool v);
void towire_errcode_t(u8 **pptr, errcode_t v);
@ -103,7 +102,6 @@ u64 fromwire_u64(const u8 **cursor, size_t *max);
u16 fromwire_tu16(const u8 **cursor, size_t *max);
u32 fromwire_tu32(const u8 **cursor, size_t *max);
u64 fromwire_tu64(const u8 **cursor, size_t *max);
void fromwire_double(const u8 **cursor, size_t *max, double *v);
bool fromwire_bool(const u8 **cursor, size_t *max);
errcode_t fromwire_errcode_t(const u8 **cursor, size_t *max);
bigsize_t fromwire_bigsize(const u8 **cursor, size_t *max);