From b08ccfec1e2c05c7c1b305b36bd0be795556178f Mon Sep 17 00:00:00 2001 From: niftynei Date: Tue, 19 Jul 2022 14:39:26 +0930 Subject: [PATCH] bookkeeper: initial crud (no tests) --- plugins/bkpr/Makefile | 16 +- plugins/bkpr/account.c | 23 ++ plugins/bkpr/account.h | 43 +++ plugins/bkpr/bookkeeper.c | 109 ++++--- plugins/bkpr/chain_event.h | 50 ++++ plugins/bkpr/channel_event.h | 43 +++ plugins/bkpr/db.c | 4 +- plugins/bkpr/onchain_fee.h | 25 ++ plugins/bkpr/recorder.c | 545 +++++++++++++++++++++++++++++++++++ plugins/bkpr/recorder.h | 56 ++++ 10 files changed, 871 insertions(+), 43 deletions(-) create mode 100644 plugins/bkpr/account.c create mode 100644 plugins/bkpr/account.h create mode 100644 plugins/bkpr/chain_event.h create mode 100644 plugins/bkpr/channel_event.h create mode 100644 plugins/bkpr/onchain_fee.h create mode 100644 plugins/bkpr/recorder.c create mode 100644 plugins/bkpr/recorder.h diff --git a/plugins/bkpr/Makefile b/plugins/bkpr/Makefile index 075f0a7f3..e07686315 100644 --- a/plugins/bkpr/Makefile +++ b/plugins/bkpr/Makefile @@ -1,15 +1,24 @@ #! /usr/bin/make BOOKKEEPER_PLUGIN_SRC := \ + plugins/bkpr/account.c \ plugins/bkpr/bookkeeper.c \ - plugins/bkpr/db.c + plugins/bkpr/db.c \ + plugins/bkpr/recorder.c BOOKKEEPER_DB_QUERIES := \ plugins/bkpr/db_sqlite3_sqlgen.c \ plugins/bkpr/db_postgres_sqlgen.c BOOKKEEPER_SRC := $(BOOKKEEPER_PLUGIN_SRC) $(BOOKKEEPER_DB_QUERIES) -BOOKKEEPER_HEADER := plugins/bkpr/db.h +BOOKKEEPER_HEADER := \ + plugins/bkpr/account.h \ + plugins/bkpr/chain_event.h \ + plugins/bkpr/channel_event.h \ + plugins/bkpr/db.h \ + plugins/bkpr/onchain_fee.h \ + plugins/bkpr/recorder.h + BOOKKEEPER_OBJS := $(BOOKKEEPER_SRC:.c=.o) $(BOOKKEEPER_OBJS): $(PLUGIN_LIB_HEADER) $(BOOKKEEPER_HEADER) @@ -23,7 +32,8 @@ plugins/bookkeeper: bitcoin/chainparams.o common/coin_mvt.o $(BOOKKEEPER_OBJS) $ # The following files contain SQL-annotated statements that we need to extact BOOKKEEPER_SQL_FILES := \ $(DB_SQL_FILES) \ - plugins/bkpr/db.c + plugins/bkpr/db.c \ + plugins/bkpr/recorder.c plugins/bkpr/statements_gettextgen.po: $(BOOKKEEPER_SQL_FILES) $(FORCE) @if $(call SHA256STAMP_CHANGED_ALL); then \ diff --git a/plugins/bkpr/account.c b/plugins/bkpr/account.c new file mode 100644 index 000000000..c97108729 --- /dev/null +++ b/plugins/bkpr/account.c @@ -0,0 +1,23 @@ +#include "config.h" + +#include +#include +#include + +struct account *new_account(const tal_t *ctx, + const char *name STEALS, + struct node_id *peer_id) +{ + struct account *a = tal(ctx, struct account); + + a->name = tal_steal(a, name); + a->peer_id = peer_id; + a->is_wallet = streq(a->name, WALLET); + a->we_opened = false; + a->leased = false; + a->onchain_resolved_block = 0; + a->open_event_db_id = NULL; + a->closed_event_db_id = NULL; + + return a; +} diff --git a/plugins/bkpr/account.h b/plugins/bkpr/account.h new file mode 100644 index 000000000..f73d8ca72 --- /dev/null +++ b/plugins/bkpr/account.h @@ -0,0 +1,43 @@ +#ifndef LIGHTNING_PLUGINS_BKPR_ACCOUNT_H +#define LIGHTNING_PLUGINS_BKPR_ACCOUNT_H + +#include "config.h" +#include + +struct node_id; + +struct account { + + /* Id of this account in the database */ + u64 db_id; + + /* Name of account, typically channel id */ + const char *name; + + /* Peer we have this account with (NULL if not a channel) */ + struct node_id *peer_id; + + /* Is this our internal wallet account? */ + bool is_wallet; + + /* Is this an account we initiated open for? */ + bool we_opened; + + /* Was any portion of this account's funds leased? */ + bool leased; + + /* Block account was totally resolved at */ + u64 onchain_resolved_block; + + /* db_id of chain event that opened this account */ + u64 *open_event_db_id; + + /* db_id of chain event that closed this account */ + u64 *closed_event_db_id; +}; + +/* Get a new account */ +struct account *new_account(const tal_t *ctx, + const char *name STEALS, + struct node_id *peer_id); +#endif /* LIGHTNING_PLUGINS_BKPR_ACCOUNT_H */ diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index 7b04ed61f..c51831c93 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -3,9 +3,13 @@ #include #include #include -#include #include +#include #include +#include +#include +#include +#include #include #define CHAIN_MOVE "chain_mvt" @@ -42,7 +46,6 @@ static struct command_result *json_balance_snapshot(struct command *cmd, { const char *err; size_t i; - struct node_id node_id; u32 blockheight; u64 timestamp; struct account_snap *snaps; @@ -56,10 +59,8 @@ static struct command_result *json_balance_snapshot(struct command *cmd, json_tok_full(buf, params)); err = json_scan(cmd, buf, snap_tok, - "{node_id:%" - ",blockheight:%" + "{blockheight:%" ",timestamp:%}", - JSON_SCAN(json_to_node_id, &node_id), JSON_SCAN(json_to_number, &blockheight), JSON_SCAN(json_to_u64, ×tamp)); @@ -69,11 +70,6 @@ static struct command_result *json_balance_snapshot(struct command *cmd, err, json_tok_full_len(params), json_tok_full(buf, params)); - plugin_log(cmd->plugin, LOG_DBG, "balances for node %s at %d" - " (%"PRIu64")", - type_to_string(tmpctx, struct node_id, &node_id), - blockheight, timestamp); - accounts_tok = json_get_member(buf, snap_tok, "accounts"); if (accounts_tok == NULL || accounts_tok->type != JSMN_ARRAY) plugin_err(cmd->plugin, @@ -113,19 +109,17 @@ static struct command_result *json_balance_snapshot(struct command *cmd, static const char *parse_and_log_chain_move(struct command *cmd, const char *buf, const jsmntok_t *params, - const struct node_id *node_id, const char *acct_name STEALS, const struct amount_msat credit, const struct amount_msat debit, const char *coin_type STEALS, - const u32 timestamp, + const u64 timestamp, const enum mvt_tag *tags) { - struct bitcoin_outpoint outpt; - static struct amount_msat output_value; - struct sha256 *payment_hash = tal(tmpctx, struct sha256); - struct bitcoin_txid *spending_txid = tal(tmpctx, struct bitcoin_txid); - u32 blockheight; + struct chain_event *e = tal(cmd, struct chain_event); + struct sha256 *payment_hash = tal(cmd, struct sha256); + struct bitcoin_txid *spending_txid = tal(cmd, struct bitcoin_txid); + struct account *acct; const char *err; /* Fields we expect on *every* chain movement */ @@ -136,10 +130,10 @@ static const char *parse_and_log_chain_move(struct command *cmd, ",output_msat:%" ",blockheight:%" "}}", - JSON_SCAN(json_to_txid, &outpt.txid), - JSON_SCAN(json_to_number, &outpt.n), - JSON_SCAN(json_to_msat, &output_value), - JSON_SCAN(json_to_number, &blockheight)); + JSON_SCAN(json_to_txid, &e->outpoint.txid), + JSON_SCAN(json_to_number, &e->outpoint.n), + JSON_SCAN(json_to_msat, &e->output_value), + JSON_SCAN(json_to_number, &e->blockheight)); if (err) return err; @@ -154,6 +148,8 @@ static const char *parse_and_log_chain_move(struct command *cmd, if (err) spending_txid = tal_free(spending_txid); + e->spending_txid = tal_steal(e, spending_txid); + /* Now try to get out the optional parts */ err = json_scan(tmpctx, buf, params, "{coin_movement:" @@ -164,25 +160,49 @@ static const char *parse_and_log_chain_move(struct command *cmd, if (err) payment_hash = tal_free(payment_hash); - // FIXME: enter into database + e->payment_id = tal_steal(e, payment_hash); + + e->credit = credit; + e->debit = debit; + e->currency = tal_steal(e, coin_type); + e->timestamp = timestamp; + e->tag = mvt_tag_str(tags[0]); + + db_begin_transaction(db); + acct = find_account(cmd, db, acct_name); + + if (!acct) { + /* FIXME: lookup the peer id for this channel! */ + acct = new_account(cmd, acct_name, NULL); + account_add(db, acct); + } + + log_chain_event(db, acct, e); + + /* This event *might* have implications for account; + * update as necessary */ + maybe_update_account(db, acct, e, tags); + + /* Can we calculate any onchain fees now? */ + + /* FIXME: maybe mark channel as 'onchain_resolved' */ + db_commit_transaction(db); + return NULL; } static const char *parse_and_log_channel_move(struct command *cmd, const char *buf, const jsmntok_t *params, - const struct node_id *node_id, const char *acct_name STEALS, const struct amount_msat credit, const struct amount_msat debit, const char *coin_type STEALS, - const u32 timestamp, + const u64 timestamp, const enum mvt_tag *tags) { - struct sha256 payment_hash; - u64 part_id; - struct amount_msat fees; - + struct channel_event *e = tal(cmd, struct channel_event); + struct account *acct; const char *err; err = json_scan(tmpctx, buf, params, @@ -190,8 +210,8 @@ static const char *parse_and_log_channel_move(struct command *cmd, "{payment_hash:%" ",fees:%" "}}", - JSON_SCAN(json_to_sha256, &payment_hash), - JSON_SCAN(json_to_msat, &fees)); + JSON_SCAN(json_to_sha256, &e->payment_id), + JSON_SCAN(json_to_msat, &e->fees)); if (err) return err; @@ -199,11 +219,27 @@ static const char *parse_and_log_channel_move(struct command *cmd, err = json_scan(tmpctx, buf, params, "{coin_movement:" "{part_id:%}}", - JSON_SCAN(json_to_u64, &part_id)); + JSON_SCAN(json_to_number, &e->part_id)); if (err) - part_id = 0; + e->part_id = 0; - // FIXME: enter into database? + e->credit = credit; + e->debit = debit; + e->currency = tal_steal(e, coin_type); + e->timestamp = timestamp; + e->tag = mvt_tag_str(tags[0]); + + /* Go find the account for this event */ + db_begin_transaction(db); + acct = find_account(cmd, db, acct_name); + if (!acct) + plugin_err(cmd->plugin, + "Received channel event," + " but no account exists %s", + acct_name); + + log_channel_event(db, acct, e); + db_commit_transaction(db); return NULL; } @@ -234,7 +270,6 @@ static struct command_result * json_coin_moved(struct command *cmd, const jsmntok_t *params) { const char *err, *mvt_type, *acct_name, *coin_type; - struct node_id node_id; u32 version; u64 timestamp; struct amount_msat credit, debit; @@ -243,7 +278,6 @@ static struct command_result * json_coin_moved(struct command *cmd, err = json_scan(tmpctx, buf, params, "{coin_movement:" "{version:%" - ",node_id:%" ",type:%" ",account_id:%" ",credit_msat:%" @@ -252,7 +286,6 @@ static struct command_result * json_coin_moved(struct command *cmd, ",timestamp:%" "}}", JSON_SCAN(json_to_number, &version), - JSON_SCAN(json_to_node_id, &node_id), JSON_SCAN_TAL(tmpctx, json_strdup, &mvt_type), JSON_SCAN_TAL(tmpctx, json_strdup, &acct_name), JSON_SCAN(json_to_msat, &credit), @@ -285,13 +318,13 @@ static struct command_result * json_coin_moved(struct command *cmd, mvt_type, timestamp); if (streq(mvt_type, CHAIN_MOVE)) - err = parse_and_log_chain_move(cmd, buf, params, &node_id, + err = parse_and_log_chain_move(cmd, buf, params, acct_name, credit, debit, coin_type, timestamp, tags); else { assert(streq(mvt_type, CHANNEL_MOVE)); - err = parse_and_log_channel_move(cmd, buf, params, &node_id, + err = parse_and_log_channel_move(cmd, buf, params, acct_name, credit, debit, coin_type, timestamp, tags); diff --git a/plugins/bkpr/chain_event.h b/plugins/bkpr/chain_event.h new file mode 100644 index 000000000..764c85410 --- /dev/null +++ b/plugins/bkpr/chain_event.h @@ -0,0 +1,50 @@ +#ifndef LIGHTNING_PLUGINS_BKPR_CHAIN_EVENT_H +#define LIGHTNING_PLUGINS_BKPR_CHAIN_EVENT_H + +#include "config.h" +#include + +struct amount_msat; +struct bitcoin_outpoint; +struct bitcoin_txid; + +struct chain_event { + + /* Id of this chain event in the database */ + u64 db_id; + + /* db_id of account this event belongs to */ + u64 acct_db_id; + + /* Tag describing the event */ + const char *tag; + + /* Amount we received in this event */ + struct amount_msat credit; + + /* Amount we paid in this event */ + struct amount_msat debit; + + /* Total 'amount' of output on this chain event */ + struct amount_msat output_value; + + /* What token are the credit/debits? */ + const char *currency; + + /* What time did the event happen */ + u64 timestamp; + + /* What block did the event happen */ + u32 blockheight; + + /* What txo did this event concern */ + struct bitcoin_outpoint outpoint; + + /* What tx was the outpoint spent in (if spent) */ + struct bitcoin_txid *spending_txid; + + /* Sometimes chain events resolve payments */ + struct sha256 *payment_id; +}; + +#endif /* LIGHTNING_PLUGINS_BKPR_CHAIN_EVENT_H */ diff --git a/plugins/bkpr/channel_event.h b/plugins/bkpr/channel_event.h new file mode 100644 index 000000000..d6a9533fc --- /dev/null +++ b/plugins/bkpr/channel_event.h @@ -0,0 +1,43 @@ +#ifndef LIGHTNING_PLUGINS_BKPR_CHANNEL_EVENT_H +#define LIGHTNING_PLUGINS_BKPR_CHANNEL_EVENT_H + +#include "config.h" +#include + +struct amount_msat; +struct sha256; + +struct channel_event { + + /* Id of this chain event in the database */ + u64 db_id; + + /* db_id of account this event belongs to */ + u64 acct_db_id; + + /* Tag describing the event */ + const char *tag; + + /* Amount we received in this event */ + struct amount_msat credit; + + /* Amount we paid in this event */ + struct amount_msat debit; + + /* Total 'fees' related to this channel event */ + struct amount_msat fees; + + /* What token are the credit/debits? */ + const char *currency; + + /* Payment identifier (typically the preimage hash) */ + struct sha256 payment_id; + + /* Some payments share a payment_id, and are differentiable via id */ + u32 part_id; + + /* What time did the event happen */ + u64 timestamp; +}; + +#endif /* LIGHTNING_PLUGINS_BKPR_CHANNEL_EVENT_H */ diff --git a/plugins/bkpr/db.c b/plugins/bkpr/db.c index 8fce4f398..0b44de609 100644 --- a/plugins/bkpr/db.c +++ b/plugins/bkpr/db.c @@ -45,8 +45,6 @@ static struct migration db_migrations[] = { ", is_wallet INTEGER" ", we_opened INTEGER" ", leased INTEGER" - ", last_updated_ts BIGINT" - ", last_updated_block INTEGER" ", PRIMARY KEY (id)" ");"), NULL}, @@ -62,6 +60,7 @@ static struct migration db_migrations[] = { ", blockheight INTEGER" ", utxo_txid BLOB" ", outnum INTEGER" + ", payment_id BLOB" ", spending_txid BLOB" ", PRIMARY KEY (id)" ");"), @@ -84,6 +83,7 @@ static struct migration db_migrations[] = { "account_id BIGINT REFERENCES accounts(id)" ", txid BLOB" ", amount BIGINT" + ", currency TEXT" ", PRIMARY KEY (account_id, txid)" ");"), NULL}, diff --git a/plugins/bkpr/onchain_fee.h b/plugins/bkpr/onchain_fee.h new file mode 100644 index 000000000..3b2085abb --- /dev/null +++ b/plugins/bkpr/onchain_fee.h @@ -0,0 +1,25 @@ +#ifndef LIGHTNING_PLUGINS_BKPR_ONCHAIN_FEE_H +#define LIGHTNING_PLUGINS_BKPR_ONCHAIN_FEE_H + +#include "config.h" +#include + +struct amount_msat; +struct bitcoin_txid; + +struct onchain_fee { + + /* db_id of account this event belongs to */ + u64 acct_db_id; + + /* Transaction that we're recording fees for */ + struct bitcoin_txid txid; + + /* Total amount of onchain fees we paid for this txid */ + struct amount_msat amount; + + /* What token are fees? */ + char *currency; +}; + +#endif /* LIGHTNING_PLUGINS_BKPR_ONCHAIN_FEE_H */ diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c new file mode 100644 index 000000000..6a1f01605 --- /dev/null +++ b/plugins/bkpr/recorder.c @@ -0,0 +1,545 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct chain_event *stmt2chain_event(const tal_t *ctx, struct db_stmt *stmt) +{ + struct chain_event *e = tal(ctx, struct chain_event); + e->db_id = db_col_u64(stmt, "id"); + e->acct_db_id = db_col_u64(stmt, "account_id"); + + e->tag = db_col_strdup(e, stmt, "tag"); + + db_col_amount_msat(stmt, "credit", &e->credit); + db_col_amount_msat(stmt, "debit", &e->debit); + db_col_amount_msat(stmt, "output_value", &e->output_value); + + e->currency = db_col_strdup(e, stmt, "currency"); + e->timestamp = db_col_u64(stmt, "timestamp"); + e->blockheight = db_col_int(stmt, "blockheight"); + + db_col_txid(stmt, "utxo_txid", &e->outpoint.txid); + e->outpoint.n = db_col_int(stmt, "outnum"); + + if (!db_col_is_null(stmt, "payment_id")) { + e->payment_id = tal(e, struct sha256); + db_col_sha256(stmt, "payment_id", e->payment_id); + } else + e->payment_id = NULL; + + if (!db_col_is_null(stmt, "spending_txid")) { + e->spending_txid = tal(e, struct bitcoin_txid); + db_col_txid(stmt, "spending_txid", e->spending_txid); + } else + e->spending_txid = NULL; + + return e; +} + +static struct channel_event *stmt2channel_event(const tal_t *ctx, struct db_stmt *stmt) +{ + struct channel_event *e = tal(ctx, struct channel_event); + + e->db_id = db_col_u64(stmt, "id"); + e->acct_db_id = db_col_u64(stmt, "account_id"); + + e->tag = db_col_strdup(e, stmt, "tag"); + + db_col_amount_msat(stmt, "credit", &e->credit); + db_col_amount_msat(stmt, "debit", &e->debit); + db_col_amount_msat(stmt, "fees", &e->fees); + + e->currency = db_col_strdup(e, stmt, "currency"); + db_col_sha256(stmt, "payment_id", &e->payment_id); + e->part_id = db_col_int(stmt, "part_id"); + e->timestamp = db_col_u64(stmt, "timestamp"); + + return e; +} + +struct chain_event **account_get_chain_events(const tal_t *ctx, + struct db *db, + struct account *acct) +{ + struct db_stmt *stmt; + struct chain_event **results; + + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", account_id" + ", tag" + ", credit" + ", debit" + ", output_value" + ", currency" + ", timestamp" + ", blockheight" + ", utxo_txid" + ", outnum" + ", spending_txid" + ", payment_id" + " FROM chain_events" + " WHERE account_id = ?;")); + + db_bind_int(stmt, 0, acct->db_id); + db_query_prepared(stmt); + + results = tal_arr(ctx, struct chain_event *, 0); + while (db_step(stmt)) { + struct chain_event *e = stmt2chain_event(results, stmt); + tal_arr_expand(&results, e); + } + tal_free(stmt); + + return results; +} + +static struct chain_event *find_chain_event(const tal_t *ctx, + struct db *db, + const struct account *acct, + const struct bitcoin_outpoint *outpoint, + const struct bitcoin_txid *spending_txid) + +{ + struct db_stmt *stmt; + struct chain_event *e; + + if (spending_txid) { + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", account_id" + ", tag" + ", credit" + ", debit" + ", output_value" + ", currency" + ", timestamp" + ", blockheight" + ", utxo_txid" + ", outnum" + ", spending_txid" + ", payment_id" + " FROM chain_events" + " WHERE " + " account_id = ?" + " AND utxo_txid = ?" + " AND outnum = ?" + " AND spending_txid = ?")); + db_bind_txid(stmt, 3, spending_txid); + } else { + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", account_id" + ", tag" + ", credit" + ", debit" + ", output_value" + ", currency" + ", timestamp" + ", blockheight" + ", utxo_txid" + ", outnum" + ", spending_txid" + ", payment_id" + " FROM chain_events" + " WHERE " + " account_id = ?" + " AND utxo_txid = ?" + " AND outnum = ?" + " AND spending_txid IS NULL")); + } + + db_bind_u64(stmt, 0, acct->db_id); + db_bind_txid(stmt, 1, &outpoint->txid); + db_bind_int(stmt, 2, outpoint->n); + + db_query_prepared(stmt); + if (db_step(stmt)) + e = stmt2chain_event(ctx, stmt); + else + e = NULL; + + tal_free(stmt); + return e; +} + +struct channel_event **account_get_channel_events(const tal_t *ctx, + struct db *db, + struct account *acct) +{ + struct db_stmt *stmt; + struct channel_event **results; + + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", account_id" + ", tag" + ", credit" + ", debit" + ", fees" + ", currency" + ", payment_id" + ", part_id" + ", timestamp" + " FROM channel_events" + " WHERE account_id = ?;")); + + db_bind_u64(stmt, 0, acct->db_id); + db_query_prepared(stmt); + + results = tal_arr(ctx, struct channel_event *, 0); + while (db_step(stmt)) { + struct channel_event *e = stmt2channel_event(results, stmt); + tal_arr_expand(&results, e); + } + tal_free(stmt); + + return results; +} + +static struct account *stmt2account(const tal_t *ctx, struct db_stmt *stmt) +{ + struct account *a = tal(ctx, struct account); + + a->db_id = db_col_u64(stmt, "id"); + a->name = db_col_strdup(a, stmt, "name"); + + if (!db_col_is_null(stmt, "peer_id")) { + a->peer_id = tal(a, struct node_id); + db_col_node_id(stmt, "peer_id", a->peer_id); + } else + a->peer_id = NULL; + a->is_wallet = db_col_int(stmt, "is_wallet") != 0; + a->we_opened = db_col_int(stmt, "we_opened") != 0; + a->leased = db_col_int(stmt, "leased") != 0; + + if (!db_col_is_null(stmt, "onchain_resolved_block")) { + a->onchain_resolved_block = db_col_int(stmt, "onchain_resolved_block"); + } else + a->onchain_resolved_block = 0; + + if (!db_col_is_null(stmt, "opened_event_id")) { + a->open_event_db_id = tal(a, u64); + *a->open_event_db_id = db_col_u64(stmt, "opened_event_id"); + } else + a->open_event_db_id = NULL; + + if (!db_col_is_null(stmt, "closed_event_id")) { + a->closed_event_db_id = tal(a, u64); + *a->closed_event_db_id = db_col_u64(stmt, "closed_event_id"); + } else + a->closed_event_db_id = NULL; + + return a; +} + +struct account *find_account(const tal_t *ctx, + struct db *db, + const char *name) +{ + struct db_stmt *stmt; + struct account *a; + + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", name" + ", peer_id" + ", opened_event_id" + ", closed_event_id" + ", onchain_resolved_block" + ", is_wallet" + ", we_opened" + ", leased" + " FROM accounts" + " WHERE name = ?")); + + db_bind_text(stmt, 0, name); + db_query_prepared(stmt); + + if (db_step(stmt)) + a = stmt2account(ctx, stmt); + else + a = NULL; + + tal_free(stmt); + + return a; +} + +static struct onchain_fee *stmt2onchain_fee(const tal_t *ctx, struct db_stmt *stmt) +{ + struct onchain_fee *of = tal(ctx, struct onchain_fee); + + of->acct_db_id = db_col_u64(stmt, "account_id"); + db_col_txid(stmt, "txid", &of->txid); + db_col_amount_msat(stmt, "amount", &of->amount); + of->currency = db_col_strdup(of, stmt, "currency"); + + return of; +} + +struct onchain_fee **account_onchain_fees(const tal_t *ctx, + struct db *db, + struct account *acct) +{ + struct db_stmt *stmt; + struct onchain_fee **results; + + stmt = db_prepare_v2(db, SQL("SELECT" + " account_id" + ", txid" + ", amount" + ", currency" + " FROM onchain_fees" + " WHERE account_id = ?;")); + + db_bind_int(stmt, 0, acct->db_id); + db_query_prepared(stmt); + + results = tal_arr(ctx, struct onchain_fee*, 0); + while (db_step(stmt)) { + struct onchain_fee *of = stmt2onchain_fee(results, stmt); + tal_arr_expand(&results, of); + } + tal_free(stmt); + + return results; +} + +struct account **list_accounts(const tal_t *ctx, struct db *db) +{ + struct db_stmt *stmt; + struct account **results; + + stmt = db_prepare_v2(db, SQL("SELECT" + " id" + ", name" + ", peer_id" + ", opened_event_id" + ", closed_event_id" + ", onchain_resolved_block" + ", is_wallet" + ", we_opened" + ", leased" + " FROM accounts;")); + db_query_prepared(stmt); + + results = tal_arr(ctx, struct account *, 0); + while (db_step(stmt)) { + struct account *a = stmt2account(results, stmt); + tal_arr_expand(&results, a); + } + tal_free(stmt); + + return results; +} + +void account_add(struct db *db, struct account *acct) +{ + struct db_stmt *stmt; + + stmt = db_prepare_v2(db, SQL("INSERT INTO accounts" + " (" + " name" + ", peer_id" + ", is_wallet" + ", we_opened" + ", leased" + ")" + " VALUES" + " (?, ?, ?, ?, ?);")); + + db_bind_text(stmt, 0, acct->name); + if (acct->peer_id) + db_bind_node_id(stmt, 1, acct->peer_id); + else + db_bind_null(stmt, 1); + db_bind_int(stmt, 2, acct->is_wallet ? 1 : 0); + db_bind_int(stmt, 3, acct->we_opened ? 1 : 0); + db_bind_int(stmt, 4, acct->leased ? 1 : 0); + + db_exec_prepared_v2(stmt); + acct->db_id = db_last_insert_id_v2(stmt); + tal_free(stmt); +} + +void maybe_update_account(struct db *db, + struct account *acct, + struct chain_event *e, + const enum mvt_tag *tags) +{ + struct db_stmt *stmt; + bool updated = false; + + for (size_t i = 0; i < tal_count(tags); i++) { + switch (tags[i]) { + case CHANNEL_OPEN: + updated = true; + acct->open_event_db_id = tal(acct, u64); + *acct->open_event_db_id = e->db_id; + break; + case CHANNEL_CLOSE: + updated = true; + acct->closed_event_db_id = tal(acct, u64); + *acct->closed_event_db_id = e->db_id; + break; + case LEASED: + updated = true; + acct->leased = true; + break; + case OPENER: + updated = true; + acct->we_opened = true; + break; + case DEPOSIT: + case WITHDRAWAL: + case PENALTY: + case INVOICE: + case ROUTED: + case PUSHED: + case CHANNEL_TO_US: + case HTLC_TIMEOUT: + case HTLC_FULFILL: + case HTLC_TX: + case TO_WALLET: + case IGNORED: + case ANCHOR: + case TO_THEM: + case PENALIZED: + case STOLEN: + case TO_MINER: + case LEASE_FEE: + /* Ignored */ + break; + } + } + + /* Nothing new here */ + if (!updated) + return; + + /* Otherwise, we update the account ! */ + stmt = db_prepare_v2(db, SQL("UPDATE accounts SET" + " opened_event_id = ?" + ", closed_event_id = ?" + ", we_opened = ?" + ", leased = ?" + " WHERE" + " name = ?")); + + if (acct->open_event_db_id) + db_bind_u64(stmt, 0, *acct->open_event_db_id); + else + db_bind_null(stmt, 0); + + if (acct->closed_event_db_id) + db_bind_u64(stmt, 1, *acct->closed_event_db_id); + else + db_bind_null(stmt, 1); + + db_bind_int(stmt, 2, acct->we_opened ? 1 : 0); + db_bind_int(stmt, 3, acct->leased ? 1 : 0); + + db_bind_text(stmt, 4, acct->name); + + db_exec_prepared_v2(take(stmt)); +} + +void log_channel_event(struct db *db, + const struct account *acct, + struct channel_event *e) +{ + struct db_stmt *stmt; + + stmt = db_prepare_v2(db, SQL("INSERT INTO channel_events" + " (" + " account_id" + ", tag" + ", credit" + ", debit" + ", fees" + ", currency" + ", payment_id" + ", part_id" + ", timestamp" + ")" + " VALUES" + " (?, ?, ?, ?, ?, ?, ?, ?, ?);")); + + db_bind_u64(stmt, 0, acct->db_id); + db_bind_text(stmt, 1, e->tag); + db_bind_amount_msat(stmt, 2, &e->credit); + db_bind_amount_msat(stmt, 3, &e->debit); + db_bind_amount_msat(stmt, 4, &e->fees); + db_bind_text(stmt, 5, e->currency); + db_bind_sha256(stmt, 6, &e->payment_id); + db_bind_int(stmt, 7, e->part_id); + db_bind_u64(stmt, 8, e->timestamp); + + db_exec_prepared_v2(stmt); + e->db_id = db_last_insert_id_v2(stmt); + e->acct_db_id = acct->db_id; + tal_free(stmt); +} + +void log_chain_event(struct db *db, + const struct account *acct, + struct chain_event *e) +{ + struct db_stmt *stmt; + + /* We're responsible for de-duping chain events! */ + if (find_chain_event(e, db, acct, + &e->outpoint, e->spending_txid)) + return; + + stmt = db_prepare_v2(db, SQL("INSERT INTO chain_events" + " (" + " account_id" + ", tag" + ", credit" + ", debit" + ", output_value" + ", currency" + ", timestamp" + ", blockheight" + ", utxo_txid" + ", outnum" + ", payment_id" + ", spending_txid" + ")" + " VALUES" + " (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); + + db_bind_u64(stmt, 0, acct->db_id); + db_bind_text(stmt, 1, e->tag); + db_bind_amount_msat(stmt, 2, &e->credit); + db_bind_amount_msat(stmt, 3, &e->debit); + db_bind_amount_msat(stmt, 4, &e->output_value); + db_bind_text(stmt, 5, e->currency); + db_bind_u64(stmt, 6, e->timestamp); + db_bind_int(stmt, 7, e->blockheight); + db_bind_txid(stmt, 8, &e->outpoint.txid); + db_bind_int(stmt, 9, e->outpoint.n); + db_bind_sha256(stmt, 10, e->payment_id); + + if (e->spending_txid) + db_bind_txid(stmt, 11, e->spending_txid); + else + db_bind_null(stmt, 11); + + db_exec_prepared_v2(stmt); + e->db_id = db_last_insert_id_v2(stmt); + e->acct_db_id = acct->db_id; + tal_free(stmt); +} diff --git a/plugins/bkpr/recorder.h b/plugins/bkpr/recorder.h new file mode 100644 index 000000000..abe32823a --- /dev/null +++ b/plugins/bkpr/recorder.h @@ -0,0 +1,56 @@ +#ifndef LIGHTNING_PLUGINS_BKPR_RECORDER_H +#define LIGHTNING_PLUGINS_BKPR_RECORDER_H + +#include "config.h" +#include + +struct account; +struct chain_event; +struct channel_event; +struct db; +enum mvt_tag; +struct onchain_fee; + +/* Get all accounts */ +struct account **list_accounts(const tal_t *ctx, struct db *db); + +/* Get all onchain fee records for this account */ +struct onchain_fee **account_onchain_fees(const tal_t *ctx, + struct db *db, + struct account *acct); + +/* Get all channel events for this account */ +struct channel_event **account_get_channel_events(const tal_t *ctx, + struct db *db, + struct account *acct); + +/* Get all chain events for this account */ +struct chain_event **account_get_chain_events(const tal_t *ctx, + struct db *db, + struct account *acct); + +/* Add the given account to the database */ +void account_add(struct db *db, struct account *acct); + +/* Given an account name, find that account record */ +struct account *find_account(const tal_t *ctx, + struct db *db, + const char *name); + +/* Some events update account information */ +void maybe_update_account(struct db *db, + struct account *acct, + struct chain_event *e, + const enum mvt_tag *tags); + +/* Log a channel event */ +void log_channel_event(struct db *db, + const struct account *acct, + struct channel_event *e); + +/* Log a chain event. */ +void log_chain_event(struct db *db, + const struct account *acct, + struct chain_event *e); + +#endif /* LIGHTNING_PLUGINS_BKPR_RECORDER_H */