From 190b30e9584481794164d4134479a4da3e0b0fc6 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 18 Aug 2016 14:25:14 +0930 Subject: [PATCH] daemon: test restarting. We add a "dev-restart" command which causes the daemon to close fds and exec itself. Then we do it after every command, with the caveat that we always send a commit before newhtlc, because if not committed, that is forgotten. Fulfillhtlc and failhtlc get resent, since they're idempotent. Signed-off-by: Rusty Russell --- Makefile | 8 +++++- daemon/controlled_time.c | 18 +++++++++++++ daemon/controlled_time.h | 3 +++ daemon/jsonrpc.c | 37 ++++++++++++++++++++++++++ daemon/lightningd.c | 39 ++++++++++++++++++++++++++- daemon/lightningd.h | 3 +++ daemon/opt_time.c | 19 ++++++++++++++ daemon/opt_time.h | 3 +++ daemon/test/test.sh | 57 +++++++++++++++++++++++++++++++++++----- 9 files changed, 178 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 287472ee0..5809bb1f7 100644 --- a/Makefile +++ b/Makefile @@ -205,7 +205,13 @@ daemon-test-timeout-anchor\ --reconnect: daemon-test-different-fee-rates\ --reco daemon-test-different-fee-rates\ --reconnect: daemon-test-normal\ --reconnect daemon-test-normal\ --reconnect: daemon-test-manual-commit\ --reconnect daemon-test-manual-commit\ --reconnect: daemon-test-mutual-close-with-htlcs\ --reconnect -daemon-test-mutual-close-with-htlcs\ --reconnect: daemon-all +daemon-test-mutual-close-with-htlcs\ --reconnect: daemon-test-steal\ --restart +daemon-test-steal\ --restart: daemon-test-dump-onchain\ --restart +daemon-test-dump-onchain\ --restart: daemon-test-timeout-anchor\ --restart +daemon-test-timeout-anchor\ --restart: daemon-test-different-fee-rates\ --restart +daemon-test-different-fee-rates\ --restart: daemon-test-normal\ --restart +daemon-test-normal\ --restart: daemon-test-mutual-close-with-htlcs\ --restart +daemon-test-mutual-close-with-htlcs\ --restart: daemon-all daemon-tests: daemon-test-steal test-onion: test/test_onion test/onion_key diff --git a/daemon/controlled_time.c b/daemon/controlled_time.c index fbd8d4e0c..d95556552 100644 --- a/daemon/controlled_time.c +++ b/daemon/controlled_time.c @@ -2,6 +2,8 @@ #include "jsonrpc.h" #include "lightningd.h" #include "log.h" +#include "opt_time.h" +#include #include #include @@ -14,6 +16,22 @@ struct timeabs controlled_time(void) return time_now(); } +void controlled_time_register_opts(void) +{ + opt_register_arg("--mocktime", opt_set_timeabs, opt_show_timeabs, + &mock_time, opt_hidden); +} + +char *controlled_time_arg(const tal_t *ctx) +{ + char buf[sizeof("--mocktime=") + OPT_SHOW_LEN] = "--mocktime="; + if (!mock_time.ts.tv_sec) + return NULL; + + opt_show_timeabs(buf + strlen(buf), &mock_time); + return tal_strdup(ctx, buf); +} + static void json_mocktime(struct command *cmd, const char *buffer, const jsmntok_t *params) { diff --git a/daemon/controlled_time.h b/daemon/controlled_time.h index 81105bc9e..2c74dd5c2 100644 --- a/daemon/controlled_time.h +++ b/daemon/controlled_time.h @@ -2,8 +2,11 @@ #define LIGHTNING_DAEMON_CONTROLLED_TIME_H #include "config.h" #include +#include #include struct timeabs controlled_time(void); +void controlled_time_register_opts(void); +char *controlled_time_arg(const tal_t *ctx); #endif /* LIGHTNING_DAEMON_CONTROLLED_TIME_H */ diff --git a/daemon/jsonrpc.c b/daemon/jsonrpc.c index ebed500ee..de5495b44 100644 --- a/daemon/jsonrpc.c +++ b/daemon/jsonrpc.c @@ -1,9 +1,11 @@ /* Code for JSON_RPC API */ /* eg: { "method" : "dev-echo", "params" : [ "hello", "Arabella!" ], "id" : "1" } */ +#include "controlled_time.h" #include "json.h" #include "jsonrpc.h" #include "lightningd.h" #include "log.h" +#include "peer.h" #include #include #include @@ -243,6 +245,37 @@ static const struct json_command crash_command = { "Simple crash test for developers" }; +static void json_restart(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + const jsmntok_t *p, *end; + size_t n = 0; + + if (params->type != JSMN_ARRAY) { + command_fail(cmd, "Need array to reexec"); + return; + } + end = json_next(params); + + cmd->dstate->reexec = tal_arrz(cmd->dstate, char *, n+1); + for (p = params + 1; p != end; p = json_next(p)) { + tal_resizez(&cmd->dstate->reexec, n+2); + cmd->dstate->reexec[n++] = tal_strndup(cmd->dstate->reexec, + buffer + p->start, + p->end - p->start); + } + debug_dump_peers(cmd->dstate); + io_break(cmd->dstate); + command_success(cmd, null_response(cmd)); +} + +static const struct json_command restart_command = { + "dev-restart", + json_restart, + "Re-exec the given {binary}.", + "Simple restart test for developers" +}; + static const struct json_command *cmdlist[] = { &help_command, &stop_command, @@ -262,6 +295,7 @@ static const struct json_command *cmdlist[] = { &rhash_command, &mocktime_command, &crash_command, + &restart_command, &disconnect_command, &reconnect_command, &signcommit_command, @@ -340,6 +374,7 @@ void command_success(struct command *cmd, struct json_result *result) } assert(jcon->current == cmd); json_result(jcon, cmd->id, json_result_string(result), "null"); + log_debug(jcon->log, "Success"); jcon->current = tal_free(cmd); } @@ -360,6 +395,8 @@ void command_fail(struct command *cmd, const char *fmt, ...) error = tal_vfmt(cmd, fmt, ap); va_end(ap); + log_debug(jcon->log, "Failing: %s", error); + /* Remove " */ while ((quote = strchr(error, '"')) != NULL) *quote = '\''; diff --git a/daemon/lightningd.c b/daemon/lightningd.c index 2cfc8b2a0..0baff81c2 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -141,9 +141,15 @@ static void config_register_opts(struct lightningd_state *dstate) opt_register_arg("--fee-per-satoshi", opt_set_s32, opt_show_s32, &dstate->config.fee_per_satoshi, "Microsatoshi fee for every satoshi in HTLC"); - } +static void dev_register_opts(struct lightningd_state *dstate) +{ + controlled_time_register_opts(); + opt_register_noarg("--dev-no-routefail", opt_set_bool, + &dstate->dev_never_routefail, opt_hidden); +} + static void default_config(struct config *config) { /* aka. "Dude, where's my coins?" */ @@ -248,6 +254,7 @@ static struct lightningd_state *lightningd_state(void) dstate->dev_never_routefail = false; dstate->bitcoin_req_running = false; dstate->nodes = empty_node_map(dstate); + dstate->reexec = NULL; return dstate; } @@ -297,6 +304,7 @@ int main(int argc, char *argv[]) configdir_register_opts(dstate, &dstate->config_dir, &dstate->rpc_filename); config_register_opts(dstate); + dev_register_opts(dstate); /* Get any configdir options first. */ opt_early_parse(argc, argv, opt_log_stderr_exit); @@ -365,6 +373,35 @@ int main(int argc, char *argv[]) cleanup_peers(dstate); } + if (dstate->reexec) { + int fd; + char *mocktimearg; + + log_unusual(dstate->base_log, "Restart at user request"); + fflush(stdout); + fflush(stderr); + + /* Manually close all fds (or near enough!) */ + for (fd = 3; fd < 1024; fd++) + close(fd); + + /* Maybe append mocktime arg. */ + mocktimearg = controlled_time_arg(dstate->reexec); + if (mocktimearg) { + size_t n = tal_count(dstate->reexec); + tal_resizez(&dstate->reexec, n+1); + dstate->reexec[n-1] = mocktimearg; + } + if (dstate->dev_never_routefail) { + size_t n = tal_count(dstate->reexec); + tal_resizez(&dstate->reexec, n+1); + dstate->reexec[n-1] = "--dev-no-routefail"; + } + execvp(dstate->reexec[0], dstate->reexec); + fatal("Exec '%s' failed: %s", + dstate->reexec[0], strerror(errno)); + } + tal_free(dstate); opt_free_table(); return 0; diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 672fd168a..aceb8c095 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -112,5 +112,8 @@ struct lightningd_state { /* For testing: don't fail if we can't route. */ bool dev_never_routefail; + + /* Re-exec hack for testing. */ + char **reexec; }; #endif /* LIGHTNING_DAEMON_LIGHTNING_H */ diff --git a/daemon/opt_time.c b/daemon/opt_time.c index db958d504..0f7265471 100644 --- a/daemon/opt_time.c +++ b/daemon/opt_time.c @@ -77,3 +77,22 @@ void opt_show_time(char buf[OPT_SHOW_LEN], const struct timerel *t) } else sprintf(buf, "%lus", t->ts.tv_sec); } + +char *opt_set_timeabs(const char *arg, struct timeabs *t) +{ + long double d; + + if (sscanf(arg, "%Lf", &d) != 1) + return tal_fmt(NULL, "'%s' is not a time", arg); + t->ts.tv_sec = d; + t->ts.tv_nsec = (d - t->ts.tv_sec) * 1000000000; + return NULL; +} + +void opt_show_timeabs(char buf[OPT_SHOW_LEN], const struct timeabs *t) +{ + long double d = t->ts.tv_sec; + d = d * 1000000000 + t->ts.tv_nsec; + + sprintf(buf, "%.9Lf", d); +} diff --git a/daemon/opt_time.h b/daemon/opt_time.h index 9b5f8091e..55bd4ba78 100644 --- a/daemon/opt_time.h +++ b/daemon/opt_time.h @@ -7,4 +7,7 @@ char *opt_set_time(const char *arg, struct timerel *t); void opt_show_time(char buf[OPT_SHOW_LEN], const struct timerel *t); +char *opt_set_timeabs(const char *arg, struct timeabs *t); +void opt_show_timeabs(char buf[OPT_SHOW_LEN], const struct timeabs *t); + #endif /* LIGHTNING_DAEMON_OPT_TIME_H */ diff --git a/daemon/test/test.sh b/daemon/test/test.sh index 04e981621..7279d81d1 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -72,7 +72,10 @@ while [ $# != 0 ]; do x"--normal") ;; x"--reconnect") - RECONNECT=1 + RECONNECT=reconnect + ;; + x"--restart") + RECONNECT=restart ;; x"--crash") CRASH_ON_FAIL=1 @@ -93,9 +96,11 @@ LCLI3="../lightning-cli --lightning-dir=$DIR3" if [ -n "$VERBOSE" ]; then FGREP="fgrep" + SHOW="cat >&2" else # Suppress command output. exec >/dev/null + SHOW="cat" fi lcli1() @@ -112,12 +117,45 @@ lcli1() ;; dev-mocktime*) ;; + dev-disconnect) + ;; stop) ;; *) - [ -z "$VERBOSE" ] || echo RECONNECTING >&2 - $LCLI1 dev-reconnect $ID2 >/dev/null - sleep 1 + case "$RECONNECT" in + reconnect) + [ -z "$VERBOSE" ] || echo RECONNECTING >&2 + $LCLI1 dev-reconnect $ID2 >/dev/null + sleep 1 + ;; + restart) + [ -z "$VERBOSE" ] || echo RESTARTING >&2 + # FIXME: Instead, check if command was committed, and + # if not, resubmit! + if [ "$1" = "newhtlc" ]; then + $LCLI1 commit $ID2 >/dev/null 2>&1 || true + fi + $LCLI1 -- dev-restart $LIGHTNINGD1 >/dev/null 2>&1 || true + if ! check "$LCLI1 getlog 2>/dev/null | fgrep -q Hello"; then + echo "dev-restart failed!">&2 + exit 1 + fi + # It will have forgotten any added routes. + if [ -n "$ADDROUTE" ]; then + echo $LCLI1 $ADDROUTE >&2 + $LCLI1 $ADDROUTE >&2 + fi + # These are safe to resubmit, will simply fail. + if [ "$1" = "fulfillhtlc" -o "$1" = "failhtlc" ]; then + if [ -z "$VERBOSE" ]; then + $LCLI1 "$@" >/dev/null 2>&1 || true + else + echo "Rerunning $LCLI1 $@" >&2 + $LCLI1 "$@" 2>&1 || true + fi + fi + ;; + esac ;; esac fi @@ -324,11 +362,15 @@ if [ -n "$DIFFERENT_FEES" ]; then echo "default-fee-rate=$CLOSE_FEE_RATE2" >> $DIR2/config fi +# Need absolute path for re-exec testing. +LIGHTNINGD1="$(readlink -f `pwd`/../lightningd) --lightning-dir=$DIR1" if [ -n "$GDB1" ]; then - echo Press return once you run: gdb --args daemon/lightningd --lightning-dir=$DIR1 >&2 + echo Press return once you run: gdb --args $LIGHTNINGD1 >&2 + read REPLY else - $PREFIX ../lightningd --lightning-dir=$DIR1 > $REDIR1 2> $REDIRERR1 & + LIGHTNINGD1="$PREFIX $LIGHTNINGD1" + $LIGHTNINGD1 > $REDIR1 2> $REDIRERR1 & fi if [ -n "$GDB2" ]; then @@ -869,7 +911,8 @@ if [ ! -n "$MANUALCOMMIT" ]; then HTLC_AMOUNT=100000000 # Tell node 1 about the 2->3 route. - lcli1 add-route $ID2 $ID3 546000 10 36 36 + ADDROUTE="add-route $ID2 $ID3 546000 10 36 36" + lcli1 $ADDROUTE RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` # Try wrong hash.