mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-20 02:27:51 +01:00
1a25651182
We were silently failing when NULLS FIRST wasn't found on older boxes (running sqlite3 < v3.30.1)
2051 lines
52 KiB
C
2051 lines
52 KiB
C
#include "config.h"
|
|
#include <bitcoin/tx.h>
|
|
#include <ccan/array_size/array_size.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/coin_mvt.h>
|
|
#include <common/node_id.h>
|
|
#include <common/type_to_string.h>
|
|
#include <db/bindings.h>
|
|
#include <db/common.h>
|
|
#include <db/exec.h>
|
|
#include <db/utils.h>
|
|
#include <inttypes.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/onchain_fee.h>
|
|
#include <plugins/bkpr/recorder.h>
|
|
|
|
|
|
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, "e.id");
|
|
e->acct_db_id = db_col_u64(stmt, "e.account_id");
|
|
e->acct_name = db_col_strdup(e, stmt, "a.name");
|
|
|
|
if (!db_col_is_null(stmt, "e.origin"))
|
|
e->origin_acct = db_col_strdup(e, stmt, "e.origin");
|
|
else
|
|
e->origin_acct = NULL;
|
|
|
|
e->tag = db_col_strdup(e, stmt, "e.tag");
|
|
|
|
db_col_amount_msat(stmt, "e.credit", &e->credit);
|
|
db_col_amount_msat(stmt, "e.debit", &e->debit);
|
|
db_col_amount_msat(stmt, "e.output_value", &e->output_value);
|
|
|
|
e->currency = db_col_strdup(e, stmt, "e.currency");
|
|
e->timestamp = db_col_u64(stmt, "e.timestamp");
|
|
e->blockheight = db_col_int(stmt, "e.blockheight");
|
|
|
|
db_col_txid(stmt, "e.utxo_txid", &e->outpoint.txid);
|
|
e->outpoint.n = db_col_int(stmt, "e.outnum");
|
|
|
|
if (!db_col_is_null(stmt, "e.payment_id")) {
|
|
e->payment_id = tal(e, struct sha256);
|
|
db_col_sha256(stmt, "e.payment_id", e->payment_id);
|
|
} else
|
|
e->payment_id = NULL;
|
|
|
|
if (!db_col_is_null(stmt, "e.spending_txid")) {
|
|
e->spending_txid = tal(e, struct bitcoin_txid);
|
|
db_col_txid(stmt, "e.spending_txid", e->spending_txid);
|
|
} else
|
|
e->spending_txid = NULL;
|
|
|
|
e->ignored = db_col_int(stmt, "e.ignored") == 1;
|
|
e->stealable = db_col_int(stmt, "e.stealable") == 1;
|
|
|
|
if (!db_col_is_null(stmt, "e.ev_desc"))
|
|
e->desc = db_col_strdup(e, stmt, "e.ev_desc");
|
|
else
|
|
e->desc = NULL;
|
|
|
|
return e;
|
|
}
|
|
|
|
static struct chain_event **find_chain_events(const tal_t *ctx,
|
|
struct db_stmt *stmt TAKES)
|
|
{
|
|
struct chain_event **results;
|
|
|
|
db_query_prepared(stmt);
|
|
if (stmt->error)
|
|
db_fatal("find_chain_events err: %s", stmt->error);
|
|
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);
|
|
}
|
|
|
|
if (taken(stmt))
|
|
tal_free(stmt);
|
|
|
|
return results;
|
|
}
|
|
|
|
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, "e.id");
|
|
e->acct_db_id = db_col_u64(stmt, "e.account_id");
|
|
e->acct_name = db_col_strdup(e, stmt, "a.name");
|
|
|
|
e->tag = db_col_strdup(e, stmt, "e.tag");
|
|
|
|
db_col_amount_msat(stmt, "e.credit", &e->credit);
|
|
db_col_amount_msat(stmt, "e.debit", &e->debit);
|
|
db_col_amount_msat(stmt, "e.fees", &e->fees);
|
|
|
|
e->currency = db_col_strdup(e, stmt, "e.currency");
|
|
if (!db_col_is_null(stmt, "e.payment_id")) {
|
|
e->payment_id = tal(e, struct sha256);
|
|
db_col_sha256(stmt, "e.payment_id", e->payment_id);
|
|
} else
|
|
e->payment_id = NULL;
|
|
e->part_id = db_col_int(stmt, "e.part_id");
|
|
e->timestamp = db_col_u64(stmt, "e.timestamp");
|
|
|
|
if (!db_col_is_null(stmt, "e.ev_desc"))
|
|
e->desc = db_col_strdup(e, stmt, "e.ev_desc");
|
|
else
|
|
e->desc = NULL;
|
|
|
|
if (!db_col_is_null(stmt, "e.rebalance_id")) {
|
|
e->rebalance_id = tal(e, u64);
|
|
*e->rebalance_id = db_col_u64(stmt, "e.rebalance_id");
|
|
} else
|
|
e->rebalance_id = NULL;
|
|
|
|
return e;
|
|
}
|
|
|
|
static struct rebalance *stmt2rebalance(const tal_t *ctx, struct db_stmt *stmt)
|
|
{
|
|
struct rebalance *r = tal(ctx, struct rebalance);
|
|
|
|
r->in_ev_id = db_col_u64(stmt, "in_e.id");
|
|
r->out_ev_id = db_col_u64(stmt, "out_e.id");
|
|
r->in_acct_name = db_col_strdup(r, stmt, "in_acct.name");
|
|
r->out_acct_name = db_col_strdup(r, stmt, "out_acct.name");
|
|
db_col_amount_msat(stmt, "in_e.credit", &r->rebal_msat);
|
|
db_col_amount_msat(stmt, "out_e.fees", &r->fee_msat);
|
|
|
|
return r;
|
|
}
|
|
|
|
struct chain_event **list_chain_events_timebox(const tal_t *ctx,
|
|
struct db *db,
|
|
u64 start_time,
|
|
u64 end_time)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE e.timestamp > ?"
|
|
" AND e.timestamp <= ?"
|
|
" ORDER BY e.timestamp, e.id;"));
|
|
|
|
db_bind_u64(stmt, 0, start_time);
|
|
db_bind_u64(stmt, 1, end_time);
|
|
return find_chain_events(ctx, take(stmt));
|
|
}
|
|
|
|
struct chain_event **list_chain_events(const tal_t *ctx, struct db *db)
|
|
{
|
|
return list_chain_events_timebox(ctx, db, 0, SQLITE_MAX_UINT);
|
|
}
|
|
|
|
struct chain_event **account_get_chain_events(const tal_t *ctx,
|
|
struct db *db,
|
|
struct account *acct)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE e.account_id = ?"
|
|
" ORDER BY e.timestamp, e.id"));
|
|
|
|
db_bind_int(stmt, 0, acct->db_id);
|
|
return find_chain_events(ctx, take(stmt));
|
|
}
|
|
|
|
static struct chain_event **find_txos_for_tx(const tal_t *ctx,
|
|
struct db *db,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE e.utxo_txid = ?"
|
|
" ORDER BY "
|
|
" e.utxo_txid"
|
|
", e.outnum"
|
|
", e.spending_txid NULLS FIRST"
|
|
", e.blockheight"));
|
|
|
|
db_bind_txid(stmt, 0, txid);
|
|
return find_chain_events(ctx, take(stmt));
|
|
}
|
|
|
|
struct fee_sum **calculate_onchain_fee_sums(const tal_t *ctx, struct db *db)
|
|
{
|
|
struct db_stmt *stmt;
|
|
struct fee_sum **sums;
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" of.txid"
|
|
", of.account_id"
|
|
", a.name"
|
|
", of.currency"
|
|
", CAST(SUM(of.credit) AS BIGINT) as credit"
|
|
", CAST(SUM(of.debit) AS BIGINT) as debit"
|
|
" FROM onchain_fees of"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON of.account_id = a.id"
|
|
" GROUP BY of.txid"
|
|
", of.account_id"
|
|
", a.name"
|
|
", of.currency"
|
|
" ORDER BY txid, account_id"));
|
|
|
|
db_query_prepared(stmt);
|
|
|
|
sums = tal_arr(ctx, struct fee_sum *, 0);
|
|
while (db_step(stmt)) {
|
|
struct fee_sum *sum;
|
|
struct amount_msat debit;
|
|
bool ok;
|
|
|
|
sum = tal(sums, struct fee_sum);
|
|
sum->txid = tal(sum, struct bitcoin_txid);
|
|
|
|
db_col_txid(stmt, "of.txid", sum->txid);
|
|
sum->acct_db_id = db_col_u64(stmt, "of.account_id");
|
|
sum->acct_name = db_col_strdup(sum, stmt, "a.name");
|
|
sum->currency = db_col_strdup(sum, stmt, "of.currency");
|
|
db_col_amount_msat(stmt, "credit", &sum->fees_paid);
|
|
db_col_amount_msat(stmt, "debit", &debit);
|
|
|
|
ok = amount_msat_sub(&sum->fees_paid, sum->fees_paid,
|
|
debit);
|
|
assert(ok);
|
|
tal_arr_expand(&sums, sum);
|
|
}
|
|
|
|
tal_free(stmt);
|
|
return sums;
|
|
}
|
|
|
|
u64 onchain_fee_last_timestamp(struct db *db,
|
|
u64 acct_db_id,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
struct db_stmt *stmt;
|
|
u64 timestamp;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" timestamp"
|
|
" FROM onchain_fees"
|
|
" WHERE account_id = ?"
|
|
" AND txid = ?"
|
|
" ORDER BY timestamp DESC"));
|
|
|
|
|
|
db_bind_u64(stmt, 0, acct_db_id);
|
|
db_bind_txid(stmt, 1, txid);
|
|
db_query_prepared(stmt);
|
|
|
|
if (db_step(stmt))
|
|
timestamp = db_col_u64(stmt, "timestamp");
|
|
else
|
|
timestamp = 0;
|
|
|
|
tal_free(stmt);
|
|
return timestamp;
|
|
}
|
|
|
|
struct fee_sum **find_account_onchain_fees(const tal_t *ctx,
|
|
struct db *db,
|
|
struct account *acct)
|
|
{
|
|
struct db_stmt *stmt;
|
|
struct fee_sum **sums;
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" txid"
|
|
", CAST(SUM(credit) AS BIGINT) as credit"
|
|
", CAST(SUM(debit) AS BIGINT) as debit"
|
|
" FROM onchain_fees"
|
|
" WHERE account_id = ?"
|
|
" GROUP BY txid"
|
|
" ORDER BY txid, update_count"));
|
|
|
|
db_bind_u64(stmt, 0, acct->db_id);
|
|
db_query_prepared(stmt);
|
|
|
|
sums = tal_arr(ctx, struct fee_sum *, 0);
|
|
while (db_step(stmt)) {
|
|
struct fee_sum *sum;
|
|
struct amount_msat amt;
|
|
bool ok;
|
|
|
|
sum = tal(sums, struct fee_sum);
|
|
sum->acct_db_id = acct->db_id;
|
|
sum->txid = tal(sum, struct bitcoin_txid);
|
|
db_col_txid(stmt, "txid", sum->txid);
|
|
|
|
db_col_amount_msat(stmt, "credit", &sum->fees_paid);
|
|
db_col_amount_msat(stmt, "debit", &amt);
|
|
ok = amount_msat_sub(&sum->fees_paid, sum->fees_paid, amt);
|
|
assert(ok);
|
|
tal_arr_expand(&sums, sum);
|
|
}
|
|
|
|
tal_free(stmt);
|
|
return sums;
|
|
}
|
|
|
|
static struct txo_pair *new_txo_pair(const tal_t *ctx)
|
|
{
|
|
struct txo_pair *pr = tal(ctx, struct txo_pair);
|
|
pr->txo = NULL;
|
|
pr->spend = NULL;
|
|
return pr;
|
|
}
|
|
|
|
static struct txo_set *find_txo_set(const tal_t *ctx,
|
|
struct db *db,
|
|
struct bitcoin_txid *txid,
|
|
u64 *acct_db_id,
|
|
bool *is_complete)
|
|
{
|
|
struct txo_pair *pr;
|
|
struct chain_event **evs;
|
|
struct txo_set *txos = tal(ctx, struct txo_set);
|
|
|
|
/* In some special cases (the opening tx), we only
|
|
* want the outputs that pertain to a given account,
|
|
* most other times we want all utxos, regardless of account */
|
|
evs = find_txos_for_tx(ctx, db, txid);
|
|
txos->pairs = tal_arr(txos, struct txo_pair *, 0);
|
|
txos->txid = tal_dup(txos, struct bitcoin_txid, txid);
|
|
|
|
pr = NULL;
|
|
|
|
/* If there's nothing for this txid, we're missing data */
|
|
if (is_complete)
|
|
*is_complete = tal_count(evs) > 0;
|
|
|
|
for (size_t i = 0; i < tal_count(evs); i++) {
|
|
struct chain_event *ev = evs[i];
|
|
|
|
if (acct_db_id && ev->acct_db_id != *acct_db_id)
|
|
continue;
|
|
|
|
if (ev->spending_txid) {
|
|
if (!pr) {
|
|
/* We're missing data!! */
|
|
pr = new_txo_pair(txos->pairs);
|
|
if (is_complete)
|
|
*is_complete = false;
|
|
} else {
|
|
assert(pr->txo);
|
|
/* Make sure it's the same txo */
|
|
assert(bitcoin_outpoint_eq(&pr->txo->outpoint,
|
|
&ev->outpoint));
|
|
}
|
|
|
|
pr->spend = tal_steal(pr, ev);
|
|
tal_arr_expand(&txos->pairs, pr);
|
|
pr = NULL;
|
|
} else {
|
|
/* We might not have a spend event
|
|
* for everything */
|
|
if (pr) {
|
|
/* Disappear "channel_proposed" events */
|
|
if (streq(pr->txo->tag,
|
|
mvt_tag_str(CHANNEL_PROPOSED)))
|
|
pr = tal_free(pr);
|
|
else
|
|
tal_arr_expand(&txos->pairs, pr);
|
|
}
|
|
pr = new_txo_pair(txos->pairs);
|
|
pr->txo = tal_steal(pr, ev);
|
|
}
|
|
}
|
|
|
|
/* Might have a single entry 'pr' left over */
|
|
if (pr)
|
|
tal_arr_expand(&txos->pairs, pr);
|
|
|
|
return txos;
|
|
}
|
|
|
|
static bool is_channel_acct(struct chain_event *ev)
|
|
{
|
|
return !streq(ev->acct_name, WALLET_ACCT)
|
|
&& !streq(ev->acct_name, EXTERNAL_ACCT);
|
|
}
|
|
|
|
static bool txid_in_list(struct bitcoin_txid **list,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
for (size_t i = 0; i < tal_count(list); i++) {
|
|
if (bitcoin_txid_eq(list[i], txid))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool find_txo_chain(const tal_t *ctx,
|
|
struct db *db,
|
|
struct account *acct,
|
|
struct txo_set ***sets)
|
|
{
|
|
struct bitcoin_txid **txids;
|
|
struct chain_event *open_ev;
|
|
bool is_complete = true;
|
|
u64 *start_acct_id = tal(NULL, u64);
|
|
|
|
assert(acct->open_event_db_id);
|
|
open_ev = find_chain_event_by_id(ctx, db,
|
|
*acct->open_event_db_id);
|
|
|
|
if (sets)
|
|
*sets = tal_arr(ctx, struct txo_set *, 0);
|
|
txids = tal_arr(ctx, struct bitcoin_txid *, 0);
|
|
tal_arr_expand(&txids, &open_ev->outpoint.txid);
|
|
|
|
/* We only want to filter by the account for the very
|
|
* first utxo that we get the tree for, so we
|
|
* start w/ this acct id... */
|
|
*start_acct_id = open_ev->acct_db_id;
|
|
|
|
for (size_t i = 0; i < tal_count(txids); i++) {
|
|
struct txo_set *set;
|
|
bool set_complete;
|
|
|
|
set = find_txo_set(ctx, db, txids[i],
|
|
start_acct_id,
|
|
&set_complete);
|
|
|
|
/* After first use, we free the acct dbid ptr,
|
|
* which will pass in NULL and not filter by
|
|
* account for any subsequent txo_set hunt */
|
|
if (start_acct_id)
|
|
start_acct_id = tal_free(start_acct_id);
|
|
|
|
is_complete &= set_complete;
|
|
for (size_t j = 0; j < tal_count(set->pairs); j++) {
|
|
struct txo_pair *pr = set->pairs[j];
|
|
|
|
/* Has this been resolved? */
|
|
if ((pr->txo
|
|
&& is_channel_acct(pr->txo))
|
|
&& !pr->spend)
|
|
is_complete = false;
|
|
|
|
/* wallet accts and zero-fee-htlc anchors
|
|
* might overlap txids */
|
|
if (pr->spend
|
|
&& pr->spend->spending_txid
|
|
/* 'to_miner' outputs are skipped */
|
|
&& !streq(pr->spend->tag, "to_miner")
|
|
&& !txid_in_list(txids, pr->spend->spending_txid)
|
|
/* We dont trace utxos for non related accts */
|
|
&& (pr->spend->acct_db_id == acct->db_id
|
|
/* Unless it's stealable, in which case
|
|
* we track the resolution of the htlc tx */
|
|
|| pr->spend->stealable))
|
|
tal_arr_expand(&txids,
|
|
pr->spend->spending_txid);
|
|
}
|
|
|
|
if (sets)
|
|
tal_arr_expand(sets, set);
|
|
}
|
|
|
|
return is_complete;
|
|
}
|
|
|
|
struct account *find_close_account(const tal_t *ctx,
|
|
struct db *db,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
struct db_stmt *stmt;
|
|
struct account *close_acct;
|
|
char *acct_name;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" a.name"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE "
|
|
" e.tag = ?"
|
|
" AND e.spending_txid = ?"));
|
|
|
|
db_bind_text(stmt, 0, mvt_tag_str(CHANNEL_CLOSE));
|
|
db_bind_txid(stmt, 1, txid);
|
|
db_query_prepared(stmt);
|
|
|
|
if (db_step(stmt)) {
|
|
acct_name = db_col_strdup(stmt, stmt, "a.name");
|
|
close_acct = find_account(ctx, db, acct_name);
|
|
} else
|
|
close_acct = NULL;
|
|
|
|
tal_free(stmt);
|
|
return close_acct;
|
|
}
|
|
|
|
void maybe_mark_account_onchain(struct db *db, struct account *acct)
|
|
{
|
|
const u8 *ctx = tal(NULL, u8);
|
|
struct txo_set **sets;
|
|
struct chain_event *close_ev;
|
|
struct db_stmt *stmt;
|
|
|
|
assert(acct->closed_count > 0);
|
|
|
|
close_ev = find_chain_event_by_id(ctx, db,
|
|
*acct->closed_event_db_id);
|
|
|
|
if (find_txo_chain(ctx, db, acct, &sets)) {
|
|
/* Ok now we find the max block height of the
|
|
* spending chain_events for this channel */
|
|
bool ok;
|
|
|
|
/* Have we accounted for all the outputs */
|
|
ok = false;
|
|
for (size_t i = 0; i < tal_count(sets); i++) {
|
|
if (bitcoin_txid_eq(sets[i]->txid,
|
|
close_ev->spending_txid)) {
|
|
|
|
ok = tal_count(sets[i]->pairs)
|
|
== acct->closed_count;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
tal_free(ctx);
|
|
return;
|
|
}
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" blockheight"
|
|
" FROM chain_events"
|
|
" WHERE account_id = ?"
|
|
" AND spending_txid IS NOT NULL"
|
|
" ORDER BY blockheight DESC"
|
|
" LIMIT 1"));
|
|
|
|
db_bind_u64(stmt, 0, acct->db_id);
|
|
db_query_prepared(stmt);
|
|
ok = db_step(stmt);
|
|
assert(ok);
|
|
|
|
acct->onchain_resolved_block = db_col_int(stmt, "blockheight");
|
|
tal_free(stmt);
|
|
|
|
/* Ok, now we update the account with this blockheight */
|
|
stmt = db_prepare_v2(db, SQL("UPDATE accounts SET"
|
|
" onchain_resolved_block = ?"
|
|
" WHERE"
|
|
" id = ?"));
|
|
db_bind_int(stmt, 0, acct->onchain_resolved_block);
|
|
db_bind_u64(stmt, 1, acct->db_id);
|
|
db_exec_prepared_v2(take(stmt));
|
|
}
|
|
|
|
tal_free(ctx);
|
|
}
|
|
|
|
void add_payment_hash_desc(struct db *db,
|
|
struct sha256 *payment_hash,
|
|
const char *desc)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
/* Ok, now we update the account with this blockheight */
|
|
stmt = db_prepare_v2(db, SQL("UPDATE channel_events SET"
|
|
" ev_desc = ?"
|
|
" WHERE"
|
|
" payment_id = ?"));
|
|
db_bind_text(stmt, 0, desc);
|
|
db_bind_sha256(stmt, 1, payment_hash);
|
|
db_exec_prepared_v2(take(stmt));
|
|
|
|
/* Ok, now we update the account with this blockheight */
|
|
stmt = db_prepare_v2(db, SQL("UPDATE chain_events SET"
|
|
" ev_desc = ?"
|
|
" WHERE"
|
|
" payment_id = ?"));
|
|
db_bind_text(stmt, 0, desc);
|
|
db_bind_sha256(stmt, 1, payment_hash);
|
|
db_exec_prepared_v2(take(stmt));
|
|
}
|
|
|
|
struct chain_event *find_chain_event_by_id(const tal_t *ctx,
|
|
struct db *db,
|
|
u64 event_db_id)
|
|
{
|
|
struct db_stmt *stmt;
|
|
struct chain_event *e;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE "
|
|
" e.id = ?"));
|
|
|
|
db_bind_u64(stmt, 0, event_db_id);
|
|
db_query_prepared(stmt);
|
|
if (db_step(stmt))
|
|
e = stmt2chain_event(ctx, stmt);
|
|
else
|
|
e = NULL;
|
|
|
|
tal_free(stmt);
|
|
return e;
|
|
}
|
|
|
|
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,
|
|
const char *tag)
|
|
|
|
{
|
|
struct db_stmt *stmt;
|
|
struct chain_event *e;
|
|
|
|
if (spending_txid) {
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE "
|
|
" e.account_id = ?"
|
|
" AND e.utxo_txid = ?"
|
|
" AND e.outnum = ?"
|
|
" AND e.spending_txid = ?"));
|
|
db_bind_txid(stmt, 3, spending_txid);
|
|
} else {
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE "
|
|
" e.account_id = ?"
|
|
" AND e.utxo_txid = ?"
|
|
" AND e.outnum = ?"
|
|
" AND e.spending_txid IS NULL"
|
|
" AND e.tag = ?"));
|
|
|
|
db_bind_text(stmt, 3, tag);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
char *account_get_balance(const tal_t *ctx,
|
|
struct db *db,
|
|
const char *acct_name,
|
|
bool calc_sum,
|
|
bool skip_ignored,
|
|
struct acct_balance ***balances,
|
|
bool *account_exists)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" CAST(SUM(ce.credit) AS BIGINT) as credit"
|
|
", CAST(SUM(ce.debit) AS BIGINT) as debit"
|
|
", ce.currency"
|
|
" FROM chain_events ce"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON a.id = ce.account_id"
|
|
" WHERE a.name = ?"
|
|
" AND ce.ignored != ?"
|
|
" GROUP BY ce.currency"));
|
|
|
|
db_bind_text(stmt, 0, acct_name);
|
|
/* We populate ignored with a 0 or 1,
|
|
* if we want both 0+1, we just ignore everything with a 2 */
|
|
db_bind_int(stmt, 1, skip_ignored ? 1 : 2);
|
|
db_query_prepared(stmt);
|
|
*balances = tal_arr(ctx, struct acct_balance *, 0);
|
|
if (account_exists)
|
|
*account_exists = false;
|
|
|
|
while (db_step(stmt)) {
|
|
struct acct_balance *bal;
|
|
|
|
bal = tal(*balances, struct acct_balance);
|
|
|
|
bal->currency = db_col_strdup(bal, stmt, "ce.currency");
|
|
db_col_amount_msat(stmt, "credit", &bal->credit);
|
|
db_col_amount_msat(stmt, "debit", &bal->debit);
|
|
tal_arr_expand(balances, bal);
|
|
|
|
if (account_exists)
|
|
*account_exists = true;
|
|
}
|
|
tal_free(stmt);
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" CAST(SUM(ce.credit) AS BIGINT) as credit"
|
|
", CAST(SUM(ce.debit) AS BIGINT) as debit"
|
|
", ce.currency"
|
|
" FROM channel_events ce"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON a.id = ce.account_id"
|
|
" WHERE a.name = ?"
|
|
" GROUP BY ce.currency"));
|
|
db_bind_text(stmt, 0, acct_name);
|
|
db_query_prepared(stmt);
|
|
|
|
while (db_step(stmt)) {
|
|
struct amount_msat amt;
|
|
struct acct_balance *bal = NULL;
|
|
char *currency;
|
|
|
|
currency = db_col_strdup(ctx, stmt, "ce.currency");
|
|
|
|
/* Find the currency entry from above */
|
|
for (size_t i = 0; i < tal_count(*balances); i++) {
|
|
if (streq((*balances)[i]->currency, currency)) {
|
|
bal = (*balances)[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bal) {
|
|
bal = tal(*balances, struct acct_balance);
|
|
bal->credit = AMOUNT_MSAT(0);
|
|
bal->debit = AMOUNT_MSAT(0);
|
|
bal->currency = tal_steal(bal, currency);
|
|
tal_arr_expand(balances, bal);
|
|
}
|
|
|
|
db_col_amount_msat(stmt, "credit", &amt);
|
|
if (!amount_msat_add(&bal->credit, bal->credit, amt)) {
|
|
tal_free(stmt);
|
|
return "overflow adding channel_event credits";
|
|
}
|
|
|
|
db_col_amount_msat(stmt, "debit", &amt);
|
|
if (!amount_msat_add(&bal->debit, bal->debit, amt)) {
|
|
tal_free(stmt);
|
|
return "overflow adding channel_event debits";
|
|
}
|
|
}
|
|
tal_free(stmt);
|
|
|
|
if (!calc_sum)
|
|
return NULL;
|
|
|
|
for (size_t i = 0; i < tal_count(*balances); i++) {
|
|
struct acct_balance *bal = (*balances)[i];
|
|
if (!amount_msat_sub(&bal->balance, bal->credit, bal->debit))
|
|
return tal_fmt(ctx,
|
|
"%s channel balance is negative? %s - %s",
|
|
bal->currency,
|
|
type_to_string(ctx, struct amount_msat,
|
|
&bal->credit),
|
|
type_to_string(ctx, struct amount_msat,
|
|
&bal->debit));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct channel_event **list_channel_events_timebox(const tal_t *ctx,
|
|
struct db *db,
|
|
u64 start_time,
|
|
u64 end_time)
|
|
|
|
{
|
|
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"
|
|
", e.ev_desc"
|
|
", e.rebalance_id"
|
|
" FROM channel_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON a.id = e.account_id"
|
|
" WHERE e.timestamp > ?"
|
|
" AND e.timestamp <= ?"
|
|
" ORDER BY e.timestamp, e.id;"));
|
|
|
|
db_bind_u64(stmt, 0, start_time);
|
|
db_bind_u64(stmt, 1, end_time);
|
|
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 **list_channel_events(const tal_t *ctx, struct db *db)
|
|
{
|
|
return list_channel_events_timebox(ctx, db, 0, SQLITE_MAX_UINT);
|
|
}
|
|
|
|
|
|
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"
|
|
" e.id"
|
|
", a.name"
|
|
", e.account_id"
|
|
", e.tag"
|
|
", e.credit"
|
|
", e.debit"
|
|
", e.fees"
|
|
", e.currency"
|
|
", e.payment_id"
|
|
", e.part_id"
|
|
", e.timestamp"
|
|
", e.ev_desc"
|
|
", e.rebalance_id"
|
|
" FROM channel_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON a.id = e.account_id"
|
|
" WHERE e.account_id = ?"
|
|
" ORDER BY e.timestamp, e.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 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, "of.account_id");
|
|
of->acct_name = db_col_strdup(of, stmt, "a.name");
|
|
db_col_txid(stmt, "of.txid", &of->txid);
|
|
db_col_amount_msat(stmt, "of.credit", &of->credit);
|
|
db_col_amount_msat(stmt, "of.debit", &of->debit);
|
|
of->currency = db_col_strdup(of, stmt, "of.currency");
|
|
of->timestamp = db_col_u64(stmt, "of.timestamp");
|
|
of->update_count = db_col_int(stmt, "of.update_count");
|
|
|
|
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_timebox(const tal_t *ctx, struct db *db,
|
|
u64 start_time, u64 end_time)
|
|
{
|
|
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 timestamp > ?"
|
|
" AND timestamp <= ?"
|
|
" ORDER BY "
|
|
" of.timestamp"
|
|
", of.account_id"
|
|
", of.txid"
|
|
", of.update_count"));
|
|
|
|
db_bind_u64(stmt, 0, start_time);
|
|
db_bind_u64(stmt, 1, end_time);
|
|
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)
|
|
{
|
|
return list_chain_fees_timebox(ctx, db, 0, SQLITE_MAX_UINT);
|
|
}
|
|
|
|
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;
|
|
|
|
a->closed_count = db_col_int(stmt, "closed_count");
|
|
|
|
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"
|
|
", closed_count"
|
|
" 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;
|
|
}
|
|
|
|
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"
|
|
" 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 = ?;"));
|
|
|
|
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 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"
|
|
", closed_count"
|
|
" 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,
|
|
u32 closed_count,
|
|
struct node_id *peer_id)
|
|
{
|
|
struct db_stmt *stmt;
|
|
bool updated = false;
|
|
|
|
for (size_t i = 0; i < tal_count(tags); i++) {
|
|
switch (tags[i]) {
|
|
case CHANNEL_PROPOSED:
|
|
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:
|
|
case STEALABLE:
|
|
/* Ignored */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (peer_id) {
|
|
updated = true;
|
|
acct->peer_id = tal_dup(acct, struct node_id, peer_id);
|
|
}
|
|
|
|
if (closed_count > 0) {
|
|
updated = true;
|
|
acct->closed_count = closed_count;
|
|
}
|
|
|
|
/* 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 = ?"
|
|
", closed_count = ?"
|
|
", peer_id = ?"
|
|
" 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_int(stmt, 4, acct->closed_count);
|
|
if (acct->peer_id)
|
|
db_bind_node_id(stmt, 5, acct->peer_id);
|
|
else
|
|
db_bind_null(stmt, 5);
|
|
|
|
db_bind_text(stmt, 6, 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"
|
|
", ev_desc"
|
|
", rebalance_id"
|
|
")"
|
|
" 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);
|
|
if (e->payment_id)
|
|
db_bind_sha256(stmt, 6, e->payment_id);
|
|
else
|
|
db_bind_null(stmt, 6);
|
|
db_bind_int(stmt, 7, e->part_id);
|
|
db_bind_u64(stmt, 8, e->timestamp);
|
|
if (e->desc)
|
|
db_bind_text(stmt, 9, e->desc);
|
|
else
|
|
db_bind_null(stmt, 9);
|
|
|
|
if (e->rebalance_id)
|
|
db_bind_u64(stmt, 10, *e->rebalance_id);
|
|
else
|
|
db_bind_null(stmt, 10);
|
|
|
|
db_exec_prepared_v2(stmt);
|
|
e->db_id = db_last_insert_id_v2(stmt);
|
|
e->acct_db_id = acct->db_id;
|
|
e->acct_name = tal_strdup(e, acct->name);
|
|
tal_free(stmt);
|
|
}
|
|
|
|
static struct chain_event **find_chain_events_bytxid(const tal_t *ctx, struct db *db,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT "
|
|
" e.id"
|
|
", e.account_id"
|
|
", a.name"
|
|
", e.origin"
|
|
", 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"
|
|
", e.ignored"
|
|
", e.stealable"
|
|
", e.ev_desc"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON a.id = e.account_id"
|
|
" WHERE e.spending_txid = ?"
|
|
" OR (e.utxo_txid = ? AND e.spending_txid IS NULL)"
|
|
" ORDER BY e.account_id"));
|
|
|
|
db_bind_txid(stmt, 0, txid);
|
|
db_bind_txid(stmt, 1, txid);
|
|
return find_chain_events(ctx, take(stmt));
|
|
}
|
|
|
|
static u64 find_acct_id(struct db *db, const char *name)
|
|
{
|
|
u64 acct_id;
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" id"
|
|
" FROM accounts"
|
|
" WHERE name = ?"));
|
|
|
|
db_bind_text(stmt, 0, name);
|
|
db_query_prepared(stmt);
|
|
if (db_step(stmt))
|
|
acct_id = db_col_u64(stmt, "id");
|
|
else
|
|
acct_id = 0;
|
|
|
|
tal_free(stmt);
|
|
return acct_id;
|
|
}
|
|
|
|
static void insert_chain_fees_diff(struct db *db,
|
|
u64 acct_id,
|
|
struct bitcoin_txid *txid,
|
|
struct amount_msat amount,
|
|
const char *currency,
|
|
u64 timestamp)
|
|
{
|
|
struct db_stmt *stmt;
|
|
u32 update_count;
|
|
struct amount_msat current_amt, credit, debit;
|
|
|
|
/* First, look to see if there's an already existing
|
|
* record to update */
|
|
stmt = db_prepare_v2(db, SQL("SELECT"
|
|
" update_count"
|
|
", credit"
|
|
", debit"
|
|
" FROM onchain_fees"
|
|
" WHERE txid = ?"
|
|
" AND account_id = ?"
|
|
" ORDER BY update_count"));
|
|
|
|
db_bind_txid(stmt, 0, txid);
|
|
db_bind_u64(stmt, 1, acct_id);
|
|
db_query_prepared(stmt);
|
|
|
|
/* If there's no current record, add it */
|
|
current_amt = AMOUNT_MSAT(0);
|
|
update_count = 0;
|
|
while (db_step(stmt)) {
|
|
update_count = db_col_int(stmt, "update_count");
|
|
db_col_amount_msat(stmt, "credit", &credit);
|
|
db_col_amount_msat(stmt, "debit", &debit);
|
|
|
|
/* These should apply perfectly, as we sorted them by
|
|
* insert order */
|
|
if (!amount_msat_add(¤t_amt, current_amt, credit))
|
|
db_fatal("Overflow when adding onchain fees");
|
|
|
|
if (!amount_msat_sub(¤t_amt, current_amt, debit))
|
|
db_fatal("Underflow when subtracting onchain fees");
|
|
|
|
}
|
|
tal_free(stmt);
|
|
|
|
/* If they're already equal, no need to update */
|
|
if (amount_msat_eq(current_amt, amount))
|
|
return;
|
|
|
|
if (!amount_msat_sub(&credit, amount, current_amt)) {
|
|
credit = AMOUNT_MSAT(0);
|
|
if (!amount_msat_sub(&debit, current_amt, amount))
|
|
db_fatal("shouldn't happen, unable to subtract");
|
|
} else
|
|
debit = AMOUNT_MSAT(0);
|
|
|
|
stmt = db_prepare_v2(db, SQL("INSERT INTO onchain_fees"
|
|
" ("
|
|
" account_id"
|
|
", txid"
|
|
", credit"
|
|
", debit"
|
|
", currency"
|
|
", timestamp"
|
|
", update_count"
|
|
") VALUES"
|
|
" (?, ?, ?, ?, ?, ?, ?);"));
|
|
|
|
db_bind_u64(stmt, 0, acct_id);
|
|
db_bind_txid(stmt, 1, txid);
|
|
db_bind_amount_msat(stmt, 2, &credit);
|
|
db_bind_amount_msat(stmt, 3, &debit);
|
|
db_bind_text(stmt, 4, currency);
|
|
db_bind_u64(stmt, 5, timestamp);
|
|
db_bind_int(stmt, 6, ++update_count);
|
|
db_exec_prepared_v2(take(stmt));
|
|
}
|
|
|
|
char *update_channel_onchain_fees(const tal_t *ctx,
|
|
struct db *db,
|
|
struct account *acct)
|
|
{
|
|
struct chain_event *close_ev, **events;
|
|
struct amount_msat onchain_amt;
|
|
|
|
assert(acct->onchain_resolved_block);
|
|
close_ev = find_chain_event_by_id(ctx, db,
|
|
*acct->closed_event_db_id);
|
|
events = find_chain_events_bytxid(ctx, db,
|
|
close_ev->spending_txid);
|
|
|
|
/* Starting balance is close-ev's debit amount */
|
|
onchain_amt = AMOUNT_MSAT(0);
|
|
for (size_t i = 0; i < tal_count(events); i++) {
|
|
struct chain_event *ev = events[i];
|
|
|
|
/* Ignore:
|
|
- htlc_fufill (to me)
|
|
- anchors (already exlc from output)
|
|
- to_external (if !htlc_fulfill)
|
|
*/
|
|
if (is_channel_acct(ev)
|
|
&& streq("htlc_fulfill", ev->tag))
|
|
continue;
|
|
|
|
if (streq("anchor", ev->tag))
|
|
continue;
|
|
|
|
/* Ignore stuff it's paid to
|
|
* the peer's account (external),
|
|
* except for fulfilled htlcs (which originated
|
|
* in our balance) */
|
|
if (streq(ev->acct_name, EXTERNAL_ACCT)
|
|
&& !streq("htlc_fulfill", ev->tag))
|
|
continue;
|
|
|
|
/* anything else we count? */
|
|
if (!amount_msat_add(&onchain_amt, onchain_amt,
|
|
ev->credit))
|
|
return tal_fmt(ctx, "Unable to add"
|
|
"onchain + %s's credit",
|
|
ev->tag);
|
|
}
|
|
|
|
/* Was this an 'old state' tx, where we ended up
|
|
* with more sats than we had on record? */
|
|
if (amount_msat_greater(onchain_amt, close_ev->debit)) {
|
|
struct channel_event *ev;
|
|
struct amount_msat diff;
|
|
|
|
if (!amount_msat_sub(&diff, onchain_amt,
|
|
close_ev->debit))
|
|
return tal_fmt(ctx, "Unable to sub"
|
|
"close debit from onchain_amt");
|
|
/* Add in/out journal entries for it */
|
|
ev = new_channel_event(ctx,
|
|
tal_fmt(tmpctx, "%s",
|
|
account_entry_tag_str(PENALTY_ADJ)),
|
|
diff,
|
|
AMOUNT_MSAT(0),
|
|
AMOUNT_MSAT(0),
|
|
close_ev->currency,
|
|
NULL, 0,
|
|
close_ev->timestamp);
|
|
log_channel_event(db, acct, ev);
|
|
ev = new_channel_event(ctx,
|
|
tal_fmt(tmpctx, "%s",
|
|
account_entry_tag_str(PENALTY_ADJ)),
|
|
AMOUNT_MSAT(0),
|
|
diff,
|
|
AMOUNT_MSAT(0),
|
|
close_ev->currency,
|
|
NULL, 0,
|
|
close_ev->timestamp);
|
|
log_channel_event(db, acct, ev);
|
|
} else {
|
|
struct amount_msat fees;
|
|
if (!amount_msat_sub(&fees, close_ev->debit,
|
|
onchain_amt))
|
|
return tal_fmt(ctx, "Unable to sub"
|
|
"onchain sum from %s",
|
|
close_ev->tag);
|
|
|
|
insert_chain_fees_diff(db, acct->db_id,
|
|
close_ev->spending_txid,
|
|
fees, close_ev->currency,
|
|
close_ev->timestamp);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *is_closed_channel_txid(const tal_t *ctx, struct db *db,
|
|
struct chain_event *ev,
|
|
struct bitcoin_txid *txid,
|
|
bool *is_channel_close_tx)
|
|
{
|
|
struct account *acct;
|
|
struct chain_event *closed;
|
|
u8 *inner_ctx = tal(NULL, u8);
|
|
|
|
/* Figure out if this is a channel close tx */
|
|
acct = find_account(inner_ctx, db, ev->acct_name);
|
|
assert(acct);
|
|
|
|
/* There's a separate process for figuring out
|
|
* our onchain fees for channel closures */
|
|
if (!acct->closed_event_db_id) {
|
|
*is_channel_close_tx = false;
|
|
tal_free(inner_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
/* is the closed utxo the same as the one
|
|
* we're trying to find fees for now */
|
|
closed = find_chain_event_by_id(inner_ctx, db,
|
|
*acct->closed_event_db_id);
|
|
if (!closed) {
|
|
*is_channel_close_tx = false;
|
|
tal_free(inner_ctx);
|
|
return tal_fmt(ctx, "Unable to find"
|
|
" db record (chain_evt)"
|
|
" with id %"PRIu64,
|
|
*acct->closed_event_db_id);
|
|
}
|
|
|
|
if (!closed->spending_txid) {
|
|
*is_channel_close_tx = false;
|
|
tal_free(inner_ctx);
|
|
return tal_fmt(ctx, "Marked a closing"
|
|
" event that's not"
|
|
" actually a spend");
|
|
}
|
|
|
|
*is_channel_close_tx =
|
|
bitcoin_txid_eq(txid, closed->spending_txid);
|
|
tal_free(inner_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
void maybe_record_rebalance(struct db *db,
|
|
struct channel_event *out)
|
|
{
|
|
/* If there's a matching credit event, this is
|
|
* a rebalance. Mark everything with the payment_id
|
|
* and amt as such. If you repeat a payment_id
|
|
* with the same amt, they'll be marked as rebalances
|
|
* also */
|
|
struct db_stmt *stmt;
|
|
struct amount_msat credit;
|
|
bool ok;
|
|
|
|
/* The amount of we were credited is debit - fees */
|
|
ok = amount_msat_sub(&credit, out->debit, out->fees);
|
|
assert(ok);
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT "
|
|
" e.id"
|
|
" FROM channel_events e"
|
|
" WHERE e.payment_id = ?"
|
|
" AND e.credit = ?"
|
|
" AND e.rebalance_id IS NULL"));
|
|
|
|
db_bind_sha256(stmt, 0, out->payment_id);
|
|
db_bind_amount_msat(stmt, 1, &credit);
|
|
db_query_prepared(stmt);
|
|
|
|
if (!db_step(stmt)) {
|
|
/* No matching invoice found */
|
|
tal_free(stmt);
|
|
return;
|
|
}
|
|
|
|
/* We just take the first one */
|
|
out->rebalance_id = tal(out, u64);
|
|
*out->rebalance_id = db_col_u64(stmt, "e.id");
|
|
tal_free(stmt);
|
|
|
|
/* Set rebalance flag on both records */
|
|
stmt = db_prepare_v2(db, SQL("UPDATE channel_events SET"
|
|
" rebalance_id = ?"
|
|
" WHERE"
|
|
" id = ?"));
|
|
db_bind_u64(stmt, 0, *out->rebalance_id);
|
|
db_bind_u64(stmt, 1, out->db_id);
|
|
db_exec_prepared_v2(take(stmt));
|
|
|
|
stmt = db_prepare_v2(db, SQL("UPDATE channel_events SET"
|
|
" rebalance_id = ?"
|
|
" WHERE"
|
|
" id = ?"));
|
|
db_bind_u64(stmt, 0, out->db_id);
|
|
db_bind_u64(stmt, 1, *out->rebalance_id);
|
|
db_exec_prepared_v2(take(stmt));
|
|
}
|
|
|
|
struct rebalance **list_rebalances(const tal_t *ctx, struct db *db)
|
|
{
|
|
struct rebalance **result;
|
|
struct db_stmt *stmt;
|
|
|
|
stmt = db_prepare_v2(db, SQL("SELECT "
|
|
" in_e.id"
|
|
", out_e.id"
|
|
", in_acct.name"
|
|
", out_acct.name"
|
|
", in_e.credit"
|
|
", out_e.fees"
|
|
" FROM channel_events in_e"
|
|
" LEFT OUTER JOIN channel_events out_e"
|
|
" ON in_e.rebalance_id = out_e.id"
|
|
" LEFT OUTER JOIN accounts out_acct"
|
|
" ON out_acct.id = out_e.account_id"
|
|
" LEFT OUTER JOIN accounts in_acct"
|
|
" ON in_acct.id = in_e.account_id"
|
|
" WHERE in_e.rebalance_id IS NOT NULL"
|
|
" AND in_e.credit > 0"));
|
|
db_query_prepared(stmt);
|
|
result = tal_arr(ctx, struct rebalance *, 0);
|
|
while (db_step(stmt)) {
|
|
struct rebalance *r = stmt2rebalance(result, stmt);
|
|
tal_arr_expand(&result, r);
|
|
}
|
|
tal_free(stmt);
|
|
return result;
|
|
}
|
|
|
|
char *maybe_update_onchain_fees(const tal_t *ctx, struct db *db,
|
|
struct bitcoin_txid *txid)
|
|
{
|
|
size_t no_accts = 0, plus_ones;
|
|
u64 last_id = 0, wallet_id, extern_id;
|
|
bool contains_wallet = false, skip_wallet = false;
|
|
struct chain_event **events;
|
|
struct amount_msat deposit_msat = AMOUNT_MSAT(0),
|
|
withdraw_msat = AMOUNT_MSAT(0),
|
|
fees_msat, fee_part_msat;
|
|
char *err = NULL;
|
|
u8 *inner_ctx = tal(NULL, u8);
|
|
|
|
/* Find all the deposits/withdrawals for this txid */
|
|
events = find_chain_events_bytxid(inner_ctx, db, txid);
|
|
wallet_id = find_acct_id(db, WALLET_ACCT);
|
|
extern_id = find_acct_id(db, EXTERNAL_ACCT);
|
|
|
|
/* If we don't even have two events, skip */
|
|
if (tal_count(events) < 2)
|
|
goto finished;
|
|
|
|
for (size_t i = 0; i < tal_count(events); i++) {
|
|
bool is_channel_close_tx;
|
|
err = is_closed_channel_txid(ctx, db,
|
|
events[i], txid,
|
|
&is_channel_close_tx);
|
|
|
|
if (err)
|
|
goto finished;
|
|
|
|
/* We skip channel close txs here! */
|
|
if (is_channel_close_tx)
|
|
goto finished;
|
|
|
|
if (events[i]->spending_txid) {
|
|
if (!amount_msat_add(&withdraw_msat, withdraw_msat,
|
|
events[i]->debit)) {
|
|
err = tal_fmt(ctx, "Overflow adding withdrawal debits for"
|
|
" txid: %s",
|
|
type_to_string(ctx, struct bitcoin_txid,
|
|
txid));
|
|
goto finished;
|
|
}
|
|
} else {
|
|
if (!amount_msat_add(&deposit_msat, deposit_msat,
|
|
events[i]->credit)) {
|
|
err = tal_fmt(ctx, "Overflow adding deposit credits for"
|
|
" txid: %s",
|
|
type_to_string(ctx, struct bitcoin_txid,
|
|
txid));
|
|
goto finished;
|
|
}
|
|
}
|
|
|
|
/* While we're here, also count number of accts
|
|
* that were involved! Two little tricks here.
|
|
*
|
|
* One) we sorted the output
|
|
* by acct id, so we can cheat how we count: if
|
|
* it's a different acct_id than the last seen, we inc
|
|
* the counter.
|
|
*
|
|
* Two) who "gets" fee attribution is complicated
|
|
* and requires knowing if the wallet/external accts
|
|
* were involved (everything else is channel accts)
|
|
* */
|
|
if (last_id != events[i]->acct_db_id) {
|
|
last_id = events[i]->acct_db_id;
|
|
/* Don't count external accts */
|
|
if (last_id != extern_id)
|
|
no_accts++;
|
|
|
|
contains_wallet |= (last_id == wallet_id);
|
|
}
|
|
}
|
|
|
|
/* Only affects external accounts, we can ignore */
|
|
if (no_accts == 0)
|
|
goto finished;
|
|
|
|
/* If either is zero, keep waiting */
|
|
if (amount_msat_zero(withdraw_msat)
|
|
|| amount_msat_zero(deposit_msat))
|
|
goto finished;
|
|
|
|
/* If our withdraws < deposits, wait for more data */
|
|
if (amount_msat_less(withdraw_msat, deposit_msat))
|
|
goto finished;
|
|
|
|
if (!amount_msat_sub(&fees_msat, withdraw_msat, deposit_msat)) {
|
|
err = tal_fmt(ctx, "Err subtracting withdraw %s from deposit %s"
|
|
" for txid %s",
|
|
type_to_string(ctx, struct amount_msat, &withdraw_msat),
|
|
type_to_string(ctx, struct amount_msat, &deposit_msat),
|
|
type_to_string(ctx, struct bitcoin_txid, txid));
|
|
goto finished;
|
|
}
|
|
|
|
/* Now we need to figure out how to allocate fees to each account
|
|
* that was involved in the tx. This is a lil complex, buckle up*/
|
|
|
|
/* If the wallet's involved + there were any other accounts, decr by one */
|
|
if (no_accts > 1 && contains_wallet) {
|
|
skip_wallet = true;
|
|
no_accts--;
|
|
}
|
|
|
|
/* Now we divide by the number of accts involved, to figure out the
|
|
* value to log for each account! */
|
|
fee_part_msat = amount_msat_div(fees_msat, no_accts);
|
|
|
|
/* So we don't lose any msats b/c of rounding, find the number of
|
|
* accts to add an extra msat onto */
|
|
plus_ones = fees_msat.millisatoshis % no_accts; /* Raw: mod calc */
|
|
|
|
/* Now we log (or update the existing record) for each acct */
|
|
last_id = 0;
|
|
for (size_t i = 0; i < tal_count(events); i++) {
|
|
struct amount_msat fees;
|
|
|
|
if (last_id == events[i]->acct_db_id)
|
|
continue;
|
|
|
|
last_id = events[i]->acct_db_id;
|
|
|
|
/* We *never* assign fees to external accounts;
|
|
* if external funds were contributed to a tx
|
|
* we wouldn't record it -- fees are solely ours */
|
|
if (last_id == extern_id)
|
|
continue;
|
|
|
|
/* We only attribute fees to the wallet
|
|
* if the wallet is the only game in town */
|
|
if (skip_wallet && last_id == wallet_id) {
|
|
/* But we might need to clean up any fees assigned
|
|
* to the wallet from a previous round, where it
|
|
* *was* the only game in town */
|
|
insert_chain_fees_diff(db, last_id, txid,
|
|
AMOUNT_MSAT(0),
|
|
events[i]->currency,
|
|
events[i]->timestamp);
|
|
continue;
|
|
}
|
|
|
|
/* Add an extra msat onto plus_ones accts
|
|
* so we don't lose any precision in
|
|
* our accounting */
|
|
if (plus_ones > 0) {
|
|
plus_ones--;
|
|
if (!amount_msat_add(&fees, fee_part_msat,
|
|
AMOUNT_MSAT(1))) {
|
|
err = "Overflow adding 1 ... yeah right";
|
|
/* We're gonna keep going, yolo */
|
|
fees = fee_part_msat;
|
|
}
|
|
} else
|
|
fees = fee_part_msat;
|
|
|
|
/* FIXME: fee_currency property of acct? */
|
|
insert_chain_fees_diff(db, last_id, txid, fees,
|
|
events[i]->currency,
|
|
events[i]->timestamp);
|
|
|
|
}
|
|
|
|
finished:
|
|
tal_free(inner_ctx);
|
|
return err;
|
|
}
|
|
|
|
void maybe_closeout_external_deposits(struct db *db,
|
|
struct chain_event *ev)
|
|
{
|
|
struct db_stmt *stmt;
|
|
|
|
assert(ev->spending_txid);
|
|
stmt = db_prepare_v2(db, SQL("SELECT "
|
|
" e.id"
|
|
" FROM chain_events e"
|
|
" LEFT OUTER JOIN accounts a"
|
|
" ON e.account_id = a.id"
|
|
" WHERE e.blockheight = ?"
|
|
" AND e.utxo_txid = ?"
|
|
" AND a.name = ?"));
|
|
|
|
/* Blockheight for unconfirmeds is zero */
|
|
db_bind_int(stmt, 0, 0);
|
|
db_bind_txid(stmt, 1, ev->spending_txid);
|
|
db_bind_text(stmt, 2, EXTERNAL_ACCT);
|
|
db_query_prepared(stmt);
|
|
|
|
while (db_step(stmt)) {
|
|
struct db_stmt *update_stmt;
|
|
u64 id;
|
|
|
|
id = db_col_u64(stmt, "e.id");
|
|
update_stmt = db_prepare_v2(db, SQL("UPDATE chain_events SET"
|
|
" blockheight = ?"
|
|
" WHERE id = ?"));
|
|
|
|
db_bind_int(update_stmt, 0, ev->blockheight);
|
|
db_bind_u64(update_stmt, 1, id);
|
|
db_exec_prepared_v2(take(update_stmt));
|
|
}
|
|
|
|
tal_free(stmt);
|
|
}
|
|
|
|
bool 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,
|
|
e->tag))
|
|
return false;
|
|
|
|
stmt = db_prepare_v2(db, SQL("INSERT INTO chain_events"
|
|
" ("
|
|
" account_id"
|
|
", origin"
|
|
", tag"
|
|
", credit"
|
|
", debit"
|
|
", output_value"
|
|
", currency"
|
|
", timestamp"
|
|
", blockheight"
|
|
", utxo_txid"
|
|
", outnum"
|
|
", payment_id"
|
|
", spending_txid"
|
|
", ignored"
|
|
", stealable"
|
|
", ev_desc"
|
|
")"
|
|
" VALUES "
|
|
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"));
|
|
|
|
db_bind_u64(stmt, 0, acct->db_id);
|
|
if (e->origin_acct)
|
|
db_bind_text(stmt, 1, e->origin_acct);
|
|
else
|
|
db_bind_null(stmt, 1);
|
|
db_bind_text(stmt, 2, e->tag);
|
|
db_bind_amount_msat(stmt, 3, &e->credit);
|
|
db_bind_amount_msat(stmt, 4, &e->debit);
|
|
db_bind_amount_msat(stmt, 5, &e->output_value);
|
|
db_bind_text(stmt, 6, e->currency);
|
|
db_bind_u64(stmt, 7, e->timestamp);
|
|
db_bind_int(stmt, 8, e->blockheight);
|
|
db_bind_txid(stmt, 9, &e->outpoint.txid);
|
|
db_bind_int(stmt, 10, e->outpoint.n);
|
|
|
|
if (e->payment_id)
|
|
db_bind_sha256(stmt, 11, e->payment_id);
|
|
else
|
|
db_bind_null(stmt, 11);
|
|
|
|
if (e->spending_txid)
|
|
db_bind_txid(stmt, 12, e->spending_txid);
|
|
else
|
|
db_bind_null(stmt, 12);
|
|
|
|
db_bind_int(stmt, 13, e->ignored ? 1 : 0);
|
|
db_bind_int(stmt, 14, e->stealable ? 1 : 0);
|
|
if (e->desc)
|
|
db_bind_text(stmt, 15, e->desc);
|
|
else
|
|
db_bind_null(stmt, 15);
|
|
db_exec_prepared_v2(stmt);
|
|
e->db_id = db_last_insert_id_v2(stmt);
|
|
e->acct_db_id = acct->db_id;
|
|
e->acct_name = tal_strdup(e, acct->name);
|
|
tal_free(stmt);
|
|
return true;
|
|
}
|