Skip to content

Commit dc2c366

Browse files
committed
chanbackup: use local memory to store peer backups.
By keeping a local hash table, we won't have to look up every time. We still write to the datastore when it changes, and we need to initialize it at plugin start. Signed-off-by: Rusty Russell <[email protected]>
1 parent 2d821d6 commit dc2c366

File tree

2 files changed

+132
-28
lines changed

2 files changed

+132
-28
lines changed

plugins/chanbackup.c

Lines changed: 128 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <common/hsm_encryption.h>
1313
#include <common/json_param.h>
1414
#include <common/json_stream.h>
15+
#include <common/memleak.h>
1516
#include <common/scb_wiregen.h>
1617
#include <errno.h>
1718
#include <fcntl.h>
@@ -27,17 +28,55 @@
2728
/* VERSION is the current version of the data encrypted in the file */
2829
#define VERSION ((u64)1)
2930

31+
struct peer_backup {
32+
struct node_id peer;
33+
/* Empty if it's a placeholder */
34+
const u8 *data;
35+
};
36+
37+
static const struct node_id *peer_backup_keyof(const struct peer_backup *pb)
38+
{
39+
return &pb->peer;
40+
}
41+
42+
static bool peer_backup_eq_node_id(const struct peer_backup *pb,
43+
const struct node_id *id)
44+
{
45+
return node_id_eq(&pb->peer, id);
46+
}
47+
48+
HTABLE_DEFINE_NODUPS_TYPE(struct peer_backup,
49+
peer_backup_keyof,
50+
node_id_hash,
51+
peer_backup_eq_node_id,
52+
backup_map);
53+
3054
struct chanbackup {
3155
bool peer_backup;
3256
/* Global secret object to keep the derived encryption key for the SCB */
3357
struct secret secret;
58+
59+
/* Cache of backups for each peer we know about */
60+
struct backup_map *backups;
3461
};
3562

3663
static struct chanbackup *chanbackup(struct plugin *plugin)
3764
{
3865
return plugin_get_data(plugin, struct chanbackup);
3966
}
4067

68+
/* Must not already exist in map! */
69+
static struct peer_backup *add_to_backup_map(struct chanbackup *cb,
70+
const struct node_id *peer,
71+
const u8 *data TAKES)
72+
{
73+
struct peer_backup *pb = tal(cb->backups, struct peer_backup);
74+
pb->peer = *peer;
75+
pb->data = tal_dup_talarr(pb, u8, data);
76+
backup_map_add(cb->backups, pb);
77+
return pb;
78+
}
79+
4180
/* Helper to fetch out SCB from the RPC call */
4281
static bool json_to_scb_chan(const char *buffer,
4382
const jsmntok_t *tok,
@@ -585,19 +624,25 @@ static struct command_result *after_staticbackup(struct command *cmd,
585624
return send_outreq(req);
586625
}
587626

588-
static struct command_result *datastore_create_done(struct command *cmd,
589-
const char *method,
590-
const char *buf,
591-
const jsmntok_t *result,
592-
void *unused)
627+
/* Write to the datastore */
628+
static struct command_result *commit_peer_backup(struct command *cmd,
629+
const struct peer_backup *pb)
593630
{
594-
return notification_or_hook_done(cmd);
631+
return jsonrpc_set_datastore_binary(cmd,
632+
tal_fmt(cmd,
633+
"chanbackup/peers/%s",
634+
fmt_node_id(tmpctx,
635+
&pb->peer)),
636+
pb->data,
637+
"create-or-replace",
638+
NULL, NULL, NULL);
595639
}
596640

597641
static struct command_result *json_state_changed(struct command *cmd,
598642
const char *buf,
599643
const jsmntok_t *params)
600644
{
645+
struct chanbackup *cb = chanbackup(cmd->plugin);
601646
const jsmntok_t *notiftok = json_get_member(buf,
602647
params,
603648
"channel_state_changed"),
@@ -628,18 +673,13 @@ static struct command_result *json_state_changed(struct command *cmd,
628673
json_tok_full_len(notiftok),
629674
json_tok_full(buf, notiftok));
630675

631-
/* Might already exist, that's OK, just don't overwrite! */
632-
return jsonrpc_set_datastore_binary(cmd,
633-
tal_fmt(cmd,
634-
"chanbackup/peers/%s",
635-
fmt_node_id(tmpctx,
636-
&node_id)),
637-
/* Empty record */
638-
tal_arr(tmpctx, u8, 0),
639-
"must-create",
640-
datastore_create_done,
641-
datastore_create_done,
642-
NULL);
676+
/* Create a placeholder if necessary */
677+
if (!backup_map_get(cb->backups, &node_id)) {
678+
struct peer_backup *pb
679+
= add_to_backup_map(cb, &node_id,
680+
take(tal_arr(NULL, u8, 0)));
681+
return commit_peer_backup(cmd, pb);
682+
}
643683
}
644684

645685
return notification_or_hook_done(cmd);
@@ -754,6 +794,8 @@ static struct command_result *handle_your_peer_storage(struct command *cmd,
754794
}
755795

756796
if (fromwire_peer_storage(cmd, payload, &payload_deserialise)) {
797+
struct peer_backup *pb;
798+
757799
/* BOLT #1:
758800
* The receiver of `peer_storage`:
759801
* - If it offered `option_provide_storage`:
@@ -772,16 +814,13 @@ static struct command_result *handle_your_peer_storage(struct command *cmd,
772814
* update per minute.
773815
* - MUST replace the old `blob` with the latest received.
774816
*/
775-
return jsonrpc_set_datastore_binary(cmd,
776-
tal_fmt(cmd,
777-
"chanbackup/peers/%s",
778-
fmt_node_id(tmpctx,
779-
&node_id)),
780-
payload_deserialise,
781-
"must-replace",
782-
datastore_success,
783-
datastore_failed,
784-
"Saving chanbackup/peers/");
817+
pb = backup_map_get(cb->backups, &node_id);
818+
if (!pb)
819+
return command_hook_success(cmd);
820+
821+
tal_free(pb->data);
822+
pb->data = tal_steal(pb, payload_deserialise);
823+
return commit_peer_backup(cmd, pb);
785824
} else if (fromwire_peer_storage_retrieval(cmd, payload, &payload_deserialise)) {
786825
plugin_log(cmd->plugin, LOG_DBG,
787826
"Received peer_storage from peer.");
@@ -951,6 +990,64 @@ static struct command_result *on_commitment_revocation(struct command *cmd,
951990
return send_outreq(req);
952991
}
953992

993+
static void setup_backup_map(struct command *init_cmd,
994+
struct chanbackup *cb)
995+
{
996+
struct json_out *params = json_out_new(init_cmd);
997+
const jsmntok_t *result;
998+
const char *buf;
999+
const jsmntok_t *datastore, *t;
1000+
size_t i, total = 0;
1001+
1002+
cb->backups = tal(cb, struct backup_map);
1003+
backup_map_init(cb->backups);
1004+
1005+
json_out_start(params, NULL, '{');
1006+
json_out_start(params, "key", '[');
1007+
json_out_addstr(params, NULL, "chanbackup");
1008+
json_out_addstr(params, NULL, "peers");
1009+
json_out_end(params, ']');
1010+
json_out_end(params, '}');
1011+
1012+
result = jsonrpc_request_sync(tmpctx, init_cmd,
1013+
"listdatastore",
1014+
take(params), &buf);
1015+
1016+
datastore = json_get_member(buf, result, "datastore");
1017+
json_for_each_arr(i, t, datastore) {
1018+
const jsmntok_t *keytok, *hextok;
1019+
struct node_id peer;
1020+
u8 *data;
1021+
1022+
/* Key is an array, first two elements are chanbackup, peers */
1023+
keytok = json_get_member(buf, t, "key") + 3;
1024+
hextok = json_get_member(buf, t, "hex");
1025+
/* In case someone creates a subdir? */
1026+
if (!hextok)
1027+
continue;
1028+
if (!json_to_node_id(buf, keytok, &peer))
1029+
plugin_err(init_cmd->plugin,
1030+
"Could not parse datastore id '%.*s'",
1031+
json_tok_full_len(keytok),
1032+
json_tok_full(buf, keytok));
1033+
data = json_tok_bin_from_hex(NULL, buf, hextok);
1034+
/* Only count non-empty ones. */
1035+
if (tal_bytelen(data) != 0)
1036+
total++;
1037+
add_to_backup_map(cb, &peer, take(data));
1038+
}
1039+
if (total)
1040+
plugin_log(init_cmd->plugin, LOG_INFORM,
1041+
"Loaded %zu stored backups for peers", total);
1042+
}
1043+
1044+
static void chanbackup_mark_mem(struct plugin *plugin,
1045+
struct htable *memtable)
1046+
{
1047+
const struct chanbackup *cb = chanbackup(plugin);
1048+
memleak_scan_htable(memtable, &cb->backups->raw);
1049+
}
1050+
9541051
static const char *init(struct command *init_cmd,
9551052
const char *buf UNUSED,
9561053
const jsmntok_t *config UNUSED)
@@ -979,6 +1076,7 @@ static const char *init(struct command *init_cmd,
9791076
tal_bytelen(info_hex)))),
9801077
"{secret:%}", JSON_SCAN(json_to_secret, &cb->secret));
9811078

1079+
setup_backup_map(init_cmd, cb);
9821080
plugin_set_data(init_cmd->plugin, cb);
9831081
plugin_log(init_cmd->plugin, LOG_DBG, "Chanbackup Initialised!");
9841082

@@ -987,6 +1085,8 @@ static const char *init(struct command *init_cmd,
9871085

9881086
maybe_create_new_scb(init_cmd->plugin, scb_chan);
9891087

1088+
plugin_set_memleak_handler(init_cmd->plugin,
1089+
chanbackup_mark_mem);
9901090
return NULL;
9911091
}
9921092

tests/test_plugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4639,6 +4639,10 @@ def test_peer_storage(node_factory, bitcoind):
46394639

46404640
# Now try restarting l2 and connecting that way instead.
46414641
l2.restart()
4642+
# Could happen before or after Started message.
4643+
l2.daemon.logsearch_start = 0
4644+
l2.daemon.wait_for_log("INFO.*chanbackup: Loaded 1 stored backups for peers")
4645+
46424646
l2.rpc.connect(l1.info['id'], 'localhost', l1.port)
46434647

46444648
l1.daemon.wait_for_logs([r'peer_out WIRE_PEER_STORAGE_RETRIEVAL',

0 commit comments

Comments
 (0)