diff --git a/common/json.c b/common/json.c index 41d04ebb3..c89d8af00 100644 --- a/common/json.c +++ b/common/json.c @@ -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; diff --git a/common/json.h b/common/json.h index a808cde9c..0c1cb12f3 100644 --- a/common/json.h +++ b/common/json.h @@ -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); diff --git a/lightningd/Makefile b/lightningd/Makefile index 193c2d177..b43cd4e30 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -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 \ diff --git a/lightningd/params.c b/lightningd/params.c new file mode 100644 index 000000000..e6c600b80 --- /dev/null +++ b/lightningd/params.c @@ -0,0 +1,297 @@ +#include +#include +#include +#include +#include +#include +#include + +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(¶ms, tal_count(params) + 1); + params[tal_count(params) - 1] = def; + } + va_end(ap); + + return param_parse_arr(cmd, buffer, tokens, params); +} diff --git a/lightningd/params.h b/lightningd/params.h new file mode 100644 index 000000000..1aab8ddc1 --- /dev/null +++ b/lightningd/params.h @@ -0,0 +1,93 @@ +#ifndef LIGHTNING_LIGHTNINGD_PARAMS_H +#define LIGHTNING_LIGHTNINGD_PARAMS_H +#include "config.h" +#include + +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, ¬e), + 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 */ diff --git a/lightningd/test/run-params.c b/lightningd/test/run-params.c new file mode 100644 index 000000000..22ddf0c07 --- /dev/null +++ b/lightningd/test/run-params.c @@ -0,0 +1,446 @@ +#include +#include +#include + +#include +#include +#include +#include + +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, ¬e), + 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"); +}