From 2b4b91ff5c0a06b0dd94e4041bef6872fbbb6848 Mon Sep 17 00:00:00 2001 From: niftynei Date: Thu, 22 Aug 2024 18:00:22 -0500 Subject: [PATCH] bkpr: add new RPC bkpr-editdescriptionbyoutpoint Given an {outpoint}, sets the description on the matching outpoint (if exists). Note that if no outpoint exists in bookkeeper, will return an empty list Changleog-Added: PLUGINS: bookkeeper has a new RPC `bkrp-editdescriptionbyoutpoint` which will set/update a description for an outpoint creation event. --- contrib/msggen/msggen/schema.json | 270 ++++++++++++++++++ ...htning-bkpr-editdescriptionbyoutpoint.json | 270 ++++++++++++++++++ ...tning-bkpr-editdescriptionbypaymentid.json | 1 + plugins/bkpr/bookkeeper.c | 45 +++ plugins/bkpr/recorder.c | 87 ++++++ plugins/bkpr/recorder.h | 12 + 6 files changed, 685 insertions(+) create mode 100644 doc/schemas/lightning-bkpr-editdescriptionbyoutpoint.json diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index a02e0e29b..77de576fe 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -2605,6 +2605,275 @@ } ] }, + "lightning-bkpr-editdescriptionbyoutpoint.json": { + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "bkpr-editdescriptionbyoutpoint", + "title": "Command to change the description for events with {outpoint} after they're made", + "description": [ + "The **bkpr-editdescriptionbyoutpoint** RPC command updates all chain and channel events that match the {outpoint} with the provided {description}" + ], + "request": { + "required": [ + "outpoint", + "description" + ], + "properties": { + "outpoint": { + "type": "string", + "description": [ + "The outpoint to update the description for." + ] + }, + "description": { + "type": "string", + "description": [ + "The description to update to" + ] + } + } + }, + "response": { + "required": [ + "updated" + ], + "properties": { + "updated": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "required": [ + "account", + "type", + "tag", + "credit_msat", + "debit_msat", + "currency", + "timestamp", + "description" + ], + "properties": { + "account": { + "type": "string", + "description": [ + "The account name. If the account is a channel, the channel_id." + ] + }, + "type": { + "type": "string", + "enum": [ + "chain", + "channel" + ], + "description": [ + "Coin movement type." + ] + }, + "tag": { + "type": "string", + "description": [ + "Description of movement." + ] + }, + "credit_msat": { + "type": "msat", + "description": [ + "Amount credited." + ] + }, + "debit_msat": { + "type": "msat", + "description": [ + "Amount debited." + ] + }, + "currency": { + "type": "string", + "description": [ + "Human-readable bech32 part for this coin type." + ] + }, + "timestamp": { + "type": "u32", + "description": [ + "Timestamp this event was recorded by the node. For consolidated events such as onchain_fees, the most recent timestamp." + ] + }, + "description": { + "type": "string", + "description": [ + "The description of this event" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "chain" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": { + "type": "string", + "description": [ + "A description of this outpoint." + ] + }, + "outpoint": { + "type": "string", + "description": [ + "The txid:outnum for this event." + ] + }, + "blockheight": { + "type": "u32", + "description": [ + "For chain events, blockheight this occured at." + ] + }, + "origin": { + "type": "string", + "description": [ + "The account this movement originated from." + ] + }, + "payment_id": { + "type": "hex", + "description": [ + "Lightning payment identifier. For an htlc, this will be the preimage." + ] + }, + "txid": { + "type": "txid", + "description": [ + "The txid of the transaction that created this event." + ] + } + }, + "required": [ + "outpoint", + "blockheight" + ], + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "onchain_fee" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": {}, + "txid": { + "type": "txid", + "description": [ + "The txid of the transaction that created this event." + ] + } + }, + "required": [ + "txid" + ], + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "channel" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": {}, + "fees_msat": { + "type": "msat", + "description": [ + "Amount paid in fees." + ] + }, + "is_rebalance": { + "type": "boolean", + "description": [ + "Is this payment part of a rebalance." + ] + }, + "payment_id": { + "type": "hex", + "description": [ + "Lightning payment identifier. For an htlc, this will be the preimage." + ] + }, + "part_id": { + "type": "u32", + "description": [ + "Counter for multi-part payments." + ] + } + }, + "additionalProperties": false + } + } + ] + } + } + } + }, + "author": [ + "Lisa Neigut <> is mainly responsible." + ], + "see_also": [ + "lightning-bkpr-editdescriptionbypaymentid(7)", + "lightning-bkpr-listaccountevents(7)", + "lightning-bkpr-listincome(7)" + ], + "resources": [ + "Main web site: " + ], + "examples": [] + }, "lightning-bkpr-editdescriptionbypaymentid.json": { "$schema": "../rpc-schema-draft.json", "type": "object", @@ -2859,6 +3128,7 @@ "Lisa Neigut <> is mainly responsible." ], "see_also": [ + "lightning-bkpr-editdescriptionbyoutpoint(7)", "lightning-bkpr-listaccountevents(7)", "lightning-bkpr-listincome(7)" ], diff --git a/doc/schemas/lightning-bkpr-editdescriptionbyoutpoint.json b/doc/schemas/lightning-bkpr-editdescriptionbyoutpoint.json new file mode 100644 index 000000000..e4aa09b86 --- /dev/null +++ b/doc/schemas/lightning-bkpr-editdescriptionbyoutpoint.json @@ -0,0 +1,270 @@ +{ + "$schema": "../rpc-schema-draft.json", + "type": "object", + "additionalProperties": false, + "rpc": "bkpr-editdescriptionbyoutpoint", + "title": "Command to change the description for events with {outpoint} after they're made", + "description": [ + "The **bkpr-editdescriptionbyoutpoint** RPC command updates all chain and channel events that match the {outpoint} with the provided {description}" + ], + "request": { + "required": [ + "outpoint", + "description" + ], + "properties": { + "outpoint": { + "type": "string", + "description": [ + "The outpoint to update the description for." + ] + }, + "description": { + "type": "string", + "description": [ + "The description to update to" + ] + } + } + }, + "response": { + "required": [ + "updated" + ], + "properties": { + "updated": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "required": [ + "account", + "type", + "tag", + "credit_msat", + "debit_msat", + "currency", + "timestamp", + "description" + ], + "properties": { + "account": { + "type": "string", + "description": [ + "The account name. If the account is a channel, the channel_id." + ] + }, + "type": { + "type": "string", + "enum": [ + "chain", + "channel" + ], + "description": [ + "Coin movement type." + ] + }, + "tag": { + "type": "string", + "description": [ + "Description of movement." + ] + }, + "credit_msat": { + "type": "msat", + "description": [ + "Amount credited." + ] + }, + "debit_msat": { + "type": "msat", + "description": [ + "Amount debited." + ] + }, + "currency": { + "type": "string", + "description": [ + "Human-readable bech32 part for this coin type." + ] + }, + "timestamp": { + "type": "u32", + "description": [ + "Timestamp this event was recorded by the node. For consolidated events such as onchain_fees, the most recent timestamp." + ] + }, + "description": { + "type": "string", + "description": [ + "The description of this event" + ] + } + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "chain" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": { + "type": "string", + "description": [ + "A description of this outpoint." + ] + }, + "outpoint": { + "type": "string", + "description": [ + "The txid:outnum for this event." + ] + }, + "blockheight": { + "type": "u32", + "description": [ + "For chain events, blockheight this occured at." + ] + }, + "origin": { + "type": "string", + "description": [ + "The account this movement originated from." + ] + }, + "payment_id": { + "type": "hex", + "description": [ + "Lightning payment identifier. For an htlc, this will be the preimage." + ] + }, + "txid": { + "type": "txid", + "description": [ + "The txid of the transaction that created this event." + ] + } + }, + "required": [ + "outpoint", + "blockheight" + ], + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "onchain_fee" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": {}, + "txid": { + "type": "txid", + "description": [ + "The txid of the transaction that created this event." + ] + } + }, + "required": [ + "txid" + ], + "additionalProperties": false + } + }, + { + "if": { + "properties": { + "type": { + "type": "string", + "enum": [ + "channel" + ] + } + } + }, + "then": { + "properties": { + "account": {}, + "type": {}, + "tag": {}, + "credit_msat": {}, + "debit_msat": {}, + "currency": {}, + "timestamp": {}, + "description": {}, + "fees_msat": { + "type": "msat", + "description": [ + "Amount paid in fees." + ] + }, + "is_rebalance": { + "type": "boolean", + "description": [ + "Is this payment part of a rebalance." + ] + }, + "payment_id": { + "type": "hex", + "description": [ + "Lightning payment identifier. For an htlc, this will be the preimage." + ] + }, + "part_id": { + "type": "u32", + "description": [ + "Counter for multi-part payments." + ] + } + }, + "additionalProperties": false + } + } + ] + } + } + } + }, + "author": [ + "Lisa Neigut <> is mainly responsible." + ], + "see_also": [ + "lightning-bkpr-editdescriptionbypaymentid(7)", + "lightning-bkpr-listaccountevents(7)", + "lightning-bkpr-listincome(7)" + ], + "resources": [ + "Main web site: " + ], + "examples": [ + ] +} diff --git a/doc/schemas/lightning-bkpr-editdescriptionbypaymentid.json b/doc/schemas/lightning-bkpr-editdescriptionbypaymentid.json index 7a3cab2c5..b131c03a0 100644 --- a/doc/schemas/lightning-bkpr-editdescriptionbypaymentid.json +++ b/doc/schemas/lightning-bkpr-editdescriptionbypaymentid.json @@ -252,6 +252,7 @@ "Lisa Neigut <> is mainly responsible." ], "see_also": [ + "lightning-bkpr-editdescriptionbyoutpoint(7)", "lightning-bkpr-listaccountevents(7)", "lightning-bkpr-listincome(7)" ], diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index 03dd29da5..ad03de847 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -479,6 +479,47 @@ static struct command_result *json_list_account_events(struct command *cmd, return command_finished(cmd, res); } +static struct command_result *param_outpoint(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct bitcoin_outpoint **outp) +{ + *outp = tal(cmd, struct bitcoin_outpoint); + if (json_to_outpoint(buffer, tok, *outp)) + return NULL; + return command_fail_badparam(cmd, name, buffer, tok, + "should be a txid:outnum"); +} + +static struct command_result *json_edit_desc_utxo(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct json_stream *res; + struct bitcoin_outpoint *outpoint; + const char *new_desc; + struct chain_event **chain_events; + + if (!param(cmd, buf, params, + p_req("identifier", param_outpoint, &outpoint), + p_req("description", param_string, &new_desc), + NULL)) + return command_param_failed(); + + db_begin_transaction(db); + edit_utxo_description(db, outpoint, new_desc); + chain_events = get_chain_events_by_outpoint(cmd, db, outpoint, true); + db_commit_transaction(db); + + res = jsonrpc_stream_success(cmd); + json_array_start(res, "updated"); + json_add_events(res, NULL, chain_events, NULL); + json_array_end(res); + + return command_finished(cmd, res); +} + static struct command_result *json_edit_desc_payment_id(struct command *cmd, const char *buf, const jsmntok_t *params) @@ -2031,6 +2072,10 @@ static const struct plugin_command commands[] = { "bkpr-editdescriptionbypaymentid", json_edit_desc_payment_id }, + { + "bkpr-editdescriptionbyoutpoint", + json_edit_desc_utxo + }, }; static const char *init(struct command *init_cmd, const char *b, const jsmntok_t *t) diff --git a/plugins/bkpr/recorder.c b/plugins/bkpr/recorder.c index 51e44f309..363a401de 100644 --- a/plugins/bkpr/recorder.c +++ b/plugins/bkpr/recorder.c @@ -654,6 +654,26 @@ void maybe_mark_account_onchain(struct db *db, struct account *acct) tal_free(ctx); } +void edit_utxo_description(struct db *db, + struct bitcoin_outpoint *outpoint, + const char *desc) +{ + struct db_stmt *stmt; + + /* Ok, now we update the account with this blockheight */ + stmt = db_prepare_v2(db, SQL("UPDATE chain_events SET" + " ev_desc = ?" + " WHERE" + " utxo_txid = ?" + " AND outnum = ?" + " AND credit > 0")); + db_bind_text(stmt, desc); + db_bind_txid(stmt, &outpoint->txid); + db_bind_int(stmt, outpoint->n); + + db_exec_prepared_v2(take(stmt)); +} + void add_payment_hash_desc(struct db *db, struct sha256 *payment_hash, const char *desc) @@ -723,6 +743,73 @@ struct chain_event *find_chain_event_by_id(const tal_t *ctx, return e; } +struct chain_event **get_chain_events_by_outpoint(const tal_t *ctx, + struct db *db, + const struct bitcoin_outpoint *outpoint, + bool credits_only) +{ + struct db_stmt *stmt; + if (credits_only) + 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" + ", e.spliced" + " FROM chain_events e" + " LEFT OUTER JOIN accounts a" + " ON e.account_id = a.id" + " WHERE " + " e.utxo_txid = ?" + " AND e.outnum = ?" + " AND credit > 0")); + 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" + ", e.spliced" + " FROM chain_events e" + " LEFT OUTER JOIN accounts a" + " ON e.account_id = a.id" + " WHERE " + " e.utxo_txid = ?" + " AND e.outnum = ?")); + + db_bind_txid(stmt, &outpoint->txid); + db_bind_int(stmt, outpoint->n); + return find_chain_events(ctx, take(stmt)); +} + struct chain_event **get_chain_events_by_id(const tal_t *ctx, struct db *db, const struct sha256 *id) diff --git a/plugins/bkpr/recorder.h b/plugins/bkpr/recorder.h index 9f502e468..4be23df48 100644 --- a/plugins/bkpr/recorder.h +++ b/plugins/bkpr/recorder.h @@ -112,6 +112,12 @@ struct chain_event **get_chain_events_by_id(const tal_t *ctx, struct db *db, const struct sha256 *id); +/* Get all chain events for a utxo */ +struct chain_event **get_chain_events_by_outpoint(const tal_t *ctx, + struct db *db, + const struct bitcoin_outpoint *outpoint, + bool credits_only); + /* Calculate the balances for an account * * @calc_sum - compute the total balance. error if negative @@ -220,6 +226,12 @@ void add_payment_hash_desc(struct db *db, struct sha256 *payment_hash, const char *desc); +/* Set the description for all events on this outpoint to + * the provided one */ +void edit_utxo_description(struct db *db, + struct bitcoin_outpoint *outpoint, + const char *desc); + /* When we make external deposits from the wallet, we don't * count them until any output that was spent *into* them is * confirmed onchain.