Skip to content

Peer storage enhancements #8422

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13860,7 +13860,9 @@
},
"response": {
"required": [
"filedata"
"filedata",
"can_create_penalty",
"backed_up_channel_ids"
],
"additionalProperties": false,
"properties": {
Expand All @@ -13869,6 +13871,21 @@
"description": [
"The raw, hex-encoded, emergency.recover file"
]
},
"can_create_penalty": {
"type": "boolean",
"description": [
"If false, you are using legacy file version, which can not create penalty transactions, kindly delete emergency.recover and restart the node!"
]
},
"backed_up_channel_ids": {
"type": "array",
"items": {
"type": "hex",
"description": [
"Channel IDs of channels backed up inside emergency.recover"
]
}
}
}
},
Expand Down
19 changes: 18 additions & 1 deletion doc/schemas/getemergencyrecoverdata.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
},
"response": {
"required": [
"filedata"
"filedata",
"can_create_penalty",
"backed_up_channel_ids"
],
"additionalProperties": false,
"properties": {
Expand All @@ -22,6 +24,21 @@
"description": [
"The raw, hex-encoded, emergency.recover file"
]
},
"can_create_penalty": {
"type": "boolean",
"description": [
"If false, you are using legacy file version, which can not create penalty transactions, kindly delete emergency.recover and restart the node!"
]
},
"backed_up_channel_ids": {
"type": "array",
"items": {
"type": "hex",
"description": [
"Channel IDs of channels backed up inside emergency.recover"
]
}
}
}
},
Expand Down
186 changes: 107 additions & 79 deletions plugins/chanbackup.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,35 @@ static struct modern_scb_chan *convert_from_legacy(const tal_t *ctx, struct lega
return modern_scb_tlv;
}

/* Reads WIRE_STATIC_CHAN_BACKUP and converts from legacy_scb_chan to modern_scb_chan, if required. */
static void read_static_chan_backup(struct command *cmd, const u8 *blob, u64 *version, u32 *timestamp, struct modern_scb_chan ***scb_tlvs, bool *is_converted) {
bool is_tlvs = false;
struct legacy_scb_chan **scb;

if(!fromwire_static_chan_backup(cmd,
blob,
version,
timestamp,
&scb)) {
is_tlvs = true;
if (!fromwire_static_chan_backup_with_tlvs(cmd,
blob,
version,
timestamp,
scb_tlvs)) {
plugin_err(cmd->plugin, "Corrupted SCB!");
}
}
*is_converted = !is_tlvs;
if (!is_tlvs) {
*scb_tlvs = tal_count(scb) ? tal_arr(cmd, struct modern_scb_chan *, tal_count(scb)): NULL;
for (size_t i=0; i < tal_count(scb); i++){
(*scb_tlvs)[i] = convert_from_legacy(cmd, scb[i]);
}
}
return;
}

/* Recovers the channels by making RPC to `recoverchannel` */
static struct command_result *json_emergencyrecover(struct command *cmd,
const char *buf,
Expand All @@ -331,28 +360,14 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
struct out_req *req;
u64 version;
u32 timestamp;
struct legacy_scb_chan **scb;
bool is_converted;
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,
&timestamp,
&scb)) {
if(!fromwire_static_chan_backup_with_tlvs(cmd,
res,
&version,
&timestamp,
&scb_tlvs)) {
plugin_err(cmd->plugin, "Corrupted SCB!");
}
is_tlvs = true;
}
read_static_chan_backup(cmd, res, &version, &timestamp, &scb_tlvs, &is_converted);

if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) {
plugin_err(cmd->plugin,
Expand All @@ -363,26 +378,18 @@ static struct command_result *json_emergencyrecover(struct command *cmd,
after_recover_rpc,
forward_error, NULL);

json_array_start(req->js, "scb");
if (is_tlvs) {
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
towire_modern_scb_chan(&scb_hex,scb_tlvs[i]);
json_add_hex_talarr(req->js, NULL, scb_hex);
}
} else {
if (is_converted) {
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; i<tal_count(scb); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
struct modern_scb_chan *tmp_scb = convert_from_legacy(cmd, scb[i]);
towire_modern_scb_chan(&scb_hex, tmp_scb);
json_add_hex_talarr(req->js, NULL, scb_hex);
}
"Please migrate to the latest file format for improved "
"compatibility and fund recovery.");
}

json_array_start(req->js, "scb");
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
towire_modern_scb_chan(&scb_hex, scb_tlvs[i]);
json_add_hex_talarr(req->js, NULL, scb_hex);
}
json_array_end(req->js);

return send_outreq(req);
Expand Down Expand Up @@ -807,6 +814,48 @@ static struct command_result *datastore_failed(struct command *cmd,
return command_hook_success(cmd);
}

/* Compares the data between the stored scb and latest recvd scb, stores the most
* recent one only. */
static struct command_result *store_latest_scb(struct command *cmd,
const u8 *latest_stored_scb,
u8 *received_scb)
{
size_t stored_scb_len = tal_bytelen(latest_stored_scb);
size_t recvd_scb_len = tal_bytelen(received_scb);
const u8 *recvd_scb_const = received_scb;

if (stored_scb_len == 0)
goto store_data;

// We only need the timestamps, so instead of fully deserializing with `fromwire_static_chan_backup`,
// we manually parse just enough fields to reach the timestamp in each SCB blob.
assert(fromwire_u16(&latest_stored_scb, &stored_scb_len) == WIRE_STATIC_CHAN_BACKUP);
assert(fromwire_u16(&recvd_scb_const, &recvd_scb_len) == WIRE_STATIC_CHAN_BACKUP);

// Fetching version from the SCB.
fromwire_u64(&latest_stored_scb, &stored_scb_len);
fromwire_u64(&recvd_scb_const, &recvd_scb_len);

u32 timestamp = fromwire_u32(&latest_stored_scb, &stored_scb_len);
u32 timestamp_new = fromwire_u32(&recvd_scb_const, &recvd_scb_len);

if (timestamp_new > timestamp)
goto store_data;

plugin_log(cmd->plugin, LOG_DBG, "Ignoring old Peer Storage");
return command_hook_success(cmd);

store_data:
return jsonrpc_set_datastore_binary(cmd,
"chanbackup/latestscb",
received_scb,
"create-or-replace",
datastore_success,
datastore_failed,
"Saving latestscb");

}

static struct command_result *handle_your_peer_storage(struct command *cmd,
const char *buf,
const jsmntok_t *params)
Expand Down Expand Up @@ -895,14 +944,10 @@ static struct command_result *handle_your_peer_storage(struct command *cmd,
return failed_peer_restore(cmd, &node_id,
"Peer altered our data");


return jsonrpc_set_datastore_binary(cmd,
"chanbackup/latestscb",
decoded_bkp,
"create-or-replace",
datastore_success,
datastore_failed,
"Saving latestscb");
return jsonrpc_get_datastore_binary(cmd,
"chanbackup/latestscb",
store_latest_scb,
decoded_bkp);
} else {
/* Any other message we ignore */
return command_hook_success(cmd);
Expand All @@ -915,8 +960,8 @@ static struct command_result *after_latestscb(struct command *cmd,
{
u64 version;
u32 timestamp;
bool is_converted;
struct modern_scb_chan **scb_tlvs;
struct legacy_scb_chan **scb;
struct json_stream *response;
struct out_req *req;

Expand All @@ -928,21 +973,7 @@ static struct command_result *after_latestscb(struct command *cmd,
return command_finished(cmd, response);
}

bool is_tlvs = false;
if (!fromwire_static_chan_backup(cmd,
res,
&version,
&timestamp,
&scb)) {
if(!fromwire_static_chan_backup_with_tlvs(cmd,
res,
&version,
&timestamp,
&scb_tlvs)) {
plugin_err(cmd->plugin, "Corrupted SCB!");
}
is_tlvs = true;
}
read_static_chan_backup(cmd, res, &version, &timestamp, &scb_tlvs, &is_converted);

if ((version & 0x5555555555555555ULL) != (VERSION & 0x5555555555555555ULL)) {
plugin_err(cmd->plugin,
Expand All @@ -954,28 +985,11 @@ static struct command_result *after_latestscb(struct command *cmd,
&forward_error, NULL);

json_array_start(req->js, "scb");
if (is_tlvs) {
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
towire_modern_scb_chan(&scb_hex,scb_tlvs[i]);
json_add_hex_talarr(req->js, NULL, scb_hex);
}
} else {
for (size_t i=0; i<tal_count(scb); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
struct modern_scb_chan *tmp_scb_tlv = tal(cmd, struct modern_scb_chan);
tmp_scb_tlv->id = 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);
}
for (size_t i=0; i<tal_count(scb_tlvs); i++) {
u8 *scb_hex = tal_arr(cmd, u8, 0);
towire_modern_scb_chan(&scb_hex, scb_tlvs[i]);
json_add_hex_talarr(req->js, NULL, scb_hex);
}

json_array_end(req->js);

return send_outreq(req);
Expand Down Expand Up @@ -1009,7 +1023,21 @@ static struct command_result *json_getemergencyrecoverdata(struct command *cmd,
response = jsonrpc_stream_success(cmd);
json_add_hex_talarr(response, "filedata", filedata);


// Add details about the SCB.
const u8 *decrypted_filedata = decrypt_scb(cmd->plugin);
u64 version;
u32 timestamp;
struct modern_scb_chan **scb_tlvs;
bool is_converted;
read_static_chan_backup(cmd, decrypted_filedata, &version, &timestamp, &scb_tlvs, &is_converted);

// If false, update the emergency.recover file immediately!
json_add_bool(response, "can_create_penalty", !is_converted);
json_array_start(response, "backed_up_channel_ids");
for (int i = 0; i < tal_count(scb_tlvs); i++) {
json_add_channel_id(response, NULL, &scb_tlvs[i]->cid);
}
json_array_end(response);
return command_finished(cmd, response);
}

Expand Down
6 changes: 4 additions & 2 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2941,8 +2941,10 @@ def test_getemergencyrecoverdata(node_factory):
Test getemergencyrecoverdata
"""
l1 = node_factory.get_node()
filedata = l1.rpc.getemergencyrecoverdata()['filedata']

rpc = l1.rpc.getemergencyrecoverdata()
filedata = rpc['filedata']
assert rpc['can_create_penalty'] == True
assert len(rpc['backed_up_channel_ids']) == 0
with open(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "emergency.recover"), "rb") as f:
lines = f.read().hex()
assert lines == filedata
Expand Down
Loading