wallet: Implement shachain persistence

This needed a rather annoying hack since sqlite3 can only store
integers up to 2^63, so I just squash it down/invert it, and hope that
we never ever have more than 2^63 updates.
This commit is contained in:
Christian Decker 2017-07-18 19:01:26 +02:00 committed by Rusty Russell
parent b0c64909e7
commit 34baf05973
2 changed files with 152 additions and 0 deletions

View file

@ -2,8 +2,11 @@
#include <bitcoin/script.h> #include <bitcoin/script.h>
#include <ccan/str/hex/hex.h> #include <ccan/str/hex/hex.h>
#include <inttypes.h>
#include <lightningd/lightningd.h> #include <lightningd/lightningd.h>
#define SQLITE_MAX_UINT 0x7FFFFFFFFFFFFFFF
struct wallet *wallet_new(const tal_t *ctx, struct log *log) struct wallet *wallet_new(const tal_t *ctx, struct log *log)
{ {
struct wallet *wallet = tal(ctx, struct wallet); struct wallet *wallet = tal(ctx, struct wallet);
@ -232,3 +235,112 @@ s64 wallet_get_newindex(struct lightningd *ld)
db_set_intvar(ld->wallet->db, "bip32_max_index", newidx); db_set_intvar(ld->wallet->db, "bip32_max_index", newidx);
return newidx; return newidx;
} }
bool wallet_shachain_init(struct wallet *wallet, struct wallet_shachain *chain)
{
/* Create shachain */
shachain_init(&chain->chain);
if (!db_exec(__func__, wallet->db,
"INSERT INTO shachains (min_index, num_valid) VALUES (0,0);")) {
return false;
}
chain->id = sqlite3_last_insert_rowid(wallet->db->sql);
return true;
}
/* TODO(cdecker) Stolen from shachain, move to some appropriate location */
static unsigned int count_trailing_zeroes(shachain_index_t index)
{
#if HAVE_BUILTIN_CTZLL
return index ? (unsigned int)__builtin_ctzll(index) : SHACHAIN_BITS;
#else
unsigned int i;
for (i = 0; i < SHACHAIN_BITS; i++) {
if (index & (1ULL << i))
break;
}
return i;
#endif
}
bool wallet_shachain_add_hash(struct wallet *wallet,
struct wallet_shachain *chain,
shachain_index_t index,
const struct sha256 *hash)
{
tal_t *tmpctx = tal_tmpctx(wallet);
bool ok = true;
u32 pos = count_trailing_zeroes(index);
assert(index < SQLITE_MAX_UINT);
char *hexhash = tal_hexstr(tmpctx, hash, sizeof(struct sha256));
if (!shachain_add_hash(&chain->chain, index, hash)) {
tal_free(tmpctx);
return false;
}
db_begin_transaction(wallet->db);
ok &= db_exec(__func__, wallet->db,
"UPDATE shachains SET num_valid=%d, min_index=%" PRIu64
" WHERE id=%" PRIu64,
chain->chain.num_valid, index, chain->id);
ok &= db_exec(__func__, wallet->db,
"REPLACE INTO shachain_known "
"(shachain_id, pos, idx, hash) VALUES "
"(%" PRIu64 ", %d, %" PRIu64 ", '%s');",
chain->id, pos, index, hexhash);
if (ok)
ok &= db_commit_transaction(wallet->db);
else
db_rollback_transaction(wallet->db);
tal_free(tmpctx);
return ok;
}
bool wallet_shachain_load(struct wallet *wallet, u64 id,
struct wallet_shachain *chain)
{
int err;
sqlite3_stmt *stmt;
chain->id = id;
shachain_init(&chain->chain);
/* Load shachain metadata */
stmt = db_query(
__func__, wallet->db,
"SELECT min_index, num_valid FROM shachains WHERE id=%" PRIu64, id);
if (!stmt)
return false;
err = sqlite3_step(stmt);
if (err != SQLITE_ROW) {
sqlite3_finalize(stmt);
return false;
}
chain->chain.min_index = sqlite3_column_int64(stmt, 0);
chain->chain.num_valid = sqlite3_column_int64(stmt, 1);
sqlite3_finalize(stmt);
/* Load shachain known entries */
stmt = db_query(
__func__, wallet->db,
"SELECT idx, hash, pos FROM shachain_known WHERE shachain_id=%" PRIu64,
id);
if (!stmt)
return false;
while (sqlite3_step(stmt) == SQLITE_ROW) {
int pos = sqlite3_column_int(stmt, 2);
chain->chain.known[pos].index = sqlite3_column_int64(stmt, 0);
hex_decode(
sqlite3_column_blob(stmt, 1), sqlite3_column_bytes(stmt, 1),
&chain->chain.known[pos].hash, sizeof(struct sha256));
}
sqlite3_finalize(stmt);
return true;
}

View file

@ -3,6 +3,7 @@
#include "config.h" #include "config.h"
#include "db.h" #include "db.h"
#include <ccan/crypto/shachain/shachain.h>
#include <ccan/tal/tal.h> #include <ccan/tal/tal.h>
#include <lightningd/utxo.h> #include <lightningd/utxo.h>
#include <wally_bip32.h> #include <wally_bip32.h>
@ -38,6 +39,14 @@ enum wallet_output_type {
htlc_recv = 4 htlc_recv = 4
}; };
/* A database backed shachain struct. The datastructure is
* writethrough, reads are performed from an in-memory version, all
* writes are passed through to the DB. */
struct wallet_shachain {
u64 id;
struct shachain chain;
};
/** /**
* wallet_new - Constructor for a new sqlite3 based wallet * wallet_new - Constructor for a new sqlite3 based wallet
* *
@ -114,4 +123,35 @@ bool wallet_can_spend(struct wallet *w, const u8 *script,
*/ */
s64 wallet_get_newindex(struct lightningd *ld); s64 wallet_get_newindex(struct lightningd *ld);
/**
* wallet_shachain_init -- wallet wrapper around shachain_init
*/
bool wallet_shachain_init(struct wallet *wallet, struct wallet_shachain *chain);
/**
* wallet_shachain_add_hash -- wallet wrapper around shachain_add_hash
*/
bool wallet_shachain_add_hash(struct wallet *wallet,
struct wallet_shachain *chain,
shachain_index_t index,
const struct sha256 *hash);
/* Simply passes through to shachain_get_hash since it doesn't touch
* the DB */
static inline bool wallet_shachain_get_hash(struct wallet *w,
struct wallet_shachain *chain,
u64 index, struct sha256 *hash)
{
return shachain_get_hash(&chain->chain, index, hash);
}
/**
* wallet_shachain_load -- Load an existing shachain from the wallet.
*
* @wallet: the wallet to load from
* @id: the shachain id to load
* @chain: where to load the shachain into
*/
bool wallet_shachain_load(struct wallet *wallet, u64 id,
struct wallet_shachain *chain);
#endif /* WALLET_WALLET_H */ #endif /* WALLET_WALLET_H */