mirror of
https://github.com/aljazceru/lightning.git
synced 2026-01-07 16:14:26 +01:00
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>
This commit is contained in:
@@ -78,6 +78,13 @@ struct command_result *param_string(struct command *cmd, const char *name,
|
||||
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,
|
||||
const char * buffer, const jsmntok_t *tok,
|
||||
struct json_escape **label)
|
||||
|
||||
@@ -85,4 +85,8 @@ struct command_result *param_tok(struct command *cmd, const char *name,
|
||||
const char *buffer, const jsmntok_t * tok,
|
||||
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 */
|
||||
|
||||
@@ -263,6 +263,50 @@ static struct command_result *param_arr(struct command *cmd, const char *buffer,
|
||||
"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(¶ms, "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,
|
||||
const jsmntok_t tokens[], ...)
|
||||
{
|
||||
|
||||
@@ -58,13 +58,25 @@ typedef struct command_result *(*param_cbx)(struct command *cmd,
|
||||
const jsmntok_t *tok,
|
||||
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.
|
||||
*/
|
||||
#define p_req(name, cbx, arg) \
|
||||
name"", \
|
||||
true, \
|
||||
(cbx), \
|
||||
(param_cbx)(cbx), \
|
||||
(arg) + 0*sizeof((cbx)((struct command *)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) \
|
||||
name"", \
|
||||
false, \
|
||||
(cbx), \
|
||||
(param_cbx)(cbx), \
|
||||
({ *arg = NULL; \
|
||||
(arg) + 0*sizeof((cbx)((struct command *)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) \
|
||||
name"", \
|
||||
false, \
|
||||
(cbx), \
|
||||
(param_cbx)(cbx), \
|
||||
({ (*arg) = tal((cmd), typeof(**arg)); \
|
||||
(**arg) = (def); \
|
||||
(arg) + 0*sizeof((cbx)((struct command *)NULL, \
|
||||
|
||||
@@ -269,49 +269,63 @@ static void slowcmd_start(struct slowcmd *sc)
|
||||
slowcmd_finish, sc);
|
||||
}
|
||||
|
||||
static struct command_result *json_slowcmd(struct command *cmd,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNUSED,
|
||||
const jsmntok_t *params)
|
||||
static struct command_result *json_dev(struct command *cmd UNUSED,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
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))
|
||||
subcmd = param_subcommand(cmd, buffer, params,
|
||||
"crash", "rhash", "slowcmd", NULL);
|
||||
if (!subcmd)
|
||||
return command_param_failed();
|
||||
|
||||
new_reltimer(cmd->ld->timers, sc, time_from_msec(0), slowcmd_start, sc);
|
||||
return command_still_pending(cmd);
|
||||
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_slowcmd_command = {
|
||||
"dev-slowcmd",
|
||||
static const struct json_command dev_command = {
|
||||
"dev",
|
||||
"developer",
|
||||
json_slowcmd,
|
||||
"Torture test for slow commands, optional {msec}"
|
||||
json_dev,
|
||||
"Developer command test multiplexer"
|
||||
};
|
||||
AUTODATA(json_command, &dev_slowcmd_command);
|
||||
|
||||
static struct command_result *json_crash(struct command *cmd UNUSED,
|
||||
const char *buffer,
|
||||
const jsmntok_t *obj UNNEEDED,
|
||||
const jsmntok_t *params)
|
||||
{
|
||||
if (!param(cmd, buffer, params, NULL))
|
||||
return command_param_failed();
|
||||
|
||||
fatal("Crash at user request");
|
||||
}
|
||||
|
||||
static const struct json_command dev_crash_command = {
|
||||
"dev-crash",
|
||||
"developer",
|
||||
json_crash,
|
||||
"Crash lightningd by calling fatal()"
|
||||
};
|
||||
AUTODATA(json_command, &dev_crash_command);
|
||||
AUTODATA(json_command, &dev_command);
|
||||
#endif /* DEVELOPER */
|
||||
|
||||
static size_t num_cmdlist;
|
||||
|
||||
@@ -70,6 +70,11 @@ struct command_result *param_feerate_estimate(struct command *cmd UNNEEDED,
|
||||
u32 **feerate_per_kw UNNEEDED,
|
||||
enum feerate feerate UNNEEDED)
|
||||
{ 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 */
|
||||
struct command_result *param_number(struct command *cmd UNNEEDED, const char *name 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,
|
||||
struct sha256 **hash UNNEEDED)
|
||||
{ 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 */
|
||||
struct command_result *param_tok(struct command *cmd UNNEEDED, const char *name UNNEEDED,
|
||||
const char *buffer UNNEEDED, const jsmntok_t * tok UNNEEDED,
|
||||
|
||||
@@ -506,14 +506,14 @@ def test_multiplexed_rpc(node_factory):
|
||||
|
||||
# Neighbouring ones may be in or out of order.
|
||||
commands = [
|
||||
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}',
|
||||
b'{"id":1,"jsonrpc":"2.0","method":"dev-slowcmd","params":[2000]}',
|
||||
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}',
|
||||
b'{"id":2,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1500]}',
|
||||
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}',
|
||||
b'{"id":3,"jsonrpc":"2.0","method":"dev-slowcmd","params":[1000]}',
|
||||
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}',
|
||||
b'{"id":4,"jsonrpc":"2.0","method":"dev-slowcmd","params":[500]}'
|
||||
b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
|
||||
b'{"id":1,"jsonrpc":"2.0","method":"dev","params":["slowcmd",2000]}',
|
||||
b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
|
||||
b'{"id":2,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1500]}',
|
||||
b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
|
||||
b'{"id":3,"jsonrpc":"2.0","method":"dev","params":["slowcmd",1000]}',
|
||||
b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}',
|
||||
b'{"id":4,"jsonrpc":"2.0","method":"dev","params":["slowcmd",500]}'
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
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'})
|
||||
|
||||
Reference in New Issue
Block a user