From 1f7905259be1736c825e16be9b53d52af0fdbf49 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 19 Aug 2025 10:30:50 +0930 Subject: [PATCH] bkpr: forward utxo_deposit / utxo_spend notifications to new injectutxodeposit / injectutxospend calls. And thus we absorb them as normal when they come back as "foreign" entries. Signed-off-by: Rusty Russell --- plugins/bkpr/bookkeeper.c | 198 +++++++++++++++---------------------- plugins/bkpr/chain_event.h | 3 + tests/test_bookkeeper.py | 8 +- 3 files changed, 88 insertions(+), 121 deletions(-) diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c index e7e1409bf..06b10ac71 100644 --- a/plugins/bkpr/bookkeeper.c +++ b/plugins/bkpr/bookkeeper.c @@ -1517,11 +1517,24 @@ parse_and_log_chain_move(struct command *cmd, e->stealable = false; e->splice_close = false; + e->foreign = false; for (size_t i = 0; i < tal_count(tags); i++) { e->stealable |= tags[i] == MVT_STEALABLE; e->splice_close |= tags[i] == MVT_SPLICE; + e->foreign |= tags[i] == MVT_FOREIGN; } + /* For tests, we log these harder! */ + if (e->foreign) + plugin_log(cmd->plugin, LOG_DBG, + "Foreign chain event: %s (%s) %s -%s %"PRIu64" %d %s %s", + e->tag, acct_name, + fmt_amount_msat(tmpctx, e->credit), + fmt_amount_msat(tmpctx, e->debit), + e->timestamp, e->blockheight, + fmt_bitcoin_outpoint(tmpctx, &e->outpoint), + e->spending_txid ? fmt_bitcoin_txid(tmpctx, e->spending_txid) : ""); + db_begin_transaction(bkpr->db); /* FIXME: lookup the peer id for this channel! */ acct = find_or_create_account(cmd, bkpr, acct_name); @@ -1574,9 +1587,10 @@ parse_and_log_chain_move(struct command *cmd, /* If this is a channel account event, it's possible * that we *never* got the open event. (This happens * if you add the plugin *after* you've closed the channel) */ - if ((!acct->open_event_db_id && is_channel_account(acct->name)) - || (orig_acct && is_channel_account(orig_acct->name) - && !orig_acct->open_event_db_id)) { + if (!e->foreign + && ((!acct->open_event_db_id && is_channel_account(acct->name)) + || (orig_acct && is_channel_account(orig_acct->name) + && !orig_acct->open_event_db_id))) { /* Find the channel open info for this peer */ struct out_req *req; struct event_info *info; @@ -1729,14 +1743,26 @@ static bool json_to_tok(const char *buffer, const jsmntok_t *tok, const jsmntok_ return true; } +static struct command_result *inject_done(struct command *notif_cmd, + const char *methodname, + const char *buf, + const jsmntok_t *result, + void *unused) +{ + return notification_handled(notif_cmd); +} + +/* FIXME: Deprecate */ static struct command_result *json_utxo_deposit(struct command *cmd, const char *buf, const jsmntok_t *params) { - const char *move_tag ="utxo_deposit"; - struct chain_event *ev = tal(cmd, struct chain_event); - struct account *acct; - const char *err; - struct bkpr *bkpr = bkpr_of(cmd->plugin); + const char *acct_name, *origin_acct; + struct amount_msat amount; + struct bitcoin_outpoint outpoint; + u64 timestamp; + u32 blockheight; const jsmntok_t *transfer_from; + const char *err; + struct out_req *req; transfer_from = NULL; err = json_scan(tmpctx, buf, params, @@ -1748,71 +1774,49 @@ static struct command_result *json_utxo_deposit(struct command *cmd, const char ",timestamp:%" ",blockheight:%" "}}", - JSON_SCAN_TAL(ev, json_strdup, &ev->acct_name), + JSON_SCAN_TAL(tmpctx, json_strdup, &acct_name), JSON_SCAN(json_to_tok, &transfer_from), - JSON_SCAN(json_to_outpoint, &ev->outpoint), - JSON_SCAN(json_to_msat, &ev->credit), - JSON_SCAN(json_to_u64, &ev->timestamp), - JSON_SCAN(json_to_u32, &ev->blockheight)); + JSON_SCAN(json_to_outpoint, &outpoint), + JSON_SCAN(json_to_msat, &amount), + JSON_SCAN(json_to_u64, ×tamp), + JSON_SCAN(json_to_u32, &blockheight)); if (err) plugin_err(cmd->plugin, - "`%s` parameters did not scan %s: %.*s", - move_tag, err, json_tok_full_len(params), + "`utxo_deposit` parameters did not scan %s: %.*s", + err, json_tok_full_len(params), json_tok_full(buf, params)); if (!transfer_from || json_tok_is_null(buf, transfer_from)) - ev->origin_acct = NULL; + origin_acct = NULL; else - ev->origin_acct = json_strdup(ev, buf, transfer_from); + origin_acct = json_strdup(tmpctx, buf, transfer_from); - /* Log the thing */ - db_begin_transaction(bkpr->db); - acct = find_or_create_account(cmd, bkpr, ev->acct_name); - - ev->tag = "deposit"; - ev->stealable = false; - ev->splice_close = false; - ev->debit = AMOUNT_MSAT(0); - ev->output_value = ev->credit; - ev->spending_txid = NULL; - ev->payment_id = NULL; - ev->splice_close = false; - - plugin_log(cmd->plugin, LOG_DBG, "%s (%s|%s) %s -%s %"PRIu64" %d %s", - move_tag, ev->tag, ev->acct_name, - fmt_amount_msat(tmpctx, ev->credit), - fmt_amount_msat(tmpctx, ev->debit), - ev->timestamp, ev->blockheight, - fmt_bitcoin_outpoint(tmpctx, &ev->outpoint)); - - if (!log_chain_event(bkpr, acct, ev)) { - db_commit_transaction(bkpr->db); - /* This is not a new event, do nothing */ - return notification_handled(cmd); - } - - /* Can we calculate any onchain fees now? */ - err = maybe_update_onchain_fees(cmd, cmd, bkpr, &ev->outpoint.txid); - db_commit_transaction(bkpr->db); - if (err) - plugin_err(cmd->plugin, - "Unable to update onchain fees %s", - err); - - /* FIXME: do account close checks, when allow onchain close to externals? */ - return notification_handled(cmd);; + req = jsonrpc_request_start(cmd, "injectutxodeposit", + inject_done, + plugin_broken_cb, + NULL); + json_add_string(req->js, "account", acct_name); + if (origin_acct) + json_add_string(req->js, "transfer_from", origin_acct); + json_add_outpoint(req->js, "outpoint", &outpoint); + json_add_amount_msat(req->js, "amount_msat", amount); + json_add_u64(req->js, "timestamp", timestamp); + json_add_u32(req->js, "blockheight", blockheight); + return send_outreq(req); } static struct command_result *json_utxo_spend(struct command *cmd, const char *buf, const jsmntok_t *params) { - const char *move_tag ="utxo_spend"; - struct account *acct; - struct chain_event *ev = tal(cmd, struct chain_event); - const char *err, *acct_name; - struct bkpr *bkpr = bkpr_of(cmd->plugin); + const char *acct_name; + struct amount_msat amount; + struct bitcoin_txid spending_txid; + struct bitcoin_outpoint outpoint; + u64 timestamp; + u32 blockheight; + const char *err; + struct out_req *req; - ev->spending_txid = tal(ev, struct bitcoin_txid); err = json_scan(tmpctx, buf, params, "{utxo_spend:{" "account:%" @@ -1823,69 +1827,29 @@ static struct command_result *json_utxo_spend(struct command *cmd, const char *b ",blockheight:%" "}}", JSON_SCAN_TAL(tmpctx, json_strdup, &acct_name), - JSON_SCAN(json_to_outpoint, &ev->outpoint), - JSON_SCAN(json_to_txid, ev->spending_txid), - JSON_SCAN(json_to_msat, &ev->debit), - JSON_SCAN(json_to_u64, &ev->timestamp), - JSON_SCAN(json_to_u32, &ev->blockheight)); + JSON_SCAN(json_to_outpoint, &outpoint), + JSON_SCAN(json_to_txid, &spending_txid), + JSON_SCAN(json_to_msat, &amount), + JSON_SCAN(json_to_u64, ×tamp), + JSON_SCAN(json_to_u32, &blockheight)); if (err) plugin_err(cmd->plugin, - "`%s` parameters did not scan %s: %.*s", - move_tag, err, json_tok_full_len(params), + "`utxo_spend` parameters did not scan %s: %.*s", + err, json_tok_full_len(params), json_tok_full(buf, params)); - /* Log the thing */ - db_begin_transaction(bkpr->db); - acct = find_or_create_account(cmd, bkpr, acct_name); - - ev->origin_acct = NULL; - ev->tag = "withdrawal"; - ev->stealable = false; - ev->splice_close = false; - ev->credit = AMOUNT_MSAT(0); - ev->output_value = ev->debit; - ev->payment_id = NULL; - - plugin_log(cmd->plugin, LOG_DBG, "%s (%s|%s) %s -%s %"PRIu64" %d %s %s", - move_tag, ev->tag, acct_name, - fmt_amount_msat(tmpctx, ev->credit), - fmt_amount_msat(tmpctx, ev->debit), - ev->timestamp, ev->blockheight, - fmt_bitcoin_outpoint(tmpctx, &ev->outpoint), - fmt_bitcoin_txid(tmpctx, ev->spending_txid)); - - if (!log_chain_event(bkpr, acct, ev)) { - db_commit_transaction(bkpr->db); - /* This is not a new event, do nothing */ - return notification_handled(cmd); - } - - err = maybe_update_onchain_fees(cmd, cmd, bkpr, ev->spending_txid); - if (err) { - db_commit_transaction(bkpr->db); - plugin_err(cmd->plugin, - "Unable to update onchain fees %s", - err); - } - - err = maybe_update_onchain_fees(cmd, cmd, bkpr, &ev->outpoint.txid); - if (err) { - db_commit_transaction(bkpr->db); - plugin_err(cmd->plugin, - "Unable to update onchain fees %s", - err); - } - - /* Go see if there's any deposits to an external - * that are now confirmed */ - /* FIXME: might need updating when we can splice? */ - maybe_closeout_external_deposits(cmd, bkpr, ev->spending_txid, - ev->blockheight); - db_commit_transaction(bkpr->db); - - /* FIXME: do account close checks, when allow onchain close to externals? */ - return notification_handled(cmd);; + req = jsonrpc_request_start(cmd, "injectutxospend", + inject_done, + plugin_broken_cb, + NULL); + json_add_string(req->js, "account", acct_name); + json_add_outpoint(req->js, "outpoint", &outpoint); + json_add_txid(req->js, "spending_txid", &spending_txid); + json_add_amount_msat(req->js, "amount_msat", amount); + json_add_u64(req->js, "timestamp", timestamp); + json_add_u32(req->js, "blockheight", blockheight); + return send_outreq(req); } static struct command_result *json_coin_moved(struct command *cmd, diff --git a/plugins/bkpr/chain_event.h b/plugins/bkpr/chain_event.h index 267f4bc1b..0096ffca0 100644 --- a/plugins/bkpr/chain_event.h +++ b/plugins/bkpr/chain_event.h @@ -33,6 +33,9 @@ struct chain_event { * confirmation? */ bool splice_close; + /* Injected? */ + bool foreign; + /* Amount we received in this event */ struct amount_msat credit; diff --git a/tests/test_bookkeeper.py b/tests/test_bookkeeper.py index 27a339fef..615bafdd3 100644 --- a/tests/test_bookkeeper.py +++ b/tests/test_bookkeeper.py @@ -909,9 +909,9 @@ def test_bookkeeper_custom_notifs(node_factory, chainparams): acct = "nifty's secret stash" l1.rpc.senddeposit(acct, False, outpoint_in, amount) + l1.daemon.wait_for_log(r"Foreign chain event: deposit \(nifty's secret stash\) 180000000msat -0msat 1679955976 111 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0") l1.rpc.sendspend(acct, outpoint_in, spend_txid, amount) - l1.daemon.wait_for_log(r"utxo_deposit \(deposit|nifty's secret stash\) .* -0msat 1679955976 111 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0") - l1.daemon.wait_for_log(r"utxo_spend \(withdrawal|nifty's secret stash\) 0msat -12345678000msat 1679955976 111 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") + l1.daemon.wait_for_log(r"Foreign chain event: withdrawal \(nifty's secret stash\) 0msat -180000000msat 1679955976 111 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:0 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") # balance should be zero bals = l1.rpc.bkpr_listbalances()['accounts'] @@ -921,7 +921,7 @@ def test_bookkeeper_custom_notifs(node_factory, chainparams): assert only_one(bal['balances'])['balance_msat'] == Millisatoshi(0) l1.rpc.senddeposit(acct, False, change_deposit, amount - withdraw_amt - fee) - l1.daemon.wait_for_log(r"utxo_deposit \(deposit|nifty's secret stash\) .* -0msat 1679955976 111 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:0") + l1.daemon.wait_for_log(r"Foreign chain event: deposit \(nifty's secret stash\) .* -0msat 1679955976 111 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:0") # balance should be equal to amount events = l1.rpc.bkpr_listaccountevents(acct)['events'] @@ -933,7 +933,7 @@ def test_bookkeeper_custom_notifs(node_factory, chainparams): assert onchain_fee_one == fee + withdraw_amt l1.rpc.senddeposit(acct, True, external_deposit, withdraw_amt) - l1.daemon.wait_for_log(r"utxo_deposit \(deposit|external\) .* -0msat 1679955976 111 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:1") + l1.daemon.wait_for_log(r"Foreign chain event: deposit \(external\) .* -0msat 1679955976 111 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:1") events = l1.rpc.bkpr_listaccountevents(acct)['events'] onchain_fees = [x for x in events if x['type'] == 'onchain_fee'] assert len(onchain_fees) == 2