diff --git a/daemon/Makefile b/daemon/Makefile index 6cd22ed4d..a3fa0ffad 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -15,6 +15,7 @@ DAEMON_LIB_OBJS := $(DAEMON_LIB_SRC:.c=.o) DAEMON_SRC := \ daemon/bitcoind.c \ + daemon/controlled_time.c \ daemon/cryptopkt.c \ daemon/dns.c \ daemon/jsonrpc.c \ @@ -39,6 +40,7 @@ DAEMON_JSMN_HEADERS := daemon/jsmn/jsmn.h DAEMON_HEADERS := \ daemon/bitcoind.h \ daemon/configdir.h \ + daemon/controlled_time.h \ daemon/cryptopkt.h \ daemon/dns.h \ daemon/json.h \ diff --git a/daemon/controlled_time.c b/daemon/controlled_time.c new file mode 100644 index 000000000..1d8084cda --- /dev/null +++ b/daemon/controlled_time.c @@ -0,0 +1,52 @@ +#include "controlled_time.h" +#include "jsonrpc.h" +#include "lightningd.h" +#include "log.h" +#include +#include + +static struct timeabs mock_time; + +struct timeabs controlled_time(void) +{ + if (mock_time.ts.tv_sec) + return mock_time; + return time_now(); +} + +static void json_mocktime(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct json_result *response = new_json_result(cmd); + jsmntok_t *mocktimetok; + u64 prev_time, mocktime; + char mocktimestr[STR_MAX_CHARS(int64_t)]; + + json_get_params(buffer, params, + "mocktime", &mocktimetok, + NULL); + + prev_time = controlled_time().ts.tv_sec; + if (!mocktimetok || !json_tok_u64(buffer, mocktimetok, &mocktime)) { + command_fail(cmd, "Need valid mocktime"); + return; + } + mock_time.ts.tv_sec = mocktime; + + json_object_start(response, NULL); + sprintf(mocktimestr, "%"PRIi64, + (s64)controlled_time().ts.tv_sec - prev_time); + json_add_string(response, "offset", mocktimestr); + json_object_end(response); + + log_unusual(cmd->dstate->base_log, + "mocktime set to %"PRIu64, (u64)mock_time.ts.tv_sec); + command_success(cmd, response); +} + +const struct json_command mocktime_command = { + "dev-mocktime", + json_mocktime, + "Set current time to {mocktime} seconds (0 to return to normal)", + "Returns the offset on success" +}; diff --git a/daemon/controlled_time.h b/daemon/controlled_time.h new file mode 100644 index 000000000..81105bc9e --- /dev/null +++ b/daemon/controlled_time.h @@ -0,0 +1,9 @@ +#ifndef LIGHTNING_DAEMON_CONTROLLED_TIME_H +#define LIGHTNING_DAEMON_CONTROLLED_TIME_H +#include "config.h" +#include +#include + +struct timeabs controlled_time(void); + +#endif /* LIGHTNING_DAEMON_CONTROLLED_TIME_H */ diff --git a/daemon/jsonrpc.c b/daemon/jsonrpc.c index e5dcbd259..bca0b1482 100644 --- a/daemon/jsonrpc.c +++ b/daemon/jsonrpc.c @@ -244,6 +244,7 @@ static const struct json_command *cmdlist[] = { /* Developer/debugging options. */ &echo_command, &rhash_command, + &mocktime_command }; static void json_help(struct command *cmd, diff --git a/daemon/jsonrpc.h b/daemon/jsonrpc.h index 3b7da4e72..d1518ddba 100644 --- a/daemon/jsonrpc.h +++ b/daemon/jsonrpc.h @@ -61,4 +61,5 @@ extern const struct json_command getpeers_command; extern const struct json_command newhtlc_command; extern const struct json_command fulfillhtlc_command; extern const struct json_command failhtlc_command; +extern const struct json_command mocktime_command; #endif /* LIGHTNING_DAEMON_JSONRPC_H */ diff --git a/daemon/lightning-cli.c b/daemon/lightning-cli.c index 6d35f0a5f..4c236920e 100644 --- a/daemon/lightning-cli.c +++ b/daemon/lightning-cli.c @@ -2,6 +2,7 @@ * Helper to submit via JSON-RPC and get back response. */ #include "configdir.h" +#include "controlled_time.h" #include "json.h" #include "version.h" #include @@ -39,6 +40,11 @@ static void tal_freefn(void *ptr) tal_free(ptr); } +struct timeabs controlled_time(void) +{ + return time_now(); +} + int main(int argc, char *argv[]) { int fd, i, off; diff --git a/daemon/lightningd.c b/daemon/lightningd.c index bce8c6768..856314126 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -1,5 +1,6 @@ #include "bitcoind.h" #include "configdir.h" +#include "controlled_time.h" #include "jsonrpc.h" #include "lightningd.h" #include "log.h" @@ -147,7 +148,7 @@ static struct lightningd_state *lightningd_state(void) "lightningd(%u):", (int)getpid()); list_head_init(&dstate->peers); - timers_init(&dstate->timers, time_now()); + timers_init(&dstate->timers, controlled_time()); txwatch_hash_init(&dstate->txwatches); txowatch_hash_init(&dstate->txowatches); dstate->secpctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY @@ -240,6 +241,9 @@ int main(int argc, char *argv[]) /* Create timer to do watches. */ setup_watch_timer(dstate); + /* Make sure we use the artificially-controlled time for timers */ + io_time_override(controlled_time); + log_info(dstate->base_log, "Hello world!"); /* If io_loop returns NULL, either a timer expired, or all fds closed */ diff --git a/daemon/log.c b/daemon/log.c index 1e69f8133..e39f42144 100644 --- a/daemon/log.c +++ b/daemon/log.c @@ -1,3 +1,4 @@ +#include "controlled_time.h" #include "log.h" #include "pseudorand.h" #include @@ -102,7 +103,7 @@ struct log_record *new_log_record(const tal_t *ctx, lr->max_mem = max_mem; lr->print = log_default_print; lr->print_level = printlevel; - lr->init_time = time_now(); + lr->init_time = controlled_time(); list_head_init(&lr->log); return lr; @@ -183,7 +184,7 @@ static struct log_entry *new_log_entry(struct log *log, enum log_level level) { struct log_entry *l = tal(log->lr, struct log_entry); - l->time = time_now(); + l->time = controlled_time(); l->level = level; l->skipped = 0; l->prefix = log->prefix; diff --git a/daemon/timeout.c b/daemon/timeout.c index fcc408e8e..81f179150 100644 --- a/daemon/timeout.c +++ b/daemon/timeout.c @@ -1,3 +1,4 @@ +#include "controlled_time.h" #include "lightningd.h" #include "timeout.h" @@ -14,7 +15,7 @@ void refresh_timeout(struct lightningd_state *dstate, struct timeout *t) { timer_del(&dstate->timers, &t->timer); timer_add(&dstate->timers, &t->timer, - timeabs_add(time_now(), t->interval)); + timeabs_add(controlled_time(), t->interval)); } /* FIXME: Make all timers one-shot! */