Browse Source

param: implement helpers for multiplex commands.

Our previous param support was a bit limited in this case.

We create a dev- command multiplexer, so we can exercise it.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
pull/2938/head
Rusty Russell 6 years ago
parent
commit
fb6870c139
  1. 7
      common/json_tok.c
  2. 4
      common/json_tok.h
  3. 44
      common/param.c
  4. 18
      common/param.h
  5. 84
      lightningd/jsonrpc.c
  6. 10
      lightningd/test/run-jsonrpc.c
  7. 67
      tests/test_misc.py

7
common/json_tok.c

@ -78,6 +78,13 @@ struct command_result *param_string(struct command *cmd, const char *name,
return NULL; return NULL;
} }
struct command_result *param_ignore(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
const void *unused)
{
return NULL;
}
struct command_result *param_label(struct command *cmd, const char *name, struct command_result *param_label(struct command *cmd, const char *name,
const char * buffer, const jsmntok_t *tok, const char * buffer, const jsmntok_t *tok,
struct json_escape **label) struct json_escape **label)

4
common/json_tok.h

@ -85,4 +85,8 @@ struct command_result *param_tok(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t * tok, const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out); const jsmntok_t **out);
/* Ignore the token. Not usually used. */
struct command_result *param_ignore(struct command *cmd, const char *name,
const char *buffer, const jsmntok_t *tok,
const void *unused);
#endif /* LIGHTNING_COMMON_JSON_TOK_H */ #endif /* LIGHTNING_COMMON_JSON_TOK_H */

44
common/param.c

@ -263,6 +263,50 @@ static struct command_result *param_arr(struct command *cmd, const char *buffer,
"Expected array or object for params"); "Expected array or object for params");
} }
#include <stdio.h>
const char *param_subcommand(struct command *cmd, const char *buffer,
const jsmntok_t tokens[],
const char *name, ...)
{
va_list ap;
struct param *params = tal_arr(cmd, struct param, 0);
const char *arg, **names = tal_arr(tmpctx, const char *, 1);
const char *subcmd;
param_add(&params, "subcommand", true, (void *)param_string, &subcmd);
names[0] = name;
va_start(ap, name);
while ((arg = va_arg(ap, const char *)) != NULL)
tal_arr_expand(&names, arg);
va_end(ap);
if (command_usage_only(cmd)) {
char *usage = tal_strdup(cmd, "subcommand");
for (size_t i = 0; i < tal_count(names); i++)
tal_append_fmt(&usage, "%c%s",
i == 0 ? '=' : '|', names[i]);
command_set_usage(cmd, usage);
return NULL;
}
/* Check it's valid */
if (param_arr(cmd, buffer, tokens, params, true) != NULL) {
return NULL;
}
/* Check it's one of the known ones. */
for (size_t i = 0; i < tal_count(names); i++)
if (streq(subcmd, names[i]))
return subcmd;
/* We really do ignore this. */
if (command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Unknown subcommand '%s'", subcmd))
;
return NULL;
}
bool param(struct command *cmd, const char *buffer, bool param(struct command *cmd, const char *buffer,
const jsmntok_t tokens[], ...) const jsmntok_t tokens[], ...)
{ {

18
common/param.h

@ -58,13 +58,25 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
const jsmntok_t *tok, const jsmntok_t *tok,
void **arg); void **arg);
/**
* Parse the first json value.
*
* name...: NULL-terminated array of valid values.
*
* Returns subcommand: if it returns NULL if you should return
* command_param_failed() immediately.
*/
const char *param_subcommand(struct command *cmd, const char *buffer,
const jsmntok_t tokens[],
const char *name, ...) LAST_ARG_NULL;
/* /*
* Add a required parameter. * Add a required parameter.
*/ */
#define p_req(name, cbx, arg) \ #define p_req(name, cbx, arg) \
name"", \ name"", \
true, \ true, \
(cbx), \ (param_cbx)(cbx), \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \ (arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \ (const char *)NULL, \
(const char *)NULL, \ (const char *)NULL, \
@ -77,7 +89,7 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
#define p_opt(name, cbx, arg) \ #define p_opt(name, cbx, arg) \
name"", \ name"", \
false, \ false, \
(cbx), \ (param_cbx)(cbx), \
({ *arg = NULL; \ ({ *arg = NULL; \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \ (arg) + 0*sizeof((cbx)((struct command *)NULL, \
(const char *)NULL, \ (const char *)NULL, \
@ -91,7 +103,7 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
#define p_opt_def(name, cbx, arg, def) \ #define p_opt_def(name, cbx, arg, def) \
name"", \ name"", \
false, \ false, \
(cbx), \ (param_cbx)(cbx), \
({ (*arg) = tal((cmd), typeof(**arg)); \ ({ (*arg) = tal((cmd), typeof(**arg)); \
(**arg) = (def); \ (**arg) = (def); \
(arg) + 0*sizeof((cbx)((struct command *)NULL, \ (arg) + 0*sizeof((cbx)((struct command *)NULL, \

84
lightningd/jsonrpc.c

@ -269,49 +269,63 @@ static void slowcmd_start(struct slowcmd *sc)
slowcmd_finish, sc); slowcmd_finish, sc);
} }
static struct command_result *json_slowcmd(struct command *cmd, static struct command_result *json_dev(struct command *cmd UNUSED,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNUSED, const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params) const jsmntok_t *params)
{ {
struct slowcmd *sc = tal(cmd, struct slowcmd); const char *subcmd;
sc->cmd = cmd;
if (!param(cmd, buffer, params,
p_opt_def("msec", param_number, &sc->msec, 1000),
NULL))
return command_param_failed();
new_reltimer(cmd->ld->timers, sc, time_from_msec(0), slowcmd_start, sc);
return command_still_pending(cmd);
}
static const struct json_command dev_slowcmd_command = {
"dev-slowcmd",
"developer",
json_slowcmd,
"Torture test for slow commands, optional {msec}"
};
AUTODATA(json_command, &dev_slowcmd_command);
static struct command_result *json_crash(struct command *cmd UNUSED, subcmd = param_subcommand(cmd, buffer, params,
const char *buffer, "crash", "rhash", "slowcmd", NULL);
const jsmntok_t *obj UNNEEDED, if (!subcmd)
const jsmntok_t *params)
{
if (!param(cmd, buffer, params, NULL))
return command_param_failed(); return command_param_failed();
fatal("Crash at user request"); if (streq(subcmd, "crash")) {
if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
NULL))
return command_param_failed();
fatal("Crash at user request");
} else if (streq(subcmd, "slowcmd")) {
struct slowcmd *sc = tal(cmd, struct slowcmd);
sc->cmd = cmd;
if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
p_opt_def("msec", param_number, &sc->msec, 1000),
NULL))
return command_param_failed();
new_reltimer(cmd->ld->timers, sc, time_from_msec(0),
slowcmd_start, sc);
return command_still_pending(cmd);
} else {
assert(streq(subcmd, "rhash"));
struct json_stream *response;
struct sha256 *secret;
if (!param(cmd, buffer, params,
p_req("subcommand", param_ignore, cmd),
p_req("secret", param_sha256, &secret),
NULL))
return command_param_failed();
/* Hash in place. */
sha256(secret, secret, sizeof(*secret));
response = json_stream_success(cmd);
json_add_hex(response, "rhash", secret, sizeof(*secret));
return command_success(cmd, response);
}
} }
static const struct json_command dev_crash_command = { static const struct json_command dev_command = {
"dev-crash", "dev",
"developer", "developer",
json_crash, json_dev,
"Crash lightningd by calling fatal()" "Developer command test multiplexer"
}; };
AUTODATA(json_command, &dev_crash_command); AUTODATA(json_command, &dev_command);
#endif /* DEVELOPER */ #endif /* DEVELOPER */
static size_t num_cmdlist; static size_t num_cmdlist;

10
lightningd/test/run-jsonrpc.c

@ -70,6 +70,11 @@ struct command_result *param_feerate_estimate(struct command *cmd UNNEEDED,
u32 **feerate_per_kw UNNEEDED, u32 **feerate_per_kw UNNEEDED,
enum feerate feerate UNNEEDED) enum feerate feerate UNNEEDED)
{ fprintf(stderr, "param_feerate_estimate called!\n"); abort(); } { fprintf(stderr, "param_feerate_estimate called!\n"); abort(); }
/* Generated stub for param_ignore */
struct command_result *param_ignore(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
const void *unused UNNEEDED)
{ fprintf(stderr, "param_ignore called!\n"); abort(); }
/* Generated stub for param_number */ /* Generated stub for param_number */
struct command_result *param_number(struct command *cmd UNNEEDED, const char *name UNNEEDED, struct command_result *param_number(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
@ -80,6 +85,11 @@ struct command_result *param_sha256(struct command *cmd UNNEEDED, const char *na
const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
struct sha256 **hash UNNEEDED) struct sha256 **hash UNNEEDED)
{ fprintf(stderr, "param_sha256 called!\n"); abort(); } { fprintf(stderr, "param_sha256 called!\n"); abort(); }
/* Generated stub for param_subcommand */
const char *param_subcommand(struct command *cmd UNNEEDED, const char *buffer UNNEEDED,
const jsmntok_t tokens[] UNNEEDED,
const char *name UNNEEDED, ...)
{ fprintf(stderr, "param_subcommand called!\n"); abort(); }
/* Generated stub for param_tok */ /* Generated stub for param_tok */
struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name UNNEEDED, struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name UNNEEDED,
const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED,

67
tests/test_misc.py

@ -506,14 +506,14 @@ def test_multiplexed_rpc(node_factory):
# Neighbouring ones may be in or out of order. # Neighbouring ones may be in or out of order.
commands = [ commands = [
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}', b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}', b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}', b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}', b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}', b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}', b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}', b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}',
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}' b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}'
] ]
sock.sendall(b'\n'.join(commands)) sock.sendall(b'\n'.join(commands))
@ -1260,3 +1260,54 @@ def test_bitcoind_fail_first(node_factory, bitcoind, executor):
l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None) l1.daemon.rpcproxy.mock_rpc('estimatesmartfee', None)
f.result() f.result()
@unittest.skipIf(not DEVELOPER, "needs dev command")
def test_dev_demux(node_factory):
l1 = node_factory.get_node(may_fail=True, allow_broken_log=True)
# Check should work.
l1.rpc.check(command_to_check='dev', subcommand='crash')
l1.rpc.check(command_to_check='dev', subcommand='slowcmd', msec=1000)
l1.rpc.check(command_to_check='dev', subcommand='rhash', secret='00' * 32)
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.check(command_to_check='dev', subcommand='foobar')
with pytest.raises(RpcError, match=r'unknown parameter'):
l1.rpc.check(command_to_check='dev', subcommand='crash', unk=1)
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.check(command_to_check='dev', subcommand='slowcmd', msec='aaa')
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.check(command_to_check='dev', subcommand='rhash')
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.check(command_to_check='dev')
# Non-check failures should fail, in both object and array form.
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.call('dev', {'subcommand': 'foobar'})
with pytest.raises(RpcError, match=r'Unknown subcommand'):
l1.rpc.call('dev', ['foobar'])
with pytest.raises(RpcError, match=r'unknown parameter'):
l1.rpc.call('dev', {'subcommand': 'crash', 'unk': 1})
with pytest.raises(RpcError, match=r'too many parameters'):
l1.rpc.call('dev', ['crash', 1])
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.call('dev', {'subcommand': 'slowcmd', 'msec': 'aaa'})
with pytest.raises(RpcError, match=r"'msec' should be an integer"):
l1.rpc.call('dev', ['slowcmd', 'aaa'])
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev', {'subcommand': 'rhash'})
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev', ['rhash'])
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('dev')
# Help should list them all.
assert 'subcommand=crash|rhash|slowcmd' in l1.rpc.help('dev')['help'][0]['command']
# These work
assert l1.rpc.call('dev', ['slowcmd', '7'])['msec'] == 7
assert l1.rpc.call('dev', {'subcommand': 'slowcmd', 'msec': '7'})['msec'] == 7
assert l1.rpc.call('dev', {'subcommand': 'rhash', 'secret': '00' * 32})['rhash'] == '66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925'
with pytest.raises(RpcError):
l1.rpc.call('dev', {'subcommand': 'crash'})

Loading…
Cancel
Save