diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index 823026b1a..33e0c4ba0 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -1,5 +1,6 @@ #include "config.h" #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #include @@ -24,6 +26,158 @@ static struct db *db ; // FIXME: make relative to directory we're loaded into static char *db_dsn = "sqlite3://accounts.sqlite3"; +static void json_add_channel_event(struct json_stream *out, + struct channel_event *ev) +{ + json_object_start(out, NULL); + json_add_string(out, "account", ev->acct_name); + json_add_string(out, "type", "channel"); + json_add_string(out, "tag", ev->tag); + json_add_amount_msat_only(out, "credit", ev->credit); + json_add_amount_msat_only(out, "debit", ev->debit); + json_add_string(out, "currency", ev->currency); + if (ev->payment_id) + json_add_sha256(out, "payment_id", ev->payment_id); + json_add_u64(out, "timestamp", ev->timestamp); + json_object_end(out); +} + +static void json_add_chain_event(struct json_stream *out, + struct chain_event *ev) +{ + json_object_start(out, NULL); + json_add_string(out, "account", ev->acct_name); + json_add_string(out, "type", "chain"); + json_add_string(out, "tag", ev->tag); + json_add_amount_msat_only(out, "credit", ev->credit); + json_add_amount_msat_only(out, "debit", ev->debit); + json_add_string(out, "currency", ev->currency); + json_add_outpoint(out, "outpoint", &ev->outpoint); + if (ev->spending_txid) + json_add_txid(out, "txid", ev->spending_txid); + if (ev->payment_id) + json_add_sha256(out, "payment_id", ev->payment_id); + json_add_u64(out, "timestamp", ev->timestamp); + json_add_u32(out, "blockheight", ev->blockheight); + json_object_end(out); +} + +static void json_add_onchain_fee(struct json_stream *out, + struct onchain_fee *fee) +{ + json_object_start(out, NULL); + json_add_string(out, "account", fee->acct_name); + json_add_string(out, "type", "onchain_fee"); + json_add_string(out, "tag", "onchain_fee"); + json_add_amount_msat_only(out, "credit", fee->credit); + json_add_amount_msat_only(out, "debit", fee->debit); + json_add_string(out, "currency", fee->currency); + json_add_u64(out, "timestamp", fee->timestamp); + json_add_txid(out, "txid", &fee->txid); + json_object_end(out); +} + +/* Find all the events for this account, ordered by timestamp */ +static struct command_result *json_list_account_events(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct json_stream *res; + struct account *acct; + const char *acct_name; + struct channel_event **channel_events; + struct chain_event **chain_events; + struct onchain_fee **onchain_fees; + + if (!param(cmd, buf, params, + p_opt("account", param_string, &acct_name), + NULL)) + return command_param_failed(); + + if (acct_name) { + db_begin_transaction(db); + acct = find_account(cmd, db, acct_name); + db_commit_transaction(db); + + if (!acct) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Account '%s' not found", + acct_name); + } else + acct = NULL; + + db_begin_transaction(db); + if (acct) { + channel_events = account_get_channel_events(cmd, db, acct); + chain_events = account_get_chain_events(cmd, db, acct); + onchain_fees = account_get_chain_fees(cmd, db, acct); + } else { + channel_events = list_channel_events(cmd, db); + chain_events = list_chain_events(cmd, db); + onchain_fees = list_chain_fees(cmd, db); + } + db_commit_transaction(db); + + res = jsonrpc_stream_success(cmd); + json_array_start(res, "events"); + for (size_t i = 0, j = 0, k = 0; + i < tal_count(channel_events) + || j < tal_count(chain_events) + || k < tal_count(onchain_fees); + /* Incrementing happens inside loop */) { + struct channel_event *chan; + struct chain_event *chain; + struct onchain_fee *fee; + u64 lowest = 0; + + if (i < tal_count(channel_events)) + chan = channel_events[i]; + else + chan = NULL; + if (j < tal_count(chain_events)) + chain = chain_events[j]; + else + chain = NULL; + if (k < tal_count(onchain_fees)) + fee = onchain_fees[k]; + else + fee = NULL; + + if (chan) + lowest = chan->timestamp; + + if (chain + && (lowest == 0 || lowest > chain->timestamp)) + lowest = chain->timestamp; + + if (fee + && (lowest == 0 || lowest > fee->timestamp)) + lowest = fee->timestamp; + + /* channel events first, then chain events, then fees. + * (channel events contain journal entries, which + * are the starting balance for accounts created + * before the accountant plugin was installed) */ + if (chan && chan->timestamp == lowest) { + json_add_channel_event(res, chan); + i++; + continue; + } + + if (chain && chain->timestamp == lowest) { + json_add_chain_event(res, chain); + j++; + continue; + } + + /* Last thing left is the fee */ + json_add_onchain_fee(res, fee); + k++; + } + json_array_end(res); + return command_finished(cmd, res); +} + static struct command_result *json_list_balances(struct command *cmd, const char *buf, const jsmntok_t *params) @@ -870,6 +1024,14 @@ static const struct plugin_command commands[] = { "List of current accounts and their balances", json_list_balances }, + { + "listaccountevents", + "bookkeeping", + "List all events for an {account}", + "List all events for an {account} (or all accounts, if" + " no account specified) in {format}. Sorted by timestamp", + json_list_account_events + }, }; static const char *init(struct plugin *p, const char *b, const jsmntok_t *t) diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c index 8f9175f7e..1a8b45d44 100644 --- a/plugins/bkpr/recorder.c +++ b/plugins/bkpr/recorder.c @@ -97,6 +97,33 @@ static struct channel_event *stmt2channel_event(const tal_t *ctx, struct db_stmt return e; } +struct chain_event **list_chain_events(const tal_t *ctx, struct db *db) +{ + struct db_stmt *stmt; + + stmt = db_prepare_v2(db, SQL("SELECT" + " e.id" + ", e.account_id" + ", a.name" + ", e.tag" + ", e.credit" + ", e.debit" + ", e.output_value" + ", e.currency" + ", e.timestamp" + ", e.blockheight" + ", e.utxo_txid" + ", e.outnum" + ", e.spending_txid" + ", e.payment_id" + " FROM chain_events e" + " LEFT OUTER JOIN accounts a" + " ON e.account_id = a.id" + " ORDER BY e.timestamp, e.id;")); + + return find_chain_events(ctx, take(stmt)); +} + struct chain_event **account_get_chain_events(const tal_t *ctx, struct db *db, struct account *acct) @@ -304,6 +331,40 @@ char *account_get_balance(const tal_t *ctx, return NULL; } +struct channel_event **list_channel_events(const tal_t *ctx, struct db *db) +{ + struct db_stmt *stmt; + struct channel_event **results; + + stmt = db_prepare_v2(db, SQL("SELECT" + " e.id" + ", e.account_id" + ", a.name" + ", e.tag" + ", e.credit" + ", e.debit" + ", e.fees" + ", e.currency" + ", e.payment_id" + ", e.part_id" + ", e.timestamp" + " FROM channel_events e" + " LEFT OUTER JOIN accounts a" + " ON a.id = e.account_id" + " ORDER BY e.timestamp, e.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; +} + struct channel_event **account_get_channel_events(const tal_t *ctx, struct db *db, struct account *acct) @@ -359,6 +420,43 @@ static struct onchain_fee *stmt2onchain_fee(const tal_t *ctx, return of; } +struct onchain_fee **account_get_chain_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" + " of.account_id" + ", a.name" + ", of.txid" + ", of.credit" + ", of.debit" + ", of.currency" + ", of.timestamp" + ", of.update_count" + " FROM onchain_fees of" + " LEFT OUTER JOIN accounts a" + " ON a.id = of.account_id" + " WHERE of.account_id = ?" + " ORDER BY " + " of.timestamp" + ", of.txid" + ", of.update_count")); + + db_bind_u64(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 onchain_fee **list_chain_fees(const tal_t *ctx, struct db *db) { struct db_stmt *stmt; diff --git a/plugins/bkpr/recorder.h b/plugins/bkpr/recorder.h index 03aa09c5f..2f41e0232 100644 --- a/plugins/bkpr/recorder.h +++ b/plugins/bkpr/recorder.h @@ -32,10 +32,17 @@ struct channel_event **account_get_channel_events(const tal_t *ctx, struct db *db, struct account *acct); +/* Get all channel events, ordered by timestamp */ +struct channel_event **list_channel_events(const tal_t *ctx, struct db *db); + /* Get all chain events for this account */ struct chain_event **account_get_chain_events(const tal_t *ctx, struct db *db, struct account *acct); + +/* Get all chain events, ordered by timestamp */ +struct chain_event **list_chain_events(const tal_t *ctx, struct db *db); + /* Calculate the balances for an account * * @calc_sum - compute the total balance. error if negative @@ -46,6 +53,10 @@ char *account_get_balance(const tal_t *ctx, bool calc_sum, struct acct_balance ***balances); +/* Get chain fees for account */ +struct onchain_fee **account_get_chain_fees(const tal_t *ctx, struct db *db, + struct account *acct); + /* List all chain fees, for all accounts */ struct onchain_fee **list_chain_fees(const tal_t *ctx, struct db *db);