From 5e1e7c71f2fdfc6346e2c06a9656b7c330fddc19 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 2 Feb 2023 14:25:10 +1030 Subject: [PATCH 01/21] lightningd: fix leak report from peer_connected. `their_features` is allocated off the hook_payload. Signed-off-by: Rusty Russell --- lightningd/peer_control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 6a9d19369111..c894799f23d9 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1424,7 +1424,7 @@ void peer_connected(struct lightningd *ld, const u8 *msg) if (peer->remote_addr) tal_free(peer->remote_addr); peer->remote_addr = NULL; - peer_update_features(peer, their_features); + peer_update_features(peer, take(their_features)); tal_steal(peer, hook_payload); hook_payload->peer = peer; From 33d5b43a0da62205861db3fb25ee05332a07ae4f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 3 Feb 2023 20:29:47 +1030 Subject: [PATCH 02/21] lightningd: don't access peer after free if it disconnects during peer_connected hook. We keep the node_id, not a pointer to the peer. This also means that it might have reconnected while we were in the hook, so make sure we ignore the result if it's in state PEER_CONNECTED. And remove the `tal_steal(peer, hook_payload)` which doesn't do anything: the plugin_hook call steals hook_payload anyway! Fixes: #5944 --- lightningd/peer_control.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index c894799f23d9..dd137efe6192 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1071,7 +1071,8 @@ struct peer_connected_hook_payload { struct wireaddr_internal addr; struct wireaddr *remote_addr; bool incoming; - struct peer *peer; + /* We don't keep a pointer to peer: it might be freed! */ + struct node_id peer_id; u8 *error; }; @@ -1079,9 +1080,8 @@ static void peer_connected_serialize(struct peer_connected_hook_payload *payload, struct json_stream *stream, struct plugin *plugin) { - const struct peer *p = payload->peer; json_object_start(stream, "peer"); - json_add_node_id(stream, "id", &p->id); + json_add_node_id(stream, "id", &payload->peer_id); json_add_string(stream, "direction", payload->incoming ? "in" : "out"); json_add_string( stream, "addr", @@ -1090,7 +1090,10 @@ peer_connected_serialize(struct peer_connected_hook_payload *payload, json_add_string( stream, "remote_addr", type_to_string(stream, struct wireaddr, payload->remote_addr)); - json_add_hex_talarr(stream, "features", p->their_features); + /* Since this is start of hook, peer is always in table! */ + json_add_hex_talarr(stream, "features", + peer_by_id(payload->ld, &payload->peer_id) + ->their_features); json_object_end(stream); /* .peer */ } @@ -1186,7 +1189,7 @@ static void peer_connected_hook_final(struct peer_connected_hook_payload *payloa struct lightningd *ld = payload->ld; struct channel *channel; struct wireaddr_internal addr = payload->addr; - struct peer *peer = payload->peer; + struct peer *peer; u8 *error; /* Whatever happens, we free payload (it's currently a child @@ -1194,9 +1197,16 @@ static void peer_connected_hook_final(struct peer_connected_hook_payload *payloa * subd). */ tal_steal(tmpctx, payload); + /* Peer might have gone away while we were waiting for plugin! */ + peer = peer_by_id(ld, &payload->peer_id); + if (!peer) + return; + /* If we disconnected in the meantime, forget about it. - * (disconnect will have failed any connect commands). */ - if (peer->connected == PEER_DISCONNECTED) + * (disconnect will have failed any connect commands). + * And if it has reconnected, and we're the second time the + * hook has been called, it'll be PEER_CONNECTED. */ + if (peer->connected != PEER_CONNECTING) return; /* Check for specific errors of a hook */ @@ -1424,10 +1434,8 @@ void peer_connected(struct lightningd *ld, const u8 *msg) if (peer->remote_addr) tal_free(peer->remote_addr); peer->remote_addr = NULL; - peer_update_features(peer, take(their_features)); - - tal_steal(peer, hook_payload); - hook_payload->peer = peer; + peer_update_features(peer, their_features); + hook_payload->peer_id = id; /* If there's a connect command, use its id as basis for hook id */ cmd_id = connect_any_cmd_id(tmpctx, ld, peer); From 61045b29f77c5de005260771e95b264212d5ca54 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 2 Feb 2023 14:26:10 +1030 Subject: [PATCH 03/21] lightningd: allow sendcustommsg even if plugins are still processing peer_connected. This is needed for the next patch, which does this from the peer_connected hook! Signed-off-by: Rusty Russell Changelog-Changed: JSON-RPC: `sendcustommsg` can now be called by a plugin from within the `peer_connected` hook. --- lightningd/connect_control.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index ca50c7c42d32..1632e0387128 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -779,11 +779,11 @@ static struct command_result *json_sendcustommsg(struct command *cmd, type_to_string(cmd, struct node_id, dest)); } - if (peer->connected != PEER_CONNECTED) + /* We allow messages from plugins responding to peer_connected hook, + * so can be PEER_CONNECTING. */ + if (peer->connected == PEER_DISCONNECTED) return command_fail(cmd, JSONRPC2_INVALID_REQUEST, - "Peer is %s", - peer->connected == PEER_DISCONNECTED - ? "not connected" : "still connecting"); + "Peer is not connected"); subd_send_msg(cmd->ld->connectd, take(towire_connectd_custommsg_out(cmd, dest, msg))); From 7908e5139f0f99e89c5b3d68d026c30ce5579703 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:22 +1030 Subject: [PATCH 04/21] wire: Add patch file for peer storage bkp Add msg type peer_storage and your_peer_storage --- channeld/channeld.c | 2 ++ connectd/gossip_rcvd_filter.c | 2 ++ connectd/gossip_store.c | 2 ++ connectd/multiplex.c | 2 ++ gossipd/gossipd.c | 2 ++ openingd/dualopend.c | 6 ++++++ wire/extracted_peer_07_peer_storage.patch | 15 +++++++++++++++ wire/extracted_peer_exp_upgradable.patch | 6 +++--- wire/peer_wire.c | 6 ++++++ wire/peer_wire.csv | 6 ++++++ 10 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 wire/extracted_peer_07_peer_storage.patch diff --git a/channeld/channeld.c b/channeld/channeld.c index 70f4e970086d..feba13169f9e 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -2300,6 +2300,8 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_WARNING: case WIRE_ERROR: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: abort(); } diff --git a/connectd/gossip_rcvd_filter.c b/connectd/gossip_rcvd_filter.c index b3a73ee24fba..740b52957ffe 100644 --- a/connectd/gossip_rcvd_filter.c +++ b/connectd/gossip_rcvd_filter.c @@ -87,6 +87,8 @@ static bool is_msg_gossip_broadcast(const u8 *cursor) case WIRE_TX_INIT_RBF: case WIRE_TX_ACK_RBF: case WIRE_TX_ABORT: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: case WIRE_OPEN_CHANNEL2: case WIRE_ACCEPT_CHANNEL2: #if EXPERIMENTAL_FEATURES diff --git a/connectd/gossip_store.c b/connectd/gossip_store.c index 9101812c1736..0ebb4f37772a 100644 --- a/connectd/gossip_store.c +++ b/connectd/gossip_store.c @@ -92,6 +92,8 @@ static bool public_msg_type(enum peer_wire type) case WIRE_REPLY_CHANNEL_RANGE: case WIRE_GOSSIP_TIMESTAMP_FILTER: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 5623a4c6b992..3332f1e4ccc1 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -385,6 +385,8 @@ static bool is_urgent(enum peer_wire type) case WIRE_REPLY_CHANNEL_RANGE: case WIRE_GOSSIP_TIMESTAMP_FILTER: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 9d34f91c57db..ab995192190d 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -569,6 +569,8 @@ static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) case WIRE_OPEN_CHANNEL2: case WIRE_ACCEPT_CHANNEL2: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 4b2956f97508..e8f68ce1ed49 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -1443,6 +1443,8 @@ static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state) case WIRE_WARNING: case WIRE_PING: case WIRE_PONG: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif @@ -1817,6 +1819,8 @@ static bool run_tx_interactive(struct state *state, case WIRE_REPLY_SHORT_CHANNEL_IDS_END: case WIRE_PING: case WIRE_PONG: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif @@ -4166,6 +4170,8 @@ static u8 *handle_peer_in(struct state *state) case WIRE_WARNING: case WIRE_PING: case WIRE_PONG: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif diff --git a/wire/extracted_peer_07_peer_storage.patch b/wire/extracted_peer_07_peer_storage.patch new file mode 100644 index 000000000000..71afebd85cd0 --- /dev/null +++ b/wire/extracted_peer_07_peer_storage.patch @@ -0,0 +1,15 @@ +--- wire/peer_wire.csv 2022-07-18 13:49:29.079542016 +0530 ++++ - 2022-07-18 13:58:17.706696582 +0530 +@@ -249,6 +249,12 @@ + msgdata,channel_reestablish,next_revocation_number,u64, + msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32 + msgdata,channel_reestablish,my_current_per_commitment_point,point, ++msgtype,peer_storage,7 ++msgdata,peer_storage,len,u16, ++msgdata,peer_storage,blob,byte,len ++msgtype,your_peer_storage,9 ++msgdata,your_peer_storage,len,u16, ++msgdata,your_peer_storage,blob,byte,len + msgtype,announcement_signatures,259 + msgdata,announcement_signatures,channel_id,channel_id, + msgdata,announcement_signatures,short_channel_id,short_channel_id, diff --git a/wire/extracted_peer_exp_upgradable.patch b/wire/extracted_peer_exp_upgradable.patch index e5818692ffe4..c168586abeca 100644 --- a/wire/extracted_peer_exp_upgradable.patch +++ b/wire/extracted_peer_exp_upgradable.patch @@ -13,6 +13,6 @@ +tlvdata,channel_reestablish_tlvs,current_channel_type,type,byte,... +tlvtype,channel_reestablish_tlvs,upgradable_channel_type,7 +tlvdata,channel_reestablish_tlvs,upgradable_channel_type,type,byte,... - msgtype,announcement_signatures,259 - msgdata,announcement_signatures,channel_id,channel_id, - msgdata,announcement_signatures,short_channel_id,short_channel_id, + msgtype,peer_storage,7 + msgdata,peer_storage,len,u16, + msgdata,peer_storage,blob,byte,len diff --git a/wire/peer_wire.c b/wire/peer_wire.c index 29e67f386db3..aa6f554933d7 100644 --- a/wire/peer_wire.c +++ b/wire/peer_wire.c @@ -45,6 +45,8 @@ static bool unknown_type(enum peer_wire t) case WIRE_TX_INIT_RBF: case WIRE_TX_ACK_RBF: case WIRE_TX_ABORT: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: case WIRE_OPEN_CHANNEL2: case WIRE_ACCEPT_CHANNEL2: #if EXPERIMENTAL_FEATURES @@ -101,6 +103,8 @@ bool is_msg_for_gossipd(const u8 *cursor) case WIRE_OPEN_CHANNEL2: case WIRE_ACCEPT_CHANNEL2: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: #if EXPERIMENTAL_FEATURES case WIRE_STFU: #endif @@ -140,6 +144,8 @@ bool extract_channel_id(const u8 *in_pkt, struct channel_id *channel_id) case WIRE_REPLY_CHANNEL_RANGE: case WIRE_GOSSIP_TIMESTAMP_FILTER: case WIRE_ONION_MESSAGE: + case WIRE_PEER_STORAGE: + case WIRE_YOUR_PEER_STORAGE: return false; /* Special cases: */ diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv index 398a7b4352bd..f76088cfb74a 100644 --- a/wire/peer_wire.csv +++ b/wire/peer_wire.csv @@ -262,6 +262,12 @@ msgdata,channel_reestablish,next_commitment_number,u64, msgdata,channel_reestablish,next_revocation_number,u64, msgdata,channel_reestablish,your_last_per_commitment_secret,byte,32 msgdata,channel_reestablish,my_current_per_commitment_point,point, +msgtype,peer_storage,7 +msgdata,peer_storage,len,u16, +msgdata,peer_storage,blob,byte,len +msgtype,your_peer_storage,9 +msgdata,your_peer_storage,len,u16, +msgdata,your_peer_storage,blob,byte,len msgtype,announcement_signatures,259 msgdata,announcement_signatures,channel_id,channel_id, msgdata,announcement_signatures,short_channel_id,short_channel_id, From 156bdc42a09cd851d4f0e8e26983bd8cff2b06ec Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:24 +1030 Subject: [PATCH 05/21] feature(PEER_STORAGE and YOUR_PEER_STORAGE) added in feature.c and internal message. --- common/features.c | 10 ++++++++-- common/features.h | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/common/features.c b/common/features.c index 203c0d2f39b8..8177b7146195 100644 --- a/common/features.c +++ b/common/features.c @@ -136,6 +136,12 @@ static const struct feature_style feature_styles[] = { [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [BOLT11_FEATURE] = FEATURE_DONT_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, + { PEER_STORAGE_FEATURE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL } }, + { YOUR_PEER_STORAGE_FEATURE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL } }, }; struct dependency { @@ -456,8 +462,8 @@ const char *feature_name(const tal_t *ctx, size_t f) "option_quiesce", /* https://github.com/lightning/bolts/pull/869 */ NULL, "option_onion_messages", /* https://github.com/lightning/bolts/pull/759 */ - "option_want_peer_backup", /* 40/41 */ /* https://github.com/lightning/bolts/pull/881 */ - "option_provide_peer_backup", /* https://github.com/lightning/bolts/pull/881 */ + "option_your_peer_storage", /* 40/41 */ + "option_peer_storage", "option_channel_type", "option_scid_alias", /* https://github.com/lightning/bolts/pull/910 */ "option_payment_metadata", diff --git a/common/features.h b/common/features.h index a862bafe1c53..efb3567f56e5 100644 --- a/common/features.h +++ b/common/features.h @@ -15,7 +15,8 @@ enum feature_place { BOLT12_INVOICE_FEATURE, }; #define NUM_FEATURE_PLACE (BOLT12_INVOICE_FEATURE+1) - +#define PEER_STORAGE_FEATURE 42 +#define YOUR_PEER_STORAGE_FEATURE 40 extern const char *feature_place_names[NUM_FEATURE_PLACE]; /* The complete set of features for all contexts */ From 3126ccab74511698c5967175f6f6747845f936bc Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:24 +1030 Subject: [PATCH 06/21] peer_wire_is_internal helper. We are now going to have messages which we know about, but yet we don't handle ourselves. [ I reversed this from Adi's, as that was clearer! --RR ] --- wire/peer_wire.c | 14 ++++++++++++++ wire/peer_wire.h | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/wire/peer_wire.c b/wire/peer_wire.c index aa6f554933d7..1d3772c8fd65 100644 --- a/wire/peer_wire.c +++ b/wire/peer_wire.c @@ -120,6 +120,20 @@ bool is_unknown_msg_discardable(const u8 *cursor) return unknown_type(t) && (t & 1); } +/* Returns true if the message type should be handled by CLN's core */ +bool peer_wire_is_internal(enum peer_wire type) +{ + /* Unknown messages are not handled by CLN */ + if (!peer_wire_is_defined(type)) + return false; + + /* handled by pluigns */ + if (type == WIRE_PEER_STORAGE || type == WIRE_YOUR_PEER_STORAGE) + return false; + + return true; +} + /* Extract channel_id from various packets, return true if possible. */ bool extract_channel_id(const u8 *in_pkt, struct channel_id *channel_id) { diff --git a/wire/peer_wire.h b/wire/peer_wire.h index 12c951b8ff3e..f92a9bce968e 100644 --- a/wire/peer_wire.h +++ b/wire/peer_wire.h @@ -23,7 +23,8 @@ bool is_unknown_msg_discardable(const u8 *cursor); /* Return true if it's a message for gossipd. */ bool is_msg_for_gossipd(const u8 *cursor); - +/* Returns true if the message type should be treated as a custommsg */ +bool peer_wire_is_internal(enum peer_wire type); /* Extract channel_id from various packets, return true if possible. */ bool extract_channel_id(const u8 *in_pkt, struct channel_id *channel_id); From 234c0a3dfe9346e062aef67fbedcce6b330414e9 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:24 +1030 Subject: [PATCH 07/21] connectd: make exception for peer storage msgs. --- connectd/multiplex.c | 2 +- lightningd/connect_control.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 3332f1e4ccc1..72db922e9931 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -723,7 +723,7 @@ static bool handle_custommsg(struct daemon *daemon, const u8 *msg) { enum peer_wire type = fromwire_peektype(msg); - if (type % 2 == 1 && !peer_wire_is_defined(type)) { + if (type % 2 == 1 && !peer_wire_is_internal(type)) { /* The message is not part of the messages we know how to * handle. Assuming this is a custommsg, we just forward it to the * master. */ diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index 1632e0387128..93c617a0cc6b 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -752,7 +752,9 @@ static struct command_result *json_sendcustommsg(struct command *cmd, return command_param_failed(); type = fromwire_peektype(msg); - if (peer_wire_is_defined(type)) { + + /* Allow peer_storage and your_peer_storage msgtypes */ + if (peer_wire_is_internal(type)) { return command_fail( cmd, JSONRPC2_INVALID_REQUEST, "Cannot send messages of type %d (%s). It is not possible " From 228f547e63df10726eac02b606cec0baf643d302 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:24 +1030 Subject: [PATCH 08/21] plugins/chanbackup: PLUGIN_RESTARTABLE to PLUGIN_STATIC... --- plugins/chanbackup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 606a96ab7cd0..e0d49d2d6497 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -410,7 +410,7 @@ static const struct plugin_command commands[] = { { int main(int argc, char *argv[]) { setup_locale(); - plugin_main(argv, init, PLUGIN_RESTARTABLE, true, NULL, + plugin_main(argv, init, PLUGIN_STATIC, true, NULL, commands, ARRAY_SIZE(commands), notifs, ARRAY_SIZE(notifs), NULL, 0, NULL, 0, /* Notification topics we publish */ From cb5309cd4fa25cd7043f4f9987bf8ea33d734e24 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 09/21] Plugins/chanbackup: Add featurebit Peerstrg and YourPeerStrg. --- plugins/chanbackup.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index e0d49d2d6497..b80859808026 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -409,7 +410,12 @@ static const struct plugin_command commands[] = { { int main(int argc, char *argv[]) { - setup_locale(); + setup_locale(); + struct feature_set *features = feature_set_for_feature(NULL, PEER_STORAGE_FEATURE); + feature_set_or(features, + take(feature_set_for_feature(NULL, + YOUR_PEER_STORAGE_FEATURE))); + plugin_main(argv, init, PLUGIN_STATIC, true, NULL, commands, ARRAY_SIZE(commands), notifs, ARRAY_SIZE(notifs), NULL, 0, From 3a391d6fa072c62f1d29580ef283f47af1eeff2f Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 10/21] plugins/chanbackup: Define FILENAME globally (Good Manners) --- plugins/chanbackup.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index b80859808026..7f907e0c79fe 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -21,6 +21,8 @@ #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) @@ -111,7 +113,7 @@ static void maybe_create_new_scb(struct plugin *p, /* Note that this is opened for write-only, even though the permissions * are set to read-only. That's perfectly valid! */ - int fd = open("emergency.recover", O_CREAT|O_EXCL|O_WRONLY, 0400); + 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) @@ -120,7 +122,7 @@ static void maybe_create_new_scb(struct plugin *p, } /* Comes here only if the file haven't existed before */ - unlink_noerr("emergency.recover"); + unlink_noerr(FILENAME); /* This couldn't give EEXIST because we call unlink_noerr("scb.tmp") * in INIT */ @@ -161,7 +163,7 @@ static void maybe_create_new_scb(struct plugin *p, close(fd); /* This will update the scb file */ - rename("scb.tmp", "emergency.recover"); + rename("scb.tmp", FILENAME); } @@ -169,9 +171,9 @@ static void maybe_create_new_scb(struct plugin *p, static u8 *decrypt_scb(struct plugin *p) { struct stat st; - int fd = open("emergency.recover", O_RDONLY); + int fd = open(FILENAME, O_RDONLY); - if (stat("emergency.recover", &st) != 0) + if (stat(FILENAME, &st) != 0) plugin_err(p, "SCB file is corrupted!: %s", strerror(errno)); @@ -315,7 +317,7 @@ static void update_scb(struct plugin *p, struct scb_chan **channels) close(fd); /* This will atomically replace the main file */ - rename("scb.tmp", "emergency.recover"); + rename("scb.tmp", FILENAME); } static struct command_result *after_staticbackup(struct command *cmd, From 65496fd2c92afa772bbcf7d4d3149fa2bed0fe6a Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 11/21] plugins/chanbackup: use grab_file. --- plugins/chanbackup.c | 45 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 7f907e0c79fe..67f7ba150bdf 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -166,57 +167,51 @@ static void maybe_create_new_scb(struct plugin *p, rename("scb.tmp", FILENAME); } +static u8* get_file_data(struct plugin *p) +{ + u8 *scb = grab_file(tmpctx, "emergency.recover"); + if (!scb) { + plugin_err(p, "Cannot read emergency.recover: %s", strerror(errno)); + } else { + /* grab_file adds nul term */ + tal_resize(&scb, tal_bytelen(scb) - 1); + } + return scb; +} /* Returns decrypted SCB in form of a u8 array */ static u8 *decrypt_scb(struct plugin *p) { - struct stat st; - int fd = open(FILENAME, O_RDONLY); - - if (stat(FILENAME, &st) != 0) - plugin_err(p, "SCB file is corrupted!: %s", - strerror(errno)); - - u8 final[st.st_size]; - - if (!read_all(fd, &final, st.st_size)) { - plugin_log(p, LOG_DBG, "SCB file is corrupted!: %s", - strerror(errno)); - return NULL; - } + u8 *filedata = get_file_data(p); crypto_secretstream_xchacha20poly1305_state crypto_state; - if (st.st_size < ABYTES + + if (tal_bytelen(filedata) < ABYTES + HEADER_LEN) plugin_err(p, "SCB file is corrupted!"); - u8 *ans = tal_arr(tmpctx, u8, st.st_size - + u8 *decrypt_scb = tal_arr(tmpctx, u8, tal_bytelen(filedata) - ABYTES - HEADER_LEN); /* The header part */ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, - final, + filedata, (&secret)->data) != 0) { plugin_err(p, "SCB file is corrupted!"); } - if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, ans, + if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, decrypt_scb, NULL, 0, - final + + filedata + HEADER_LEN, - st.st_size - + tal_bytelen(filedata)- HEADER_LEN, NULL, 0) != 0) { plugin_err(p, "SCB file is corrupted!"); } - - if (close(fd) != 0) - plugin_err(p, "Closing: %s", strerror(errno)); - - return ans; + return decrypt_scb; } static struct command_result *after_recover_rpc(struct command *cmd, From 53b427c584f81f078a01c1f21b2aad7de63ab73d Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 12/21] Plugins/chanbackup: Add SCB on CHANNELD_AWAITING_LOCKING stage --- plugins/chanbackup.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 67f7ba150bdf..4bb51a603f48 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -338,14 +338,9 @@ static struct command_result *json_state_changed(struct command *cmd, "channel_state_changed"), *statetok = json_get_member(buf, notiftok, "new_state"); - /* FIXME: I wanted to update the file on CHANNELD_AWAITING_LOCKIN, - * But I don't get update for it, maybe because there is - * no previous_state, also apparently `channel_opened` gets published - * when *peer* funded a channel with us? - * So, is their no way to get a notif on CHANNELD_AWAITING_LOCKIN? */ if (json_tok_streq(buf, statetok, "CLOSED") || - json_tok_streq(buf, statetok, "CHANNELD_NORMAL")) { - + json_tok_streq(buf, statetok, "CHANNELD_AWAITING_LOCKIN") || + json_tok_streq(buf, statetok, "DUALOPENED_AWAITING_LOCKIN")) { struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, From 6f8a8006fba592add77f37153a87adcb794b8b9b Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 13/21] Plugins/chanbackup: Add hook for receiving custommsg --- plugins/chanbackup.c | 284 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 279 insertions(+), 5 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 4bb51a603f48..f892db31c61b 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -257,7 +258,7 @@ static struct command_result *json_emergencyrecover(struct command *cmd, if (version != VERSION) { plugin_err(cmd->plugin, - "Incompatible version, Contact the admin!"); + "Incompatible SCB file version on disk, contact the admin!"); } req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", @@ -315,6 +316,81 @@ static void update_scb(struct plugin *p, struct scb_chan **channels) rename("scb.tmp", FILENAME); } +struct info { + size_t idx; +}; + +static struct command_result *after_send_scb_single(struct command *cmd, + const char *buf, + const jsmntok_t *params, + struct info *info) +{ + plugin_log(cmd->plugin, LOG_INFORM, "Peer storage sent!"); + if (--info->idx != 0) + return command_still_pending(cmd); + + return notification_handled(cmd); +} + +static struct command_result *after_send_scb_single_fail(struct command *cmd, + const char *buf, + const jsmntok_t *params, + struct info *info) +{ + plugin_log(cmd->plugin, LOG_DBG, "Peer storage send failed!"); + if (--info->idx != 0) + return command_still_pending(cmd); + + return notification_handled(cmd); +} + +static struct command_result *after_listpeers(struct command *cmd, + const char *buf, + const jsmntok_t *params, + void *cb_arg UNUSED) +{ + const jsmntok_t *peers, *peer, *nodeid; + struct out_req *req; + size_t i; + struct info *info = tal(cmd, struct info); + struct node_id *node_id = tal(cmd, struct node_id); + bool is_connected; + + u8 *scb = get_file_data(cmd->plugin); + + u8 *serialise_scb = towire_peer_storage(cmd, scb); + + peers = json_get_member(buf, params, "peers"); + + info->idx = 0; + json_for_each_arr(i, peer, peers) { + json_to_bool(buf, json_get_member(buf, peer, "connected"), + &is_connected); + + if (is_connected) { + nodeid = json_get_member(buf, peer, "id"); + json_to_node_id(buf, nodeid, node_id); + + req = jsonrpc_request_start(cmd->plugin, + cmd, + "sendcustommsg", + after_send_scb_single, + after_send_scb_single_fail, + info); + + json_add_node_id(req->js, "node_id", node_id); + json_add_hex(req->js, "msg", serialise_scb, + tal_bytelen(serialise_scb)); + info->idx++; + send_outreq(cmd->plugin, req); + } + } + + if (info->idx == 0) + return notification_handled(cmd); + return command_still_pending(cmd); +} + static struct command_result *after_staticbackup(struct command *cmd, const char *buf, const jsmntok_t *params, @@ -322,11 +398,20 @@ static struct command_result *after_staticbackup(struct command *cmd, { struct scb_chan **scb_chan; const jsmntok_t *scbs = json_get_member(buf, params, "scb"); + struct out_req *req; json_to_scb_chan(buf, scbs, &scb_chan); plugin_log(cmd->plugin, LOG_INFORM, "Updating the SCB"); update_scb(cmd->plugin, scb_chan); - return notification_handled(cmd); + struct info *info = tal(cmd, struct info); + info->idx = 0; + req = jsonrpc_request_start(cmd->plugin, + cmd, + "listpeers", + after_listpeers, + &forward_error, + info); + return send_outreq(cmd->plugin, req); } static struct command_result *json_state_changed(struct command *cmd, @@ -355,6 +440,179 @@ static struct command_result *json_state_changed(struct command *cmd, return notification_handled(cmd); } +static struct command_result *failed_peer_restore(struct command *cmd, + struct node_id *node_id, + char *reason) +{ + plugin_log(cmd->plugin, LOG_DBG, "PeerStorageFailed!: %s: %s", + type_to_string(tmpctx, struct node_id, node_id), + reason); + return command_hook_success(cmd); +} + +static struct command_result *datastore_success(struct command *cmd, + 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 *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 = 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)) { + return jsonrpc_set_datastore_binary(cmd->plugin, + cmd, + tal_fmt(cmd, + "chanbackup/peers/%s", + type_to_string(tmpctx, + struct node_id, + &node_id)), + payload_deserialise, + "create-or-replace", + datastore_success, + datastore_failed, + "Saving chanbackup/peers/"); + } else if (fromwire_your_peer_storage(cmd, payload, &payload_deserialise)) { + plugin_log(cmd->plugin, LOG_DBG, + "Received peer_storage from peer."); + + crypto_secretstream_xchacha20poly1305_state crypto_state; + + 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, + (&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->plugin, + cmd, + "chanbackup/latestscb", + decoded_bkp, + "create-or-replace", + datastore_success, + datastore_failed, + "Saving latestscb"); + } else { + plugin_log(cmd->plugin, LOG_DBG, + "Peer sent bad custom message for chanbackup!"); + 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 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); + } + + if (!fromwire_static_chan_backup(cmd, + res, + &version, + ×tamp, + &scb)) { + plugin_err(cmd->plugin, "Corrupted SCB on disk!"); + } + + if (version != VERSION) { + plugin_err(cmd->plugin, + "Incompatible version, Contact the admin!"); + } + + req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", + after_recover_rpc, + &forward_error, NULL); + + json_array_start(req->js, "scb"); + for (size_t i=0; ijs, NULL, scb_hex, tal_bytelen(scb_hex)); + } + json_array_end(req->js); + + return send_outreq(cmd->plugin, 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->plugin, + cmd, + "chanbackup/latestscb", + after_latestscb, + NULL); +} + static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) @@ -391,13 +649,29 @@ static const struct plugin_notification notifs[] = { } }; +static const struct plugin_hook hooks[] = { + { + "custommsg", + handle_your_peer_storage, + }, +}; + static const struct plugin_command commands[] = { { "emergencyrecover", "recovery", "Populates the DB with stub channels", "returns stub channel-id's on completion", json_emergencyrecover, - } + }, + { + "restorefrompeer", + "recovery", + "Checks if i have got a backup from a peer, and if so, will stub " + "those channels in the database and if is successful, will return " + "list of channels that have been successfully stubbed", + "return channel-id's on completion", + json_restorefrompeer, + }, }; int main(int argc, char *argv[]) @@ -408,9 +682,9 @@ int main(int argc, char *argv[]) take(feature_set_for_feature(NULL, YOUR_PEER_STORAGE_FEATURE))); - plugin_main(argv, init, PLUGIN_STATIC, true, NULL, + plugin_main(argv, init, PLUGIN_STATIC, true, features, commands, ARRAY_SIZE(commands), - notifs, ARRAY_SIZE(notifs), NULL, 0, + notifs, ARRAY_SIZE(notifs), hooks, ARRAY_SIZE(hooks), NULL, 0, /* Notification topics we publish */ NULL); } From 837e9e1820334e300a0fff8c7617a893c5a4b789 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 14/21] Plugins/chanbackup: Add hook for exchanging msgs on connect with a peer --- plugins/chanbackup.c | 189 +++++++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 71 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index f892db31c61b..bd861a2b0844 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -258,7 +258,7 @@ static struct command_result *json_emergencyrecover(struct command *cmd, if (version != VERSION) { plugin_err(cmd->plugin, - "Incompatible SCB file version on disk, contact the admin!"); + "Incompatible version, Contact the admin!"); } req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", @@ -316,6 +316,82 @@ static void update_scb(struct plugin *p, struct scb_chan **channels) rename("scb.tmp", FILENAME); } + +static struct command_result +*peer_after_send_their_peer_strg(struct command *cmd, + const char *buf, + const jsmntok_t *params, + void *cb_arg UNUSED) +{ + 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 *buf, + const jsmntok_t *params, + void *cb_arg UNUSED) +{ + plugin_log(cmd->plugin, LOG_DBG, "Unable to send Peer storage!"); + return command_hook_success(cmd); +} + +static struct command_result *peer_after_listdatastore(struct command *cmd, + const u8 *hexdata, + struct node_id *nodeid) +{ + if (tal_bytelen(hexdata) == 0) + return command_hook_success(cmd); + struct out_req *req; + + u8 *payload = towire_your_peer_storage(cmd, hexdata); + + plugin_log(cmd->plugin, LOG_DBG, + "sending their backup from our datastore"); + + req = jsonrpc_request_start(cmd->plugin, + cmd, + "sendcustommsg", + peer_after_send_their_peer_strg, + peer_after_send_their_peer_strg_err, + NULL); + + json_add_node_id(req->js, "node_id", nodeid); + json_add_hex(req->js, "msg", payload, + tal_bytelen(payload)); + + return send_outreq(cmd->plugin, req); +} + +static struct command_result *peer_after_send_scb(struct command *cmd, + const char *buf, + const jsmntok_t *params, + struct node_id *nodeid) +{ + plugin_log(cmd->plugin, LOG_DBG, "Peer storage sent!"); + + return jsonrpc_get_datastore_binary(cmd->plugin, + cmd, + tal_fmt(cmd, + "chanbackup/peers/%s", + type_to_string(tmpctx, + struct node_id, + nodeid)), + peer_after_listdatastore, + nodeid); +} + +static struct command_result *peer_after_send_scb_failed(struct command *cmd, + 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)); + return command_hook_success(cmd); +} + struct info { size_t idx; }; @@ -440,6 +516,43 @@ static struct command_result *json_state_changed(struct command *cmd, return notification_handled(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 = tal(cmd, struct node_id); + struct out_req *req; + u8 *scb = get_file_data(cmd->plugin); + u8 *serialise_scb = towire_peer_storage(cmd, scb); + const char *err; + + err = json_scan(cmd, buf, params, + "{peer:{id:%}}", + JSON_SCAN(json_to_node_id, node_id)); + 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)); + } + + req = jsonrpc_request_start(cmd->plugin, + 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(req->js, "msg", serialise_scb, + tal_bytelen(serialise_scb)); + + return send_outreq(cmd->plugin, req); +} + static struct command_result *failed_peer_restore(struct command *cmd, struct node_id *node_id, char *reason) @@ -552,67 +665,6 @@ static struct command_result *handle_your_peer_storage(struct command *cmd, } } -static struct command_result *after_latestscb(struct command *cmd, - const u8 *res, - void *cb_arg UNUSED) -{ - u64 version; - u32 timestamp; - struct 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); - } - - if (!fromwire_static_chan_backup(cmd, - res, - &version, - ×tamp, - &scb)) { - plugin_err(cmd->plugin, "Corrupted SCB on disk!"); - } - - if (version != VERSION) { - plugin_err(cmd->plugin, - "Incompatible version, Contact the admin!"); - } - - req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", - after_recover_rpc, - &forward_error, NULL); - - json_array_start(req->js, "scb"); - for (size_t i=0; ijs, NULL, scb_hex, tal_bytelen(scb_hex)); - } - json_array_end(req->js); - - return send_outreq(cmd->plugin, 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->plugin, - cmd, - "chanbackup/latestscb", - after_latestscb, - NULL); -} - static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) @@ -654,6 +706,10 @@ static const struct plugin_hook hooks[] = { "custommsg", handle_your_peer_storage, }, + { + "peer_connected", + peer_connected, + }, }; static const struct plugin_command commands[] = { { @@ -663,15 +719,6 @@ static const struct plugin_command commands[] = { { "returns stub channel-id's on completion", json_emergencyrecover, }, - { - "restorefrompeer", - "recovery", - "Checks if i have got a backup from a peer, and if so, will stub " - "those channels in the database and if is successful, will return " - "list of channels that have been successfully stubbed", - "return channel-id's on completion", - json_restorefrompeer, - }, }; int main(int argc, char *argv[]) From 8140b4a6ea2364211b36d24df4386b6b661a049d Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 15/21] Plugins/chanbackup: Add RPC for recovering from the latestscb received from peers. --- plugins/chanbackup.c | 72 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index bd861a2b0844..ca480433e60d 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -258,7 +258,7 @@ static struct command_result *json_emergencyrecover(struct command *cmd, if (version != VERSION) { plugin_err(cmd->plugin, - "Incompatible version, Contact the admin!"); + "Incompatible SCB file version on disk, contact the admin!"); } req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", @@ -665,6 +665,67 @@ static struct command_result *handle_your_peer_storage(struct command *cmd, } } +static struct command_result *after_latestscb(struct command *cmd, + const u8 *res, + void *cb_arg UNUSED) +{ + u64 version; + u32 timestamp; + struct 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); + } + + if (!fromwire_static_chan_backup(cmd, + res, + &version, + ×tamp, + &scb)) { + plugin_err(cmd->plugin, "Corrupted SCB on disk!"); + } + + if (version != VERSION) { + plugin_err(cmd->plugin, + "Incompatible version, Contact the admin!"); + } + + req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", + after_recover_rpc, + &forward_error, NULL); + + json_array_start(req->js, "scb"); + for (size_t i=0; ijs, NULL, scb_hex, tal_bytelen(scb_hex)); + } + json_array_end(req->js); + + return send_outreq(cmd->plugin, 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->plugin, + cmd, + "chanbackup/latestscb", + after_latestscb, + NULL); +} + static const char *init(struct plugin *p, const char *buf UNUSED, const jsmntok_t *config UNUSED) @@ -719,6 +780,15 @@ static const struct plugin_command commands[] = { { "returns stub channel-id's on completion", json_emergencyrecover, }, + { + "restorefrompeer", + "recovery", + "Checks if i have got a backup from a peer, and if so, will stub " + "those channels in the database and if is successful, will return " + "list of channels that have been successfully stubbed", + "return channel-id's on completion", + json_restorefrompeer, + }, }; int main(int argc, char *argv[]) From 3be74fdd3fe8ee8e60fe582d19bf5b16ba287426 Mon Sep 17 00:00:00 2001 From: adi2011 Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 16/21] tests/test_misc.py: Add test_restorefrompeer. --- tests/test_misc.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_misc.py b/tests/test_misc.py index 10532241a4aa..1c826fe832e2 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2505,6 +2505,45 @@ def test_emergencyrecover(node_factory, bitcoind): assert l2.rpc.listfunds()["channels"][0]["state"] == "ONCHAIN" +@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "deletes database, which is assumed sqlite3") +def test_restorefrompeer(node_factory, bitcoind): + """ + Test restorefrompeer + """ + l1, l2 = node_factory.get_nodes(2, opts=[{'allow_broken_log': True, 'may_reconnect': True}, + {'may_reconnect': True}]) + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + + c12, _ = l1.fundchannel(l2, 10**5) + assert l1.daemon.is_in_log('Peer storage sent!') + assert l2.daemon.is_in_log('Peer storage sent!') + + l1.stop() + os.unlink(os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "lightningd.sqlite3")) + + l1.start() + assert l1.daemon.is_in_log('Server started with public key') + + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + l1.daemon.wait_for_log('peer_in WIRE_YOUR_PEER_STORAGE') + + assert l1.rpc.restorefrompeer()['stubs'][0] == _['channel_id'] + + l1.daemon.wait_for_log('peer_out WIRE_ERROR') + l2.daemon.wait_for_log('State changed from CHANNELD_NORMAL to AWAITING_UNILATERAL') + + bitcoind.generate_block(5, wait_for_mempool=1) + sync_blockheight(bitcoind, [l1, l2]) + + l1.daemon.wait_for_log(r'All outputs resolved.*') + wait_for(lambda: l1.rpc.listfunds()["channels"][0]["state"] == "ONCHAIN") + + # Check if funds are recovered. + assert l1.rpc.listfunds()["channels"][0]["state"] == "ONCHAIN" + assert l2.rpc.listfunds()["channels"][0]["state"] == "ONCHAIN" + + def test_commitfee_option(node_factory): """Sanity check for the --commit-fee startup option.""" l1, l2 = node_factory.get_nodes(2, opts=[{"commit-fee": "200"}, {}]) From b372392c8ac950dd023233c006bf30af14e36769 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 2 Feb 2023 20:31:25 +1030 Subject: [PATCH 17/21] plugins/chanbackup: switch to normal indentation. Signed-off-by: Rusty Russell --- plugins/chanbackup.c | 147 ++++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 73 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index ca480433e60d..e94a897aeffc 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -33,8 +33,8 @@ static struct secret secret; /* Helper to fetch out SCB from the RPC call */ static bool json_to_scb_chan(const char *buffer, - const jsmntok_t *tok, - struct scb_chan ***channels) + const jsmntok_t *tok, + struct scb_chan ***channels) { size_t i; const jsmntok_t *t; @@ -74,10 +74,10 @@ static void write_scb(struct plugin *p, scb_chan_arr)); u8 *encrypted_scb = tal_arr(tmpctx, - u8, - tal_bytelen(decrypted_scb) + - ABYTES + - HEADER_LEN); + u8, + tal_bytelen(decrypted_scb) + + ABYTES + + HEADER_LEN); crypto_secretstream_xchacha20poly1305_state crypto_state; @@ -90,20 +90,20 @@ static void write_scb(struct plugin *p, } 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)) { + 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)); + unlink_noerr("scb.tmp"); + plugin_err(p, "Writing encrypted SCB: %s", + strerror(errno)); } } @@ -188,12 +188,12 @@ static u8 *decrypt_scb(struct plugin *p) crypto_secretstream_xchacha20poly1305_state crypto_state; if (tal_bytelen(filedata) < ABYTES + - HEADER_LEN) + HEADER_LEN) plugin_err(p, "SCB file is corrupted!"); u8 *decrypt_scb = tal_arr(tmpctx, u8, tal_bytelen(filedata) - - ABYTES - - HEADER_LEN); + ABYTES - + HEADER_LEN); /* The header part */ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, @@ -235,8 +235,8 @@ static struct command_result *after_recover_rpc(struct command *cmd, /* Recovers the channels by making RPC to `recoverchannel` */ static struct command_result *json_emergencyrecover(struct command *cmd, - const char *buf, - const jsmntok_t *params) + const char *buf, + const jsmntok_t *params) { struct out_req *req; u64 version; @@ -262,8 +262,8 @@ static struct command_result *json_emergencyrecover(struct command *cmd, } req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", - after_recover_rpc, - &forward_error, NULL); + after_recover_rpc, + &forward_error, NULL); json_array_start(req->js, "scb"); for (size_t i=0; iplugin, LOG_DBG, "Sent their peer storage!"); return command_hook_success(cmd); @@ -329,9 +329,9 @@ static struct command_result static struct command_result *peer_after_send_their_peer_strg_err(struct command *cmd, - const char *buf, - const jsmntok_t *params, - void *cb_arg UNUSED) + const char *buf, + const jsmntok_t *params, + void *cb_arg UNUSED) { plugin_log(cmd->plugin, LOG_DBG, "Unable to send Peer storage!"); return command_hook_success(cmd); @@ -375,9 +375,9 @@ static struct command_result *peer_after_send_scb(struct command *cmd, cmd, tal_fmt(cmd, "chanbackup/peers/%s", - type_to_string(tmpctx, - struct node_id, - nodeid)), + type_to_string(tmpctx, + struct node_id, + nodeid)), peer_after_listdatastore, nodeid); } @@ -397,9 +397,9 @@ struct info { }; static struct command_result *after_send_scb_single(struct command *cmd, - const char *buf, - const jsmntok_t *params, - struct info *info) + const char *buf, + const jsmntok_t *params, + struct info *info) { plugin_log(cmd->plugin, LOG_INFORM, "Peer storage sent!"); if (--info->idx != 0) @@ -409,9 +409,9 @@ static struct command_result *after_send_scb_single(struct command *cmd, } static struct command_result *after_send_scb_single_fail(struct command *cmd, - const char *buf, - const jsmntok_t *params, - struct info *info) + const char *buf, + const jsmntok_t *params, + struct info *info) { plugin_log(cmd->plugin, LOG_DBG, "Peer storage send failed!"); if (--info->idx != 0) @@ -421,9 +421,9 @@ static struct command_result *after_send_scb_single_fail(struct command *cmd, } static struct command_result *after_listpeers(struct command *cmd, - const char *buf, - const jsmntok_t *params, - void *cb_arg UNUSED) + const char *buf, + const jsmntok_t *params, + void *cb_arg UNUSED) { const jsmntok_t *peers, *peer, *nodeid; struct out_req *req; @@ -441,22 +441,22 @@ static struct command_result *after_listpeers(struct command *cmd, info->idx = 0; json_for_each_arr(i, peer, peers) { json_to_bool(buf, json_get_member(buf, peer, "connected"), - &is_connected); + &is_connected); if (is_connected) { nodeid = json_get_member(buf, peer, "id"); json_to_node_id(buf, nodeid, node_id); req = jsonrpc_request_start(cmd->plugin, - cmd, - "sendcustommsg", - after_send_scb_single, - after_send_scb_single_fail, - info); + cmd, + "sendcustommsg", + after_send_scb_single, + after_send_scb_single_fail, + info); json_add_node_id(req->js, "node_id", node_id); json_add_hex(req->js, "msg", serialise_scb, - tal_bytelen(serialise_scb)); + tal_bytelen(serialise_scb)); info->idx++; send_outreq(cmd->plugin, req); } @@ -497,11 +497,11 @@ static struct command_result *json_state_changed(struct command *cmd, const jsmntok_t *notiftok = json_get_member(buf, params, "channel_state_changed"), - *statetok = json_get_member(buf, notiftok, "new_state"); + *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, "DUALOPENED_AWAITING_LOCKIN")) { + json_tok_streq(buf, statetok, "CHANNELD_AWAITING_LOCKIN") || + json_tok_streq(buf, statetok, "DUALOPENED_AWAITING_LOCKIN")) { struct out_req *req; req = jsonrpc_request_start(cmd->plugin, cmd, @@ -583,8 +583,8 @@ static struct command_result *datastore_failed(struct command *cmd, } static struct command_result *handle_your_peer_storage(struct command *cmd, - const char *buf, - const jsmntok_t *params) + const char *buf, + const jsmntok_t *params) { struct node_id node_id; u8 *payload, *payload_deserialise; @@ -622,14 +622,14 @@ static struct command_result *handle_your_peer_storage(struct command *cmd, crypto_secretstream_xchacha20poly1305_state crypto_state; if (tal_bytelen(payload_deserialise) < ABYTES + - HEADER_LEN) + 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); + tal_bytelen(payload_deserialise) - + ABYTES - + HEADER_LEN); /* The header part */ if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, @@ -659,15 +659,15 @@ static struct command_result *handle_your_peer_storage(struct command *cmd, datastore_failed, "Saving latestscb"); } else { - plugin_log(cmd->plugin, LOG_DBG, - "Peer sent bad custom message for chanbackup!"); - return command_hook_success(cmd); + plugin_log(cmd->plugin, LOG_DBG, + "Peer sent bad custom message for chanbackup!"); + return command_hook_success(cmd); } } static struct command_result *after_latestscb(struct command *cmd, - const u8 *res, - void *cb_arg UNUSED) + const u8 *res, + void *cb_arg UNUSED) { u64 version; u32 timestamp; @@ -697,8 +697,8 @@ static struct command_result *after_latestscb(struct command *cmd, } req = jsonrpc_request_start(cmd->plugin, cmd, "recoverchannel", - after_recover_rpc, - &forward_error, NULL); + after_recover_rpc, + &forward_error, NULL); json_array_start(req->js, "scb"); for (size_t i=0; i Date: Sat, 4 Feb 2023 16:18:24 +1030 Subject: [PATCH 18/21] features: make name of peer storage features match spec. And we should always represent them as is, not as optional: it's possible in future we could *require* "WANT_PEER_BACKUP_STORAGE". Signed-off-by: Rusty Russell --- common/features.c | 16 ++++++++-------- common/features.h | 10 ++++++++-- plugins/chanbackup.c | 4 ++-- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/common/features.c b/common/features.c index 8177b7146195..a86f6f30d02a 100644 --- a/common/features.c +++ b/common/features.c @@ -136,12 +136,12 @@ static const struct feature_style feature_styles[] = { [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT, [BOLT11_FEATURE] = FEATURE_DONT_REPRESENT, [CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT } }, - { PEER_STORAGE_FEATURE, - .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL, - [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL } }, - { YOUR_PEER_STORAGE_FEATURE, - .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL, - [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT_AS_OPTIONAL } }, + { OPT_WANT_PEER_BACKUP_STORAGE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT } }, + { OPT_PROVIDE_PEER_BACKUP_STORAGE, + .copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT, + [NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT } }, }; struct dependency { @@ -462,8 +462,8 @@ const char *feature_name(const tal_t *ctx, size_t f) "option_quiesce", /* https://github.com/lightning/bolts/pull/869 */ NULL, "option_onion_messages", /* https://github.com/lightning/bolts/pull/759 */ - "option_your_peer_storage", /* 40/41 */ - "option_peer_storage", + "option_want_peer_backup_storage", /* 40/41 */ /* https://github.com/lightning/bolts/pull/881/files */ + "option_provide_peer_backup_storage", /* https://github.com/lightning/bolts/pull/881/files */ "option_channel_type", "option_scid_alias", /* https://github.com/lightning/bolts/pull/910 */ "option_payment_metadata", diff --git a/common/features.h b/common/features.h index efb3567f56e5..d9ec5744ed72 100644 --- a/common/features.h +++ b/common/features.h @@ -15,8 +15,6 @@ enum feature_place { BOLT12_INVOICE_FEATURE, }; #define NUM_FEATURE_PLACE (BOLT12_INVOICE_FEATURE+1) -#define PEER_STORAGE_FEATURE 42 -#define YOUR_PEER_STORAGE_FEATURE 40 extern const char *feature_place_names[NUM_FEATURE_PLACE]; /* The complete set of features for all contexts */ @@ -166,4 +164,12 @@ struct feature_set *feature_set_dup(const tal_t *ctx, #define OPT_SHUTDOWN_WRONG_FUNDING 104 +/* BOLT-peer-storage #9: + * + * | 40/41 | `want_peer_backup_storage` | Want to use other nodes to store encrypted backup data | IN ... + * | 42/43 | `provide_peer_backup_storage` | Can store other nodes' encrypted backup data | IN ... + */ +#define OPT_WANT_PEER_BACKUP_STORAGE 40 +#define OPT_PROVIDE_PEER_BACKUP_STORAGE 42 + #endif /* LIGHTNING_COMMON_FEATURES_H */ diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index e94a897aeffc..3cf736f87c74 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -795,10 +795,10 @@ static const struct plugin_command commands[] = { int main(int argc, char *argv[]) { setup_locale(); - struct feature_set *features = feature_set_for_feature(NULL, PEER_STORAGE_FEATURE); + struct feature_set *features = feature_set_for_feature(NULL, OPT_WANT_PEER_BACKUP_STORAGE); feature_set_or(features, take(feature_set_for_feature(NULL, - YOUR_PEER_STORAGE_FEATURE))); + OPT_PROVIDE_PEER_BACKUP_STORAGE))); plugin_main(argv, init, PLUGIN_STATIC, true, features, commands, ARRAY_SIZE(commands), From 349828d29abe033a94e7b9c49ed63d64b424046e Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 4 Feb 2023 16:22:29 +1030 Subject: [PATCH 19/21] plugins/chanbackup: neaten a little. node_id can be on the stack, avoiding a tal call. Signed-off-by: Rusty Russell --- plugins/chanbackup.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 3cf736f87c74..79a758dd770d 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -425,11 +425,10 @@ static struct command_result *after_listpeers(struct command *cmd, const jsmntok_t *params, void *cb_arg UNUSED) { - const jsmntok_t *peers, *peer, *nodeid; + const jsmntok_t *peers, *peer; struct out_req *req; size_t i; struct info *info = tal(cmd, struct info); - struct node_id *node_id = tal(cmd, struct node_id); bool is_connected; u8 *scb = get_file_data(cmd->plugin); @@ -444,8 +443,11 @@ static struct command_result *after_listpeers(struct command *cmd, &is_connected); if (is_connected) { + const jsmntok_t *nodeid; + struct node_id node_id; + nodeid = json_get_member(buf, peer, "id"); - json_to_node_id(buf, nodeid, node_id); + json_to_node_id(buf, nodeid, &node_id); req = jsonrpc_request_start(cmd->plugin, cmd, @@ -454,7 +456,7 @@ static struct command_result *after_listpeers(struct command *cmd, after_send_scb_single_fail, info); - json_add_node_id(req->js, "node_id", node_id); + json_add_node_id(req->js, "node_id", &node_id); json_add_hex(req->js, "msg", serialise_scb, tal_bytelen(serialise_scb)); info->idx++; From 6c8ba171a576a464133eab71b55a59709543ebd7 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sat, 4 Feb 2023 16:32:36 +1030 Subject: [PATCH 20/21] plugins/chanbackup: make get_file_data take ctx. When you return an allocated pointer, you should always hand in the context you want it allocated from. This is more explicit, because it may really matter to the caller! This also folds some simple operations, and avoids doing too much variable assignment in the declarations themselves: some coding styles prohibit such initializers, but that's a bit exteme. Signed-off-by: Rusty Russell --- plugins/chanbackup.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 79a758dd770d..8f8ea93ea3eb 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -168,9 +168,9 @@ static void maybe_create_new_scb(struct plugin *p, rename("scb.tmp", FILENAME); } -static u8* get_file_data(struct plugin *p) +static u8 *get_file_data(const tal_t *ctx, struct plugin *p) { - u8 *scb = grab_file(tmpctx, "emergency.recover"); + u8 *scb = grab_file(ctx, FILENAME); if (!scb) { plugin_err(p, "Cannot read emergency.recover: %s", strerror(errno)); } else { @@ -183,7 +183,7 @@ static u8* get_file_data(struct plugin *p) /* Returns decrypted SCB in form of a u8 array */ static u8 *decrypt_scb(struct plugin *p) { - u8 *filedata = get_file_data(p); + u8 *filedata = get_file_data(tmpctx, p); crypto_secretstream_xchacha20poly1305_state crypto_state; @@ -430,10 +430,10 @@ static struct command_result *after_listpeers(struct command *cmd, size_t i; struct info *info = tal(cmd, struct info); bool is_connected; + u8 *serialise_scb; - u8 *scb = get_file_data(cmd->plugin); - - u8 *serialise_scb = towire_peer_storage(cmd, scb); + serialise_scb = towire_peer_storage(cmd, + get_file_data(tmpctx, cmd->plugin)); peers = json_get_member(buf, params, "peers"); @@ -525,12 +525,14 @@ static struct command_result *peer_connected(struct command *cmd, const char *buf, const jsmntok_t *params) { - struct node_id *node_id = tal(cmd, struct node_id); + struct node_id *node_id; struct out_req *req; - u8 *scb = get_file_data(cmd->plugin); - u8 *serialise_scb = towire_peer_storage(cmd, scb); + u8 *serialise_scb; const char *err; + 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:%}}", JSON_SCAN(json_to_node_id, node_id)); From 081a1d847ff287c02ebc093e1d524f61d588d118 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Sun, 5 Feb 2023 14:14:00 +1030 Subject: [PATCH 21/21] options: create enable/disable option for peer storage. Since it's not spec-final yet (hell, it's not even properly specified yet!) we need to put it behind an experimental flag. Unfortunately, we don't have support for doing this in a plugin; a plugin must present features before parsing options. So we need to do it in core. Signed-off-by: Rusty Russell --- doc/lightning-listconfigs.7.md | 3 ++- doc/lightningd-config.5.md | 6 +++++ doc/schemas/listconfigs.schema.json | 5 +++++ lightningd/options.c | 17 +++++++++++++++ plugins/chanbackup.c | 34 +++++++++++++++++++---------- tests/test_misc.py | 7 ++++-- 6 files changed, 57 insertions(+), 15 deletions(-) diff --git a/doc/lightning-listconfigs.7.md b/doc/lightning-listconfigs.7.md index 3232d88a0fb9..fe99657469a3 100644 --- a/doc/lightning-listconfigs.7.md +++ b/doc/lightning-listconfigs.7.md @@ -62,6 +62,7 @@ On success, an object is returned, containing: - **experimental-offers** (boolean, optional): `experimental-offers` field from config or cmdline, or default - **experimental-shutdown-wrong-funding** (boolean, optional): `experimental-shutdown-wrong-funding` field from config or cmdline, or default - **experimental-websocket-port** (u16, optional): `experimental-websocket-port` field from config or cmdline, or default +- **experimental-peer-storage** (boolean, optional): `experimental-peer-storage` field from config or cmdline, or default *(added v23.02)* - **database-upgrade** (boolean, optional): `database-upgrade` field from config or cmdline - **rgb** (hex, optional): `rgb` field from config or cmdline, or default (always 6 characters) - **alias** (string, optional): `alias` field from config or cmdline, or default @@ -223,4 +224,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:581225b26efd84bfa99dc98e7a91e6fae11ef0b11939031d3da07f751f6d8f87) +[comment]: # ( SHA256STAMP:1088401b9aeae1e079dab550d3b035ef82195f0466ad471bc7373386182f37dc) diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index f5037669ef2a..0621ccb72e91 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -714,6 +714,12 @@ connections on that port, on any IPv4 and IPv6 addresses you listen to ([bolt][bolt] #891). The normal protocol is expected to be sent over WebSocket binary frames once the connection is upgraded. +* **experimental-peer-storage** + + Specifying this option means we will store up to 64k of encrypted +data for our peers, and give them our (encrypted!) backup data to +store as well, based on a protocol similar to [bolt][bolt] #881. + BUGS ---- diff --git a/doc/schemas/listconfigs.schema.json b/doc/schemas/listconfigs.schema.json index 8149e40c1627..b2ebdd003c68 100644 --- a/doc/schemas/listconfigs.schema.json +++ b/doc/schemas/listconfigs.schema.json @@ -137,6 +137,11 @@ "type": "u16", "description": "`experimental-websocket-port` field from config or cmdline, or default" }, + "experimental-peer-storage": { + "type": "boolean", + "added": "v23.02", + "description": "`experimental-peer-storage` field from config or cmdline, or default" + }, "database-upgrade": { "type": "boolean", "description": "`database-upgrade` field from config or cmdline" diff --git a/lightningd/options.c b/lightningd/options.c index 380de41464bd..475c0d387cc5 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -1078,6 +1078,15 @@ static char *opt_set_shutdown_wrong_funding(struct lightningd *ld) return NULL; } +static char *opt_set_peer_storage(struct lightningd *ld) +{ + feature_set_or(ld->our_features, + take(feature_set_for_feature(NULL, OPT_PROVIDE_PEER_BACKUP_STORAGE))); + feature_set_or(ld->our_features, + take(feature_set_for_feature(NULL, OPT_WANT_PEER_BACKUP_STORAGE))); + return NULL; +} + static char *opt_set_offers(struct lightningd *ld) { ld->config.exp_offers = true; @@ -1156,6 +1165,9 @@ static void register_opts(struct lightningd *ld) opt_register_early_noarg("--experimental-shutdown-wrong-funding", opt_set_shutdown_wrong_funding, ld, "EXPERIMENTAL: allow shutdown with alternate txids"); + opt_register_early_noarg("--experimental-peer-storage", + opt_set_peer_storage, ld, + "EXPERIMENTAL: enable peer backup storage and restore"); opt_register_early_arg("--announce-addr-dns", opt_set_bool_arg, opt_show_bool, &ld->announce_dns, @@ -1641,6 +1653,11 @@ static void add_config(struct lightningd *ld, feature_offered(ld->our_features ->bits[INIT_FEATURE], OPT_SHUTDOWN_WRONG_FUNDING)); + } else if (opt->cb == (void *)opt_set_peer_storage) { + json_add_bool(response, name0, + feature_offered(ld->our_features + ->bits[INIT_FEATURE], + OPT_PROVIDE_PEER_BACKUP_STORAGE)); } else if (opt->cb == (void *)plugin_opt_flag_set) { /* Noop, they will get added below along with the * OPT_HASARG options. */ diff --git a/plugins/chanbackup.c b/plugins/chanbackup.c index 8f8ea93ea3eb..bf906b7edc76 100644 --- a/plugins/chanbackup.c +++ b/plugins/chanbackup.c @@ -30,6 +30,7 @@ /* Global secret object to keep the derived encryption key for the SCB */ static struct secret secret; +static bool peer_backup; /* Helper to fetch out SCB from the RPC call */ static bool json_to_scb_chan(const char *buffer, @@ -530,6 +531,9 @@ static struct command_result *peer_connected(struct command *cmd, u8 *serialise_scb; const char *err; + if (!peer_backup) + return command_hook_success(cmd); + serialise_scb = towire_peer_storage(cmd, get_file_data(tmpctx, cmd->plugin)); node_id = tal(cmd, struct node_id); @@ -592,13 +596,15 @@ static struct command_result *handle_your_peer_storage(struct command *cmd, { struct node_id node_id; u8 *payload, *payload_deserialise; - const char *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)); + const char *err; + + if (!peer_backup) + return command_hook_success(cmd); + + 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", @@ -737,6 +743,14 @@ static const char *init(struct plugin *p, struct 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(p, "getinfo", + take(json_out_obj(NULL, NULL, NULL)), + "{our_features:{init:%}}", + JSON_SCAN_TAL(tmpctx, json_tok_bin_from_hex, &features)); + peer_backup = feature_offered(features, OPT_WANT_PEER_BACKUP_STORAGE); rpc_scan(p, "staticbackup", take(json_out_obj(NULL, NULL, NULL)), @@ -799,12 +813,8 @@ static const struct plugin_command commands[] = { int main(int argc, char *argv[]) { setup_locale(); - struct feature_set *features = feature_set_for_feature(NULL, OPT_WANT_PEER_BACKUP_STORAGE); - feature_set_or(features, - take(feature_set_for_feature(NULL, - OPT_PROVIDE_PEER_BACKUP_STORAGE))); - plugin_main(argv, init, PLUGIN_STATIC, true, features, + plugin_main(argv, init, PLUGIN_STATIC, true, NULL, commands, ARRAY_SIZE(commands), notifs, ARRAY_SIZE(notifs), hooks, ARRAY_SIZE(hooks), NULL, 0, /* Notification topics we publish */ diff --git a/tests/test_misc.py b/tests/test_misc.py index 1c826fe832e2..d144c66a89aa 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2510,8 +2510,11 @@ def test_restorefrompeer(node_factory, bitcoind): """ Test restorefrompeer """ - l1, l2 = node_factory.get_nodes(2, opts=[{'allow_broken_log': True, 'may_reconnect': True}, - {'may_reconnect': True}]) + l1, l2 = node_factory.get_nodes(2, [{'allow_broken_log': True, + 'experimental-peer-storage': None, + 'may_reconnect': True}, + {'experimental-peer-storage': None, + 'may_reconnect': True}]) l1.rpc.connect(l2.info['id'], 'localhost', l2.port)