diff --git a/wallet/db.c b/wallet/db.c index 26d6c8718..60cf44003 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -135,6 +135,16 @@ char *dbmigrations[] = { /* Add expiry field to invoices (effectively infinite). */ "ALTER TABLE invoices ADD expiry_time INTEGER;", "UPDATE invoices SET expiry_time=9223372036854775807;", + /* Add pay_index field to paid invoices (initially, same order as id). */ + "ALTER TABLE invoices ADD pay_index INTEGER;", + "CREATE UNIQUE INDEX invoices_pay_index" + " ON invoices(pay_index);", + "UPDATE invoices SET pay_index=id WHERE state=1;", /* only paid invoice */ + /* Create next_pay_index variable (highest pay_index). */ + "INSERT OR REPLACE INTO vars(name, val)" + " VALUES('next_pay_index', " + " COALESCE((SELECT MAX(pay_index) FROM invoices WHERE state=1), 0) + 1" + " );", NULL, }; diff --git a/wallet/wallet.c b/wallet/wallet.c index 8525c5d02..4048c2da6 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -1124,15 +1125,51 @@ bool wallet_htlcs_reconnect(struct wallet *wallet, return true; } +/* Acquire the next pay_index. */ +static s64 wallet_invoice_next_pay_index(struct db *db) +{ + /* Equivalent to (next_pay_index++) */ + s64 next_pay_index; + next_pay_index = db_get_intvar(db, "next_pay_index", 0); + /* Variable should exist. */ + assert(next_pay_index > 0); + db_set_intvar(db, "next_pay_index", next_pay_index + 1); + return next_pay_index; +} +/* Determine the on-database state of an invoice. */ +static enum invoice_status wallet_invoice_db_state( + struct db *db, const struct invoice *inv) +{ + sqlite3_stmt *stmt; + int res; + int state; + + stmt = db_prepare(db, "SELECT state FROM invoices WHERE id=?;"); + + sqlite3_bind_int64(stmt, 1, inv->id); + + res = sqlite3_step(stmt); + assert(res == SQLITE_ROW); + state = sqlite3_column_int(stmt, 0); + res = sqlite3_step(stmt); + assert(res == SQLITE_DONE); + + sqlite3_finalize(stmt); + return (enum invoice_status) state; +} + void wallet_invoice_save(struct wallet *wallet, struct invoice *inv) { + sqlite3_stmt *stmt; + s64 pay_index; + bool unpaid_to_paid = false; + /* Need to use the lower level API of sqlite3 to bind * label. Otherwise we'd need to implement sanitization of * that string for sql injections... */ - sqlite3_stmt *stmt; if (!inv->id) { stmt = db_prepare(wallet->db, - "INSERT INTO invoices (payment_hash, payment_key, state, msatoshi, label, expiry_time) VALUES (?, ?, ?, ?, ?, ?);"); + "INSERT INTO invoices (payment_hash, payment_key, state, msatoshi, label, expiry_time, pay_index) VALUES (?, ?, ?, ?, ?, ?, ?);"); sqlite3_bind_blob(stmt, 1, &inv->rhash, sizeof(inv->rhash), SQLITE_TRANSIENT); sqlite3_bind_blob(stmt, 2, &inv->r, sizeof(inv->r), SQLITE_TRANSIENT); @@ -1140,17 +1177,39 @@ void wallet_invoice_save(struct wallet *wallet, struct invoice *inv) sqlite3_bind_int64(stmt, 4, inv->msatoshi); sqlite3_bind_text(stmt, 5, inv->label, strlen(inv->label), SQLITE_TRANSIENT); sqlite3_bind_int64(stmt, 6, inv->expiry_time); + if (inv->state == PAID) { + pay_index = wallet_invoice_next_pay_index(wallet->db); + sqlite3_bind_int64(stmt, 7, pay_index); + } else { + sqlite3_bind_null(stmt, 7); + } db_exec_prepared(wallet->db, stmt); inv->id = sqlite3_last_insert_rowid(wallet->db->sql); } else { - stmt = db_prepare(wallet->db, "UPDATE invoices SET state=? WHERE id=?;"); + if (inv->state == PAID) { + if (wallet_invoice_db_state(wallet->db, inv) == UNPAID) { + unpaid_to_paid = true; + } + } + if (unpaid_to_paid) { + stmt = db_prepare(wallet->db, "UPDATE invoices SET state=?, pay_index=? WHERE id=?;"); - sqlite3_bind_int(stmt, 1, inv->state); - sqlite3_bind_int64(stmt, 2, inv->id); + sqlite3_bind_int(stmt, 1, inv->state); + pay_index = wallet_invoice_next_pay_index(wallet->db); + sqlite3_bind_int64(stmt, 2, pay_index); + sqlite3_bind_int64(stmt, 3, inv->id); - db_exec_prepared(wallet->db, stmt); + db_exec_prepared(wallet->db, stmt); + } else { + stmt = db_prepare(wallet->db, "UPDATE invoices SET state=? WHERE id=?;"); + + sqlite3_bind_int(stmt, 1, inv->state); + sqlite3_bind_int64(stmt, 2, inv->id); + + db_exec_prepared(wallet->db, stmt); + } } }