What changed, and why it matters
This commit restores a bookkeeping plugin test that had been disabled. It adds a new test file that exercises account, chain-event, channel-event, and on-chain-fee recording logic against a temporary local SQLite database. The changes are confined to test code and build files; no production plugin or node logic is modified.
No security action required. Treat as normal test-infrastructure maintenance. If reviewing further, verify that the restored test passes in CI and that the local SQL stub does not accidentally get compiled into production builds.
Security signals we found
No changes to network-facing code, cryptographic operations, or consensus logic.
No modifications to production plugin behavior; changes are test-only.
No new dependencies on untrusted input parsing in production paths.
Test harness uses temporary SQLite databases and asserts on query results.
Evidence from the diff
The patch re-enables the run-recorder unit test for the bkpr (bookkeeper) plugin. It introduces plugins/bkpr/test/run-recorder.c, which sets up an in-memory/temp SQLite database, runs migrations copied from the plugin’s schema, and implements local stubs for jsonrpc_request_sync and datastore helpers so that recorder and SQL code can be tested without a running lightningd. The Makefile gains common/json_parse.o to satisfy new link dependencies, and run-sql.c drops a batch of now-unneeded JSON-parsing stubs. sql.h receives only whitespace/indentation changes.
Changed components
plugins/bkpr/test/run-recorder.c (new test)plugins/bkpr/test/Makefile (build dependencies)plugins/bkpr/test/run-sql.c (removed obsolete stubs)plugins/bkpr/sql.h (cosmetic formatting only)Inspect captured patch +1681 / −58
diff --git a/plugins/bkpr/sql.h b/plugins/bkpr/sql.h
index 8cde88ab..5d248972 100644
--- a/plugins/bkpr/sql.h
+++ b/plugins/bkpr/sql.h
@@ -43,10 +43,9 @@ struct command_result;
" AND et.extra_tags = 'splice') AS spliced" \
" FROM chainmoves cm "
-const jsmntok_t *
-PRINTF_FMT(4, 5) sql_req(const tal_t *ctx,
- struct command *cmd, const char **buf,
- const char *fmt, ...);
+const jsmntok_t *PRINTF_FMT(4, 5) sql_req(const tal_t *ctx,
+ struct command *cmd, const char **buf,
+ const char *fmt, ...);
const jsmntok_t *sql_reqv(const tal_t *ctx,
struct command *cmd, const char **buf,
diff --git a/plugins/bkpr/test/Makefile b/plugins/bkpr/test/Makefile
index 52b92a1f..80fed187 100644
--- a/plugins/bkpr/test/Makefile
+++ b/plugins/bkpr/test/Makefile
@@ -13,6 +13,7 @@ BOOKKEEPER_TEST_COMMON_OBJS := \
common/channel_type.o \
common/coin_mvt.o \
common/features.o \
+ common/json_parse.o \
common/json_stream.o \
common/json_parse_simple.o \
common/key_derive.o \
diff --git a/plugins/bkpr/test/run-recorder.c b/plugins/bkpr/test/run-recorder.c
new file mode 100644
index 00000000..a8b131e1
--- /dev/null
+++ b/plugins/bkpr/test/run-recorder.c
@@ -0,0 +1,1677 @@
+#include "config.h"
+#include "common/json_filter.c"
+#include "test_utils.h"
+
+#include <bitcoin/tx.h>
+#include <ccan/tal/str/str.h>
+#include <common/coin_mvt.h>
+#include <common/daemon.h>
+#include <common/deprecation.h>
+#include <common/fee_states.h>
+#include <common/htlc.h>
+#include <common/json_param.h>
+#include <common/json_parse_simple.h>
+#include <common/json_stream.h>
+#include <common/plugin.h>
+#include <common/setup.h>
+#include <common/trace.h>
+#include <common/utils.h>
+#include <plugins/bkpr/account.h>
+#include <plugins/bkpr/account_entry.h>
+#include <plugins/bkpr/bookkeeper.h>
+#include <plugins/bkpr/chain_event.h>
+#include <plugins/bkpr/channel_event.h>
+#include <plugins/bkpr/onchain_fee.h>
+#include <plugins/bkpr/recorder.h>
+#include <plugins/libplugin.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <wire/wire.h>
+
+#include "plugins/bkpr/account.c"
+#include "plugins/bkpr/blockheights.c"
+#include "plugins/bkpr/channel_event.c"
+#include "plugins/bkpr/chain_event.c"
+#include "plugins/bkpr/recorder.c"
+#include "plugins/bkpr/onchain_fee.c"
+#include "plugins/bkpr/rebalances.c"
+#include "plugins/bkpr/sql.c"
+
+#if HAVE_SQLITE3
+ #include <sqlite3.h>
+#endif
+
+/* AUTOGENERATED MOCKS START */
+/* Generated stub for chain_event_description */
+const char *chain_event_description(const struct bkpr *bkpr UNNEEDED,
+ const struct chain_event *ce UNNEEDED)
+{ fprintf(stderr, "chain_event_description called!\n"); abort(); }
+/* Generated stub for channel_event_description */
+const char *channel_event_description(const struct bkpr *bkpr UNNEEDED,
+ const struct channel_event *ce UNNEEDED)
+{ fprintf(stderr, "channel_event_description called!\n"); abort(); }
+/* Generated stub for command_fail_badparam */
+struct command_result *command_fail_badparam(struct command *cmd UNNEEDED,
+ const char *paramname UNNEEDED,
+ const char *buffer UNNEEDED,
+ const jsmntok_t *tok UNNEEDED,
+ const char *msg UNNEEDED)
+{ fprintf(stderr, "command_fail_badparam called!\n"); abort(); }
+/* Generated stub for command_filter_ptr */
+struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); }
+/* Generated stub for first_fee_state */
+enum htlc_state first_fee_state(enum side opener UNNEEDED)
+{ fprintf(stderr, "first_fee_state called!\n"); abort(); }
+/* Generated stub for fmt_channel_id */
+char *fmt_channel_id(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED)
+{ fprintf(stderr, "fmt_channel_id called!\n"); abort(); }
+/* Generated stub for fmt_wireaddr_without_port */
+char *fmt_wireaddr_without_port(const tal_t *ctx UNNEEDED, const struct wireaddr *a UNNEEDED)
+{ fprintf(stderr, "fmt_wireaddr_without_port called!\n"); abort(); }
+/* Generated stub for fromwire_wireaddr */
+bool fromwire_wireaddr(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct wireaddr *addr UNNEEDED)
+{ fprintf(stderr, "fromwire_wireaddr called!\n"); abort(); }
+/* Generated stub for htlc_state_flags */
+int htlc_state_flags(enum htlc_state state UNNEEDED)
+{ fprintf(stderr, "htlc_state_flags called!\n"); abort(); }
+/* Generated stub for htlc_state_name */
+const char *htlc_state_name(enum htlc_state s UNNEEDED)
+{ fprintf(stderr, "htlc_state_name called!\n"); abort(); }
+/* Generated stub for last_fee_state */
+enum htlc_state last_fee_state(enum side opener UNNEEDED)
+{ fprintf(stderr, "last_fee_state called!\n"); abort(); }
+/* Generated stub for plugin_err */
+void plugin_err(struct plugin *p UNNEEDED, const char *fmt UNNEEDED, ...)
+{ fprintf(stderr, "plugin_err called!\n"); abort(); }
+/* Generated stub for plugin_log */
+void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
+{ fprintf(stderr, "plugin_log called!\n"); abort(); }
+/* AUTOGENERATED MOCKS END */
+
+static sqlite3 *bkpr_db;
+
+/* Stolen from old plugins/bkpr/db.c */
+struct migration {
+ const char *sql;
+ void (*func)(struct plugin *p, struct db *db);
+};
+
+static struct migration db_migrations[] = {
+ {SQL("CREATE TABLE version (version INTEGER);"), NULL},
+ {SQL("INSERT INTO version VALUES (1);"), NULL},
+ {SQL("CREATE TABLE vars ("
+ " name TEXT"
+ ", val TEXT"
+ ", intval INTEGER"
+ ", blobval BLOB"
+ ", PRIMARY KEY (name)"
+ ");"),
+ NULL},
+ {SQL("INSERT INTO vars ("
+ " name"
+ ", intval"
+ ") VALUES ("
+ " 'data_version'"
+ ", 0"
+ ");"),
+ NULL},
+ {SQL("CREATE TABLE accounts ("
+ " id INTEGER"
+ ", name TEXT"
+ ", peer_id BLOB"
+ ", opened_event_id INTEGER"
+ ", closed_event_id INTEGER"
+ ", onchain_resolved_block INTEGER"
+ ", is_wallet INTEGER"
+ ", we_opened INTEGER"
+ ", leased INTEGER"
+ ", PRIMARY KEY (id)"
+ ");"),
+ NULL},
+ {SQL("CREATE TABLE chainmoves ("
+ " created_index INTEGER"
+ ", account_id TEXT"
+ ", primary_tag TEXT"
+ ", credit_msat INTEGER"
+ ", debit_msat INTEGER"
+ ", output_msat INTEGER"
+ ", timestamp INTEGER"
+ ", blockheight INTEGER"
+ ", utxo TEXT"
+ ", payment_hash BLOB"
+ ", spending_txid BLOB"
+ ", PRIMARY KEY (created_index)"
+ ");"),
+ NULL},
+ {SQL("CREATE TABLE chainmoves_extra_tags ("
+ " row INTEGER"
+ ", arrindex INTEGER"
+ ", extra_tags TEXT)"), NULL},
+ {SQL("CREATE TABLE channelmoves ("
+ " created_index INTEGER"
+ ", account_id TEXT"
+ ", primary_tag TEXT"
+ ", credit_msat INTEGER"
+ ", debit_msat INTEGER"
+ ", fees_msat INTEGER"
+ ", payment_hash BLOB"
+ ", part_id INTEGER"
+ ", timestamp INTEGER"
+ ", PRIMARY KEY (created_index)"
+ ");"),
+ NULL},
+ {SQL("CREATE TABLE onchain_fees ("
+ "account_id TEXT"
+ ", txid BLOB"
+ ", credit INTEGER"
+ ", debit INTEGER"
+ ", currency TEXT"
+ ", timestamp INTEGER"
+ ", update_count INT"
+ ", PRIMARY KEY (account_id, txid, update_count)"
+ ");"),
+ NULL},
+ {SQL("ALTER TABLE chainmoves ADD originating_account TEXT;"), NULL},
+ {SQL("ALTER TABLE accounts ADD closed_count INTEGER DEFAULT 0;"), NULL},
+ {SQL("ALTER TABLE chainmoves ADD ignored INTEGER;"), NULL},
+ {SQL("ALTER TABLE chainmoves ADD stealable INTEGER;"), NULL},
+ {SQL("ALTER TABLE chainmoves ADD ev_desc TEXT DEFAULT NULL;"), NULL},
+ {SQL("ALTER TABLE channelmoves ADD ev_desc TEXT DEFAULT NULL;"), NULL},
+ {SQL("ALTER TABLE channelmoves ADD rebalance_id INTEGER DEFAULT NULL;"), NULL},
+ {SQL("ALTER TABLE chainmoves ADD spliced INTEGER DEFAULT 0;"), NULL},
+};
+
+#undef db_begin_transaction
+#define db_begin_transaction(db)
+#define db_commit_transaction(db)
+
+static bool db_migrate(struct plugin *p, sqlite3 *db)
+{
+ sqlite3_stmt *s;
+ for (size_t i = 0; i < ARRAY_SIZE(db_migrations); i++) {
+ int rc;
+ sqlite3_prepare_v2(db, db_migrations[i].sql, -1, &s, NULL);
+ rc = sqlite3_step(s);
+ assert(rc == SQLITE_DONE);
+ sqlite3_finalize(s);
+ }
+ return true;
+}
+
+static sqlite3 *db_setup(struct plugin *p, const char *db_dsn)
+{
+ sqlite3_stmt *stmt;
+ sqlite3 *sql;
+ int err;
+
+ err = sqlite3_open_v2(db_dsn, &sql, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);
+ assert(err == SQLITE_OK);
+
+ err = sqlite3_extended_result_codes(sql, 1);
+ assert(err == SQLITE_OK);
+
+ sqlite3_busy_timeout(sql, 60000);
+ sqlite3_prepare_v2(sql,
+ "PRAGMA foreign_keys = ON;", -1, &stmt, NULL);
+ err = sqlite3_step(stmt);
+ assert(err == SQLITE_DONE);
+ sqlite3_finalize(stmt);
+
+ db_migrate(p, sql);
+ return sql;
+}
+
+static int sqlite3_bind_outpoint(sqlite3_stmt *stmt, int pos, const struct bitcoin_outpoint *o)
+{
+ return sqlite3_bind_text(stmt, pos, fmt_bitcoin_outpoint(tmpctx, o), -1, SQLITE_TRANSIENT);
+}
+
+static int sqlite3_bind_txid(sqlite3_stmt *stmt, int pos, const struct bitcoin_txid *txid)
+{
+ return sqlite3_bind_blob(stmt, pos, txid, sizeof(*txid), SQLITE_TRANSIENT);
+}
+
+static int sqlite3_bind_hash(sqlite3_stmt *stmt, int pos, const struct sha256 *hash)
+{
+ return sqlite3_bind_blob(stmt, pos, hash, sizeof(*hash), SQLITE_TRANSIENT);
+}
+
+/* Stolen from old plugins/bkpr/recorder.c */
+static bool find_chain_event(sqlite3 *db,
+ const struct account *acct,
+ const struct bitcoin_outpoint *outpoint,
+ const struct bitcoin_txid *spending_txid,
+ const char *tag)
+
+{
+ sqlite3_stmt *stmt;
+ bool ret;
+ int rc;
+
+ if (spending_txid) {
+ sqlite3_prepare_v2(db,
+ "SELECT 1"
+ " FROM chainmoves e"
+ " LEFT OUTER JOIN accounts a"
+ " ON e.account_id = a.name"
+ " WHERE "
+ " e.spending_txid = ?"
+ " AND e.account_id = ?"
+ " AND e.utxo = ?",
+ -1, &stmt, NULL);
+ rc = sqlite3_bind_txid(stmt, 1, spending_txid);
+ assert(rc == SQLITE_OK);
+ } else {
+ sqlite3_prepare_v2(db,
+ "SELECT 1"
+ " FROM chainmoves e"
+ " LEFT OUTER JOIN accounts a"
+ " ON e.account_id = a.name"
+ " WHERE "
+ " e.primary_tag = ?"
+ " AND e.account_id = ?"
+ " AND e.utxo = ?"
+ " AND e.spending_txid IS NULL",
+ -1, &stmt, NULL);
+ rc = sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+ }
+
+ rc = sqlite3_bind_text(stmt, 2, acct->name, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_outpoint(stmt, 3, outpoint);
+ assert(rc == SQLITE_OK);
+
+ ret = (sqlite3_step(stmt) == SQLITE_ROW);
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+static bool log_chain_event(struct bkpr *bkpr,
+ const struct account *acct,
+ struct chain_event *e)
+{
+ int rc;
+ sqlite3_stmt *stmt;
+
+ /* We're responsible for de-duping chain events! */
+ if (find_chain_event(bkpr_db, acct,
+ &e->outpoint, e->spending_txid,
+ e->tag))
+ return false;
+ rc = sqlite3_prepare_v2(bkpr_db,
+ "INSERT INTO chainmoves"
+ " ("
+ " account_id"
+ ", originating_account"
+ ", primary_tag"
+ ", credit_msat"
+ ", debit_msat"
+ ", output_msat"
+ ", timestamp"
+ ", blockheight"
+ ", utxo"
+ ", payment_hash"
+ ", spending_txid"
+ ")"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
+ -1, &stmt, NULL);
+ assert(rc == SQLITE_OK);
+
+ rc = sqlite3_bind_text(stmt, 1, acct->name, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+ if (e->origin_acct)
+ sqlite3_bind_text(stmt, 2, e->origin_acct, -1, SQLITE_TRANSIENT);
+ else
+ sqlite3_bind_null(stmt, 2);
+ rc = sqlite3_bind_text(stmt, 3, e->tag, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_int64(stmt, 4, (sqlite3_int64)e->credit.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_int64(stmt, 5, (sqlite3_int64)e->debit.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_int64(stmt, 6, (sqlite3_int64)e->output_value.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_int64(stmt, 7, (sqlite3_int64)e->timestamp);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_int64(stmt, 8, (sqlite3_int64)e->blockheight);
+ assert(rc == SQLITE_OK);
+ rc = sqlite3_bind_outpoint(stmt, 9, &e->outpoint);
+ assert(rc == SQLITE_OK);
+ if (e->payment_id) {
+ rc = sqlite3_bind_hash(stmt, 10, e->payment_id);
+ } else {
+ rc = sqlite3_bind_null(stmt,10);
+ }
+ assert(rc == SQLITE_OK);
+ if (e->spending_txid) {
+ rc = sqlite3_bind_txid(stmt, 11, e->spending_txid);
+ } else {
+ rc = sqlite3_bind_null(stmt, 11);
+ }
+ assert(rc == SQLITE_OK);
+
+ /* FIXME: Put stealable and spliced into extra_tags! */
+ /* Execute */
+ rc = sqlite3_step(stmt);
+ assert(rc == SQLITE_DONE);
+
+ e->db_id = (u64)sqlite3_last_insert_rowid(bkpr_db);
+ e->acct_name = tal_strdup(e, acct->name);
+ sqlite3_finalize(stmt);
+ return true;
+}
+
+/* All chatgpt */
+static void log_channel_event(sqlite3 *db,
+ const struct account *acct,
+ struct channel_event *e)
+{
+ static const char *SQL =
+ "INSERT INTO channelmoves ("
+ " account_id, primary_tag, credit_msat, debit_msat, fees_msat, payment_hash, part_id, timestamp"
+ ") VALUES (?, ?, ?, ?, ?, ?, ?, ?);";
+
+ sqlite3_stmt *stmt;
+ int rc = sqlite3_prepare_v2(db, SQL, -1, &stmt, NULL);
+ assert(rc == SQLITE_OK);
+
+ /* 1 account_id (TEXT) */
+ rc = sqlite3_bind_text(stmt, 1, acct->name, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+
+ /* 2 tag (TEXT) */
+ rc = sqlite3_bind_text(stmt, 2, e->tag, -1, SQLITE_TRANSIENT);
+ assert(rc == SQLITE_OK);
+
+ /* 3 credit (INTEGER) */
+ rc = sqlite3_bind_int64(stmt, 3, (sqlite3_int64)e->credit.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+
+ /* 4 debit (INTEGER) */
+ rc = sqlite3_bind_int64(stmt, 4, (sqlite3_int64)e->debit.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+
+ /* 5 fees (INTEGER) */
+ rc = sqlite3_bind_int64(stmt, 5, (sqlite3_int64)e->fees.millisatoshis /* Raw: db */);
+ assert(rc == SQLITE_OK);
+
+ /* 6 payment_hash (BLOB 32) or NULL */
+ if (e->payment_id) {
+ rc = sqlite3_bind_blob(stmt, 6, (const void *)e->payment_id,
+ (int)sizeof(*e->payment_id), SQLITE_TRANSIENT);
+ } else {
+ rc = sqlite3_bind_null(stmt, 6);
+ }
+ assert(rc == SQLITE_OK);
+
+ /* 7 part_id (INTEGER) */
+ rc = sqlite3_bind_int64(stmt, 7, (sqlite3_int64)e->part_id);
+ assert(rc == SQLITE_OK);
+
+ /* 8 timestamp (INTEGER) */
+ rc = sqlite3_bind_int64(stmt, 8, (sqlite3_int64)e->timestamp);
+ assert(rc == SQLITE_OK);
+
+ rc = sqlite3_step(stmt);
+ assert(rc == SQLITE_DONE);
+
+ e->db_id = (u64)sqlite3_last_insert_rowid(db);
+ e->acct_name = tal_strdup(e, acct->name);
+
+ sqlite3_finalize(stmt);
+}
+
+struct command_result *jsonrpc_set_datastore_(struct command *cmd UNNEEDED,
+ const char *path UNNEEDED,
+ const void *value UNNEEDED,
+ int len_or_str UNNEEDED,
+ const char *mode UNNEEDED,
+ struct command_result *(*cb)(struct command *command,
+ const char *method,
+ const char *buf,
+ const jsmntok_t *result,
+ void *arg),
+ struct command_result *(*errcb)(struct command *command UNNEEDED,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg) UNNEEDED,
+ void *arg)
+
+{
+ return cb(cmd, NULL, NULL, NULL, arg);
+}
+
+struct command_result *ignore_datastore_reply(struct command *cmd,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg UNNEEDED)
+{
+ return NULL;
+}
+
+struct json_out *json_out_obj(const tal_t *ctx,
+ const char *fieldname,
+ const char *str)
+{
+ struct json_out *jout = json_out_new(ctx);
+ json_out_start(jout, NULL, '{');
+ if (str)
+ json_out_addstr(jout, fieldname, str);
+ if (taken(str))
+ tal_free(str);
+ json_out_end(jout, '}');
+ json_out_finished(jout);
+
+ return jout;
+}
+
+const jsmntok_t *jsonrpc_request_sync(const tal_t *ctx,
+ struct command *cmd,
+ const char *method,
+ const struct json_out *params TAKES,
+ const char **resp)
+{
+#if HAVE_SQLITE3
+ sqlite3_stmt *s;
+ jsmntok_t *toks;
+ jsmn_parser parser;
+ size_t len;
+ char *buf;
+ const char *p, *querystr;
+ bool complete;
+ int rc;
+
+ if (streq(method, "listdatastore")) {
+ buf = tal_fmt(ctx, "{\"datastore\": []}");
+ goto done;
+ }
+
+ assert(streq(method, "sql"));
+
+ /* Grab select query */
+ jsmn_init(&parser);
+ p = json_out_contents(params, &len);
+ toks = toks_alloc(tmpctx);
+ assert(json_parse_input(&parser, &toks, p, len, &complete));
+ assert(complete);
+ /* { "query": <querystr> } */
+ querystr = json_strdup(tmpctx, p, toks + 2);
+
+ sqlite3_prepare_v2(bkpr_db, querystr,
+ -1, &s, NULL);
+
+ /* Make JSON-style result */
+ buf = tal_fmt(ctx, "{\"rows\": [");
+ while ((rc = sqlite3_step(s)) == SQLITE_ROW) {
+ size_t num_cols = sqlite3_column_count(s);
+
+ tal_append_fmt(&buf, "[");
+ for (size_t i = 0; i < num_cols; i++) {
+ switch (sqlite3_column_type(s, i)) {
+ case SQLITE_NULL:
+ tal_append_fmt(&buf, "null");
+ break;
+ case SQLITE_INTEGER:
+ tal_append_fmt(&buf, "%"PRIu64, (u64)sqlite3_column_int64(s, i));
+ break;
+ case SQLITE_TEXT:
+ tal_append_fmt(&buf, "\"%s\"", sqlite3_column_text(s, i));
+ break;
+ case SQLITE_BLOB: {
+ const void *blob = sqlite3_column_blob(s, i);
+ int bloblen = sqlite3_column_bytes(s, i);
+ tal_append_fmt(&buf, "\"%s\"", tal_hexstr(tmpctx, blob, bloblen));
+ break;
+ }
+ default:
+ abort();
+ }
+ if (i != num_cols - 1)
+ tal_append_fmt(&buf, ",");
+ }
+ tal_append_fmt(&buf, "]");
+ }
+ assert(rc == SQLITE_DONE);
+ tal_append_fmt(&buf, "]}");
+ sqlite3_finalize(s);
+
+done:
+ jsmn_init(&parser);
+ toks = toks_alloc(ctx);
+ assert(json_parse_input(&parser, &toks, buf, strlen(buf), &complete));
+ assert(complete);
+
+ if (taken(params))
+ tal_free(params);
+ *resp = buf;
+ return toks;
+#else
+ return NULL;
+#endif
+}
+
+static char *tmp_dsn(const tal_t *ctx)
+{
+ char *dsn, *filename;
+ int fd = tmpdir_mkstemp(ctx, "lacct-db-XXXXXX", &filename);
+ if (fd == -1)
+ return NULL;
+ close(fd);
+
+ dsn = tal_fmt(ctx, "%s", filename);
+ tal_free(filename);
+
+ return dsn;
+}
+
+static struct bkpr *bkpr_setup(const tal_t *ctx)
+{
+ struct bkpr *bkpr = tal(ctx, struct bkpr);
+
+ if (bkpr_db)
+ assert(sqlite3_close(bkpr_db) == SQLITE_OK);
+
+ bkpr_db = db_setup(NULL, tmp_dsn(ctx));
+ bkpr->accounts = init_accounts(ctx, NULL);
+ bkpr->onchain_fees = init_onchain_fees(bkpr, NULL);
+ bkpr->rebalances = init_rebalances(bkpr, NULL);
+
+ /* We need to see everything */
+ bkpr->chainmoves_index = bkpr->channelmoves_index = UINT64_MAX;
+ return bkpr;
+}
+
+static bool accountseq(struct account *a1, struct account *a2)
+{
+ CHECK(streq(a1->name, a2->name));
+ CHECK((a1->peer_id != NULL) == (a2->peer_id != NULL));
+ if (a1->peer_id)
+ CHECK(node_id_eq(a1->peer_id, a2->peer_id));
+ CHECK(a1->is_wallet == a2->is_wallet);
+ CHECK(a1->we_opened == a2->we_opened);
+ CHECK(a1->leased == a2->leased);
+ CHECK(a1->onchain_resolved_block == a2->onchain_resolved_block);
+
+ CHECK((a1->open_event_db_id != NULL) == (a2->open_event_db_id != NULL));
+ if (a1->open_event_db_id)
+ CHECK(*a1->open_event_db_id == *a2->open_event_db_id);
+
+ CHECK((a1->closed_event_db_id != NULL) == (a2->closed_event_db_id != NULL));
+ if (a1->closed_event_db_id)
+ CHECK(*a1->closed_event_db_id == *a2->closed_event_db_id);
+
+ CHECK(a1->closed_count == a2->closed_count);
+
+ return true;
+}
+
+static bool channel_events_eq(struct channel_event *e1, struct channel_event *e2)
+{
+ CHECK(e1->db_id == e2->db_id);
+ CHECK(streq(e1->acct_name, e2->acct_name));
+ CHECK(streq(e1->tag, e2->tag));
+ CHECK(amount_msat_eq(e1->credit, e2->credit));
+ CHECK(amount_msat_eq(e1->debit, e2->debit));
+ CHECK(amount_msat_eq(e1->fees, e2->fees));
+ CHECK((e1->payment_id != NULL) == (e2->payment_id != NULL));
+ if (e1->payment_id)
+ CHECK(sha256_eq(e1->payment_id, e2->payment_id));
+ CHECK(e1->part_id == e2->part_id);
+ CHECK(e1->timestamp == e2->timestamp);
+
+ return true;
+}
+
+static bool chain_events_eq(struct chain_event *e1, struct chain_event *e2)
+{
+ CHECK(e1->db_id == e2->db_id);
+ CHECK(streq(e1->acct_name, e2->acct_name));
+ CHECK((e1->origin_acct != NULL) == (e2->origin_acct != NULL));
+ if (e1->origin_acct)
+ CHECK(streq(e1->origin_acct, e2->origin_acct));
+ CHECK(streq(e1->tag, e2->tag));
+ CHECK(amount_msat_eq(e1->credit, e2->credit));
+ CHECK(amount_msat_eq(e1->debit, e2->debit));
+ CHECK(amount_msat_eq(e1->output_value, e2->output_value));
+ CHECK(e1->timestamp == e2->timestamp);
+ CHECK(e1->blockheight == e2->blockheight);
+ CHECK(e1->stealable == e2->stealable);
+ CHECK(bitcoin_outpoint_eq(&e1->outpoint, &e2->outpoint));
+
+ CHECK((e1->spending_txid != NULL) == (e2->spending_txid != NULL));
+ if (e1->spending_txid)
+ CHECK(bitcoin_txid_eq(e1->spending_txid, e2->spending_txid));
+
+ CHECK((e1->payment_id != NULL) == (e2->payment_id != NULL));
+ if (e1->payment_id)
+ CHECK(sha256_eq(e1->payment_id, e2->payment_id));
+
+ CHECK(e1->splice_close == e2->splice_close);
+
+ return true;
+}
+
+static struct channel_event *make_channel_event(const tal_t *ctx,
+ char *tag,
+ struct amount_msat credit,
+ struct amount_msat debit,
+ char payment_char)
+{
+ struct channel_event *ev = tal(ctx, struct channel_event);
+
+ ev->payment_id = tal(ev, struct sha256);
+ memset(ev->payment_id, payment_char, sizeof(struct sha256));
+ ev->credit = credit;
+ ev->debit = debit;
+ ev->fees = AMOUNT_MSAT(104);
+ ev->timestamp = 1919191;
+ ev->part_id = 19;
+ ev->tag = tag;
+ return ev;
+}
+
+static struct chain_event *make_chain_event(const tal_t *ctx,
+ char *tag,
+ struct amount_msat credit,
+ struct amount_msat debit,
+ struct amount_msat output_val,
+ u32 blockheight,
+ char outpoint_char,
+ u32 outnum,
+ /* Note that '*' is magic */
+ char spend_char)
+
+
+{
+ struct chain_event *ev = tal(ctx, struct chain_event);
+
+ /* This event spends the second inserted event */
+ ev->tag = tal_fmt(ctx, "%s", tag);
+ ev->origin_acct = NULL;
+ ev->credit = credit;
+ ev->debit = debit;
+ ev->output_value = output_val;
+ ev->timestamp = 1919191;
+ ev->blockheight = blockheight;
+ ev->stealable = false;
+ ev->splice_close = false;
+ memset(&ev->outpoint.txid, outpoint_char, sizeof(struct bitcoin_txid));
+ ev->outpoint.n = outnum;
+
+ if (spend_char != '*') {
+ ev->spending_txid = tal(ctx, struct bitcoin_txid);
+ memset(ev->spending_txid, spend_char,
+ sizeof(struct bitcoin_txid));
+ } else
+ ev->spending_txid = NULL;
+
+ ev->payment_id = NULL;
+
+ return ev;
+}
+
+static bool test_onchain_fee_wallet_spend(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ struct node_id node_id, peer_id;
+ struct account *wal_acct, *ext_acct;
+ struct bitcoin_txid txid;
+ struct onchain_fee **ofs;
+ u32 blockheight = 100000;
+
+ memset(&node_id, 2, sizeof(struct node_id));
+ memset(&peer_id, 3, sizeof(struct node_id));
+
+ wal_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ wal_acct->peer_id = &node_id;
+ ext_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_EXTERNAL));
+ ext_acct->peer_id = &peer_id;
+ memset(&txid, '1', sizeof(struct bitcoin_txid));
+
+ db_begin_transaction(db);
+ account_datastore_set(NULL, wal_acct, "must-create");
+ account_datastore_set(NULL, ext_acct, "must-create");
+ db_commit_transaction(db);
+
+
+ /* Send funds to an external address
+ * tag utxo_id vout txid debits credits acct_id
+ * withdr XXXX 0 1111 1000 wallet
+ * deposit 1111 1 200 wallet
+ * deposit 1111 0 700 external
+ */
+ db_begin_transaction(db);
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "withdrawal",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(1000),
+ blockheight,
+ 'X', 0, '1'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(200),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(200),
+ blockheight,
+ '1', 1, '*'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, ext_acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(700),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(700),
+ blockheight,
+ '1', 0, '*'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+ db_commit_transaction(db);
+
+ ofs = list_chain_fees(ctx, bkpr);
+ CHECK(tal_count(ofs) == 2);
+
+ /* we expect 800, then -700 */
+ CHECK(amount_msat_eq(ofs[0]->credit, AMOUNT_MSAT(800)));
+ CHECK(amount_msat_is_zero(ofs[0]->debit));
+ CHECK(ofs[0]->update_count == 1);
+
+ CHECK(amount_msat_is_zero(ofs[1]->credit));
+ CHECK(amount_msat_eq(ofs[1]->debit, AMOUNT_MSAT(700)));
+ CHECK(ofs[1]->update_count == 2);
+
+ return true;
+}
+
+static bool test_onchain_fee_chan_close(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ struct node_id node_id, peer_id;
+ struct account *acct, *wal_acct, *ext_acct;
+ struct onchain_fee **ofs, **ofs1;
+ struct bitcoin_txid txid;
+ struct chain_event *ev;
+ enum mvt_tag *tags;
+ u32 close_output_count;
+ u32 blockheight = 100000;
+ char *err;
+
+ memset(&node_id, 2, sizeof(struct node_id));
+ memset(&peer_id, 3, sizeof(struct node_id));
+ /* to_us, to_them, 1 htlc, 2 anchors */
+ close_output_count = 5;
+
+ wal_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ wal_acct->peer_id = &node_id;
+ ext_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_EXTERNAL));
+ ext_acct->peer_id = &peer_id;
+ acct = new_account(bkpr->accounts, tal_fmt(ctx, "chan-1"));
+ acct->peer_id = &peer_id;
+
+ db_begin_transaction(db);
+ account_datastore_set(NULL, wal_acct, "must-create");
+ account_datastore_set(NULL, ext_acct, "must-create");
+ account_datastore_set(NULL, acct, "must-create");
+ db_commit_transaction(db);
+
+ /* Close a channel */
+ /* tag utxo_id vout txid debits credits acct_id
+ * close XXXX 0 1111 1000 wallet
+ * delay 1111 1 200 chan-1
+ * anchor 1111 0 30 chan-1
+ * anchor 1111 4 30 external
+ * to_wall 1111 1 2222 200 chan-1
+ * htlc_tim1111 2 600 chan-1
+ * htlc_tim1111 2 3333 600 chan-1
+ * to_them 1111 3 300 external
+ * deposit 2222 0 150 chan-1
+ * htlc_tx 3333 0 450 chan-1
+ * to_wall 3333 0 4444 450 chan-1
+ * deposit 4444 0 350 wallet
+ */
+ tags = tal_arr(ctx, enum mvt_tag, 1);
+ db_begin_transaction(db);
+ ev = make_chain_event(ctx, "channel_open",
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1660),
+ blockheight,
+ 'X', 0, '*');
+ log_chain_event(bkpr, acct, ev);
+ tags[0] = MVT_CHANNEL_OPEN;
+ maybe_update_account(NULL, acct, ev, tags, 0, NULL);
+
+ ev = make_chain_event(ctx, "channel_close",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(1660),
+ blockheight,
+ 'X', 0, '1');
+ log_chain_event(bkpr, acct, ev);
+
+ /* Update the account to have the right info! */
+ tags[0] = MVT_CHANNEL_CLOSE;
+ maybe_update_account(NULL, acct, ev, tags, close_output_count, NULL);
+
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "delayed_to_us",
+ AMOUNT_MSAT(200),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(200),
+ blockheight,
+ '1', 1, '*'));
+ memset(&txid, '1', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "anchor",
+ AMOUNT_MSAT(30),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(30),
+ blockheight,
+ '1', 0, '*'));
+ log_chain_event(bkpr, ext_acct,
+ make_chain_event(ctx, "anchor",
+ AMOUNT_MSAT(30),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(30),
+ blockheight,
+ '1', 4, '*'));
+ memset(&txid, '1', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ /* Should be no fees yet */
+ ofs = list_chain_fees(ctx, bkpr);
+ CHECK(tal_count(ofs) == 0);
+
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "htlc_timeout",
+ AMOUNT_MSAT(600),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(600),
+ blockheight,
+ '1', 2, '*'));
+
+ log_chain_event(bkpr, ext_acct,
+ make_chain_event(ctx, "to_them",
+ AMOUNT_MSAT(300),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(300),
+ blockheight,
+ '1', 3, '*'));
+
+ memset(&txid, '1', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+ db_commit_transaction(db);
+
+ /* txid 2222 */
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "to_wallet",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(200),
+ AMOUNT_MSAT(200),
+ blockheight + 1,
+ '1', 1, '2'));
+
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(150),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(150),
+ blockheight + 1,
+ '2', 0, '*'));
+ memset(&txid, '2', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+ CHECK(acct->onchain_resolved_block == 0);
+
+ assert(account_onchain_closeheight(bkpr, NULL, acct) == 0);
+ CHECK(acct->onchain_resolved_block == 0);
+ db_commit_transaction(db);
+
+ /* Expect: 1 onchain fee records, all for chan-1 */
+ ofs = list_chain_fees(ctx, bkpr);
+ ofs1 = account_get_chain_fees(tmpctx, bkpr, acct->name);
+
+ CHECK(tal_count(ofs) == tal_count(ofs1));
+ CHECK(tal_count(ofs) == 1);
+
+ /* txid 4444 */
+ db_begin_transaction(db);
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(350),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(350),
+ blockheight + 2,
+ '4', 0, '*'));
+
+ memset(&txid, '4', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ /* txid 3333 */
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "htlc_timeout",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(600),
+ AMOUNT_MSAT(600),
+ blockheight + 2,
+ '1', 2, '3'));
+
+ assert(account_onchain_closeheight(bkpr, NULL, acct) == 0);
+ CHECK(acct->onchain_resolved_block == 0);
+
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "htlc_tx",
+ AMOUNT_MSAT(450),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(450),
+ blockheight + 2,
+ '3', 0, '*'));
+
+ memset(&txid, '3', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "to_wallet",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(450),
+ AMOUNT_MSAT(450),
+ blockheight + 2,
+ '3', 0, '4'));
+
+ memset(&txid, '4', sizeof(struct bitcoin_txid));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ db_commit_transaction(db);
+
+ /* Expect: onchain fee records for tx except channel close */
+ ofs = list_chain_fees(ctx, bkpr);
+ ofs1 = account_get_chain_fees(tmpctx, bkpr, acct->name);
+
+ CHECK(tal_count(ofs) == tal_count(ofs1));
+ CHECK(tal_count(ofs) == 3);
+
+ /* Now we update the channel's onchain fees */
+ CHECK(acct->onchain_resolved_block == 0);
+ db_begin_transaction(db);
+ account_update_closeheight(NULL, acct, account_onchain_closeheight(bkpr, NULL, acct));
+ CHECK(acct->onchain_resolved_block == blockheight + 2);
+ err = update_channel_onchain_fees(ctx, NULL, bkpr, acct);
+ CHECK_MSG(!err, err);
+ db_commit_transaction(db);
+ ofs = account_get_chain_fees(tmpctx, bkpr, acct->name);
+
+ /* Expect: fees as follows
+ *
+ * chan-1, 1111, 200 (anchor outs ignored)
+ * chan-1, 2222, 50
+ * chan-1, 3333, 150
+ * chan-1, 4444, 100
+ */
+ CHECK(tal_count(ofs) == 4);
+ for (size_t i = 0; i < tal_count(ofs); i++) {
+ CHECK(streq(ofs[i]->acct_name, acct->name));
+
+ memset(&txid, '1', sizeof(struct bitcoin_txid));
+ if (bitcoin_txid_eq(&txid, &ofs[i]->txid)) {
+ CHECK(ofs[i]->update_count == 1);
+ CHECK(amount_msat_eq(ofs[i]->credit, AMOUNT_MSAT(200)));
+ CHECK(amount_msat_is_zero(ofs[i]->debit));
+ continue;
+ }
+ memset(&txid, '2', sizeof(struct bitcoin_txid));
+ if (bitcoin_txid_eq(&txid, &ofs[i]->txid)) {
+ CHECK(ofs[i]->update_count == 1);
+ CHECK(amount_msat_eq(ofs[i]->credit, AMOUNT_MSAT(50)));
+ CHECK(amount_msat_is_zero(ofs[i]->debit));
+ continue;
+ }
+ memset(&txid, '3', sizeof(struct bitcoin_txid));
+ if (bitcoin_txid_eq(&txid, &ofs[i]->txid)) {
+ CHECK(ofs[i]->update_count == 1);
+ CHECK(amount_msat_eq(ofs[i]->credit, AMOUNT_MSAT(150)));
+ CHECK(amount_msat_is_zero(ofs[i]->debit));
+ continue;
+ }
+ memset(&txid, '4', sizeof(struct bitcoin_txid));
+ if (bitcoin_txid_eq(&txid, &ofs[i]->txid)) {
+ CHECK(ofs[i]->update_count == 1);
+ CHECK(amount_msat_eq(ofs[i]->credit, AMOUNT_MSAT(100)));
+ CHECK(amount_msat_is_zero(ofs[i]->debit));
+ continue;
+ }
+
+ CHECK_MSG(false, "txid didn't match");
+ }
+
+ return true;
+}
+
+static bool test_onchain_fee_chan_open(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ struct node_id node_id, peer_id;
+ struct account *acct, *acct2, *wal_acct, *ext_acct;
+ struct bitcoin_txid txid;
+ struct onchain_fee **ofs;
+ u32 blockheight = 100000;
+
+ memset(&node_id, 2, sizeof(struct node_id));
+ memset(&peer_id, 3, sizeof(struct node_id));
+
+ wal_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ wal_acct->peer_id = &peer_id;
+ ext_acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_EXTERNAL));
+ ext_acct->peer_id = &peer_id;
+ acct = new_account(bkpr->accounts, tal_fmt(ctx, "chan-1"));
+ acct->peer_id = &peer_id;
+ acct2 = new_account(bkpr->accounts, tal_fmt(ctx, "chan-2"));
+ acct2->peer_id = &peer_id;
+
+ db_begin_transaction(db);
+ account_datastore_set(NULL, wal_acct, "must-create");
+ account_datastore_set(NULL, ext_acct, "must-create");
+ account_datastore_set(NULL, acct, "must-create");
+ account_datastore_set(NULL, acct2, "must-create");
+ db_commit_transaction(db);
+
+ /* Assumption that we rely on later */
+ CHECK(strcmp(acct->name, acct2->name) < 0);
+
+ /* Open two channels from wallet */
+ /* tag utxo_id vout txid debits credits acct_id
+ * withd XXXX 0 AAAA 1000 wallet
+ * withd YYYY 0 AAAA 3001 wallet
+ * open AAAA 0 500 chan-1
+ * open AAAA 1 1000 chan-2
+ * depo AAAA 2 2200 wallet
+ */
+ memset(&txid, 'A', sizeof(struct bitcoin_txid));
+ db_begin_transaction(db);
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "withdrawal",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(1000),
+ blockheight,
+ 'X', 0, 'A'));
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "withdrawal",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(3001),
+ AMOUNT_MSAT(3001),
+ blockheight,
+ 'Y', 0, 'A'));
+
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(500),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(500),
+ blockheight,
+ 'A', 0, '*'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, acct2,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1000),
+ blockheight,
+ 'A', 1, '*'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ log_chain_event(bkpr, wal_acct,
+ make_chain_event(ctx, "deposit",
+ AMOUNT_MSAT(2200),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(2200),
+ blockheight,
+ 'A', 2, '*'));
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+
+ maybe_update_onchain_fees(ctx, NULL, bkpr, &txid);
+ db_commit_transaction(db);
+
+ /* Expect: 5 onchain fee records, totaling to 151/150msat ea,
+ * none for wallet */
+ ofs = list_chain_fees(ctx, bkpr);
+
+ CHECK(tal_count(ofs) == 5);
+
+ struct exp_result {
+ u32 credit;
+ u32 debit;
+ u32 update_count;
+ };
+
+ struct exp_result exp_results[] = {
+ { .credit = 3501, .debit = 0, .update_count = 1 },
+ { .credit = 0, .debit = 2250, .update_count = 2 },
+ { .credit = 0, .debit = 1100, .update_count = 3 },
+ { .credit = 1250, .debit = 0, .update_count = 1 },
+ { .credit = 0, .debit = 1100, .update_count = 2 },
+ };
+
+ /* Since these are sorted on fetch,
+ * this *should* be stable */
+ for (size_t i = 0; i < tal_count(ofs); i++) {
+ CHECK(i < ARRAY_SIZE(exp_results));
+ CHECK(amount_msat_eq(ofs[i]->credit,
+ amount_msat(exp_results[i].credit)));
+ CHECK(amount_msat_eq(ofs[i]->debit,
+ amount_msat(exp_results[i].debit)));
+ CHECK(ofs[i]->update_count == exp_results[i].update_count);
+
+ CHECK(bitcoin_txid_eq(&ofs[i]->txid, &txid));
+ }
+
+ return true;
+}
+
+static bool test_channel_rebalances(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ sqlite3 *db = bkpr_db;
+ struct channel_event *ev1, *ev2, *ev3, **chan_evs;
+ struct rebalance_pair *rebals;
+ struct rebalance_htable_iter it;
+ struct account *acct1, *acct2, *acct3;
+ struct node_id peer_id;
+
+ memset(&peer_id, 3, sizeof(struct node_id));
+ acct1 = new_account(bkpr->accounts, tal_fmt(ctx, "one"));
+ acct1->peer_id = &peer_id;
+ acct2 = new_account(bkpr->accounts, tal_fmt(ctx, "two"));
+ acct2->peer_id = &peer_id;
+ acct3 = new_account(bkpr->accounts, tal_fmt(ctx, "three"));
+ acct3->peer_id = &peer_id;
+
+ db_begin_transaction(db);
+
+ account_datastore_set(NULL, acct1, "must-create");
+ account_datastore_set(NULL, acct2, "must-create");
+ account_datastore_set(NULL, acct3, "must-create");
+
+ /* Simulate a rebalance of 100msats, w/ a 12msat fee */
+ ev1 = make_channel_event(ctx, "invoice",
+ AMOUNT_MSAT(100),
+ AMOUNT_MSAT(0),
+ 'A');
+ ev1->fees = AMOUNT_MSAT(0);
+ log_channel_event(db, acct1, ev1);
+
+ ev2 = make_channel_event(ctx, "invoice",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(112),
+ 'A');
+ ev2->fees = AMOUNT_MSAT(12);
+ log_channel_event(db, acct2, ev2);
+
+ /* Third event w/ same preimage but diff amounts */
+ ev3 = make_channel_event(ctx, "invoice",
+ AMOUNT_MSAT(105),
+ AMOUNT_MSAT(0),
+ 'A');
+ log_channel_event(db, acct3, ev3);
+ db_commit_transaction(db);
+
+ db_begin_transaction(db);
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct1);
+ CHECK(tal_count(chan_evs) == 1 && !find_rebalance(bkpr, chan_evs[0]->db_id));
+
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct2);
+ CHECK(tal_count(chan_evs) == 1 && !find_rebalance(bkpr, chan_evs[0]->db_id));
+
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct3);
+ CHECK(tal_count(chan_evs) == 1 && !find_rebalance(bkpr, chan_evs[0]->db_id));
+
+ maybe_record_rebalance(NULL, bkpr, ev2);
+ CHECK(find_rebalance(bkpr, ev2->db_id) != NULL);
+
+ /* Both events should be marked as rebalance */
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct1);
+ CHECK(tal_count(chan_evs) == 1 && find_rebalance(bkpr, chan_evs[0]->db_id));
+ ev1 = chan_evs[0];
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct2);
+ CHECK(tal_count(chan_evs) == 1 && find_rebalance(bkpr, chan_evs[0]->db_id));
+ CHECK(*find_rebalance(bkpr, chan_evs[0]->db_id) == ev1->db_id);
+ CHECK(*find_rebalance(bkpr, ev1->db_id) == chan_evs[0]->db_id);
+
+ /* Third event is not a rebalance though */
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct3);
+ CHECK(tal_count(chan_evs) == 1 && !find_rebalance(bkpr, chan_evs[0]->db_id));
+
+ /* Did we get an accurate rebalances entry? */
+ assert(rebalance_htable_count(bkpr->rebalances->pairs) == 2);
+ rebals = rebalance_htable_first(bkpr->rebalances->pairs, &it);
+
+ if (rebals->pair[0] == ev1->db_id) {
+ CHECK(rebals->pair[1] == ev2->db_id);
+ } else {
+ CHECK(rebals->pair[1] == ev1->db_id);
+ CHECK(rebals->pair[0] == ev2->db_id);
+ }
+ db_commit_transaction(db);
+
+ return true;
+}
+
+static bool test_channel_event_crud(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ sqlite3 *db = bkpr_db;
+ struct node_id peer_id;
+ struct account *acct, *acct2;
+ struct channel_event *ev1, *ev2, *ev3, **chan_evs;
+
+ memset(&peer_id, 3, sizeof(struct node_id));
+
+ acct = new_account(bkpr->accounts, tal_fmt(ctx, "example"));
+ acct->peer_id = &peer_id;
+ acct2 = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ acct2->peer_id = &peer_id;
+ db_begin_transaction(db);
+ account_datastore_set(NULL, acct, "must-create");
+ account_datastore_set(NULL, acct2, "must-create");
+ db_commit_transaction(db);
+
+ ev1 = tal(ctx, struct channel_event);
+ ev1->payment_id = tal(ev1, struct sha256);
+ memset(ev1->payment_id, 'B', sizeof(struct sha256));
+ ev1->credit = AMOUNT_MSAT(100);
+ ev1->debit = AMOUNT_MSAT(102);
+ ev1->fees = AMOUNT_MSAT(104);
+ ev1->timestamp = 11111;
+ ev1->part_id = 19;
+
+ /* Passing unknown tags in should be ok */
+ ev1->tag = "hello";
+
+ ev2 = tal(ctx, struct channel_event);
+ ev2->payment_id = tal(ev2, struct sha256);
+ memset(ev2->payment_id, 'C', sizeof(struct sha256));
+ ev2->credit = AMOUNT_MSAT(200);
+ ev2->debit = AMOUNT_MSAT(202);
+ ev2->fees = AMOUNT_MSAT(204);
+ ev2->timestamp = 22222;
+ ev2->part_id = 0;
+ ev2->tag = tal_fmt(ev2, "deposit");
+
+ ev3 = tal(ctx, struct channel_event);
+ ev3->payment_id = tal(ev3, struct sha256);
+ memset(ev3->payment_id, 'D', sizeof(struct sha256));
+ ev3->credit = AMOUNT_MSAT(300);
+ ev3->debit = AMOUNT_MSAT(302);
+ ev3->fees = AMOUNT_MSAT(304);
+ ev3->timestamp = 33333;
+ ev3->part_id = 5;
+ ev3->tag = tal_fmt(ev3, "routed");
+
+ db_begin_transaction(db);
+ log_channel_event(db, acct, ev1);
+ log_channel_event(db, acct, ev2);
+
+ /* log a channel event to a different acct */
+ log_channel_event(db, acct2, ev3);
+
+ /* log a channel event without a payment id */
+ ev3->payment_id = NULL;
+ log_channel_event(db, acct2, ev3);
+
+ db_commit_transaction(db);
+
+ db_begin_transaction(db);
+ chan_evs = account_get_channel_events(ctx, bkpr, NULL, acct);
+ db_commit_transaction(db);
+
+ CHECK(streq(acct->name, chan_evs[0]->acct_name));
+ CHECK(streq(acct->name, chan_evs[1]->acct_name));
+
+ CHECK(tal_count(chan_evs) == 2);
+ channel_events_eq(ev1, chan_evs[0]);
+ channel_events_eq(ev2, chan_evs[1]);
+
+ return true;
+}
+
+static bool test_chain_event_crud(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ struct node_id peer_id;
+ struct account *acct, *acct2;
+ struct chain_event *ev1, *ev2, *ev3, **chain_evs;
+ char *name = tal_fmt(ctx, "example");
+
+ ev2 = tal(ctx, struct chain_event);
+ memset(&peer_id, 3, sizeof(struct node_id));
+
+ acct = new_account(bkpr->accounts, name);
+ acct->peer_id = &peer_id;
+ acct2 = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ acct2->peer_id = &peer_id;
+ db_begin_transaction(db);
+ account_datastore_set(NULL, acct, "must-create");
+ account_datastore_set(NULL, acct2, "must-create");
+ db_commit_transaction(db);
+
+ /* This event spends the second inserted event */
+ ev1 = tal(ctx, struct chain_event);
+ ev1->tag = tal_fmt(ev1, "withdrawal");
+ ev1->origin_acct = NULL;
+ ev1->credit = AMOUNT_MSAT(100);
+ ev1->debit = AMOUNT_MSAT(102);
+ ev1->output_value = AMOUNT_MSAT(104);
+ ev1->timestamp = 1919191;
+ ev1->blockheight = 1919191;
+ ev1->stealable = false;
+ ev1->splice_close = false;
+ memset(&ev1->outpoint.txid, 'D', sizeof(struct bitcoin_txid));
+ ev1->outpoint.n = 1;
+ ev1->spending_txid = tal(ctx, struct bitcoin_txid);
+ memset(ev1->spending_txid, 'C', sizeof(struct bitcoin_txid));
+ ev1->payment_id = NULL;
+
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct, ev1);
+ db_commit_transaction(db);
+
+ ev2->tag = tal_fmt(ctx, "deposit");
+ ev2->origin_acct = tal_fmt(ctx, ACCOUNT_NAME_WALLET);
+ ev2->credit = AMOUNT_MSAT(200);
+ ev2->debit = AMOUNT_MSAT(202);
+ ev2->output_value = AMOUNT_MSAT(104);
+ ev2->timestamp = 1919191;
+ ev2->blockheight = 1919191;
+ ev2->stealable = false;
+ ev2->splice_close = false;
+ memset(&ev2->outpoint.txid, 'D', sizeof(struct bitcoin_txid));
+ ev2->outpoint.n = 1;
+ ev2->spending_txid = NULL;
+ ev2->payment_id = tal(ctx, struct sha256);
+ memset(ev2->payment_id, 'B', sizeof(struct sha256));
+
+ /* Dummy event, logged to separate account */
+ ev3 = tal(ctx, struct chain_event);
+ ev3->tag = tal_fmt(ev3, "deposit");
+ ev3->origin_acct = NULL;
+ ev3->credit = AMOUNT_MSAT(300);
+ ev3->debit = AMOUNT_MSAT(302);
+ ev3->output_value = AMOUNT_MSAT(304);
+ ev3->timestamp = 3939393;
+ ev3->blockheight = 3939393;
+ ev3->stealable = false;
+ ev3->splice_close = false;
+ memset(&ev3->outpoint.txid, 'E', sizeof(struct bitcoin_txid));
+ ev3->outpoint.n = 1;
+ ev3->spending_txid = tal(ctx, struct bitcoin_txid);
+ memset(ev3->spending_txid, 'D', sizeof(struct bitcoin_txid));
+ ev3->payment_id = NULL;
+
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct, ev2);
+
+ /* log new event to a different account.. */
+ log_chain_event(bkpr, acct2, ev3);
+ db_commit_transaction(db);
+
+ /* Try to add an already exiting event */
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct, ev2);
+ db_commit_transaction(db);
+
+ /* Ok now we ge the list, there should only be two */
+ db_begin_transaction(db);
+ chain_evs = account_get_chain_events(ctx, bkpr, NULL, acct);
+ db_commit_transaction(db);
+ CHECK(tal_count(chain_evs) == 2);
+
+ CHECK(streq(acct->name, chain_evs[0]->acct_name));
+ CHECK(streq(acct->name, chain_evs[1]->acct_name));
+ chain_events_eq(ev1, chain_evs[0]);
+ chain_events_eq(ev2, chain_evs[1]);
+
+ /* Now insert a utxo create and spend, in that order */
+ ev1->db_id = 0;
+ memset(&ev1->outpoint.txid, 'A', sizeof(struct bitcoin_txid));
+ ev1->outpoint.n = 10;
+ ev1->spending_txid = tal_free(ev1->spending_txid);
+
+ ev2->db_id = 0;
+ memset(&ev2->outpoint.txid, 'A', sizeof(struct bitcoin_txid));
+ ev2->outpoint.n = 10;
+ ev2->spending_txid = tal(ctx, struct bitcoin_txid);
+ memset(ev2->spending_txid, 'B', sizeof(struct bitcoin_txid));
+
+
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct, ev1);
+ log_chain_event(bkpr, acct, ev2);
+ chain_evs = account_get_chain_events(ctx, bkpr, NULL, acct);
+ db_commit_transaction(db);
+
+ /* There should be four now */
+ CHECK(tal_count(chain_evs) == 4);
+
+ chain_events_eq(ev1, chain_evs[2]);
+ chain_events_eq(ev2, chain_evs[3]);
+
+ return true;
+}
+
+struct acct_balance {
+ struct amount_msat credit;
+ struct amount_msat debit;
+ struct amount_msat balance;
+};
+
+static bool account_get_balance(struct bkpr *bkpr,
+ const char *acct_name,
+ struct acct_balance *bal)
+{
+ account_get_credit_debit(bkpr, NULL, acct_name,
+ &bal->credit, &bal->debit);
+
+ return amount_msat_sub(&bal->balance, bal->credit, bal->debit);
+}
+
+static bool test_account_balances(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ sqlite3 *db = bkpr_db;
+ struct node_id peer_id;
+ struct account *acct, *acct2;
+ struct chain_event *ev1;
+ struct acct_balance balance;
+ bool ok;
+
+ memset(&peer_id, 3, sizeof(struct node_id));
+
+ acct = new_account(bkpr->accounts, tal_fmt(ctx, "example"));
+ acct->peer_id = &peer_id;
+ acct2 = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ acct2->peer_id = &peer_id;
+
+ db_begin_transaction(db);
+ /* Check that account does not exist yet */
+ ok = account_get_balance(bkpr, acct->name, &balance);
+ CHECK(ok);
+
+ account_datastore_set(NULL, acct, "must-create");
+ account_datastore_set(NULL, acct2, "must-create");
+
+ /* +1000btc */
+ log_chain_event(bkpr, acct,
+ make_chain_event(ctx, "one",
+ AMOUNT_MSAT(1000),
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(1000),
+ 1019,
+ 'A', 1, '*'));
+ ev1 = make_chain_event(ctx, "two",
+ AMOUNT_MSAT(0), AMOUNT_MSAT(999),
+ AMOUNT_MSAT(999),
+ 1020, 'A', 2, '*');
+
+ /* -999btc */
+ log_chain_event(bkpr, acct, ev1);
+
+ /* -440btc */
+ log_channel_event(db, acct,
+ make_channel_event(ctx, "chan",
+ AMOUNT_MSAT(0),
+ AMOUNT_MSAT(440),
+ 'C'));
+
+ /* 500btc */
+ log_channel_event(db, acct,
+ make_channel_event(ctx, "chan",
+ AMOUNT_MSAT(500),
+ AMOUNT_MSAT(0),
+ 'D'));
+
+ ok = account_get_balance(bkpr, acct->name, &balance);
+ CHECK(ok);
+ db_commit_transaction(db);
+
+ CHECK(amount_msat_eq(balance.balance, AMOUNT_MSAT(500 - 440 + 1)));
+
+ /* Should error if account balance is negative */
+ db_begin_transaction(db);
+ /* -5001btc */
+ ev1 = make_chain_event(ctx, "two",
+ AMOUNT_MSAT(0), AMOUNT_MSAT(5001),
+ AMOUNT_MSAT(5001), 2020,
+ 'A', 4, '*');
+ log_chain_event(bkpr, acct, ev1);
+
+ ok = account_get_balance(bkpr, acct->name, &balance);
+ CHECK(!ok);
+ db_commit_transaction(db);
+
+ return true;
+}
+
+static bool test_account_crud(const tal_t *ctx)
+{
+ struct bkpr *bkpr = bkpr_setup(ctx);
+ struct node_id *peer_id;
+ struct account *acct, *acct2, **acct_list;
+ struct chain_event *ev1;
+ enum mvt_tag *tags;
+ char *name = tal_fmt(ctx, "example");
+
+ peer_id = tal(ctx, struct node_id);
+ memset(peer_id, 3, sizeof(struct node_id));
+
+ acct = new_account(bkpr->accounts, name);
+ CHECK(!acct->is_wallet);
+
+ db_begin_transaction(db);
+ account_datastore_set(NULL, acct, "must-create");
+ db_commit_transaction(db);
+
+ acct_list = list_accounts(ctx, bkpr);
+ CHECK(tal_count(acct_list) == 1);
+ accountseq(acct_list[0], acct);
+
+ acct = new_account(bkpr->accounts, tal_fmt(ctx, ACCOUNT_NAME_WALLET));
+ CHECK(acct->is_wallet);
+
+ db_begin_transaction(db);
+ account_datastore_set(NULL, acct, "must-create");
+ db_commit_transaction(db);
+
+ acct_list = list_accounts(ctx, bkpr);
+ CHECK(tal_count(acct_list) == 2);
+
+ /* Can we find an account ok? */
+ acct2 = find_account(bkpr, ACCOUNT_NAME_WALLET);
+ accountseq(acct, acct2);
+
+ /* Will we update an account's properties
+ * correctly, given an event and tag list? */
+ ev1 = tal(ctx, struct chain_event);
+ ev1->tag = tal_fmt(ctx, "withdrawal");
+ ev1->origin_acct = NULL;
+ ev1->credit = AMOUNT_MSAT(100);
+ ev1->debit = AMOUNT_MSAT(102);
+ ev1->output_value = AMOUNT_MSAT(104);
+ ev1->timestamp = 1919191;
+ ev1->blockheight = 1919191;
+ ev1->stealable = false;
+ ev1->splice_close = false;
+ memset(&ev1->outpoint.txid, 'D', sizeof(struct bitcoin_txid));
+ ev1->outpoint.n = 1;
+ ev1->spending_txid = tal(ctx, struct bitcoin_txid);
+ memset(ev1->spending_txid, 'C', sizeof(struct bitcoin_txid));
+ ev1->payment_id = NULL;
+
+ db_begin_transaction(db);
+ log_chain_event(bkpr, acct, ev1);
+
+ tags = tal_arr(ctx, enum mvt_tag, 2);
+
+ /* should not update the account info */
+ tags[0] = MVT_PUSHED;
+ tags[1] = MVT_PENALTY;
+ maybe_update_account(NULL, acct, ev1, tags, 0, peer_id);
+ acct2 = find_account(bkpr, ACCOUNT_NAME_WALLET);
+ accountseq(acct, acct2);
+
+ /* channel_open -> open event db updated */
+ CHECK(!acct->leased);
+ CHECK(acct->open_event_db_id == NULL);
+ tags[0] = MVT_CHANNEL_OPEN;
+ tags[1] = MVT_LEASED;
+ maybe_update_account(NULL, acct, ev1, tags, 2, peer_id);
+ acct2 = find_account(bkpr, ACCOUNT_NAME_WALLET);
+ accountseq(acct, acct2);
+ CHECK(acct->leased);
+ CHECK(acct->open_event_db_id != NULL);
+ CHECK(acct->closed_count == 2);
+
+ tags[0] = MVT_CHANNEL_CLOSE;
+ tags[1] = MVT_OPENER;
+ CHECK(acct->closed_event_db_id == NULL);
+ CHECK(!acct->we_opened);
+ maybe_update_account(NULL, acct, ev1, tags, 0, NULL);
+ acct2 = find_account(bkpr, ACCOUNT_NAME_WALLET);
+ accountseq(acct, acct2);
+ CHECK(acct->closed_event_db_id != NULL);
+ CHECK(acct->we_opened);
+
+ db_commit_transaction(db);
+
+ return true;
+}
+
+int main(int argc, char *argv[])
+{
+ bool ok = true;
+
+ common_setup(argv[0]);
+
+ if (HAVE_SQLITE3) {
+ ok &= test_account_crud(tmpctx);
+ ok &= test_channel_event_crud(tmpctx);
+ ok &= test_chain_event_crud(tmpctx);
+ ok &= test_account_balances(tmpctx);
+ ok &= test_onchain_fee_chan_close(tmpctx);
+ ok &= test_onchain_fee_chan_open(tmpctx);
+ ok &= test_channel_rebalances(tmpctx);
+ ok &= test_onchain_fee_wallet_spend(tmpctx);
+ sqlite3_close(bkpr_db);
+ }
+
+ common_shutdown();
+ trace_cleanup();
+ return !ok;
+}
diff --git a/plugins/bkpr/test/run-sql.c b/plugins/bkpr/test/run-sql.c
index 595661ae..8be5859a 100644
--- a/plugins/bkpr/test/run-sql.c
+++ b/plugins/bkpr/test/run-sql.c
@@ -69,60 +69,6 @@ bool json_filter_ok(const struct json_filter *filter UNNEEDED, const char *membe
/* Generated stub for json_filter_up */
bool json_filter_up(struct json_filter **filter UNNEEDED)
{ fprintf(stderr, "json_filter_up called!\n"); abort(); }
-/* Generated stub for json_scan */
-const char *json_scan(const tal_t *ctx UNNEEDED,
- const char *buffer UNNEEDED,
- const jsmntok_t *tok UNNEEDED,
- const char *guide UNNEEDED,
- ...)
-{ fprintf(stderr, "json_scan called!\n"); abort(); }
-/* Generated stub for json_scanv */
-const char *json_scanv(const tal_t *ctx UNNEEDED,
- const char *buffer UNNEEDED,
- const jsmntok_t *tok UNNEEDED,
- const char *guide UNNEEDED,
- va_list ap UNNEEDED)
-{ fprintf(stderr, "json_scanv called!\n"); abort(); }
-/* Generated stub for json_to_int */
-bool json_to_int(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, int *num UNNEEDED)
-{ fprintf(stderr, "json_to_int called!\n"); abort(); }
-/* Generated stub for json_to_msat */
-bool json_to_msat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- struct amount_msat *msat UNNEEDED)
-{ fprintf(stderr, "json_to_msat called!\n"); abort(); }
-/* Generated stub for json_to_node_id */
-bool json_to_node_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- struct node_id *id UNNEEDED)
-{ fprintf(stderr, "json_to_node_id called!\n"); abort(); }
-/* Generated stub for json_to_number */
-bool json_to_number(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- unsigned int *num UNNEEDED)
-{ fprintf(stderr, "json_to_number called!\n"); abort(); }
-/* Generated stub for json_to_outpoint */
-bool json_to_outpoint(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- struct bitcoin_outpoint *op UNNEEDED)
-{ fprintf(stderr, "json_to_outpoint called!\n"); abort(); }
-/* Generated stub for json_to_secret */
-bool json_to_secret(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct secret *dest UNNEEDED)
-{ fprintf(stderr, "json_to_secret called!\n"); abort(); }
-/* Generated stub for json_to_sha256 */
-bool json_to_sha256(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, struct sha256 *dest UNNEEDED)
-{ fprintf(stderr, "json_to_sha256 called!\n"); abort(); }
-/* Generated stub for json_to_short_channel_id */
-bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- struct short_channel_id *scid UNNEEDED)
-{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); }
-/* Generated stub for json_to_txid */
-bool json_to_txid(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- struct bitcoin_txid *txid UNNEEDED)
-{ fprintf(stderr, "json_to_txid called!\n"); abort(); }
-/* Generated stub for json_to_u16 */
-bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
- uint16_t *num UNNEEDED)
-{ fprintf(stderr, "json_to_u16 called!\n"); abort(); }
-/* Generated stub for json_tok_bin_from_hex */
-u8 *json_tok_bin_from_hex(const tal_t *ctx UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
-{ fprintf(stderr, "json_tok_bin_from_hex called!\n"); abort(); }
/* Generated stub for last_fee_state */
enum htlc_state last_fee_state(enum side opener UNNEEDED)
{ fprintf(stderr, "last_fee_state called!\n"); abort(); }
Why this scored 13/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.