core-lightning/plugins/recover.c
Rusty Russell c797b6fb20 libplugin: add method string to jsonrpc callbacks, implement generic helpers.
Without knowing what method was called, we can't have useful general logging
methods, so go through the pain of adding "const char *method" everywhere,
and add:

1. ignore_and_complete - we're done when jsonrpc returned
2. log_broken_and_complete - we're done, but emit BROKEN log.
3. plugin_broken_cb - if this happens, fail the plugin.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2024-11-07 17:04:35 +10:30

300 lines
8.7 KiB
C

#include "config.h"
#include <ccan/array_size/array_size.h>
#include <common/features.h>
#include <common/gossmap.h>
#include <common/hsm_encryption.h>
#include <common/json_param.h>
#include <common/json_stream.h>
#include <common/memleak.h>
#include <errno.h>
#include <plugins/libplugin.h>
#include <unistd.h>
/* How long to wait after startup before starting the timer loop */
#define STARTUP_TIME 2
/* Check peers for lost ones every 5 minutes */
#define CHECK_PEER_INTERVAL 300
/* How often to check recovery storage */
#define CHECK_STORAGE_INTERVAL 300
/* Interval to check for former peers in the gossip. */
#define CHECK_GOSSIP_INTERVAL 300
static struct plugin *plugin;
static struct gossmap *global_gossmap;
static struct plugin_timer *lost_state_timer, *find_exes_timer, *peer_storage_timer;
/* This tells if we are already in the process of recovery. */
static bool recovery, already_has_peers;
static struct command_result *do_check_lost_peer (struct command *cmd, void *unused);
static struct command_result *do_check_gossip (struct command *cmd, void *unused);
static struct command_result *do_find_peer_storage (struct command *cmd, void *unused);
static struct node_id local_id;
/* List of most connected nodes on the network */
static const char *nodes_for_gossip[] = {
"03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f@3.33.236.230", //ACINQ
"035e4ff418fc8b5554c5d9eea66396c227bd429a3251c8cbc711002ba215bfc226@170.75.163.209", //WalletOfSatoshi
"0217890e3aad8d35bc054f43acc00084b25229ecff0ab68debd82883ad65ee8266@23.237.77.11", //1ML.com node ALPHA
"0242a4ae0c5bef18048fbecf995094b74bfb0f7391418d71ed394784373f41e4f3@3.124.63.44", //CoinGate
"0364913d18a19c671bb36dd04d6ad5be0fe8f2894314c36a9db3f03c2d414907e1@192.243.215.102", //LQwD-Canada
"02f1a8c87607f415c8f22c00593002775941dea48869ce23096af27b0cfdcc0b69@52.13.118.208", //Kraken 🐙⚡
"037659a0ac8eb3b8d0a720114efc861d3a940382dcfa1403746b4f8f6b2e8810ba@34.78.139.195" //nicehash-ln1
"024b9a1fa8e006f1e3937f65f66c408e6da8e1ca728ea43222a7381df1cc449605@165.232.168.69", //BLUEIRON-v23.08.1
"034ea80f8b148c750463546bd999bf7321a0e6dfc60aaf84bd0400a2e8d376c0d5@213.174.156.66", //LNBiG Hub-1
"026165850492521f4ac8abd9bd8088123446d126f648ca35e60f88177dc149ceb2@45.86.229.190", //Boltz
};
static struct command_result *connect_success(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *params,
void *cb_arg UNUSED)
{
plugin_log(plugin, LOG_DBG, "Connected sucessfully!");
return command_still_pending(cmd);
}
static struct command_result *connect_fail(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *params,
void *cb_arg UNUSED)
{
plugin_log(plugin, LOG_DBG, "Failed to connect!");
return command_still_pending(cmd);
}
static struct command_result *after_emergency_recover(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *params,
void *cb_arg UNUSED)
{
/* TODO: CREATE GOSSMAP, connect to old peers and fetch peer storage */
plugin_log(plugin, LOG_DBG, "emergencyrecover called");
return command_still_pending(cmd);
}
static struct command_result *after_restorefrompeer(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *params,
void *cb_arg UNUSED)
{
plugin_log(plugin, LOG_DBG, "restorefrompeer called");
peer_storage_timer =
global_timer(plugin,
time_from_sec(CHECK_STORAGE_INTERVAL),
do_find_peer_storage, NULL);
return timer_complete(cmd);
}
static struct command_result *find_peer_storage (struct command *cmd)
{
peer_storage_timer = NULL;
struct out_req *req;
req = jsonrpc_request_start(cmd, "restorefrompeer",
after_restorefrompeer,
&forward_error, NULL);
return send_outreq(req);
}
static struct command_result *do_find_peer_storage(struct command *cmd, void *unused)
{
return find_peer_storage(cmd);
}
static struct command_result *do_check_gossip(struct command *cmd, void *unused)
{
find_exes_timer = NULL;
gossmap_refresh(global_gossmap, NULL);
plugin_log(plugin, LOG_DBG, "Finding our node in gossip");
struct gossmap_node *n = gossmap_find_node(global_gossmap, &local_id);
if (n) {
for (size_t i = 0; i < n->num_chans; i++) {
int half;
struct node_id peer_id;
struct gossmap_chan *c = gossmap_nth_chan(global_gossmap, n, i, &half);
struct gossmap_node *neighbour = gossmap_nth_node(global_gossmap, c, !half);
gossmap_node_get_id(global_gossmap, neighbour, &peer_id);
struct out_req *req;
req = jsonrpc_request_start(cmd,
"connect",
connect_success,
connect_fail,
NULL);
json_add_node_id(req->js, "id", &peer_id);
plugin_log(plugin, LOG_DBG, "Connecting to: %s",
fmt_node_id(tmpctx, &peer_id));
send_outreq(req);
}
peer_storage_timer =
global_timer(plugin,
time_from_sec(CHECK_STORAGE_INTERVAL),
do_find_peer_storage, NULL);
return timer_complete(cmd);
}
find_exes_timer = global_timer(
plugin, time_from_sec(CHECK_PEER_INTERVAL), do_check_gossip, NULL);
return timer_complete(cmd);
}
static void entering_recovery_mode(struct command *cmd)
{
if (!already_has_peers) {
for (size_t i = 0; i < ARRAY_SIZE(nodes_for_gossip); i++) {
struct out_req *req;
req = jsonrpc_request_start(cmd,
"connect",
connect_success,
connect_fail,
NULL);
plugin_log (plugin, LOG_DBG, "Connecting to %s", nodes_for_gossip[i]);
json_add_string(req->js, "id", nodes_for_gossip[i]);
send_outreq(req);
}
}
struct out_req *req_emer_recovery;
/* Let's try to recover whatever we have in the emergencyrecover file. */
req_emer_recovery = jsonrpc_request_start(cmd,
"emergencyrecover",
after_emergency_recover,
&forward_error,
NULL);
send_outreq(req_emer_recovery);
find_exes_timer = global_timer(
plugin, time_from_sec(CHECK_GOSSIP_INTERVAL), do_check_gossip, cmd);
return;
}
static struct command_result *after_listpeerchannels(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *params,
void *cb_arg UNUSED)
{
const jsmntok_t *iter, *lost_statetok;
const jsmntok_t *channelstok = json_get_member(buf, params, "channels");
size_t i;
bool lost_state;
json_for_each_arr(i, iter, channelstok) {
lost_statetok = json_get_member(buf, iter, "lost_state");
if (lost_statetok) {
json_to_bool(buf, lost_statetok, &lost_state);
if (lost_state) {
plugin_log(plugin, LOG_DBG, "Detected a channel with lost state, Entering Recovery mode!");
recovery = true;
break;
}
}
}
if (recovery) {
entering_recovery_mode(cmd);
return command_still_pending(cmd);
}
lost_state_timer =
global_timer(plugin, time_from_sec(CHECK_PEER_INTERVAL),
do_check_lost_peer, NULL);
return command_still_pending(cmd);
}
static struct command_result *check_lost_peer(struct command *cmd)
{
struct out_req *req;
req = jsonrpc_request_start(cmd, "listpeerchannels",
after_listpeerchannels,
&forward_error, NULL);
return send_outreq(req);
}
static struct command_result *do_check_lost_peer(struct command *cmd, void *unused)
{
/* Set to NULL when already in progress. */
lost_state_timer = NULL;
if (recovery) {
return timer_complete(cmd);
}
return check_lost_peer(cmd);
}
static const char *init(struct command *init_cmd,
const char *buf UNUSED,
const jsmntok_t *config UNUSED)
{
plugin = init_cmd->plugin;
plugin_log(plugin, LOG_DBG, "Recover Plugin Initialised!");
recovery = false;
lost_state_timer = global_timer(plugin, time_from_sec(STARTUP_TIME),
do_check_lost_peer, NULL);
u32 num_peers;
size_t num_cupdates_rejected;
/* Find number of peers */
rpc_scan(init_cmd, "getinfo",
take(json_out_obj(NULL, NULL, NULL)),
"{id:%,num_peers:%}",
JSON_SCAN(json_to_node_id, &local_id),
JSON_SCAN(json_to_u32, &num_peers));
global_gossmap = notleak_with_children(gossmap_load(NULL,
GOSSIP_STORE_FILENAME,
&num_cupdates_rejected));
if (!global_gossmap)
plugin_err(plugin, "Could not load gossmap %s: %s",
GOSSIP_STORE_FILENAME, strerror(errno));
if (num_cupdates_rejected)
plugin_log(plugin, LOG_DBG,
"gossmap ignored %zu channel updates",
num_cupdates_rejected);
plugin_log(plugin, LOG_DBG, "Gossmap loaded!");
already_has_peers = num_peers > 1 ? 1: 0;
return NULL;
}
int main(int argc, char *argv[])
{
setup_locale();
plugin_main(argv, init, NULL, PLUGIN_STATIC, true, NULL,
NULL, 0,
NULL, 0, NULL, 0,
NULL, 0, /* Notification topics we publish */
NULL);
}