From ab125f709b1287074720e697ff0f7d62f2a7cf8c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 6 Sep 2016 13:47:33 +0930 Subject: [PATCH] db: save and restore accepted payments. Signed-off-by: Rusty Russell --- daemon/db.c | 76 +++++++++++++++++++++++++++++++++++++++++++++ daemon/db.h | 6 +++- daemon/payment.c | 20 ++++++++++++ daemon/payment.h | 7 +++++ daemon/peer.c | 19 ++++++++++++ daemon/test/test.sh | 34 ++++++++++++++++++-- 6 files changed, 159 insertions(+), 3 deletions(-) diff --git a/daemon/db.c b/daemon/db.c index d51c05c02..1c9f239b0 100644 --- a/daemon/db.c +++ b/daemon/db.c @@ -8,6 +8,7 @@ #include "names.h" #include "netaddr.h" #include "pay.h" +#include "payment.h" #include "routing.h" #include "secrets.h" #include "utils.h" @@ -1071,6 +1072,40 @@ static void db_load_pay(struct lightningd_state *dstate) tal_free(ctx); } +static void db_load_payment(struct lightningd_state *dstate) +{ + int err; + sqlite3_stmt *stmt; + char *ctx = tal(dstate, char); + + err = sqlite3_prepare_v2(dstate->db->sql, "SELECT * FROM payment;", -1, + &stmt, NULL); + + if (err != SQLITE_OK) + fatal("db_load_payment:prepare gave %s:%s", + sqlite3_errstr(err), sqlite3_errmsg(dstate->db->sql)); + + while ((err = sqlite3_step(stmt)) != SQLITE_DONE) { + struct rval r; + u64 msatoshis; + bool complete; + + if (err != SQLITE_ROW) + fatal("db_load_payment:step gave %s:%s", + sqlite3_errstr(err), + sqlite3_errmsg(dstate->db->sql)); + if (sqlite3_column_count(stmt) != 3) + fatal("db_load_pay:step gave %i cols, not 3", + sqlite3_column_count(stmt)); + + from_sql_blob(stmt, 0, &r, sizeof(r)); + msatoshis = sqlite3_column_int64(stmt, 1); + complete = sqlite3_column_int(stmt, 2); + payment_add(dstate, &r, msatoshis, complete); + } + tal_free(ctx); +} + static void db_load_addresses(struct lightningd_state *dstate) { int err; @@ -1112,6 +1147,7 @@ static void db_load(struct lightningd_state *dstate) db_load_addresses(dstate); db_load_peers(dstate); db_load_pay(dstate); + db_load_payment(dstate); } void db_init(struct lightningd_state *dstate) @@ -1158,6 +1194,9 @@ void db_init(struct lightningd_state *dstate) SQL_BLOB(ids), SQL_PUBKEY(htlc_peer), SQL_U64(htlc_id), SQL_R(r), SQL_FAIL(fail), "PRIMARY KEY(rhash)") + TABLE(payment, + SQL_R(r), SQL_U64(msatoshis), SQL_BOOL(complete), + "PRIMARY KEY(r)") TABLE(anchors, SQL_PUBKEY(peer), SQL_TXID(txid), SQL_U32(idx), SQL_U64(amount), @@ -1886,3 +1925,40 @@ bool db_complete_pay_command(struct lightningd_state *dstate, tal_free(ctx); return !errmsg; } + +bool db_new_payment(struct lightningd_state *dstate, + u64 msatoshis, + const struct rval *r) +{ + const char *errmsg, *ctx = tal(dstate, char); + + log_debug(dstate->base_log, "%s", __func__); + + assert(!dstate->db->in_transaction); + + errmsg = db_exec(ctx, dstate, "INSERT INTO payment VALUES (x'%s', %"PRIu64", %s);", + tal_hexstr(ctx, r, sizeof(*r)), + msatoshis, + sql_bool(false)); + if (errmsg) + log_broken(dstate->base_log, "%s:%s", __func__, errmsg); + tal_free(ctx); + return !errmsg; +} + +bool db_resolve_payment(struct lightningd_state *dstate, const struct rval *r) +{ + const char *errmsg, *ctx = tal(dstate, char); + + log_debug(dstate->base_log, "%s", __func__); + + assert(dstate->db->in_transaction); + + errmsg = db_exec(ctx, dstate, "UPDATE payment SET complete=%s WHERE r=x'%s';", + sql_bool(true), + tal_hexstr(ctx, r, sizeof(*r))); + 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 45ad366ba..a97d2d630 100644 --- a/daemon/db.h +++ b/daemon/db.h @@ -37,6 +37,9 @@ bool db_replace_pay_command(struct lightningd_state *dstate, const struct pubkey *ids, u64 msatoshis, const struct htlc *htlc); +bool db_new_payment(struct lightningd_state *dstate, + u64 msatoshis, + const struct rval *r); /* FIXME: save error handling until db_commit_transaction for calls * which have to be inside transaction anyway. */ @@ -46,8 +49,9 @@ 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, +bool db_complete_pay_command(struct lightningd_state *dstate, const struct htlc *htlc); +bool db_resolve_payment(struct lightningd_state *dstate, const struct rval *r); bool db_update_feechange_state(struct peer *peer, const struct feechange *f, enum htlc_state oldstate); diff --git a/daemon/payment.c b/daemon/payment.c index c068744a8..4b88d9275 100644 --- a/daemon/payment.c +++ b/daemon/payment.c @@ -1,3 +1,4 @@ +#include "db.h" #include "jsonrpc.h" #include "lightningd.h" #include "payment.h" @@ -17,6 +18,20 @@ struct payment *find_payment(struct lightningd_state *dstate, return NULL; } +void payment_add(struct lightningd_state *dstate, + const struct rval *r, + u64 msatoshis, + bool complete) +{ + struct payment *payment = tal(dstate, struct payment); + + payment->msatoshis = msatoshis; + payment->r = *r; + payment->complete = complete; + sha256(&payment->rhash, payment->r.r, sizeof(payment->r.r)); + list_add(&dstate->payments, &payment->list); +} + static void json_accept_payment(struct command *cmd, const char *buffer, const jsmntok_t *params) { @@ -57,7 +72,12 @@ static void json_accept_payment(struct command *cmd, buffer + msatoshis->start); return; } + payment->complete = false; + if (!db_new_payment(cmd->dstate, payment->msatoshis, &payment->r)) { + command_fail(cmd, "database error"); + return; + } /* OK, connect it to main state, respond with hash */ tal_steal(cmd->dstate, payment); list_add(&cmd->dstate->payments, &payment->list); diff --git a/daemon/payment.h b/daemon/payment.h index b7729ab45..f11b2bc7c 100644 --- a/daemon/payment.h +++ b/daemon/payment.h @@ -10,8 +10,15 @@ struct payment { u64 msatoshis; struct rval r; struct sha256 rhash; + bool complete; }; +/* From database */ +void payment_add(struct lightningd_state *dstate, + const struct rval *r, + u64 msatoshis, + bool complete); + struct payment *find_payment(struct lightningd_state *dstate, const struct sha256 *rhash); diff --git a/daemon/peer.c b/daemon/peer.c index 275967780..c4746b7fa 100644 --- a/daemon/peer.c +++ b/daemon/peer.c @@ -560,9 +560,28 @@ static void their_htlc_added(struct peer *peer, struct htlc *htlc, return; } + /* This is a courtesy: we could simply take your money! */ + if (payment->complete) { + log_unusual(peer->log, + "Repeated payment for HTLC %"PRIu64, + htlc->id); + command_htlc_set_fail(peer, htlc, + UNAUTHORIZED_401, + "already received payment"); + return; + } + log_info(peer->log, "Immediately resolving HTLC %"PRIu64, htlc->id); + if (!db_resolve_payment(peer->dstate, &payment->r)) { + command_htlc_set_fail(peer, htlc, + INTERNAL_SERVER_ERROR_500, + "database error"); + return; + } + + payment->complete = true; set_htlc_rval(peer, htlc, &payment->r); command_htlc_fulfill(peer, htlc); goto free_rest; diff --git a/daemon/test/test.sh b/daemon/test/test.sh index b7e90c436..eb51970c2 100755 --- a/daemon/test/test.sh +++ b/daemon/test/test.sh @@ -386,9 +386,10 @@ EOF cp $DIR2/config $DIR3/config if [ x"$RECONNECT" = xrestart ]; then - # Make sure node2 restarts on same port, by setting in config. + # Make sure node2 & 3 restart on same port, by setting in config. # Find a free TCP port. echo port=`findport 4000` >> $DIR2/config + echo port=`findport 4010` >> $DIR3/config fi if [ -n "$DIFFERENT_FEES" ]; then @@ -418,7 +419,8 @@ else LIGHTNINGD2="$PREFIX $LIGHTNINGD2" $LIGHTNINGD2 > $REDIR2 2> $REDIRERR2 & fi -$PREFIX ../lightningd --lightning-dir=$DIR3 > $DIR3/output 2> $DIR3/errors & +LIGHTNINGD3="$PREFIX $(readlink -f `pwd`/../lightningd) --lightning-dir=$DIR3" +$LIGHTNINGD3 > $DIR3/output 2> $DIR3/errors & if ! check "$LCLI1 getlog 2>/dev/null | $FGREP Hello"; then echo Failed to start daemon 1 >&2 @@ -1028,6 +1030,15 @@ if [ ! -n "$MANUALCOMMIT" ]; then exit 1 fi + # If restarting, make sure node3 remembers incoming payment. + if [ "$RECONNECT" = restart ]; then + $LCLI3 -- dev-restart $LIGHTNINGD3 >/dev/null 2>&1 || true + if ! check "$LCLI3 getpeers | tr -s '\012\011\" ' ' ' | fgrep -q 'connected : true'"; then + echo "Failed to reconnect!">&2 + exit 1 + fi + fi + # Pay correctly. lcli1 sendpay "$ROUTE" $RHASH5 @@ -1035,6 +1046,25 @@ if [ ! -n "$MANUALCOMMIT" ]; then # Note that it is delayed a little, since node2 fulfils as soon as fulfill # starts. check lcli3 "getpeers | $FGREP \"\\\"our_amount\\\" : $(($HTLC_AMOUNT - $NO_HTLCS_FEE / 2))\"" + + # If restarting, make sure node3 remembers completed payment. + if [ "$RECONNECT" = restart ]; then + echo RESTARTING NODE3 + $LCLI3 -- dev-restart $LIGHTNINGD3 >/dev/null 2>&1 || true + if ! check "$LCLI3 getpeers 2>/dev/null | tr -s '\012\011\" ' ' ' | fgrep -q 'connected : true'"; then + echo "Failed to reconnect!">&2 + exit 1 + fi + fi + + # Can't pay twice (try from node2) + ROUTE2=`lcli2 getroute $ID3 $HTLC_AMOUNT` + ROUTE2=`echo $ROUTE2 | sed 's/^{ "route" : \(.*\) }$/\1/'` + if lcli2 sendpay "$ROUTE2" $RHASH5; then + echo "Paying twice worked?" >&2 + exit 1 + fi + lcli3 close $ID2 # Re-send should be a noop (doesn't matter that node3 is down!)