diff --git a/channeld/channeld.c b/channeld/channeld.c index fc36fee04..d33e9da1e 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 25083fb08..a376f5a65 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 11bc0e143..4d4fe6c67 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 356551707..7d6f16c54 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 3958e9dfa..bf9f91cf5 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 a44103d29..b4368a090 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)