Browse Source

wallet: Add primitives to store htlcs in the database

Signed-off-by: Christian Decker <decker.christian@gmail.com>
ppa-0.6.1
Christian Decker 7 years ago
committed by Rusty Russell
parent
commit
b8fa765c3f
  1. 8
      lightningd/htlc_end.h
  2. 3
      wallet/db.c
  3. 216
      wallet/wallet.c
  4. 45
      wallet/wallet.h
  5. 43
      wallet/wallet_tests.c

8
lightningd/htlc_end.h

@ -15,6 +15,10 @@ struct htlc_key {
/* Incoming HTLC */ /* Incoming HTLC */
struct htlc_in { struct htlc_in {
/* The database primary key for this htlc. Must be 0 until it
* is saved to the database, must be >0 after saving to the
* database. */
u64 dbid;
struct htlc_key key; struct htlc_key key;
u64 msatoshi; u64 msatoshi;
u32 cltv_expiry; u32 cltv_expiry;
@ -39,6 +43,10 @@ struct htlc_in {
}; };
struct htlc_out { struct htlc_out {
/* The database primary key for this htlc. Must be 0 until it
* is saved to the database, must be >0 after saving to the
* database. */
u64 dbid;
struct htlc_key key; struct htlc_key key;
u64 msatoshi; u64 msatoshi;
u32 cltv_expiry; u32 cltv_expiry;

3
wallet/db.c

@ -99,7 +99,7 @@ char *dbmigrations[] = {
" direction INTEGER," " direction INTEGER,"
" origin_htlc INTEGER," " origin_htlc INTEGER,"
" msatoshi INTEGER," " msatoshi INTEGER,"
" ctlv_expiry INTEGER," " cltv_expiry INTEGER,"
" payment_hash BLOB," " payment_hash BLOB,"
" payment_key BLOB," " payment_key BLOB,"
" routing_onion BLOB," " routing_onion BLOB,"
@ -107,7 +107,6 @@ char *dbmigrations[] = {
" malformed_onion INTEGER," " malformed_onion INTEGER,"
" hstate INTEGER," " hstate INTEGER,"
" shared_secret BLOB," " shared_secret BLOB,"
" preimage BLOB,"
" PRIMARY KEY (id)," " PRIMARY KEY (id),"
" UNIQUE (channel_id, channel_htlc_id, direction)" " UNIQUE (channel_id, channel_htlc_id, direction)"
");", ");",

216
wallet/wallet.c

@ -10,6 +10,8 @@
#include <lightningd/peer_htlcs.h> #include <lightningd/peer_htlcs.h>
#define SQLITE_MAX_UINT 0x7FFFFFFFFFFFFFFF #define SQLITE_MAX_UINT 0x7FFFFFFFFFFFFFFF
#define DIRECTION_INCOMING 0
#define DIRECTION_OUTGOING 1
struct wallet *wallet_new(const tal_t *ctx, struct log *log) struct wallet *wallet_new(const tal_t *ctx, struct log *log)
{ {
@ -845,6 +847,220 @@ int wallet_extract_owned_outputs(struct wallet *w, const struct bitcoin_tx *tx,
return num_utxos; return num_utxos;
} }
bool wallet_htlc_save_in(struct wallet *wallet,
const struct wallet_channel *chan, struct htlc_in *in)
{
bool ok = true;
tal_t *tmpctx = tal_tmpctx(wallet);
ok &= db_exec(
__func__, wallet->db,
"INSERT INTO channel_htlcs "
"(channel_id, channel_htlc_id, direction, origin_htlc, msatoshi, cltv_expiry, payment_hash, payment_key, hstate, shared_secret, routing_onion) VALUES "
"(%" PRIu64 ", %"PRIu64", %d, NULL, %"PRIu64", %d, '%s', %s, %d, '%s', '%s');",
chan->id, in->key.id, DIRECTION_INCOMING, in->msatoshi, in->cltv_expiry,
tal_hexstr(tmpctx, &in->payment_hash, sizeof(struct sha256)),
in->preimage == NULL ? "NULL" : tal_hexstr(tmpctx, &in->preimage,
sizeof(struct preimage)),
in->hstate,
tal_hexstr(tmpctx, &in->shared_secret, sizeof(struct secret)),
tal_hexstr(tmpctx, &in->onion_routing_packet, sizeof(in->onion_routing_packet))
);
tal_free(tmpctx);
if (ok) {
in->dbid = sqlite3_last_insert_rowid(wallet->db->sql);
}
return ok;
}
bool wallet_htlc_save_out(struct wallet *wallet,
const struct wallet_channel *chan,
struct htlc_out *out)
{
bool ok = true;
tal_t *tmpctx = tal_tmpctx(wallet);
/* We absolutely need the incoming HTLC to be persisted before
* we can persist it's dependent */
assert(out->in == NULL || out->in->dbid != 0);
ok &= db_exec(
__func__, wallet->db,
"INSERT INTO channel_htlcs "
"(channel_id, channel_htlc_id, direction, origin_htlc, msatoshi, cltv_expiry, "
"payment_hash, payment_key, hstate, shared_secret, routing_onion) VALUES "
"(%" PRIu64 ", %" PRIu64 ", %d, %s, %" PRIu64", %d, '%s', %s, %d, NULL, '%s');",
chan->id,
out->key.id,
DIRECTION_OUTGOING,
out->in ? tal_fmt(tmpctx, "%" PRIu64, out->in->dbid) : "NULL",
out->msatoshi,
out->cltv_expiry,
tal_hexstr(tmpctx, &out->payment_hash, sizeof(struct sha256)),
out->preimage ? tal_hexstr(tmpctx, &out->preimage, sizeof(struct preimage)) : "NULL",
out->hstate,
tal_hexstr(tmpctx, &out->onion_routing_packet, sizeof(out->onion_routing_packet))
);
tal_free(tmpctx);
if (ok) {
out->dbid = sqlite3_last_insert_rowid(wallet->db->sql);
}
return ok;
}
bool wallet_htlc_update(struct wallet *wallet, const u64 htlc_dbid,
const enum htlc_state new_state,
const struct preimage *payment_key)
{
bool ok = true;
tal_t *tmpctx = tal_tmpctx(wallet);
if (payment_key) {
ok &= db_exec(
__func__, wallet->db, "UPDATE channel_htlcs SET hstate=%d, "
"payment_key='%s' WHERE id=%" PRIu64,
new_state,
tal_hexstr(tmpctx, payment_key, sizeof(struct preimage)),
htlc_dbid);
} else {
ok &= db_exec(
__func__, wallet->db,
"UPDATE channel_htlcs SET hstate = %d WHERE id=%" PRIu64,
new_state, htlc_dbid);
}
tal_free(tmpctx);
return ok;
}
static bool wallet_stmt2htlc_in(const struct wallet_channel *channel,
sqlite3_stmt *stmt, struct htlc_in *in)
{
bool ok = true;
in->dbid = sqlite3_column_int64(stmt, 0);
in->key.id = sqlite3_column_int64(stmt, 1);
in->key.peer = channel->peer;
in->msatoshi = sqlite3_column_int64(stmt, 2);
in->cltv_expiry = sqlite3_column_int(stmt, 3);
in->hstate = sqlite3_column_int(stmt, 4);
ok &= sqlite3_column_hexval(stmt, 5, &in->payment_hash,
sizeof(in->payment_hash));
ok &= sqlite3_column_hexval(stmt, 6, &in->shared_secret,
sizeof(in->shared_secret));
if (sqlite3_column_type(stmt, 7) != SQLITE_NULL) {
in->preimage = tal(in, struct preimage);
ok &= sqlite3_column_hexval(stmt, 7, in->preimage, sizeof(*in->preimage));
} else {
in->preimage = NULL;
}
sqlite3_column_hexval(stmt, 8, &in->onion_routing_packet,
sizeof(in->onion_routing_packet));
in->failuremsg = NULL;
in->malformed = 0;
return ok;
}
static bool wallet_stmt2htlc_out(const struct wallet_channel *channel,
sqlite3_stmt *stmt, struct htlc_out *out)
{
bool ok = true;
out->dbid = sqlite3_column_int64(stmt, 0);
out->key.id = sqlite3_column_int64(stmt, 1);
out->key.peer = channel->peer;
out->msatoshi = sqlite3_column_int64(stmt, 2);
out->cltv_expiry = sqlite3_column_int(stmt, 3);
out->hstate = sqlite3_column_int(stmt, 4);
ok &= sqlite3_column_hexval(stmt, 5, &out->payment_hash,
sizeof(out->payment_hash));
if (sqlite3_column_type(stmt, 6) != SQLITE_NULL) {
out->origin_htlc_id = sqlite3_column_int64(stmt, 6);
} else {
out->origin_htlc_id = 0;
}
if (sqlite3_column_type(stmt, 7) != SQLITE_NULL) {
out->preimage = tal(out, struct preimage);
ok &= sqlite3_column_hexval(stmt, 7, &out->preimage, sizeof(struct preimage));
} else {
out->preimage = NULL;
}
sqlite3_column_hexval(stmt, 8, &out->onion_routing_packet,
sizeof(out->onion_routing_packet));
out->failuremsg = NULL;
out->malformed = 0;
/* Need to defer wiring until we can look up all incoming
* htlcs, will wire using origin_htlc_id */
out->in = NULL;
return ok;
}
bool wallet_htlcs_load_for_channel(struct wallet *wallet,
struct wallet_channel *chan,
struct htlc_in_map *htlcs_in,
struct htlc_out_map *htlcs_out)
{
bool ok = true;
int incount = 0, outcount = 0;
log_debug(wallet->log, "Loading HTLCs for channel %"PRIu64, chan->id);
sqlite3_stmt *stmt = db_query(
__func__, wallet->db,
"SELECT id, channel_htlc_id, msatoshi, cltv_expiry, hstate, "
"payment_hash, shared_secret, payment_key FROM channel_htlcs WHERE "
"direction=%d AND channel_id=%" PRIu64 " AND hstate != %d",
DIRECTION_INCOMING, chan->id, SENT_REMOVE_ACK_REVOCATION);
if (!stmt) {
log_broken(wallet->log, "Could not select htlc_ins: %s", wallet->db->err);
return false;
}
while (ok && stmt && sqlite3_step(stmt) == SQLITE_ROW) {
struct htlc_in *in = tal(chan, struct htlc_in);
ok &= wallet_stmt2htlc_in(chan, stmt, in);
connect_htlc_in(htlcs_in, in);
ok &= htlc_in_check(in, "wallet_htlcs_load") != NULL;
incount++;
}
sqlite3_finalize(stmt);
stmt = db_query(
__func__, wallet->db,
"SELECT id, channel_htlc_id, msatoshi, cltv_expiry, hstate, "
"payment_hash, origin_htlc, payment_key FROM channel_htlcs WHERE "
"direction=%d AND channel_id=%" PRIu64 " AND hstate != %d",
DIRECTION_OUTGOING, chan->id, RCVD_REMOVE_ACK_REVOCATION);
if (!stmt) {
log_broken(wallet->log, "Could not select htlc_outs: %s", wallet->db->err);
return false;
}
while (ok && stmt && sqlite3_step(stmt) == SQLITE_ROW) {
struct htlc_out *out = tal(chan, struct htlc_out);
ok &= wallet_stmt2htlc_out(chan, stmt, out);
connect_htlc_out(htlcs_out, out);
/* Cannot htlc_out_check because we haven't wired the
* dependencies in yet */
outcount++;
}
sqlite3_finalize(stmt);
log_debug(wallet->log, "Restored %d incoming and %d outgoing HTLCS", incount, outcount);
return ok;
}
/** /**
* wallet_shachain_delete - Drop the shachain from the database * wallet_shachain_delete - Drop the shachain from the database
* *

45
wallet/wallet.h

@ -9,6 +9,7 @@
#include <ccan/tal/tal.h> #include <ccan/tal/tal.h>
#include <common/channel_config.h> #include <common/channel_config.h>
#include <common/utxo.h> #include <common/utxo.h>
#include <lightningd/htlc_end.h>
#include <wally_bip32.h> #include <wally_bip32.h>
struct lightningd; struct lightningd;
@ -219,4 +220,48 @@ bool wallet_channels_load_active(struct wallet *w, struct list_head *peers);
int wallet_extract_owned_outputs(struct wallet *w, const struct bitcoin_tx *tx, int wallet_extract_owned_outputs(struct wallet *w, const struct bitcoin_tx *tx,
u64 *total_satoshi); u64 *total_satoshi);
/**
* wallet_htlc_save_in - store a htlc_in in the database
*
* @wallet: wallet to store the htlc into
* @chan: the `wallet_channel` this HTLC is associated with
* @in: the htlc_in to store
*
* This will store the contents of the `struct htlc_in` in the
* database. Since `struct htlc_in` commonly only change state after
* being created we do not support updating arbitrary fields and this
* function will fail when attempting to call it multiple times for
* the same `struct htlc_in`. Instead `wallet_htlc_update` may be used
* for state transitions or to set the `payment_key` for completed
* HTLCs.
*/
bool wallet_htlc_save_in(struct wallet *wallet,
const struct wallet_channel *chan, struct htlc_in *in);
/**
* wallet_htlc_save_out - store a htlc_out in the database
*
* See comment for wallet_htlc_save_in.
*/
bool wallet_htlc_save_out(struct wallet *wallet,
const struct wallet_channel *chan,
struct htlc_out *out);
/**
* wallet_htlc_update - perform state transition or add payment_key
*
* @wallet: the wallet containing the HTLC to update
* @htlc_dbid: the database ID used to identify the HTLC
* @new_state: the state we should transition to
* @payment_key: the `payment_key` which hashes to the `payment_hash`,
* or NULL if unknown.
*
* Used to update the state of an HTLC, either a `struct htlc_in` or a
* `struct htlc_out` and optionally set the `payment_key` should the
* HTLC have been settled.
*/
bool wallet_htlc_update(struct wallet *wallet, const u64 htlc_dbid,
const enum htlc_state new_state,
const struct preimage *payment_key);
#endif /* WALLET_WALLET_H */ #endif /* WALLET_WALLET_H */

43
wallet/wallet_tests.c

@ -294,6 +294,48 @@ static bool test_channel_config_crud(const tal_t *ctx)
return true; return true;
} }
static bool test_htlc_crud(const tal_t *ctx)
{
struct htlc_in in;
struct htlc_out out;
struct preimage payment_key;
struct wallet_channel chan;
struct wallet *w = create_test_wallet(ctx);
/* Make sure we have our references correct */
db_exec(__func__, w->db, "INSERT INTO channels (id) VALUES (1);");
chan.id = 1;
memset(&in, 0, sizeof(in));
memset(&out, 0, sizeof(out));
memset(&in.payment_hash, 'A', sizeof(struct sha256));
memset(&out.payment_hash, 'A', sizeof(struct sha256));
memset(&payment_key, 'B', sizeof(payment_key));
out.in = &in;
out.key.id = 1337;
/* Store the htlc_in */
CHECK_MSG(wallet_htlc_save_in(w, &chan, &in),
tal_fmt(ctx, "Save htlc_in failed: %s", w->db->err));
CHECK_MSG(in.dbid != 0, "HTLC DB ID was not set.");
/* Saving again should get us a collision */
CHECK_MSG(!wallet_htlc_save_in(w, &chan, &in),
"Saving two HTLCs with the same data must not succeed.");
/* Update */
CHECK_MSG(wallet_htlc_update(w, in.dbid, RCVD_ADD_HTLC, NULL),
"Update HTLC with null payment_key failed");
CHECK_MSG(
wallet_htlc_update(w, in.dbid, SENT_REMOVE_HTLC, &payment_key),
"Update HTLC with payment_key failed");
CHECK_MSG(wallet_htlc_save_out(w, &chan, &out),
tal_fmt(ctx, "Save htlc_out failed: %s", w->db->err));
CHECK_MSG(out.dbid != 0, "HTLC DB ID was not set.");
CHECK_MSG(!wallet_htlc_save_out(w, &chan, &out),
"Saving two HTLCs with the same data must not succeed.");
return true;
}
int main(void) int main(void)
{ {
bool ok = true; bool ok = true;
@ -303,6 +345,7 @@ int main(void)
ok &= test_shachain_crud(); ok &= test_shachain_crud();
ok &= test_channel_crud(tmpctx); ok &= test_channel_crud(tmpctx);
ok &= test_channel_config_crud(tmpctx); ok &= test_channel_config_crud(tmpctx);
ok &= test_htlc_crud(tmpctx);
tal_free(tmpctx); tal_free(tmpctx);
return !ok; return !ok;

Loading…
Cancel
Save