bkpr: convert all the local db sql queries into calls to sql plugin.
What changed, and why it matters
This commit rewrites the bookkeeper (bkpr) plugin so that most of its internal SQLite queries are sent through a new SQL plugin instead of being executed directly. The change is described as a refactoring to use a shared SQL backend. There is no claim in the commit that this fixes a security bug, and the diff does not show an obvious vulnerability. However, because the new queries are built by concatenating strings with user-influenced values (account names, transaction IDs, payment hashes) there is a small risk of SQL injection if the helper functions do not escape those values correctly. The commit also removes a duplicate-check when logging chain events, which could in principle allow duplicate records, but that is a data-integrity issue rather than a direct security flaw.
Review the new sql plugin helpers (sql_string, fmt_bitcoin_txid, fmt_sha256, fmt_bitcoin_outpoint, json_to_outpoint, json_to_msat, chain_events_from_sql, channel_events_from_sql) to confirm they use proper escaping or parameterized queries. Run static analysis and targeted tests for SQL injection with malicious account names or crafted txids/payment hashes. Verify that removing the duplicate check in log_chain_event does not allow duplicate chain_events to be inserted.
Security signals we found
Large-scale SQL query rewrite moving from parameterized prepared statements to dynamically built SQL strings
User-influenced values embedded into SQL (account_id, txid, payment_hash, outpoint)
Removal of in-code duplicate detection for chain event logging
No vendor statement that this is a security fix
Evidence from the diff
The patch converts direct sqlite3 prepared-statement calls in plugins/bkpr into calls to a new sql plugin (sql_req, chain_events_from_sql, channel_events_from_sql). It removes the old db_prepare_v2/db_bind_* style code and replaces it with formatted SQL strings. Several queries embed externally-derived values (account_id, txid, payment_hash, outpoint) using fmt_bitcoin_txid, fmt_sha256, fmt_bitcoin_outpoint and sql_string helpers. The correctness of these helpers is not shown in the diff, so the security of the conversion depends on whether they properly escape/parameterize inputs. The patch also removes the find_chain_event duplicate check inside log_chain_event, relying on the caller or database to prevent duplicates. The commit message explicitly frames this as a conversion/refactoring, not a security fix.
Changed components
plugins/bkpr/bookkeeper.cplugins/bkpr/channelsapy.cplugins/bkpr/channelsapy.hplugins/bkpr/incomestmt.cplugins/bkpr/incomestmt.hplugins/bkpr/onchain_fee.cplugins/bkpr/recorder.cplugins/bkpr/recorder.hInspect captured patch +355 / −665
diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c
index 77f40ac3..b3c9354b 100644
--- a/plugins/bkpr/bookkeeper.c
+++ b/plugins/bkpr/bookkeeper.c
@@ -219,7 +219,7 @@ getblockheight_done(struct command *cmd,
/* Get the income events */
db_begin_transaction(bkpr->db);
- apys = compute_channel_apys(cmd, bkpr,
+ apys = compute_channel_apys(cmd, bkpr, cmd,
*req->start_time,
*req->end_time,
blockheight);
@@ -314,7 +314,7 @@ static struct command_result *do_dump_income(struct command *cmd,
/* Ok, go find me some income events! */
db_begin_transaction(bkpr->db);
- evs = list_income_events(cmd, bkpr, *info->start_time, *info->end_time,
+ evs = list_income_events(cmd, bkpr, cmd, *info->start_time, *info->end_time,
*info->consolidate_fees);
db_commit_transaction(bkpr->db);
@@ -366,7 +366,7 @@ static struct command_result *do_list_income(struct command *cmd,
/* Ok, go find me some income events! */
db_begin_transaction(bkpr->db);
- evs = list_income_events(cmd, bkpr, *info->start_time, *info->end_time,
+ evs = list_income_events(cmd, bkpr, cmd, *info->start_time, *info->end_time,
*info->consolidate_fees);
db_commit_transaction(bkpr->db);
@@ -418,7 +418,7 @@ static struct command_result *do_inspect(struct command *cmd,
acct_name);
db_begin_transaction(bkpr->db);
- find_txo_chain(cmd, bkpr, acct, &txos);
+ find_txo_chain(cmd, bkpr, cmd, acct, &txos);
fee_sums = find_account_onchain_fees(cmd, bkpr, acct);
db_commit_transaction(bkpr->db);
@@ -625,22 +625,22 @@ static struct command_result *do_account_events(struct command *cmd,
db_begin_transaction(bkpr->db);
if (acct) {
- channel_events = account_get_channel_events(cmd, bkpr->db, acct);
- chain_events = account_get_chain_events(cmd, bkpr, acct);
+ channel_events = account_get_channel_events(cmd, bkpr, cmd, acct);
+ chain_events = account_get_chain_events(cmd, bkpr, cmd, acct);
onchain_fees = account_get_chain_fees(tmpctx, bkpr, acct->name);
} else if (info->payment_id != NULL) {
- channel_events = get_channel_events_by_id(cmd, bkpr->db, info->payment_id);
+ channel_events = get_channel_events_by_id(cmd, bkpr, cmd, info->payment_id);
tx_id = tal(cmd, struct bitcoin_txid);
tx_id->shad.sha = *info->payment_id;
/* Transaction ids are stored as big-endian in the database */
reverse_bytes(tx_id->shad.sha.u.u8, sizeof(tx_id->shad.sha.u.u8));
- chain_events = find_chain_events_bytxid(cmd, bkpr, tx_id);
+ chain_events = find_chain_events_bytxid(cmd, bkpr, cmd, tx_id);
onchain_fees = get_chain_fees_by_txid(cmd, bkpr, tx_id);
} else {
- channel_events = list_channel_events(cmd, bkpr->db);
- chain_events = list_chain_events(cmd, bkpr);
+ channel_events = list_channel_events(cmd, bkpr, cmd);
+ chain_events = list_chain_events(cmd, bkpr, cmd);
onchain_fees = list_chain_fees(cmd, bkpr);
}
db_commit_transaction(bkpr->db);
@@ -682,7 +682,7 @@ static struct command_result *do_edit_desc(struct command *cmd,
db_begin_transaction(bkpr->db);
add_utxo_description(cmd, bkpr, info->outpoint, info->new_desc);
- chain_events = get_chain_events_by_outpoint(cmd, bkpr, info->outpoint, true);
+ chain_events = get_chain_events_by_outpoint(cmd, bkpr, cmd, info->outpoint);
db_commit_transaction(bkpr->db);
res = jsonrpc_stream_success(cmd);
@@ -724,8 +724,8 @@ static struct command_result *do_edit_desc_payment(struct command *cmd,
db_begin_transaction(bkpr->db);
add_payment_hash_description(cmd, bkpr, info->identifier, info->new_desc);
- chain_events = get_chain_events_by_id(cmd, bkpr, info->identifier);
- channel_events = get_channel_events_by_id(cmd, bkpr->db, info->identifier);
+ chain_events = get_chain_events_by_id(cmd, bkpr, cmd, info->identifier);
+ channel_events = get_channel_events_by_id(cmd, bkpr, cmd, info->identifier);
db_commit_transaction(bkpr->db);
res = jsonrpc_stream_success(cmd);
@@ -768,7 +768,7 @@ static struct command_result *do_list_balances(struct command *cmd,
struct amount_msat credit, debit, balance;
bool has_events;
- has_events = account_get_credit_debit(cmd->plugin, bkpr->db,
+ has_events = account_get_credit_debit(bkpr, cmd,
accts[i]->name,
&credit, &debit);
if (!amount_msat_sub(&balance, credit, debit)) {
@@ -845,7 +845,7 @@ static void try_update_open_fees(struct command *cmd,
struct bkpr *bkpr = bkpr_of(cmd->plugin);
assert(acct->closed_event_db_id);
- ev = find_chain_event_by_id(cmd, bkpr, *acct->closed_event_db_id);
+ ev = find_chain_event_by_id(cmd, bkpr, cmd, *acct->closed_event_db_id);
assert(ev);
err = maybe_update_onchain_fees(cmd, cmd, bkpr, ev->spending_txid);
@@ -1163,7 +1163,7 @@ static char *do_account_close_checks(struct command *cmd,
} else if (!is_channel_account(acct->name) && !e->spending_txid) {
const char *acctname;
- acctname = find_close_account_name(tmpctx, bkpr->db, &e->outpoint.txid);
+ acctname = find_close_account_name(tmpctx, bkpr, cmd, &e->outpoint.txid);
if (acctname) {
closed_acct = find_account(bkpr, acctname);
} else {
@@ -1175,7 +1175,7 @@ static char *do_account_close_checks(struct command *cmd,
if (closed_acct && closed_acct->closed_event_db_id) {
- u64 closeheight = account_onchain_closeheight(bkpr, closed_acct);
+ u64 closeheight = account_onchain_closeheight(bkpr, cmd, closed_acct);
if (closeheight != 0) {
char *err;
account_update_closeheight(cmd, closed_acct, closeheight);
@@ -1398,7 +1398,7 @@ listpeerchannels_done(struct command *cmd,
info->acct,
info->ev->timestamp)) {
db_begin_transaction(bkpr->db);
- account_get_credit_debit(cmd->plugin, bkpr->db, info->acct->name,
+ account_get_credit_debit(bkpr, cmd, info->acct->name,
&credit, &debit);
db_commit_transaction(bkpr->db);
diff --git a/plugins/bkpr/channelsapy.c b/plugins/bkpr/channelsapy.c
index cc311c48..ed7f85b9 100644
--- a/plugins/bkpr/channelsapy.c
+++ b/plugins/bkpr/channelsapy.c
@@ -97,6 +97,7 @@ static struct account *search_account(struct account **accts, const char *acctna
}
static void fillin_apy_acct_details(const struct bkpr *bkpr,
+ struct command *cmd,
const struct account *acct,
u32 current_blockheight,
struct channel_apy *apy)
@@ -107,7 +108,7 @@ static void fillin_apy_acct_details(const struct bkpr *bkpr,
apy->acct_name = tal_strdup(apy, acct->name);
assert(acct->open_event_db_id);
- ev = find_chain_event_by_id(tmpctx, bkpr, *acct->open_event_db_id);
+ ev = find_chain_event_by_id(tmpctx, bkpr, cmd, *acct->open_event_db_id);
assert(ev);
apy->start_blockheight = ev->blockheight;
@@ -116,7 +117,7 @@ static void fillin_apy_acct_details(const struct bkpr *bkpr,
/* if this account is closed, add closing blockheight */
if (acct->closed_event_db_id) {
- ev = find_chain_event_by_id(acct, bkpr,
+ ev = find_chain_event_by_id(acct, bkpr, cmd,
*acct->closed_event_db_id);
assert(ev);
apy->end_blockheight = ev->blockheight;
@@ -140,7 +141,8 @@ static void fillin_apy_acct_details(const struct bkpr *bkpr,
}
struct channel_apy **compute_channel_apys(const tal_t *ctx,
- struct bkpr *bkpr,
+ const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time,
u32 current_blockheight)
@@ -149,7 +151,7 @@ struct channel_apy **compute_channel_apys(const tal_t *ctx,
struct channel_apy *apy, **apys;
struct account *acct, **accts;
- evs = list_channel_events_timebox(ctx, bkpr->db, start_time, end_time);
+ evs = list_channel_events_timebox(ctx, bkpr, cmd, start_time, end_time);
accts = list_accounts(ctx, bkpr);
apys = tal_arr(ctx, struct channel_apy *, 0);
@@ -167,7 +169,7 @@ struct channel_apy **compute_channel_apys(const tal_t *ctx,
if (!acct || !streq(acct->name, ev->acct_name)) {
if (acct && is_channel_account(acct->name)) {
- fillin_apy_acct_details(bkpr, acct,
+ fillin_apy_acct_details(bkpr, cmd, acct,
current_blockheight,
apy);
/* Save current apy, make new */
@@ -225,7 +227,7 @@ struct channel_apy **compute_channel_apys(const tal_t *ctx,
}
if (acct && is_channel_account(acct->name)) {
- fillin_apy_acct_details(bkpr, acct,
+ fillin_apy_acct_details(bkpr, cmd, acct,
current_blockheight,
apy);
/* Save current apy, make new */
diff --git a/plugins/bkpr/channelsapy.h b/plugins/bkpr/channelsapy.h
index 73cacaee..6a63d4e0 100644
--- a/plugins/bkpr/channelsapy.h
+++ b/plugins/bkpr/channelsapy.h
@@ -34,7 +34,8 @@ WARN_UNUSED_RESULT bool channel_apy_sum(struct channel_apy *sum_apy,
const struct channel_apy *entry);
struct channel_apy **compute_channel_apys(const tal_t *ctx,
- struct bkpr *bkpr,
+ const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time,
u32 current_blockheight);
diff --git a/plugins/bkpr/incomestmt.c b/plugins/bkpr/incomestmt.c
index eac90f96..fd3a7e45 100644
--- a/plugins/bkpr/incomestmt.c
+++ b/plugins/bkpr/incomestmt.c
@@ -1,14 +1,12 @@
#include "config.h"
+#include <assert.h>
#include <bitcoin/chainparams.h>
#include <ccan/array_size/array_size.h>
+#include <ccan/json_escape/json_escape.h>
#include <ccan/tal/str/str.h>
#include <common/coin_mvt.h>
#include <common/json_parse_simple.h>
#include <common/json_stream.h>
-#include <db/bindings.h>
-#include <db/common.h>
-#include <db/exec.h>
-#include <db/utils.h>
#include <inttypes.h>
#include <plugins/bkpr/account.h>
#include <plugins/bkpr/account_entry.h>
@@ -20,6 +18,7 @@
#include <plugins/bkpr/onchain_fee.h>
#include <plugins/bkpr/rebalances.h>
#include <plugins/bkpr/recorder.h>
+#include <plugins/bkpr/sql.h>
#include <time.h>
#define ONCHAIN_FEE "onchain_fee"
@@ -111,6 +110,7 @@ static char *csv_safe_str(const tal_t *ctx, const char *input TAKES)
static struct income_event *maybe_chain_income(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
struct account *acct,
struct chain_event *ev)
{
@@ -141,7 +141,8 @@ static struct income_event *maybe_chain_income(const tal_t *ctx,
/* income */
if (streq(ev->tag, "deposit")) {
- struct db_stmt *stmt;
+ const jsmntok_t *toks;
+ const char *buf;
/* deposit to external is cost to us */
if (is_external_account(ev->acct_name)) {
@@ -168,16 +169,16 @@ static struct income_event *maybe_chain_income(const tal_t *ctx,
* into a tx that included funds from a 3rd party
* coming to us... eg. a splice out from the peer
* to our onchain wallet */
- stmt = db_prepare_v2(bkpr->db, SQL("SELECT"
- " 1"
- " FROM chain_events e"
- " WHERE "
- " e.spending_txid = ?"));
-
- db_bind_txid(stmt, &ev->outpoint.txid);
- db_query_prepared(stmt);
- if (!db_step(stmt)) {
- tal_free(stmt);
+ toks = sql_req(tmpctx, cmd, &buf,
+ "SELECT"
+ " 1"
+ " FROM chainmoves"
+ " WHERE "
+ " spending_txid = X'%s'"
+ " AND created_index <= %"PRIu64,
+ fmt_bitcoin_txid(tmpctx, &ev->outpoint.txid),
+ bkpr->chainmoves_index);
+ if (json_get_member(buf, toks, "rows")->size == 0) {
/* no matching withdrawal from internal,
* so must be new deposit (external) */
return chain_to_income(ctx, bkpr, ev,
@@ -185,9 +186,6 @@ static struct income_event *maybe_chain_income(const tal_t *ctx,
ev->credit,
ev->debit);
}
-
- db_col_ignore(stmt, "1");
- tal_free(stmt);
return NULL;
}
@@ -306,6 +304,7 @@ static struct onchain_fee **find_consolidated_fees(const tal_t *ctx,
struct income_event **list_income_events(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time,
bool consolidate_fees)
@@ -315,9 +314,9 @@ struct income_event **list_income_events(const tal_t *ctx,
struct onchain_fee **onchain_fees;
struct income_event **evs;
- channel_events = list_channel_events_timebox(ctx, bkpr->db,
+ channel_events = list_channel_events_timebox(ctx, bkpr, cmd,
start_time, end_time);
- chain_events = list_chain_events_timebox(ctx, bkpr, start_time, end_time);
+ chain_events = list_chain_events_timebox(ctx, bkpr, cmd, start_time, end_time);
if (consolidate_fees) {
onchain_fees = find_consolidated_fees(ctx, bkpr,
@@ -369,7 +368,7 @@ struct income_event **list_income_events(const tal_t *ctx,
struct account *acct =
find_account(bkpr, chain->acct_name);
- ev = maybe_chain_income(evs, bkpr, acct, chain);
+ ev = maybe_chain_income(evs, bkpr, cmd, acct, chain);
if (ev)
tal_arr_expand(&evs, ev);
i++;
@@ -407,9 +406,10 @@ struct income_event **list_income_events(const tal_t *ctx,
struct income_event **list_income_events_all(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
bool consolidate_fees)
{
- return list_income_events(ctx, bkpr, 0, SQLITE_MAX_UINT,
+ return list_income_events(ctx, bkpr, cmd, 0, SQLITE_MAX_UINT,
consolidate_fees);
}
diff --git a/plugins/bkpr/incomestmt.h b/plugins/bkpr/incomestmt.h
index a6c813aa..0de3c00d 100644
--- a/plugins/bkpr/incomestmt.h
+++ b/plugins/bkpr/incomestmt.h
@@ -31,12 +31,14 @@ struct csv_fmt {
/* List all the events that are income related (gain/loss) */
struct income_event **list_income_events_all(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
bool consolidate_fees);
/* List all the events that are income related (gain/loss),
* by a start and end date */
struct income_event **list_income_events(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time,
bool consolidate_fees);
diff --git a/plugins/bkpr/onchain_fee.c b/plugins/bkpr/onchain_fee.c
index 3f4e9dc0..4af85e25 100644
--- a/plugins/bkpr/onchain_fee.c
+++ b/plugins/bkpr/onchain_fee.c
@@ -397,9 +397,9 @@ char *update_channel_onchain_fees(const tal_t *ctx,
struct amount_msat onchain_amt;
assert(acct->onchain_resolved_block);
- close_ev = find_chain_event_by_id(ctx, bkpr,
+ close_ev = find_chain_event_by_id(ctx, bkpr, cmd,
*acct->closed_event_db_id);
- events = find_chain_events_bytxid(ctx, bkpr,
+ events = find_chain_events_bytxid(ctx, bkpr, cmd,
close_ev->spending_txid);
/* Starting balance is close-ev's debit amount */
@@ -453,6 +453,7 @@ char *update_channel_onchain_fees(const tal_t *ctx,
static char *is_closed_channel_txid(const tal_t *ctx,
struct bkpr *bkpr,
+ struct command *cmd,
struct chain_event *ev,
struct bitcoin_txid *txid,
bool *is_channel_close_tx)
@@ -475,7 +476,7 @@ static char *is_closed_channel_txid(const tal_t *ctx,
/* is the closed utxo the same as the one
* we're trying to find fees for now */
- closed = find_chain_event_by_id(inner_ctx, bkpr,
+ closed = find_chain_event_by_id(inner_ctx, bkpr, cmd,
*acct->closed_event_db_id);
if (!closed) {
*is_channel_close_tx = false;
@@ -516,7 +517,7 @@ char *maybe_update_onchain_fees(const tal_t *ctx,
u8 *inner_ctx = tal(NULL, u8);
/* Find all the deposits/withdrawals for this txid */
- events = find_chain_events_bytxid(inner_ctx, bkpr, txid);
+ events = find_chain_events_bytxid(inner_ctx, bkpr, cmd, txid);
/* If we don't even have two events, skip */
if (tal_count(events) < 2)
@@ -524,7 +525,7 @@ char *maybe_update_onchain_fees(const tal_t *ctx,
for (size_t i = 0; i < tal_count(events); i++) {
bool is_channel_close_tx;
- err = is_closed_channel_txid(ctx, bkpr,
+ err = is_closed_channel_txid(ctx, bkpr, cmd,
events[i], txid,
&is_channel_close_tx);
diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c
index 08fda0f1..2e84bedd 100644
--- a/plugins/bkpr/recorder.c
+++ b/plugins/bkpr/recorder.c
@@ -18,220 +18,62 @@
#include <plugins/bkpr/onchain_fee.h>
#include <plugins/bkpr/rebalances.h>
#include <plugins/bkpr/recorder.h>
+#include <plugins/bkpr/sql.h>
#include <plugins/libplugin.h>
-
-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,
+ struct command *cmd,
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));
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE timestamp > %"PRIu64
+ " AND timestamp <= %"PRIu64
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index;",
+ start_time, end_time,
+ bkpr->chainmoves_index);
}
-struct chain_event **list_chain_events(const tal_t *ctx, const struct bkpr *bkpr)
+struct chain_event **list_chain_events(const tal_t *ctx,
+ const struct bkpr *bkpr,
+ struct command *cmd)
{
- return list_chain_events_timebox(ctx, bkpr, 0, SQLITE_MAX_UINT);
+ return list_chain_events_timebox(ctx, bkpr, cmd, 0, SQLITE_MAX_UINT);
}
struct chain_event **account_get_chain_events(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
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));
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE account_id = '%s'"
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index;",
+ sql_string(tmpctx, acct->name),
+ bkpr->chainmoves_index);
}
static struct chain_event **find_txos_for_tx(const tal_t *ctx,
const struct bkpr *bkpr,
- struct bitcoin_txid *txid)
+ struct command *cmd,
+ 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.utxo_txid = ?"
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ /* utxo is txid:outnum */
+ " WHERE utxo LIKE '%s:%%'"
+ " AND created_index <= %"PRIu64
" 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));
+ " utxo"
+ ", spending_txid NULLS FIRST"
+ ", blockheight",
+ fmt_bitcoin_txid(tmpctx, txid),
+ bkpr->chainmoves_index);
}
static struct txo_pair *new_txo_pair(const tal_t *ctx)
@@ -244,7 +86,8 @@ static struct txo_pair *new_txo_pair(const tal_t *ctx)
static struct txo_set *find_txo_set(const tal_t *ctx,
const struct bkpr *bkpr,
- struct bitcoin_txid *txid,
+ struct command *cmd,
+ const struct bitcoin_txid *txid,
const char *acct_name,
bool *is_complete)
{
@@ -255,7 +98,7 @@ static struct txo_set *find_txo_set(const tal_t *ctx,
/* 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);
+ evs = find_txos_for_tx(ctx, bkpr, cmd, txid);
txos->pairs = tal_arr(txos, struct txo_pair *, 0);
txos->txid = tal_dup(txos, struct bitcoin_txid, txid);
@@ -323,6 +166,7 @@ static bool txid_in_list(struct bitcoin_txid **list,
bool find_txo_chain(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
const struct account *acct,
struct txo_set ***sets)
{
@@ -332,7 +176,7 @@ bool find_txo_chain(const tal_t *ctx,
const char *start_acct_name;
assert(acct->open_event_db_id);
- open_ev = find_chain_event_by_id(ctx, bkpr,
+ open_ev = find_chain_event_by_id(ctx, bkpr, cmd,
*acct->open_event_db_id);
if (sets)
@@ -349,7 +193,7 @@ bool find_txo_chain(const tal_t *ctx,
struct txo_set *set;
bool set_complete;
- set = find_txo_set(ctx, bkpr, txids[i],
+ set = find_txo_set(ctx, bkpr, cmd, txids[i],
start_acct_name,
&set_complete);
@@ -392,55 +236,69 @@ bool find_txo_chain(const tal_t *ctx,
}
const char *find_close_account_name(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
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;
+ const char *buf;
+ const jsmntok_t *result, *rows, *row;
+ size_t i;
+ const char *acct_name = NULL;
+
+ /* We look for a CHANNEL_CLOSE spend, but ignore “spliced” close events. */
+ result = sql_req(ctx, cmd, &buf,
+ "SELECT account_id"
+ " FROM chainmoves cm"
+ " WHERE cm.primary_tag = '%s'"
+ " AND cm.spending_txid = X'%s'"
+ " AND NOT EXISTS ("
+ " SELECT 1 FROM chainmoves_extra_tags et"
+ " WHERE et.row = cm.created_index"
+ " AND et.extra_tags = 'spliced'"
+ " )"
+ " AND"
+ " cm.created_index <= %"PRIu64
+ " LIMIT 1",
+ sql_string(tmpctx, mvt_tag_str(MVT_CHANNEL_CLOSE)),
+ fmt_bitcoin_txid(tmpctx, txid),
+ bkpr->chainmoves_index);
+
+ rows = json_get_member(buf, result, "rows");
+ json_for_each_arr(i, row, rows) {
+ /* Single column => row->size == 1; first value token is row+1 */
+ const jsmntok_t *val = row + 1;
+ acct_name = json_strdup(ctx, buf, val);
+ break; /* only need the first row */
+ }
- tal_free(stmt);
- return acct_name;
+ return acct_name; /* NULL if none found */
}
-u64 account_onchain_closeheight(const struct bkpr *bkpr, const struct account *acct)
+u64 account_onchain_closeheight(const struct bkpr *bkpr,
+ struct command *cmd,
+ 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,
+ close_ev = find_chain_event_by_id(ctx, bkpr, cmd,
*acct->closed_event_db_id);
- if (find_txo_chain(ctx, bkpr, acct, &sets)) {
+ if (find_txo_chain(ctx, bkpr, cmd, acct, &sets)) {
/* Ok now we find the max block height of the
* spending chain_events for this channel */
bool ok;
+ const char *buf;
+ const jsmntok_t *result, *rows, *row;
+ size_t i;
/* Have we accounted for all the outputs */
ok = false;
- for (size_t i = 0; i < tal_count(sets); i++) {
+ for (i = 0; i < tal_count(sets); i++) {
if (bitcoin_txid_eq(sets[i]->txid,
close_ev->spending_txid)) {
@@ -455,21 +313,28 @@ u64 account_onchain_closeheight(const struct bkpr *bkpr, const struct account *a
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);
+ result = sql_req(tmpctx, cmd, &buf,
+ "SELECT blockheight"
+ " FROM chainmoves"
+ " WHERE account_id = '%s'"
+ " AND spending_txid IS NOT NULL"
+ " AND created_index <= %"PRIu64
+ " ORDER BY blockheight DESC"
+ " LIMIT 1",
+ sql_string(tmpctx, acct->name),
+ bkpr->chainmoves_index);
+
+ rows = json_get_member(buf, result, "rows");
+ assert(rows && rows->type == JSMN_ARRAY);
- height = db_col_int(stmt, "blockheight");
- tal_free(stmt);
+ height = 0;
+ json_for_each_arr(i, row, rows) {
+ const jsmntok_t *val = row + 1;
+ assert(row->size == 1);
+ ok = json_to_u64(buf, val, &height);
+ assert(ok);
+ break;
+ }
} else {
height = 0;
}
@@ -480,356 +345,169 @@ u64 account_onchain_closeheight(const struct bkpr *bkpr, const struct account *a
struct chain_event *find_chain_event_by_id(const tal_t *ctx,
const struct bkpr *bkpr,
- u64 event_db_id)
+ struct command *cmd,
+ u64 created_index)
{
- 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;
+ struct chain_event **evs =
+ chain_events_from_sql(tmpctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE created_index = %"PRIu64
+ " LIMIT 1;",
+ created_index);
- tal_free(stmt);
- return e;
+ if (tal_count(evs) == 0)
+ return NULL;
+
+ return tal_steal(ctx, evs[0]);
}
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 command *cmd,
+ const struct bitcoin_outpoint *outpoint)
{
- 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));
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE utxo = '%s'"
+ " AND credit_msat > 0"
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index",
+ fmt_bitcoin_outpoint(tmpctx, outpoint),
+ bkpr->chainmoves_index);
}
struct chain_event **get_chain_events_by_id(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
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;
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE payment_hash = X'%s'"
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index",
+ fmt_sha256(tmpctx, id),
+ bkpr->chainmoves_index);
}
-bool account_get_credit_debit(struct plugin *plugin,
- struct db *db,
+bool account_get_credit_debit(const struct bkpr *bkpr,
+ struct command *cmd,
const char *acct_name,
struct amount_msat *credit,
struct amount_msat *debit)
{
- struct db_stmt *stmt;
+ const jsmntok_t *result, *rows, *row;
+ const char *buf;
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");
+ result = sql_req(tmpctx, cmd, &buf,
+ "SELECT"
+ " CAST(SUM(credit_msat) AS BIGINT)"
+ ", CAST(SUM(debit_msat) AS BIGINT)"
+ " FROM chainmoves"
+ " WHERE account_id = '%s'"
+ " AND created_index <= %"PRIu64,
+ sql_string(tmpctx, acct_name),
+ bkpr->chainmoves_index);
+ rows = json_get_member(buf, result, "rows");
+ assert(rows && rows->type == JSMN_ARRAY && rows->size == 1);
+ row = rows + 1;
+ assert(row->size == 2);
+ if (json_tok_is_null(buf, row + 1)) {
*credit = *debit = AMOUNT_MSAT(0);
exists = false;
} else {
- *credit = db_col_amount_msat(stmt, "credit");
- *debit = db_col_amount_msat(stmt, "debit");
+ json_to_msat(buf, row + 1, credit);
+ json_to_msat(buf, row + 2, 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",
+ result = sql_req(tmpctx, cmd, &buf,
+ "SELECT"
+ " CAST(SUM(credit_msat) AS BIGINT)"
+ ", CAST(SUM(debit_msat) AS BIGINT)"
+ " FROM channelmoves"
+ " WHERE account_id = '%s'"
+ " AND created_index <= %"PRIu64,
+ sql_string(tmpctx, acct_name),
+ bkpr->channelmoves_index);
+ rows = json_get_member(buf, result, "rows");
+ assert(rows && rows->type == JSMN_ARRAY && rows->size == 1);
+ row = rows + 1;
+ assert(row->size == 2);
+ if (!json_tok_is_null(buf, row + 1)) {
+ struct amount_msat channel_credit, channel_debit;
+ json_to_msat(buf, row + 1, &channel_credit);
+ json_to_msat(buf, row + 2, &channel_debit);
+
+ if (!amount_msat_accumulate(credit, channel_credit)) {
+ plugin_err(cmd->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")));
+ fmt_amount_msat(tmpctx, channel_credit));
}
- if (!amount_msat_accumulate(debit,
- db_col_amount_msat(stmt, "debit"))) {
- plugin_err(plugin, "db overflow: chain debit %s, adding channel debit %s",
+ if (!amount_msat_accumulate(debit, channel_debit)) {
+ plugin_err(cmd->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")));
+ fmt_amount_msat(tmpctx, channel_debit));
}
exists = true;
}
- tal_free(stmt);
return exists;
}
struct channel_event **list_channel_events_timebox(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
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;
+ return channel_events_from_sql(ctx, cmd,
+ SELECT_CHANNEL_EVENTS
+ " WHERE timestamp > %"PRIu64
+ " AND timestamp <= %"PRIu64
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index;",
+ start_time, end_time,
+ bkpr->channelmoves_index);
}
-struct channel_event **list_channel_events(const tal_t *ctx, struct db *db)
+struct channel_event **list_channel_events(const tal_t *ctx,
+ const struct bkpr *bkpr,
+ struct command *cmd)
{
- return list_channel_events_timebox(ctx, db, 0, SQLITE_MAX_UINT);
+ return list_channel_events_timebox(ctx, bkpr, cmd, 0, SQLITE_MAX_UINT);
}
struct channel_event **account_get_channel_events(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
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));
+ return channel_events_from_sql(ctx, cmd,
+ SELECT_CHANNEL_EVENTS
+ " WHERE account_id = '%s'"
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index",
+ sql_string(tmpctx, acct->name),
+ bkpr->channelmoves_index);
}
struct channel_event **get_channel_events_by_id(const tal_t *ctx,
- struct db *db,
- struct sha256 *id)
+ const struct bkpr *bkpr,
+ struct command *cmd,
+ const 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));
+ return channel_events_from_sql(ctx, cmd,
+ SELECT_CHANNEL_EVENTS
+ " WHERE payment_hash = X'%s'"
+ " AND created_index <= %"PRIu64
+ " ORDER BY timestamp, created_index",
+ fmt_sha256(tmpctx, id),
+ bkpr->channelmoves_index);
}
void log_channel_event(struct db *db,
@@ -872,34 +550,18 @@ void log_channel_event(struct db *db,
struct chain_event **find_chain_events_bytxid(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
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));
+ return chain_events_from_sql(ctx, bkpr, cmd,
+ SELECT_CHAIN_EVENTS
+ " WHERE created_index <= %"PRIu64
+ " AND (spending_txid = X'%s'"
+ " OR (utxo LIKE '%s%%' AND spending_txid IS NULL))"
+ " ORDER BY account_id, created_index",
+ bkpr->chainmoves_index,
+ fmt_bitcoin_txid(tmpctx, txid), /* spending_txid match */
+ fmt_bitcoin_txid(tmpctx, txid)); /* utxo prefix (txid:*) */
}
void maybe_record_rebalance(struct command *cmd,
@@ -911,7 +573,9 @@ void maybe_record_rebalance(struct command *cmd,
* 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;
+ const char *buf;
+ const jsmntok_t *res, *rows, *row, *val;
+ size_t i;
struct amount_msat credit;
bool ok;
@@ -919,54 +583,65 @@ void maybe_record_rebalance(struct command *cmd,
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);
+ /* Look for a matching credit-side event for the same payment */
+ res = sql_req(tmpctx, cmd, &buf,
+ "SELECT created_index"
+ " FROM channelmoves"
+ " WHERE payment_hash = X'%s'"
+ " AND credit_msat = %"PRIu64
+ " AND created_index <= %"PRIu64,
+ fmt_sha256(tmpctx, out->payment_id),
+ credit.millisatoshis /* Raw: sql query */,
+ bkpr->channelmoves_index);
+
+ rows = json_get_member(buf, res, "rows");
+ json_for_each_arr(i, row, rows) {
+ u64 id;
+ val = row + 1; /* single column */
+ ok = json_to_u64(buf, val, &id);
+ assert(ok);
- 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,
+ const struct bitcoin_txid *txid,
u32 blockheight)
{
- struct db_stmt *stmt;
+ const char *buf;
+ const jsmntok_t *res, *rows, *row;
+ size_t i;
+
+ /* Find any unconfirmed external deposits for this txid. */
+ res = sql_req(tmpctx, cmd, &buf,
+ "SELECT utxo"
+ " FROM chainmoves"
+ " WHERE blockheight = 0"
+ " AND utxo LIKE '%s:%%'"
+ " AND account_id = '%s'"
+ " AND created_index <= %"PRIu64,
+ /* utxo is '<txid>:<vout>' so we prefix-match on txid: */
+ fmt_bitcoin_txid(tmpctx, txid),
+ sql_string(tmpctx, ACCOUNT_NAME_EXTERNAL),
+ bkpr->chainmoves_index);
+
+ rows = json_get_member(buf, res, "rows");
+ json_for_each_arr(i, row, rows) {
+ const jsmntok_t *val = row + 1; /* single column */
+ struct bitcoin_outpoint outp;
+ bool ok;
- 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);
+ ok = json_to_outpoint(buf, val, &outp);
+ assert(ok);
+ add_blockheight(cmd, bkpr, &outp.txid, blockheight);
}
-
- tal_free(stmt);
}
bool log_chain_event(struct bkpr *bkpr,
@@ -975,12 +650,6 @@ bool log_chain_event(struct bkpr *bkpr,
{
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"
diff --git a/plugins/bkpr/recorder.h b/plugins/bkpr/recorder.h
index 991a2a59..9ffdd272 100644
--- a/plugins/bkpr/recorder.h
+++ b/plugins/bkpr/recorder.h
@@ -33,16 +33,20 @@ struct txo_set {
/* Get all channel events for this account */
struct channel_event **account_get_channel_events(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
struct account *acct);
/* Get all channel events for a payment id, order by timestamp */
struct channel_event **get_channel_events_by_id(const tal_t *ctx,
- struct db *db,
- struct sha256 *id);
+ const struct bkpr *bkpr,
+ struct command *cmd,
+ const struct sha256 *id);
/* Get all channel events, ordered by timestamp */
-struct channel_event **list_channel_events(const tal_t *ctx, struct db *db);
+struct channel_event **list_channel_events(const tal_t *ctx,
+ const struct bkpr *bkpr,
+ struct command *cmd);
/* Get all channel events, order by timestamp.
*
@@ -52,50 +56,56 @@ struct channel_event **list_channel_events(const tal_t *ctx, struct db *db);
* @end_time - UNIX timestamp to query until (inclusive)
*/
struct channel_event **list_channel_events_timebox(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time);
/* Get all chain events for this account */
struct chain_event **account_get_chain_events(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
struct account *acct);
/* Get all chain events for a transaction id, order by timestamp */
struct chain_event **find_chain_events_bytxid(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
const struct bitcoin_txid *txid);
/* Get all chain events, order by timestamp. */
-struct chain_event **list_chain_events(const tal_t *ctx, const struct bkpr *bkpr);
+struct chain_event **list_chain_events(const tal_t *ctx,
+ const struct bkpr *bkpr,
+ struct command *cmd);
/* Get all chain events, order by timestamp.
*
* @ctx - context to allocate from
- * @db - database to query
* @start_time - UNIX timestamp to query after (exclusive)
* @end_time - UNIX timestamp to query until (inclusive)
*/
struct chain_event **list_chain_events_timebox(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
u64 start_time,
u64 end_time);
/* Get all chain events for a payment hash */
struct chain_event **get_chain_events_by_id(const tal_t *ctx,
- const struct bkpr *bkpr,
- const struct sha256 *id);
+ const struct bkpr *bkpr,
+ struct command *cmd,
+ const struct sha256 *id);
/* Get all chain events for a utxo */
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 command *cmd,
+ const struct bitcoin_outpoint *outpoint);
/* Get total credits and debits for this account: returns false if no entries at all
* (in which case, credit and debit will both be AMOUNT_MSAT(0)). */
-bool account_get_credit_debit(struct plugin *plugin,
- struct db *db,
+bool account_get_credit_debit(const struct bkpr *bkpr,
+ struct command *cmd,
const char *acct_name,
struct amount_msat *credit,
struct amount_msat *debit);
@@ -104,6 +114,7 @@ bool account_get_credit_debit(struct plugin *plugin,
/* Find a chain event by its database id */
struct chain_event *find_chain_event_by_id(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
u64 event_db_id);
/* Find the utxos for this account.
@@ -113,13 +124,15 @@ struct chain_event *find_chain_event_by_id(const tal_t *ctx,
*/
bool find_txo_chain(const tal_t *ctx,
const struct bkpr *bkpr,
+ struct command *cmd,
const struct account *acct,
struct txo_set ***sets);
/* Find the account that was closed by this txid.
* Returns NULL if none */
const char *find_close_account_name(const tal_t *ctx,
- struct db *db,
+ const struct bkpr *bkpr,
+ struct command *cmd,
const struct bitcoin_txid *txid);
/* Have all the outputs for this account's close tx
@@ -127,7 +140,9 @@ const char *find_close_account_name(const tal_t *ctx,
* highest blockheight that has a resolving tx in it.
*
* The point of this is to allow us to prune data, eventually */
-u64 account_onchain_closeheight(const struct bkpr *bkpr, const struct account *acct);
+u64 account_onchain_closeheight(const struct bkpr *bkpr,
+ struct command *cmd,
+ const struct account *acct);
/* When we make external deposits from the wallet, we don't
* count them until any output that was spent *into* them is
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.