Typesafe callback system for parsing json

This is part of #1464 and incorporates Rusty's suggested updates from #1569.

See comment in param.h for description, here's the basics:

	unsigned cltv;
	const jsmntok_t *note;
	u64 msatoshi;
	struct param * mp;

	if (!param_parse(cmd, buffer, tokens,
			 param_req("cltv", json_tok_number, &cltv),
			 param_opt("note", json_tok_tok, &note),
			 mp = param_opt("msatoshi", json_tok_u64, &msatoshi),
			 NULL))
		return;

	if (param_is_set(mp))
		do_something()

There is a lot of developer mode code to make sure we don't make mistakes,
like trying to unmarshal into the same variable twice or adding a required param
after optional.

During testing, I found a bug (of sorts) in the current system.  It allows you
to provide two named parameters with the same name without error; e.g.:

	# cli/lightning-cli -k newaddr addresstype=p2sh-segwit addresstype=bech32
	{
		  "address": "2N3r6fT65PhfhE1mcMS6TtcdaEurud6M7pA"
	}

It just takes the first and ignores the second.  The new system reports this as an
error for now.  We can always change this later.
This commit is contained in:
Mark Beckwith 2018-06-16 10:29:32 -05:00 committed by Rusty Russell
parent fed5a117e7
commit 4d1d0438e1
6 changed files with 851 additions and 0 deletions

View File

@ -143,6 +143,13 @@ bool json_tok_bool(const char *buffer, const jsmntok_t *tok, bool *b)
return false;
}
bool json_tok_tok(const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out)
{
*out = tok;
return true;
}
const jsmntok_t *json_next(const jsmntok_t *tok)
{
const jsmntok_t *t;

View File

@ -42,6 +42,13 @@ bool json_tok_bitcoin_amount(const char *buffer, const jsmntok_t *tok,
/* Extract boolean this (must be a true or false) */
bool json_tok_bool(const char *buffer, const jsmntok_t *tok, bool *b);
/*
* Set the address of @out to @tok. Used as a param_table callback by handlers that
* want to unmarshal @tok themselves.
*/
bool json_tok_tok(const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out);
/* Is this the null primitive? */
bool json_tok_is_null(const char *buffer, const jsmntok_t *tok);

View File

@ -70,6 +70,7 @@ LIGHTNINGD_SRC := \
lightningd/onchain_control.c \
lightningd/opening_control.c \
lightningd/options.c \
lightningd/params.c \
lightningd/pay.c \
lightningd/payalgo.c \
lightningd/peer_control.c \

297
lightningd/params.c Normal file
View File

@ -0,0 +1,297 @@
#include <ccan/tal/str/str.h>
#include <common/utils.h>
#include <lightningd/json.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/jsonrpc_errors.h>
#include <lightningd/lightningd.h>
#include <lightningd/params.h>
struct param {
char *name;
bool required;
bool is_set;
param_cb cb;
void *arg;
};
void *param_is_set(struct param *def)
{
return def->is_set ? def->arg : NULL;
}
struct param *param_add_(bool required, char *name, param_cb cb, void *arg)
{
#if DEVELOPER
assert(name);
assert(cb);
assert(arg);
#endif
struct param *last = tal(tmpctx, struct param);
last->is_set = false;
last->name = tal_strdup(last, name);
last->cb = cb;
last->arg = arg;
last->required = required;
return last;
}
struct fail_format {
void *cb;
const char *format;
};
static struct fail_format fail_formats[] = {
{json_tok_bool, "'%s' should be 'true' or 'false', not '%.*s'"},
{json_tok_double, "'%s' should be a double, not '%.*s'"},
{json_tok_u64, "'%s' should be an unsigned 64 bit integer, not '%.*s'"},
{json_tok_number, "'%s' should be an integer, not '%.*s'"},
{json_tok_wtx,
"'%s' should be 'all' or a positive integer greater than "
"545, not '%.*s'"},
{NULL, "'%s' of '%.*s' is invalid'"}
};
static const char *find_fail_format(param_cb cb)
{
struct fail_format *fmt = fail_formats;
while (fmt->cb != NULL) {
if (fmt->cb == cb)
break;
fmt++;
}
return fmt->format;
}
static bool make_callback(struct command *cmd,
struct param *def,
const char *buffer, const jsmntok_t * tok)
{
def->is_set = true;
if (!def->cb(buffer, tok, def->arg)) {
struct json_result *data = new_json_result(cmd);
const char *val = tal_fmt(cmd, "%.*s", tok->end - tok->start,
buffer + tok->start);
json_object_start(data, NULL);
json_add_string(data, def->name, val);
json_object_end(data);
command_fail_detailed(cmd, JSONRPC2_INVALID_PARAMS, data,
find_fail_format(def->cb), def->name,
tok->end - tok->start,
buffer + tok->start);
return false;
}
return true;
}
static struct param **post_check(struct command *cmd, struct param **params)
{
struct param **first = params;
struct param **last = first + tal_count(params);
/* Make sure required params were provided. */
while (first != last && (*first)->required) {
if (!(*first)->is_set) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"missing required parameter: '%s'",
(*first)->name);
return NULL;
}
first++;
}
/* Set optional missing jsmntok_t args to NULL. */
while (first != last) {
struct param *p = *first;
if (!p->is_set && (p->cb == (param_cb) json_tok_tok)) {
jsmntok_t **tok = p->arg;
*tok = NULL;
}
first++;
}
return params;
}
static struct param **parse_by_position(struct command *cmd,
struct param **params,
const char *buffer,
const jsmntok_t tokens[])
{
const jsmntok_t *tok = tokens + 1;
const jsmntok_t *end = json_next(tokens);
struct param **first = params;
struct param **last = first + tal_count(params);
while (first != last && tok != end) {
if (!json_tok_is_null(buffer, tok))
if (!make_callback(cmd, *first, buffer, tok))
return NULL;
tok = json_next(tok);
first++;
}
/* check for unexpected trailing params */
if (tok != end) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"too many parameters:"
" got %u, expected %zu",
tokens->size, tal_count(params));
return NULL;
}
return post_check(cmd, params);
}
static struct param *find_param(struct param **params, const char *start,
size_t n)
{
struct param **first = params;
struct param **last = first + tal_count(params);
while (first != last) {
if (strncmp((*first)->name, start, n) == 0)
if (strlen((*first)->name) == n)
return *first;
first++;
}
return NULL;
}
static struct param **parse_by_name(struct command *cmd,
struct param **params,
const char *buffer,
const jsmntok_t tokens[])
{
const jsmntok_t *first = tokens + 1;
const jsmntok_t *last = json_next(tokens);
while (first != last) {
struct param *p = find_param(params, buffer + first->start,
first->end - first->start);
if (!p) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"unknown parameter: '%.*s'",
first->end - first->start,
buffer + first->start);
return NULL;
}
if (p->is_set) {
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"duplicate json names: '%s'", p->name);
return NULL;
}
if (!make_callback(cmd, p, buffer, first + 1))
return NULL;
first = json_next(first + 1);
}
return post_check(cmd, params);
}
#if DEVELOPER
static int comp_by_name(const void *a, const void *b)
{
const char *x = (*(const struct param **) a)->name;
const char *y = (*(const struct param **) b)->name;
return strcmp(x, y);
}
static int comp_by_arg(const void *a, const void *b)
{
size_t x = (size_t) ((*(const struct param **) a)->arg);
size_t y = (size_t) ((*(const struct param **) b)->arg);
return x - y;
}
/* This comparator is a bit different, but works well.
* Return 0 if @a is optional and @b is required. Otherwise return 1.
*/
static int comp_req_order(const void *a, const void *b)
{
bool x = (bool) ((*(const struct param **) a)->required);
bool y = (bool) ((*(const struct param **) b)->required);
if (!x && y)
return 0;
return 1;
}
/*
* Make sure 2 sequential items in @params are not equal (based on
* provided comparator).
*/
static void check_distinct(struct param **params,
int (*compar) (const void *, const void *))
{
struct param **first = params;
struct param **last = first + tal_count(params);
first++;
while (first != last) {
assert(compar(first - 1, first) != 0);
first++;
}
}
static void check_unique(struct param **copy,
int (*compar) (const void *, const void *))
{
qsort(copy, tal_count(copy), sizeof(struct param *), compar);
check_distinct(copy, compar);
}
/*
* Verify consistent internal state.
*/
static void check_params(struct param **params)
{
if (tal_count(params) < 2)
return;
/* make sure there are no required params following optional */
check_distinct(params, comp_req_order);
/* duplicate so we can sort */
struct param **copy = tal_dup_arr(params, struct param *,
params, tal_count(params), 0);
/* check for repeated names and args */
check_unique(copy, comp_by_name);
check_unique(copy, comp_by_arg);
tal_free(copy);
}
#endif
static struct param **param_parse_arr(struct command *cmd,
const char *buffer,
const jsmntok_t tokens[],
struct param **params)
{
#if DEVELOPER
check_params(params);
#endif
if (tokens->type == JSMN_ARRAY)
return parse_by_position(cmd, params, buffer, tokens);
else if (tokens->type == JSMN_OBJECT)
return parse_by_name(cmd, params, buffer, tokens);
command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Expected array or object for params");
return NULL;
}
struct param **param_parse(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...)
{
struct param *def;
struct param **params = tal_arr(cmd, struct param *, 0);
va_list ap;
va_start(ap, tokens);
while ((def = va_arg(ap, struct param *)) != NULL) {
tal_steal(params, def);
tal_resize(&params, tal_count(params) + 1);
params[tal_count(params) - 1] = def;
}
va_end(ap);
return param_parse_arr(cmd, buffer, tokens, params);
}

93
lightningd/params.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef LIGHTNING_LIGHTNINGD_PARAMS_H
#define LIGHTNING_LIGHTNINGD_PARAMS_H
#include "config.h"
#include <ccan/ccan/typesafe_cb/typesafe_cb.h>
struct param;
/*
Typesafe callback system for unmarshalling and validating json parameters.
Typical usage:
unsigned cltv;
const jsmntok_t *note;
u64 msatoshi;
struct param * mp;
if (!param_parse(cmd, buffer, tokens,
param_req("cltv", json_tok_number, &cltv),
param_opt("note", json_tok_tok, &note),
mp = param_opt("msatoshi", json_tok_u64, &msatoshi),
NULL))
return;
At this point in the code you can be assured the json tokens were successfully
parsed. If not, param_parse() returned NULL, having already called
command_fail() with a descriptive error message. The data section of the json
result contains the offending parameter and its value.
cltv is a required parameter, and is set correctly.
note and msatoshi are optional parameters. You can see if they have been set
by calling param_is_set(); e.g.:
if (param_is_set(mp))
do_something()
The note parameter uses a special callback, json_tok_tok(). It
simply sets seedtok to the appropriate value and lets the handler do the
validating. It has the added feature of setting seedtok to NULL if it is null
or not specified.
There are canned failure messages for common callbacks. An example:
'msatoshi' should be an unsigned 64 bit integer, not '123z'
Otherwise a generic message is provided.
*/
struct param **param_parse(struct command *cmd, const char *buffer,
const jsmntok_t params[], ...);
/*
* This callback provided must follow this signature; e.g.,
* bool json_tok_double(const char *buffer, const jsmntok_t *tok, double *arg)
*/
typedef bool(*param_cb)(const char *buffer, const jsmntok_t *tok, void *arg);
/*
* Add a handler to unmarshal a required json token into @arg. The handler must
* return true on success and false on failure. Upon failure, command_fail will be
* called with a descriptive error message.
*
* This operation is typesafe; i.e., a compilation error will occur if the types
* of @arg and the last parameter of @cb do not match.
*
* Returns an opaque pointer that can be later used in param_is_set().
*/
#define param_req(name, cb, arg) \
param_add_(true, name, \
typesafe_cb_preargs(bool, void *, \
(cb), (arg), \
const char *, \
const jsmntok_t *), \
(arg))
/*
* Same as above but for optional parameters.
*/
#define param_opt(name, cb, arg) \
param_add_(false, name, \
typesafe_cb_preargs(bool, void *, \
(cb), (arg), \
const char *, \
const jsmntok_t *), \
(arg))
struct param * param_add_(bool required, char *name, param_cb cb, void *arg);
/*
* Check to see if an optional parameter was set during parsing (although it
* works for all parameters).
* Returns the @arg if set, otherwise NULL.
*/
void * param_is_set(struct param *p);
#endif /* LIGHTNING_LIGHTNINGD_PARAMS_H */

View File

@ -0,0 +1,446 @@
#include <signal.h>
#include <setjmp.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/params.c>
#include <common/json.c>
#include <common/json_escaped.c>
#include <ccan/array_size/array_size.h>
bool failed;
char *fail_msg;
struct command *cmd;
void command_fail(struct command *cmd, int code, const char *fmt, ...)
{
failed = true;
va_list ap;
va_start(ap, fmt);
fail_msg = tal_vfmt(cmd, fmt, ap);
va_end(ap);
}
void command_fail_detailed(struct command *cmd, int code,
const struct json_result *data, const char *fmt, ...)
{
failed = true;
va_list ap;
va_start(ap, fmt);
fail_msg = tal_vfmt(cmd, fmt, ap);
fail_msg =
tal_fmt(cmd, "%s data: %s", fail_msg, json_result_string(data));
va_end(ap);
}
/* AUTOGENERATED MOCKS START */
/* Generated stub for json_tok_wtx */
bool json_tok_wtx(struct wallet_tx *tx UNNEEDED, const char *buffer UNNEEDED,
const jsmntok_t * sattok UNNEEDED)
{
abort();
}
/* AUTOGENERATED MOCKS END */
struct json {
jsmntok_t *toks;
char *buffer;
};
static void convert_quotes(char *first)
{
while (*first != '\0') {
if (*first == '\'')
*first = '"';
first++;
}
}
static struct json *json_parse(const tal_t * ctx, const char *str)
{
struct json *j = tal(ctx, struct json);
j->buffer = tal_strdup(j, str);
convert_quotes(j->buffer);
j->toks = tal_arr(j, jsmntok_t, 50);
assert(j->toks);
jsmn_parser parser;
again:
jsmn_init(&parser);
int ret = jsmn_parse(&parser, j->buffer, strlen(j->buffer), j->toks,
tal_count(j->toks));
if (ret == JSMN_ERROR_NOMEM) {
tal_resize(&j->toks, tal_count(j->toks) * 2);
goto again;
}
if (ret <= 0) {
assert(0);
}
failed = false;
return j;
}
static void zero_params(void)
{
struct json *j = json_parse(cmd, "{}");
assert(param_parse(cmd, j->buffer, j->toks, NULL));
j = json_parse(cmd, "[]");
assert(param_parse(cmd, j->buffer, j->toks, NULL));
}
struct sanity {
char *str;
bool failed;
int ival;
double dval;
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},
// fail
{"{'u64':'42', 'double':'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"},
// not enough
{"{'u64':'42'}", true, 0, 0, "missing required"},
{"['42']", true, 0, 0, "missing required"},
// fail wrong type
{"{'u64':'hello', 'double':'3.15'}", true, 0, 0, "\"u64\": \"hello\""},
{"['3.15', '3.15', 'stuff']", true, 0, 0, "integer"},
};
static void stest(const struct json *j, struct sanity *b)
{
u64 ival;
double dval;
if (!param_parse(cmd, j->buffer, j->toks,
param_req("u64", json_tok_u64, &ival),
param_req("double", json_tok_double, &dval), NULL)) {
assert(failed == true);
assert(b->failed == true);
assert(strstr(fail_msg, b->fail_str));
} else {
assert(b->failed == false);
assert(ival == 42);
assert(dval > 3.1499 && b->dval < 3.1501);
}
}
static void sanity(void)
{
for (int i = 0; i < ARRAY_SIZE(buffers); ++i) {
struct json *j = json_parse(cmd, buffers[i].str);
assert(j->toks->type == JSMN_OBJECT
|| j->toks->type == JSMN_ARRAY);
stest(j, &buffers[i]);
}
}
/*
* Make sure toks are passed through correctly, and also make sure
* optional missing toks are set to NULL.
*/
static void tok_tok(void)
{
{
unsigned int n;
const jsmntok_t *tok = NULL;
struct json *j = json_parse(cmd, "{ 'satoshi', '546' }");
assert(param_parse(cmd, j->buffer, j->toks,
param_req("satoshi", json_tok_tok,
&tok), NULL));
assert(tok);
assert(json_tok_number(j->buffer, tok, &n));
assert(n == 546);
}
// again with missing optional parameter
{
/* make sure it is *not* NULL */
const jsmntok_t *tok = (const jsmntok_t *) 65535;
struct json *j = json_parse(cmd, "{}");
assert(param_parse(cmd, j->buffer, j->toks,
param_opt("satoshi", json_tok_tok,
&tok), NULL));
/* make sure it *is* NULL */
assert(tok == NULL);
}
}
/* check for valid but duplicate json name-value pairs */
static void dup(void)
{
struct json *j =
json_parse(cmd,
"{ 'u64' : '42', 'u64' : '43', 'double' : '3.15' }");
u64 i;
double d;
assert(!param_parse(cmd, j->buffer, j->toks,
param_req("u64", json_tok_u64, &i),
param_req("double", json_tok_double, &d), NULL));
}
static void null_params(void)
{
uint64_t *ints = tal_arr(cmd, uint64_t, 7);
/* no null params */
struct json *j =
json_parse(cmd, "[ '10', '11', '12', '13', '14', '15', '16']");
for (int i = 0; i < tal_count(ints); ++i)
ints[i] = i;
assert(param_parse(cmd, j->buffer, j->toks,
param_req("0", json_tok_u64, &ints[0]),
param_req("1", json_tok_u64, &ints[1]),
param_req("2", json_tok_u64, &ints[2]),
param_req("3", json_tok_u64, &ints[3]),
param_opt("4", json_tok_u64, &ints[4]),
param_opt("5", json_tok_u64, &ints[5]),
param_opt("6", json_tok_u64, &ints[6]), NULL));
for (int i = 0; i < tal_count(ints); ++i)
assert(ints[i] == i + 10);
/* missing at end */
for (int i = 0; i < tal_count(ints); ++i)
ints[i] = 42;
j = json_parse(cmd, "[ '10', '11', '12', '13', '14']");
struct param *four, *five;
assert(param_parse(cmd, j->buffer, j->toks,
param_req("0", json_tok_u64, &ints[0]),
param_req("1", json_tok_u64, &ints[1]),
param_req("2", json_tok_u64, &ints[2]),
param_req("3", json_tok_u64, &ints[3]),
four = param_opt("4", json_tok_u64, &ints[4]),
five = param_opt("5", json_tok_u64, &ints[5]),
param_opt("6", json_tok_u64, &ints[6]), NULL));
assert(ints[4] == 14);
assert(param_is_set(four) == &ints[4]);
assert(!param_is_set(five));
assert(ints[5] == 42);
assert(ints[6] == 42);
}
#if DEVELOPER
jmp_buf jump;
static void handle_abort(int sig)
{
longjmp(jump, 1);
}
static void set_assert(void)
{
struct sigaction act;
memset(&act, '\0', sizeof(act));
act.sa_handler = &handle_abort;
if (sigaction(SIGABRT, &act, NULL) < 0) {
perror("set_assert");
exit(0);
}
}
static void restore_assert(void)
{
struct sigaction act;
memset(&act, '\0', sizeof(act));
act.sa_handler = SIG_DFL;
if (sigaction(SIGABRT, &act, NULL) < 0) {
perror("reset_assert");
exit(0);
}
}
/*
* Check to make sure there are no programming mistakes.
*/
static void bad_programmer(void)
{
u64 ival;
u64 ival2;
double dval;
set_assert();
struct json *j = json_parse(cmd, "[ '25', '546', '26' ]");
/* check for repeated names */
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req("repeat", json_tok_u64, &ival),
param_req("double", json_tok_double, &dval),
param_req("repeat", json_tok_u64, &ival2), NULL);
/* shouldn't get here */
restore_assert();
assert(false);
}
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req("repeat", json_tok_u64, &ival),
param_req("double", json_tok_double, &dval),
param_req("repeat", json_tok_u64, &ival), NULL);
restore_assert();
assert(false);
}
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req("u64", json_tok_u64, &ival),
param_req("repeat", json_tok_double, &dval),
param_req("repeat", json_tok_double, &dval), NULL);
restore_assert();
assert(false);
}
/* check for repeated arguments */
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req("u64", json_tok_u64, &ival),
param_req("repeated-arg", json_tok_u64, &ival),
NULL);
restore_assert();
assert(false);
}
/* check for NULL input */
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req(NULL, json_tok_u64, &ival), NULL);
restore_assert();
assert(false);
}
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req(NULL, json_tok_u64, &ival), NULL);
restore_assert();
assert(false);
}
if (setjmp(jump) == 0) {
param_parse(cmd, j->buffer, j->toks,
param_req("u64", NULL, &ival), NULL);
restore_assert();
assert(false);
}
if (setjmp(jump) == 0) {
/* Add required param after optional */
struct json *j =
json_parse(cmd, "[ '25', '546', '26', '1.1' ]");
unsigned int msatoshi;
double riskfactor;
param_parse(cmd, j->buffer, j->toks,
param_req("u64", json_tok_u64, &ival),
param_req("double", json_tok_double, &dval),
param_opt("msatoshi", json_tok_number, &msatoshi),
param_req("riskfactor", json_tok_double,
&riskfactor), NULL);
restore_assert();
assert(false);
}
restore_assert();
}
#endif
static void add_members(struct param **params,
struct json_result *obj,
struct json_result *arr, unsigned int *ints)
{
char name[256];
for (int i = 0; i < tal_count(ints); ++i) {
sprintf(name, "%d", i);
json_add_num(obj, name, i);
json_add_num(arr, NULL, i);
params[i] = param_req(name, json_tok_number, &ints[i]);
tal_steal(params, params[i]);
}
}
/*
* A roundabout way of initializing an array of ints to:
* ints[0] = 0, ints[1] = 1, ... ints[499] = 499
*/
static void five_hundred_params(void)
{
struct param **params = tal_arr(NULL, struct param *, 500);
unsigned int *ints = tal_arr(params, unsigned int, 500);
struct json_result *obj = new_json_result(params);
struct json_result *arr = new_json_result(params);
json_object_start(obj, NULL);
json_array_start(arr, NULL);
add_members(params, obj, arr, ints);
json_object_end(obj);
json_array_end(arr);
/* first test object version */
struct json *j = json_parse(params, obj->s);
assert(param_parse_arr(cmd, j->buffer, j->toks, params));
for (int i = 0; i < tal_count(ints); ++i) {
assert(ints[i] == i);
ints[i] = 65535;
}
/* now test array */
j = json_parse(params, arr->s);
assert(param_parse_arr(cmd, j->buffer, j->toks, params));
for (int i = 0; i < tal_count(ints); ++i) {
assert(ints[i] == i);
}
tal_free(params);
}
static void sendpay(void)
{
struct json *j = json_parse(cmd, "[ 'A', '123', 'hello there' '547']");
const jsmntok_t *routetok, *note;
u64 msatoshi;
unsigned cltv;
struct param * mp;
if (!param_parse(cmd, j->buffer, j->toks,
param_req("route", json_tok_tok, &routetok),
param_req("cltv", json_tok_number, &cltv),
param_opt("note", json_tok_tok, &note),
mp = param_opt("msatoshi", json_tok_u64, &msatoshi),
NULL))
assert(false);
assert(param_is_set(mp));
assert(msatoshi == 547);
}
int main(void)
{
setup_locale();
cmd = tal(NULL, struct command);
fail_msg = tal_arr(cmd, char, 10000);
zero_params();
sanity();
tok_tok();
null_params();
#if DEVELOPER
bad_programmer();
#endif
dup();
five_hundred_params();
sendpay();
tal_free(cmd);
printf("run-params ok\n");
}