#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct chain_event *stmt2chain_event(const tal_t *ctx, const struct bkpr *bkpr, struct db_stmt *stmt) { struct chain_event *e = tal(ctx, struct chain_event); e->db_id = db_col_u64(stmt, "e.id"); e->acct_name = db_col_strdup(e, stmt, "e.account_name"); if (!db_col_is_null(stmt, "e.origin")) e->origin_acct = db_col_strdup(e, stmt, "e.origin"); else e->origin_acct = NULL; e->tag = db_col_strdup(e, stmt, "e.tag"); e->credit = db_col_amount_msat(stmt, "e.credit"); e->debit = db_col_amount_msat(stmt, "e.debit"); e->output_value = db_col_amount_msat(stmt, "e.output_value"); e->timestamp = db_col_u64(stmt, "e.timestamp"); e->blockheight = db_col_int(stmt, "e.blockheight"); db_col_txid(stmt, "e.utxo_txid", &e->outpoint.txid); e->outpoint.n = db_col_int(stmt, "e.outnum"); if (e->blockheight == 0) e->blockheight = find_blockheight(bkpr, &e->outpoint.txid); if (!db_col_is_null(stmt, "e.payment_id")) { e->payment_id = tal(e, struct sha256); db_col_sha256(stmt, "e.payment_id", e->payment_id); } else e->payment_id = NULL; if (!db_col_is_null(stmt, "e.spending_txid")) { e->spending_txid = tal(e, struct bitcoin_txid); db_col_txid(stmt, "e.spending_txid", e->spending_txid); } else e->spending_txid = NULL; e->stealable = db_col_int(stmt, "e.stealable") == 1; e->splice_close = db_col_int(stmt, "e.spliced") == 1; return e; } static struct chain_event **find_chain_events(const tal_t *ctx, const struct bkpr *bkpr, struct db_stmt *stmt TAKES) { struct chain_event **results; db_query_prepared(stmt); if (stmt->error) db_fatal(stmt->db, "find_chain_events err: %s", stmt->error); results = tal_arr(ctx, struct chain_event *, 0); while (db_step(stmt)) { struct chain_event *e = stmt2chain_event(results, bkpr, stmt); tal_arr_expand(&results, e); } if (taken(stmt)) tal_free(stmt); return results; } static struct channel_event *stmt2channel_event(const tal_t *ctx, struct db_stmt *stmt) { struct channel_event *e = tal(ctx, struct channel_event); e->db_id = db_col_u64(stmt, "e.id"); e->acct_name = db_col_strdup(e, stmt, "e.account_name"); e->tag = db_col_strdup(e, stmt, "e.tag"); e->credit = db_col_amount_msat(stmt, "e.credit"); e->debit = db_col_amount_msat(stmt, "e.debit"); e->fees = db_col_amount_msat(stmt, "e.fees"); if (!db_col_is_null(stmt, "e.payment_id")) { e->payment_id = tal(e, struct sha256); db_col_sha256(stmt, "e.payment_id", e->payment_id); } else e->payment_id = NULL; e->part_id = db_col_int(stmt, "e.part_id"); e->timestamp = db_col_u64(stmt, "e.timestamp"); return e; } static struct channel_event **find_channel_events(const tal_t *ctx, struct db_stmt *stmt TAKES) { struct channel_event **results; db_query_prepared(stmt); if (stmt->error) db_fatal(stmt->db, "find_channel_events err: %s", stmt->error); results = tal_arr(ctx, struct channel_event *, 0); while (db_step(stmt)) { struct channel_event *e = stmt2channel_event(results, stmt); tal_arr_expand(&results, e); } if (taken(stmt)) tal_free(stmt); return results; } struct chain_event **list_chain_events_timebox(const tal_t *ctx, const struct bkpr *bkpr, u64 start_time, u64 end_time) { struct db_stmt *stmt; stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE e.timestamp > ?" " AND e.timestamp <= ?" " ORDER BY e.timestamp, e.id;")); db_bind_u64(stmt, start_time); db_bind_u64(stmt, end_time); return find_chain_events(ctx, bkpr, take(stmt)); } struct chain_event **list_chain_events(const tal_t *ctx, const struct bkpr *bkpr) { return list_chain_events_timebox(ctx, bkpr, 0, SQLITE_MAX_UINT); } struct chain_event **account_get_chain_events(const tal_t *ctx, const struct bkpr *bkpr, struct account *acct) { struct db_stmt *stmt; stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE e.account_name = ?" " ORDER BY e.timestamp, e.id")); db_bind_text(stmt, acct->name); return find_chain_events(ctx, bkpr, take(stmt)); } static struct chain_event **find_txos_for_tx(const tal_t *ctx, const struct bkpr *bkpr, struct bitcoin_txid *txid) { struct db_stmt *stmt; stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE e.utxo_txid = ?" " ORDER BY " " e.utxo_txid" ", e.outnum" ", e.spending_txid NULLS FIRST" ", e.blockheight")); db_bind_txid(stmt, txid); return find_chain_events(ctx, bkpr, take(stmt)); } static struct txo_pair *new_txo_pair(const tal_t *ctx) { struct txo_pair *pr = tal(ctx, struct txo_pair); pr->txo = NULL; pr->spend = NULL; return pr; } static struct txo_set *find_txo_set(const tal_t *ctx, const struct bkpr *bkpr, struct bitcoin_txid *txid, const char *acct_name, bool *is_complete) { struct txo_pair *pr; struct chain_event **evs; struct txo_set *txos = tal(ctx, struct txo_set); /* In some special cases (the opening tx), we only * want the outputs that pertain to a given account, * most other times we want all utxos, regardless of account */ evs = find_txos_for_tx(ctx, bkpr, txid); txos->pairs = tal_arr(txos, struct txo_pair *, 0); txos->txid = tal_dup(txos, struct bitcoin_txid, txid); pr = NULL; /* If there's nothing for this txid, we're missing data */ if (is_complete) *is_complete = tal_count(evs) > 0; for (size_t i = 0; i < tal_count(evs); i++) { struct chain_event *ev = evs[i]; if (acct_name && !streq(ev->acct_name, acct_name)) continue; if (ev->spending_txid) { if (!pr) { /* We're missing data!! */ pr = new_txo_pair(txos->pairs); if (is_complete) *is_complete = false; } else { assert(pr->txo); /* Make sure it's the same txo */ assert(bitcoin_outpoint_eq(&pr->txo->outpoint, &ev->outpoint)); } pr->spend = tal_steal(pr, ev); tal_arr_expand(&txos->pairs, pr); pr = NULL; } else { /* We might not have a spend event * for everything */ if (pr) { /* Disappear "channel_proposed" events */ if (streq(pr->txo->tag, mvt_tag_str(MVT_CHANNEL_PROPOSED))) pr = tal_free(pr); else tal_arr_expand(&txos->pairs, pr); } pr = new_txo_pair(txos->pairs); pr->txo = tal_steal(pr, ev); } } /* Might have a single entry 'pr' left over */ if (pr) tal_arr_expand(&txos->pairs, pr); return txos; } static bool txid_in_list(struct bitcoin_txid **list, struct bitcoin_txid *txid) { for (size_t i = 0; i < tal_count(list); i++) { if (bitcoin_txid_eq(list[i], txid)) return true; } return false; } bool find_txo_chain(const tal_t *ctx, const struct bkpr *bkpr, const struct account *acct, struct txo_set ***sets) { struct bitcoin_txid **txids; struct chain_event *open_ev; bool is_complete = true; const char *start_acct_name; assert(acct->open_event_db_id); open_ev = find_chain_event_by_id(ctx, bkpr, *acct->open_event_db_id); if (sets) *sets = tal_arr(ctx, struct txo_set *, 0); txids = tal_arr(ctx, struct bitcoin_txid *, 0); tal_arr_expand(&txids, &open_ev->outpoint.txid); /* We only want to filter by the account for the very * first utxo that we get the tree for, so we * start w/ this acct id... */ start_acct_name = open_ev->acct_name; for (size_t i = 0; i < tal_count(txids); i++) { struct txo_set *set; bool set_complete; set = find_txo_set(ctx, bkpr, txids[i], start_acct_name, &set_complete); /* After first use, we free the acct dbid ptr, * which will pass in NULL and not filter by * account for any subsequent txo_set hunt */ start_acct_name = NULL; is_complete &= set_complete; for (size_t j = 0; j < tal_count(set->pairs); j++) { struct txo_pair *pr = set->pairs[j]; /* Has this been resolved? */ if ((pr->txo && is_channel_account(pr->txo->acct_name)) && !pr->spend) is_complete = false; /* wallet accts and zero-fee-htlc anchors * might overlap txids */ if (pr->spend && pr->spend->spending_txid /* 'to_miner' outputs are skipped */ && !streq(pr->spend->tag, "to_miner") && !txid_in_list(txids, pr->spend->spending_txid) /* We dont trace utxos for non related accts */ && (streq(pr->spend->acct_name, acct->name) /* Unless it's stealable, in which case * we track the resolution of the htlc tx */ || pr->spend->stealable)) tal_arr_expand(&txids, pr->spend->spending_txid); } if (sets) tal_arr_expand(sets, set); } return is_complete; } const char *find_close_account_name(const tal_t *ctx, struct db *db, const struct bitcoin_txid *txid) { struct db_stmt *stmt; char *acct_name; stmt = db_prepare_v2(db, SQL("SELECT" " e.account_name" " FROM chain_events e" " WHERE " " e.tag = ?" " AND e.spending_txid = ?" /* ignore splicing 'close' events */ " AND e.spliced = 0 ")); db_bind_text(stmt, mvt_tag_str(MVT_CHANNEL_CLOSE)); db_bind_txid(stmt, txid); db_query_prepared(stmt); if (db_step(stmt)) { acct_name = db_col_strdup(ctx, stmt, "e.account_name"); } else acct_name = NULL; tal_free(stmt); return acct_name; } u64 account_onchain_closeheight(const struct bkpr *bkpr, const struct account *acct) { const u8 *ctx = tal(NULL, u8); struct txo_set **sets; struct chain_event *close_ev; struct db_stmt *stmt; u64 height; assert(acct->closed_count > 0); close_ev = find_chain_event_by_id(ctx, bkpr, *acct->closed_event_db_id); if (find_txo_chain(ctx, bkpr, acct, &sets)) { /* Ok now we find the max block height of the * spending chain_events for this channel */ bool ok; /* Have we accounted for all the outputs */ ok = false; for (size_t i = 0; i < tal_count(sets); i++) { if (bitcoin_txid_eq(sets[i]->txid, close_ev->spending_txid)) { ok = tal_count(sets[i]->pairs) == acct->closed_count; break; } } if (!ok) { tal_free(ctx); return 0; } stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " blockheight" " FROM chain_events" " WHERE account_name = ?" " AND spending_txid IS NOT NULL" " ORDER BY blockheight DESC" " LIMIT 1")); db_bind_text(stmt, acct->name); db_query_prepared(stmt); ok = db_step(stmt); assert(ok); height = db_col_int(stmt, "blockheight"); tal_free(stmt); } else { height = 0; } tal_free(ctx); return height; } struct chain_event *find_chain_event_by_id(const tal_t *ctx, const struct bkpr *bkpr, u64 event_db_id) { struct db_stmt *stmt; struct chain_event *e; stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.id = ?")); db_bind_u64(stmt, event_db_id); db_query_prepared(stmt); if (db_step(stmt)) e = stmt2chain_event(ctx, bkpr, stmt); else e = NULL; tal_free(stmt); return e; } struct chain_event **get_chain_events_by_outpoint(const tal_t *ctx, const struct bkpr *bkpr, const struct bitcoin_outpoint *outpoint, bool credits_only) { struct db_stmt *stmt; if (credits_only) stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.utxo_txid = ?" " AND e.outnum = ?" " AND credit > 0")); else stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.utxo_txid = ?" " AND e.outnum = ?")); db_bind_txid(stmt, &outpoint->txid); db_bind_int(stmt, outpoint->n); return find_chain_events(ctx, bkpr, take(stmt)); } struct chain_event **get_chain_events_by_id(const tal_t *ctx, const struct bkpr *bkpr, const struct sha256 *id) { struct db_stmt *stmt; stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.payment_id = ?")); db_bind_sha256(stmt, id); return find_chain_events(ctx, bkpr, take(stmt)); } static struct chain_event *find_chain_event(const tal_t *ctx, struct bkpr *bkpr, const struct account *acct, const struct bitcoin_outpoint *outpoint, const struct bitcoin_txid *spending_txid, const char *tag) { struct db_stmt *stmt; struct chain_event *e; if (spending_txid) { stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.spending_txid = ?" " AND e.account_name = ?" " AND e.utxo_txid = ?" " AND e.outnum = ?")); db_bind_txid(stmt, spending_txid); } else { stmt = db_prepare_v2(bkpr->db, SQL("SELECT" " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE " " e.tag = ?" " AND e.account_name = ?" " AND e.utxo_txid = ?" " AND e.outnum = ?" " AND e.spending_txid IS NULL")); db_bind_text(stmt, tag); } db_bind_text(stmt, acct->name); db_bind_txid(stmt, &outpoint->txid); db_bind_int(stmt, outpoint->n); db_query_prepared(stmt); if (db_step(stmt)) e = stmt2chain_event(ctx, bkpr, stmt); else e = NULL; tal_free(stmt); return e; } bool account_get_credit_debit(struct plugin *plugin, struct db *db, const char *acct_name, struct amount_msat *credit, struct amount_msat *debit) { struct db_stmt *stmt; bool exists; /* Get sum from chain_events */ stmt = db_prepare_v2(db, SQL("SELECT" " CAST(SUM(ce.credit) AS BIGINT) as credit" ", CAST(SUM(ce.debit) AS BIGINT) as debit" " FROM chain_events ce" " WHERE ce.account_name = ?")); db_bind_text(stmt, acct_name); db_query_prepared(stmt); db_step(stmt); if (db_col_is_null(stmt, "credit")) { db_col_ignore(stmt, "debit"); *credit = *debit = AMOUNT_MSAT(0); exists = false; } else { *credit = db_col_amount_msat(stmt, "credit"); *debit = db_col_amount_msat(stmt, "debit"); exists = true; } tal_free(stmt); /* Get sum from channel_events */ stmt = db_prepare_v2(db, SQL("SELECT" " CAST(SUM(ce.credit) AS BIGINT) as credit" ", CAST(SUM(ce.debit) AS BIGINT) as debit" " FROM channel_events ce" " WHERE ce.account_name = ?")); db_bind_text(stmt, acct_name); db_query_prepared(stmt); db_step(stmt); if (db_col_is_null(stmt, "credit")) { db_col_ignore(stmt, "debit"); } else { if (!amount_msat_accumulate(credit, db_col_amount_msat(stmt, "credit"))) { plugin_err(plugin, "db overflow: chain credit %s, adding channel credit %s", fmt_amount_msat(tmpctx, *credit), fmt_amount_msat(tmpctx, db_col_amount_msat(stmt, "credit"))); } if (!amount_msat_accumulate(debit, db_col_amount_msat(stmt, "debit"))) { plugin_err(plugin, "db overflow: chain debit %s, adding channel debit %s", fmt_amount_msat(tmpctx, *debit), fmt_amount_msat(tmpctx, db_col_amount_msat(stmt, "debit"))); } exists = true; } tal_free(stmt); return exists; } struct channel_event **list_channel_events_timebox(const tal_t *ctx, struct db *db, u64 start_time, u64 end_time) { struct db_stmt *stmt; struct channel_event **results; stmt = db_prepare_v2(db, SQL("SELECT" " e.id" ", e.account_name" ", e.tag" ", e.credit" ", e.debit" ", e.fees" ", e.payment_id" ", e.part_id" ", e.timestamp" " FROM channel_events e" " WHERE e.timestamp > ?" " AND e.timestamp <= ?" " ORDER BY e.timestamp, e.id;")); db_bind_u64(stmt, start_time); db_bind_u64(stmt, end_time); db_query_prepared(stmt); results = tal_arr(ctx, struct channel_event *, 0); while (db_step(stmt)) { struct channel_event *e = stmt2channel_event(results, stmt); tal_arr_expand(&results, e); } tal_free(stmt); return results; } struct channel_event **list_channel_events(const tal_t *ctx, struct db *db) { return list_channel_events_timebox(ctx, db, 0, SQLITE_MAX_UINT); } struct channel_event **account_get_channel_events(const tal_t *ctx, struct db *db, struct account *acct) { struct db_stmt *stmt; stmt = db_prepare_v2(db, SQL("SELECT" " e.id" ", e.account_name" ", e.tag" ", e.credit" ", e.debit" ", e.fees" ", e.payment_id" ", e.part_id" ", e.timestamp" " FROM channel_events e" " WHERE e.account_name = ?" " ORDER BY e.timestamp, e.id")); db_bind_text(stmt, acct->name); return find_channel_events(ctx, take(stmt)); } struct channel_event **get_channel_events_by_id(const tal_t *ctx, struct db *db, struct sha256 *id) { struct db_stmt *stmt; stmt = db_prepare_v2(db, SQL("SELECT" " e.id" ", e.account_name" ", e.tag" ", e.credit" ", e.debit" ", e.fees" ", e.payment_id" ", e.part_id" ", e.timestamp" " FROM channel_events e" " WHERE e.payment_id = ?" " ORDER BY e.timestamp, e.id")); db_bind_sha256(stmt, id); return find_channel_events(ctx, take(stmt)); } void log_channel_event(struct db *db, const struct account *acct, struct channel_event *e) { struct db_stmt *stmt; stmt = db_prepare_v2(db, SQL("INSERT INTO channel_events" " (" " account_name" ", tag" ", credit" ", debit" ", fees" ", payment_id" ", part_id" ", timestamp" ")" " VALUES" " (?, ?, ?, ?, ?, ?, ?, ?);")); db_bind_text(stmt, acct->name); db_bind_text(stmt, e->tag); db_bind_amount_msat(stmt, e->credit); db_bind_amount_msat(stmt, e->debit); db_bind_amount_msat(stmt, e->fees); if (e->payment_id) db_bind_sha256(stmt, e->payment_id); else db_bind_null(stmt); db_bind_int(stmt, e->part_id); db_bind_u64(stmt, e->timestamp); db_exec_prepared_v2(stmt); e->db_id = db_last_insert_id_v2(stmt); e->acct_name = tal_strdup(e, acct->name); tal_free(stmt); } struct chain_event **find_chain_events_bytxid(const tal_t *ctx, const struct bkpr *bkpr, const struct bitcoin_txid *txid) { struct db_stmt *stmt; stmt = db_prepare_v2(bkpr->db, SQL("SELECT " " e.id" ", e.account_name" ", e.origin" ", e.tag" ", e.credit" ", e.debit" ", e.output_value" ", e.timestamp" ", e.blockheight" ", e.utxo_txid" ", e.outnum" ", e.spending_txid" ", e.payment_id" ", e.stealable" ", e.spliced" " FROM chain_events e" " WHERE e.spending_txid = ?" " OR (e.utxo_txid = ? AND e.spending_txid IS NULL)" " ORDER BY e.account_name")); db_bind_txid(stmt, txid); db_bind_txid(stmt, txid); return find_chain_events(ctx, bkpr, take(stmt)); } void maybe_record_rebalance(struct command *cmd, struct bkpr *bkpr, const struct channel_event *out) { /* If there's a matching credit event, this is * a rebalance. Mark everything with the payment_id * and amt as such. If you repeat a payment_id * with the same amt, they'll be marked as rebalances * also */ struct db_stmt *stmt; struct amount_msat credit; bool ok; /* The amount of we were credited is debit - fees */ ok = amount_msat_sub(&credit, out->debit, out->fees); assert(ok); stmt = db_prepare_v2(bkpr->db, SQL("SELECT " " e.id" " FROM channel_events e" " WHERE e.payment_id = ?" " AND e.credit = ?")); db_bind_sha256(stmt, out->payment_id); db_bind_amount_msat(stmt, credit); db_query_prepared(stmt); while (db_step(stmt)) { u64 id = db_col_u64(stmt, "e.id"); /* Already has one? */ if (find_rebalance(bkpr, id)) continue; add_rebalance_pair(cmd, bkpr, out->db_id, id); break; } tal_free(stmt); } void maybe_closeout_external_deposits(struct command *cmd, struct bkpr *bkpr, const struct bitcoin_txid *txid, u32 blockheight) { struct db_stmt *stmt; assert(txid); stmt = db_prepare_v2(bkpr->db, SQL("SELECT " " 1" " FROM chain_events e" " WHERE e.blockheight = ?" " AND e.utxo_txid = ?" " AND e.account_name = ?")); /* Blockheight for unconfirmeds is zero */ db_bind_int(stmt, 0); db_bind_txid(stmt, txid); db_bind_text(stmt, ACCOUNT_NAME_EXTERNAL); db_query_prepared(stmt); if (db_step(stmt)) { db_col_ignore(stmt, "1"); add_blockheight(cmd, bkpr, txid, blockheight); } tal_free(stmt); } bool log_chain_event(struct bkpr *bkpr, const struct account *acct, struct chain_event *e) { struct db_stmt *stmt; /* We're responsible for de-duping chain events! */ if (find_chain_event(tmpctx, bkpr, acct, &e->outpoint, e->spending_txid, e->tag)) return false; stmt = db_prepare_v2(bkpr->db, SQL("INSERT INTO chain_events" " (" " account_name" ", origin" ", tag" ", credit" ", debit" ", output_value" ", timestamp" ", blockheight" ", utxo_txid" ", outnum" ", payment_id" ", spending_txid" ", stealable" ", spliced" ")" " VALUES " "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); db_bind_text(stmt, acct->name); if (e->origin_acct) db_bind_text(stmt, e->origin_acct); else db_bind_null(stmt); db_bind_text(stmt, e->tag); db_bind_amount_msat(stmt, e->credit); db_bind_amount_msat(stmt, e->debit); db_bind_amount_msat(stmt, e->output_value); db_bind_u64(stmt, e->timestamp); db_bind_int(stmt, e->blockheight); db_bind_txid(stmt, &e->outpoint.txid); db_bind_int(stmt, e->outpoint.n); if (e->payment_id) db_bind_sha256(stmt, e->payment_id); else db_bind_null(stmt); if (e->spending_txid) db_bind_txid(stmt, e->spending_txid); else db_bind_null(stmt); db_bind_int(stmt, e->stealable ? 1 : 0); db_bind_int(stmt, e->splice_close ? 1 : 0); db_exec_prepared_v2(stmt); e->db_id = db_last_insert_id_v2(stmt); e->acct_name = tal_strdup(e, acct->name); tal_free(stmt); return true; }