lightingnd: wrap all JSON reply callbacks in db transactions.

It was always a bit weird they weren't, and it seems a premature
optimization to make the callbacks to this themselves.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2023-10-24 12:11:28 +10:30
parent 524eff0e93
commit 8170aba75f
5 changed files with 40 additions and 39 deletions

View file

@ -404,10 +404,7 @@ static void sendrawtx_callback(const char *buf, const jsmntok_t *toks,
err); err);
} }
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, success, errmsg, call->cb_arg); call->cb(call->bitcoind, success, errmsg, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
tal_free(call); tal_free(call);
} }
@ -472,9 +469,7 @@ getrawblockbyheight_callback(const char *buf, const jsmntok_t *toks,
* with NULL values. */ * with NULL values. */
err = json_scan(tmpctx, buf, toks, "{result:{blockhash:null}}"); err = json_scan(tmpctx, buf, toks, "{result:{blockhash:null}}");
if (!err) { if (!err) {
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, NULL, NULL, call->cb_arg); call->cb(call->bitcoind, NULL, NULL, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
goto clean; goto clean;
} }
@ -493,9 +488,7 @@ getrawblockbyheight_callback(const char *buf, const jsmntok_t *toks,
"getrawblockbyheight", "getrawblockbyheight",
"bad block"); "bad block");
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, &blkid, blk, call->cb_arg); call->cb(call->bitcoind, &blkid, blk, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
clean: clean:
tal_free(call); tal_free(call);
@ -574,10 +567,8 @@ static void getchaininfo_callback(const char *buf, const jsmntok_t *toks,
bitcoin_plugin_error(call->bitcoind, buf, toks, "getchaininfo", bitcoin_plugin_error(call->bitcoind, buf, toks, "getchaininfo",
"bad 'result' field: %s", err); "bad 'result' field: %s", err);
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, chain, headers, blocks, ibd, call->cb(call->bitcoind, chain, headers, blocks, ibd,
call->first_call, call->cb_arg); call->first_call, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
tal_free(call); tal_free(call);
} }
@ -640,9 +631,7 @@ static void getutxout_callback(const char *buf, const jsmntok_t *toks,
err = json_scan(tmpctx, buf, toks, "{result:{script:null}}"); err = json_scan(tmpctx, buf, toks, "{result:{script:null}}");
if (!err) { if (!err) {
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, NULL, call->cb_arg); call->cb(call->bitcoind, NULL, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
goto clean; goto clean;
} }
@ -654,9 +643,7 @@ static void getutxout_callback(const char *buf, const jsmntok_t *toks,
bitcoin_plugin_error(call->bitcoind, buf, toks, "getutxout", bitcoin_plugin_error(call->bitcoind, buf, toks, "getutxout",
"bad 'result' field: %s", err); "bad 'result' field: %s", err);
db_begin_transaction(call->bitcoind->ld->wallet->db);
call->cb(call->bitcoind, &txout, call->cb_arg); call->cb(call->bitcoind, &txout, call->cb_arg);
db_commit_transaction(call->bitcoind->ld->wallet->db);
clean: clean:
tal_free(call); tal_free(call);

View file

@ -921,7 +921,6 @@ static void listincoming_done(const char *buffer,
const jsmntok_t *idtok UNUSED, const jsmntok_t *idtok UNUSED,
struct invoice_info *info) struct invoice_info *info)
{ {
struct lightningd *ld = info->cmd->ld;
struct command_result *ret; struct command_result *ret;
bool warning_mpp, warning_capacity, warning_deadends, warning_offline, warning_private_unused; bool warning_mpp, warning_capacity, warning_deadends, warning_offline, warning_private_unused;
@ -934,8 +933,6 @@ static void listincoming_done(const char *buffer,
if (ret) if (ret)
return; return;
/* We're actually outside a db transaction here: spooky! */
db_begin_transaction(ld->wallet->db);
invoice_complete(info, invoice_complete(info,
false, false,
warning_mpp, warning_mpp,
@ -943,7 +940,6 @@ static void listincoming_done(const char *buffer,
warning_deadends, warning_deadends,
warning_offline, warning_offline,
warning_private_unused); warning_private_unused);
db_commit_transaction(ld->wallet->db);
} }
/* Since this is a dev-only option, we will crash if dev-routes is not /* Since this is a dev-only option, we will crash if dev-routes is not

View file

@ -17,6 +17,7 @@
#include <common/timeout.h> #include <common/timeout.h>
#include <common/version.h> #include <common/version.h>
#include <connectd/connectd_wiregen.h> #include <connectd/connectd_wiregen.h>
#include <db/exec.h>
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <lightningd/io_loop_with_timers.h> #include <lightningd/io_loop_with_timers.h>
@ -84,6 +85,7 @@ struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
p->blacklist = tal_arr(p, const char *, 0); p->blacklist = tal_arr(p, const char *, 0);
p->plugin_idx = 0; p->plugin_idx = 0;
p->dev_builtin_plugins_unimportant = false; p->dev_builtin_plugins_unimportant = false;
p->want_db_transaction = true;
return p; return p;
} }
@ -619,6 +621,7 @@ static const char *plugin_response_handle(struct plugin *plugin,
/* We expect the request->cb to copy if needed */ /* We expect the request->cb to copy if needed */
pd = plugin_detect_destruction(plugin); pd = plugin_detect_destruction(plugin);
request->response_cb(plugin->buffer, toks, idtok, request->response_cb_arg); request->response_cb(plugin->buffer, toks, idtok, request->response_cb_arg);
/* Note that in the case of 'plugin stop' this can free request (since /* Note that in the case of 'plugin stop' this can free request (since
@ -639,12 +642,14 @@ static const char *plugin_response_handle(struct plugin *plugin,
* If @destroyed was set, it means the plugin called plugin stop on itself. * If @destroyed was set, it means the plugin called plugin stop on itself.
*/ */
static const char *plugin_read_json_one(struct plugin *plugin, static const char *plugin_read_json_one(struct plugin *plugin,
bool want_transaction,
bool *complete, bool *complete,
bool *destroyed) bool *destroyed)
{ {
const jsmntok_t *jrtok, *idtok; const jsmntok_t *jrtok, *idtok;
struct plugin_destroyed *pd; struct plugin_destroyed *pd;
const char *err; const char *err;
struct wallet *wallet = plugin->plugins->ld->wallet;
*destroyed = false; *destroyed = false;
/* Note that in the case of 'plugin stop' this can free request (since /* Note that in the case of 'plugin stop' this can free request (since
@ -687,6 +692,11 @@ static const char *plugin_read_json_one(struct plugin *plugin,
"JSON-RPC message does not contain \"jsonrpc\" field"); "JSON-RPC message does not contain \"jsonrpc\" field");
} }
/* We can be called extremely early, or as db hook, or for
* fake "terminated" request. */
if (want_transaction)
db_begin_transaction(wallet->db);
pd = plugin_detect_destruction(plugin); pd = plugin_detect_destruction(plugin);
if (!idtok) { if (!idtok) {
/* A Notification is a Request object without an "id" /* A Notification is a Request object without an "id"
@ -732,6 +742,8 @@ static const char *plugin_read_json_one(struct plugin *plugin,
*/ */
err = plugin_response_handle(plugin, plugin->toks, idtok); err = plugin_response_handle(plugin, plugin->toks, idtok);
} }
if (want_transaction)
db_commit_transaction(wallet->db);
/* Corner case: rpc_command hook can destroy plugin for 'plugin /* Corner case: rpc_command hook can destroy plugin for 'plugin
* stop'! */ * stop'! */
@ -753,6 +765,9 @@ static struct io_plan *plugin_read_json(struct io_conn *conn,
{ {
bool success; bool success;
bool have_full; bool have_full;
/* wallet is NULL in really early code */
bool want_transaction = (plugin->plugins->want_db_transaction
&& plugin->plugins->ld->wallet != NULL);
log_io(plugin->log, LOG_IO_IN, NULL, "", log_io(plugin->log, LOG_IO_IN, NULL, "",
plugin->buffer + plugin->used, plugin->len_read); plugin->buffer + plugin->used, plugin->len_read);
@ -774,8 +789,9 @@ static struct io_plan *plugin_read_json(struct io_conn *conn,
do { do {
bool destroyed; bool destroyed;
const char *err; const char *err;
err =
plugin_read_json_one(plugin, &success, &destroyed); err = plugin_read_json_one(plugin, want_transaction,
&success, &destroyed);
/* If it's destroyed, conn is already freed! */ /* If it's destroyed, conn is already freed! */
if (destroyed) if (destroyed)

View file

@ -108,6 +108,10 @@ struct plugins {
struct list_head plugins; struct list_head plugins;
bool startup; bool startup;
/* Normally we want to wrap callbacks in a db transaction, but
* not for the db hook servicing */
bool want_db_transaction;
struct logger *log; struct logger *log;
struct lightningd *ld; struct lightningd *ld;

View file

@ -134,7 +134,10 @@ static void plugin_hook_killed(struct plugin_hook_call_link *link)
/* Call next will unlink, so we don't need to. This is treated /* Call next will unlink, so we don't need to. This is treated
* equivalent to the plugin returning a continue-result. * equivalent to the plugin returning a continue-result.
*/ */
struct db *db = link->plugin->plugins->ld->wallet->db;
db_begin_transaction(db);
plugin_hook_callback(NULL, NULL, NULL, link->req); plugin_hook_callback(NULL, NULL, NULL, link->req);
db_commit_transaction(db);
} else { } else {
/* The plugin is in the list waiting to be called, just remove /* The plugin is in the list waiting to be called, just remove
* it from the list. */ * it from the list. */
@ -159,9 +162,7 @@ static void plugin_hook_callback(const char *buffer, const jsmntok_t *toks,
struct plugin_hook_request *r) struct plugin_hook_request *r)
{ {
const jsmntok_t *resulttok; const jsmntok_t *resulttok;
struct db *db = r->db;
struct plugin_hook_call_link *last, *it; struct plugin_hook_call_link *last, *it;
bool in_transaction = false;
/* Pop the head off the call chain and continue with the next */ /* Pop the head off the call chain and continue with the next */
last = list_pop(&r->call_chain, struct plugin_hook_call_link, list); last = list_pop(&r->call_chain, struct plugin_hook_call_link, list);
@ -187,31 +188,22 @@ static void plugin_hook_callback(const char *buffer, const jsmntok_t *toks,
r->hook->name, toks->end - toks->start, r->hook->name, toks->end - toks->start,
buffer + toks->start); buffer + toks->start);
db_begin_transaction(db);
if (!r->hook->deserialize_cb(r->cb_arg, buffer, if (!r->hook->deserialize_cb(r->cb_arg, buffer,
resulttok)) { resulttok)) {
tal_free(r->cb_arg); tal_free(r->cb_arg);
db_commit_transaction(db);
goto cleanup; goto cleanup;
} }
in_transaction = true;
} else { } else {
/* plugin died */ /* plugin died */
resulttok = NULL; resulttok = NULL;
} }
if (!list_empty(&r->call_chain)) { if (!list_empty(&r->call_chain)) {
if (in_transaction)
db_commit_transaction(db);
plugin_hook_call_next(r); plugin_hook_call_next(r);
return; return;
} }
/* We optimize for the case where we already called deserialize_cb */
if (!in_transaction)
db_begin_transaction(db);
r->hook->final_cb(r->cb_arg); r->hook->final_cb(r->cb_arg);
db_commit_transaction(db);
cleanup: cleanup:
/* We need to remove the destructors from the remaining /* We need to remove the destructors from the remaining
@ -349,7 +341,8 @@ void plugin_hook_db_sync(struct db *db)
struct jsonrpc_request *req; struct jsonrpc_request *req;
struct plugin_hook_request *ph_req; struct plugin_hook_request *ph_req;
void *ret; void *ret;
struct plugin **plugins; struct plugin **plugin_arr;
struct plugins *plugins;
size_t i; size_t i;
size_t num_hooks; size_t num_hooks;
@ -358,11 +351,12 @@ void plugin_hook_db_sync(struct db *db)
if (num_hooks == 0) if (num_hooks == 0)
return; return;
plugins = notleak(tal_arr(NULL, struct plugin *, plugin_arr = notleak(tal_arr(NULL, struct plugin *,
num_hooks)); num_hooks));
for (i = 0; i < num_hooks; ++i) for (i = 0; i < num_hooks; ++i)
plugins[i] = hook->hooks[i]->plugin; plugin_arr[i] = hook->hooks[i]->plugin;
plugins = plugin_arr[0]->plugins;
ph_req = notleak(tal(hook->hooks, struct plugin_hook_request)); ph_req = notleak(tal(hook->hooks, struct plugin_hook_request));
ph_req->hook = hook; ph_req->hook = hook;
ph_req->db = db; ph_req->db = db;
@ -372,7 +366,7 @@ void plugin_hook_db_sync(struct db *db)
/* Create an object for this plugin. */ /* Create an object for this plugin. */
struct db_write_hook_req *dwh_req; struct db_write_hook_req *dwh_req;
dwh_req = tal(ph_req, struct db_write_hook_req); dwh_req = tal(ph_req, struct db_write_hook_req);
dwh_req->plugin = plugins[i]; dwh_req->plugin = plugin_arr[i];
dwh_req->ph_req = ph_req; dwh_req->ph_req = ph_req;
dwh_req->num_hooks = &num_hooks; dwh_req->num_hooks = &num_hooks;
@ -393,21 +387,25 @@ void plugin_hook_db_sync(struct db *db)
json_array_end(req->stream); json_array_end(req->stream);
jsonrpc_request_end(req); jsonrpc_request_end(req);
plugin_request_send(plugins[i], req); plugin_request_send(plugin_arr[i], req);
} }
/* We don't want to try to open another transaction: we're in one! */
plugins->want_db_transaction = false;
ret = plugins_exclusive_loop(plugin_arr);
plugins->want_db_transaction = true;
/* We can be called on way out of an io_loop, which is already breaking. /* We can be called on way out of an io_loop, which is already breaking.
* That will make this immediately return; save the break value and call * That will make this immediately return; save the break value and call
* again, then hand it onwards. */ * again, then hand it onwards. */
ret = plugins_exclusive_loop(plugins);
if (ret != ph_req) { if (ret != ph_req) {
void *ret2 = plugins_exclusive_loop(plugins); void *ret2 = plugins_exclusive_loop(plugin_arr);
assert(ret2 == ph_req); assert(ret2 == ph_req);
log_debug(plugins[0]->plugins->ld->log, "io_break: %s", __func__); log_debug(plugins->ld->log, "io_break: %s", __func__);
io_break(ret); io_break(ret);
} }
assert(num_hooks == 0); assert(num_hooks == 0);
tal_free(plugins); tal_free(plugin_arr);
tal_free(ph_req); tal_free(ph_req);
} }