Files
palladum-lightning/plugins/bkpr/test/run-recorder.c
Rusty Russell f8a44d911d bkpr: restore run-recorder.
This requires us to turn "sql" calls into calls to a local db, which
means pulling in a lot of infrastructure.  But it's possible.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-08-19 13:37:50 +09:30

1678 lines
49 KiB
C

#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;
}