rgb-cln/wallet/wallet.c

6373 lines
176 KiB
C
Raw Normal View History

#include "config.h"
#include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <channeld/channeld_wiregen.h>
#include <common/blockheight_states.h>
#include <common/fee_states.h>
#include <common/onionreply.h>
#include <common/trace.h>
#include <common/type_to_string.h>
#include <db/bindings.h>
#include <db/common.h>
#include <db/exec.h>
#include <db/utils.h>
#include <lightningd/chaintopology.h>
#include <lightningd/channel.h>
#include <lightningd/channel_gossip.h>
#include <lightningd/closed_channel.h>
2020-04-02 03:55:44 +01:00
#include <lightningd/coin_mvts.h>
#include <lightningd/notification.h>
#include <lightningd/peer_control.h>
#include <lightningd/runes.h>
#include <onchaind/onchaind_wiregen.h>
#include <wallet/invoices.h>
#include <wallet/txfilter.h>
#include <wallet/wallet.h>
#include <wally_bip32.h>
#define SQLITE_MAX_UINT 0x7FFFFFFFFFFFFFFF
#define DIRECTION_INCOMING 0
#define DIRECTION_OUTGOING 1
/* How many blocks must a UTXO entry be buried under to be considered old enough
* to prune? */
#define UTXO_PRUNE_DEPTH 144
/* 12 hours is usually enough reservation time */
#define RESERVATION_INC (6 * 12)
/* Possible channel state */
enum channel_state_bucket {
IN_OFFERED = 0,
IN_FULLFILLED = 1,
OUT_OFFERED = 2,
OUT_FULLFILLED = 3,
};
/* channel state identifier */
struct channel_state_param {
const char *dir_key;
const char *type_key;
const enum channel_state_bucket state;
};
/* These go in db, so values cannot change (we can't put this into
* lightningd/channel_state.h since it confuses cdump!) */
static enum state_change state_change_in_db(enum state_change s)
{
switch (s) {
case REASON_UNKNOWN:
BUILD_ASSERT(REASON_UNKNOWN == 0);
return s;
case REASON_LOCAL:
BUILD_ASSERT(REASON_LOCAL == 1);
return s;
case REASON_USER:
BUILD_ASSERT(REASON_USER == 2);
return s;
case REASON_REMOTE:
BUILD_ASSERT(REASON_REMOTE == 3);
return s;
case REASON_PROTOCOL:
BUILD_ASSERT(REASON_PROTOCOL == 4);
return s;
case REASON_ONCHAIN:
BUILD_ASSERT(REASON_ONCHAIN == 5);
return s;
}
fatal("%s: %u is invalid", __func__, s);
}
static void outpointfilters_init(struct wallet *w)
{
struct db_stmt *stmt;
struct utxo **utxos = wallet_get_all_utxos(NULL, w);
struct bitcoin_outpoint outpoint;
w->owned_outpoints = outpointfilter_new(w);
for (size_t i = 0; i < tal_count(utxos); i++)
outpointfilter_add(w->owned_outpoints, &utxos[i]->outpoint);
tal_free(utxos);
w->utxoset_outpoints = outpointfilter_new(w);
stmt = db_prepare_v2(
w->db,
SQL("SELECT txid, outnum FROM utxoset WHERE spendheight is NULL"));
db_query_prepared(stmt);
while (db_step(stmt)) {
db_col_txid(stmt, "txid", &outpoint.txid);
outpoint.n = db_col_int(stmt, "outnum");
outpointfilter_add(w->utxoset_outpoints, &outpoint);
}
tal_free(stmt);
}
struct wallet *wallet_new(struct lightningd *ld, struct timers *timers)
{
struct wallet *wallet = tal(ld, struct wallet);
wallet->ld = ld;
wallet->log = new_logger(wallet, ld->log_book, NULL, "wallet");
wallet->keyscan_gap = 50;
trace_span_start("db_setup", wallet);
wallet->db = db_setup(wallet, ld, ld->bip32_base);
trace_span_end(wallet);
db_begin_transaction(wallet->db);
trace_span_start("load_indexes", wallet);
load_indexes(wallet->db, ld->indexes);
trace_span_end(wallet);
trace_span_start("invoices_new", wallet);
wallet->invoices = invoices_new(wallet, wallet, timers);
trace_span_end(wallet);
trace_span_start("outpointfilters_init", wallet);
outpointfilters_init(wallet);
trace_span_end(wallet);
db_commit_transaction(wallet->db);
return wallet;
}
/**
* wallet_add_utxo - Register an UTXO which we (partially) own
*
* Add an UTXO to the set of outputs we care about.
*
* This can fail if we've already seen UTXO.
*/
static bool wallet_add_utxo(struct wallet *w,
const struct utxo *utxo,
enum wallet_output_type type)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT * from outputs WHERE "
"prev_out_tx=? AND prev_out_index=?"));
db_bind_txid(stmt, &utxo->outpoint.txid);
db_bind_int(stmt, utxo->outpoint.n);
db_query_prepared(stmt);
/* If we get a result, that means a clash. */
if (db_step(stmt)) {
db_col_ignore(stmt, "*");
tal_free(stmt);
return false;
}
tal_free(stmt);
stmt = db_prepare_v2(
w->db, SQL("INSERT INTO outputs ("
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey"
", is_in_coinbase"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_txid(stmt, &utxo->outpoint.txid);
db_bind_int(stmt, utxo->outpoint.n);
db_bind_amount_sat(stmt, &utxo->amount);
db_bind_int(stmt, wallet_output_type_in_db(type));
db_bind_int(stmt, OUTPUT_STATE_AVAILABLE);
db_bind_int(stmt, utxo->keyindex);
if (utxo->close_info) {
db_bind_u64(stmt, utxo->close_info->channel_id);
db_bind_node_id(stmt, &utxo->close_info->peer_id);
if (utxo->close_info->commitment_point)
db_bind_pubkey(stmt, utxo->close_info->commitment_point);
else
db_bind_null(stmt);
db_bind_int(stmt, utxo->close_info->option_anchors);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
}
if (utxo->blockheight) {
db_bind_int(stmt, *utxo->blockheight);
} else
db_bind_null(stmt);
if (utxo->spendheight)
db_bind_int(stmt, *utxo->spendheight);
else
db_bind_null(stmt);
db_bind_blob(stmt, utxo->scriptPubkey,
tal_bytelen(utxo->scriptPubkey));
db_bind_int(stmt, utxo->is_in_coinbase);
db_exec_prepared_v2(take(stmt));
return true;
}
/**
2018-02-08 21:43:01 +00:00
* wallet_stmt2output - Extract data from stmt and fill an UTXO
*/
static struct utxo *wallet_stmt2output(const tal_t *ctx, struct db_stmt *stmt)
{
struct utxo *utxo = tal(ctx, struct utxo);
u32 *blockheight, *spendheight;
db_col_txid(stmt, "prev_out_tx", &utxo->outpoint.txid);
utxo->outpoint.n = db_col_int(stmt, "prev_out_index");
utxo->amount = db_col_amount_sat(stmt, "value");
utxo->is_p2sh = db_col_int(stmt, "type") == p2sh_wpkh;
utxo->status = db_col_int(stmt, "status");
utxo->keyindex = db_col_int(stmt, "keyindex");
utxo->is_in_coinbase = db_col_int(stmt, "is_in_coinbase") == 1;
if (!db_col_is_null(stmt, "channel_id")) {
utxo->close_info = tal(utxo, struct unilateral_close_info);
utxo->close_info->channel_id = db_col_u64(stmt, "channel_id");
db_col_node_id(stmt, "peer_id", &utxo->close_info->peer_id);
utxo->close_info->commitment_point
= db_col_optional(utxo->close_info, stmt,
"commitment_point",
pubkey);
utxo->close_info->option_anchors
= db_col_int(stmt, "option_anchor_outputs");
utxo->close_info->csv = db_col_int(stmt, "csv_lock");
} else {
utxo->close_info = NULL;
db_col_ignore(stmt, "peer_id");
db_col_ignore(stmt, "commitment_point");
db_col_ignore(stmt, "option_anchor_outputs");
db_col_ignore(stmt, "csv_lock");
}
utxo->scriptPubkey = db_col_arr(utxo, stmt, "scriptpubkey", u8);
utxo->blockheight = NULL;
utxo->spendheight = NULL;
if (!db_col_is_null(stmt, "confirmation_height")) {
blockheight = tal(utxo, u32);
*blockheight = db_col_int(stmt, "confirmation_height");
utxo->blockheight = blockheight;
}
if (!db_col_is_null(stmt, "spend_height")) {
spendheight = tal(utxo, u32);
*spendheight = db_col_int(stmt, "spend_height");
utxo->spendheight = spendheight;
}
/* This column can be null if 0.9.1 db or below. */
utxo->reserved_til = db_col_int_or_default(stmt, "reserved_til", 0);
return utxo;
}
bool wallet_update_output_status(struct wallet *w,
const struct bitcoin_outpoint *outpoint,
enum output_status oldstatus,
enum output_status newstatus)
{
struct db_stmt *stmt;
size_t changes;
if (oldstatus != OUTPUT_STATE_ANY) {
stmt = db_prepare_v2(
w->db, SQL("UPDATE outputs SET status=? WHERE status=? AND "
"prev_out_tx=? AND prev_out_index=?"));
db_bind_int(stmt, output_status_in_db(newstatus));
db_bind_int(stmt, output_status_in_db(oldstatus));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
} else {
stmt = db_prepare_v2(w->db,
SQL("UPDATE outputs SET status=? WHERE "
"prev_out_tx=? AND prev_out_index=?"));
db_bind_int(stmt, output_status_in_db(newstatus));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
}
db_exec_prepared_v2(stmt);
changes = db_count_changes(stmt);
tal_free(stmt);
return changes > 0;
}
static struct utxo **gather_utxos(const tal_t *ctx, struct db_stmt *stmt STEALS)
{
struct utxo **results;
db_query_prepared(stmt);
results = tal_arr(ctx, struct utxo *, 0);
while (db_step(stmt)) {
struct utxo *u = wallet_stmt2output(results, stmt);
tal_arr_expand(&results, u);
}
tal_free(stmt);
return results;
}
struct utxo **wallet_get_all_utxos(const tal_t *ctx, struct wallet *w)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey "
", reserved_til "
", csv_lock "
", is_in_coinbase "
"FROM outputs"));
return gather_utxos(ctx, stmt);
}
/**
* wallet_get_unspent_utxos - Return reserved and unreserved UTXOs.
*
* Returns a `tal_arr` of `utxo` structs. Double indirection in order
* to be able to steal individual elements onto something else.
*
* Use utxo_is_reserved() to test if it's reserved.
*/
struct utxo **wallet_get_unspent_utxos(const tal_t *ctx, struct wallet *w)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey "
", reserved_til "
", csv_lock "
", is_in_coinbase "
"FROM outputs "
"WHERE status != ?"));
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_SPENT));
return gather_utxos(ctx, stmt);
}
struct utxo **wallet_get_unconfirmed_closeinfo_utxos(const tal_t *ctx,
struct wallet *w)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey"
", reserved_til"
", csv_lock"
", is_in_coinbase"
" FROM outputs"
" WHERE channel_id IS NOT NULL AND "
"confirmation_height IS NULL"));
return gather_utxos(ctx, stmt);
}
struct utxo *wallet_utxo_get(const tal_t *ctx, struct wallet *w,
const struct bitcoin_outpoint *outpoint)
{
struct db_stmt *stmt;
struct utxo *utxo;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey"
", reserved_til"
", csv_lock"
", is_in_coinbase"
" FROM outputs"
" WHERE prev_out_tx = ?"
" AND prev_out_index = ?"));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
utxo = wallet_stmt2output(ctx, stmt);
tal_free(stmt);
return utxo;
}
/* Gather enough utxos to meet feerate, otherwise all we can. */
struct utxo **wallet_utxo_boost(const tal_t *ctx,
struct wallet *w,
u32 blockheight,
struct amount_sat fee_amount,
u32 feerate_target,
size_t *weight)
{
struct utxo **all_utxos = wallet_get_unspent_utxos(tmpctx, w);
struct utxo **utxos = tal_arr(ctx, struct utxo *, 0);
u32 feerate;
/* Select in random order */
tal_arr_randomize(all_utxos, struct utxo *);
/* Can't overflow, it's from our tx! */
if (!amount_feerate(&feerate, fee_amount, *weight))
abort();
for (size_t i = 0; i < tal_count(all_utxos); i++) {
u32 new_feerate;
size_t new_weight;
struct amount_sat new_fee_amount;
/* Convenience var */
struct utxo *utxo = all_utxos[i];
/* Are we already happy? */
if (feerate >= feerate_target)
break;
/* Don't add reserved ones */
if (utxo_is_reserved(utxo, blockheight))
continue;
/* UTXOs must be sane amounts */
if (!amount_sat_add(&new_fee_amount,
fee_amount, utxo->amount))
abort();
new_weight = *weight + utxo_spend_weight(utxo, 0);
if (!amount_feerate(&new_feerate, new_fee_amount, new_weight))
abort();
/* Don't add uneconomic ones! */
if (new_feerate < feerate)
continue;
feerate = new_feerate;
*weight = new_weight;
fee_amount = new_fee_amount;
tal_arr_expand(&utxos, tal_steal(utxos, utxo));
}
return utxos;
}
static void db_set_utxo(struct db *db, const struct utxo *utxo)
{
struct db_stmt *stmt;
if (utxo->status == OUTPUT_STATE_RESERVED)
assert(utxo->reserved_til);
else
assert(!utxo->reserved_til);
stmt = db_prepare_v2(
db, SQL("UPDATE outputs SET status=?, reserved_til=? "
"WHERE prev_out_tx=? AND prev_out_index=?"));
db_bind_int(stmt, output_status_in_db(utxo->status));
db_bind_int(stmt, utxo->reserved_til);
db_bind_txid(stmt, &utxo->outpoint.txid);
db_bind_int(stmt, utxo->outpoint.n);
db_exec_prepared_v2(take(stmt));
}
bool wallet_reserve_utxo(struct wallet *w, struct utxo *utxo,
u32 current_height,
u32 reserve)
{
switch (utxo->status) {
case OUTPUT_STATE_SPENT:
return false;
case OUTPUT_STATE_AVAILABLE:
case OUTPUT_STATE_RESERVED:
break;
case OUTPUT_STATE_ANY:
abort();
}
/* We simple increase existing reservations, which DTRT if we unreserve */
if (utxo->reserved_til >= current_height)
utxo->reserved_til += reserve;
else
utxo->reserved_til = current_height + reserve;
utxo->status = OUTPUT_STATE_RESERVED;
db_set_utxo(w->db, utxo);
return true;
}
void wallet_unreserve_utxo(struct wallet *w, struct utxo *utxo,
u32 current_height,
u32 unreserve)
{
if (utxo->status != OUTPUT_STATE_RESERVED)
fatal("UTXO %s is not reserved",
type_to_string(tmpctx, struct bitcoin_outpoint,
&utxo->outpoint));
if (utxo->reserved_til <= current_height + unreserve) {
utxo->status = OUTPUT_STATE_AVAILABLE;
utxo->reserved_til = 0;
} else
utxo->reserved_til -= unreserve;
db_set_utxo(w->db, utxo);
}
static bool excluded(const struct utxo **excludes,
const struct utxo *utxo)
{
for (size_t i = 0; i < tal_count(excludes); i++) {
if (bitcoin_outpoint_eq(&excludes[i]->outpoint, &utxo->outpoint))
return true;
}
return false;
}
static bool deep_enough(u32 maxheight, const struct utxo *utxo,
u32 current_blockheight)
{
if (utxo->close_info
&& utxo->close_info->option_anchors) {
/* BOLT #3:
* If `option_anchors` applies to the commitment transaction, the
* `to_remote` output is encumbered by a one block csv lock.
*/
if (!utxo->blockheight)
return false;
u32 csv_free = *utxo->blockheight + utxo->close_info->csv - 1;
assert(csv_free >= *utxo->blockheight);
if (csv_free > current_blockheight)
return false;
}
2022-11-08 15:06:55 +00:00
bool immature = utxo_is_immature(utxo, current_blockheight);
if (immature)
return false;
/* If we require confirmations check that we have a
* confirmation height and that it is below the required
* maxheight (current_height - minconf) */
if (maxheight == 0)
return true;
if (!utxo->blockheight)
return false;
return *utxo->blockheight <= maxheight;
}
/* FIXME: Make this wallet_find_utxos, and branch and bound and I've
* left that to @niftynei to do, who actually read the paper! */
struct utxo *wallet_find_utxo(const tal_t *ctx, struct wallet *w,
unsigned current_blockheight,
struct amount_sat *amount_hint,
unsigned feerate_per_kw,
u32 maxheight,
bool nonwrapped,
const struct utxo **excludes)
{
struct db_stmt *stmt;
struct utxo *utxo;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey "
", reserved_til"
", csv_lock"
", is_in_coinbase"
" FROM outputs"
" WHERE status = ?"
" OR (status = ? AND reserved_til <= ?)"
"ORDER BY RANDOM();"));
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_AVAILABLE));
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_RESERVED));
db_bind_u64(stmt, current_blockheight);
/* FIXME: Use feerate + estimate of input cost to establish
* range for amount_hint */
db_query_prepared(stmt);
utxo = NULL;
while (!utxo && db_step(stmt)) {
utxo = wallet_stmt2output(ctx, stmt);
if (excluded(excludes, utxo)
|| (nonwrapped && utxo->is_p2sh)
|| !deep_enough(maxheight, utxo, current_blockheight))
utxo = tal_free(utxo);
}
tal_free(stmt);
return utxo;
}
bool wallet_has_funds(struct wallet *w,
const struct utxo **excludes,
u32 current_blockheight,
struct amount_sat sats)
{
struct db_stmt *stmt;
struct amount_sat total = AMOUNT_SAT(0);
stmt = db_prepare_v2(w->db, SQL("SELECT"
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey "
", reserved_til"
", csv_lock"
", is_in_coinbase"
" FROM outputs"
" WHERE status = ?"
" OR (status = ? AND reserved_til <= ?)"));
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_AVAILABLE));
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_RESERVED));
db_bind_u64(stmt, current_blockheight);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct utxo *utxo = wallet_stmt2output(tmpctx, stmt);
if (excluded(excludes, utxo)
|| !deep_enough(-1U, utxo, current_blockheight)) {
continue;
}
/* Overflow Should Not Happen */
if (!amount_sat_add(&total, total, utxo->amount)) {
db_fatal(w->db, "Invalid value for %s: %s",
type_to_string(tmpctx,
struct bitcoin_outpoint,
&utxo->outpoint),
fmt_amount_sat(tmpctx, utxo->amount));
}
/* If we've found enough, answer is yes. */
if (amount_sat_greater_eq(total, sats)) {
tal_free(stmt);
return true;
}
}
/* Insufficient funds! */
tal_free(stmt);
return false;
}
bool wallet_add_onchaind_utxo(struct wallet *w,
const struct bitcoin_outpoint *outpoint,
const u8 *scriptpubkey,
u32 blockheight,
struct amount_sat amount,
const struct channel *channel,
/* NULL if option_static_remotekey */
const struct pubkey *commitment_point,
u32 csv_lock)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT * from outputs WHERE "
"prev_out_tx=? AND prev_out_index=?"));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_query_prepared(stmt);
/* If we get a result, that means a clash. */
if (db_step(stmt)) {
db_col_ignore(stmt, "*");
tal_free(stmt);
return false;
}
tal_free(stmt);
stmt = db_prepare_v2(
w->db, SQL("INSERT INTO outputs ("
" prev_out_tx"
", prev_out_index"
", value"
", type"
", status"
", keyindex"
", channel_id"
", peer_id"
", commitment_point"
", option_anchor_outputs"
", confirmation_height"
", spend_height"
", scriptpubkey"
", csv_lock"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_bind_amount_sat(stmt, &amount);
db_bind_int(stmt, wallet_output_type_in_db(p2wpkh));
db_bind_int(stmt, OUTPUT_STATE_AVAILABLE);
db_bind_int(stmt, 0);
db_bind_u64(stmt, channel->dbid);
db_bind_node_id(stmt, &channel->peer->id);
if (commitment_point)
db_bind_pubkey(stmt, commitment_point);
else
db_bind_null(stmt);
db_bind_int(stmt,
channel_type_has_anchors(channel->type));
db_bind_int(stmt, blockheight);
/* spendheight */
db_bind_null(stmt);
db_bind_blob(stmt, scriptpubkey, tal_bytelen(scriptpubkey));
db_bind_int(stmt, csv_lock);
db_exec_prepared_v2(take(stmt));
return true;
}
bool wallet_can_spend(struct wallet *w, const u8 *script,
u32 *index, bool *output_is_p2sh)
{
struct ext_key ext;
u64 bip32_max_index = db_get_intvar(w->db, "bip32_max_index", 0);
u32 i;
/* If not one of these, can't be for us. */
if (is_p2sh(script, NULL))
*output_is_p2sh = true;
else if (is_p2wpkh(script, NULL))
*output_is_p2sh = false;
else if (is_p2tr(script, NULL))
*output_is_p2sh = false;
else
return false;
for (i = 0; i <= bip32_max_index + w->keyscan_gap; i++) {
u8 *s;
if (bip32_key_from_parent(w->ld->bip32_base, i,
BIP32_FLAG_KEY_PUBLIC, &ext)
!= WALLY_OK) {
abort();
}
s = scriptpubkey_p2wpkh_derkey(w, ext.pub_key);
if (*output_is_p2sh) {
u8 *p2sh = scriptpubkey_p2sh(w, s);
tal_free(s);
s = p2sh;
}
if (scripteq(s, script)) {
/* If we found a used key in the keyscan_gap we should
* remember that. */
if (i > bip32_max_index)
db_set_intvar(w->db, "bip32_max_index", i);
tal_free(s);
*index = i;
return true;
}
tal_free(s);
/* Try taproot output now */
s = scriptpubkey_p2tr_derkey(w, ext.pub_key);
if (scripteq(s, script)) {
/* If we found a used key in the keyscan_gap we should
* remember that. */
if (i > bip32_max_index)
db_set_intvar(w->db, "bip32_max_index", i);
tal_free(s);
*index = i;
return true;
}
tal_free(s);
}
return false;
}
s64 wallet_get_newindex(struct lightningd *ld)
{
u64 newidx = db_get_intvar(ld->wallet->db, "bip32_max_index", 0) + 1;
if (newidx == BIP32_INITIAL_HARDENED_CHILD)
return -1;
db_set_intvar(ld->wallet->db, "bip32_max_index", newidx);
return newidx;
}
static void wallet_shachain_init(struct wallet *wallet,
struct wallet_shachain *chain)
{
struct db_stmt *stmt;
assert(chain->id == 0);
/* Create shachain */
shachain_init(&chain->chain);
stmt = db_prepare_v2(
wallet->db,
SQL("INSERT INTO shachains (min_index, num_valid) VALUES (?, 0);"));
db_bind_u64(stmt, chain->chain.min_index);
db_exec_prepared_v2(stmt);
chain->id = db_last_insert_id_v2(stmt);
tal_free(stmt);
}
/* TODO(cdecker) Stolen from shachain, move to some appropriate location */
static unsigned int count_trailing_zeroes(uint64_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,
uint64_t index,
const struct secret *hash)
{
struct db_stmt *stmt;
u32 pos = count_trailing_zeroes(index);
struct sha256 s;
bool updated;
BUILD_ASSERT(sizeof(s) == sizeof(*hash));
memcpy(&s, hash, sizeof(s));
assert(index < SQLITE_MAX_UINT);
if (!shachain_add_hash(&chain->chain, index, &s)) {
return false;
}
stmt = db_prepare_v2(
wallet->db,
SQL("UPDATE shachains SET num_valid=?, min_index=? WHERE id=?"));
db_bind_int(stmt, chain->chain.num_valid);
db_bind_u64(stmt, index);
db_bind_u64(stmt, chain->id);
db_exec_prepared_v2(take(stmt));
stmt = db_prepare_v2(wallet->db,
SQL("UPDATE shachain_known SET idx=?, hash=? "
"WHERE shachain_id=? AND pos=?"));
db_bind_u64(stmt, index);
db_bind_secret(stmt, hash);
db_bind_u64(stmt, chain->id);
db_bind_int(stmt, pos);
db_exec_prepared_v2(stmt);
updated = db_count_changes(stmt) == 1;
tal_free(stmt);
if (!updated) {
stmt = db_prepare_v2(
wallet->db, SQL("INSERT INTO shachain_known (shachain_id, "
"pos, idx, hash) VALUES (?, ?, ?, ?);"));
db_bind_u64(stmt, chain->id);
db_bind_int(stmt, pos);
db_bind_u64(stmt, index);
db_bind_secret(stmt, hash);
db_exec_prepared_v2(take(stmt));
}
return true;
}
static bool wallet_shachain_load(struct wallet *wallet, u64 id,
struct wallet_shachain *chain)
{
struct db_stmt *stmt;
chain->id = id;
shachain_init(&chain->chain);
/* Load shachain metadata */
stmt = db_prepare_v2(
wallet->db,
SQL("SELECT min_index, num_valid FROM shachains WHERE id=?"));
db_bind_u64(stmt, id);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return false;
}
chain->chain.min_index = db_col_u64(stmt, "min_index");
chain->chain.num_valid = db_col_u64(stmt, "num_valid");
tal_free(stmt);
/* Load shachain known entries */
stmt = db_prepare_v2(wallet->db,
SQL("SELECT idx, hash, pos FROM shachain_known "
"WHERE shachain_id=?"));
db_bind_u64(stmt, id);
db_query_prepared(stmt);
while (db_step(stmt)) {
int pos = db_col_int(stmt, "pos");
chain->chain.known[pos].index = db_col_u64(stmt, "idx");
db_col_sha256(stmt, "hash", &chain->chain.known[pos].hash);
}
tal_free(stmt);
return true;
}
static struct peer *wallet_peer_load(struct wallet *w, const u64 dbid)
{
const char *addrstr, *err;
struct peer *peer = NULL;
struct node_id id;
struct wireaddr_internal addr;
struct db_stmt *stmt;
stmt = db_prepare_v2(
w->db, SQL("SELECT id, node_id, address, feature_bits FROM peers WHERE id=?;"));
db_bind_u64(stmt, dbid);
db_query_prepared(stmt);
if (!db_step(stmt))
goto done;
if (db_col_is_null(stmt, "node_id")) {
db_col_ignore(stmt, "address");
db_col_ignore(stmt, "id");
db_col_ignore(stmt, "feature_bits");
goto done;
}
db_col_node_id(stmt, "node_id", &id);
/* This can happen for peers last seen on Torv2! */
addrstr = db_col_strdup(tmpctx, stmt, "address");
err = parse_wireaddr_internal(tmpctx, addrstr, chainparams_get_ln_port(chainparams), true, &addr);
if (err) {
log_unusual(w->log, "Unparsable peer address %s (%s): replacing",
addrstr, err);
err = parse_wireaddr_internal(tmpctx, "127.0.0.1:1", chainparams_get_ln_port(chainparams),
false, &addr);
assert(!err);
}
/* FIXME: save incoming in db! */
peer = new_peer(w->ld, db_col_u64(stmt, "id"), &id, &addr, db_col_arr(stmt, stmt, "feature_bits", u8), false);
done:
tal_free(stmt);
return peer;
}
static struct bitcoin_signature *
wallet_htlc_sigs_load(const tal_t *ctx, struct wallet *w, u64 channelid,
bool option_anchors)
{
struct db_stmt *stmt;
struct bitcoin_signature *htlc_sigs = tal_arr(ctx, struct bitcoin_signature, 0);
stmt = db_prepare_v2(
w->db, SQL("SELECT signature FROM htlc_sigs WHERE channelid = ?"
" AND inflight_tx_id is NULL"));
db_bind_u64(stmt, channelid);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct bitcoin_signature sig;
db_col_signature(stmt, "signature", &sig.s);
/* BOLT #3:
* ## HTLC-Timeout and HTLC-Success Transactions
*...
* * if `option_anchors` applies to this commitment
* transaction, `SIGHASH_SINGLE|SIGHASH_ANYONECANPAY` is
* used as described in [BOLT #5]
*/
if (option_anchors)
sig.sighash_type = SIGHASH_SINGLE|SIGHASH_ANYONECANPAY;
else
sig.sighash_type = SIGHASH_ALL;
tal_arr_expand(&htlc_sigs, sig);
}
tal_free(stmt);
log_debug(w->log, "Loaded %zu HTLC signatures from DB",
tal_count(htlc_sigs));
return htlc_sigs;
}
bool wallet_remote_ann_sigs_load(struct wallet *w,
const struct channel *chan,
secp256k1_ecdsa_signature *remote_ann_node_sig,
secp256k1_ecdsa_signature *remote_ann_bitcoin_sig)
{
struct db_stmt *stmt;
bool res;
stmt = db_prepare_v2(
w->db, SQL("SELECT remote_ann_node_sig, remote_ann_bitcoin_sig"
" FROM channels WHERE id = ?"));
db_bind_u64(stmt, chan->dbid);
db_query_prepared(stmt);
res = db_step(stmt);
/* This must succeed, since we know the channel exists */
assert(res);
/* if only one sig exists, forget the sig and hope peer send new ones*/
if (db_col_is_null(stmt, "remote_ann_node_sig")
|| db_col_is_null(stmt, "remote_ann_bitcoin_sig")) {
db_col_ignore(stmt, "remote_ann_bitcoin_sig");
tal_free(stmt);
return false;
}
if (!db_col_signature(stmt, "remote_ann_node_sig", remote_ann_node_sig))
db_fatal(w->db, "Failed to decode remote_ann_node_sig for id %"PRIu64, chan->dbid);
if (!db_col_signature(stmt, "remote_ann_bitcoin_sig", remote_ann_bitcoin_sig))
db_fatal(w->db, "Failed to decode remote_ann_bitcoin_sig for id %"PRIu64, chan->dbid);
tal_free(stmt);
return true;
}
void wallet_remote_ann_sigs_clear(struct wallet *w, const struct channel *chan)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("UPDATE channels"
" SET remote_ann_node_sig=?, remote_ann_bitcoin_sig=?"
" WHERE id = ?"));
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
}
static struct fee_states *wallet_channel_fee_states_load(struct wallet *w,
const u64 id,
enum side opener)
{
struct fee_states *fee_states;
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT hstate, feerate_per_kw FROM channel_feerates WHERE channel_id = ?"));
db_bind_u64(stmt, id);
db_query_prepared(stmt);
/* Start with blank slate. */
fee_states = new_fee_states(w, opener, NULL);
while (db_step(stmt)) {
enum htlc_state hstate = htlc_state_in_db(db_col_int(stmt, "hstate"));
u32 feerate = db_col_int(stmt, "feerate_per_kw");
if (fee_states->feerate[hstate] != NULL) {
log_broken(w->log,
"duplicate channel_feerates for %s id %"PRIu64,
htlc_state_name(hstate), id);
fee_states = tal_free(fee_states);
break;
}
fee_states->feerate[hstate] = tal_dup(fee_states, u32, &feerate);
}
tal_free(stmt);
if (fee_states && !fee_states_valid(fee_states, opener)) {
log_broken(w->log,
"invalid channel_feerates for id %"PRIu64, id);
fee_states = tal_free(fee_states);
}
return fee_states;
}
static struct height_states *wallet_channel_height_states_load(struct wallet *w,
const u64 id,
enum side opener)
{
struct height_states *states;
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT hstate, blockheight FROM channel_blockheights WHERE channel_id = ?"));
db_bind_u64(stmt, id);
db_query_prepared(stmt);
/* Start with blank slate. */
states = new_height_states(w, opener, NULL);
while (db_step(stmt)) {
enum htlc_state hstate = htlc_state_in_db(db_col_int(stmt, "hstate"));
u32 blockheight = db_col_int(stmt, "blockheight");
if (states->height[hstate] != NULL) {
log_broken(w->log,
"duplicate channel_blockheights for %s id %"PRIu64,
htlc_state_name(hstate), id);
states = tal_free(states);
break;
}
states->height[hstate] = tal_dup(states, u32, &blockheight);
}
tal_free(stmt);
if (states && !height_states_valid(states, opener)) {
log_broken(w->log,
"invalid channel_blockheight for id %"PRIu64, id);
states = tal_free(states);
}
return states;
}
void wallet_inflight_add(struct wallet *w, struct channel_inflight *inflight)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO channel_funding_inflights ("
" channel_id"
", funding_tx_id"
", funding_tx_outnum"
", funding_feerate"
", funding_satoshi"
", our_funding_satoshi"
", funding_psbt"
", funding_tx_remote_sigs_received"
", last_tx"
", last_sig"
", lease_commit_sig"
", lease_chan_max_msat"
", lease_chan_max_ppt"
", lease_expiry"
", lease_blockheight_start"
", lease_fee"
", lease_satoshi"
", splice_amnt"
", i_am_initiator"
", force_sign_first"
") VALUES ("
"?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_u64(stmt, inflight->channel->dbid);
db_bind_txid(stmt, &inflight->funding->outpoint.txid);
db_bind_int(stmt, inflight->funding->outpoint.n);
db_bind_int(stmt, inflight->funding->feerate);
db_bind_amount_sat(stmt, &inflight->funding->total_funds);
db_bind_amount_sat(stmt, &inflight->funding->our_funds);
db_bind_psbt(stmt, inflight->funding_psbt);
db_bind_int(stmt, inflight->remote_tx_sigs ? 1 : 0);
if (inflight->last_tx) {
db_bind_psbt(stmt, inflight->last_tx->psbt);
db_bind_signature(stmt, &inflight->last_sig.s);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
if (inflight->lease_expiry != 0) {
db_bind_signature(stmt, inflight->lease_commit_sig);
db_bind_int(stmt, inflight->lease_chan_max_msat);
db_bind_int(stmt, inflight->lease_chan_max_ppt);
db_bind_int(stmt, inflight->lease_expiry);
db_bind_int(stmt, inflight->lease_blockheight_start);
db_bind_amount_msat(stmt, &inflight->lease_fee);
db_bind_amount_sat(stmt, &inflight->lease_amt);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_int(stmt, 0);
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_int(stmt, 0);
}
db_bind_s64(stmt, inflight->funding->splice_amnt);
db_bind_int(stmt, inflight->i_am_initiator);
db_bind_int(stmt, inflight->force_sign_first);
db_exec_prepared_v2(stmt);
assert(!stmt->error);
tal_free(stmt);
}
void wallet_inflight_save(struct wallet *w,
struct channel_inflight *inflight)
{
struct db_stmt *stmt;
/* The *only* thing you can update on an
* inflight is the funding PSBT (to add sigs)
* and the last_tx/last_sig if this is for a splice */
stmt = db_prepare_v2(w->db,
SQL("UPDATE channel_funding_inflights SET"
" funding_psbt=?" // 0
", funding_tx_remote_sigs_received=?" // 1
", last_tx=?" // 2
", last_sig=?" // 3
" WHERE"
" channel_id=?" // 4
" AND funding_tx_id=?" // 5
" AND funding_tx_outnum=?")); // 6
db_bind_psbt(stmt, inflight->funding_psbt);
db_bind_int(stmt, inflight->remote_tx_sigs);
if (inflight->last_tx) {
db_bind_psbt(stmt, inflight->last_tx->psbt);
db_bind_signature(stmt, &inflight->last_sig.s);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_u64(stmt, inflight->channel->dbid);
db_bind_txid(stmt, &inflight->funding->outpoint.txid);
db_bind_int(stmt, inflight->funding->outpoint.n);
db_exec_prepared_v2(take(stmt));
}
void wallet_channel_clear_inflights(struct wallet *w,
struct channel *chan)
{
struct db_stmt *stmt;
struct channel_inflight *inflight;
/* Remove all the inflights for the channel */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_funding_inflights"
" WHERE channel_id = ?"));
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
/* Empty out the list too */
while ((inflight = list_tail(&chan->inflights,
struct channel_inflight, list)))
tal_free(inflight);
}
static struct channel_inflight *
wallet_stmt2inflight(struct wallet *w, struct db_stmt *stmt,
struct channel *chan)
{
struct amount_sat funding_sat, our_funding_sat;
struct amount_msat lease_fee;
struct bitcoin_outpoint funding;
struct bitcoin_signature last_sig;
struct bitcoin_tx *last_tx;
struct channel_inflight *inflight;
s64 splice_amnt;
bool i_am_initiator, force_sign_first;
secp256k1_ecdsa_signature *lease_commit_sig;
u32 lease_blockheight_start;
u64 lease_chan_max_msat;
u16 lease_chan_max_ppt;
struct amount_sat lease_amt;
db_col_txid(stmt, "funding_tx_id", &funding.txid);
funding.n = db_col_int(stmt, "funding_tx_outnum"),
funding_sat = db_col_amount_sat(stmt, "funding_satoshi");
our_funding_sat = db_col_amount_sat(stmt, "our_funding_satoshi");
if (!db_col_is_null(stmt, "lease_commit_sig")) {
lease_commit_sig = tal(tmpctx, secp256k1_ecdsa_signature);
db_col_signature(stmt, "lease_commit_sig", lease_commit_sig);
lease_chan_max_msat = db_col_u64(stmt, "lease_chan_max_msat");
lease_chan_max_ppt = db_col_int(stmt, "lease_chan_max_ppt");
lease_blockheight_start = db_col_int(stmt, "lease_blockheight_start");
lease_fee = db_col_amount_msat(stmt, "lease_fee");
lease_amt = db_col_amount_sat(stmt, "lease_satoshi");
} else {
lease_commit_sig = NULL;
lease_chan_max_msat = 0;
lease_chan_max_ppt = 0;
lease_blockheight_start = 0;
lease_fee = AMOUNT_MSAT(0);
lease_amt = AMOUNT_SAT(0);
db_col_ignore(stmt, "lease_chan_max_msat");
db_col_ignore(stmt, "lease_chan_max_ppt");
db_col_ignore(stmt, "lease_blockheight_start");
db_col_ignore(stmt, "lease_fee");
db_col_ignore(stmt, "lease_satoshi");
}
splice_amnt = db_col_s64(stmt, "splice_amnt");
i_am_initiator = db_col_int(stmt, "i_am_initiator");
force_sign_first = db_col_int(stmt, "force_sign_first");
inflight = new_inflight(chan, &funding,
db_col_int(stmt, "funding_feerate"),
funding_sat,
our_funding_sat,
db_col_psbt(tmpctx, stmt, "funding_psbt"),
db_col_int(stmt, "lease_expiry"),
lease_commit_sig,
lease_chan_max_msat,
lease_chan_max_ppt,
lease_blockheight_start,
lease_fee,
lease_amt,
splice_amnt,
i_am_initiator,
force_sign_first);
/* last_tx is null for not yet committed
* channels + static channel backup recoveries */
if (!db_col_is_null(stmt, "last_tx")) {
last_tx = db_col_psbt_to_tx(tmpctx, stmt, "last_tx");
if (!last_tx)
db_fatal(w->db, "Failed to decode inflight psbt %s",
tal_hex(tmpctx, db_col_arr(tmpctx, stmt,
"last_tx", u8)));
if (!db_col_signature(stmt, "last_sig", &last_sig.s))
db_fatal(w->db, "Failed to decode inflight signature %s",
tal_hex(tmpctx, db_col_arr(tmpctx, stmt,
"last_sig", u8)));
last_sig.sighash_type = SIGHASH_ALL;
inflight_set_last_tx(inflight, last_tx, last_sig);
} else
db_col_ignore(stmt, "last_sig");
/* Pull out the serialized tx-sigs-received-ness */
inflight->remote_tx_sigs = db_col_int(stmt, "funding_tx_remote_sigs_received");
return inflight;
}
static bool wallet_channel_load_inflights(struct wallet *w,
struct channel *chan)
{
bool ok = true;
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" funding_tx_id"
", funding_tx_outnum"
", funding_feerate"
", funding_satoshi"
", our_funding_satoshi"
", funding_psbt"
", last_tx"
", last_sig"
", funding_tx_remote_sigs_received"
", lease_expiry"
", lease_commit_sig"
", lease_chan_max_msat"
", lease_chan_max_ppt"
", lease_blockheight_start"
", lease_fee"
", lease_satoshi"
", splice_amnt"
", i_am_initiator"
", force_sign_first"
" FROM channel_funding_inflights"
" WHERE channel_id = ?"
" ORDER BY funding_feerate"));
db_bind_u64(stmt, chan->dbid);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct channel_inflight *inflight;
inflight = wallet_stmt2inflight(w, stmt, chan);
if (!inflight) {
ok = false;
break;
}
}
tal_free(stmt);
return ok;
}
static bool wallet_channel_config_load(struct wallet *w, const u64 id,
struct channel_config *cc)
{
bool ok = true;
const char *query = SQL(
"SELECT dust_limit_satoshis, max_htlc_value_in_flight_msat, "
"channel_reserve_satoshis, htlc_minimum_msat, to_self_delay, "
"max_accepted_htlcs, max_dust_htlc_exposure_msat"
" FROM channel_configs WHERE id= ? ;");
struct db_stmt *stmt = db_prepare_v2(w->db, query);
db_bind_u64(stmt, id);
db_query_prepared(stmt);
if (!db_step(stmt))
return false;
cc->id = id;
cc->dust_limit = db_col_amount_sat(stmt, "dust_limit_satoshis");
cc->max_htlc_value_in_flight = db_col_amount_msat(stmt, "max_htlc_value_in_flight_msat");
cc->channel_reserve = db_col_amount_sat(stmt, "channel_reserve_satoshis");
cc->htlc_minimum = db_col_amount_msat(stmt, "htlc_minimum_msat");
cc->to_self_delay = db_col_int(stmt, "to_self_delay");
cc->max_accepted_htlcs = db_col_int(stmt, "max_accepted_htlcs");
cc->max_dust_htlc_exposure_msat = db_col_amount_msat(stmt, "max_dust_htlc_exposure_msat");
tal_free(stmt);
return ok;
}
/**
* wallet_stmt2channel - Helper to populate a wallet_channel from a `db_stmt`
*/
static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stmt)
{
bool ok = true;
struct channel_info channel_info;
struct fee_states *fee_states;
struct height_states *height_states;
struct short_channel_id *scid, *alias[NUM_SIDES];
struct channel_id cid;
struct channel *chan;
u64 peer_dbid;
struct peer *peer;
struct wallet_shachain wshachain;
struct channel_config our_config;
struct bitcoin_outpoint funding;
struct bitcoin_outpoint *shutdown_wrong_funding;
struct bitcoin_signature *last_sig;
struct bitcoin_tx *last_tx;
u8 *remote_shutdown_scriptpubkey;
u8 *local_shutdown_scriptpubkey;
struct changed_htlc *last_sent_commit;
s64 final_key_idx, channel_config_id;
struct basepoints local_basepoints;
struct pubkey local_funding_pubkey;
struct pubkey *future_per_commitment_point;
struct amount_sat funding_sat, our_funding_sat;
struct amount_msat push_msat, our_msat, msat_to_us_min, msat_to_us_max, htlc_minimum_msat, htlc_maximum_msat;
struct channel_type *type;
secp256k1_ecdsa_signature *lease_commit_sig;
u32 lease_chan_max_msat;
u16 lease_chan_max_ppt;
bool ignore_fee_limits;
struct peer_update *remote_update;
peer_dbid = db_col_u64(stmt, "peer_id");
peer = find_peer_by_dbid(w->ld, peer_dbid);
if (!peer) {
peer = wallet_peer_load(w, peer_dbid);
if (!peer) {
return NULL;
}
}
scid = db_col_optional(tmpctx, stmt, "scid", short_channel_id);
alias[LOCAL] = db_col_optional(tmpctx, stmt, "alias_local",
short_channel_id);
alias[REMOTE] = db_col_optional(tmpctx, stmt, "alias_remote",
short_channel_id);
ok &= wallet_shachain_load(w, db_col_u64(stmt, "shachain_remote_id"),
&wshachain);
remote_shutdown_scriptpubkey = db_col_arr(tmpctx, stmt,
"shutdown_scriptpubkey_remote", u8);
local_shutdown_scriptpubkey = db_col_arr(tmpctx, stmt,
"shutdown_scriptpubkey_local", u8);
/* Do we have a last_sent_commit, if yes, populate */
if (!db_col_is_null(stmt, "last_sent_commit")) {
const u8 *cursor = db_col_blob(stmt, "last_sent_commit");
size_t len = db_col_bytes(stmt, "last_sent_commit");
size_t n = 0;
last_sent_commit = tal_arr(tmpctx, struct changed_htlc, n);
while (len) {
tal_resize(&last_sent_commit, n+1);
fromwire_changed_htlc(&cursor, &len,
&last_sent_commit[n++]);
}
} else
last_sent_commit = NULL;
#ifdef COMPAT_V060
if (!last_sent_commit && !db_col_is_null(stmt, "last_sent_commit_state")) {
last_sent_commit = tal(tmpctx, struct changed_htlc);
last_sent_commit->newstate = db_col_u64(stmt, "last_sent_commit_state");
last_sent_commit->id = db_col_u64(stmt, "last_sent_commit_id");
}
#endif
db_col_ignore(stmt, "last_sent_commit_state");
db_col_ignore(stmt, "last_sent_commit_id");
future_per_commitment_point = db_col_optional(tmpctx, stmt,
"future_per_commitment_point",
pubkey);
db_col_channel_id(stmt, "full_channel_id", &cid);
channel_config_id = db_col_u64(stmt, "channel_config_local");
ok &= wallet_channel_config_load(w, channel_config_id, &our_config);
db_col_sha256d(stmt, "funding_tx_id", &funding.txid.shad);
funding.n = db_col_int(stmt, "funding_tx_outnum"),
/* Populate channel_info */
db_col_pubkey(stmt, "fundingkey_remote", &channel_info.remote_fundingkey);
db_col_pubkey(stmt, "revocation_basepoint_remote", &channel_info.theirbase.revocation);
db_col_pubkey(stmt, "payment_basepoint_remote", &channel_info.theirbase.payment);
db_col_pubkey(stmt, "htlc_basepoint_remote", &channel_info.theirbase.htlc);
db_col_pubkey(stmt, "delayed_payment_basepoint_remote", &channel_info.theirbase.delayed_payment);
db_col_pubkey(stmt, "per_commit_remote", &channel_info.remote_per_commit);
db_col_pubkey(stmt, "old_per_commit_remote", &channel_info.old_remote_per_commit);
wallet_channel_config_load(w, db_col_u64(stmt, "channel_config_remote"),
&channel_info.their_config);
fee_states
= wallet_channel_fee_states_load(w,
db_col_u64(stmt, "id"),
db_col_int(stmt, "funder"));
if (!fee_states)
ok = false;
if (!ok) {
tal_free(fee_states);
return NULL;
}
/* Blockheight states for the channel! */
height_states
= wallet_channel_height_states_load(w,
db_col_u64(stmt, "id"),
db_col_int(stmt, "funder"));
if (!height_states)
ok = false;
if (!ok) {
tal_free(height_states);
return NULL;
}
final_key_idx = db_col_u64(stmt, "shutdown_keyidx_local");
if (final_key_idx < 0) {
tal_free(fee_states);
log_broken(w->log, "%s: Final key < 0", __func__);
return NULL;
}
db_col_pubkey(stmt, "revocation_basepoint_local",
&local_basepoints.revocation);
db_col_pubkey(stmt, "payment_basepoint_local",
&local_basepoints.payment);
db_col_pubkey(stmt, "htlc_basepoint_local",
&local_basepoints.htlc);
db_col_pubkey(stmt, "delayed_payment_basepoint_local",
&local_basepoints.delayed_payment);
db_col_pubkey(stmt, "funding_pubkey_local", &local_funding_pubkey);
if (db_col_is_null(stmt, "shutdown_wrong_txid")) {
db_col_ignore(stmt, "shutdown_wrong_outnum");
shutdown_wrong_funding = NULL;
} else {
shutdown_wrong_funding = tal(tmpctx, struct bitcoin_outpoint);
db_col_txid(stmt, "shutdown_wrong_txid",
&shutdown_wrong_funding->txid);
shutdown_wrong_funding->n
= db_col_int(stmt, "shutdown_wrong_outnum");
}
funding_sat = db_col_amount_sat(stmt, "funding_satoshi");
our_funding_sat = db_col_amount_sat(stmt, "our_funding_satoshi");
push_msat = db_col_amount_msat(stmt, "push_msatoshi");
our_msat = db_col_amount_msat(stmt, "msatoshi_local");
msat_to_us_min = db_col_amount_msat(stmt, "msatoshi_to_us_min");
msat_to_us_max = db_col_amount_msat(stmt, "msatoshi_to_us_max");
htlc_minimum_msat = db_col_amount_msat(stmt, "htlc_minimum_msat");
htlc_maximum_msat = db_col_amount_msat(stmt, "htlc_maximum_msat");
ignore_fee_limits = db_col_int(stmt, "ignore_fee_limits");
if (!db_col_is_null(stmt, "lease_commit_sig")) {
lease_commit_sig = tal(w, secp256k1_ecdsa_signature);
db_col_signature(stmt, "lease_commit_sig", lease_commit_sig);
lease_chan_max_msat = db_col_int(stmt, "lease_chan_max_msat");
lease_chan_max_ppt = db_col_int(stmt, "lease_chan_max_ppt");
} else {
db_col_ignore(stmt, "lease_chan_max_msat");
db_col_ignore(stmt, "lease_chan_max_ppt");
lease_commit_sig = NULL;
lease_chan_max_msat = 0;
lease_chan_max_ppt = 0;
}
type = db_col_channel_type(NULL, stmt, "channel_type");
/* last_tx is null for stub channels used for recovering funds through
* Static channel backups. */
if (!db_col_is_null(stmt, "last_tx")) {
last_tx = db_col_psbt_to_tx(tmpctx, stmt, "last_tx");
if (!last_tx)
db_fatal(w->db, "Failed to decode channel %s psbt %s",
type_to_string(tmpctx, struct channel_id, &cid),
tal_hex(tmpctx, db_col_arr(tmpctx, stmt,
"last_tx", u8)));
last_sig = tal(tmpctx, struct bitcoin_signature);
db_col_signature(stmt, "last_sig", &last_sig->s);
last_sig->sighash_type = SIGHASH_ALL;
} else {
last_tx = NULL;
last_sig = NULL;
}
if (!db_col_is_null(stmt, "remote_cltv_expiry_delta")) {
remote_update = tal(NULL, struct peer_update);
if (scid)
remote_update->scid = *scid;
else
remote_update->scid = *alias[LOCAL];
remote_update->fee_base = db_col_int(stmt, "remote_feerate_base");
remote_update->fee_ppm = db_col_int(stmt, "remote_feerate_ppm");
remote_update->cltv_delta = db_col_int(stmt, "remote_cltv_expiry_delta");
remote_update->htlc_minimum_msat = db_col_amount_msat(stmt, "remote_htlc_minimum_msat");
remote_update->htlc_maximum_msat = db_col_amount_msat(stmt, "remote_htlc_maximum_msat");
} else {
remote_update = NULL;
db_col_ignore(stmt, "remote_feerate_base");
db_col_ignore(stmt, "remote_feerate_ppm");
db_col_ignore(stmt, "remote_cltv_expiry_delta");
db_col_ignore(stmt, "remote_htlc_minimum_msat");
db_col_ignore(stmt, "remote_htlc_maximum_msat");
}
chan = new_channel(peer, db_col_u64(stmt, "id"),
&wshachain,
channel_state_in_db(db_col_int(stmt, "state")),
db_col_int(stmt, "funder"),
NULL, /* Set up fresh log */
"Loaded from database",
db_col_int(stmt, "channel_flags"),
db_col_int(stmt, "require_confirm_inputs_local") != 0,
db_col_int(stmt, "require_confirm_inputs_remote") != 0,
&our_config,
db_col_int(stmt, "minimum_depth"),
db_col_u64(stmt, "next_index_local"),
db_col_u64(stmt, "next_index_remote"),
db_col_u64(stmt, "next_htlc_id"),
&funding,
funding_sat,
push_msat,
our_funding_sat,
db_col_int(stmt, "funding_locked_remote") != 0,
scid,
alias[LOCAL],
alias[REMOTE],
&cid,
our_msat,
msat_to_us_min, /* msatoshi_to_us_min */
msat_to_us_max, /* msatoshi_to_us_max */
last_tx,
last_sig,
wallet_htlc_sigs_load(tmpctx, w,
db_col_u64(stmt, "id"),
channel_type_has_anchors(type)),
&channel_info,
take(fee_states),
remote_shutdown_scriptpubkey,
local_shutdown_scriptpubkey,
final_key_idx,
db_col_int(stmt, "last_was_revoke") != 0,
last_sent_commit,
db_col_u64(stmt, "first_blocknum"),
db_col_int(stmt, "min_possible_feerate"),
db_col_int(stmt, "max_possible_feerate"),
&local_basepoints, &local_funding_pubkey,
future_per_commitment_point,
db_col_int(stmt, "feerate_base"),
db_col_int(stmt, "feerate_ppm"),
db_col_arr(tmpctx, stmt, "remote_upfront_shutdown_script", u8),
db_col_u64(stmt, "local_static_remotekey_start"),
db_col_u64(stmt, "remote_static_remotekey_start"),
type,
db_col_int(stmt, "closer"),
state_change_in_db(db_col_int(stmt, "state_change_reason")),
shutdown_wrong_funding,
take(height_states),
db_col_int(stmt, "lease_expiry"),
lease_commit_sig,
lease_chan_max_msat,
lease_chan_max_ppt,
htlc_minimum_msat,
htlc_maximum_msat,
ignore_fee_limits,
remote_update,
db_col_u64(stmt, "last_stable_connection"));
if (!wallet_channel_load_inflights(w, chan)) {
tal_free(chan);
return NULL;
}
return chan;
}
static struct closed_channel *wallet_stmt2closed_channel(const tal_t *ctx,
struct wallet *w,
struct db_stmt *stmt)
{
struct closed_channel *cc = tal(ctx, struct closed_channel);
/* Can be missing in older dbs! */
cc->peer_id = db_col_optional(cc, stmt, "p.node_id", node_id);
db_col_channel_id(stmt, "full_channel_id", &cc->cid);
cc->scid = db_col_optional(cc, stmt, "scid", short_channel_id);
cc->alias[LOCAL] = db_col_optional(cc, stmt, "alias_local",
short_channel_id);
cc->alias[REMOTE] = db_col_optional(cc, stmt, "alias_remote",
short_channel_id);
cc->opener = db_col_int(stmt, "funder");
cc->closer = db_col_int(stmt, "closer");
cc->channel_flags = db_col_int(stmt, "channel_flags");
cc->next_index[LOCAL] = db_col_u64(stmt, "next_index_local");
cc->next_index[REMOTE] = db_col_u64(stmt, "next_index_remote");
cc->next_htlc_id = db_col_u64(stmt, "next_htlc_id");
db_col_sha256d(stmt, "funding_tx_id", &cc->funding.txid.shad);
cc->funding.n = db_col_int(stmt, "funding_tx_outnum");
cc->funding_sats = db_col_amount_sat(stmt, "funding_satoshi");
cc->push = db_col_amount_msat(stmt, "push_msatoshi");
cc->our_msat = db_col_amount_msat(stmt, "msatoshi_local");
cc->msat_to_us_min = db_col_amount_msat(stmt, "msatoshi_to_us_min");
cc->msat_to_us_max = db_col_amount_msat(stmt, "msatoshi_to_us_max");
cc->last_stable_connection = db_col_u64(stmt, "last_stable_connection");
/* last_tx is null for stub channels used for recovering funds through
* Static channel backups. */
if (!db_col_is_null(stmt, "last_tx"))
cc->last_tx = db_col_psbt_to_tx(cc, stmt, "last_tx");
else
cc->last_tx = NULL;
cc->type = db_col_channel_type(cc, stmt, "channel_type");
cc->state_change_cause
= state_change_in_db(db_col_int(stmt, "state_change_reason"));
cc->leased = !db_col_is_null(stmt, "lease_commit_sig");
return cc;
}
struct closed_channel **wallet_load_closed_channels(const tal_t *ctx,
struct wallet *w)
{
struct db_stmt *stmt;
struct closed_channel **chans = tal_arr(ctx, struct closed_channel *, 0);
/* We load all channels */
stmt = db_prepare_v2(w->db, SQL("SELECT "
" p.node_id"
", full_channel_id"
", scid"
", alias_local"
", alias_remote"
", funder"
", closer"
", channel_flags"
", next_index_local"
", next_index_remote"
", next_htlc_id"
", funding_tx_id"
", funding_tx_outnum"
", funding_satoshi"
", push_msatoshi"
", msatoshi_local"
", msatoshi_to_us_min"
", msatoshi_to_us_max"
", last_tx"
", channel_type"
", state_change_reason"
", lease_commit_sig"
", last_stable_connection"
" FROM channels"
" LEFT JOIN peers p ON p.id = peer_id"
" WHERE state = ?;"));
db_bind_int(stmt, CLOSED);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct closed_channel *cc = wallet_stmt2closed_channel(chans,
w, stmt);
tal_arr_expand(&chans, cc);
}
tal_free(stmt);
return chans;
}
static void set_max_channel_dbid(struct wallet *w)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT id FROM channels ORDER BY id DESC LIMIT 1;"));
db_query_prepared(stmt);
w->max_channel_dbid = 0;
if (db_step(stmt))
w->max_channel_dbid = db_col_u64(stmt, "id");
tal_free(stmt);
}
static bool wallet_channels_load_active(struct wallet *w)
{
bool ok = true;
struct db_stmt *stmt;
int count = 0;
/* We load all channels */
stmt = db_prepare_v2(w->db, SQL("SELECT"
" id"
", peer_id"
", scid"
", full_channel_id"
", channel_config_local"
", channel_config_remote"
", state"
", funder"
", channel_flags"
", require_confirm_inputs_local"
", require_confirm_inputs_remote"
", minimum_depth"
", next_index_local"
", next_index_remote"
", next_htlc_id"
", funding_tx_id"
", funding_tx_outnum"
", funding_satoshi"
", our_funding_satoshi"
", funding_locked_remote"
", push_msatoshi"
", msatoshi_local"
", fundingkey_remote"
", revocation_basepoint_remote"
", payment_basepoint_remote"
", htlc_basepoint_remote"
", delayed_payment_basepoint_remote"
", per_commit_remote"
", old_per_commit_remote"
", shachain_remote_id"
", shutdown_scriptpubkey_remote"
", shutdown_keyidx_local"
", last_sent_commit_state"
", last_sent_commit_id"
", last_tx"
", last_sig"
", last_was_revoke"
", first_blocknum"
", min_possible_feerate"
", max_possible_feerate"
", msatoshi_to_us_min"
", msatoshi_to_us_max"
", future_per_commitment_point"
", last_sent_commit"
", feerate_base"
", feerate_ppm"
", remote_upfront_shutdown_script"
", local_static_remotekey_start"
", remote_static_remotekey_start"
", channel_type"
", shutdown_scriptpubkey_local"
", closer"
", state_change_reason"
", revocation_basepoint_local"
", payment_basepoint_local"
", htlc_basepoint_local"
", delayed_payment_basepoint_local"
", funding_pubkey_local"
", shutdown_wrong_txid"
", shutdown_wrong_outnum"
", lease_expiry"
", lease_commit_sig"
", lease_chan_max_msat"
", lease_chan_max_ppt"
", htlc_minimum_msat"
", htlc_maximum_msat"
", alias_local"
", alias_remote"
", ignore_fee_limits"
", remote_feerate_base"
", remote_feerate_ppm"
", remote_cltv_expiry_delta"
", remote_htlc_minimum_msat"
", remote_htlc_maximum_msat"
", last_stable_connection"
" FROM channels"
" WHERE state != ?;")); //? 0
db_bind_int(stmt, CLOSED);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct channel *c = wallet_stmt2channel(w, stmt);
if (!c) {
ok = false;
break;
}
count++;
}
log_debug(w->log, "Loaded %d channels from DB", count);
tal_free(stmt);
return ok;
}
bool wallet_init_channels(struct wallet *w)
{
/* We set the max channel database id separately */
set_max_channel_dbid(w);
return wallet_channels_load_active(w);
}
static enum channel_state_bucket get_state_channel_db(const char *dir, const char *typ)
{
enum channel_state_bucket channel_state = IN_OFFERED;
if (streq(dir, "out"))
channel_state += 2;
if (streq(typ, "fulfilled"))
channel_state += 1;
return channel_state;
}
static
void wallet_channel_stats_incr_x(struct wallet *w,
char const *dir,
char const *typ,
u64 cdbid,
struct amount_msat msat)
{
struct db_stmt *stmt;
const char *query = NULL;
switch (get_state_channel_db(dir, typ)) {
case IN_OFFERED:
query = SQL("UPDATE channels"
" SET in_payments_offered = COALESCE(in_payments_offered, 0) + 1"
" , in_msatoshi_offered = COALESCE(in_msatoshi_offered, 0) + ?"
" WHERE id = ?;");
break;
case IN_FULLFILLED:
query = SQL("UPDATE channels"
" SET in_payments_fulfilled = COALESCE(in_payments_fulfilled, 0) + 1"
" , in_msatoshi_fulfilled = COALESCE(in_msatoshi_fulfilled, 0) + ?"
" WHERE id = ?;");
break;
case OUT_OFFERED:
query = SQL("UPDATE channels"
" SET out_payments_offered = COALESCE(out_payments_offered, 0) + 1"
" , out_msatoshi_offered = COALESCE(out_msatoshi_offered, 0) + ?"
" WHERE id = ?;");
break;
case OUT_FULLFILLED:
query = SQL("UPDATE channels"
" SET out_payments_fulfilled = COALESCE(out_payments_fulfilled, 0) + 1"
" , out_msatoshi_fulfilled = COALESCE(out_msatoshi_fulfilled, 0) + ?"
" WHERE id = ?;");
break;
}
// Sanity check!
if (!query)
fatal("Unknown channel state key (direction %s, type %s)", dir, typ);
stmt = db_prepare_v2(w->db, query);
db_bind_amount_msat(stmt, &msat);
db_bind_u64(stmt, cdbid);
db_exec_prepared_v2(take(stmt));
}
void wallet_channel_stats_incr_in_offered(struct wallet *w, u64 id,
struct amount_msat m)
{
wallet_channel_stats_incr_x(w, "in", "offered", id, m);
}
void wallet_channel_stats_incr_in_fulfilled(struct wallet *w, u64 id,
struct amount_msat m)
{
wallet_channel_stats_incr_x(w, "in", "fulfilled", id, m);
}
void wallet_channel_stats_incr_out_offered(struct wallet *w, u64 id,
struct amount_msat m)
{
wallet_channel_stats_incr_x(w, "out", "offered", id, m);
}
void wallet_channel_stats_incr_out_fulfilled(struct wallet *w, u64 id,
struct amount_msat m)
{
wallet_channel_stats_incr_x(w, "out", "fulfilled", id, m);
}
void wallet_channel_stats_load(struct wallet *w,
u64 id,
struct channel_stats *stats)
{
struct db_stmt *stmt;
int res;
stmt = db_prepare_v2(w->db, SQL(
"SELECT"
" in_payments_offered, in_payments_fulfilled"
", in_msatoshi_offered, in_msatoshi_fulfilled"
", out_payments_offered, out_payments_fulfilled"
", out_msatoshi_offered, out_msatoshi_fulfilled"
" FROM channels"
" WHERE id = ?"));
db_bind_u64(stmt, id);
db_query_prepared(stmt);
res = db_step(stmt);
/* This must succeed, since we know the channel exists */
assert(res);
stats->in_payments_offered
= db_col_int_or_default(stmt, "in_payments_offered", 0);
stats->in_payments_fulfilled
= db_col_int_or_default(stmt, "in_payments_fulfilled", 0);
db_col_amount_msat_or_default(stmt, "in_msatoshi_offered",
&stats->in_msatoshi_offered,
AMOUNT_MSAT(0));
db_col_amount_msat_or_default(stmt, "in_msatoshi_fulfilled",
&stats->in_msatoshi_fulfilled,
AMOUNT_MSAT(0));
stats->out_payments_offered
= db_col_int_or_default(stmt, "out_payments_offered", 0);
stats->out_payments_fulfilled
= db_col_int_or_default(stmt, "out_payments_fulfilled", 0);
db_col_amount_msat_or_default(stmt, "out_msatoshi_offered",
&stats->out_msatoshi_offered,
AMOUNT_MSAT(0));
db_col_amount_msat_or_default(stmt, "out_msatoshi_fulfilled",
&stats->out_msatoshi_fulfilled,
AMOUNT_MSAT(0));
tal_free(stmt);
}
void wallet_blocks_heights(struct wallet *w, u32 def, u32 *min, u32 *max)
{
assert(min != NULL && max != NULL);
struct db_stmt *stmt = db_prepare_v2(w->db, SQL("SELECT MIN(height), MAX(height) FROM blocks;"));
db_query_prepared(stmt);
*min = def;
*max = def;
/* If we ever processed a block we'll get the latest block in the chain */
if (db_step(stmt)) {
if (!db_col_is_null(stmt, "MIN(height)")) {
*min = db_col_int(stmt, "MIN(height)");
*max = db_col_int(stmt, "MAX(height)");
} else {
db_col_ignore(stmt, "MAX(height)");
}
}
tal_free(stmt);
}
static void wallet_channel_config_insert(struct wallet *w,
struct channel_config *cc)
{
struct db_stmt *stmt;
assert(cc->id == 0);
stmt = db_prepare_v2(w->db, SQL("INSERT INTO channel_configs DEFAULT VALUES;"));
db_exec_prepared_v2(stmt);
cc->id = db_last_insert_id_v2(stmt);
tal_free(stmt);
}
static void wallet_channel_config_save(struct wallet *w,
const struct channel_config *cc)
{
struct db_stmt *stmt;
assert(cc->id != 0);
stmt = db_prepare_v2(w->db, SQL("UPDATE channel_configs SET"
" dust_limit_satoshis=?,"
" max_htlc_value_in_flight_msat=?,"
" channel_reserve_satoshis=?,"
" htlc_minimum_msat=?,"
" to_self_delay=?,"
" max_accepted_htlcs=?,"
" max_dust_htlc_exposure_msat=?"
" WHERE id=?;"));
db_bind_amount_sat(stmt, &cc->dust_limit);
db_bind_amount_msat(stmt, &cc->max_htlc_value_in_flight);
db_bind_amount_sat(stmt, &cc->channel_reserve);
db_bind_amount_msat(stmt, &cc->htlc_minimum);
db_bind_int(stmt, cc->to_self_delay);
db_bind_int(stmt, cc->max_accepted_htlcs);
db_bind_amount_msat(stmt, &cc->max_dust_htlc_exposure_msat);
db_bind_u64(stmt, cc->id);
db_exec_prepared_v2(take(stmt));
}
u64 wallet_get_channel_dbid(struct wallet *wallet)
{
return ++wallet->max_channel_dbid;
}
/* When we receive the remote announcement message, we will also call this function */
void wallet_announcement_save(struct wallet *w, u64 id,
secp256k1_ecdsa_signature *remote_ann_node_sig,
secp256k1_ecdsa_signature *remote_ann_bitcoin_sig)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("UPDATE channels SET"
" remote_ann_node_sig=?,"
" remote_ann_bitcoin_sig=?"
" WHERE id=?"));
db_bind_signature(stmt, remote_ann_node_sig);
db_bind_signature(stmt, remote_ann_bitcoin_sig);
db_bind_u64(stmt, id);
db_exec_prepared_v2(take(stmt));
}
void wallet_htlcsigs_confirm_inflight(struct wallet *w, struct channel *chan,
const struct bitcoin_outpoint *confirmed_outpoint)
{
struct db_stmt *stmt;
/* A NULL inflight_tx_id means these htlc_sigs apply to the currently
* active channel */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM htlc_sigs"
" WHERE channelid=?"
" AND (inflight_tx_id is NULL"
" OR ("
" inflight_tx_id!=?"
" AND "
" inflight_tx_outnum!=?"
")"
")"));
db_bind_u64(stmt, chan->dbid);
db_bind_txid(stmt, &confirmed_outpoint->txid);
db_bind_int(stmt, confirmed_outpoint->n);
db_exec_prepared_v2(take(stmt));
stmt = db_prepare_v2(w->db, SQL("UPDATE htlc_sigs"
" SET inflight_tx_id=NULL"
" WHERE channelid=?"));
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
}
void wallet_channel_save(struct wallet *w, struct channel *chan)
{
struct db_stmt *stmt;
u8 *last_sent_commit;
const struct peer_update *peer_update;
assert(chan->first_blocknum);
wallet_channel_config_save(w, &chan->our_config);
stmt = db_prepare_v2(w->db, SQL("UPDATE channels SET"
" shachain_remote_id=?," // 0
" scid=?," // 1
" full_channel_id=?," // 2
" state=?," // 3
" funder=?," // 4
" channel_flags=?," // 5
" minimum_depth=?," // 6
" next_index_local=?," // 7
" next_index_remote=?," // 8
" next_htlc_id=?," // 9
" funding_tx_id=?," // 10
" funding_tx_outnum=?," // 11
" funding_satoshi=?," // 12
" our_funding_satoshi=?," // 13
" funding_locked_remote=?," // 14
" push_msatoshi=?," // 15
" msatoshi_local=?," // 16
" shutdown_scriptpubkey_remote=?,"
" shutdown_keyidx_local=?," // 18
" channel_config_local=?," // 19
" last_tx=?, last_sig=?," // 20 + 21
" last_was_revoke=?," // 22
" min_possible_feerate=?," // 23
" max_possible_feerate=?," // 24
" msatoshi_to_us_min=?," // 25
" msatoshi_to_us_max=?," // 26
" feerate_base=?," // 27
" feerate_ppm=?," // 28
" remote_upfront_shutdown_script=?," // 29
" local_static_remotekey_start=?," // 30
" remote_static_remotekey_start=?," // 31
" channel_type=?," // 32
" shutdown_scriptpubkey_local=?," // 33
" closer=?," // 34
" state_change_reason=?," // 35
" shutdown_wrong_txid=?," // 36
" shutdown_wrong_outnum=?," // 37
" lease_expiry=?," // 38
" lease_commit_sig=?," // 39
" lease_chan_max_msat=?," // 40
" lease_chan_max_ppt=?," // 41
" htlc_minimum_msat=?," // 42
" htlc_maximum_msat=?," // 43
" alias_local=?," // 44
" alias_remote=?," // 45
" ignore_fee_limits=?," // 46
" remote_feerate_base=?," // 47
" remote_feerate_ppm=?," // 48
" remote_cltv_expiry_delta=?," // 49
" remote_htlc_minimum_msat=?," // 50
" remote_htlc_maximum_msat=?,"
" last_stable_connection=?"
" WHERE id=?"));
db_bind_u64(stmt, chan->their_shachain.id);
if (chan->scid)
db_bind_short_channel_id(stmt, chan->scid);
else
db_bind_null(stmt);
db_bind_channel_id(stmt, &chan->cid);
db_bind_int(stmt, channel_state_in_db(chan->state));
db_bind_int(stmt, chan->opener);
db_bind_int(stmt, chan->channel_flags);
db_bind_int(stmt, chan->minimum_depth);
db_bind_u64(stmt, chan->next_index[LOCAL]);
db_bind_u64(stmt, chan->next_index[REMOTE]);
db_bind_u64(stmt, chan->next_htlc_id);
db_bind_sha256d(stmt, &chan->funding.txid.shad);
db_bind_int(stmt, chan->funding.n);
db_bind_amount_sat(stmt, &chan->funding_sats);
db_bind_amount_sat(stmt, &chan->our_funds);
db_bind_int(stmt, chan->remote_channel_ready);
db_bind_amount_msat(stmt, &chan->push);
db_bind_amount_msat(stmt, &chan->our_msat);
db_bind_talarr(stmt, chan->shutdown_scriptpubkey[REMOTE]);
db_bind_u64(stmt, chan->final_key_idx);
db_bind_u64(stmt, chan->our_config.id);
if (chan->last_tx) {
db_bind_psbt(stmt, chan->last_tx->psbt);
db_bind_signature(stmt, &chan->last_sig.s);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_int(stmt, chan->last_was_revoke);
db_bind_int(stmt, chan->min_possible_feerate);
db_bind_int(stmt, chan->max_possible_feerate);
db_bind_amount_msat(stmt, &chan->msat_to_us_min);
db_bind_amount_msat(stmt, &chan->msat_to_us_max);
db_bind_int(stmt, chan->feerate_base);
db_bind_int(stmt, chan->feerate_ppm);
db_bind_talarr(stmt, chan->remote_upfront_shutdown_script);
db_bind_u64(stmt, chan->static_remotekey_start[LOCAL]);
db_bind_u64(stmt, chan->static_remotekey_start[REMOTE]);
db_bind_channel_type(stmt, chan->type);
db_bind_talarr(stmt, chan->shutdown_scriptpubkey[LOCAL]);
db_bind_int(stmt, chan->closer);
db_bind_int(stmt, state_change_in_db(chan->state_change_cause));
if (chan->shutdown_wrong_funding) {
db_bind_txid(stmt, &chan->shutdown_wrong_funding->txid);
db_bind_int(stmt, chan->shutdown_wrong_funding->n);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_int(stmt, chan->lease_expiry);
if (chan->lease_commit_sig) {
db_bind_signature(stmt, chan->lease_commit_sig);
db_bind_int(stmt, chan->lease_chan_max_msat);
db_bind_int(stmt, chan->lease_chan_max_ppt);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_amount_msat(stmt, &chan->htlc_minimum_msat);
db_bind_amount_msat(stmt, &chan->htlc_maximum_msat);
if (chan->alias[LOCAL] != NULL)
db_bind_short_channel_id(stmt, chan->alias[LOCAL]);
else
db_bind_null(stmt);
if (chan->alias[REMOTE] != NULL)
db_bind_short_channel_id(stmt, chan->alias[REMOTE]);
else
db_bind_null(stmt);
db_bind_int(stmt, chan->ignore_fee_limits);
peer_update = channel_gossip_get_remote_update(chan);
if (peer_update) {
db_bind_int(stmt, peer_update->fee_base);
db_bind_int(stmt, peer_update->fee_ppm);
db_bind_int(stmt, peer_update->cltv_delta);
db_bind_amount_msat(stmt, &peer_update->htlc_minimum_msat);
db_bind_amount_msat(stmt, &peer_update->htlc_maximum_msat);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_u64(stmt, chan->last_stable_connection);
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
wallet_channel_config_save(w, &chan->channel_info.their_config);
stmt = db_prepare_v2(w->db, SQL("UPDATE channels SET"
" fundingkey_remote=?,"
" revocation_basepoint_remote=?,"
" payment_basepoint_remote=?,"
" htlc_basepoint_remote=?,"
" delayed_payment_basepoint_remote=?,"
" per_commit_remote=?,"
" old_per_commit_remote=?,"
" channel_config_remote=?,"
" future_per_commitment_point=?"
" WHERE id=?"));
db_bind_pubkey(stmt, &chan->channel_info.remote_fundingkey);
db_bind_pubkey(stmt, &chan->channel_info.theirbase.revocation);
db_bind_pubkey(stmt, &chan->channel_info.theirbase.payment);
db_bind_pubkey(stmt, &chan->channel_info.theirbase.htlc);
db_bind_pubkey(stmt, &chan->channel_info.theirbase.delayed_payment);
db_bind_pubkey(stmt, &chan->channel_info.remote_per_commit);
db_bind_pubkey(stmt, &chan->channel_info.old_remote_per_commit);
db_bind_u64(stmt, chan->channel_info.their_config.id);
if (chan->future_per_commitment_point)
db_bind_pubkey(stmt, chan->future_per_commitment_point);
else
db_bind_null(stmt);
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
/* FIXME: Updates channel_feerates by discarding and rewriting. */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_feerates "
"WHERE channel_id=?"));
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
for (enum htlc_state i = 0;
i < ARRAY_SIZE(chan->fee_states->feerate);
i++) {
if (!chan->fee_states->feerate[i])
continue;
stmt = db_prepare_v2(w->db, SQL("INSERT INTO channel_feerates "
" VALUES(?, ?, ?)"));
db_bind_u64(stmt, chan->dbid);
db_bind_int(stmt, htlc_state_in_db(i));
db_bind_int(stmt, *chan->fee_states->feerate[i]);
db_exec_prepared_v2(take(stmt));
}
/* FIXME: Updates channel_blockheights by discarding and rewriting. */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_blockheights "
"WHERE channel_id=?"));
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
for (enum htlc_state i = 0;
i < ARRAY_SIZE(chan->blockheight_states->height);
i++) {
if (!chan->blockheight_states->height[i])
continue;
stmt = db_prepare_v2(w->db, SQL("INSERT INTO channel_blockheights "
" VALUES(?, ?, ?)"));
db_bind_u64(stmt, chan->dbid);
db_bind_int(stmt, htlc_state_in_db(i));
db_bind_int(stmt, *chan->blockheight_states->height[i]);
db_exec_prepared_v2(take(stmt));
}
/* If we have a last_sent_commit, store it */
last_sent_commit = tal_arr(tmpctx, u8, 0);
for (size_t i = 0; i < tal_count(chan->last_sent_commit); i++)
towire_changed_htlc(&last_sent_commit,
&chan->last_sent_commit[i]);
/* Make it null in db if it's empty */
if (tal_count(last_sent_commit) == 0)
last_sent_commit = tal_free(last_sent_commit);
stmt = db_prepare_v2(w->db, SQL("UPDATE channels SET"
" last_sent_commit=?"
" WHERE id=?"));
/* Update the inflights also */
struct channel_inflight *inflight;
list_for_each(&chan->inflights, inflight, list)
if (!inflight->splice_locked_memonly)
wallet_inflight_save(w, inflight);
db_bind_talarr(stmt, last_sent_commit);
db_bind_u64(stmt, chan->dbid);
db_exec_prepared_v2(take(stmt));
channel_gossip_update(chan);
}
void wallet_state_change_add(struct wallet *w,
const u64 channel_id,
struct timeabs timestamp,
enum channel_state old_state,
enum channel_state new_state,
enum state_change cause,
const char *message)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO channel_state_changes ("
" channel_id"
", timestamp"
", old_state"
", new_state"
", cause"
", message"
") VALUES (?, ?, ?, ?, ?, ?);"));
db_bind_u64(stmt, channel_id);
db_bind_timeabs(stmt, timestamp);
db_bind_int(stmt, channel_state_in_db(old_state));
db_bind_int(stmt, channel_state_in_db(new_state));
db_bind_int(stmt, state_change_in_db(cause));
db_bind_text(stmt, message);
db_exec_prepared_v2(take(stmt));
}
struct state_change_entry *wallet_state_change_get(struct wallet *w,
const tal_t *ctx,
u64 channel_id)
{
struct db_stmt *stmt;
struct state_change_entry tmp;
struct state_change_entry *res = tal_arr(ctx,
struct state_change_entry, 0);
stmt = db_prepare_v2(
w->db, SQL("SELECT"
" timestamp,"
" old_state,"
" new_state,"
" cause,"
" message "
"FROM channel_state_changes "
"WHERE channel_id = ? "
"ORDER BY timestamp ASC;"));
db_bind_int(stmt, channel_id);
db_query_prepared(stmt);
while (db_step(stmt)) {
tmp.timestamp = db_col_timeabs(stmt, "timestamp");
tmp.old_state = db_col_int(stmt, "old_state");
tmp.new_state = db_col_int(stmt, "new_state");
tmp.cause = state_change_in_db(db_col_int(stmt, "cause"));
tmp.message = db_col_strdup(res, stmt, "message");
tal_arr_expand(&res, tmp);
}
tal_free(stmt);
return res;
}
static void wallet_peer_save(struct wallet *w, struct peer *peer)
{
const char *addr =
type_to_string(tmpctx, struct wireaddr_internal, &peer->addr);
struct db_stmt *stmt =
db_prepare_v2(w->db, SQL("SELECT id FROM peers WHERE node_id = ?"));
db_bind_node_id(stmt, &peer->id);
db_query_prepared(stmt);
if (db_step(stmt)) {
/* So we already knew this peer, just return its dbid */
peer_set_dbid(peer, db_col_u64(stmt, "id"));
tal_free(stmt);
/* Since we're at it update the wireaddr, feature bits */
stmt = db_prepare_v2(
w->db, SQL("UPDATE peers SET address = ?, feature_bits = ? WHERE id = ?"));
db_bind_text(stmt, addr);
db_bind_talarr(stmt, peer->their_features);
db_bind_u64(stmt, peer->dbid);
db_exec_prepared_v2(take(stmt));
} else {
/* Unknown peer, create it from scratch */
tal_free(stmt);
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO peers (node_id, address, feature_bits) VALUES (?, ?, ?);")
);
db_bind_node_id(stmt, &peer->id);
db_bind_text(stmt, addr);
db_bind_talarr(stmt, peer->their_features);
db_exec_prepared_v2(stmt);
peer_set_dbid(peer, db_last_insert_id_v2(take(stmt)));
}
}
void wallet_channel_insert(struct wallet *w, struct channel *chan)
{
struct db_stmt *stmt;
assert(chan->dbid != 0);
assert(chan->unsaved_dbid == 0);
if (chan->peer->dbid == 0)
wallet_peer_save(w, chan->peer);
/* Insert a stub, that we update, unifies INSERT and UPDATE paths */
stmt = db_prepare_v2(
w->db, SQL("INSERT INTO channels ("
" peer_id"
", first_blocknum"
", id"
", revocation_basepoint_local"
", payment_basepoint_local"
", htlc_basepoint_local"
", delayed_payment_basepoint_local"
", funding_pubkey_local"
", require_confirm_inputs_remote"
", require_confirm_inputs_local"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_u64(stmt, chan->peer->dbid);
db_bind_int(stmt, chan->first_blocknum);
db_bind_int(stmt, chan->dbid);
db_bind_pubkey(stmt, &chan->local_basepoints.revocation);
db_bind_pubkey(stmt, &chan->local_basepoints.payment);
db_bind_pubkey(stmt, &chan->local_basepoints.htlc);
db_bind_pubkey(stmt, &chan->local_basepoints.delayed_payment);
db_bind_pubkey(stmt, &chan->local_funding_pubkey);
db_bind_int(stmt, chan->req_confirmed_ins[REMOTE]);
db_bind_int(stmt, chan->req_confirmed_ins[LOCAL]);
db_exec_prepared_v2(take(stmt));
wallet_channel_config_insert(w, &chan->our_config);
wallet_channel_config_insert(w, &chan->channel_info.their_config);
wallet_shachain_init(w, &chan->their_shachain);
/* Now save path as normal */
wallet_channel_save(w, chan);
}
void wallet_channel_close(struct wallet *w, u64 wallet_id)
{
/* We keep a couple of dependent tables around as well, such as the
* channel_configs table, since that might help us debug some issues,
* and it is rather limited in size. Tables that can grow quite
* considerably and that are of limited use after channel closure will
* be pruned as well. */
struct db_stmt *stmt;
/* Delete entries from `channel_htlcs` */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channel_htlcs "
"WHERE channel_id=?"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
/* Delete entries from `htlc_sigs` */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM htlc_sigs "
"WHERE channelid=?"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
/* Delete entries from `htlc_sigs` */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM channeltxs "
"WHERE channel_id=?"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
/* Delete any entries from 'inflights' */
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM channel_funding_inflights "
" WHERE channel_id=?"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
/* Delete shachains */
stmt = db_prepare_v2(w->db, SQL("DELETE FROM shachains "
"WHERE id IN ("
" SELECT shachain_remote_id "
" FROM channels "
" WHERE channels.id=?"
")"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
/* Set the channel to closed */
stmt = db_prepare_v2(w->db, SQL("UPDATE channels "
"SET state=? "
"WHERE channels.id=?"));
db_bind_u64(stmt, channel_state_in_db(CLOSED));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
}
void wallet_channel_inflight_cleanup_incomplete(struct wallet *w, u64 wallet_id)
{
struct db_stmt *stmt;
/* Delete any incomplete entries from 'inflights' */
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM channel_funding_inflights "
" WHERE channel_id=? AND last_tx IS NULL"));
db_bind_u64(stmt, wallet_id);
db_exec_prepared_v2(take(stmt));
}
void wallet_delete_peer_if_unused(struct wallet *w, u64 peer_dbid)
{
struct db_stmt *stmt;
/* Must not have any channels still using this peer */
stmt = db_prepare_v2(w->db, SQL("SELECT * FROM channels WHERE peer_id = ?;"));
db_bind_u64(stmt, peer_dbid);
db_query_prepared(stmt);
if (db_step(stmt)) {
db_col_ignore(stmt, "*");
tal_free(stmt);
return;
}
tal_free(stmt);
stmt = db_prepare_v2(w->db, SQL("DELETE FROM peers WHERE id=?"));
db_bind_u64(stmt, peer_dbid);
db_exec_prepared_v2(take(stmt));
}
void wallet_confirm_tx(struct wallet *w,
const struct bitcoin_txid *txid,
const u32 confirmation_height)
{
struct db_stmt *stmt;
assert(confirmation_height > 0);
stmt = db_prepare_v2(w->db, SQL("UPDATE outputs "
"SET confirmation_height = ? "
"WHERE prev_out_tx = ?"));
db_bind_int(stmt, confirmation_height);
db_bind_sha256d(stmt, &txid->shad);
db_exec_prepared_v2(take(stmt));
}
int wallet_extract_owned_outputs(struct wallet *w, const struct wally_tx *wtx,
bool is_coinbase,
const u32 *blockheight,
struct amount_sat *total)
{
int num_utxos = 0;
if (total)
*total = AMOUNT_SAT(0);
for (size_t output = 0; output < wtx->num_outputs; output++) {
struct utxo *utxo;
u32 index;
bool is_p2sh;
const u8 *script;
struct amount_asset asset =
wally_tx_output_get_amount(&wtx->outputs[output]);
2020-04-02 03:55:44 +01:00
struct chain_coin_mvt *mvt;
if (!amount_asset_is_main(&asset))
continue;
script = cln_wally_tx_output_get_script(tmpctx,
&wtx->outputs[output]);
if (!script)
continue;
if (!wallet_can_spend(w, script, &index, &is_p2sh))
continue;
utxo = tal(w, struct utxo);
utxo->keyindex = index;
utxo->is_p2sh = is_p2sh;
utxo->amount = amount_asset_to_sat(&asset);
utxo->status = OUTPUT_STATE_AVAILABLE;
wally_txid(wtx, &utxo->outpoint.txid);
utxo->outpoint.n = output;
utxo->close_info = NULL;
utxo->is_in_coinbase = is_coinbase;
utxo->blockheight = blockheight ? blockheight : NULL;
utxo->spendheight = NULL;
utxo->scriptPubkey = tal_dup_talarr(utxo, u8, script);
log_debug(w->log, "Owning output %zu %s (%s) txid %s%s%s",
output,
type_to_string(tmpctx, struct amount_sat,
&utxo->amount),
is_p2sh ? "P2SH" : "SEGWIT",
type_to_string(tmpctx, struct bitcoin_txid,
&utxo->outpoint.txid),
blockheight ? " CONFIRMED" : "",
is_coinbase ? " COINBASE" : "");
/* We only record final ledger movements */
if (blockheight) {
mvt = new_coin_wallet_deposit(tmpctx, &utxo->outpoint,
*blockheight,
utxo->amount,
DEPOSIT);
notify_chain_mvt(w->ld, mvt);
}
if (!wallet_add_utxo(w, utxo, is_p2sh ? p2sh_wpkh : our_change)) {
/* In case we already know the output, make
* sure we actually track its
* blockheight. This can happen when we grab
* the output from a transaction we created
2018-03-23 10:01:43 +00:00
* ourselves. */
if (blockheight)
wallet_confirm_tx(w, &utxo->outpoint.txid,
*blockheight);
tal_free(utxo);
continue;
}
/* This is an unconfirmed change output, we should track it */
if (!is_p2sh && !blockheight)
txfilter_add_scriptpubkey(w->ld->owned_txfilter, script);
outpointfilter_add(w->owned_outpoints, &utxo->outpoint);
if (total && !amount_sat_add(total, *total, utxo->amount))
fatal("Cannot add utxo output %zu/%zu %s + %s",
output, wtx->num_outputs,
type_to_string(tmpctx, struct amount_sat, total),
type_to_string(tmpctx, struct amount_sat,
&utxo->amount));
wallet_annotate_txout(w, &utxo->outpoint, TX_WALLET_DEPOSIT, 0);
tal_free(utxo);
num_utxos++;
}
return num_utxos;
}
void wallet_htlc_save_in(struct wallet *wallet,
const struct channel *chan, struct htlc_in *in)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db,
SQL("INSERT INTO channel_htlcs ("
" channel_id,"
" channel_htlc_id, "
" direction,"
" msatoshi,"
" cltv_expiry,"
" payment_hash, "
" payment_key,"
" hstate,"
" shared_secret,"
" routing_onion,"
" received_time,"
" min_commit_num, "
" fail_immediate) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
db_bind_u64(stmt, chan->dbid);
db_bind_u64(stmt, in->key.id);
db_bind_int(stmt, DIRECTION_INCOMING);
db_bind_amount_msat(stmt, &in->msat);
db_bind_int(stmt, in->cltv_expiry);
db_bind_sha256(stmt, &in->payment_hash);
2017-10-30 21:44:00 +00:00
if (in->preimage)
db_bind_preimage(stmt, in->preimage);
else
db_bind_null(stmt);
db_bind_int(stmt, in->hstate);
if (!in->shared_secret)
db_bind_null(stmt);
else
db_bind_secret(stmt, in->shared_secret);
2017-10-30 21:44:00 +00:00
db_bind_blob(stmt, in->onion_routing_packet,
sizeof(in->onion_routing_packet));
db_bind_timeabs(stmt, in->received_time);
db_bind_u64(stmt, min_unsigned(chan->next_index[LOCAL]-1,
chan->next_index[REMOTE]-1));
db_bind_int(stmt, in->fail_immediate);
db_exec_prepared_v2(stmt);
in->dbid = db_last_insert_id_v2(take(stmt));
}
void wallet_htlc_save_out(struct wallet *wallet,
const struct channel *chan,
struct htlc_out *out)
{
struct db_stmt *stmt;
/* We absolutely need the incoming HTLC to be persisted before
* we can persist it's dependent */
assert(out->in == NULL || out->in->dbid != 0);
stmt = db_prepare_v2(
wallet->db,
SQL("INSERT INTO channel_htlcs ("
" channel_id,"
" channel_htlc_id,"
" direction,"
" origin_htlc,"
" msatoshi,"
" cltv_expiry,"
" payment_hash,"
" payment_key,"
" hstate,"
" routing_onion,"
" malformed_onion,"
2021-09-29 11:33:57 +01:00
" partid,"
" groupid,"
" fees_msat,"
" min_commit_num"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?);"));
db_bind_u64(stmt, chan->dbid);
db_bind_u64(stmt, out->key.id);
db_bind_int(stmt, DIRECTION_OUTGOING);
if (out->in)
db_bind_u64(stmt, out->in->dbid);
else
db_bind_null(stmt);
db_bind_amount_msat(stmt, &out->msat);
db_bind_int(stmt, out->cltv_expiry);
db_bind_sha256(stmt, &out->payment_hash);
2017-10-30 21:44:00 +00:00
if (out->preimage)
db_bind_preimage(stmt, out->preimage);
else
db_bind_null(stmt);
db_bind_int(stmt, out->hstate);
db_bind_blob(stmt, out->onion_routing_packet,
sizeof(out->onion_routing_packet));
2021-09-29 11:33:57 +01:00
/* groupid and partid are only relevant when we are the origin */
if (!out->am_origin) {
db_bind_null(stmt);
db_bind_null(stmt);
2021-09-29 11:33:57 +01:00
} else {
db_bind_u64(stmt, out->partid);
db_bind_u64(stmt, out->groupid);
2021-09-29 11:33:57 +01:00
}
db_bind_amount_msat(stmt, &out->fees);
db_bind_u64(stmt, min_u64(chan->next_index[LOCAL]-1,
chan->next_index[REMOTE]-1));
db_exec_prepared_v2(stmt);
out->dbid = db_last_insert_id_v2(stmt);
tal_free(stmt);
}
/* input htlcs use failcode & failonion & we_filled, output htlcs use failmsg & failonion */
void wallet_htlc_update(struct wallet *wallet, const u64 htlc_dbid,
const enum htlc_state new_state,
const struct preimage *payment_key,
u64 max_commit_num,
enum onion_wire badonion,
const struct onionreply *failonion,
const u8 *failmsg,
bool *we_filled)
{
struct db_stmt *stmt;
bool terminal = (new_state == RCVD_REMOVE_ACK_REVOCATION
|| new_state == SENT_REMOVE_ACK_REVOCATION);
/* We should only use this for badonion codes */
assert(!badonion || (badonion & BADONION));
/* The database ID must be set by a previous call to
* `wallet_htlc_save_*` */
assert(htlc_dbid);
stmt = db_prepare_v2(
wallet->db, SQL("UPDATE channel_htlcs SET hstate=?, payment_key=?, "
"malformed_onion=?, failuremsg=?, localfailmsg=?, "
"we_filled=?, max_commit_num=?"
" WHERE id=?"));
2017-10-30 21:44:00 +00:00
db_bind_int(stmt, htlc_state_in_db(new_state));
2017-10-30 21:44:00 +00:00
if (payment_key)
db_bind_preimage(stmt, payment_key);
else
db_bind_null(stmt);
2017-10-30 21:44:00 +00:00
db_bind_int(stmt, badonion);
if (failonion)
db_bind_onionreply(stmt, failonion);
else
db_bind_null(stmt);
db_bind_talarr(stmt, failmsg);
if (we_filled)
db_bind_int(stmt, *we_filled);
else
db_bind_null(stmt);
/* Set max_commit_num iff we're in final state. */
if (terminal)
db_bind_u64(stmt, max_commit_num);
else
db_bind_null(stmt);
db_bind_u64(stmt, htlc_dbid);
db_exec_prepared_v2(take(stmt));
if (terminal) {
/* If it's terminal, remove the data we needed for re-xmission. */
stmt = db_prepare_v2(
wallet->db,
SQL("UPDATE channel_htlcs SET payment_key=NULL, routing_onion=NULL, failuremsg=NULL, shared_secret=NULL, localfailmsg=NULL "
" WHERE id=?"));
db_bind_u64(stmt, htlc_dbid);
db_exec_prepared_v2(take(stmt));
}
}
static bool wallet_stmt2htlc_in(struct channel *channel,
struct db_stmt *stmt, struct htlc_in *in)
{
bool ok = true;
in->dbid = db_col_u64(stmt, "id");
in->key.id = db_col_u64(stmt, "channel_htlc_id");
in->key.channel = channel;
in->msat = db_col_amount_msat(stmt, "msatoshi");
in->cltv_expiry = db_col_int(stmt, "cltv_expiry");
in->hstate = db_col_int(stmt, "hstate");
2021-06-04 09:47:47 +01:00
in->status = NULL;
/* FIXME: save blinding in db !*/
in->blinding = NULL;
in->payload = NULL;
db_col_sha256(stmt, "payment_hash", &in->payment_hash);
2017-10-30 21:44:00 +00:00
in->preimage = db_col_optional(in, stmt, "payment_key", preimage);
assert(db_col_bytes(stmt, "routing_onion")
== sizeof(in->onion_routing_packet));
memcpy(&in->onion_routing_packet, db_col_blob(stmt, "routing_onion"),
2017-10-30 21:44:00 +00:00
sizeof(in->onion_routing_packet));
if (db_col_is_null(stmt, "failuremsg"))
in->failonion = NULL;
else
in->failonion = db_col_onionreply(in, stmt, "failuremsg");
in->badonion = db_col_int(stmt, "malformed_onion");
in->shared_secret = db_col_optional(in, stmt, "shared_secret", secret);
#ifdef COMPAT_V062
if (in->shared_secret
&& memeqzero(in->shared_secret, sizeof(*in->shared_secret)))
in->shared_secret = tal_free(in->shared_secret);
#endif
#ifdef COMPAT_V072
if (db_col_is_null(stmt, "received_time")) {
in->received_time.ts.tv_sec = 0;
in->received_time.ts.tv_nsec = 0;
} else
#endif /* COMPAT_V072 */
in->received_time = db_col_timeabs(stmt, "received_time");
#ifdef COMPAT_V080
/* This field is now reserved for badonion codes: the rest should
* use the failonion field. */
if (in->badonion && !(in->badonion & BADONION)) {
log_broken(channel->log,
"Replacing incoming HTLC %"PRIu64" error "
"%s with WIRE_TEMPORARY_NODE_FAILURE",
in->key.id, onion_wire_name(in->badonion));
in->badonion = 0;
in->failonion = create_onionreply(in,
in->shared_secret,
towire_temporary_node_failure(tmpctx));
}
#endif
if (!db_col_is_null(stmt, "we_filled")) {
in->we_filled = tal(in, bool);
*in->we_filled = db_col_int(stmt, "we_filled");
} else
in->we_filled = NULL;
in->fail_immediate = db_col_int(stmt, "fail_immediate");
return ok;
}
/* Removes matching htlc from unconnected_htlcs_in */
static bool wallet_stmt2htlc_out(struct wallet *wallet,
struct channel *channel,
struct db_stmt *stmt, struct htlc_out *out,
struct htlc_in_map *unconnected_htlcs_in)
{
bool ok = true;
out->dbid = db_col_u64(stmt, "id");
out->key.id = db_col_u64(stmt, "channel_htlc_id");
out->key.channel = channel;
out->msat = db_col_amount_msat(stmt, "msatoshi");
out->cltv_expiry = db_col_int(stmt, "cltv_expiry");
out->hstate = db_col_int(stmt, "hstate");
db_col_sha256(stmt, "payment_hash", &out->payment_hash);
/* FIXME: save blinding in db !*/
out->blinding = NULL;
out->preimage = db_col_optional(out, stmt, "payment_key", preimage);
assert(db_col_bytes(stmt, "routing_onion")
== sizeof(out->onion_routing_packet));
memcpy(&out->onion_routing_packet, db_col_blob(stmt, "routing_onion"),
2017-10-30 21:44:00 +00:00
sizeof(out->onion_routing_packet));
if (db_col_is_null(stmt, "failuremsg"))
out->failonion = NULL;
else
out->failonion = db_col_onionreply(out, stmt, "failuremsg");
if (db_col_is_null(stmt, "localfailmsg"))
out->failmsg = NULL;
else
out->failmsg = db_col_arr(out, stmt, "localfailmsg", u8);
out->in = NULL;
out->fees = db_col_amount_msat(stmt, "fees_msat");
if (!db_col_is_null(stmt, "origin_htlc")) {
u64 in_id = db_col_u64(stmt, "origin_htlc");
struct htlc_in *hin;
wallet: don't get so upset on orphaned HTLC. User grubman on IRC reported a crash due to new HTLC checks: ``` 2022-04-05T01:15:17.707Z **BROKEN** wallet: Missing preimage for orphaned HTLC; replacing with zeros ... 2022-04-05T01:15:44.950Z **BROKEN** lightningd: check_already_failed:Both failed and succeeded? 2022-04-05T01:15:45.020Z **BROKEN** lightningd: FATAL SIGNAL 6 (version v0.11.0rc1-modded) 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: common/daemon.c:38 (send_backtrace) 0xaaaab8f7f8bb 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: common/daemon.c:46 (crashdump) 0xaaaab8f7f91b 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: (null):0 ((null)) 0xffffa4a315bf 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: (null):0 ((null)) 0xffffa45c6cd8 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: (null):0 ((null)) 0xffffa45b3a27 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/log.c:821 (fatal_vfmt) 0xaaaab8f3e1b7 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/log.c:829 (fatal) 0xaaaab8f3e24b 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/htlc_end.c:87 (corrupt) 0xaaaab8f325d3 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/htlc_end.c:175 (htlc_out_check) 0xaaaab8f32ee3 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/peer_htlcs.c:1471 (check_already_failed) 0xaaaab8f53ea7 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/peer_htlcs.c:1575 (onchain_failed_our_htlc) 0xaaaab8f54aab 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/onchain_control.c:411 (handle_missing_htlc_output) 0xaaaab8f4037b 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/onchain_control.c:544 (onchain_msg) 0xaaaab8f409bf 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/subd.c:556 (sd_msg_read) 0xaaaab8f5f8ef 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: ccan/ccan/io/io.c:59 (next_plan) 0xaaaab8fda197 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: ccan/ccan/io/io.c:407 (do_plan) 0xaaaab8fda76f 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: ccan/ccan/io/io.c:417 (io_ready) 0xaaaab8fda82f 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: ccan/ccan/io/poll.c:453 (io_loop) 0xaaaab8fdc77f 2022-04-05T01:15:45.020Z **BROKEN** lightningd: backtrace: lightningd/io_loop_with_timers.c:22 (io_loop_with_timers) 0xaaaab8f37857 2022-04-05T01:15:45.021Z **BROKEN** lightningd: backtrace: lightningd/lightningd.c:1181 (main) 0xaaaab8f3bcff 2022-04-05T01:15:45.021Z **BROKEN** lightningd: backtrace: (null):0 ((null)) 0xffffa45b3d4f 2022-04-05T01:15:45.021Z **BROKEN** lightningd: backtrace: (null):0 ((null)) 0xaaaab8f1ed33 ``` Since no HTLCs are likely to still be pending since v0.6.1, we can remove that compat code. Indeed, this happens on my node: we've not loaded the incoming HTLC because it's resolved. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2022-04-05 03:06:58 +01:00
/* If it failed / succeeded already, we could have
* closed incoming htlc */
hin = remove_htlc_in_by_dbid(unconnected_htlcs_in, in_id);
if (hin)
htlc_out_connect_htlc_in(out, hin);
out->am_origin = false;
db_col_ignore(stmt, "partid");
db_col_ignore(stmt, "groupid");
} else {
out->partid = db_col_u64(stmt, "partid");
out->groupid = db_col_u64(stmt, "groupid");
out->am_origin = true;
}
return ok;
}
static void fixup_hin(struct wallet *wallet, struct htlc_in *hin)
{
/* We didn't used to save failcore, failonion... */
#ifdef COMPAT_V061
/* We care about HTLCs being removed only, not those being added. */
if (hin->hstate < SENT_REMOVE_HTLC)
return;
/* Successful ones are fine. */
if (hin->preimage)
return;
/* Failed ones (only happens after db fixed!) OK. */
if (hin->badonion || hin->failonion)
return;
hin->failonion = create_onionreply(hin,
hin->shared_secret,
towire_temporary_node_failure(tmpctx));
log_broken(wallet->log, "HTLC #%"PRIu64" (%s) "
" for amount %s"
" from %s"
" is missing a resolution:"
" subsituting temporary node failure",
hin->key.id, htlc_state_name(hin->hstate),
type_to_string(tmpctx, struct amount_msat, &hin->msat),
type_to_string(tmpctx, struct node_id,
&hin->key.channel->peer->id));
#endif
}
bool wallet_htlcs_load_in_for_channel(struct wallet *wallet,
struct channel *chan,
struct htlc_in_map *htlcs_in)
{
struct db_stmt *stmt;
bool ok = true;
int incount = 0;
log_debug(wallet->log, "Loading in HTLCs for channel %"PRIu64, chan->dbid);
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", channel_htlc_id"
", msatoshi"
", cltv_expiry"
", hstate"
", payment_hash"
", payment_key"
", routing_onion"
", failuremsg"
", malformed_onion"
", shared_secret"
", received_time"
", we_filled"
", fail_immediate"
" FROM channel_htlcs"
" WHERE direction= ?"
" AND channel_id= ?"
" AND hstate NOT IN (?, ?)"));
db_bind_int(stmt, DIRECTION_INCOMING);
db_bind_u64(stmt, chan->dbid);
/* We need to generate `hstate NOT IN (9, 19)` in order to match
* the `WHERE` clause of the database index; incoming HTLCs will
* never actually get the state `RCVD_REMOVE_ACK_REVOCATION`.
* See https://sqlite.org/partialindex.html#queries_using_partial_indexes
*/
db_bind_int(stmt, RCVD_REMOVE_ACK_REVOCATION); /* Not gonna happen. */
db_bind_int(stmt, SENT_REMOVE_ACK_REVOCATION);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct htlc_in *in = tal(chan, struct htlc_in);
ok &= wallet_stmt2htlc_in(chan, stmt, in);
connect_htlc_in(htlcs_in, in);
fixup_hin(wallet, in);
ok &= htlc_in_check(in, NULL) != NULL;
incount++;
}
tal_free(stmt);
log_debug(wallet->log, "Restored %d incoming HTLCS", incount);
return ok;
}
bool wallet_htlcs_load_out_for_channel(struct wallet *wallet,
struct channel *chan,
struct htlc_out_map *htlcs_out,
struct htlc_in_map *unconnected_htlcs_in)
{
struct db_stmt *stmt;
bool ok = true;
int outcount = 0;
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", channel_htlc_id"
", msatoshi"
", cltv_expiry"
", hstate"
", payment_hash"
", payment_key"
", routing_onion"
", failuremsg"
", origin_htlc"
", partid"
", localfailmsg"
2021-09-29 11:33:57 +01:00
", groupid"
", fees_msat"
" FROM channel_htlcs"
" WHERE direction = ?"
" AND channel_id = ?"
" AND hstate NOT IN (?, ?)"));
db_bind_int(stmt, DIRECTION_OUTGOING);
db_bind_u64(stmt, chan->dbid);
/* We need to generate `hstate NOT IN (9, 19)` in order to match
* the `WHERE` clause of the database index; outgoing HTLCs will
* never actually get the state `SENT_REMOVE_ACK_REVOCATION`.
* See https://sqlite.org/partialindex.html#queries_using_partial_indexes
*/
db_bind_int(stmt, RCVD_REMOVE_ACK_REVOCATION);
db_bind_int(stmt, SENT_REMOVE_ACK_REVOCATION); /* Not gonna happen. */
db_query_prepared(stmt);
while (db_step(stmt)) {
struct htlc_out *out = tal(chan, struct htlc_out);
ok &= wallet_stmt2htlc_out(wallet, chan, stmt, out,
unconnected_htlcs_in);
connect_htlc_out(htlcs_out, out);
/* Cannot htlc_out_check because we haven't wired the
* dependencies in yet */
outcount++;
}
tal_free(stmt);
log_debug(wallet->log, "Restored %d outgoing HTLCS", outcount);
return ok;
}
struct htlc_stub *wallet_htlc_stubs(const tal_t *ctx, struct wallet *wallet,
struct channel *chan, u64 commit_num)
{
struct htlc_stub *stubs;
struct sha256 payment_hash;
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db,
SQL("SELECT channel_id, direction, cltv_expiry, "
"channel_htlc_id, payment_hash "
"FROM channel_htlcs WHERE channel_id = ? AND min_commit_num <= ? AND ((max_commit_num IS NULL) OR max_commit_num >= ?);"));
db_bind_u64(stmt, chan->dbid);
db_bind_u64(stmt, commit_num);
db_bind_u64(stmt, commit_num);
db_query_prepared(stmt);
stubs = tal_arr(ctx, struct htlc_stub, 0);
while (db_step(stmt)) {
struct htlc_stub stub;
assert(db_col_u64(stmt, "channel_id") == chan->dbid);
/* FIXME: merge these two enums */
stub.owner = db_col_int(stmt, "direction")==DIRECTION_INCOMING?REMOTE:LOCAL;
stub.cltv_expiry = db_col_int(stmt, "cltv_expiry");
stub.id = db_col_u64(stmt, "channel_htlc_id");
db_col_sha256(stmt, "payment_hash", &payment_hash);
ripemd160(&stub.ripemd, payment_hash.u.u8, sizeof(payment_hash.u));
tal_arr_expand(&stubs, stub);
}
tal_free(stmt);
return stubs;
}
void wallet_local_htlc_out_delete(struct wallet *wallet,
struct channel *chan,
const struct sha256 *payment_hash,
u64 partid)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db, SQL("DELETE FROM channel_htlcs"
" WHERE direction = ?"
" AND origin_htlc = ?"
" AND payment_hash = ?"
" AND partid = ?;"));
db_bind_int(stmt, DIRECTION_OUTGOING);
db_bind_int(stmt, 0);
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_exec_prepared_v2(take(stmt));
}
/* FIXME: reorder! */
static
struct wallet_payment *wallet_payment_new(const tal_t *ctx,
u64 dbid,
u64 updated_index,
u32 timestamp,
const u32 *completed_at,
const struct sha256 *payment_hash,
u64 partid,
u64 groupid,
enum payment_status status,
/* The destination may not be known if we used `sendonion` */
const struct node_id *destination,
struct amount_msat msatoshi,
struct amount_msat msatoshi_sent,
struct amount_msat total_msat,
/* If and only if PAYMENT_COMPLETE */
const struct preimage *payment_preimage,
const struct secret *path_secrets,
const struct node_id *route_nodes,
const struct short_channel_id *route_channels,
const char *invstring,
const char *label,
const char *description,
const u8 *failonion,
const struct sha256 *local_invreq_id);
struct wallet_payment *wallet_add_payment(const tal_t *ctx,
struct wallet *wallet,
u32 timestamp,
const u32 *completed_at,
const struct sha256 *payment_hash,
u64 partid,
u64 groupid,
enum payment_status status,
/* The destination may not be known if we used `sendonion` */
const struct node_id *destination TAKES,
struct amount_msat msatoshi,
struct amount_msat msatoshi_sent,
struct amount_msat total_msat,
/* If and only if PAYMENT_COMPLETE */
const struct preimage *payment_preimage TAKES,
const struct secret *path_secrets TAKES,
const struct node_id *route_nodes TAKES,
const struct short_channel_id *route_channels TAKES,
const char *invstring TAKES,
const char *label TAKES,
const char *description TAKES,
const u8 *failonion TAKES,
const struct sha256 *local_invreq_id)
{
struct db_stmt *stmt;
struct wallet_payment *payment;
u64 id;
id = sendpay_index_created(wallet->ld,
payment_hash,
partid, groupid, status);
payment = wallet_payment_new(ctx, id, 0,
timestamp,
completed_at,
payment_hash,
partid,
groupid,
status,
destination,
msatoshi,
msatoshi_sent,
total_msat,
payment_preimage,
path_secrets,
route_nodes,
route_channels,
invstring,
label,
description,
failonion,
local_invreq_id);
stmt = db_prepare_v2(
wallet->db,
SQL("INSERT INTO payments ("
" id,"
2020-05-21 22:43:27 +01:00
" status,"
" payment_hash,"
" destination,"
" msatoshi,"
" timestamp,"
" path_secrets,"
" route_nodes,"
" route_channels,"
" msatoshi_sent,"
" description,"
" bolt11,"
" total_msat,"
" partid,"
" local_invreq_id,"
" groupid,"
" paydescription"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
assert(payment->id > 0);
db_bind_u64(stmt, payment->id);
db_bind_int(stmt, payment->status);
db_bind_sha256(stmt, &payment->payment_hash);
if (payment->destination != NULL)
db_bind_node_id(stmt, payment->destination);
else
db_bind_null(stmt);
db_bind_amount_msat(stmt, &payment->msatoshi);
db_bind_int(stmt, payment->timestamp);
if (payment->path_secrets != NULL)
db_bind_secret_arr(stmt, payment->path_secrets);
else
db_bind_null(stmt);
assert((payment->route_channels == NULL) == (payment->route_nodes == NULL));
if (payment->route_nodes) {
db_bind_node_id_arr(stmt, payment->route_nodes);
db_bind_short_channel_id_arr(stmt, payment->route_channels);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_amount_msat(stmt, &payment->msatoshi_sent);
if (payment->label != NULL)
db_bind_text(stmt, payment->label);
else
db_bind_null(stmt);
if (payment->invstring != NULL)
db_bind_text(stmt, payment->invstring);
else
db_bind_null(stmt);
db_bind_amount_msat(stmt, &payment->total_msat);
db_bind_u64(stmt, payment->partid);
if (payment->local_invreq_id != NULL)
db_bind_sha256(stmt, payment->local_invreq_id);
else
db_bind_null(stmt);
db_bind_u64(stmt, payment->groupid);
2021-09-28 16:32:50 +01:00
if (payment->description != NULL)
db_bind_text(stmt, payment->description);
else
db_bind_null(stmt);
db_exec_prepared_v2(stmt);
tal_free(stmt);
return payment;
}
u64 wallet_payment_get_groupid(struct wallet *wallet,
const struct sha256 *payment_hash)
{
struct db_stmt *stmt;
u64 groupid = 0;
stmt = db_prepare_v2(
wallet->db, SQL("SELECT MAX(groupid) FROM payments WHERE payment_hash = ?"));
db_bind_sha256(stmt, payment_hash);
db_query_prepared(stmt);
if (db_step(stmt) && !db_col_is_null(stmt, "MAX(groupid)")) {
groupid = db_col_u64(stmt, "MAX(groupid)");
}
tal_free(stmt);
return groupid;
}
void wallet_payment_delete(struct wallet *wallet,
const struct sha256 *payment_hash,
delpay: delete the payment by status from the db There are cases (difficult to reproduce with a test) where a payment will fail one time and succeed later. As far I understand in this case the groupid field of the payment is the same, and the only thing that change is the status, so our logic inside the delpay is ambiguous where it is not possible to delete a payment as described in https://github.com/ElementsProject/lightning/issues/6114 A sequence of commands that explain the problem is ``` $ lc -k listpays payment_hash=H { "pays": [ { "bolt11": "I", "destination": "redacted", "payment_hash": "H", "status": "complete", "created_at": redacted, "completed_at": redacted, "preimage": "P", "amount_msat": "redacted", "amount_sent_msat": "redacted" } ] } $ lc delpay H complete { "code": 211, "message": "Payment with hash H has failed status but it should be complete" } ``` In this case, the delpay is not able to delete a payment because the listpays is returning only the succeeded one, so by running the listsendpays we may see the following result where our delpay logic will be stuck because it works to ensure that all the payments stored in the database has the status specified by the user ``` ➜ VincentSSD clightning --testnet listsendpays -k payment_hash=7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4 { "payments": [ { "id": 322, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 1, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 1664, "created_at": 1679510203, "completed_at": 1679510205, "status": "failed", "bolt11": "lntb1pjpkj4xsp52trda39rfpe7qtqahx8jjplhnj3tatxy8rh6sc6afgvmdz7n0llspp50lr5hmdm0re0xvcp2hv3nf2wwvx0r8q3h3e7jmqz0awdfg6w206qdp0w3jhxarfdenjqargv5sxgetvwpshjgrzw4njqun9wphhyaqxqyjw5qcqp2rzjqtp28uqy77te96ylt7ek703h4ayldljsf8rnlztgf3p8mg7pd0qzwf8a3yqqpdqqqyqqqqt2qqqqqqgqqc9qxpqysgqgeya2lguaj6sflc4hx2d89jvah8mw9uax4j77d8rzkut3rkm0554x37fc7gy92ws9l76yprdva2lalrs7fqjp9lcx40zuty8gca0g5spme3dup" }, { "id": 323, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 2, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510205, "completed_at": 1679510207, "status": "failed" }, { "id": 324, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 3, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510207, "completed_at": 1679510209, "status": "failed" }, { "id": 325, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 4, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 4663, "created_at": 1679510209, "completed_at": 1679510221, "status": "complete", "payment_preimage": "43f746f2d28d4902489cbde9b3b8f3d04db5db7e973f8a55b7229ce774bf33a7" } ] } ``` This commit solves the problem by forcing the delete query in the database to specify status too, and work around this kind of ambiguous case. Fixes: f52ff07558709bd1f7ed0cdca65c891d80b1a785 (lightningd: allow delpay to delete a specific payment.) Reported-by: Antoine Poinsot <darosior@protonmail.com> Link: https://github.com/ElementsProject/lightning/issues/6114 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Developed-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Fixed: delpay be more pedantic about delete logic by allowing delete payments by status directly on the database.
2023-03-22 19:22:25 +00:00
const u64 *groupid, const u64 *partid,
const enum payment_status *status)
{
struct db_stmt *stmt;
delpay: delete the payment by status from the db There are cases (difficult to reproduce with a test) where a payment will fail one time and succeed later. As far I understand in this case the groupid field of the payment is the same, and the only thing that change is the status, so our logic inside the delpay is ambiguous where it is not possible to delete a payment as described in https://github.com/ElementsProject/lightning/issues/6114 A sequence of commands that explain the problem is ``` $ lc -k listpays payment_hash=H { "pays": [ { "bolt11": "I", "destination": "redacted", "payment_hash": "H", "status": "complete", "created_at": redacted, "completed_at": redacted, "preimage": "P", "amount_msat": "redacted", "amount_sent_msat": "redacted" } ] } $ lc delpay H complete { "code": 211, "message": "Payment with hash H has failed status but it should be complete" } ``` In this case, the delpay is not able to delete a payment because the listpays is returning only the succeeded one, so by running the listsendpays we may see the following result where our delpay logic will be stuck because it works to ensure that all the payments stored in the database has the status specified by the user ``` ➜ VincentSSD clightning --testnet listsendpays -k payment_hash=7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4 { "payments": [ { "id": 322, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 1, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 1664, "created_at": 1679510203, "completed_at": 1679510205, "status": "failed", "bolt11": "lntb1pjpkj4xsp52trda39rfpe7qtqahx8jjplhnj3tatxy8rh6sc6afgvmdz7n0llspp50lr5hmdm0re0xvcp2hv3nf2wwvx0r8q3h3e7jmqz0awdfg6w206qdp0w3jhxarfdenjqargv5sxgetvwpshjgrzw4njqun9wphhyaqxqyjw5qcqp2rzjqtp28uqy77te96ylt7ek703h4ayldljsf8rnlztgf3p8mg7pd0qzwf8a3yqqpdqqqyqqqqt2qqqqqqgqqc9qxpqysgqgeya2lguaj6sflc4hx2d89jvah8mw9uax4j77d8rzkut3rkm0554x37fc7gy92ws9l76yprdva2lalrs7fqjp9lcx40zuty8gca0g5spme3dup" }, { "id": 323, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 2, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510205, "completed_at": 1679510207, "status": "failed" }, { "id": 324, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 3, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510207, "completed_at": 1679510209, "status": "failed" }, { "id": 325, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 4, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 4663, "created_at": 1679510209, "completed_at": 1679510221, "status": "complete", "payment_preimage": "43f746f2d28d4902489cbde9b3b8f3d04db5db7e973f8a55b7229ce774bf33a7" } ] } ``` This commit solves the problem by forcing the delete query in the database to specify status too, and work around this kind of ambiguous case. Fixes: f52ff07558709bd1f7ed0cdca65c891d80b1a785 (lightningd: allow delpay to delete a specific payment.) Reported-by: Antoine Poinsot <darosior@protonmail.com> Link: https://github.com/ElementsProject/lightning/issues/6114 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Developed-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Fixed: delpay be more pedantic about delete logic by allowing delete payments by status directly on the database.
2023-03-22 19:22:25 +00:00
assert(status);
if (groupid) {
assert(partid);
stmt = db_prepare_v2(wallet->db,
SQL("DELETE FROM payments"
" WHERE payment_hash = ?"
" AND groupid = ?"
delpay: delete the payment by status from the db There are cases (difficult to reproduce with a test) where a payment will fail one time and succeed later. As far I understand in this case the groupid field of the payment is the same, and the only thing that change is the status, so our logic inside the delpay is ambiguous where it is not possible to delete a payment as described in https://github.com/ElementsProject/lightning/issues/6114 A sequence of commands that explain the problem is ``` $ lc -k listpays payment_hash=H { "pays": [ { "bolt11": "I", "destination": "redacted", "payment_hash": "H", "status": "complete", "created_at": redacted, "completed_at": redacted, "preimage": "P", "amount_msat": "redacted", "amount_sent_msat": "redacted" } ] } $ lc delpay H complete { "code": 211, "message": "Payment with hash H has failed status but it should be complete" } ``` In this case, the delpay is not able to delete a payment because the listpays is returning only the succeeded one, so by running the listsendpays we may see the following result where our delpay logic will be stuck because it works to ensure that all the payments stored in the database has the status specified by the user ``` ➜ VincentSSD clightning --testnet listsendpays -k payment_hash=7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4 { "payments": [ { "id": 322, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 1, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 1664, "created_at": 1679510203, "completed_at": 1679510205, "status": "failed", "bolt11": "lntb1pjpkj4xsp52trda39rfpe7qtqahx8jjplhnj3tatxy8rh6sc6afgvmdz7n0llspp50lr5hmdm0re0xvcp2hv3nf2wwvx0r8q3h3e7jmqz0awdfg6w206qdp0w3jhxarfdenjqargv5sxgetvwpshjgrzw4njqun9wphhyaqxqyjw5qcqp2rzjqtp28uqy77te96ylt7ek703h4ayldljsf8rnlztgf3p8mg7pd0qzwf8a3yqqpdqqqyqqqqt2qqqqqqgqqc9qxpqysgqgeya2lguaj6sflc4hx2d89jvah8mw9uax4j77d8rzkut3rkm0554x37fc7gy92ws9l76yprdva2lalrs7fqjp9lcx40zuty8gca0g5spme3dup" }, { "id": 323, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 2, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510205, "completed_at": 1679510207, "status": "failed" }, { "id": 324, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 3, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510207, "completed_at": 1679510209, "status": "failed" }, { "id": 325, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 4, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 4663, "created_at": 1679510209, "completed_at": 1679510221, "status": "complete", "payment_preimage": "43f746f2d28d4902489cbde9b3b8f3d04db5db7e973f8a55b7229ce774bf33a7" } ] } ``` This commit solves the problem by forcing the delete query in the database to specify status too, and work around this kind of ambiguous case. Fixes: f52ff07558709bd1f7ed0cdca65c891d80b1a785 (lightningd: allow delpay to delete a specific payment.) Reported-by: Antoine Poinsot <darosior@protonmail.com> Link: https://github.com/ElementsProject/lightning/issues/6114 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Developed-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Fixed: delpay be more pedantic about delete logic by allowing delete payments by status directly on the database.
2023-03-22 19:22:25 +00:00
" AND partid = ?"
" AND status = ?"));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, *groupid);
db_bind_u64(stmt, *partid);
db_bind_u64(stmt, *status);
sendpay_index_deleted(wallet->ld, payment_hash, *partid, *groupid,
*status);
} else {
assert(!partid);
stmt = db_prepare_v2(wallet->db,
SQL("DELETE FROM payments"
delpay: delete the payment by status from the db There are cases (difficult to reproduce with a test) where a payment will fail one time and succeed later. As far I understand in this case the groupid field of the payment is the same, and the only thing that change is the status, so our logic inside the delpay is ambiguous where it is not possible to delete a payment as described in https://github.com/ElementsProject/lightning/issues/6114 A sequence of commands that explain the problem is ``` $ lc -k listpays payment_hash=H { "pays": [ { "bolt11": "I", "destination": "redacted", "payment_hash": "H", "status": "complete", "created_at": redacted, "completed_at": redacted, "preimage": "P", "amount_msat": "redacted", "amount_sent_msat": "redacted" } ] } $ lc delpay H complete { "code": 211, "message": "Payment with hash H has failed status but it should be complete" } ``` In this case, the delpay is not able to delete a payment because the listpays is returning only the succeeded one, so by running the listsendpays we may see the following result where our delpay logic will be stuck because it works to ensure that all the payments stored in the database has the status specified by the user ``` ➜ VincentSSD clightning --testnet listsendpays -k payment_hash=7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4 { "payments": [ { "id": 322, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 1, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 1664, "created_at": 1679510203, "completed_at": 1679510205, "status": "failed", "bolt11": "lntb1pjpkj4xsp52trda39rfpe7qtqahx8jjplhnj3tatxy8rh6sc6afgvmdz7n0llspp50lr5hmdm0re0xvcp2hv3nf2wwvx0r8q3h3e7jmqz0awdfg6w206qdp0w3jhxarfdenjqargv5sxgetvwpshjgrzw4njqun9wphhyaqxqyjw5qcqp2rzjqtp28uqy77te96ylt7ek703h4ayldljsf8rnlztgf3p8mg7pd0qzwf8a3yqqpdqqqyqqqqt2qqqqqqgqqc9qxpqysgqgeya2lguaj6sflc4hx2d89jvah8mw9uax4j77d8rzkut3rkm0554x37fc7gy92ws9l76yprdva2lalrs7fqjp9lcx40zuty8gca0g5spme3dup" }, { "id": 323, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 2, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510205, "completed_at": 1679510207, "status": "failed" }, { "id": 324, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 3, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 3663, "created_at": 1679510207, "completed_at": 1679510209, "status": "failed" }, { "id": 325, "payment_hash": "7fc74bedbb78f2f3330155d919a54e730cf19c11bc73e96c027f5cd4a34e53f4", "groupid": 1, "partid": 4, "destination": "030b686a163aa2bba03cebb8bab7778fac251536498141df0a436d688352d426f6", "amount_msat": 300, "amount_sent_msat": 4663, "created_at": 1679510209, "completed_at": 1679510221, "status": "complete", "payment_preimage": "43f746f2d28d4902489cbde9b3b8f3d04db5db7e973f8a55b7229ce774bf33a7" } ] } ``` This commit solves the problem by forcing the delete query in the database to specify status too, and work around this kind of ambiguous case. Fixes: f52ff07558709bd1f7ed0cdca65c891d80b1a785 (lightningd: allow delpay to delete a specific payment.) Reported-by: Antoine Poinsot <darosior@protonmail.com> Link: https://github.com/ElementsProject/lightning/issues/6114 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Developed-by: Rusty Russell <rusty@rustcorp.com.au> Changelog-Fixed: delpay be more pedantic about delete logic by allowing delete payments by status directly on the database.
2023-03-22 19:22:25 +00:00
" WHERE payment_hash = ?"
" AND status = ?"));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, *status);
/* FIXME: Increment deleted appropriately! */
}
db_exec_prepared_v2(take(stmt));
}
static
struct wallet_payment *wallet_payment_new(const tal_t *ctx,
u64 dbid,
u64 updated_index,
u32 timestamp,
const u32 *completed_at,
const struct sha256 *payment_hash,
u64 partid,
u64 groupid,
enum payment_status status,
/* The destination may not be known if we used `sendonion` */
const struct node_id *destination,
struct amount_msat msatoshi,
struct amount_msat msatoshi_sent,
struct amount_msat total_msat,
/* If and only if PAYMENT_COMPLETE */
const struct preimage *payment_preimage,
const struct secret *path_secrets,
const struct node_id *route_nodes,
const struct short_channel_id *route_channels,
const char *invstring,
const char *label,
const char *description,
const u8 *failonion,
const struct sha256 *local_invreq_id)
{
struct wallet_payment *payment = tal(ctx, struct wallet_payment);
payment->id = dbid;
payment->updated_index = updated_index;
payment->status = status;
payment->timestamp = timestamp;
payment->payment_hash = *payment_hash;
payment->partid = partid;
payment->groupid = groupid;
payment->status = status;
payment->msatoshi = msatoshi;
payment->msatoshi_sent = msatoshi_sent;
payment->total_msat = total_msat;
/* Optional fields */
payment->completed_at = tal_dup_or_null(payment, u32, completed_at);
payment->destination = tal_dup_or_null(payment, struct node_id, destination);
payment->payment_preimage = tal_dup_or_null(payment, struct preimage, payment_preimage);
payment->path_secrets = tal_dup_talarr(payment, struct secret, path_secrets);
payment->route_nodes = tal_dup_talarr(payment, struct node_id, route_nodes);
payment->route_channels = tal_dup_talarr(payment, struct short_channel_id, route_channels);
payment->invstring = tal_strdup_or_null(payment, invstring);
payment->label = tal_strdup_or_null(payment, label);
payment->description = tal_strdup_or_null(payment, description);
payment->failonion = tal_dup_talarr(payment, u8, failonion);
payment->local_invreq_id = tal_dup_or_null(payment, struct sha256, local_invreq_id);
return payment;
}
struct wallet_payment *payment_get_details(const tal_t *ctx,
struct db_stmt *stmt)
{
struct wallet_payment *payment;
u32 *completed_at;
struct sha256 payment_hash;
db_col_sha256(stmt, "payment_hash", &payment_hash);
if (!db_col_is_null(stmt, "completed_at")) {
completed_at = tal(tmpctx, u32);
*completed_at = db_col_int(stmt, "completed_at");
} else
completed_at = NULL;
payment = wallet_payment_new(ctx,
db_col_u64(stmt, "id"),
db_col_u64(stmt, "updated_index"),
db_col_int(stmt, "timestamp"),
completed_at,
&payment_hash,
db_col_is_null(stmt, "partid") ? 0 : db_col_u64(stmt, "partid"),
db_col_u64(stmt, "groupid"),
payment_status_in_db(db_col_int(stmt, "status")),
take(db_col_optional(NULL, stmt, "destination", node_id)),
db_col_amount_msat(stmt, "msatoshi"),
db_col_amount_msat(stmt, "msatoshi_sent"),
db_col_is_null(stmt, "total_msat") ? AMOUNT_MSAT(0) : db_col_amount_msat(stmt, "total_msat"),
take(db_col_optional(NULL, stmt, "payment_preimage", preimage)),
take(db_col_secret_arr(NULL, stmt, "path_secrets")),
take(db_col_node_id_arr(NULL, stmt, "route_nodes")),
take(db_col_short_channel_id_arr(NULL, stmt, "route_channels")),
take(db_col_strdup_optional(NULL, stmt, "bolt11")),
take(db_col_strdup_optional(NULL, stmt, "description")),
take(db_col_strdup_optional(NULL, stmt, "paydescription")),
take(db_col_arr(NULL, stmt, "failonionreply", u8)),
take(db_col_optional(NULL, stmt, "local_invreq_id", sha256)));
2021-09-28 16:32:50 +01:00
/* Either none, or both are set */
assert(db_col_is_null(stmt, "route_nodes")
== db_col_is_null(stmt, "route_channels"));
return payment;
}
struct wallet_payment *
wallet_payment_by_hash(const tal_t *ctx, struct wallet *wallet,
const struct sha256 *payment_hash,
u64 partid, u64 groupid)
{
struct db_stmt *stmt;
struct wallet_payment *payment;
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
2021-09-28 16:32:50 +01:00
", groupid"
", completed_at"
" FROM payments"
" WHERE payment_hash = ?"
" AND partid = ? AND groupid=?"));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_bind_u64(stmt, groupid);
db_query_prepared(stmt);
if (db_step(stmt)) {
payment = payment_get_details(ctx, stmt);
} else {
payment = NULL;
}
tal_free(stmt);
return payment;
}
void wallet_payment_set_status(struct wallet *wallet,
const struct sha256 *payment_hash,
2021-09-29 11:33:57 +01:00
u64 partid, u64 groupid,
const enum payment_status newstatus,
const struct preimage *preimage)
{
struct db_stmt *stmt;
u32 completed_at = 0;
if (newstatus != PAYMENT_PENDING)
completed_at = time_now().ts.tv_sec;
stmt = db_prepare_v2(wallet->db,
SQL("UPDATE payments SET status=?, completed_at=?, updated_index=? "
2021-09-29 11:33:57 +01:00
"WHERE payment_hash=? AND partid=? AND groupid=?"));
db_bind_int(stmt, payment_status_in_db(newstatus));
if (completed_at != 0) {
db_bind_u64(stmt, completed_at);
} else {
db_bind_null(stmt);
}
db_bind_u64(stmt, sendpay_index_update_status(wallet->ld, payment_hash,
partid, groupid, newstatus));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_bind_u64(stmt, groupid);
db_exec_prepared_v2(take(stmt));
if (preimage) {
stmt = db_prepare_v2(wallet->db,
SQL("UPDATE payments SET payment_preimage=? "
2021-09-29 11:33:57 +01:00
"WHERE payment_hash=? AND partid=? AND groupid=?"));
db_bind_preimage(stmt, preimage);
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_bind_u64(stmt, groupid);
db_exec_prepared_v2(take(stmt));
}
if (newstatus != PAYMENT_PENDING) {
stmt =
db_prepare_v2(wallet->db, SQL("UPDATE payments"
" SET path_secrets = NULL"
" , route_nodes = NULL"
" , route_channels = NULL"
" WHERE payment_hash = ?"
2021-09-29 11:33:57 +01:00
" AND partid = ? AND groupid=?;"));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_bind_u64(stmt, groupid);
db_exec_prepared_v2(take(stmt));
}
}
void wallet_payment_get_failinfo(const tal_t *ctx,
struct wallet *wallet,
const struct sha256 *payment_hash,
u64 partid,
u64 groupid,
/* outputs */
struct onionreply **failonionreply,
bool *faildestperm,
int *failindex,
enum onion_wire *failcode,
struct node_id **failnode,
struct short_channel_id **failchannel,
u8 **failupdate,
char **faildetail,
int *faildirection)
{
struct db_stmt *stmt;
bool resb;
stmt = db_prepare_v2(wallet->db,
SQL("SELECT failonionreply, faildestperm"
", failindex, failcode"
", failnode, failscid"
", failupdate, faildetail, faildirection"
" FROM payments"
" WHERE payment_hash=? AND partid=? AND groupid=?;"));
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_bind_u64(stmt, groupid);
db_query_prepared(stmt);
resb = db_step(stmt);
assert(resb);
if (db_col_is_null(stmt, "failonionreply"))
*failonionreply = NULL;
else {
*failonionreply = db_col_onionreply(ctx, stmt, "failonionreply");
}
*faildestperm = db_col_int(stmt, "faildestperm") != 0;
*failindex = db_col_int(stmt, "failindex");
*failcode = (enum onion_wire) db_col_int(stmt, "failcode");
*failnode = db_col_optional(ctx, stmt, "failnode", node_id);
*failchannel = db_col_optional(ctx, stmt, "failscid", short_channel_id);
if (*failchannel) {
/* For pre-0.6.2 dbs, direction will be 0 */
*faildirection = db_col_int(stmt, "faildirection");
} else {
db_col_ignore(stmt, "faildirection");
}
if (db_col_is_null(stmt, "failupdate"))
*failupdate = NULL;
else {
*failupdate = db_col_arr(ctx, stmt, "failupdate", u8);
}
if (!db_col_is_null(stmt, "faildetail"))
*faildetail = db_col_strdup(ctx, stmt, "faildetail");
else
*faildetail = NULL;
tal_free(stmt);
}
void wallet_payment_set_failinfo(struct wallet *wallet,
const struct sha256 *payment_hash,
u64 partid,
const struct onionreply *failonionreply,
bool faildestperm,
int failindex,
enum onion_wire failcode,
const struct node_id *failnode,
const struct short_channel_id *failchannel,
const u8 *failupdate /*tal_arr*/,
const char *faildetail,
int faildirection)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db, SQL("UPDATE payments"
" SET failonionreply=?"
" , faildestperm=?"
" , failindex=?"
" , failcode=?"
" , failnode=?"
" , failscid=?"
" , faildirection=?"
" , failupdate=?"
" , faildetail=?"
" WHERE payment_hash=?"
" AND partid=?;"));
if (failonionreply)
db_bind_talarr(stmt, failonionreply->contents);
else
db_bind_null(stmt);
db_bind_int(stmt, faildestperm ? 1 : 0);
db_bind_int(stmt, failindex);
db_bind_int(stmt, (int) failcode);
if (failnode)
db_bind_node_id(stmt, failnode);
else
db_bind_null(stmt);
if (failchannel) {
db_bind_short_channel_id(stmt, failchannel);
db_bind_int(stmt, faildirection);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_talarr(stmt, failupdate);
if (faildetail != NULL)
db_bind_text(stmt, faildetail);
else
db_bind_null(stmt);
db_bind_sha256(stmt, payment_hash);
db_bind_u64(stmt, partid);
db_exec_prepared_v2(take(stmt));
}
struct db_stmt *payments_first(struct wallet *wallet,
const enum wait_index *listindex,
u64 liststart,
const u32 *listlimit)
{
struct db_stmt *stmt;
if (listindex && *listindex == WAIT_INDEX_UPDATED) {
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE updated_index >= ?"
" ORDER BY updated_index"
" LIMIT ?;"));
} else {
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE id >= ?"
" ORDER BY id"
" LIMIT ?;"));
}
db_bind_u64(stmt, liststart);
if (listlimit)
db_bind_int(stmt, *listlimit);
else
db_bind_int(stmt, INT_MAX);
db_query_prepared(stmt);
return payments_next(wallet, stmt);
}
struct db_stmt *payments_by_hash(struct wallet *wallet,
const struct sha256 *payment_hash)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE"
" payment_hash = ?"
" ORDER BY id;"));
db_bind_sha256(stmt, payment_hash);
db_query_prepared(stmt);
return payments_next(wallet, stmt);
}
struct db_stmt *payments_by_label(struct wallet *wallet,
const struct json_escape *label)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE"
/* label is called "description" in db */
" description = ?;"));
db_bind_json_escape(stmt, label);
db_query_prepared(stmt);
return payments_next(wallet, stmt);
}
struct db_stmt *payments_by_status(struct wallet *wallet,
enum payment_status status,
const enum wait_index *listindex,
u64 liststart,
const u32 *listlimit)
{
struct db_stmt *stmt;
if (listindex && *listindex == WAIT_INDEX_UPDATED) {
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE"
" status = ?"
" AND"
" updated_index >= ?"
" ORDER BY updated_index"
" LIMIT ?;"));
} else {
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
", groupid"
", completed_at"
" FROM payments"
" WHERE"
" status = ?"
" AND"
" id >= ?"
" ORDER BY id"
" LIMIT ?;"));
}
db_bind_int(stmt, payment_status_in_db(status));
db_bind_u64(stmt, liststart);
if (listlimit)
db_bind_int(stmt, *listlimit);
else
db_bind_int(stmt, INT_MAX);
db_query_prepared(stmt);
return payments_next(wallet, stmt);
}
struct db_stmt *payments_by_invoice_request(struct wallet *wallet,
const struct sha256 *local_invreq_id)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db, SQL("SELECT"
" id"
", updated_index"
", status"
", destination"
", msatoshi"
", payment_hash"
", timestamp"
", payment_preimage"
", path_secrets"
", route_nodes"
", route_channels"
", msatoshi_sent"
", description"
", bolt11"
", paydescription"
", failonionreply"
", total_msat"
", partid"
", local_invreq_id"
2021-09-28 16:32:50 +01:00
", groupid"
", completed_at"
" FROM payments"
" WHERE local_invreq_id = ?;"));
db_bind_sha256(stmt, local_invreq_id);
db_query_prepared(stmt);
return payments_next(wallet, stmt);
}
struct db_stmt *payments_next(struct wallet *w,
struct db_stmt *stmt)
{
if (!db_step(stmt))
return tal_free(stmt);
return stmt;
}
void wallet_htlc_sigs_save(struct wallet *w, u64 channel_id,
const struct bitcoin_signature *htlc_sigs)
{
/* Clear any existing HTLC sigs for this channel */
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("DELETE FROM htlc_sigs WHERE channelid = ?"));
db_bind_u64(stmt, channel_id);
db_exec_prepared_v2(take(stmt));
/* Now insert the new ones */
for (size_t i=0; i<tal_count(htlc_sigs); i++) {
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO htlc_sigs (channelid, "
"signature) VALUES (?, ?)"));
db_bind_u64(stmt, channel_id);
db_bind_signature(stmt, &htlc_sigs[i].s);
db_exec_prepared_v2(take(stmt));
}
}
void wallet_htlc_sigs_add(struct wallet *w, u64 channel_id,
struct bitcoin_outpoint inflight_outpoint,
const struct bitcoin_signature *htlc_sigs)
{
struct db_stmt *stmt;
/* Now insert the new ones */
for (size_t i=0; i<tal_count(htlc_sigs); i++) {
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO htlc_sigs (channelid,"
" inflight_tx_id, inflight_tx_outnum,"
2023-09-22 11:12:35 +01:00
" signature) VALUES (?, ?, ?, ?)"));
db_bind_u64(stmt, channel_id);
db_bind_txid(stmt, &inflight_outpoint.txid);
db_bind_int(stmt, inflight_outpoint.n);
db_bind_signature(stmt, &htlc_sigs[i].s);
db_exec_prepared_v2(take(stmt));
}
}
bool wallet_sanity_check(struct wallet *w)
{
struct bitcoin_blkid chainhash;
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("SELECT blobval FROM vars WHERE name='genesis_hash'"));
db_query_prepared(stmt);
if (db_step(stmt)) {
db_col_sha256d(stmt, "blobval", &chainhash.shad);
tal_free(stmt);
if (!bitcoin_blkid_eq(&chainhash,
&chainparams->genesis_blockhash)) {
log_broken(w->log, "Wallet blockchain hash does not "
"match network blockchain hash: %s "
"!= %s. "
2018-10-19 05:21:00 +01:00
"Are you on the right network? "
"(--network={one of %s})",
type_to_string(w, struct bitcoin_blkid,
&chainhash),
type_to_string(w, struct bitcoin_blkid,
&chainparams->genesis_blockhash),
chainparams_get_network_names(tmpctx));
return false;
}
} else {
tal_free(stmt);
/* Still a pristine wallet, claim it for the chain
* that we are running */
stmt = db_prepare_v2(w->db, SQL("INSERT INTO vars (name, blobval) "
"VALUES ('genesis_hash', ?);"));
db_bind_sha256d(stmt, &chainparams->genesis_blockhash.shad);
db_exec_prepared_v2(take(stmt));
}
stmt = db_prepare_v2(w->db,
SQL("SELECT blobval FROM vars WHERE name='node_id'"));
db_query_prepared(stmt);
if (db_step(stmt)) {
struct node_id id;
db_col_node_id(stmt, "blobval", &id);
tal_free(stmt);
if (!node_id_eq(&id, &w->ld->id)) {
log_broken(w->log, "Wallet node_id does not "
"match HSM: %s "
"!= %s. "
"Did your hsm_secret change?",
type_to_string(tmpctx, struct node_id, &id),
type_to_string(tmpctx, struct node_id,
&w->ld->id));
return false;
}
} else {
tal_free(stmt);
/* Still a pristine wallet, claim it for the node_id we are now */
stmt = db_prepare_v2(w->db, SQL("INSERT INTO vars (name, blobval) "
"VALUES ('node_id', ?);"));
db_bind_node_id(stmt, &w->ld->id);
db_exec_prepared_v2(take(stmt));
}
return true;
}
/**
* wallet_utxoset_prune -- Remove spent UTXO entries that are old
*/
static void wallet_utxoset_prune(struct wallet *w, const u32 blockheight)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(
w->db,
SQL("SELECT txid, outnum FROM utxoset WHERE spendheight < ?"));
db_bind_int(stmt, blockheight - UTXO_PRUNE_DEPTH);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct bitcoin_outpoint outpoint;
db_col_txid(stmt, "txid", &outpoint.txid);
outpoint.n = db_col_int(stmt, "outnum");
outpointfilter_remove(w->utxoset_outpoints, &outpoint);
}
tal_free(stmt);
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM utxoset WHERE spendheight < ?"));
db_bind_int(stmt, blockheight - UTXO_PRUNE_DEPTH);
db_exec_prepared_v2(take(stmt));
}
void wallet_block_add(struct wallet *w, struct block *b)
{
struct db_stmt *stmt =
db_prepare_v2(w->db, SQL("INSERT INTO blocks "
"(height, hash, prev_hash) "
"VALUES (?, ?, ?);"));
db_bind_int(stmt, b->height);
db_bind_sha256d(stmt, &b->blkid.shad);
if (b->prev) {
db_bind_sha256d(stmt, &b->prev->blkid.shad);
} else {
db_bind_null(stmt);
}
db_exec_prepared_v2(take(stmt));
/* Now cleanup UTXOs that we don't care about anymore */
wallet_utxoset_prune(w, b->height);
}
void wallet_block_remove(struct wallet *w, struct block *b)
{
struct db_stmt *stmt =
db_prepare_v2(w->db, SQL("DELETE FROM blocks WHERE hash = ?"));
db_bind_sha256d(stmt, &b->blkid.shad);
db_exec_prepared_v2(take(stmt));
/* Make sure that all descendants of the block are also deleted */
stmt = db_prepare_v2(w->db,
SQL("SELECT * FROM blocks WHERE height >= ?;"));
db_bind_int(stmt, b->height);
db_query_prepared(stmt);
assert(!db_step(stmt));
tal_free(stmt);
}
void wallet_blocks_rollback(struct wallet *w, u32 height)
{
struct db_stmt *stmt = db_prepare_v2(w->db, SQL("DELETE FROM blocks "
"WHERE height > ?"));
db_bind_int(stmt, height);
db_exec_prepared_v2(take(stmt));
}
bool wallet_outpoint_spend(struct wallet *w, const tal_t *ctx, const u32 blockheight,
const struct bitcoin_outpoint *outpoint)
{
struct db_stmt *stmt;
bool our_spend;
if (outpointfilter_matches(w->owned_outpoints, outpoint)) {
stmt = db_prepare_v2(w->db, SQL("UPDATE outputs "
"SET spend_height = ?, "
" status = ? "
"WHERE prev_out_tx = ?"
" AND prev_out_index = ?"));
db_bind_int(stmt, blockheight);
db_bind_int(stmt, output_status_in_db(OUTPUT_STATE_SPENT));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_exec_prepared_v2(take(stmt));
our_spend = true;
} else
our_spend = false;
if (outpointfilter_matches(w->utxoset_outpoints, outpoint)) {
stmt = db_prepare_v2(w->db, SQL("UPDATE utxoset "
"SET spendheight = ? "
"WHERE txid = ?"
" AND outnum = ?"));
db_bind_int(stmt, blockheight);
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
return our_spend;
}
void wallet_utxoset_add(struct wallet *w,
const struct bitcoin_outpoint *outpoint,
const u32 blockheight,
const u32 txindex, const u8 *scriptpubkey,
struct amount_sat sat)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("INSERT INTO utxoset ("
" txid,"
" outnum,"
" blockheight,"
" spendheight,"
" txindex,"
" scriptpubkey,"
" satoshis"
") VALUES(?, ?, ?, ?, ?, ?, ?);"));
db_bind_txid(stmt, &outpoint->txid);
db_bind_int(stmt, outpoint->n);
db_bind_int(stmt, blockheight);
db_bind_null(stmt);
db_bind_int(stmt, txindex);
db_bind_talarr(stmt, scriptpubkey);
db_bind_amount_sat(stmt, &sat);
db_exec_prepared_v2(take(stmt));
outpointfilter_add(w->utxoset_outpoints, outpoint);
}
void wallet_filteredblock_add(struct wallet *w, const struct filteredblock *fb)
{
struct db_stmt *stmt;
if (wallet_have_block(w, fb->height))
return;
stmt = db_prepare_v2(w->db, SQL("INSERT INTO blocks "
"(height, hash, prev_hash) "
"VALUES (?, ?, ?);"));
db_bind_int(stmt, fb->height);
db_bind_sha256d(stmt, &fb->id.shad);
db_bind_sha256d(stmt, &fb->prev_hash.shad);
db_exec_prepared_v2(take(stmt));
for (size_t i = 0; i < tal_count(fb->outpoints); i++) {
struct filteredblock_outpoint *o = fb->outpoints[i];
stmt =
db_prepare_v2(w->db, SQL("INSERT INTO utxoset ("
" txid,"
" outnum,"
" blockheight,"
" spendheight,"
" txindex,"
" scriptpubkey,"
" satoshis"
") VALUES(?, ?, ?, ?, ?, ?, ?);"));
db_bind_txid(stmt, &o->outpoint.txid);
db_bind_int(stmt, o->outpoint.n);
db_bind_int(stmt, fb->height);
db_bind_null(stmt);
db_bind_int(stmt, o->txindex);
db_bind_talarr(stmt, o->scriptPubKey);
db_bind_amount_sat(stmt, &o->amount);
db_exec_prepared_v2(take(stmt));
outpointfilter_add(w->utxoset_outpoints, &o->outpoint);
}
}
bool wallet_have_block(struct wallet *w, u32 blockheight)
{
bool result;
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("SELECT height FROM blocks WHERE height = ?"));
db_bind_int(stmt, blockheight);
db_query_prepared(stmt);
result = db_step(stmt);
if (result)
db_col_ignore(stmt, "height");
tal_free(stmt);
return result;
}
struct outpoint *wallet_outpoint_for_scid(struct wallet *w, tal_t *ctx,
const struct short_channel_id *scid)
{
struct db_stmt *stmt;
struct outpoint *op;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" txid,"
" spendheight,"
" scriptpubkey,"
" satoshis "
"FROM utxoset "
"WHERE blockheight = ?"
" AND txindex = ?"
" AND outnum = ?"
" AND spendheight IS NULL"));
db_bind_int(stmt, short_channel_id_blocknum(scid));
db_bind_int(stmt, short_channel_id_txnum(scid));
db_bind_int(stmt, short_channel_id_outnum(scid));
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
op = tal(ctx, struct outpoint);
op->blockheight = short_channel_id_blocknum(scid);
op->txindex = short_channel_id_txnum(scid);
op->outpoint.n = short_channel_id_outnum(scid);
db_col_txid(stmt, "txid", &op->outpoint.txid);
if (db_col_is_null(stmt, "spendheight"))
op->spendheight = 0;
else
op->spendheight = db_col_int(stmt, "spendheight");
op->scriptpubkey = db_col_arr(op, stmt, "scriptpubkey", u8);
op->sat = db_col_amount_sat(stmt, "satoshis");
tal_free(stmt);
return op;
}
/* Turns "SELECT blockheight, txindex, outnum" into scids */
static const struct short_channel_id *db_scids(const tal_t *ctx,
struct db_stmt *stmt STEALS)
{
struct short_channel_id *res = tal_arr(ctx, struct short_channel_id, 0);
while (db_step(stmt)) {
struct short_channel_id scid;
u64 blocknum, txnum, outnum;
bool ok;
blocknum = db_col_int(stmt, "blockheight");
txnum = db_col_int(stmt, "txindex");
outnum = db_col_int(stmt, "outnum");
ok = mk_short_channel_id(&scid, blocknum, txnum, outnum);
assert(ok);
tal_arr_expand(&res, scid);
}
tal_free(stmt);
return res;
}
const struct short_channel_id *
wallet_utxoset_get_spent(const tal_t *ctx, struct wallet *w,
u32 blockheight)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" blockheight,"
" txindex,"
" outnum "
"FROM utxoset "
"WHERE spendheight = ?"));
db_bind_int(stmt, blockheight);
db_query_prepared(stmt);
return db_scids(ctx, stmt);
}
const struct short_channel_id *
wallet_utxoset_get_created(const tal_t *ctx, struct wallet *w,
u32 blockheight)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" blockheight,"
" txindex,"
" outnum "
"FROM utxoset "
"WHERE blockheight = ?"));
db_bind_int(stmt, blockheight);
db_query_prepared(stmt);
return db_scids(ctx, stmt);
}
void wallet_transaction_add(struct wallet *w, const struct wally_tx *tx,
const u32 blockheight, const u32 txindex)
{
struct bitcoin_txid txid;
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("SELECT blockheight FROM transactions WHERE id=?"));
wally_txid(tx, &txid);
db_bind_txid(stmt, &txid);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
/* This transaction is still unknown, insert */
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO transactions ("
2020-05-21 22:43:27 +01:00
" id"
", blockheight"
", txindex"
", rawtx) VALUES (?, ?, ?, ?);"));
db_bind_txid(stmt, &txid);
if (blockheight) {
db_bind_int(stmt, blockheight);
db_bind_int(stmt, txindex);
} else {
db_bind_null(stmt);
db_bind_null(stmt);
}
db_bind_tx(stmt, tx);
db_exec_prepared_v2(take(stmt));
} else {
db_col_ignore(stmt, "blockheight");
tal_free(stmt);
if (blockheight) {
/* We know about the transaction, update */
stmt = db_prepare_v2(w->db,
SQL("UPDATE transactions "
"SET blockheight = ?, txindex = ? "
"WHERE id = ?"));
db_bind_int(stmt, blockheight);
db_bind_int(stmt, txindex);
db_bind_txid(stmt, &txid);
db_exec_prepared_v2(take(stmt));
}
}
}
static void wallet_annotation_add(struct wallet *w, const struct bitcoin_txid *txid, int num,
enum wallet_tx_annotation_type annotation_type, enum wallet_tx_type type, u64 channel)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(
w->db,SQL("INSERT INTO transaction_annotations "
"(txid, idx, location, type, channel) "
"VALUES (?, ?, ?, ?, ?) ON CONFLICT(txid,idx) DO NOTHING;"));
db_bind_txid(stmt, txid);
db_bind_int(stmt, num);
db_bind_int(stmt, annotation_type);
db_bind_int(stmt, type);
if (channel != 0)
db_bind_u64(stmt, channel);
else
db_bind_null(stmt);
db_exec_prepared_v2(take(stmt));
}
void wallet_annotate_txout(struct wallet *w,
const struct bitcoin_outpoint *outpoint,
enum wallet_tx_type type, u64 channel)
{
wallet_annotation_add(w, &outpoint->txid, outpoint->n,
OUTPUT_ANNOTATION, type, channel);
}
void wallet_annotate_txin(struct wallet *w, const struct bitcoin_txid *txid,
int innum, enum wallet_tx_type type, u64 channel)
{
wallet_annotation_add(w, txid, innum, INPUT_ANNOTATION, type, channel);
}
struct bitcoin_tx *wallet_transaction_get(const tal_t *ctx, struct wallet *w,
const struct bitcoin_txid *txid)
{
struct bitcoin_tx *tx;
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("SELECT rawtx FROM transactions WHERE id=?"));
db_bind_txid(stmt, txid);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
if (!db_col_is_null(stmt, "rawtx"))
tx = db_col_tx(ctx, stmt, "rawtx");
else
tx = NULL;
tal_free(stmt);
return tx;
}
u32 wallet_transaction_height(struct wallet *w, const struct bitcoin_txid *txid)
{
u32 blockheight;
struct db_stmt *stmt = db_prepare_v2(
w->db, SQL("SELECT blockheight FROM transactions WHERE id=?"));
db_bind_txid(stmt, txid);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return 0;
}
if (!db_col_is_null(stmt, "blockheight"))
blockheight = db_col_int(stmt, "blockheight");
else
blockheight = 0;
tal_free(stmt);
return blockheight;
}
struct txlocator *wallet_transaction_locate(const tal_t *ctx, struct wallet *w,
const struct bitcoin_txid *txid)
{
struct txlocator *loc;
struct db_stmt *stmt;
stmt = db_prepare_v2(
w->db, SQL("SELECT blockheight, txindex FROM transactions WHERE id=?"));
db_bind_txid(stmt, txid);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
if (db_col_is_null(stmt, "blockheight")) {
db_col_ignore(stmt, "txindex");
loc = NULL;
} else {
loc = tal(ctx, struct txlocator);
loc->blkheight = db_col_int(stmt, "blockheight");
loc->index = db_col_int(stmt, "txindex");
}
tal_free(stmt);
return loc;
}
struct bitcoin_txid *wallet_transactions_by_height(const tal_t *ctx,
struct wallet *w,
const u32 blockheight)
{
struct db_stmt *stmt;
struct bitcoin_txid *txids = tal_arr(ctx, struct bitcoin_txid, 0);
int count = 0;
stmt = db_prepare_v2(
w->db, SQL("SELECT id FROM transactions WHERE blockheight=?"));
db_bind_int(stmt, blockheight);
db_query_prepared(stmt);
while (db_step(stmt)) {
count++;
tal_resize(&txids, count);
db_col_txid(stmt, "id", &txids[count-1]);
}
tal_free(stmt);
return txids;
}
void wallet_channeltxs_add(struct wallet *w, struct channel *chan,
const int type, const struct bitcoin_txid *txid,
const u32 input_num, const u32 blockheight)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("INSERT INTO channeltxs ("
" channel_id"
", type"
", transaction_id"
", input_num"
", blockheight"
") VALUES (?, ?, ?, ?, ?);"));
db_bind_int(stmt, chan->dbid);
db_bind_int(stmt, type);
db_bind_sha256(stmt, &txid->shad.sha);
db_bind_int(stmt, input_num);
db_bind_int(stmt, blockheight);
db_exec_prepared_v2(take(stmt));
}
u32 *wallet_onchaind_channels(struct wallet *w,
const tal_t *ctx)
{
struct db_stmt *stmt;
size_t count = 0;
u32 *channel_ids = tal_arr(ctx, u32, 0);
stmt = db_prepare_v2(
w->db,
SQL("SELECT DISTINCT(channel_id) FROM channeltxs WHERE type = ?;"));
db_bind_int(stmt, WIRE_ONCHAIND_INIT);
db_query_prepared(stmt);
while (db_step(stmt)) {
count++;
tal_resize(&channel_ids, count);
channel_ids[count-1] = db_col_u64(stmt, "DISTINCT(channel_id)");
}
tal_free(stmt);
return channel_ids;
}
struct channeltx *wallet_channeltxs_get(struct wallet *w, const tal_t *ctx,
u32 channel_id)
{
struct db_stmt *stmt;
size_t count = 0;
struct channeltx *res = tal_arr(ctx, struct channeltx, 0);
stmt = db_prepare_v2(
w->db, SQL("SELECT"
" c.type"
", c.blockheight"
", t.rawtx"
", c.input_num"
", c.blockheight - t.blockheight + 1 AS depth"
", t.id as txid "
"FROM channeltxs c "
"JOIN transactions t ON t.id = c.transaction_id "
"WHERE c.channel_id = ? "
"ORDER BY c.id ASC;"));
db_bind_int(stmt, channel_id);
db_query_prepared(stmt);
while (db_step(stmt)) {
count++;
tal_resize(&res, count);
res[count-1].channel_id = channel_id;
res[count-1].type = db_col_int(stmt, "c.type");
res[count-1].blockheight = db_col_int(stmt, "c.blockheight");
res[count-1].tx = db_col_tx(ctx, stmt, "t.rawtx");
res[count-1].input_num = db_col_int(stmt, "c.input_num");
res[count-1].depth = db_col_int(stmt, "depth");
db_col_txid(stmt, "txid", &res[count-1].txid);
}
tal_free(stmt);
return res;
}
static bool wallet_forwarded_payment_update(struct wallet *w,
const struct htlc_in *in,
const struct htlc_out *out,
enum forward_status state,
enum onion_wire failcode,
struct timeabs *resolved_time,
enum forward_style forward_style)
{
struct db_stmt *stmt;
bool changed;
/* We update based solely on the htlc_in since an HTLC cannot be
* associated with more than one forwarded payment. This saves us from
* having to have two versions of the update statement (one with and
* one without the htlc_out restriction).*/
stmt = db_prepare_v2(w->db,
SQL("UPDATE forwards SET"
" updated_index=?"
", in_msatoshi=?"
", out_msatoshi=?"
", state=?"
", resolved_time=?"
", failcode=?"
", forward_style=?"
" WHERE in_htlc_id=? AND in_channel_scid=?"));
/* This may not work so don't increment index yet! */
db_bind_u64(stmt, w->ld->indexes[WAIT_SUBSYSTEM_FORWARD].i[WAIT_INDEX_UPDATED] + 1);
db_bind_amount_msat(stmt, &in->msat);
if (out) {
db_bind_amount_msat(stmt, &out->msat);
} else {
db_bind_null(stmt);
}
db_bind_int(stmt, wallet_forward_status_in_db(state));
if (resolved_time != NULL) {
db_bind_timeabs(stmt, *resolved_time);
} else {
db_bind_null(stmt);
}
if (failcode != 0) {
assert(state == FORWARD_FAILED || state == FORWARD_LOCAL_FAILED);
db_bind_int(stmt, (int)failcode);
} else {
db_bind_null(stmt);
}
/* This can happen for malformed onions, reload from db. */
if (forward_style == FORWARD_STYLE_UNKNOWN)
db_bind_null(stmt);
else
db_bind_int(stmt, forward_style_in_db(forward_style));
db_bind_u64(stmt, in->key.id);
db_bind_short_channel_id(stmt, channel_scid_or_local_alias(in->key.channel));
db_exec_prepared_v2(stmt);
changed = db_count_changes(stmt) != 0;
tal_free(stmt);
return changed;
}
void wallet_forwarded_payment_add(struct wallet *w, const struct htlc_in *in,
enum forward_style forward_style,
const struct short_channel_id *scid_out,
const struct htlc_out *out,
enum forward_status state,
enum onion_wire failcode)
{
struct db_stmt *stmt;
struct timeabs *resolved_time;
u64 id, updated_index;
if (state == FORWARD_SETTLED || state == FORWARD_FAILED) {
resolved_time = tal(tmpctx, struct timeabs);
*resolved_time = time_now();
} else {
resolved_time = NULL;
}
if (wallet_forwarded_payment_update(w, in, out, state, failcode, resolved_time, forward_style)) {
updated_index =
forward_index_update_status(w->ld,
state,
*channel_scid_or_local_alias(in->key.channel),
in->key.id,
in->msat,
out ? channel_scid_or_local_alias(out->key.channel) : NULL);
id = 0;
goto notify;
}
updated_index = 0;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO forwards ("
" rowid"
", in_htlc_id"
", out_htlc_id"
", in_channel_scid"
", out_channel_scid"
", in_msatoshi"
", out_msatoshi"
", state"
", received_time"
", resolved_time"
", failcode"
", forward_style"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
id = forward_index_created(w->ld,
state,
*channel_scid_or_local_alias(in->key.channel),
in->key.id,
in->msat,
out ? channel_scid_or_local_alias(out->key.channel) : NULL);
db_bind_u64(stmt, id);
db_bind_u64(stmt, in->key.id);
/* FORWARD_LOCAL_FAILED may occur before we get htlc_out */
if (!out || !scid_out) {
assert(failcode != 0);
assert(state == FORWARD_LOCAL_FAILED);
}
if (out)
db_bind_u64(stmt, out->key.id);
else
db_bind_null(stmt);
/* We use the LOCAL alias, since that's under our control, and
* we keep it stable, whereas the REMOTE alias is likely what
* the sender used to specify the channel, but that's under
* control of the remote end. */
assert(in->key.channel->scid != NULL || in->key.channel->alias[LOCAL]);
db_bind_short_channel_id(stmt, channel_scid_or_local_alias(in->key.channel));
if (scid_out)
db_bind_short_channel_id(stmt, scid_out);
else
db_bind_null(stmt);
db_bind_amount_msat(stmt, &in->msat);
if (out)
db_bind_amount_msat(stmt, &out->msat);
else
db_bind_null(stmt);
db_bind_int(stmt, wallet_forward_status_in_db(state));
db_bind_timeabs(stmt, in->received_time);
if (resolved_time != NULL)
db_bind_timeabs(stmt, *resolved_time);
else
db_bind_null(stmt);
if (failcode != 0) {
assert(state == FORWARD_FAILED || state == FORWARD_LOCAL_FAILED);
db_bind_int(stmt, (int)failcode);
} else {
db_bind_null(stmt);
}
/* This can happen for malformed onions, reload from db! */
if (forward_style == FORWARD_STYLE_UNKNOWN)
db_bind_null(stmt);
else
db_bind_int(stmt, forward_style_in_db(forward_style));
db_exec_prepared_v2(take(stmt));
notify:
notify_forward_event(w->ld, in, scid_out, out ? &out->msat : NULL,
state, failcode, resolved_time, forward_style,
id, updated_index);
}
struct amount_msat wallet_total_forward_fees(struct wallet *w)
{
struct db_stmt *stmt;
struct amount_msat total, deleted;
bool res;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)"
" FROM forwards "
"WHERE state = ?;"));
db_bind_int(stmt, wallet_forward_status_in_db(FORWARD_SETTLED));
db_query_prepared(stmt);
res = db_step(stmt);
assert(res);
total = db_col_amount_msat(stmt, "CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)");
tal_free(stmt);
deleted = amount_msat(db_get_intvar(w->db, "deleted_forward_fees", 0));
if (!amount_msat_add(&total, total, deleted))
db_fatal(w->db, "Adding forward fees %s + %s overflowed",
type_to_string(tmpctx, struct amount_msat, &total),
type_to_string(tmpctx, struct amount_msat, &deleted));
return total;
}
const struct forwarding *wallet_forwarded_payments_get(struct wallet *w,
const tal_t *ctx,
enum forward_status status,
const struct short_channel_id *chan_in,
const struct short_channel_id *chan_out,
const enum wait_index *listindex,
u64 liststart,
const u32 *listlimit)
{
struct forwarding *results = tal_arr(ctx, struct forwarding, 0);
size_t count = 0;
struct db_stmt *stmt;
// placeholder for any parameter, the value doesn't matter because it's discarded by sql
const int any = -1;
/* We don't support start/limits with this */
if (chan_in || chan_out) {
stmt = db_prepare_v2(
w->db,
SQL("SELECT"
" state"
", in_msatoshi"
", out_msatoshi"
", in_channel_scid"
", out_channel_scid"
", in_htlc_id"
", out_htlc_id"
", received_time"
", resolved_time"
", failcode "
", forward_style "
", rowid "
", updated_index "
"FROM forwards "
"WHERE (1 = ? OR state = ?) AND "
"(1 = ? OR in_channel_scid = ?) AND "
"(1 = ? OR out_channel_scid = ?)"));
if (status == FORWARD_ANY) {
// any status
db_bind_int(stmt, 1);
db_bind_int(stmt, any);
} else {
// specific forward status
db_bind_int(stmt, 0);
db_bind_int(stmt, status);
}
if (chan_in) {
// specific in_channel
db_bind_int(stmt, 0);
db_bind_short_channel_id(stmt, chan_in);
} else {
// any in_channel
db_bind_int(stmt, 1);
db_bind_int(stmt, any);
}
if (chan_out) {
// specific out_channel
db_bind_int(stmt, 0);
db_bind_short_channel_id(stmt, chan_out);
} else {
// any out_channel
db_bind_int(stmt, 1);
db_bind_int(stmt, any);
}
} else if (listindex && *listindex == WAIT_INDEX_UPDATED) {
stmt = db_prepare_v2(
w->db,
SQL("SELECT"
" state"
", in_msatoshi"
", out_msatoshi"
", in_channel_scid"
", out_channel_scid"
", in_htlc_id"
", out_htlc_id"
", received_time"
", resolved_time"
", failcode "
", forward_style "
", rowid "
", updated_index "
"FROM forwards "
" WHERE"
" (1 = ? OR state = ?)"
" AND"
" updated_index >= ?"
" ORDER BY updated_index"
" LIMIT ?;"));
if (status == FORWARD_ANY) {
// any status
db_bind_int(stmt, 1);
db_bind_int(stmt, any);
} else {
// specific forward status
db_bind_int(stmt, 0);
db_bind_int(stmt, status);
}
db_bind_u64(stmt, liststart);
if (listlimit)
db_bind_int(stmt, *listlimit);
else
db_bind_int(stmt, INT_MAX);
} else {
stmt = db_prepare_v2(
w->db,
SQL("SELECT"
" state"
", in_msatoshi"
", out_msatoshi"
", in_channel_scid"
", out_channel_scid"
", in_htlc_id"
", out_htlc_id"
", received_time"
", resolved_time"
", failcode "
", forward_style "
", rowid "
", updated_index "
"FROM forwards "
" WHERE"
" (1 = ? OR state = ?)"
" AND"
" rowid >= ?"
" ORDER BY rowid"
" LIMIT ?;"));
if (status == FORWARD_ANY) {
// any status
db_bind_int(stmt, 1);
db_bind_int(stmt, any);
} else {
// specific forward status
db_bind_int(stmt, 0);
db_bind_int(stmt, status);
}
db_bind_u64(stmt, liststart);
if (listlimit)
db_bind_int(stmt, *listlimit);
else
db_bind_int(stmt, INT_MAX);
}
db_query_prepared(stmt);
for (count=0; db_step(stmt); count++) {
tal_resize(&results, count+1);
struct forwarding *cur = &results[count];
cur->status = db_col_int(stmt, "state");
cur->msat_in = db_col_amount_msat(stmt, "in_msatoshi");
cur->created_index = db_col_u64(stmt, "rowid");
cur->updated_index = db_col_u64(stmt, "updated_index");
if (!db_col_is_null(stmt, "out_msatoshi")) {
cur->msat_out = db_col_amount_msat(stmt, "out_msatoshi");
if (!amount_msat_sub(&cur->fee, cur->msat_in, cur->msat_out)) {
log_broken(w->log, "Forwarded in %s less than out %s!",
type_to_string(tmpctx, struct amount_msat,
&cur->msat_in),
type_to_string(tmpctx, struct amount_msat,
&cur->msat_out));
cur->fee = AMOUNT_MSAT(0);
}
}
else {
assert(cur->status == FORWARD_LOCAL_FAILED);
cur->msat_out = AMOUNT_MSAT(0);
/* For this case, this forward_payment doesn't have out channel,
* so the fee should be set as 0.*/
cur->fee = AMOUNT_MSAT(0);
}
db_col_short_channel_id(stmt, "in_channel_scid", &cur->channel_in);
#ifdef COMPAT_V0121
/* This can happen due to migration! */
if (!db_col_is_null(stmt, "in_htlc_id"))
cur->htlc_id_in = db_col_u64(stmt, "in_htlc_id");
else
cur->htlc_id_in = HTLC_INVALID_ID;
#else
cur->htlc_id_in = db_col_u64(stmt, "in_htlc_id");
#endif
if (!db_col_is_null(stmt, "out_channel_scid")) {
db_col_short_channel_id(stmt, "out_channel_scid", &cur->channel_out);
} else {
assert(cur->status == FORWARD_LOCAL_FAILED);
cur->channel_out.u64 = 0;
}
if (!db_col_is_null(stmt, "out_htlc_id")) {
cur->htlc_id_out = tal(results, u64);
*cur->htlc_id_out = db_col_u64(stmt, "out_htlc_id");
} else
cur->htlc_id_out = NULL;
cur->received_time = db_col_timeabs(stmt, "received_time");
if (!db_col_is_null(stmt, "resolved_time")) {
cur->resolved_time = tal(ctx, struct timeabs);
*cur->resolved_time
= db_col_timeabs(stmt, "resolved_time");
} else {
cur->resolved_time = NULL;
}
if (!db_col_is_null(stmt, "failcode")) {
assert(cur->status == FORWARD_FAILED ||
cur->status == FORWARD_LOCAL_FAILED);
cur->failcode = db_col_int(stmt, "failcode");
} else {
cur->failcode = 0;
}
if (db_col_is_null(stmt, "forward_style")) {
cur->forward_style = FORWARD_STYLE_UNKNOWN;
} else {
cur->forward_style
= forward_style_in_db(db_col_int(stmt, "forward_style"));
}
}
tal_free(stmt);
return results;
}
bool wallet_forward_delete(struct wallet *w,
const struct short_channel_id *chan_in,
const u64 *htlc_id,
enum forward_status state)
{
struct db_stmt *stmt;
bool changed;
/* When deleting settled ones, we have to add to deleted_forward_fees! */
if (state == FORWARD_SETTLED) {
/* Of course, it might not be settled: don't add if they're wrong! */
if (htlc_id) {
stmt = db_prepare_v2(w->db, SQL("SELECT"
" CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)"
" FROM forwards "
" WHERE in_channel_scid = ?"
" AND in_htlc_id = ?"
" AND state = ?;"));
db_bind_short_channel_id(stmt, chan_in);
db_bind_u64(stmt, *htlc_id);
db_bind_int(stmt, wallet_forward_status_in_db(FORWARD_SETTLED));
} else {
stmt = db_prepare_v2(w->db, SQL("SELECT"
" CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)"
" FROM forwards "
" WHERE in_channel_scid = ?"
" AND in_htlc_id IS NULL"
" AND state = ?;"));
db_bind_short_channel_id(stmt, chan_in);
db_bind_int(stmt, wallet_forward_status_in_db(FORWARD_SETTLED));
}
db_query_prepared(stmt);
if (db_step(stmt)) {
struct amount_msat deleted;
deleted = db_col_amount_msat(stmt, "CAST(COALESCE(SUM(in_msatoshi - out_msatoshi), 0) AS BIGINT)");
deleted.millisatoshis += /* Raw: db access */
db_get_intvar(w->db, "deleted_forward_fees", 0);
db_set_intvar(w->db, "deleted_forward_fees",
deleted.millisatoshis); /* Raw: db access */
}
tal_free(stmt);
}
if (htlc_id) {
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM forwards"
" WHERE in_channel_scid = ?"
" AND in_htlc_id = ?"
" AND state = ?"));
db_bind_short_channel_id(stmt, chan_in);
db_bind_u64(stmt, *htlc_id);
db_bind_int(stmt, wallet_forward_status_in_db(state));
} else {
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM forwards"
" WHERE in_channel_scid = ?"
" AND in_htlc_id IS NULL"
" AND state = ?"));
db_bind_short_channel_id(stmt, chan_in);
db_bind_int(stmt, wallet_forward_status_in_db(state));
}
db_exec_prepared_v2(stmt);
changed = db_count_changes(stmt) != 0;
tal_free(stmt);
if (changed) {
/* FIXME: We don't set in->msat or out here, since that would
* need an extra lookup */
forward_index_deleted(w->ld,
state,
*chan_in,
htlc_id ? *htlc_id : HTLC_INVALID_ID,
NULL, NULL);
}
return changed;
}
struct wallet_transaction *wallet_transactions_get(struct wallet *w, const tal_t *ctx)
{
struct db_stmt *stmt;
wallet: fix crash on listtransactions. We removed the (experimental-only!) annotation output in 611795beee1bdecbde9e7b8c17db96d0934346be but we still loaded them from the db. Turns out that we were putting bogus annotations into the db, and accessing out of range when loading them. Consider the following db entry in transaction_annotations: ``` CREATE TABLE transaction_annotations ( txid BLOB, idx INTEGER, location INTEGER, type INTEGER, channel INTEGER REFERENCES channels(id), UNIQUE(txid, idx)); ... INSERT INTO transaction_annotations VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',18,1,129,53699); ``` Here is the corresponding entry in txs: ``` INSERT INTO transactions VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',710327,966,X'02000000000101f2add69112a1d557317826120e1f4ea3bc1cbe4674d720325695b26ecfe8355d120000000000000000013634000000000000160014dca21f104359bbb81e88ed7da985549f2cd0cbc30347304402201cdc854b76c4c7523e3ca09f38a81539d3b2f7fbd9a0de6ae10b7ceaa65ed9d402205a1770058cd1ef081c77c2fe957c07a334cb3a11bc0cc502834a29c59424fe010120589da1f809d955c7af150bf53123c27ffc0a741489b5291f6be811189863ec838576a9142f188d0d973c4ad1865a619d3748340b30746e088763ac672103b7bbcd592197ba6501e7176aabd3f908d94b126ae82ab1e7a4c58f5a789782e57c820120876475527c21029bcf62114eb36758fcb1aead7e67b6f707d32f34e67816894d5211ac9f2d6ce752ae67a9144483a115219ba65c63a3844be8445f739703bea988ac686800000000',NULL,NULL); ``` The annotation refers to output 18 of the tx, but it only has one output! However, decoding the tx shows that it spent output 18 of a previous tx, so that's probably where the `18` came from. Remove this logic: we can remove the remaining (clearly broken!) annotation adding code in another cleanup commit. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-05-27 06:29:00 +01:00
struct wallet_transaction *txs = tal_arr(ctx, struct wallet_transaction, 0);
stmt = db_prepare_v2(
w->db,
SQL("SELECT"
" t.id"
", t.rawtx"
", t.blockheight"
", t.txindex"
" FROM"
" transactions t LEFT JOIN"
wallet: fix crash on listtransactions. We removed the (experimental-only!) annotation output in 611795beee1bdecbde9e7b8c17db96d0934346be but we still loaded them from the db. Turns out that we were putting bogus annotations into the db, and accessing out of range when loading them. Consider the following db entry in transaction_annotations: ``` CREATE TABLE transaction_annotations ( txid BLOB, idx INTEGER, location INTEGER, type INTEGER, channel INTEGER REFERENCES channels(id), UNIQUE(txid, idx)); ... INSERT INTO transaction_annotations VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',18,1,129,53699); ``` Here is the corresponding entry in txs: ``` INSERT INTO transactions VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',710327,966,X'02000000000101f2add69112a1d557317826120e1f4ea3bc1cbe4674d720325695b26ecfe8355d120000000000000000013634000000000000160014dca21f104359bbb81e88ed7da985549f2cd0cbc30347304402201cdc854b76c4c7523e3ca09f38a81539d3b2f7fbd9a0de6ae10b7ceaa65ed9d402205a1770058cd1ef081c77c2fe957c07a334cb3a11bc0cc502834a29c59424fe010120589da1f809d955c7af150bf53123c27ffc0a741489b5291f6be811189863ec838576a9142f188d0d973c4ad1865a619d3748340b30746e088763ac672103b7bbcd592197ba6501e7176aabd3f908d94b126ae82ab1e7a4c58f5a789782e57c820120876475527c21029bcf62114eb36758fcb1aead7e67b6f707d32f34e67816894d5211ac9f2d6ce752ae67a9144483a115219ba65c63a3844be8445f739703bea988ac686800000000',NULL,NULL); ``` The annotation refers to output 18 of the tx, but it only has one output! However, decoding the tx shows that it spent output 18 of a previous tx, so that's probably where the `18` came from. Remove this logic: we can remove the remaining (clearly broken!) annotation adding code in another cleanup commit. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-05-27 06:29:00 +01:00
" channels c ON (t.channel_id = c.id) "
"ORDER BY t.blockheight, t.txindex ASC"));
db_query_prepared(stmt);
while (db_step(stmt)) {
wallet: fix crash on listtransactions. We removed the (experimental-only!) annotation output in 611795beee1bdecbde9e7b8c17db96d0934346be but we still loaded them from the db. Turns out that we were putting bogus annotations into the db, and accessing out of range when loading them. Consider the following db entry in transaction_annotations: ``` CREATE TABLE transaction_annotations ( txid BLOB, idx INTEGER, location INTEGER, type INTEGER, channel INTEGER REFERENCES channels(id), UNIQUE(txid, idx)); ... INSERT INTO transaction_annotations VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',18,1,129,53699); ``` Here is the corresponding entry in txs: ``` INSERT INTO transactions VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',710327,966,X'02000000000101f2add69112a1d557317826120e1f4ea3bc1cbe4674d720325695b26ecfe8355d120000000000000000013634000000000000160014dca21f104359bbb81e88ed7da985549f2cd0cbc30347304402201cdc854b76c4c7523e3ca09f38a81539d3b2f7fbd9a0de6ae10b7ceaa65ed9d402205a1770058cd1ef081c77c2fe957c07a334cb3a11bc0cc502834a29c59424fe010120589da1f809d955c7af150bf53123c27ffc0a741489b5291f6be811189863ec838576a9142f188d0d973c4ad1865a619d3748340b30746e088763ac672103b7bbcd592197ba6501e7176aabd3f908d94b126ae82ab1e7a4c58f5a789782e57c820120876475527c21029bcf62114eb36758fcb1aead7e67b6f707d32f34e67816894d5211ac9f2d6ce752ae67a9144483a115219ba65c63a3844be8445f739703bea988ac686800000000',NULL,NULL); ``` The annotation refers to output 18 of the tx, but it only has one output! However, decoding the tx shows that it spent output 18 of a previous tx, so that's probably where the `18` came from. Remove this logic: we can remove the remaining (clearly broken!) annotation adding code in another cleanup commit. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-05-27 06:29:00 +01:00
struct wallet_transaction *cur;
tal_resize(&txs, tal_count(txs) + 1);
cur = &txs[tal_count(txs) - 1];
db_col_txid(stmt, "t.id", &cur->id);
cur->tx = db_col_tx(txs, stmt, "t.rawtx");
cur->rawtx = db_col_arr(txs, stmt, "t.rawtx", u8);
if (!db_col_is_null(stmt, "t.blockheight")) {
cur->blockheight = db_col_int(stmt, "t.blockheight");
if (!db_col_is_null(stmt, "t.txindex")) {
cur->txindex = db_col_int(stmt, "t.txindex");
} else {
cur->txindex = 0;
}
} else {
wallet: fix crash on listtransactions. We removed the (experimental-only!) annotation output in 611795beee1bdecbde9e7b8c17db96d0934346be but we still loaded them from the db. Turns out that we were putting bogus annotations into the db, and accessing out of range when loading them. Consider the following db entry in transaction_annotations: ``` CREATE TABLE transaction_annotations ( txid BLOB, idx INTEGER, location INTEGER, type INTEGER, channel INTEGER REFERENCES channels(id), UNIQUE(txid, idx)); ... INSERT INTO transaction_annotations VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',18,1,129,53699); ``` Here is the corresponding entry in txs: ``` INSERT INTO transactions VALUES(X'19706f9af2875508a06c7db1754ef7ecb3da745ead005992e626441e4e83465f',710327,966,X'02000000000101f2add69112a1d557317826120e1f4ea3bc1cbe4674d720325695b26ecfe8355d120000000000000000013634000000000000160014dca21f104359bbb81e88ed7da985549f2cd0cbc30347304402201cdc854b76c4c7523e3ca09f38a81539d3b2f7fbd9a0de6ae10b7ceaa65ed9d402205a1770058cd1ef081c77c2fe957c07a334cb3a11bc0cc502834a29c59424fe010120589da1f809d955c7af150bf53123c27ffc0a741489b5291f6be811189863ec838576a9142f188d0d973c4ad1865a619d3748340b30746e088763ac672103b7bbcd592197ba6501e7176aabd3f908d94b126ae82ab1e7a4c58f5a789782e57c820120876475527c21029bcf62114eb36758fcb1aead7e67b6f707d32f34e67816894d5211ac9f2d6ce752ae67a9144483a115219ba65c63a3844be8445f739703bea988ac686800000000',NULL,NULL); ``` The annotation refers to output 18 of the tx, but it only has one output! However, decoding the tx shows that it spent output 18 of a previous tx, so that's probably where the `18` came from. Remove this logic: we can remove the remaining (clearly broken!) annotation adding code in another cleanup commit. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-05-27 06:29:00 +01:00
db_col_ignore(stmt, "t.txindex");
cur->blockheight = 0;
cur->txindex = 0;
}
}
tal_free(stmt);
return txs;
}
void wallet_penalty_base_add(struct wallet *w, u64 chan_id,
const struct penalty_base *pb)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO penalty_bases ("
" channel_id"
", commitnum"
", txid"
", outnum"
", amount"
") VALUES (?, ?, ?, ?, ?);"));
db_bind_u64(stmt, chan_id);
db_bind_u64(stmt, pb->commitment_num);
db_bind_txid(stmt, &pb->txid);
db_bind_int(stmt, pb->outnum);
db_bind_amount_sat(stmt, &pb->amount);
db_exec_prepared_v2(take(stmt));
}
struct penalty_base *wallet_penalty_base_load_for_channel(const tal_t *ctx,
struct wallet *w,
u64 chan_id)
{
struct db_stmt *stmt;
struct penalty_base *res = tal_arr(ctx, struct penalty_base, 0);
stmt = db_prepare_v2(
w->db,
SQL("SELECT commitnum, txid, outnum, amount "
"FROM penalty_bases "
"WHERE channel_id = ?"));
db_bind_u64(stmt, chan_id);
db_query_prepared(stmt);
while (db_step(stmt)) {
struct penalty_base pb;
pb.commitment_num = db_col_u64(stmt, "commitnum");
db_col_txid(stmt, "txid", &pb.txid);
pb.outnum = db_col_int(stmt, "outnum");
pb.amount = db_col_amount_sat(stmt, "amount");
tal_arr_expand(&res, pb);
}
tal_free(stmt);
return res;
}
void wallet_penalty_base_delete(struct wallet *w, u64 chan_id, u64 commitnum)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(
w->db,
SQL("DELETE FROM penalty_bases "
"WHERE channel_id = ? AND commitnum = ?"));
db_bind_u64(stmt, chan_id);
db_bind_u64(stmt, commitnum);
db_exec_prepared_v2(take(stmt));
}
bool wallet_offer_create(struct wallet *w,
const struct sha256 *offer_id,
const char *bolt12,
const struct json_escape *label,
enum offer_status status)
{
struct db_stmt *stmt;
assert(offer_status_active(status));
/* Test if already exists. */
stmt = db_prepare_v2(w->db, SQL("SELECT 1"
" FROM offers"
" WHERE offer_id = ?;"));
db_bind_sha256(stmt, offer_id);
db_query_prepared(stmt);
if (db_step(stmt)) {
db_col_ignore(stmt, "1");
tal_free(stmt);
return false;
}
tal_free(stmt);
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO offers ("
" offer_id"
", bolt12"
", label"
", status"
") VALUES (?, ?, ?, ?);"));
db_bind_sha256(stmt, offer_id);
db_bind_text(stmt, bolt12);
if (label)
db_bind_json_escape(stmt, label);
else
db_bind_null(stmt);
db_bind_int(stmt, offer_status_in_db(status));
db_exec_prepared_v2(take(stmt));
return true;
}
char *wallet_offer_find(const tal_t *ctx,
struct wallet *w,
const struct sha256 *offer_id,
const struct json_escape **label,
enum offer_status *status)
{
struct db_stmt *stmt;
char *bolt12;
stmt = db_prepare_v2(w->db, SQL("SELECT bolt12, label, status"
" FROM offers"
" WHERE offer_id = ?;"));
db_bind_sha256(stmt, offer_id);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
bolt12 = db_col_strdup(ctx, stmt, "bolt12");
if (label) {
if (db_col_is_null(stmt, "label"))
*label = NULL;
else
*label = db_col_json_escape(ctx, stmt, "label");
} else
db_col_ignore(stmt, "label");
if (status)
*status = offer_status_in_db(db_col_int(stmt, "status"));
else
db_col_ignore(stmt, "status");
tal_free(stmt);
return bolt12;
}
struct db_stmt *wallet_offer_id_first(struct wallet *w, struct sha256 *offer_id)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT offer_id FROM offers;"));
db_query_prepared(stmt);
return wallet_offer_id_next(w, stmt, offer_id);
}
struct db_stmt *wallet_offer_id_next(struct wallet *w,
struct db_stmt *stmt,
struct sha256 *offer_id)
{
if (!db_step(stmt))
return tal_free(stmt);
db_col_sha256(stmt, "offer_id", offer_id);
return stmt;
}
/* If we make an offer inactive, this also expires all invoices
* which we issued for it. */
static void offer_status_update(struct db *db,
const struct sha256 *offer_id,
enum offer_status oldstatus,
enum offer_status newstatus)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db, SQL("UPDATE offers"
" SET status=?"
" WHERE offer_id = ?;"));
db_bind_int(stmt, offer_status_in_db(newstatus));
db_bind_sha256(stmt, offer_id);
db_exec_prepared_v2(take(stmt));
if (!offer_status_active(oldstatus)
|| offer_status_active(newstatus))
return;
stmt = db_prepare_v2(db, SQL("UPDATE invoices"
" SET state=?"
" WHERE state=? AND local_offer_id = ?;"));
db_bind_int(stmt, invoice_status_in_db(EXPIRED));
db_bind_int(stmt, invoice_status_in_db(UNPAID));
db_bind_sha256(stmt, offer_id);
db_exec_prepared_v2(take(stmt));
}
enum offer_status wallet_offer_disable(struct wallet *w,
const struct sha256 *offer_id,
enum offer_status s)
{
enum offer_status newstatus;
assert(offer_status_active(s));
newstatus = offer_status_in_db(s & ~OFFER_STATUS_ACTIVE_F);
offer_status_update(w->db, offer_id, s, newstatus);
return newstatus;
}
void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id)
{
struct db_stmt *stmt;
enum offer_status status;
stmt = db_prepare_v2(db, SQL("SELECT status"
" FROM offers"
" WHERE offer_id = ?;"));
db_bind_sha256(stmt, offer_id);
db_query_prepared(stmt);
if (!db_step(stmt))
fatal("%s: unknown offer_id %s",
__func__,
type_to_string(tmpctx, struct sha256, offer_id));
status = offer_status_in_db(db_col_int(stmt, "status"));
tal_free(stmt);
if (!offer_status_active(status))
fatal("%s: offer_id %s not active: status %i",
__func__,
type_to_string(tmpctx, struct sha256, offer_id),
status);
if (!offer_status_used(status)) {
enum offer_status newstatus;
if (offer_status_single(status))
newstatus = OFFER_SINGLE_USE_USED;
else
newstatus = OFFER_MULTIPLE_USE_USED;
offer_status_update(db, offer_id, status, newstatus);
}
}
bool wallet_invoice_request_create(struct wallet *w,
const struct sha256 *invreq_id,
const char *bolt12,
const struct json_escape *label,
enum offer_status status)
{
struct db_stmt *stmt;
assert(offer_status_active(status));
/* Test if already exists. */
stmt = db_prepare_v2(w->db, SQL("SELECT 1"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, invreq_id);
db_query_prepared(stmt);
if (db_step(stmt)) {
db_col_ignore(stmt, "1");
tal_free(stmt);
return false;
}
tal_free(stmt);
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO invoicerequests ("
" invreq_id"
", bolt12"
", label"
", status"
") VALUES (?, ?, ?, ?);"));
db_bind_sha256(stmt, invreq_id);
db_bind_text(stmt, bolt12);
if (label)
db_bind_json_escape(stmt, label);
else
db_bind_null(stmt);
db_bind_int(stmt, offer_status_in_db(status));
db_exec_prepared_v2(take(stmt));
return true;
}
char *wallet_invoice_request_find(const tal_t *ctx,
struct wallet *w,
const struct sha256 *invreq_id,
const struct json_escape **label,
enum offer_status *status)
{
struct db_stmt *stmt;
char *bolt12;
stmt = db_prepare_v2(w->db, SQL("SELECT bolt12, label, status"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, invreq_id);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
bolt12 = db_col_strdup(ctx, stmt, "bolt12");
if (label) {
if (db_col_is_null(stmt, "label"))
*label = NULL;
else
*label = db_col_json_escape(ctx, stmt, "label");
} else
db_col_ignore(stmt, "label");
if (status)
*status = offer_status_in_db(db_col_int(stmt, "status"));
else
db_col_ignore(stmt, "status");
tal_free(stmt);
return bolt12;
}
struct db_stmt *wallet_invreq_id_first(struct wallet *w, struct sha256 *invreq_id)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db, SQL("SELECT invreq_id FROM invoicerequests;"));
db_query_prepared(stmt);
return wallet_invreq_id_next(w, stmt, invreq_id);
}
struct db_stmt *wallet_invreq_id_next(struct wallet *w,
struct db_stmt *stmt,
struct sha256 *invreq_id)
{
if (!db_step(stmt))
return tal_free(stmt);
db_col_sha256(stmt, "invreq_id", invreq_id);
return stmt;
}
/* If we make an invoice_request inactive */
static void invoice_request_status_update(struct db *db,
const struct sha256 *invreq_id,
enum offer_status oldstatus,
enum offer_status newstatus)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db, SQL("UPDATE invoicerequests"
" SET status=?"
" WHERE invreq_id = ?;"));
db_bind_int(stmt, offer_status_in_db(newstatus));
db_bind_sha256(stmt, invreq_id);
db_exec_prepared_v2(take(stmt));
}
enum offer_status wallet_invoice_request_disable(struct wallet *w,
const struct sha256 *invreq_id,
enum offer_status s)
{
enum offer_status newstatus;
assert(offer_status_active(s));
newstatus = offer_status_in_db(s & ~OFFER_STATUS_ACTIVE_F);
invoice_request_status_update(w->db, invreq_id, s, newstatus);
return newstatus;
}
void wallet_invoice_request_mark_used(struct db *db, const struct sha256 *invreq_id)
{
struct db_stmt *stmt;
enum offer_status status;
stmt = db_prepare_v2(db, SQL("SELECT status"
" FROM invoicerequests"
" WHERE invreq_id = ?;"));
db_bind_sha256(stmt, invreq_id);
db_query_prepared(stmt);
if (!db_step(stmt))
fatal("%s: unknown invreq_id %s",
__func__,
type_to_string(tmpctx, struct sha256, invreq_id));
status = offer_status_in_db(db_col_int(stmt, "status"));
tal_free(stmt);
if (!offer_status_active(status))
fatal("%s: invreq_id %s not active: status %i",
__func__,
type_to_string(tmpctx, struct sha256, invreq_id),
status);
if (!offer_status_used(status)) {
enum offer_status newstatus;
if (offer_status_single(status))
newstatus = OFFER_SINGLE_USE_USED;
else
newstatus = OFFER_MULTIPLE_USE_USED;
invoice_request_status_update(db, invreq_id, status, newstatus);
}
}
/* We join key parts with nuls for now. */
static void db_bind_datastore_key(struct db_stmt *stmt,
const char **key)
{
u8 *joined;
size_t len;
if (tal_count(key) == 1) {
db_bind_blob(stmt, (u8 *)key[0], strlen(key[0]));
return;
}
len = strlen(key[0]);
joined = (u8 *)tal_strdup(tmpctx, key[0]);
for (size_t i = 1; i < tal_count(key); i++) {
tal_resize(&joined, len + 1 + strlen(key[i]));
joined[len] = '\0';
memcpy(joined + len + 1, key[i], strlen(key[i]));
len += 1 + strlen(key[i]);
}
db_bind_blob(stmt, joined, len);
}
static const char **db_col_datastore_key(const tal_t *ctx,
struct db_stmt *stmt,
const char *colname)
{
char **key;
const u8 *joined = db_col_blob(stmt, colname);
size_t len = db_col_bytes(stmt, colname);
key = tal_arr(ctx, char *, 0);
do {
size_t partlen;
for (partlen = 0; partlen < len; partlen++) {
if (joined[partlen] == '\0') {
partlen++;
break;
}
}
tal_arr_expand(&key, tal_strndup(key, (char *)joined, partlen));
len -= partlen;
joined += partlen;
} while (len != 0);
return cast_const2(const char **, key);
}
void wallet_datastore_update(struct wallet *w, const char **key, const u8 *data)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("UPDATE datastore SET data=?, generation=generation+1 WHERE key=?;"));
db_bind_talarr(stmt, data);
db_bind_datastore_key(stmt, key);
db_exec_prepared_v2(take(stmt));
}
void wallet_datastore_create(struct wallet *w, const char **key, const u8 *data)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO datastore VALUES (?, ?, 0);"));
db_bind_datastore_key(stmt, key);
db_bind_talarr(stmt, data);
db_exec_prepared_v2(take(stmt));
}
static void db_datastore_remove(struct db *db, const char **key)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db, SQL("DELETE FROM datastore"
" WHERE key = ?"));
db_bind_datastore_key(stmt, key);
db_exec_prepared_v2(take(stmt));
}
void wallet_datastore_remove(struct wallet *w, const char **key)
{
db_datastore_remove(w->db, key);
}
/* Does k1 match k2 as far as k2 goes? */
bool datastore_key_startswith(const char **k1, const char **k2)
{
size_t k1len = tal_count(k1), k2len = tal_count(k2);
if (k2len > k1len)
return false;
for (size_t i = 0; i < k2len; i++) {
if (!streq(k1[i], k2[i]))
return false;
}
return true;
}
bool datastore_key_eq(const char **k1, const char **k2)
{
return tal_count(k1) == tal_count(k2)
&& datastore_key_startswith(k1, k2);
}
static u8 *db_datastore_get(const tal_t *ctx,
struct db *db,
const char **key,
u64 *generation)
{
struct db_stmt *stmt;
u8 *ret;
stmt = db_prepare_v2(db,
SQL("SELECT data, generation"
" FROM datastore"
" WHERE key = ?"));
db_bind_datastore_key(stmt, key);
db_query_prepared(stmt);
if (!db_step(stmt)) {
tal_free(stmt);
return NULL;
}
ret = db_col_arr(ctx, stmt, "data", u8);
if (generation)
*generation = db_col_u64(stmt, "generation");
else
db_col_ignore(stmt, "generation");
tal_free(stmt);
return ret;
}
u8 *wallet_datastore_get(const tal_t *ctx,
struct wallet *w,
const char **key,
u64 *generation)
{
return db_datastore_get(ctx, w->db, key, generation);
}
static struct db_stmt *db_datastore_next(const tal_t *ctx,
struct db_stmt *stmt,
const char **startkey,
const char ***key,
const u8 **data,
u64 *generation)
{
if (!db_step(stmt))
return tal_free(stmt);
*key = db_col_datastore_key(ctx, stmt, "key");
/* We select from startkey onwards, so once we're past it, stop */
if (startkey && !datastore_key_startswith(*key, startkey)) {
db_col_ignore(stmt, "data");
db_col_ignore(stmt, "generation");
return tal_free(stmt);
}
if (data)
*data = db_col_arr(ctx, stmt, "data", u8);
else
db_col_ignore(stmt, "data");
if (generation)
*generation = db_col_u64(stmt, "generation");
else
db_col_ignore(stmt, "generation");
return stmt;
}
static struct db_stmt *db_datastore_first(const tal_t *ctx,
struct db *db,
const char **startkey,
const char ***key,
const u8 **data,
u64 *generation)
{
struct db_stmt *stmt;
if (startkey) {
stmt = db_prepare_v2(db,
SQL("SELECT key, data, generation"
" FROM datastore"
" WHERE key >= ?"
" ORDER BY key;"));
db_bind_datastore_key(stmt, startkey);
} else {
stmt = db_prepare_v2(db,
SQL("SELECT key, data, generation"
" FROM datastore"
" ORDER BY key;"));
}
db_query_prepared(stmt);
return db_datastore_next(ctx, stmt, startkey, key, data, generation);
}
struct db_stmt *wallet_datastore_first(const tal_t *ctx,
struct wallet *w,
const char **startkey,
const char ***key,
const u8 **data,
u64 *generation)
{
return db_datastore_first(ctx, w->db, startkey, key, data, generation);
}
struct db_stmt *wallet_datastore_next(const tal_t *ctx,
const char **startkey,
struct db_stmt *stmt,
const char ***key,
const u8 **data,
u64 *generation)
{
return db_datastore_next(ctx, stmt, startkey, key, data, generation);
}
/* We use a different query form if we only care about a single channel. */
struct wallet_htlc_iter {
struct db_stmt *stmt;
/* Non-zero if they specified it */
struct short_channel_id scid;
};
struct wallet_htlc_iter *wallet_htlcs_first(const tal_t *ctx,
struct wallet *w,
const struct channel *chan,
struct short_channel_id *scid,
u64 *htlc_id,
int *cltv_expiry,
enum side *owner,
struct amount_msat *msat,
struct sha256 *payment_hash,
enum htlc_state *hstate)
{
struct wallet_htlc_iter *i = tal(ctx, struct wallet_htlc_iter);
if (chan) {
i->scid = *channel_scid_or_local_alias(chan);
assert(i->scid.u64 != 0);
assert(chan->dbid != 0);
i->stmt = db_prepare_v2(w->db,
SQL("SELECT h.channel_htlc_id"
", h.cltv_expiry"
", h.direction"
", h.msatoshi"
", h.payment_hash"
", h.hstate"
" FROM channel_htlcs h"
" WHERE channel_id = ?"
" ORDER BY id ASC"));
db_bind_u64(i->stmt, chan->dbid);
} else {
i->scid.u64 = 0;
i->stmt = db_prepare_v2(w->db,
SQL("SELECT channels.scid"
", channels.alias_local"
", h.channel_htlc_id"
", h.cltv_expiry"
", h.direction"
", h.msatoshi"
", h.payment_hash"
", h.hstate"
" FROM channel_htlcs h"
" JOIN channels ON channels.id = h.channel_id"
" ORDER BY h.id ASC"));
}
/* FIXME: db_prepare should take ctx! */
tal_steal(i, i->stmt);
db_query_prepared(i->stmt);
return wallet_htlcs_next(w, i,
scid, htlc_id, cltv_expiry, owner, msat,
payment_hash, hstate);
}
struct wallet_htlc_iter *wallet_htlcs_next(struct wallet *w,
struct wallet_htlc_iter *iter,
struct short_channel_id *scid,
u64 *htlc_id,
int *cltv_expiry,
enum side *owner,
struct amount_msat *msat,
struct sha256 *payment_hash,
enum htlc_state *hstate)
{
if (!db_step(iter->stmt))
return tal_free(iter);
if (iter->scid.u64 != 0)
*scid = iter->scid;
else {
if (db_col_is_null(iter->stmt, "channels.scid"))
db_col_short_channel_id(iter->stmt, "channels.alias_local", scid);
else {
db_col_short_channel_id(iter->stmt, "channels.scid", scid);
db_col_ignore(iter->stmt, "channels.alias_local");
}
}
*htlc_id = db_col_u64(iter->stmt, "h.channel_htlc_id");
if (db_col_int(iter->stmt, "h.direction") == DIRECTION_INCOMING)
*owner = REMOTE;
else
*owner = LOCAL;
*msat = db_col_amount_msat(iter->stmt, "h.msatoshi");
db_col_sha256(iter->stmt, "h.payment_hash", payment_hash);
*cltv_expiry = db_col_int(iter->stmt, "h.cltv_expiry");
*hstate = db_col_int(iter->stmt, "h.hstate");
return iter;
}
u64 wallet_get_rune_next_unique_id(const tal_t *ctx, struct wallet *wallet)
{
struct db_stmt *stmt;
u64 next_unique_id;
stmt = db_prepare_v2(wallet->db, SQL("SELECT (COALESCE(MAX(id), -1) + 1) FROM runes"));
db_query_prepared(stmt);
db_step(stmt);
next_unique_id = db_col_u64(stmt, "(COALESCE(MAX(id), -1) + 1)");
tal_free(stmt);
return next_unique_id;
}
struct rune_blacklist *wallet_get_runes_blacklist(const tal_t *ctx, struct wallet *wallet)
{
struct db_stmt *stmt;
struct rune_blacklist *blist = tal_arr(ctx, struct rune_blacklist, 0);
stmt = db_prepare_v2(wallet->db, SQL("SELECT start_index, end_index FROM runes_blacklist ORDER BY start_index ASC"));
db_query_prepared(stmt);
while (db_step(stmt)) {
struct rune_blacklist b;
b.start = db_col_u64(stmt, "start_index");
b.end = db_col_u64(stmt, "end_index");
tal_arr_expand(&blist, b);
}
tal_free(stmt);
return blist;
}
static struct timeabs db_col_time_from_nsec(struct db_stmt *stmt, const char *colname)
{
struct timerel t;
struct timeabs tabs;
if (db_col_is_null(stmt, colname))
t = time_from_nsec(0);
else
t = time_from_nsec(db_col_u64(stmt, colname));
tabs.ts = t.ts;
return tabs;
}
const char *wallet_get_rune(const tal_t *ctx, struct wallet *wallet, u64 unique_id, struct timeabs *last_used)
{
struct db_stmt *stmt;
const char *runestr;
stmt = db_prepare_v2(wallet->db, SQL("SELECT rune, last_used_nsec FROM runes WHERE id = ?"));
db_bind_u64(stmt, unique_id);
db_query_prepared(stmt);
if (db_step(stmt)) {
runestr = db_col_strdup(ctx, stmt, "rune");
*last_used = db_col_time_from_nsec(stmt, "last_used_nsec");
} else {
runestr = NULL;
}
tal_free(stmt);
return runestr;
}
/* Migration code needs db, and db does not have last_used_nsec yet */
static const char **db_get_runes(const tal_t *ctx, struct db *db)
{
struct db_stmt *stmt;
const char **strs = tal_arr(ctx, const char *, 0);
stmt = db_prepare_v2(db, SQL("SELECT rune FROM runes"));
db_query_prepared(stmt);
while (db_step(stmt)) {
const char *str = db_col_strdup(strs, stmt, "rune");
tal_arr_expand(&strs, str);
}
tal_free(stmt);
return strs;
}
/* Wallet has last_used_nsec by now */
const char **wallet_get_runes(const tal_t *ctx, struct wallet *wallet, struct timeabs **last_used)
{
struct db_stmt *stmt;
const char **strs = tal_arr(ctx, const char *, 0);
*last_used = tal_arr(ctx, struct timeabs, 0);
stmt = db_prepare_v2(wallet->db, SQL("SELECT rune, last_used_nsec FROM runes"));
db_query_prepared(stmt);
while (db_step(stmt)) {
const char *str = db_col_strdup(strs, stmt, "rune");
tal_arr_expand(&strs, str);
tal_arr_expand(last_used, db_col_time_from_nsec(stmt, "last_used_nsec"));
}
tal_free(stmt);
return strs;
}
static void db_rune_insert(struct db *db,
const struct rune *rune)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db,
SQL("INSERT INTO runes (id, rune) VALUES (?, ?);"));
db_bind_u64(stmt, atol(rune->unique_id));
db_bind_text(stmt, rune_to_base64(tmpctx, rune));
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
void wallet_rune_insert(struct wallet *wallet, const struct rune *rune)
{
db_rune_insert(wallet->db, rune);
}
void wallet_rune_update_last_used(struct wallet *wallet, const struct rune *rune, struct timeabs last_used)
{
struct db_stmt *stmt;
struct timerel t;
t.ts = last_used.ts;
stmt = db_prepare_v2(wallet->db,
SQL("UPDATE runes SET last_used_nsec = ? WHERE id = ?;"));
db_bind_u64(stmt, time_to_nsec(t));
db_bind_u64(stmt, rune_unique_id(rune));
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
static void db_insert_blacklist(struct db *db,
const struct rune_blacklist *entry)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(db,
SQL("INSERT INTO runes_blacklist VALUES (?,?)"));
db_bind_u64(stmt, entry->start);
db_bind_u64(stmt, entry->end);
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
void wallet_insert_blacklist(struct wallet *wallet, const struct rune_blacklist *entry)
{
db_insert_blacklist(wallet->db, entry);
}
void wallet_delete_blacklist(struct wallet *wallet, const struct rune_blacklist *entry)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(wallet->db,
SQL("DELETE FROM runes_blacklist WHERE start_index = ? AND end_index = ?"));
db_bind_u64(stmt, entry->start);
db_bind_u64(stmt, entry->end);
db_exec_prepared_v2(stmt);
if (db_count_changes(stmt) != 1) {
db_fatal(wallet->db, "Failed to delete from runes_blacklist");
}
tal_free(stmt);
}
void migrate_datastore_commando_runes(struct lightningd *ld, struct db *db)
{
struct db_stmt *stmt;
const char **startkey, **k;
const u8 *data;
size_t max;
/* datastore routines expect a tal_arr */
startkey = tal_arr(tmpctx, const char *, 2);
startkey[0] = "commando";
startkey[1] = "runes";
for (stmt = db_datastore_first(tmpctx, db, startkey, &k, &data, NULL);
stmt;
stmt = db_datastore_next(tmpctx, stmt, startkey, &k, &data, NULL)) {
const char *err, *str;
struct rune *r;
str = db_col_strdup(tmpctx, stmt, "data");
r = rune_from_base64(tmpctx, str);
if (!r)
db_fatal(db, "Invalid commando rune %s", str);
err = rune_is_ours(ld, r);
if (err) {
log_unusual(ld->log,
"Warning: removing commando"
" rune %s (uid %s): %s",
str, r->unique_id, err);
} else {
log_debug(ld->log, "Transferring commando rune to db: %s",
str);
db_rune_insert(db, r);
}
db_datastore_remove(db, k);
}
/* Now convert blacklist */
startkey[0] = "commando";
startkey[1] = "blacklist";
data = db_datastore_get(tmpctx, db, startkey, NULL);
max = tal_bytelen(data);
while (max) {
struct rune_blacklist b;
b.start = fromwire_u64(&data, &max);
b.end = fromwire_u64(&data, &max);
if (!data)
db_fatal(db, "Invalid commando blacklist?");
log_debug(ld->log, "Transferring commando blacklist to db: %"PRIu64"-%"PRIu64,
b.start, b.end);
db_insert_blacklist(db, &b);
}
db_datastore_remove(db, startkey);
/* Might as well clean up "rune_counter" while we're here, so
* commando datastore is completely clean. */
startkey[0] = "commando";
startkey[1] = "rune_counter";
db_datastore_remove(db, startkey);
}
void migrate_runes_idfix(struct lightningd *ld, struct db *db)
{
/* ID fields were wrong. Pull them all out and put them back */
const char **runes = db_get_runes(tmpctx, db);
struct db_stmt *stmt;
stmt = db_prepare_v2(db, SQL("DELETE FROM runes;"));
db_exec_prepared_v2(stmt);
tal_free(stmt);
for (size_t i = 0; i < tal_count(runes); i++) {
struct rune *r;
r = rune_from_base64(tmpctx, runes[i]);
if (!r)
db_fatal(db, "Invalid databse rune %s", runes[i]);
db_rune_insert(db, r);
}
}
void wallet_set_local_anchor(struct wallet *w,
u64 channel_id,
const struct local_anchor_info *anchor,
u64 remote_index)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("INSERT INTO local_anchors VALUES (?,?,?,?,?,?)"));
db_bind_u64(stmt, channel_id);
db_bind_u64(stmt, remote_index);
db_bind_txid(stmt, &anchor->anchor_point.txid);
db_bind_int(stmt, anchor->anchor_point.n);
db_bind_amount_sat(stmt, &anchor->commitment_fee);
db_bind_int(stmt, anchor->commitment_weight);
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
void wallet_remove_local_anchors(struct wallet *w,
u64 channel_id,
u64 old_remote_index)
{
struct db_stmt *stmt;
stmt = db_prepare_v2(w->db,
SQL("DELETE FROM local_anchors "
"WHERE channel_id = ? and commitment_index <= ?;"));
db_bind_u64(stmt, channel_id);
db_bind_u64(stmt, old_remote_index);
db_exec_prepared_v2(stmt);
tal_free(stmt);
}
struct local_anchor_info *wallet_get_local_anchors(const tal_t *ctx,
struct wallet *w,
u64 channel_id)
{
struct db_stmt *stmt;
struct local_anchor_info *anchors;
stmt = db_prepare_v2(w->db, SQL("SELECT"
" commitment_txid "
", commitment_anchor_outnum "
", commitment_fee "
", commitment_weight "
"FROM local_anchors"
" WHERE channel_id = ?;"));
db_bind_u64(stmt, channel_id);
db_query_prepared(stmt);
anchors = tal_arr(ctx, struct local_anchor_info, 0);
while (db_step(stmt)) {
struct local_anchor_info a;
a.commitment_fee = db_col_amount_sat(stmt, "commitment_fee");
a.commitment_weight = db_col_int(stmt, "commitment_weight");
db_col_txid(stmt, "commitment_txid", &a.anchor_point.txid);
a.anchor_point.n = db_col_int(stmt, "commitment_anchor_outnum");
tal_arr_expand(&anchors, a);
}
tal_free(stmt);
return anchors;
}