#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES #define ABYTES crypto_secretstream_xchacha20poly1305_ABYTES #define FILENAME "emergency.recover" /* VERSION is the current version of the data encrypted in the file */ #define VERSION ((u64)1) /* How many peers do we send a backup to? */ #define NUM_BACKUP_PEERS 2 struct peer_backup { struct node_id peer; /* Empty if it's a placeholder */ const u8 *data; }; static const struct node_id *peer_backup_keyof(const struct peer_backup *pb) { return &pb->peer; } static bool peer_backup_eq_node_id(const struct peer_backup *pb, const struct node_id *id) { return node_id_eq(&pb->peer, id); } HTABLE_DEFINE_NODUPS_TYPE(struct peer_backup, peer_backup_keyof, node_id_hash, peer_backup_eq_node_id, backup_map); HTABLE_DEFINE_NODUPS_TYPE(struct node_id, node_id_keyof, node_id_hash, node_id_eq, peer_map); struct chanbackup { /* Do we send/acccept peer backups? */ bool send_our_peer_backup; bool handle_their_peer_backup; /* Global secret object to keep the derived encryption key for the SCB */ struct secret secret; /* Cache of backups for each peer we know about */ struct backup_map *backups; /* Cache of known peers which support backups (for sending) */ struct peer_map *peers; }; static struct chanbackup *chanbackup(struct plugin *plugin) { return plugin_get_data(plugin, struct chanbackup); } /* Must not already exist in map! */ static struct peer_backup *add_to_backup_map(struct chanbackup *cb, const struct node_id *peer, const u8 *data TAKES) { struct peer_backup *pb = tal(cb->backups, struct peer_backup); pb->peer = *peer; pb->data = tal_dup_talarr(pb, u8, data); backup_map_add(cb->backups, pb); return pb; } static void remove_peer(struct plugin *plugin, const struct node_id *node_id) { struct chanbackup *cb = chanbackup(plugin); struct node_id *peer; /* Eliminate it (probably it's disconnected) */ peer = peer_map_get(cb->peers, node_id); if (peer) { peer_map_del(cb->peers, peer); tal_free(peer); } } /* Helper to fetch out SCB from the RPC call */ static bool json_to_scb_chan(const char *buffer, const jsmntok_t *tok, struct modern_scb_chan ***channels) { size_t i; const jsmntok_t *t; *channels = tok->size ? tal_arr(tmpctx, struct modern_scb_chan *, tok->size) : NULL; json_for_each_arr(i, t, tok) { const u8 *scb_tmp = tal_hexdata(tmpctx, json_strdup(tmpctx, buffer, t), strlen(json_strdup(tmpctx, buffer, t))); size_t scblen_tmp = tal_count(scb_tmp); (*channels)[i] = fromwire_modern_scb_chan(tmpctx, &scb_tmp, &scblen_tmp); } return true; } /* This writes encrypted static backup in the recovery file */ static void write_scb(struct plugin *p, int fd, struct modern_scb_chan **scb_chan_arr) { const struct chanbackup *cb = chanbackup(p); u32 timestamp = clock_time().ts.tv_sec; u8 *decrypted_scb = towire_static_chan_backup_with_tlvs(tmpctx, VERSION, timestamp, cast_const2(const struct modern_scb_chan **, scb_chan_arr)); u8 *encrypted_scb = tal_arr(tmpctx, u8, tal_bytelen(decrypted_scb) + ABYTES + HEADER_LEN); crypto_secretstream_xchacha20poly1305_state crypto_state; if (crypto_secretstream_xchacha20poly1305_init_push(&crypto_state, encrypted_scb, cb->secret.data) != 0) { plugin_err(p, "Can't encrypt the data!"); return; } if (crypto_secretstream_xchacha20poly1305_push(&crypto_state, encrypted_scb + HEADER_LEN, NULL, decrypted_scb, tal_bytelen(decrypted_scb), /* Additional data and tag */ NULL, 0, 0)) { plugin_err(p, "Can't encrypt the data!"); return; } if (!write_all(fd, encrypted_scb, tal_bytelen(encrypted_scb))) { unlink_noerr("scb.tmp"); plugin_err(p, "Writing encrypted SCB: %s", strerror(errno)); } } /* checks if the SCB file exists, creates a new one in case it doesn't. */ static void maybe_create_new_scb(struct plugin *p, struct modern_scb_chan **channels) { /* Note that this is opened for write-only, even though the permissions * are set to read-only. That's perfectly valid! */ int fd = open(FILENAME, O_CREAT|O_EXCL|O_WRONLY, 0400); if (fd < 0) { /* Don't do anything if the file already exists. */ if (errno == EEXIST) return; plugin_err(p, "creating: %s", strerror(errno)); } /* Comes here only if the file haven't existed before */ unlink_noerr(FILENAME); /* This couldn't give EEXIST because we call unlink_noerr("scb.tmp") * in INIT */ fd = open("scb.tmp", O_CREAT|O_EXCL|O_WRONLY, 0400); if (fd < 0) plugin_err(p, "Opening: %s", strerror(errno)); plugin_log(p, LOG_INFORM, "Creating Emergency Recovery"); write_scb(p, fd, channels); /* fsync (mostly!) ensures that the file has reached the disk. */ if (fsync(fd) != 0) { unlink_noerr("scb.tmp"); plugin_err(p, "fsync : %s", strerror(errno)); } /* This should never fail if fsync succeeded. But paranoia good, and * bugs exist. */ if (close(fd) != 0) { unlink_noerr("scb.tmp"); plugin_err(p, "closing: %s", strerror(errno)); } /* We actually need to sync the *directory itself* to make sure the * file exists! You're only allowed to open directories read-only in * modern Unix though. */ fd = open(".", O_RDONLY); if (fd < 0) plugin_err(p, "Opening: %s", strerror(errno)); if (fsync(fd) != 0) { unlink_noerr("scb.tmp"); plugin_err(p, "closing: %s", strerror(errno)); } /* This will never fail, if fsync worked! */ close(fd); /* This will update the scb file */ rename("scb.tmp", FILENAME); } static u8 *get_file_data(const tal_t *ctx, struct plugin *p) { u8 *scb = grab_file_raw(ctx, FILENAME); if (!scb) plugin_err(p, "Cannot read emergency.recover: %s", strerror(errno)); return scb; } /* Returns decrypted SCB in form of a u8 array */ static u8 *decrypt_scb(struct plugin *p) { const struct chanbackup *cb = chanbackup(p); u8 *filedata = get_file_data(tmpctx, p); crypto_secretstream_xchacha20poly1305_state crypto_state; if (tal_bytelen(filedata) < ABYTES + HEADER_LEN) plugin_err(p, "SCB file is corrupted!"); u8 *decrypt_scb = tal_arr(tmpctx, u8, tal_bytelen(filedata) - ABYTES - HEADER_LEN); /* The header part */ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, filedata, cb->secret.data) != 0) { plugin_err(p, "SCB file is corrupted!"); } if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, decrypt_scb, NULL, 0, filedata + HEADER_LEN, tal_bytelen(filedata)- HEADER_LEN, NULL, 0) != 0) { plugin_err(p, "SCB file is corrupted!"); } return decrypt_scb; } static struct command_result *after_recover_rpc(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, void *cb_arg UNUSED) { size_t i; const jsmntok_t *t; struct json_stream *response; response = jsonrpc_stream_success(cmd); json_for_each_obj(i, t, params) json_add_tok(response, json_strdup(tmpctx, buf, t), t+1, buf); return command_finished(cmd, response); } static struct modern_scb_chan *convert_from_legacy(const tal_t *ctx, struct legacy_scb_chan *legacy_scb_chan) { struct modern_scb_chan *modern_scb_tlv = tal(ctx, struct modern_scb_chan); modern_scb_tlv->id = legacy_scb_chan->id; modern_scb_tlv->addr = legacy_scb_chan->addr; modern_scb_tlv->node_id = legacy_scb_chan->node_id; modern_scb_tlv->cid = legacy_scb_chan->cid; modern_scb_tlv->funding = legacy_scb_chan->funding; modern_scb_tlv->funding_sats = legacy_scb_chan->funding_sats; modern_scb_tlv->type = legacy_scb_chan->type; modern_scb_tlv->tlvs = tlv_scb_tlvs_new(ctx); return modern_scb_tlv; } /* Recovers the channels by making RPC to `recoverchannel` */ static struct command_result *json_emergencyrecover(struct command *cmd, const char *buf, const jsmntok_t *params) { struct out_req *req; u64 version; u32 timestamp; struct legacy_scb_chan **scb; struct modern_scb_chan **scb_tlvs; if (!param(cmd, buf, params, NULL)) return command_param_failed(); u8 *res = decrypt_scb(cmd->plugin); bool is_tlvs = false; if (!fromwire_static_chan_backup(cmd, res, &version, ×tamp, &scb)) { if(!fromwire_static_chan_backup_with_tlvs(cmd, res, &version, ×tamp, &scb_tlvs)) { plugin_err(cmd->plugin, "Corrupted SCB!"); } is_tlvs = true; } if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) { plugin_err(cmd->plugin, "Incompatible emergencyrecover version: loaded version %"PRIu64", expected version %"PRIu64". Contact the admin!", version, VERSION); } req = jsonrpc_request_start(cmd, "recoverchannel", after_recover_rpc, forward_error, NULL); json_array_start(req->js, "scb"); if (is_tlvs) { for (size_t i=0; ijs, NULL, scb_hex); } } else { plugin_notify_message(cmd, LOG_DBG, "Processing legacy emergency.recover file format. " "Please migrate to the latest file format for improved " "compatibility and fund recovery."); for (size_t i=0; ijs, NULL, scb_hex); } } json_array_end(req->js); return send_outreq(req); } static void update_scb(struct plugin *p, struct modern_scb_chan **channels) { /* If the temp file existed before, remove it */ unlink_noerr("scb.tmp"); int fd = open("scb.tmp", O_CREAT|O_EXCL|O_WRONLY, 0400); if (fd<0) plugin_err(p, "Opening: %s", strerror(errno)); plugin_log(p, LOG_DBG, "Updating the SCB file..."); write_scb(p, fd, channels); /* fsync (mostly!) ensures that the file has reached the disk. */ if (fsync(fd) != 0) { unlink_noerr("scb.tmp"); } /* This should never fail if fsync succeeded. But paranoia good, and * bugs exist. */ if (close(fd) != 0) { unlink_noerr("scb.tmp"); } /* We actually need to sync the *directory itself* to make sure the * file exists! You're only allowed to open directories read-only in * modern Unix though. */ fd = open(".", O_RDONLY); if (fd < 0) { plugin_log(p, LOG_DBG, "Opening: %s", strerror(errno)); } if (fsync(fd) != 0) { unlink_noerr("scb.tmp"); } close(fd); /* This will atomically replace the main file */ rename("scb.tmp", FILENAME); } static struct command_result *peer_after_send_their_peer_strg(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct node_id *node_id) { plugin_log(cmd->plugin, LOG_DBG, "Sent their peer storage!"); return command_hook_success(cmd); } static struct command_result *peer_after_send_their_peer_strg_err(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct node_id *node_id) { plugin_log(cmd->plugin, LOG_DBG, "Unable to send Peer storage!"); remove_peer(cmd->plugin, node_id); return command_hook_success(cmd); } static struct command_result *send_peers_scb(struct command *cmd, struct node_id *nodeid) { const struct chanbackup *cb = chanbackup(cmd->plugin); struct peer_backup *pb; struct out_req *req; const u8 *msg; if (!cb->handle_their_peer_backup) return command_hook_success(cmd); /* Now send their backup, if any. */ pb = backup_map_get(cb->backups, nodeid); if (!pb || tal_bytelen(pb->data) == 0) return command_hook_success(cmd); msg = towire_peer_storage_retrieval(cmd, pb->data); plugin_log(cmd->plugin, LOG_DBG, "sending their backup from our datastore"); req = jsonrpc_request_start(cmd, "sendcustommsg", peer_after_send_their_peer_strg, peer_after_send_their_peer_strg_err, nodeid); json_add_node_id(req->js, "node_id", nodeid); json_add_hex_talarr(req->js, "msg", msg); return send_outreq(req); } static struct command_result *peer_after_send_scb(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct node_id *nodeid) { plugin_log(cmd->plugin, LOG_DBG, "Peer storage sent!"); return send_peers_scb(cmd, nodeid); } static struct command_result *peer_after_send_scb_failed(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct node_id *nodeid) { plugin_log(cmd->plugin, LOG_DBG, "Peer storage send failed %.*s!", json_tok_full_len(params), json_tok_full(buf, params)); remove_peer(cmd->plugin, nodeid); return command_hook_success(cmd); } struct info { size_t *idx; struct node_id node_id; }; /* We refresh scb from both channel_state_changed notification and on_commitment_revocation hook. Both have to be terminated differently. */ static struct command_result *notification_or_hook_done(struct command *cmd) { if (cmd->type == COMMAND_TYPE_NOTIFICATION) return notification_handled(cmd); assert(cmd->type == COMMAND_TYPE_HOOK); return command_hook_success(cmd); } static struct command_result *after_send_scb_single(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct info *info) { plugin_log(cmd->plugin, LOG_TRACE, "Peer storage sent!"); if (--(*info->idx) != 0) return command_still_pending(cmd); return notification_or_hook_done(cmd); } static struct command_result *after_send_scb_single_fail(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, struct info *info) { plugin_log(cmd->plugin, LOG_DBG, "Peer storage send failed!"); remove_peer(cmd->plugin, &info->node_id); if (--(*info->idx) != 0) return command_still_pending(cmd); return notification_or_hook_done(cmd); } static bool already_have_node_id(const struct node_id *ids, const struct node_id *id) { for (size_t i = 0; i < tal_count(ids); i++) if (node_id_eq(&ids[i], id)) return true; return false; } static const struct node_id *random_peers(const tal_t *ctx, const struct chanbackup *cb) { struct peer_map_iter it; const struct node_id *peer; struct node_id *ids = tal_arr(ctx, struct node_id, 0); /* Simple case: pick all of them */ if (peer_map_count(cb->peers) <= NUM_BACKUP_PEERS) { for (peer = peer_map_first(cb->peers, &it); peer; peer = peer_map_next(cb->peers, &it)) { tal_arr_expand(&ids, *peer); } } else { while (peer_map_count(cb->peers) < NUM_BACKUP_PEERS) { peer = peer_map_pick(cb->peers, pseudorand_u64(), &it); if (already_have_node_id(ids, peer)) continue; tal_arr_expand(&ids, *peer); } } return ids; } static struct command_result *send_to_peers(struct command *cmd) { struct out_req *req; size_t *idx = tal(cmd, size_t); u8 *serialise_scb, *data; const struct node_id *peers; struct chanbackup *cb = chanbackup(cmd->plugin); if (!cb->send_our_peer_backup) return notification_or_hook_done(cmd); /* BOLT #1: * The sender of `peer_storage`: * - MAY send `peer_storage` whenever necessary. * - MUST limit its `blob` to 65531 bytes. * - MUST encrypt the data in a manner that ensures its integrity * upon receipt. * - SHOULD pad the `blob` to ensure its length is always exactly 65531 bytes. */ /* FIXME: We do not pad! But this is because LDK doesn't store > 1k anyway */ data = get_file_data(tmpctx, cmd->plugin); if (tal_bytelen(data) > 65531) { plugin_log(cmd->plugin, LOG_UNUSUAL, "Peer backup would be %zu bytes. That is too large: disabling peer backups!", tal_bytelen(data)); cb->send_our_peer_backup = false; return notification_or_hook_done(cmd); } serialise_scb = towire_peer_storage(cmd, data); peers = random_peers(tmpctx, cb); *idx = tal_count(peers); for (size_t i = 0; i < tal_count(peers); i++) { struct info *info = tal(cmd, struct info); info->idx = idx; info->node_id = peers[i]; req = jsonrpc_request_start(cmd, "sendcustommsg", after_send_scb_single, after_send_scb_single_fail, info); json_add_node_id(req->js, "node_id", &info->node_id); json_add_hex_talarr(req->js, "msg", serialise_scb); send_outreq(req); } if (*idx == 0) return notification_or_hook_done(cmd); return command_still_pending(cmd); } static struct command_result *after_staticbackup(struct command *cmd, const char *method, const char *buf, const jsmntok_t *params, void *cb_arg UNUSED) { struct modern_scb_chan **scb_chan; const jsmntok_t *scbs = json_get_member(buf, params, "scb"); json_to_scb_chan(buf, scbs, &scb_chan); plugin_log(cmd->plugin, LOG_DBG, "Updating the SCB"); update_scb(cmd->plugin, scb_chan); return send_to_peers(cmd); } /* Write to the datastore */ static struct command_result *commit_peer_backup(struct command *cmd, const struct peer_backup *pb) { return jsonrpc_set_datastore_binary(cmd, tal_fmt(cmd, "chanbackup/peers/%s", fmt_node_id(tmpctx, &pb->peer)), pb->data, tal_bytelen(pb->data), "create-or-replace", NULL, NULL, NULL); } static struct command_result *json_state_changed(struct command *cmd, const char *buf, const jsmntok_t *params) { struct chanbackup *cb = chanbackup(cmd->plugin); const jsmntok_t *notiftok = json_get_member(buf, params, "channel_state_changed"), *statetok = json_get_member(buf, notiftok, "new_state"); if (json_tok_streq(buf, statetok, "CLOSED") || json_tok_streq(buf, statetok, "CHANNELD_AWAITING_LOCKIN") || json_tok_streq(buf, statetok, "DUALOPEND_AWAITING_LOCKIN")) { struct out_req *req; req = jsonrpc_request_start(cmd, "staticbackup", after_staticbackup, log_broken_and_complete, NULL); return send_outreq(req); } /* Once it has a normal channel, we will start storing its * backups: put an empty record in place. */ if (json_tok_streq(buf, statetok, "CHANNELD_NORMAL")) { const jsmntok_t *nodeid_tok; struct node_id node_id; nodeid_tok = json_get_member(buf, notiftok, "peer_id"); if (!json_to_node_id(buf, nodeid_tok, &node_id)) plugin_err(cmd->plugin, "Invalid peer_id in %.*s", json_tok_full_len(notiftok), json_tok_full(buf, notiftok)); /* Create a placeholder if necessary */ if (!backup_map_get(cb->backups, &node_id)) { struct peer_backup *pb = add_to_backup_map(cb, &node_id, take(tal_arr(NULL, u8, 0))); return commit_peer_backup(cmd, pb); } } return notification_or_hook_done(cmd); } /* We use the hook here, since we want to send data to peer before any * reconnect messages (which might make it hang up!) */ static struct command_result *peer_connected(struct command *cmd, const char *buf, const jsmntok_t *params) { struct node_id *node_id; struct out_req *req; u8 *serialise_scb; const char *err; u8 *features; struct chanbackup *cb = chanbackup(cmd->plugin); serialise_scb = towire_peer_storage(cmd, get_file_data(tmpctx, cmd->plugin)); node_id = tal(cmd, struct node_id); err = json_scan(cmd, buf, params, "{peer:{id:%,features:%}}", JSON_SCAN(json_to_node_id, node_id), JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &features)); if (err) { plugin_err(cmd->plugin, "peer_connected hook did not scan %s: %.*s", err, json_tok_full_len(params), json_tok_full(buf, params)); } /* We shouldn't have to check, but LND hangs up? */ if (!feature_offered(features, OPT_PROVIDE_STORAGE)) { return command_hook_success(cmd); } /* Remember this peer for future sends */ if (!peer_map_get(cb->peers, node_id)) peer_map_add(cb->peers, tal_dup(cb->peers, struct node_id, node_id)); if (!cb->send_our_peer_backup) return send_peers_scb(cmd, node_id); req = jsonrpc_request_start(cmd, "sendcustommsg", peer_after_send_scb, peer_after_send_scb_failed, node_id); json_add_node_id(req->js, "node_id", node_id); json_add_hex_talarr(req->js, "msg", serialise_scb); return send_outreq(req); } static struct command_result *failed_peer_restore(struct command *cmd, struct node_id *node_id, char *reason) { /* BOLT #1: * * The receiver of `peer_storage_retrieval`: * - when it receives `peer_storage_retrieval` with an outdated or irrelevant data: * - MAY send a warning. */ /* We don't, we just complain in the logs a little! */ plugin_log(cmd->plugin, LOG_DBG, "PeerStorageFailed!: %s: %s", fmt_node_id(tmpctx, node_id), reason); return command_hook_success(cmd); } static struct command_result *datastore_success(struct command *cmd, const char *method, const char *buf, const jsmntok_t *result, char *what) { plugin_log(cmd->plugin, LOG_DBG, "datastore succeeded for %s", what); return command_hook_success(cmd); } static struct command_result *datastore_failed(struct command *cmd, const char *method, const char *buf, const jsmntok_t *result, char *what) { plugin_log(cmd->plugin, LOG_DBG, "datastore failed for %s: %.*s", what, json_tok_full_len(result), json_tok_full(buf, result)); return command_hook_success(cmd); } static struct command_result *handle_your_peer_storage(struct command *cmd, const char *buf, const jsmntok_t *params) { struct node_id node_id; u8 *payload, *payload_deserialise; const char *err; const struct chanbackup *cb = chanbackup(cmd->plugin); err = json_scan(cmd, buf, params, "{payload:%,peer_id:%}", JSON_SCAN_TAL(cmd, json_tok_bin_from_hex, &payload), JSON_SCAN(json_to_node_id, &node_id)); if (err) { plugin_err(cmd->plugin, "`your_peer_storage` response did not scan %s: %.*s", err, json_tok_full_len(params), json_tok_full(buf, params)); } if (fromwire_peer_storage(cmd, payload, &payload_deserialise)) { struct peer_backup *pb; if (!cb->handle_their_peer_backup) return command_hook_success(cmd); /* BOLT #1: * The receiver of `peer_storage`: * - If it offered `option_provide_storage`: * - if it has an open channel with the sender: * - MUST store the message. * - MAY store the message anyway. */ /* We store if we have a datastore slot for it * (otherwise, this fails). We create those once it * has a channel, though the user could also create an * empty one if they wanted to */ /* BOLT #1: * - If it does store the message: * - MAY delay storage to ratelimit peer to no more than one * update per minute. * - MUST replace the old `blob` with the latest received. */ pb = backup_map_get(cb->backups, &node_id); if (!pb) return command_hook_success(cmd); tal_free(pb->data); pb->data = tal_steal(pb, payload_deserialise); return commit_peer_backup(cmd, pb); } else if (fromwire_peer_storage_retrieval(cmd, payload, &payload_deserialise)) { crypto_secretstream_xchacha20poly1305_state crypto_state; plugin_log(cmd->plugin, LOG_DBG, "Received peer_storage from peer."); if (!cb->send_our_peer_backup) return command_hook_success(cmd); if (tal_bytelen(payload_deserialise) < ABYTES + HEADER_LEN) return failed_peer_restore(cmd, &node_id, "Too short!"); u8 *decoded_bkp = tal_arr(tmpctx, u8, tal_bytelen(payload_deserialise) - ABYTES - HEADER_LEN); /* The header part */ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, payload_deserialise, cb->secret.data) != 0) return failed_peer_restore(cmd, &node_id, "Peer altered our data"); if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, decoded_bkp, NULL, 0, payload_deserialise + HEADER_LEN, tal_bytelen(payload_deserialise) - HEADER_LEN, NULL, 0) != 0) return failed_peer_restore(cmd, &node_id, "Peer altered our data"); return jsonrpc_set_datastore_binary(cmd, "chanbackup/latestscb", decoded_bkp, tal_bytelen(decoded_bkp), "create-or-replace", datastore_success, datastore_failed, "Saving latestscb"); } else { /* Any other message we ignore */ return command_hook_success(cmd); } } static struct command_result *after_latestscb(struct command *cmd, const u8 *res, void *cb_arg UNUSED) { u64 version; u32 timestamp; struct modern_scb_chan **scb_tlvs; struct legacy_scb_chan **scb; struct json_stream *response; struct out_req *req; if (tal_bytelen(res) == 0) { response = jsonrpc_stream_success(cmd); json_add_string(response, "result", "No backup received from peers"); return command_finished(cmd, response); } bool is_tlvs = false; if (!fromwire_static_chan_backup(cmd, res, &version, ×tamp, &scb)) { if(!fromwire_static_chan_backup_with_tlvs(cmd, res, &version, ×tamp, &scb_tlvs)) { plugin_err(cmd->plugin, "Corrupted SCB!"); } is_tlvs = true; } if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) { plugin_err(cmd->plugin, "Incompatible emergencyrecover version: loaded version %"PRIu64", expected version %"PRIu64". Contact the admin!", version, VERSION); } req = jsonrpc_request_start(cmd, "recoverchannel", after_recover_rpc, &forward_error, NULL); json_array_start(req->js, "scb"); if (is_tlvs) { for (size_t i=0; ijs, NULL, scb_hex); } } else { for (size_t i=0; iid = scb[i]->id; tmp_scb_tlv->addr = scb[i]->addr; tmp_scb_tlv->cid = scb[i]->cid; tmp_scb_tlv->funding = scb[i]->funding; tmp_scb_tlv->funding_sats = scb[i]->funding_sats; tmp_scb_tlv->type = scb[i]->type; tmp_scb_tlv->tlvs = tlv_scb_tlvs_new(cmd); towire_modern_scb_chan(&scb_hex, tmp_scb_tlv); json_add_hex_talarr(req->js, NULL, scb_hex); } } json_array_end(req->js); return send_outreq(req); } static struct command_result *json_restorefrompeer(struct command *cmd, const char *buf, const jsmntok_t *params) { if (!param(cmd, buf, params, NULL)) return command_param_failed(); return jsonrpc_get_datastore_binary(cmd, "chanbackup/latestscb", after_latestscb, NULL); } static struct command_result *json_getemergencyrecoverdata(struct command *cmd, const char *buf, const jsmntok_t *params) { u8 *filedata; if (!param(cmd, buf, params, NULL)) return command_param_failed(); struct json_stream *response; filedata = get_file_data(tmpctx, cmd->plugin); response = jsonrpc_stream_success(cmd); json_add_hex_talarr(response, "filedata", filedata); return command_finished(cmd, response); } static struct command_result *on_commitment_revocation(struct command *cmd, const char *buf, const jsmntok_t *params) { struct out_req *req; plugin_log(cmd->plugin, LOG_DBG, "Updated `emergency.recover` state after receiving new commitment secret."); req = jsonrpc_request_start(cmd, "staticbackup", after_staticbackup, log_broken_and_complete, NULL); return send_outreq(req); } static void setup_backup_map(struct command *init_cmd, struct chanbackup *cb) { struct json_out *params = json_out_new(init_cmd); const jsmntok_t *result; const char *buf; const jsmntok_t *datastore, *t; size_t i, total = 0; cb->backups = new_htable(cb, backup_map); cb->peers = new_htable(cb, peer_map); json_out_start(params, NULL, '{'); json_out_start(params, "key", '['); json_out_addstr(params, NULL, "chanbackup"); json_out_addstr(params, NULL, "peers"); json_out_end(params, ']'); json_out_end(params, '}'); result = jsonrpc_request_sync(tmpctx, init_cmd, "listdatastore", take(params), &buf); datastore = json_get_member(buf, result, "datastore"); json_for_each_arr(i, t, datastore) { const jsmntok_t *keytok, *hextok; struct node_id peer; u8 *data; /* Key is an array, first two elements are chanbackup, peers */ keytok = json_get_member(buf, t, "key") + 3; hextok = json_get_member(buf, t, "hex"); /* In case someone creates a subdir? */ if (!hextok) continue; if (!json_to_node_id(buf, keytok, &peer)) plugin_err(init_cmd->plugin, "Could not parse datastore id '%.*s'", json_tok_full_len(keytok), json_tok_full(buf, keytok)); data = json_tok_bin_from_hex(NULL, buf, hextok); /* Only count non-empty ones. */ if (tal_bytelen(data) != 0) total++; add_to_backup_map(cb, &peer, take(data)); } if (total) plugin_log(init_cmd->plugin, LOG_INFORM, "Loaded %zu stored backups for peers", total); } static const char *init(struct command *init_cmd, const char *buf UNUSED, const jsmntok_t *config UNUSED) { struct chanbackup *cb = tal(init_cmd->plugin, struct chanbackup); struct modern_scb_chan **scb_chan; const char *info = "scb secret"; u8 *info_hex = tal_dup_arr(tmpctx, u8, (u8*)info, strlen(info), 0); u8 *features; /* Figure out if they specified --experimental-peer-storage */ rpc_scan(init_cmd, "getinfo", take(json_out_obj(NULL, NULL, NULL)), "{our_features:{init:%}}", JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &features)); /* If we unset this feature, we don't even *send* peer backups */ cb->handle_their_peer_backup = cb->send_our_peer_backup = feature_offered(features, OPT_PROVIDE_STORAGE); rpc_scan(init_cmd, "staticbackup", take(json_out_obj(NULL, NULL, NULL)), "{scb:%}", JSON_SCAN(json_to_scb_chan, &scb_chan)); rpc_scan(init_cmd, "makesecret", take(json_out_obj(NULL, "hex", tal_hexstr(tmpctx, info_hex, tal_bytelen(info_hex)))), "{secret:%}", JSON_SCAN(json_to_secret, &cb->secret)); setup_backup_map(init_cmd, cb); plugin_set_data(init_cmd->plugin, cb); plugin_log(init_cmd->plugin, LOG_DBG, "Chanbackup Initialised!"); /* flush the tmp file, if exists */ unlink_noerr("scb.tmp"); maybe_create_new_scb(init_cmd->plugin, scb_chan); return NULL; } static const struct plugin_notification notifs[] = { { "channel_state_changed", json_state_changed, }, }; static u64 custommsg_types[] = { WIRE_PEER_STORAGE, WIRE_PEER_STORAGE_RETRIEVAL }; static const struct plugin_hook hooks[] = { { .name = "custommsg", .handle = handle_your_peer_storage, .intfilters = custommsg_types, .num_intfilters = ARRAY_SIZE(custommsg_types), }, { "peer_connected", peer_connected, }, { "commitment_revocation", on_commitment_revocation, } }; static const struct plugin_command commands[] = { { "emergencyrecover", json_emergencyrecover, }, { "getemergencyrecoverdata", json_getemergencyrecoverdata, }, { "restorefrompeer", json_restorefrompeer, }, }; int main(int argc, char *argv[]) { setup_locale(); plugin_main(argv, init, NULL, PLUGIN_STATIC, true, NULL, commands, ARRAY_SIZE(commands), notifs, ARRAY_SIZE(notifs), hooks, ARRAY_SIZE(hooks), NULL, 0, /* Notification topics we publish */ NULL); }