From 04a07fd90ead26eb822feddbe917479588b26cb8 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 5 Sep 2016 13:29:48 +0930 Subject: [PATCH] db: save and restore "sendpay" commands. Signed-off-by: Rusty Russell --- daemon/db.c | 183 ++++++++++++++++++++++++++++++++++++++++++++ daemon/db.h | 12 +++ daemon/pay.c | 103 +++++++++++++++++++------ daemon/pay.h | 8 ++ daemon/test/test.sh | 5 -- 5 files changed, 283 insertions(+), 28 deletions(-) diff --git a/daemon/db.c b/daemon/db.c index 03dafa2ea..8188ea82b 100644 --- a/daemon/db.c +++ b/daemon/db.c @@ -7,6 +7,7 @@ #include "log.h" #include "names.h" #include "netaddr.h" +#include "pay.h" #include "routing.h" #include "secrets.h" #include "utils.h" @@ -953,6 +954,109 @@ static void db_load_peers(struct lightningd_state *dstate) connect_htlc_src(dstate); } + +static const char *pubkeys_to_hex(const tal_t *ctx, + secp256k1_context *secpctx, + const struct pubkey *ids) +{ + u8 *ders = tal_arr(ctx, u8, PUBKEY_DER_LEN * tal_count(ids)); + size_t i; + + for (i = 0; i < tal_count(ids); i++) + pubkey_to_der(secpctx, ders + i * PUBKEY_DER_LEN, &ids[i]); + + return tal_hexstr(ctx, ders, tal_count(ders)); +} +static struct pubkey *pubkeys_from_arr(const tal_t *ctx, + secp256k1_context *secpctx, + const void *blob, size_t len) +{ + struct pubkey *ids; + size_t i; + + if (len % PUBKEY_DER_LEN) + fatal("ids array bad length %zu", len); + + ids = tal_arr(ctx, struct pubkey, len / PUBKEY_DER_LEN); + for (i = 0; i < tal_count(ids); i++) { + if (!pubkey_from_der(secpctx, blob, PUBKEY_DER_LEN, &ids[i])) + fatal("ids array invalid %zu", i); + blob = (const u8 *)blob + PUBKEY_DER_LEN; + } + return ids; +} + +static void db_load_pay(struct lightningd_state *dstate) +{ + int err; + sqlite3_stmt *stmt; + char *ctx = tal(dstate, char); + + err = sqlite3_prepare_v2(dstate->db->sql, "SELECT * FROM pay;", -1, + &stmt, NULL); + + if (err != SQLITE_OK) + fatal("db_load_pay:prepare gave %s:%s", + sqlite3_errstr(err), sqlite3_errmsg(dstate->db->sql)); + + /* CREATE TABLE pay (rhash "SQL_RHASH", msatoshis INT, ids BLOB, htlc_peer "SQL_PUBKEY", htlc_id INT, r "SQL_R", fail BLOB, PRIMARY KEY(rhash)); */ + while ((err = sqlite3_step(stmt)) != SQLITE_DONE) { + struct sha256 rhash; + struct htlc *htlc; + struct pubkey *peer_id; + u64 htlc_id, msatoshis; + struct pubkey *ids; + struct rval *r; + void *fail; + + if (err != SQLITE_ROW) + fatal("db_load_pay:step gave %s:%s", + sqlite3_errstr(err), + sqlite3_errmsg(dstate->db->sql)); + if (sqlite3_column_count(stmt) != 7) + fatal("db_load_pay:step gave %i cols, not 7", + sqlite3_column_count(stmt)); + + sha256_from_sql(stmt, 0, &rhash); + msatoshis = sqlite3_column_int64(stmt, 1); + ids = pubkeys_from_arr(ctx, dstate->secpctx, + sqlite3_column_blob(stmt, 2), + sqlite3_column_bytes(stmt, 2)); + if (sqlite3_column_type(stmt, 3) == SQLITE_NULL) + peer_id = NULL; + else { + peer_id = tal(ctx, struct pubkey); + pubkey_from_sql(dstate->secpctx, stmt, 3, peer_id); + } + htlc_id = sqlite3_column_int64(stmt, 4); + if (sqlite3_column_type(stmt, 5) == SQLITE_NULL) + r = NULL; + else { + r = tal(ctx, struct rval); + from_sql_blob(stmt, 5, r, sizeof(*r)); + } + fail = tal_sql_blob(ctx, stmt, 6); + /* Exactly one of these must be set. */ + if (!fail + !peer_id + !r != 2) + fatal("db_load_pay: not exactly one set:" + " fail=%p peer_id=%p r=%p", + fail, peer_id, r); + if (peer_id) { + struct peer *peer = find_peer(dstate, peer_id); + if (!peer) + fatal("db_load_pay: unknown peer"); + htlc = htlc_get(&peer->htlcs, htlc_id, LOCAL); + if (!htlc) + fatal("db_load_pay: unknown htlc"); + } else + htlc = NULL; + + if (!pay_add(dstate, &rhash, msatoshis, ids, htlc, fail, r)) + fatal("db_load_pay: could not add pay"); + } + tal_free(ctx); +} + static void db_load_addresses(struct lightningd_state *dstate) { int err; @@ -993,6 +1097,7 @@ static void db_load(struct lightningd_state *dstate) db_load_wallet(dstate); db_load_addresses(dstate); db_load_peers(dstate); + db_load_pay(dstate); } void db_init(struct lightningd_state *dstate) @@ -1033,6 +1138,7 @@ void db_init(struct lightningd_state *dstate) /* Set up tables. */ errmsg = db_exec(dstate, dstate, "CREATE TABLE wallet (privkey "SQL_PRIVKEY");" + "CREATE TABLE pay (rhash "SQL_RHASH", msatoshis INT, ids BLOB, htlc_peer "SQL_PUBKEY", htlc_id INT, r "SQL_R", fail BLOB, PRIMARY KEY(rhash));" "CREATE TABLE anchors (peer "SQL_PUBKEY", txid "SQL_TXID", idx INT, amount INT, ok_depth INT, min_depth INT, bool ours, PRIMARY KEY(peer));" /* FIXME: state in primary key is overkill: just need side */ "CREATE TABLE htlcs (peer "SQL_PUBKEY", id INT, state TEXT, msatoshis INT, expiry INT, rhash "SQL_RHASH", r "SQL_R", routing "SQL_ROUTING", src_peer "SQL_PUBKEY", src_id INT, fail BLOB, PRIMARY KEY(peer, id, state));" @@ -1654,3 +1760,80 @@ bool db_update_their_closing(struct peer *peer) tal_free(ctx); return !errmsg; } + +bool db_new_pay_command(struct lightningd_state *dstate, + const struct sha256 *rhash, + const struct pubkey *ids, + u64 msatoshis, + const struct htlc *htlc) +{ + const char *errmsg, *ctx = tal(dstate, char); + + log_debug(dstate->base_log, "%s", __func__); + log_add_struct(dstate->base_log, "(%s)", struct sha256, rhash); + + assert(!dstate->db->in_transaction); + /* CREATE TABLE pay (rhash "SQL_RHASH", msatoshis INT, ids BLOB, htlc_peer "SQL_PUBKEY", htlc_id INT, r "SQL_R", fail BLOB, PRIMARY KEY(rhash)); */ + errmsg = db_exec(ctx, dstate, "INSERT INTO pay VALUES (x'%s', %"PRIu64", x'%s', x'%s', %"PRIu64", NULL, NULL);", + tal_hexstr(ctx, rhash, sizeof(*rhash)), + msatoshis, + pubkeys_to_hex(ctx, dstate->secpctx, ids), + pubkey_to_hexstr(ctx, dstate->secpctx, htlc->peer->id), + htlc->id); + if (errmsg) + log_broken(dstate->base_log, "%s:%s", __func__, errmsg); + tal_free(ctx); + return !errmsg; +} + +bool db_replace_pay_command(struct lightningd_state *dstate, + const struct sha256 *rhash, + const struct pubkey *ids, + u64 msatoshis, + const struct htlc *htlc) +{ + const char *errmsg, *ctx = tal(dstate, char); + + log_debug(dstate->base_log, "%s", __func__); + log_add_struct(dstate->base_log, "(%s)", struct sha256, rhash); + + assert(!dstate->db->in_transaction); + /* CREATE TABLE pay (rhash "SQL_RHASH", msatoshis INT, ids BLOB, htlc_peer "SQL_PUBKEY", htlc_id INT, r "SQL_R", fail BLOB, PRIMARY KEY(rhash)); */ + errmsg = db_exec(ctx, dstate, "UPDATE pay SET msatoshis=%"PRIu64", ids=x'%s', htlc_peer=x'%s', htlc_id=%"PRIu64", r=NULL, fail=NULL WHERE rhash=x'%s';", + msatoshis, + pubkeys_to_hex(ctx, dstate->secpctx, ids), + pubkey_to_hexstr(ctx, dstate->secpctx, htlc->peer->id), + htlc->id, + tal_hexstr(ctx, rhash, sizeof(*rhash))); + if (errmsg) + log_broken(dstate->base_log, "%s:%s", __func__, errmsg); + tal_free(ctx); + return !errmsg; +} + +bool db_complete_pay_command(struct lightningd_state *dstate, + const struct htlc *htlc) +{ + const char *errmsg, *ctx = tal(dstate, char); + + log_debug(dstate->base_log, "%s", __func__); + log_add_struct(dstate->base_log, "(%s)", struct sha256, &htlc->rhash); + + assert(dstate->db->in_transaction); + /* CREATE TABLE pay (rhash "SQL_RHASH", msatoshis INT, ids BLOB, htlc_peer "SQL_PUBKEY", htlc_id INT, r "SQL_R", fail BLOB, PRIMARY KEY(rhash)); */ + if (htlc->r) + errmsg = db_exec(ctx, dstate, + "UPDATE pay SET r=x'%s', htlc_peer=NULL WHERE rhash=x'%s';", + tal_hexstr(ctx, htlc->r, sizeof(*htlc->r)), + tal_hexstr(ctx, &htlc->rhash, sizeof(htlc->rhash))); + else + errmsg = db_exec(ctx, dstate, + "UPDATE pay SET fail=x'%s', htlc_peer=NULL WHERE rhash=x'%s';", + tal_hexstr(ctx, htlc->fail, tal_count(htlc->fail)), + tal_hexstr(ctx, &htlc->rhash, sizeof(htlc->rhash))); + + if (errmsg) + log_broken(dstate->base_log, "%s:%s", __func__, errmsg); + tal_free(ctx); + return !errmsg; +} diff --git a/daemon/db.h b/daemon/db.h index 4be6ba57c..45ad366ba 100644 --- a/daemon/db.h +++ b/daemon/db.h @@ -27,6 +27,16 @@ bool db_set_our_closing_script(struct peer *peer); bool db_set_their_closing_script(struct peer *peer); bool db_update_our_closing(struct peer *peer); bool db_update_their_closing(struct peer *peer); +bool db_new_pay_command(struct lightningd_state *dstate, + const struct sha256 *rhash, + const struct pubkey *ids, + u64 msatoshis, + const struct htlc *htlc); +bool db_replace_pay_command(struct lightningd_state *dstate, + const struct sha256 *rhash, + const struct pubkey *ids, + u64 msatoshis, + const struct htlc *htlc); /* FIXME: save error handling until db_commit_transaction for calls * which have to be inside transaction anyway. */ @@ -36,6 +46,8 @@ bool db_new_htlc(struct peer *peer, const struct htlc *htlc); bool db_new_feechange(struct peer *peer, const struct feechange *feechange); bool db_update_htlc_state(struct peer *peer, const struct htlc *htlc, enum htlc_state oldstate); +bool db_complete_pay_command(struct lightningd_state *state, + const struct htlc *htlc); bool db_update_feechange_state(struct peer *peer, const struct feechange *f, enum htlc_state oldstate); diff --git a/daemon/pay.c b/daemon/pay.c index b9d4c2dd3..f87fe7b03 100644 --- a/daemon/pay.c +++ b/daemon/pay.c @@ -1,4 +1,5 @@ #include "chaintopology.h" +#include "db.h" #include "failure.h" #include "jsonrpc.h" #include "lightningd.h" @@ -15,15 +16,14 @@ struct pay_command { struct list_node list; struct sha256 rhash; - u64 msatoshis; - struct pubkey *ids; + u64 msatoshi; + const struct pubkey *ids; /* Set if this is in progress. */ struct htlc *htlc; /* Preimage if this succeeded. */ - struct rval *rval; + const struct rval *rval; struct command *cmd; }; - static void json_pay_success(struct command *cmd, const struct rval *rval) { struct json_result *response; @@ -101,6 +101,9 @@ void complete_pay_command(struct lightningd_state *dstate, list_for_each(&dstate->pay_commands, i, list) { if (i->htlc == htlc) { FailInfo *f = NULL; + + db_complete_pay_command(dstate, htlc); + if (htlc->r) i->rval = tal_dup(i, struct rval, htlc->r); else { @@ -119,7 +122,7 @@ void complete_pay_command(struct lightningd_state *dstate, } } - /* Can happen if RPC connection goes away. */ + /* Can happen with testing low-level commands. */ log_unusual(dstate->base_log, "No command for HTLC %"PRIu64" %s", htlc->id, htlc->r ? "fulfill" : "fail"); } @@ -151,6 +154,35 @@ static struct pay_command *find_pay_command(struct lightningd_state *dstate, return NULL; } +/* For database restore. */ +bool pay_add(struct lightningd_state *dstate, + const struct sha256 *rhash, + u64 msatoshi, + const struct pubkey *ids, + struct htlc *htlc, + const u8 *fail UNNEEDED, + const struct rval *r) +{ + struct pay_command *pc; + + if (find_pay_command(dstate, rhash)) + return false; + + pc = tal(dstate, struct pay_command); + pc->rhash = *rhash; + pc->msatoshi = msatoshi; + pc->ids = tal_dup_arr(pc, struct pubkey, ids, tal_count(ids), 0); + pc->htlc = htlc; + if (r) + pc->rval = tal_dup(pc, struct rval, r); + else + pc->rval = NULL; + pc->cmd = NULL; + + list_add_tail(&dstate->pay_commands, &pc->list); + return true; +} + static void json_add_route(struct json_result *response, secp256k1_context *secpctx, const struct pubkey *id, @@ -158,7 +190,7 @@ static void json_add_route(struct json_result *response, { json_object_start(response, NULL); json_add_pubkey(response, secpctx, "id", id); - json_add_u64(response, "msatoshis", amount); + json_add_u64(response, "msatoshi", amount); json_add_num(response, "delay", delay); json_object_end(response); } @@ -167,10 +199,10 @@ static void json_getroute(struct command *cmd, const char *buffer, const jsmntok_t *params) { struct pubkey id; - jsmntok_t *idtok, *msatoshistok; + jsmntok_t *idtok, *msatoshitok; struct json_result *response; int i; - u64 msatoshis; + u64 msatoshi; s64 fee; struct node_connection **route; struct peer *peer; @@ -179,9 +211,9 @@ static void json_getroute(struct command *cmd, if (!json_get_params(buffer, params, "id", &idtok, - "msatoshis", &msatoshistok, + "msatoshi", &msatoshitok, NULL)) { - command_fail(cmd, "Need id and msatoshis"); + command_fail(cmd, "Need id and msatoshi"); return; } @@ -192,14 +224,14 @@ static void json_getroute(struct command *cmd, return; } - if (!json_tok_u64(buffer, msatoshistok, &msatoshis)) { + if (!json_tok_u64(buffer, msatoshitok, &msatoshi)) { command_fail(cmd, "'%.*s' is not a valid number", - (int)(msatoshistok->end - msatoshistok->start), - buffer + msatoshistok->start); + (int)(msatoshitok->end - msatoshitok->start), + buffer + msatoshitok->start); return; } - peer = find_route(cmd->dstate, &id, msatoshis, &fee, &route); + peer = find_route(cmd->dstate, &id, msatoshi, &fee, &route); if (!peer) { command_fail(cmd, "no route found"); return; @@ -208,7 +240,7 @@ static void json_getroute(struct command *cmd, /* Fees, delays need to be calculated backwards along route. */ amounts = tal_arr(cmd, u64, tal_count(route)+1); delays = tal_arr(cmd, unsigned int, tal_count(route)+1); - total_amount = msatoshis; + total_amount = msatoshi; total_delay = 0; for (i = tal_count(route) - 1; i >= 0; i--) { @@ -244,8 +276,8 @@ static void json_getroute(struct command *cmd, const struct json_command getroute_command = { "getroute", json_getroute, - "Return route for {msatoshis} to {id}", - "Returns a {route} array of {id} {msatoshis} {delay}: msatoshis and delay (in blocks) is cumulative." + "Return route for {msatoshi} to {id}", + "Returns a {route} array of {id} {msatoshi} {delay}: msatoshi and delay (in blocks) is cumulative." }; static void json_sendpay(struct command *cmd, @@ -260,6 +292,7 @@ static void json_sendpay(struct command *cmd, struct sha256 rhash; struct peer *peer; struct pay_command *pc; + bool replacing = false; const u8 *onion; enum fail_error error_code; const char *err; @@ -302,18 +335,18 @@ static void json_sendpay(struct command *cmd, buffer + t->start); return; } - amttok = json_get_member(buffer, t, "msatoshis"); + amttok = json_get_member(buffer, t, "msatoshi"); idtok = json_get_member(buffer, t, "id"); delaytok = json_get_member(buffer, t, "delay"); if (!amttok || !idtok || !delaytok) { - command_fail(cmd, "route %zu needs msatoshis/id/delay", + command_fail(cmd, "route %zu needs msatoshi/id/delay", n_hops); return; } tal_resize(&amounts, n_hops+1); if (!json_tok_u64(buffer, amttok, &amounts[n_hops])) { - command_fail(cmd, "route %zu invalid msatoshis", n_hops); + command_fail(cmd, "route %zu invalid msatoshi", n_hops); return; } tal_resize(&ids, n_hops+1); @@ -339,6 +372,7 @@ static void json_sendpay(struct command *cmd, pc = find_pay_command(cmd->dstate, &rhash); if (pc) { + replacing = true; log_debug(cmd->dstate->base_log, "json_sendpay: found previous"); if (pc->htlc) { log_add(cmd->dstate->base_log, "... still in progress"); @@ -349,10 +383,10 @@ static void json_sendpay(struct command *cmd, size_t old_nhops = tal_count(pc->ids); log_add(cmd->dstate->base_log, "... succeeded"); /* Must match successful payment parameters. */ - if (pc->msatoshis != amounts[n_hops-1]) { + if (pc->msatoshi != amounts[n_hops-1]) { command_fail(cmd, "already succeeded with amount %" - PRIu64, pc->msatoshis); + PRIu64, pc->msatoshi); return; } if (!structeq(&pc->ids[old_nhops-1], &ids[n_hops-1])) { @@ -389,7 +423,7 @@ static void json_sendpay(struct command *cmd, pc->rhash = rhash; pc->rval = NULL; pc->ids = tal_steal(pc, ids); - pc->msatoshis = amounts[n_hops-1]; + pc->msatoshi = amounts[n_hops-1]; /* Expiry for HTLCs is absolute. And add one to give some margin. */ err = command_htlc_add(peer, amounts[0], @@ -398,9 +432,32 @@ static void json_sendpay(struct command *cmd, onion, &error_code, &pc->htlc); if (err) { command_fail(cmd, "could not add htlc: %u: %s", error_code, err); + tal_free(pc); return; } + if (replacing) { + if (!db_replace_pay_command(cmd->dstate, &pc->rhash, + pc->ids, pc->msatoshi, + pc->htlc)) { + command_fail(cmd, "database error"); + /* We could reconnect, but db error is *bad*. */ + peer_fail(peer, __func__); + tal_free(pc); + return; + } + } else { + if (!db_new_pay_command(cmd->dstate, &pc->rhash, + pc->ids, pc->msatoshi, + pc->htlc)) { + command_fail(cmd, "database error"); + /* We could reconnect, but db error is *bad*. */ + peer_fail(peer, __func__); + tal_free(pc); + return; + } + } + /* Wait until we get response. */ list_add_tail(&cmd->dstate->pay_commands, &pc->list); tal_add_destructor(cmd, remove_cmd_from_pc); diff --git a/daemon/pay.h b/daemon/pay.h index 75a90b70f..07a61512b 100644 --- a/daemon/pay.h +++ b/daemon/pay.h @@ -8,4 +8,12 @@ struct htlc; void complete_pay_command(struct lightningd_state *dstate, const struct htlc *htlc); +bool pay_add(struct lightningd_state *dstate, + const struct sha256 *rhash, + u64 msatoshi, + const struct pubkey *ids, + struct htlc *htlc, + const u8 *fail, + const struct rval *r); + #endif /* LIGHTNING_DAEMON_PAY_H */ diff --git a/daemon/test/test.sh b/daemon/test/test.sh index aa6228144..b7e90c436 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -1010,9 +1010,6 @@ if [ ! -n "$MANUALCOMMIT" ]; then lcli1 add-route $ID2 $ID3 546000 10 36 36 RHASH5=`lcli3 accept-payment $HTLC_AMOUNT | sed 's/.*"\([0-9a-f]*\)".*/\1/'` - # FIXME: We don't save payments in db yet! - DO_RECONNECT="" - # Get route. ROUTE=`lcli1 getroute $ID3 $HTLC_AMOUNT` ROUTE=`echo $ROUTE | sed 's/^{ "route" : \(.*\) }$/\1/'` @@ -1061,8 +1058,6 @@ if [ ! -n "$MANUALCOMMIT" ]; then echo "Pay to node3 didn't fail instantly second time" >&2 exit 1 fi - - DO_RECONNECT=$RECONNECT fi lcli1 close $ID2