Files
palladum-lightning/lightningd/coin_mvts.c
Rusty Russell 8b9020d7b9 global: use clock_time in place of time_now().
Except for tracing, that sticks with time_now().

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-11-13 21:21:29 +10:30

497 lines
15 KiB
C

#include "config.h"
#include <ccan/tal/str/str.h>
#include <common/clock_time.h>
#include <common/json_command.h>
#include <lightningd/channel.h>
#include <lightningd/coin_mvts.h>
#include <lightningd/notification.h>
struct channel_coin_mvt *new_channel_mvt_invoice_hin(const tal_t *ctx,
const struct htlc_in *hin,
const struct channel *channel)
{
return new_channel_coin_mvt(ctx, channel, coinmvt_current_time(),
&hin->payment_hash, NULL, NULL,
COIN_CREDIT, hin->msat,
mk_mvt_tags(MVT_INVOICE),
AMOUNT_MSAT(0));
}
struct channel_coin_mvt *new_channel_mvt_routed_hin(const tal_t *ctx,
const struct htlc_in *hin,
const struct channel *channel)
{
struct amount_msat fees_collected;
if (!hin->payload)
return NULL;
if (!amount_msat_sub(&fees_collected, hin->msat,
hin->payload->amt_to_forward))
return NULL;
return new_channel_coin_mvt(ctx, channel, coinmvt_current_time(),
&hin->payment_hash, NULL, NULL,
COIN_CREDIT, hin->msat,
mk_mvt_tags(MVT_ROUTED),
fees_collected);
}
struct channel_coin_mvt *new_channel_mvt_invoice_hout(const tal_t *ctx,
const struct htlc_out *hout,
const struct channel *channel)
{
return new_channel_coin_mvt(ctx, channel, coinmvt_current_time(),
&hout->payment_hash,
&hout->partid,
&hout->groupid,
COIN_DEBIT, hout->msat,
mk_mvt_tags(MVT_INVOICE),
hout->fees);
}
struct channel_coin_mvt *new_channel_mvt_routed_hout(const tal_t *ctx,
const struct htlc_out *hout,
const struct channel *channel)
{
return new_channel_coin_mvt(ctx, channel, coinmvt_current_time(),
&hout->payment_hash, NULL, NULL,
COIN_DEBIT, hout->msat,
mk_mvt_tags(MVT_ROUTED),
hout->fees);
}
struct channel_coin_mvt *new_channel_mvt_penalty_adj(const tal_t *ctx,
const struct channel *channel,
struct amount_msat amount,
enum coin_mvt_dir direction)
{
return new_channel_coin_mvt(ctx, channel, coinmvt_current_time(),
NULL, NULL, NULL,
direction, amount,
mk_mvt_tags(MVT_PENALTY_ADJ),
AMOUNT_MSAT(0));
}
static bool report_chan_balance(const struct channel *chan)
{
switch (chan->state) {
case CHANNELD_AWAITING_LOCKIN:
case DUALOPEND_OPEN_INIT:
case DUALOPEND_OPEN_COMMIT_READY:
case DUALOPEND_OPEN_COMMITTED:
case DUALOPEND_AWAITING_LOCKIN:
case CLOSINGD_COMPLETE:
case AWAITING_UNILATERAL:
case ONCHAIN:
case CLOSED:
return false;
case CHANNELD_NORMAL:
case CHANNELD_AWAITING_SPLICE:
case CHANNELD_SHUTTING_DOWN:
case CLOSINGD_SIGEXCHANGE:
case FUNDING_SPEND_SEEN:
return true;
}
abort();
}
void send_account_balance_snapshot(struct lightningd *ld)
{
struct balance_snapshot *snap = tal(NULL, struct balance_snapshot);
struct account_balance *bal;
struct utxo **utxos;
struct channel *chan;
struct peer *p;
struct peer_node_id_map_iter it;
snap->blockheight = get_block_height(ld->topology);
snap->timestamp = coinmvt_current_time();
snap->node_id = &ld->our_nodeid;
/* Add the 'wallet' account balance */
snap->accts = tal_arr(snap, struct account_balance *, 1);
bal = tal(snap, struct account_balance);
bal->balance = AMOUNT_MSAT(0);
bal->acct_id = ACCOUNT_NAME_WALLET;
bal->bip173_name = chainparams->lightning_hrp;
utxos = wallet_get_unspent_utxos(NULL, ld->wallet);
for (size_t j = 0; j < tal_count(utxos); j++) {
/* Don't count unconfirmed utxos! */
if (!utxos[j]->spendheight && !utxos[j]->blockheight)
continue;
if (!amount_msat_add_sat(&bal->balance,
bal->balance, utxos[j]->amount))
fatal("Overflow adding node balance");
}
tal_free(utxos);
snap->accts[0] = bal;
/* Add channel balances */
for (p = peer_node_id_map_first(ld->peers, &it);
p;
p = peer_node_id_map_next(ld->peers, &it)) {
list_for_each(&p->channels, chan, list) {
if (report_chan_balance(chan)) {
bal = tal(snap, struct account_balance);
bal->bip173_name = chainparams->lightning_hrp;
bal->acct_id = fmt_channel_id(bal, &chan->cid);
bal->balance = chan->our_msat;
tal_arr_expand(&snap->accts, bal);
}
}
}
notify_balance_snapshot(ld, snap);
tal_free(snap);
}
static void add_movement_tags(struct json_stream *stream,
bool include_tags_arr,
const struct mvt_tags tags,
bool extra_tags_field)
{
const char **tagstrs = mvt_tag_strs(tmpctx, tags);
if (include_tags_arr) {
json_array_start(stream, "tags");
for (size_t i = 0; i < tal_count(tagstrs); i++)
json_add_string(stream, NULL, tagstrs[i]);
json_array_end(stream);
}
json_add_string(stream, "primary_tag", tagstrs[0]);
if (extra_tags_field) {
json_array_start(stream, "extra_tags");
for (size_t i = 1; i < tal_count(tagstrs); i++)
json_add_string(stream, NULL, tagstrs[i]);
json_array_end(stream);
} else {
assert(tal_count(tagstrs) == 1);
}
}
static void json_add_mvt_account_id(struct json_stream *stream,
const char *fieldname,
const struct mvt_account_id *account_id)
{
if (account_id->channel)
json_add_channel_id(stream, fieldname, &account_id->channel->cid);
else
json_add_string(stream, fieldname, account_id->alt_account);
}
void json_add_chain_mvt_fields(struct json_stream *stream,
bool include_tags_arr,
bool include_old_utxo_fields,
bool include_old_txid_field,
const struct chain_coin_mvt *chain_mvt,
u64 id)
{
/* Fields in common with channel moves go first */
json_add_u64(stream, "created_index", id);
json_add_mvt_account_id(stream, "account_id", &chain_mvt->account);
json_add_amount_msat(stream, "credit_msat", chain_mvt->credit);
json_add_amount_msat(stream, "debit_msat", chain_mvt->debit);
json_add_u64(stream, "timestamp", chain_mvt->timestamp);
add_movement_tags(stream, include_tags_arr, chain_mvt->tags, true);
json_add_outpoint(stream, "utxo", &chain_mvt->outpoint);
if (chain_mvt->peer_id)
json_add_node_id(stream, "peer_id", chain_mvt->peer_id);
if (chain_mvt->originating_acct)
json_add_mvt_account_id(stream, "originating_account", chain_mvt->originating_acct);
if (chain_mvt->spending_txid) {
if (include_old_txid_field)
json_add_txid(stream, "txid",
chain_mvt->spending_txid);
json_add_txid(stream, "spending_txid", chain_mvt->spending_txid);
}
if (include_old_utxo_fields) {
json_add_string(stream, "utxo_txid",
fmt_bitcoin_txid(tmpctx,
&chain_mvt->outpoint.txid));
json_add_u32(stream, "vout", chain_mvt->outpoint.n);
}
/* on-chain htlcs include a payment hash */
if (chain_mvt->payment_hash)
json_add_sha256(stream, "payment_hash", chain_mvt->payment_hash);
json_add_amount_sat_msat(stream,
"output_msat", chain_mvt->output_val);
if (chain_mvt->output_count > 0)
json_add_num(stream, "output_count", chain_mvt->output_count);
json_add_u32(stream, "blockheight", chain_mvt->blockheight);
}
void json_add_channel_mvt_fields(struct json_stream *stream,
bool include_tags_arr,
const struct channel_coin_mvt *chan_mvt,
u64 id,
bool extra_tags_field)
{
/* Fields in common with chain moves go first */
json_add_u64(stream, "created_index", id);
json_add_mvt_account_id(stream, "account_id", &chan_mvt->account);
json_add_amount_msat(stream, "credit_msat", chan_mvt->credit);
json_add_amount_msat(stream, "debit_msat", chan_mvt->debit);
json_add_u64(stream, "timestamp", chan_mvt->timestamp);
add_movement_tags(stream, include_tags_arr, chan_mvt->tags, extra_tags_field);
/* push funding / leases don't have a payment_hash */
if (chan_mvt->payment_hash)
json_add_sha256(stream, "payment_hash", chan_mvt->payment_hash);
if (chan_mvt->part_and_group) {
json_add_u64(stream, "part_id", chan_mvt->part_and_group->part_id);
json_add_u64(stream, "group_id", chan_mvt->part_and_group->group_id);
}
json_add_amount_msat(stream, "fees_msat", chan_mvt->fees);
}
static u64 coinmvt_index_inc(struct lightningd *ld,
struct db *db,
enum wait_subsystem subsys,
const struct mvt_account_id *account,
struct amount_msat credit,
struct amount_msat debit,
enum wait_index idx)
{
return wait_index_increment(ld, db, subsys, idx,
"account", account->channel ? fmt_channel_id(tmpctx, &account->channel->cid) : account->alt_account,
"=credit_msat", tal_fmt(tmpctx, "%"PRIu64, credit.millisatoshis), /* Raw: JSON output */
"=debit_msat", tal_fmt(tmpctx, "%"PRIu64, debit.millisatoshis), /* Raw: JSON output */
NULL);
}
u64 chain_mvt_index_created(struct lightningd *ld,
struct db *db,
const struct mvt_account_id *account,
struct amount_msat credit,
struct amount_msat debit)
{
return coinmvt_index_inc(ld, db, WAIT_SUBSYSTEM_CHAINMOVES,
account, credit, debit,
WAIT_INDEX_CREATED);
}
u64 channel_mvt_index_created(struct lightningd *ld,
struct db *db,
const struct mvt_account_id *account,
struct amount_msat credit,
struct amount_msat debit)
{
return coinmvt_index_inc(ld, db, WAIT_SUBSYSTEM_CHANNELMOVES,
account, credit, debit,
WAIT_INDEX_CREATED);
}
static struct command_result *json_listchainmoves(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
struct db_stmt *stmt;
enum wait_index *listindex;
u64 *liststart;
u32 *listlimit;
if (!param(cmd, buffer, params,
p_opt("index", param_index, &listindex),
p_opt_def("start", param_u64, &liststart, 0),
p_opt("limit", param_u32, &listlimit),
NULL))
return command_param_failed();
if (*liststart != 0 && !listindex) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Can only specify {start} with {index}");
}
if (listlimit && !listindex) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Can only specify {limit} with {index}");
}
if (listindex && *listindex != WAIT_INDEX_CREATED) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"index must be 'created', since chainmoves are never updated");
}
response = json_stream_success(cmd);
json_array_start(response, "chainmoves");
for (stmt = wallet_chain_moves_first(cmd->ld->wallet, *liststart, listlimit);
stmt;
stmt = wallet_chain_moves_next(cmd->ld->wallet, stmt)) {
struct chain_coin_mvt *chain_mvt;
u64 id;
chain_mvt = wallet_chain_move_extract(cmd, stmt, cmd->ld, &id);
json_object_start(response, NULL);
json_add_chain_mvt_fields(response, false, false, false, chain_mvt, id);
json_object_end(response);
}
json_array_end(response);
return command_success(cmd, response);
}
static const struct json_command listchainmoves_command = {
"listchainmoves",
json_listchainmoves,
};
AUTODATA(json_command, &listchainmoves_command);
static struct command_result *json_listchannelmoves(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct json_stream *response;
struct db_stmt *stmt;
enum wait_index *listindex;
u64 *liststart;
u32 *listlimit;
if (!param(cmd, buffer, params,
p_opt("index", param_index, &listindex),
p_opt_def("start", param_u64, &liststart, 0),
p_opt("limit", param_u32, &listlimit),
NULL))
return command_param_failed();
if (*liststart != 0 && !listindex) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Can only specify {start} with {index}");
}
if (listlimit && !listindex) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Can only specify {limit} with {index}");
}
if (listindex && *listindex != WAIT_INDEX_CREATED) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"index must be 'created', since channelmoves are never updated");
}
response = json_stream_success(cmd);
json_array_start(response, "channelmoves");
for (stmt = wallet_channel_moves_first(cmd->ld->wallet, *liststart, listlimit);
stmt;
stmt = wallet_channel_moves_next(cmd->ld->wallet, stmt)) {
struct channel_coin_mvt *chan_mvt;
u64 id;
chan_mvt = wallet_channel_move_extract(cmd, stmt, cmd->ld, &id);
json_object_start(response, NULL);
/* No deprecated tags[], no extra_tags field */
json_add_channel_mvt_fields(response, false, chan_mvt, id, false);
json_object_end(response);
}
json_array_end(response);
return command_success(cmd, response);
}
static const struct json_command listchannelmoves_command = {
"listchannelmoves",
json_listchannelmoves,
};
AUTODATA(json_command, &listchannelmoves_command);
static struct command_result *param_msat_as_sat(struct command *cmd,
const char *name,
const char *buffer,
const jsmntok_t *tok,
struct amount_sat **sat)
{
struct amount_msat msat;
*sat = tal(cmd, struct amount_sat);
if (parse_amount_msat(&msat, buffer + tok->start, tok->end - tok->start)
&& amount_msat_to_sat(*sat, msat))
return NULL;
return command_fail_badparam(cmd, name, buffer, tok,
"should be a millisatoshi amount");
}
/* Internal interfaces for bookkeeper.c.
* FIXME: handle utxo_deposit / utxo_spend notifications directly! */
static struct command_result *json_injectutxodeposit(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct chain_coin_mvt *ev;
const char *account, *origin_acct;
struct bitcoin_outpoint *outpoint;
struct amount_sat *amount;
u64 *timestamp;
u32 *blockheight;
if (!param(cmd, buffer, params,
p_req("account", param_string, &account),
p_req("outpoint", param_outpoint, &outpoint),
p_req("amount_msat", param_msat_as_sat, &amount),
p_req("timestamp", param_u64, &timestamp),
p_req("blockheight", param_u32, &blockheight),
p_opt("transfer_from", param_string, &origin_acct),
NULL))
return command_param_failed();
ev = new_foreign_deposit(cmd, outpoint, *blockheight, *amount,
account, *timestamp);
if (origin_acct) {
/* Need temporary because originating_acct is const */
struct mvt_account_id *acct;
ev->originating_acct = acct = tal(ev, struct mvt_account_id);
acct->channel = NULL;
acct->alt_account = tal_strdup(acct, origin_acct);
}
wallet_save_chain_mvt(cmd->ld, ev);
return command_success(cmd, json_stream_success(cmd));
}
static const struct json_command injectutxodeposit_command = {
"injectutxodeposit",
json_injectutxodeposit,
};
AUTODATA(json_command, &injectutxodeposit_command);
static struct command_result *json_injectutxospend(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct chain_coin_mvt *ev;
const char *account;
struct bitcoin_txid *spending_txid;
struct bitcoin_outpoint *outpoint;
struct amount_sat *amount;
u64 *timestamp;
u32 *blockheight;
if (!param(cmd, buffer, params,
p_req("account", param_string, &account),
p_req("outpoint", param_outpoint, &outpoint),
p_req("spending_txid", param_txid, &spending_txid),
p_req("amount_msat", param_msat_as_sat, &amount),
p_req("timestamp", param_u64, &timestamp),
p_req("blockheight", param_u32, &blockheight),
NULL))
return command_param_failed();
ev = new_foreign_withdrawal(cmd, outpoint, spending_txid,
*amount, *blockheight, account, *timestamp);
wallet_save_chain_mvt(cmd->ld, ev);
return command_success(cmd, json_stream_success(cmd));
}
static const struct json_command injectutxospend_command = {
"injectutxospend",
json_injectutxospend,
};
AUTODATA(json_command, &injectutxospend_command);