From 2583b087e8a2e13ab8f52a6f56848e7c0672c4ad Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 31 May 2021 12:37:50 +0930 Subject: [PATCH 1/4] EXPERIMENTAL: import spec for quiescence. Imported from commit b96218b06b68cf349457b282f05d48ebd89c7273 Signed-off-by: Rusty Russell --- channeld/channeld.c | 3 +++ gossipd/gossipd.c | 3 +++ openingd/dualopend.c | 9 +++++++++ wire/extracted_peer_exp_quiescence-protocol.patch | 12 ++++++++++++ wire/peer_wire.c | 6 ++++++ 5 files changed, 33 insertions(+) create mode 100644 wire/extracted_peer_exp_quiescence-protocol.patch diff --git a/channeld/channeld.c b/channeld/channeld.c index 862f5e5a6e8f..e2a41c28e0ec 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -1949,6 +1949,9 @@ static void peer_in(struct peer *peer, const u8 *msg) return; case WIRE_INIT_RBF: case WIRE_ACK_RBF: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif break; case WIRE_CHANNEL_REESTABLISH: diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index d47bda25c7fc..5619b5ef7b3d 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -741,6 +741,9 @@ static struct io_plan *peer_msg_in(struct io_conn *conn, case WIRE_ACCEPT_CHANNEL2: case WIRE_INIT_RBF: case WIRE_ACK_RBF: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif status_broken("peer %s: relayed unexpected msg of type %s", type_to_string(tmpctx, struct node_id, &peer->id), peer_wire_name(fromwire_peektype(msg))); diff --git a/openingd/dualopend.c b/openingd/dualopend.c index 640c8ebf29c8..a264bbf208d4 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -1273,6 +1273,9 @@ static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state) case WIRE_WARNING: case WIRE_PING: case WIRE_PONG: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif break; } @@ -1614,6 +1617,9 @@ static bool run_tx_interactive(struct state *state, case WIRE_REPLY_SHORT_CHANNEL_IDS_END: case WIRE_PING: case WIRE_PONG: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif open_err_warn(state, "Unexpected wire message %s", tal_hex(tmpctx, msg)); return false; @@ -3402,6 +3408,9 @@ static u8 *handle_peer_in(struct state *state) case WIRE_WARNING: case WIRE_PING: case WIRE_PONG: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif break; } diff --git a/wire/extracted_peer_exp_quiescence-protocol.patch b/wire/extracted_peer_exp_quiescence-protocol.patch new file mode 100644 index 000000000000..804b726b923f --- /dev/null +++ b/wire/extracted_peer_exp_quiescence-protocol.patch @@ -0,0 +1,12 @@ +--- wire/peer_exp_wire.csv 2021-05-17 09:30:02.302260471 +0930 ++++ - 2021-05-31 12:20:36.390910632 +0930 +@@ -120,6 +82,9 @@ + msgtype,ack_rbf,73 + msgdata,ack_rbf,channel_id,channel_id, + msgdata,ack_rbf,funding_satoshis,u64, ++msgtype,stfu,2 ++msgdata,stfu,channel_id,channel_id, ++msgdata,stfu,initiator,u8, + msgtype,shutdown,38 + msgdata,shutdown,channel_id,channel_id, + msgdata,shutdown,len,u16, diff --git a/wire/peer_wire.c b/wire/peer_wire.c index a79a16a2e9f7..1f8fc3731306 100644 --- a/wire/peer_wire.c +++ b/wire/peer_wire.c @@ -44,6 +44,9 @@ static bool unknown_type(enum peer_wire t) case WIRE_ACCEPT_CHANNEL2: case WIRE_INIT_RBF: case WIRE_ACK_RBF: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif return false; } return true; @@ -93,6 +96,9 @@ bool is_msg_for_gossipd(const u8 *cursor) case WIRE_ACCEPT_CHANNEL2: case WIRE_INIT_RBF: case WIRE_ACK_RBF: +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: +#endif break; } return false; From 9718fcfaf1f87246e9be1b20be87029d11b3fc7c Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 31 May 2021 12:38:04 +0930 Subject: [PATCH 2/4] channeld: implement pending_updates() Says if we have started an update which is still pending somewhere. Signed-off-by: Rusty Russell --- channeld/full_channel.c | 28 ++++++++++++++++++++++++++++ channeld/full_channel.h | 7 +++++++ 2 files changed, 35 insertions(+) diff --git a/channeld/full_channel.c b/channeld/full_channel.c index 53d504d5c8c0..55ae6214c0b3 100644 --- a/channeld/full_channel.c +++ b/channeld/full_channel.c @@ -1252,6 +1252,34 @@ static bool adjust_balance(struct balance view_owed[NUM_SIDES][NUM_SIDES], return true; } +bool pending_updates(const struct channel *channel, enum side side) +{ + struct htlc_map_iter it; + const struct htlc *htlc; + + /* Initiator might have fee changes in play. */ + if (side == channel->opener) { + if (!feerate_changes_done(channel->fee_states)) + return true; + } + + for (htlc = htlc_map_first(channel->htlcs, &it); + htlc; + htlc = htlc_map_next(channel->htlcs, &it)) { + /* If it's still being added, it's owner added it. */ + if (htlc_state_flags(htlc->state) & HTLC_ADDING) { + if (htlc_owner(htlc) == side) + return true; + /* If it's being removed, non-owner removed it */ + } else if (htlc_state_flags(htlc->state) & HTLC_REMOVING) { + if (htlc_owner(htlc) != side) + return true; + } + } + + return false; +} + bool channel_force_htlcs(struct channel *channel, const struct existing_htlc **htlcs) { diff --git a/channeld/full_channel.h b/channeld/full_channel.h index 11a43451fbbf..8f04547eb70b 100644 --- a/channeld/full_channel.h +++ b/channeld/full_channel.h @@ -254,6 +254,13 @@ bool channel_force_htlcs(struct channel *channel, */ void dump_htlcs(const struct channel *channel, const char *prefix); +/** + * pending_updates: does this side have updates pending in channel? + * @channel: the channel + * @side: the side who is offering or failing/fulfilling HTLC, or feechange + */ +bool pending_updates(const struct channel *channel, enum side side); + const char *channel_add_err_name(enum channel_add_err e); const char *channel_remove_err_name(enum channel_remove_err e); From 92f160c40d71e11819a21b751741da93e9f36fad Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 31 May 2021 12:38:04 +0930 Subject: [PATCH 3/4] EXPERIMENTAL: handle receiving quiescence request. Changelog-EXPERIMENTAL: Protocol: we support the quiescence protocol from https://github.com/lightningnetwork/lightning-rfc/pull/869 Signed-off-by: Rusty Russell --- channeld/channeld.c | 139 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 3 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index e2a41c28e0ec..fc36fee04bb0 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -159,6 +159,17 @@ struct peer { /* If master told us to send wrong_funding */ struct bitcoin_outpoint *shutdown_wrong_funding; +#if EXPERIMENTAL_FEATURES + /* Do we want quiescence? */ + bool stfu; + /* Which side is considered the initiator? */ + enum side stfu_initiator; + /* Has stfu been sent by each side? */ + bool stfu_sent[NUM_SIDES]; + /* Updates master asked, which we've deferred while quiescing */ + struct msg_queue *update_queue; +#endif + /* Information used for reestablishment. */ bool last_was_revoke; struct changed_htlc *last_sent_commit; @@ -273,6 +284,99 @@ static struct amount_msat advertized_htlc_max(const struct channel *channel) return lower_bound_msat; } +#if EXPERIMENTAL_FEATURES +static void maybe_send_stfu(struct peer *peer) +{ + if (!peer->stfu) + return; + + if (!peer->stfu_sent[LOCAL] && !pending_updates(peer->channel, LOCAL)) { + u8 *msg = towire_stfu(NULL, &peer->channel_id, + peer->stfu_initiator == LOCAL); + sync_crypto_write(peer->pps, take(msg)); + peer->stfu_sent[LOCAL] = true; + } + + /* FIXME: We're finished, do something! */ + if (peer->stfu_sent[LOCAL] && peer->stfu_sent[REMOTE]) + status_unusual("STFU complete: we are quiescent"); +} + +static void handle_stfu(struct peer *peer, const u8 *stfu) +{ + struct channel_id channel_id; + u8 remote_initiated; + + if (!fromwire_stfu(stfu, &channel_id, &remote_initiated)) + peer_failed_warn(peer->pps, &peer->channel_id, + "Bad stfu %s", tal_hex(peer, stfu)); + + if (!channel_id_eq(&channel_id, &peer->channel_id)) { + peer_failed_err(peer->pps, &channel_id, + "Wrong stfu channel_id: expected %s, got %s", + type_to_string(tmpctx, struct channel_id, + &peer->channel_id), + type_to_string(tmpctx, struct channel_id, + &channel_id)); + } + + /* Sanity check */ + if (pending_updates(peer->channel, REMOTE)) + peer_failed_warn(peer->pps, &peer->channel_id, + "STFU but you still have updates pending?"); + + if (!peer->stfu) { + peer->stfu = true; + if (!remote_initiated) + peer_failed_warn(peer->pps, &peer->channel_id, + "Unsolicited STFU but you said" + " you didn't initiate?"); + peer->stfu_initiator = REMOTE; + } else { + /* BOLT-quiescent #2: + * + * If both sides send `stfu` simultaneously, they will both + * set `initiator` to `1`, in which case the "initiator" is + * arbitrarily considered to be the channel funder (the sender + * of `open_channel`). + */ + if (remote_initiated) + peer->stfu_initiator = peer->channel->opener; + } + + /* BOLT-quiescent #2: + * The receiver of `stfu`: + * - if it has sent `stfu` then: + * - MUST now consider the channel to be quiescent + * - otherwise: + * - SHOULD NOT send any more update messages. + * - MUST reply with `stfu` once it can do so. + */ + peer->stfu_sent[REMOTE] = true; + + maybe_send_stfu(peer); +} + +/* Returns true if we queued this for later handling (steals if true) */ +static bool handle_master_request_later(struct peer *peer, const u8 *msg) +{ + if (peer->stfu) { + msg_enqueue(peer->update_queue, take(msg)); + return true; + } + return false; +} +#else /* !EXPERIMENTAL_FEATURES */ +static bool handle_master_request_later(struct peer *peer, const u8 *msg) +{ + return false; +} + +static void maybe_send_stfu(struct peer *peer) +{ +} +#endif + /* Create and send channel_update to gossipd (and maybe peer) */ static void send_channel_update(struct peer *peer, int disable_flag) { @@ -952,6 +1056,12 @@ static bool want_fee_update(const struct peer *peer, u32 *target) if (peer->channel->opener != LOCAL) return false; +#if EXPERIMENTAL_FEATURES + /* No fee update while quiescing! */ + if (peer->stfu) + return false; +#endif + max = approx_max_feerate(peer->channel); val = peer->desired_feerate; @@ -1408,6 +1518,9 @@ static void handle_peer_commit_sig(struct peer *peer, const u8 *msg) send_revocation(peer, &commit_sig, htlc_sigs, changed_htlcs, txs[0]); + /* We may now be quiescent on our side. */ + maybe_send_stfu(peer); + /* This might have synced the feerates: if so, we may want to * update */ if (want_fee_update(peer, NULL)) @@ -1537,6 +1650,9 @@ static void handle_peer_revoke_and_ack(struct peer *peer, const u8 *msg) type_to_string(tmpctx, struct pubkey, &peer->old_remote_per_commit)); + /* We may now be quiescent on our side. */ + maybe_send_stfu(peer); + start_commit_timer(peer); } @@ -1931,6 +2047,11 @@ static void peer_in(struct peer *peer, const u8 *msg) handle_peer_shutdown(peer, msg); return; +#if EXPERIMENTAL_FEATURES + case WIRE_STFU: + handle_stfu(peer, msg); + return; +#endif case WIRE_INIT: case WIRE_OPEN_CHANNEL: case WIRE_ACCEPT_CHANNEL: @@ -1949,9 +2070,6 @@ static void peer_in(struct peer *peer, const u8 *msg) return; case WIRE_INIT_RBF: case WIRE_ACK_RBF: -#if EXPERIMENTAL_FEATURES - case WIRE_STFU: -#endif break; case WIRE_CHANNEL_REESTABLISH: @@ -2972,18 +3090,28 @@ static void req_in(struct peer *peer, const u8 *msg) handle_funding_depth(peer, msg); return; case WIRE_CHANNELD_OFFER_HTLC: + if (handle_master_request_later(peer, msg)) + return; handle_offer_htlc(peer, msg); return; case WIRE_CHANNELD_FEERATES: + if (handle_master_request_later(peer, msg)) + return; handle_feerates(peer, msg); return; case WIRE_CHANNELD_FULFILL_HTLC: + if (handle_master_request_later(peer, msg)) + return; handle_preimage(peer, msg); return; case WIRE_CHANNELD_FAIL_HTLC: + if (handle_master_request_later(peer, msg)) + return; handle_fail(peer, msg); return; case WIRE_CHANNELD_SPECIFIC_FEERATES: + if (handle_master_request_later(peer, msg)) + return; handle_specific_feerates(peer, msg); return; case WIRE_CHANNELD_SEND_SHUTDOWN: @@ -3266,6 +3394,11 @@ int main(int argc, char *argv[]) /* We actually received it in the previous daemon, but near enough */ peer->last_recv = time_now(); peer->last_empty_commitment = 0; +#if EXPERIMENTAL_FEATURES + peer->stfu = false; + peer->stfu_sent[LOCAL] = peer->stfu_sent[REMOTE] = false; + peer->update_queue = msg_queue_new(peer); +#endif /* We send these to HSM to get real signatures; don't have valgrind * complain. */ From e7a1a6ade8d48e71636c12c0facc22c5a2e26fef Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 31 May 2021 12:38:04 +0930 Subject: [PATCH 4/4] EXPERIMENTAL: dev-quiesce to initiate (and test) quiescence. Signed-off-by: Rusty Russell --- channeld/channeld.c | 29 ++++++++++++++++++-- channeld/channeld_wire.csv | 4 +++ channeld/channeld_wiregen.c | 45 ++++++++++++++++++++++++++++++- channeld/channeld_wiregen.h | 14 +++++++++- lightningd/channel_control.c | 52 ++++++++++++++++++++++++++++++++++++ tests/test_connection.py | 24 +++++++++++++++++ 6 files changed, 164 insertions(+), 4 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index fc36fee04bb0..d33e9da1e2fe 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -297,9 +297,11 @@ static void maybe_send_stfu(struct peer *peer) peer->stfu_sent[LOCAL] = true; } - /* FIXME: We're finished, do something! */ - if (peer->stfu_sent[LOCAL] && peer->stfu_sent[REMOTE]) + if (peer->stfu_sent[LOCAL] && peer->stfu_sent[REMOTE]) { status_unusual("STFU complete: we are quiescent"); + wire_sync_write(MASTER_FD, + towire_channeld_dev_quiesce_reply(tmpctx)); + } } static void handle_stfu(struct peer *peer, const u8 *stfu) @@ -3079,6 +3081,22 @@ static void channeld_send_custommsg(struct peer *peer, const u8 *msg) master_badmsg(WIRE_CUSTOMMSG_OUT, msg); sync_crypto_write(peer->pps, take(inner)); } + +#if EXPERIMENTAL_FEATURES +static void handle_dev_quiesce(struct peer *peer, const u8 *msg) +{ + if (!fromwire_channeld_dev_quiesce(msg)) + master_badmsg(WIRE_CHANNELD_DEV_QUIESCE, msg); + + /* Don't do this twice. */ + if (peer->stfu) + status_failed(STATUS_FAIL_MASTER_IO, "dev_quiesce already"); + + peer->stfu = true; + peer->stfu_initiator = LOCAL; + maybe_send_stfu(peer); +} +#endif /* EXPERIMENTAL_FEATURES */ #endif /* DEVELOPER */ static void req_in(struct peer *peer, const u8 *msg) @@ -3127,9 +3145,15 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNELD_DEV_MEMLEAK: handle_dev_memleak(peer, msg); return; + case WIRE_CHANNELD_DEV_QUIESCE: +#if EXPERIMENTAL_FEATURES + handle_dev_quiesce(peer, msg); + return; +#endif /* EXPERIMENTAL_FEATURES */ #else case WIRE_CHANNELD_DEV_REENABLE_COMMIT: case WIRE_CHANNELD_DEV_MEMLEAK: + case WIRE_CHANNELD_DEV_QUIESCE: #endif /* DEVELOPER */ case WIRE_CHANNELD_INIT: case WIRE_CHANNELD_OFFER_HTLC_REPLY: @@ -3147,6 +3171,7 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNELD_FAIL_FALLEN_BEHIND: case WIRE_CHANNELD_DEV_MEMLEAK_REPLY: case WIRE_CHANNELD_SEND_ERROR_REPLY: + case WIRE_CHANNELD_DEV_QUIESCE_REPLY: break; } diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index 25083fb088ed..a376f5a6536f 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -217,3 +217,7 @@ msgdata,channeld_send_error,reason,wirestring, # Tell master channeld has sent the error message. msgtype,channeld_send_error_reply,1108 + +# Ask channeld to quiesce. +msgtype,channeld_dev_quiesce,1009 +msgtype,channeld_dev_quiesce_reply,1109 diff --git a/channeld/channeld_wiregen.c b/channeld/channeld_wiregen.c index 11bc0e143c5f..4d4fe6c676e4 100644 --- a/channeld/channeld_wiregen.c +++ b/channeld/channeld_wiregen.c @@ -46,6 +46,8 @@ const char *channeld_wire_name(int e) case WIRE_CHANNELD_GOT_ANNOUNCEMENT: return "WIRE_CHANNELD_GOT_ANNOUNCEMENT"; case WIRE_CHANNELD_SEND_ERROR: return "WIRE_CHANNELD_SEND_ERROR"; case WIRE_CHANNELD_SEND_ERROR_REPLY: return "WIRE_CHANNELD_SEND_ERROR_REPLY"; + case WIRE_CHANNELD_DEV_QUIESCE: return "WIRE_CHANNELD_DEV_QUIESCE"; + case WIRE_CHANNELD_DEV_QUIESCE_REPLY: return "WIRE_CHANNELD_DEV_QUIESCE_REPLY"; } snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); @@ -81,6 +83,8 @@ bool channeld_wire_is_defined(u16 type) case WIRE_CHANNELD_GOT_ANNOUNCEMENT:; case WIRE_CHANNELD_SEND_ERROR:; case WIRE_CHANNELD_SEND_ERROR_REPLY:; + case WIRE_CHANNELD_DEV_QUIESCE:; + case WIRE_CHANNELD_DEV_QUIESCE_REPLY:; return true; } return false; @@ -1070,4 +1074,43 @@ bool fromwire_channeld_send_error_reply(const void *p) return false; return cursor != NULL; } -// SHA256STAMP:60143693b0c3611c8ecdf7f3549ef9f4c280e359cac0cd1f4df38cdca2dad3cb + +/* WIRE: CHANNELD_DEV_QUIESCE */ +/* Ask channeld to quiesce. */ +u8 *towire_channeld_dev_quiesce(const tal_t *ctx) +{ + u8 *p = tal_arr(ctx, u8, 0); + + towire_u16(&p, WIRE_CHANNELD_DEV_QUIESCE); + + return memcheck(p, tal_count(p)); +} +bool fromwire_channeld_dev_quiesce(const void *p) +{ + const u8 *cursor = p; + size_t plen = tal_count(p); + + if (fromwire_u16(&cursor, &plen) != WIRE_CHANNELD_DEV_QUIESCE) + return false; + return cursor != NULL; +} + +/* WIRE: CHANNELD_DEV_QUIESCE_REPLY */ +u8 *towire_channeld_dev_quiesce_reply(const tal_t *ctx) +{ + u8 *p = tal_arr(ctx, u8, 0); + + towire_u16(&p, WIRE_CHANNELD_DEV_QUIESCE_REPLY); + + return memcheck(p, tal_count(p)); +} +bool fromwire_channeld_dev_quiesce_reply(const void *p) +{ + const u8 *cursor = p; + size_t plen = tal_count(p); + + if (fromwire_u16(&cursor, &plen) != WIRE_CHANNELD_DEV_QUIESCE_REPLY) + return false; + return cursor != NULL; +} +// SHA256STAMP:720f9917311384d373593dc1550619ddf461bdabde8b312ed6dc632cb7860c34 diff --git a/channeld/channeld_wiregen.h b/channeld/channeld_wiregen.h index 3565517070f2..7d6f16c5426d 100644 --- a/channeld/channeld_wiregen.h +++ b/channeld/channeld_wiregen.h @@ -70,6 +70,9 @@ enum channeld_wire { WIRE_CHANNELD_SEND_ERROR = 1008, /* Tell master channeld has sent the error message. */ WIRE_CHANNELD_SEND_ERROR_REPLY = 1108, + /* Ask channeld to quiesce. */ + WIRE_CHANNELD_DEV_QUIESCE = 1009, + WIRE_CHANNELD_DEV_QUIESCE_REPLY = 1109, }; const char *channeld_wire_name(int e); @@ -211,6 +214,15 @@ bool fromwire_channeld_send_error(const tal_t *ctx, const void *p, wirestring ** u8 *towire_channeld_send_error_reply(const tal_t *ctx); bool fromwire_channeld_send_error_reply(const void *p); +/* WIRE: CHANNELD_DEV_QUIESCE */ +/* Ask channeld to quiesce. */ +u8 *towire_channeld_dev_quiesce(const tal_t *ctx); +bool fromwire_channeld_dev_quiesce(const void *p); + +/* WIRE: CHANNELD_DEV_QUIESCE_REPLY */ +u8 *towire_channeld_dev_quiesce_reply(const tal_t *ctx); +bool fromwire_channeld_dev_quiesce_reply(const void *p); + #endif /* LIGHTNING_CHANNELD_CHANNELD_WIREGEN_H */ -// SHA256STAMP:60143693b0c3611c8ecdf7f3549ef9f4c280e359cac0cd1f4df38cdca2dad3cb +// SHA256STAMP:720f9917311384d373593dc1550619ddf461bdabde8b312ed6dc632cb7860c34 diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 3958e9dfa1b7..bf9f91cf52ff 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -425,11 +425,13 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNELD_FEERATES: case WIRE_CHANNELD_SPECIFIC_FEERATES: case WIRE_CHANNELD_DEV_MEMLEAK: + case WIRE_CHANNELD_DEV_QUIESCE: /* Replies go to requests. */ case WIRE_CHANNELD_OFFER_HTLC_REPLY: case WIRE_CHANNELD_DEV_REENABLE_COMMIT_REPLY: case WIRE_CHANNELD_DEV_MEMLEAK_REPLY: case WIRE_CHANNELD_SEND_ERROR: + case WIRE_CHANNELD_DEV_QUIESCE_REPLY: break; } @@ -937,4 +939,54 @@ static const struct json_command dev_feerate_command = { "Set feerate for {id} to {feerate}" }; AUTODATA(json_command, &dev_feerate_command); + +#if EXPERIMENTAL_FEATURES +static void quiesce_reply(struct subd *channeld UNUSED, + const u8 *reply, + const int *fds UNUSED, + struct command *cmd) +{ + struct json_stream *response; + + response = json_stream_success(cmd); + was_pending(command_success(cmd, response)); +} + +static struct command_result *json_dev_quiesce(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct node_id *id; + struct peer *peer; + struct channel *channel; + const u8 *msg; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + NULL)) + return command_param_failed(); + + peer = peer_by_id(cmd->ld, id); + if (!peer) + return command_fail(cmd, LIGHTNINGD, "Peer not connected"); + + channel = peer_active_channel(peer); + if (!channel || !channel->owner || channel->state != CHANNELD_NORMAL) + return command_fail(cmd, LIGHTNINGD, "Peer bad state"); + + msg = towire_channeld_dev_quiesce(NULL); + subd_req(channel->owner, channel->owner, take(msg), -1, 0, + quiesce_reply, cmd); + return command_still_pending(cmd); +} + +static const struct json_command dev_quiesce_command = { + "dev-quiesce", + "developer", + json_dev_quiesce, + "Initiate quiscence protocol with peer" +}; +AUTODATA(json_command, &dev_quiesce_command); +#endif /* EXPERIMENTAL_FEATURES */ #endif /* DEVELOPER */ diff --git a/tests/test_connection.py b/tests/test_connection.py index a44103d2933b..b4368a0903bd 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -3470,6 +3470,30 @@ def test_openchannel_init_alternate(node_factory, executor): fut.result(10) +@unittest.skipIf(not EXPERIMENTAL_FEATURES, "quiescence is experimental") +@pytest.mark.developer("quiescence triggering is dev only") +def test_quiescence(node_factory, executor): + l1, l2 = node_factory.line_graph(2) + + # Works fine. + l1.pay(l2, 1000) + + assert l1.rpc.call('dev-quiesce', [l2.info['id']]) == {} + + # Both should consider themselves quiescent. + l1.daemon.wait_for_log("STFU complete: we are quiescent") + l2.daemon.wait_for_log("STFU complete: we are quiescent") + + # Should not be able to increase fees. + l1.rpc.call('dev-feerate', [l2.info['id'], 9999]) + + try: + l1.daemon.wait_for_log('peer_out WIRE_UPDATE_FEE', 5) + assert False + except TimeoutError: + pass + + def test_htlc_failed_noclose(node_factory): """Test a bug where the htlc timeout would kick in even if the HTLC failed""" l1, l2 = node_factory.line_graph(2)