#include "config.h" #include #include #include #include #include #include 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, ×tamp), 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, ×tamp), 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);