bkpr: add a 'listincome' event
list the income/expense events for a node.
This commit is contained in:
parent
595c52f611
commit
593f1e7365
|
@ -7,6 +7,7 @@ BOOKKEEPER_PLUGIN_SRC := \
|
||||||
plugins/bkpr/chain_event.c \
|
plugins/bkpr/chain_event.c \
|
||||||
plugins/bkpr/channel_event.c \
|
plugins/bkpr/channel_event.c \
|
||||||
plugins/bkpr/db.c \
|
plugins/bkpr/db.c \
|
||||||
|
plugins/bkpr/incomestmt.c \
|
||||||
plugins/bkpr/onchain_fee.c \
|
plugins/bkpr/onchain_fee.c \
|
||||||
plugins/bkpr/recorder.c
|
plugins/bkpr/recorder.c
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ BOOKKEEPER_HEADER := \
|
||||||
plugins/bkpr/chain_event.h \
|
plugins/bkpr/chain_event.h \
|
||||||
plugins/bkpr/channel_event.h \
|
plugins/bkpr/channel_event.h \
|
||||||
plugins/bkpr/db.h \
|
plugins/bkpr/db.h \
|
||||||
|
plugins/bkpr/incomestmt.h \
|
||||||
plugins/bkpr/onchain_fee.h \
|
plugins/bkpr/onchain_fee.h \
|
||||||
plugins/bkpr/recorder.h
|
plugins/bkpr/recorder.h
|
||||||
|
|
||||||
|
@ -39,6 +41,7 @@ plugins/bookkeeper: bitcoin/chainparams.o common/coin_mvt.o $(BOOKKEEPER_OBJS) $
|
||||||
BOOKKEEPER_SQL_FILES := \
|
BOOKKEEPER_SQL_FILES := \
|
||||||
$(DB_SQL_FILES) \
|
$(DB_SQL_FILES) \
|
||||||
plugins/bkpr/db.c \
|
plugins/bkpr/db.c \
|
||||||
|
plugins/bkpr/incomestmt.c \
|
||||||
plugins/bkpr/recorder.c
|
plugins/bkpr/recorder.c
|
||||||
|
|
||||||
plugins/bkpr/statements_gettextgen.po: $(BOOKKEEPER_SQL_FILES) $(FORCE)
|
plugins/bkpr/statements_gettextgen.po: $(BOOKKEEPER_SQL_FILES) $(FORCE)
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#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/db.h>
|
#include <plugins/bkpr/db.h>
|
||||||
|
#include <plugins/bkpr/incomestmt.h>
|
||||||
#include <plugins/bkpr/onchain_fee.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>
|
||||||
|
@ -37,6 +38,32 @@ static struct fee_sum *find_sum_for_txid(struct fee_sum **sums,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct command_result *json_list_income(struct command *cmd,
|
||||||
|
const char *buf,
|
||||||
|
const jsmntok_t *params)
|
||||||
|
{
|
||||||
|
struct json_stream *res;
|
||||||
|
struct income_event **evs;
|
||||||
|
|
||||||
|
if (!param(cmd, buf, params,
|
||||||
|
NULL))
|
||||||
|
return command_param_failed();
|
||||||
|
|
||||||
|
/* Ok, go find me some income events! */
|
||||||
|
db_begin_transaction(db);
|
||||||
|
evs = list_income_events_all(cmd, db);
|
||||||
|
db_commit_transaction(db);
|
||||||
|
|
||||||
|
res = jsonrpc_stream_success(cmd);
|
||||||
|
|
||||||
|
json_array_start(res, "income_events");
|
||||||
|
for (size_t i = 0; i < tal_count(evs); i++)
|
||||||
|
json_add_income_event(res, evs[i]);
|
||||||
|
|
||||||
|
json_array_end(res);
|
||||||
|
return command_finished(cmd, res);
|
||||||
|
}
|
||||||
|
|
||||||
static struct command_result *json_inspect(struct command *cmd,
|
static struct command_result *json_inspect(struct command *cmd,
|
||||||
const char *buf,
|
const char *buf,
|
||||||
const jsmntok_t *params)
|
const jsmntok_t *params)
|
||||||
|
@ -1261,6 +1288,13 @@ static const struct plugin_command commands[] = {
|
||||||
"Prints out the on-chain footprint of a given {account}.",
|
"Prints out the on-chain footprint of a given {account}.",
|
||||||
json_inspect
|
json_inspect
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"listincome",
|
||||||
|
"bookkeeping",
|
||||||
|
"List all income impacting events",
|
||||||
|
"List all events for this node that impacted income",
|
||||||
|
json_list_income
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -0,0 +1,324 @@
|
||||||
|
#include "config.h"
|
||||||
|
#include <ccan/tal/str/str.h>
|
||||||
|
#include <common/coin_mvt.h>
|
||||||
|
#include <common/json_stream.h>
|
||||||
|
#include <db/bindings.h>
|
||||||
|
#include <db/common.h>
|
||||||
|
#include <db/exec.h>
|
||||||
|
#include <db/utils.h>
|
||||||
|
#include <plugins/bkpr/account.h>
|
||||||
|
#include <plugins/bkpr/account_entry.h>
|
||||||
|
#include <plugins/bkpr/chain_event.h>
|
||||||
|
#include <plugins/bkpr/channel_event.h>
|
||||||
|
#include <plugins/bkpr/incomestmt.h>
|
||||||
|
#include <plugins/bkpr/onchain_fee.h>
|
||||||
|
#include <plugins/bkpr/recorder.h>
|
||||||
|
|
||||||
|
static struct account *get_account(struct account **accts,
|
||||||
|
u64 acct_db_id)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < tal_count(accts); i++) {
|
||||||
|
if (accts[i]->db_id == acct_db_id)
|
||||||
|
return accts[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct income_event *chain_to_income(const tal_t *ctx,
|
||||||
|
struct chain_event *ev,
|
||||||
|
char *acct_to_attribute,
|
||||||
|
struct amount_msat credit,
|
||||||
|
struct amount_msat debit)
|
||||||
|
{
|
||||||
|
struct income_event *inc = tal(ctx, struct income_event);
|
||||||
|
|
||||||
|
inc->acct_name = tal_strdup(inc, acct_to_attribute);
|
||||||
|
inc->tag = tal_strdup(inc, ev->tag);
|
||||||
|
inc->credit = credit;
|
||||||
|
inc->debit = debit;
|
||||||
|
inc->currency = tal_strdup(inc, ev->currency);
|
||||||
|
inc->timestamp = ev->timestamp;
|
||||||
|
inc->outpoint = tal_dup(inc, struct bitcoin_outpoint, &ev->outpoint);
|
||||||
|
|
||||||
|
if (ev->spending_txid)
|
||||||
|
inc->txid = tal_dup(inc, struct bitcoin_txid,
|
||||||
|
ev->spending_txid);
|
||||||
|
else
|
||||||
|
inc->txid = NULL;
|
||||||
|
|
||||||
|
if (ev->payment_id)
|
||||||
|
inc->payment_id = tal_dup(inc, struct sha256, ev->payment_id);
|
||||||
|
else
|
||||||
|
inc->payment_id = NULL;
|
||||||
|
|
||||||
|
return inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct income_event *channel_to_income(const tal_t *ctx,
|
||||||
|
struct channel_event *ev,
|
||||||
|
struct amount_msat credit,
|
||||||
|
struct amount_msat debit)
|
||||||
|
{
|
||||||
|
struct income_event *inc = tal(ctx, struct income_event);
|
||||||
|
|
||||||
|
inc->acct_name = tal_strdup(inc, ev->acct_name);
|
||||||
|
inc->tag = tal_strdup(inc, ev->tag);
|
||||||
|
inc->credit = credit;
|
||||||
|
inc->debit = debit;
|
||||||
|
inc->currency = tal_strdup(inc, ev->currency);
|
||||||
|
inc->timestamp = ev->timestamp;
|
||||||
|
inc->outpoint = NULL;
|
||||||
|
inc->txid = NULL;
|
||||||
|
if (ev->payment_id)
|
||||||
|
inc->payment_id = tal_dup(inc, struct sha256, ev->payment_id);
|
||||||
|
else
|
||||||
|
inc->payment_id = NULL;
|
||||||
|
|
||||||
|
return inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct income_event *onchainfee_to_income(const tal_t *ctx,
|
||||||
|
struct onchain_fee *fee)
|
||||||
|
{
|
||||||
|
struct income_event *inc = tal(ctx, struct income_event);
|
||||||
|
|
||||||
|
inc->acct_name = tal_strdup(inc, fee->acct_name);
|
||||||
|
inc->tag = tal_fmt(inc, "%s", "onchain_fee");
|
||||||
|
/* We swap these, as they're actually opposite */
|
||||||
|
inc->credit = fee->debit;
|
||||||
|
inc->debit = fee->credit;
|
||||||
|
inc->currency = tal_strdup(inc, fee->currency);
|
||||||
|
inc->timestamp = fee->timestamp;
|
||||||
|
inc->txid = tal_dup(inc, struct bitcoin_txid, &fee->txid);
|
||||||
|
inc->outpoint = NULL;
|
||||||
|
inc->payment_id = NULL;
|
||||||
|
|
||||||
|
return inc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct income_event *maybe_chain_income(const tal_t *ctx,
|
||||||
|
struct db *db,
|
||||||
|
struct account *acct,
|
||||||
|
struct chain_event *ev)
|
||||||
|
{
|
||||||
|
if (streq(ev->tag, "htlc_fulfill")) {
|
||||||
|
if (streq(ev->acct_name, EXTERNAL_ACCT))
|
||||||
|
/* Swap the credit/debit as it went to external */
|
||||||
|
return chain_to_income(ctx, ev,
|
||||||
|
ev->origin_acct,
|
||||||
|
ev->debit,
|
||||||
|
ev->credit);
|
||||||
|
/* Normal credit/debit as it originated from external */
|
||||||
|
return chain_to_income(ctx, ev,
|
||||||
|
ev->acct_name,
|
||||||
|
ev->credit, ev->debit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* expenses */
|
||||||
|
if (streq(ev->tag, "anchor")) {
|
||||||
|
if (acct->we_opened)
|
||||||
|
/* for now, we count all anchors as expenses */
|
||||||
|
return chain_to_income(ctx, ev,
|
||||||
|
ev->acct_name,
|
||||||
|
ev->debit,
|
||||||
|
ev->credit);
|
||||||
|
/* non-openers dont spend/gain anything on anchors */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* income */
|
||||||
|
if (streq(ev->tag, "deposit")) {
|
||||||
|
struct db_stmt *stmt;
|
||||||
|
|
||||||
|
/* deposit to external is cost to us */
|
||||||
|
if (streq(ev->acct_name, EXTERNAL_ACCT)) {
|
||||||
|
struct income_event *iev;
|
||||||
|
iev = chain_to_income(ctx, ev,
|
||||||
|
ev->origin_acct,
|
||||||
|
ev->debit,
|
||||||
|
ev->credit);
|
||||||
|
/* Also, really a withdrawal.. phh */
|
||||||
|
iev->tag = tal_strdup(iev, mvt_tag_str(WITHDRAWAL));
|
||||||
|
return iev;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Did this deposit originate from within our
|
||||||
|
* wallet? */
|
||||||
|
/*FIXME: there's an edge case where we put funds
|
||||||
|
* into a tx that included funds from a 3rd party
|
||||||
|
* coming to us... eg. a splice out from the peer
|
||||||
|
* to our onchain wallet */
|
||||||
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
||||||
|
" 1"
|
||||||
|
" FROM chain_events e"
|
||||||
|
" LEFT OUTER JOIN accounts a"
|
||||||
|
" ON e.account_id = a.id"
|
||||||
|
" WHERE "
|
||||||
|
" e.spending_txid = ?"));
|
||||||
|
|
||||||
|
db_bind_txid(stmt, 0, &ev->outpoint.txid);
|
||||||
|
db_query_prepared(stmt);
|
||||||
|
if (!db_step(stmt)) {
|
||||||
|
tal_free(stmt);
|
||||||
|
/* no matching withdrawal from internal,
|
||||||
|
* so must be new deposit (external) */
|
||||||
|
return chain_to_income(ctx, ev,
|
||||||
|
ev->acct_name,
|
||||||
|
ev->credit,
|
||||||
|
ev->debit);
|
||||||
|
}
|
||||||
|
|
||||||
|
db_col_ignore(stmt, "1");
|
||||||
|
tal_free(stmt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct income_event *maybe_channel_income(const tal_t *ctx,
|
||||||
|
struct channel_event *ev)
|
||||||
|
{
|
||||||
|
/* We record a +/- penalty adj, but we only count the credit */
|
||||||
|
if (streq(ev->tag, "penalty_adj")) {
|
||||||
|
if (!amount_msat_zero(ev->credit))
|
||||||
|
return channel_to_income(ctx, ev,
|
||||||
|
ev->credit,
|
||||||
|
ev->debit);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streq(ev->tag, "invoice")) {
|
||||||
|
/* FIXME: add a sub-category for fees paid */
|
||||||
|
return channel_to_income(ctx, ev,
|
||||||
|
ev->credit,
|
||||||
|
ev->debit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* for routed payments, we only record the fees on the
|
||||||
|
* debiting side -- the side the $$ was made on! */
|
||||||
|
if (streq(ev->tag, "routed")) {
|
||||||
|
if (!amount_msat_zero(ev->debit))
|
||||||
|
return channel_to_income(ctx, ev,
|
||||||
|
ev->fees,
|
||||||
|
AMOUNT_MSAT(0));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For everything else, it's straight forward */
|
||||||
|
/* (lease_fee, pushed, journal_entry) */
|
||||||
|
return channel_to_income(ctx, ev, ev->credit, ev->debit);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct income_event **list_income_events(const tal_t *ctx,
|
||||||
|
struct db *db,
|
||||||
|
u64 start_time,
|
||||||
|
u64 end_time)
|
||||||
|
{
|
||||||
|
struct channel_event **channel_events;
|
||||||
|
struct chain_event **chain_events;
|
||||||
|
struct onchain_fee **onchain_fees;
|
||||||
|
struct account **accts;
|
||||||
|
|
||||||
|
struct income_event **evs;
|
||||||
|
|
||||||
|
channel_events = list_channel_events_timebox(ctx, db,
|
||||||
|
start_time, end_time);
|
||||||
|
chain_events = list_chain_events_timebox(ctx, db, start_time, end_time);
|
||||||
|
onchain_fees = list_chain_fees_timebox(ctx, db, start_time, end_time);
|
||||||
|
accts = list_accounts(ctx, db);
|
||||||
|
|
||||||
|
evs = tal_arr(ctx, struct income_event *, 0);
|
||||||
|
|
||||||
|
for (size_t i = 0, j = 0, k = 0;
|
||||||
|
i < tal_count(chain_events)
|
||||||
|
|| j < tal_count(channel_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(chain_events))
|
||||||
|
chain = chain_events[i];
|
||||||
|
else
|
||||||
|
chain = NULL;
|
||||||
|
if (j < tal_count(channel_events))
|
||||||
|
chan = channel_events[j];
|
||||||
|
else
|
||||||
|
chan = NULL;
|
||||||
|
if (k < tal_count(onchain_fees))
|
||||||
|
fee = onchain_fees[k];
|
||||||
|
else
|
||||||
|
fee = NULL;
|
||||||
|
|
||||||
|
if (chain)
|
||||||
|
lowest = chain->timestamp;
|
||||||
|
|
||||||
|
if (chan
|
||||||
|
&& (lowest == 0 || lowest > chan->timestamp))
|
||||||
|
lowest = chan->timestamp;
|
||||||
|
|
||||||
|
if (fee
|
||||||
|
&& (lowest == 0 || lowest > fee->timestamp))
|
||||||
|
lowest = fee->timestamp;
|
||||||
|
|
||||||
|
/* chain events first, then channel events, then fees. */
|
||||||
|
if (chain && chain->timestamp == lowest) {
|
||||||
|
struct income_event *ev;
|
||||||
|
struct account *acct =
|
||||||
|
get_account(accts, chain->acct_db_id);
|
||||||
|
|
||||||
|
ev = maybe_chain_income(evs, db, acct, chain);
|
||||||
|
if (ev)
|
||||||
|
tal_arr_expand(&evs, ev);
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chan && chan->timestamp == lowest) {
|
||||||
|
struct income_event *ev;
|
||||||
|
ev = maybe_channel_income(evs, chan);
|
||||||
|
if (ev)
|
||||||
|
tal_arr_expand(&evs, ev);
|
||||||
|
|
||||||
|
j++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Last thing left is the fee */
|
||||||
|
tal_arr_expand(&evs, onchainfee_to_income(evs, fee));
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return evs;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct income_event **list_income_events_all(const tal_t *ctx, struct db *db)
|
||||||
|
{
|
||||||
|
return list_income_events(ctx, db, 0, SQLITE_MAX_UINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void json_add_income_event(struct json_stream *out, struct income_event *ev)
|
||||||
|
{
|
||||||
|
json_object_start(out, NULL);
|
||||||
|
json_add_string(out, "account", ev->acct_name);
|
||||||
|
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_u64(out, "timestamp", ev->timestamp);
|
||||||
|
|
||||||
|
if (ev->outpoint)
|
||||||
|
json_add_outpoint(out, "outpoint", ev->outpoint);
|
||||||
|
|
||||||
|
if (ev->txid)
|
||||||
|
json_add_txid(out, "txid", ev->txid);
|
||||||
|
|
||||||
|
if (ev->payment_id)
|
||||||
|
json_add_sha256(out, "payment_id", ev->payment_id);
|
||||||
|
|
||||||
|
json_object_end(out);
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef LIGHTNING_PLUGINS_BKPR_INCOMESTMT_H
|
||||||
|
#define LIGHTNING_PLUGINS_BKPR_INCOMESTMT_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <ccan/tal/tal.h>
|
||||||
|
|
||||||
|
struct income_event {
|
||||||
|
char *acct_name;
|
||||||
|
char *tag;
|
||||||
|
struct amount_msat credit;
|
||||||
|
struct amount_msat debit;
|
||||||
|
char *currency;
|
||||||
|
u64 timestamp;
|
||||||
|
|
||||||
|
struct bitcoin_outpoint *outpoint;
|
||||||
|
struct bitcoin_txid *txid;
|
||||||
|
struct sha256 *payment_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* List all the events that are income related (gain/loss) */
|
||||||
|
struct income_event **list_income_events_all(const tal_t *ctx, struct db *db);
|
||||||
|
|
||||||
|
/* List all the events that are income related (gain/loss),
|
||||||
|
* by a start and end date */
|
||||||
|
struct income_event **list_income_events(const tal_t *ctx,
|
||||||
|
struct db *db,
|
||||||
|
u64 start_time,
|
||||||
|
u64 end_time);
|
||||||
|
|
||||||
|
/* Given an event and a json_stream, add a new event object to the stream */
|
||||||
|
void json_add_income_event(struct json_stream *str, struct income_event *ev);
|
||||||
|
|
||||||
|
#endif /* LIGHTNING_PLUGINS_BKPR_INCOMESTMT_H */
|
Loading…
Reference in New Issue