bkpr: add a new command listaccountevents

Prints all the events for the requested account. If no account
requested, prints out all the events. Ordered by timestamp.

Changelog-Added: bookkeeper: new command `listaccountevents`
This commit is contained in:
niftynei
2022-07-19 17:04:35 +09:30
committed by Rusty Russell
parent 8039fde5ab
commit a1d72cef06
3 changed files with 271 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include <ccan/array_size/array_size.h> #include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h>
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <ccan/time/time.h> #include <ccan/time/time.h>
#include <common/coin_mvt.h> #include <common/coin_mvt.h>
@@ -12,6 +13,7 @@
#include <plugins/bkpr/account.h> #include <plugins/bkpr/account.h>
#include <plugins/bkpr/chain_event.h> #include <plugins/bkpr/chain_event.h>
#include <plugins/bkpr/channel_event.h> #include <plugins/bkpr/channel_event.h>
#include <plugins/bkpr/onchain_fee.h>
#include <plugins/bkpr/recorder.h> #include <plugins/bkpr/recorder.h>
#include <plugins/libplugin.h> #include <plugins/libplugin.h>
@@ -24,6 +26,158 @@ static struct db *db ;
// FIXME: make relative to directory we're loaded into // FIXME: make relative to directory we're loaded into
static char *db_dsn = "sqlite3://accounts.sqlite3"; 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, static struct command_result *json_list_balances(struct command *cmd,
const char *buf, const char *buf,
const jsmntok_t *params) const jsmntok_t *params)
@@ -870,6 +1024,14 @@ static const struct plugin_command commands[] = {
"List of current accounts and their balances", "List of current accounts and their balances",
json_list_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) static const char *init(struct plugin *p, const char *b, const jsmntok_t *t)

View File

@@ -97,6 +97,33 @@ static struct channel_event *stmt2channel_event(const tal_t *ctx, struct db_stmt
return e; 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 chain_event **account_get_chain_events(const tal_t *ctx,
struct db *db, struct db *db,
struct account *acct) struct account *acct)
@@ -304,6 +331,40 @@ char *account_get_balance(const tal_t *ctx,
return NULL; 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 channel_event **account_get_channel_events(const tal_t *ctx,
struct db *db, struct db *db,
struct account *acct) struct account *acct)
@@ -359,6 +420,43 @@ static struct onchain_fee *stmt2onchain_fee(const tal_t *ctx,
return of; 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 onchain_fee **list_chain_fees(const tal_t *ctx, struct db *db)
{ {
struct db_stmt *stmt; struct db_stmt *stmt;

View File

@@ -32,10 +32,17 @@ struct channel_event **account_get_channel_events(const tal_t *ctx,
struct db *db, struct db *db,
struct account *acct); 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 */ /* Get all chain events for this account */
struct chain_event **account_get_chain_events(const tal_t *ctx, struct chain_event **account_get_chain_events(const tal_t *ctx,
struct db *db, struct db *db,
struct account *acct); 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 /* Calculate the balances for an account
* *
* @calc_sum - compute the total balance. error if negative * @calc_sum - compute the total balance. error if negative
@@ -46,6 +53,10 @@ char *account_get_balance(const tal_t *ctx,
bool calc_sum, bool calc_sum,
struct acct_balance ***balances); 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 */ /* List all chain fees, for all accounts */
struct onchain_fee **list_chain_fees(const tal_t *ctx, struct db *db); struct onchain_fee **list_chain_fees(const tal_t *ctx, struct db *db);