diff --git a/common/json_stream.c b/common/json_stream.c index 7d1cc92cbd9a..59724a7d534f 100644 --- a/common/json_stream.c +++ b/common/json_stream.c @@ -323,11 +323,6 @@ void json_add_bool(struct json_stream *result, const char *fieldname, bool value json_add_primitive(result, fieldname, value ? "true" : "false"); } -void json_add_null(struct json_stream *stream, const char *fieldname) -{ - json_add_primitive(stream, fieldname, "null"); -} - void json_add_hex(struct json_stream *js, const char *fieldname, const void *data, size_t len) { diff --git a/common/json_stream.h b/common/json_stream.h index bfbb5d0a78df..a2c43ee46cb4 100644 --- a/common/json_stream.h +++ b/common/json_stream.h @@ -242,8 +242,7 @@ void json_add_s32(struct json_stream *result, const char *fieldname, void json_add_bool(struct json_stream *result, const char *fieldname, bool value); -/* '"fieldname" : null' or 'null' if fieldname is NULL */ -void json_add_null(struct json_stream *stream, const char *fieldname); +/* Looking for json_add_null? Don't do that: we omit fields, don't 'null' them! */ /* '"fieldname" : "0189abcdef..."' or "0189abcdef..." if fieldname is NULL */ void json_add_hex(struct json_stream *result, const char *fieldname, diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index 69c33acf4aa8..f669e56a318d 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -36358,10 +36358,12 @@ "DUALOPEND_AWAITING_LOCKIN", "CHANNELD_AWAITING_SPLICE", "DUALOPEND_OPEN_COMMITTED", - "DUALOPEND_OPEN_COMMIT_READY" + "DUALOPEND_OPEN_COMMIT_READY", + "CLOSED" ], "description": [ - "The channel state, in particular \"CHANNELD_NORMAL\" and \"CHANNELD_AWAITING_SPLICE\" mean the channel can be used normally" + "The channel state, in particular \"CHANNELD_NORMAL\" and \"CHANNELD_AWAITING_SPLICE\" mean the channel can be used normally", + "Note: *CLOSED* state was only added in v25.12." ], "added": "pre-v0.10.1" }, diff --git a/doc/developers-guide/deprecated-features.md b/doc/developers-guide/deprecated-features.md index 6769e84012c2..d2717a5ab6f0 100644 --- a/doc/developers-guide/deprecated-features.md +++ b/doc/developers-guide/deprecated-features.md @@ -24,6 +24,7 @@ hidden: false | channel_state_changed.null_scid | Notification Field | v25.09 | v26.09 | In channel_state_changed notification, `short_channel_id` will be missing instead of `null` | | notification.payload | Notification Field | v25.09 | v26.09 | Notifications from plugins used to have fields in `payload` sub-object, now they are not (just like normal notifications) | | pay_notifications.raw_fields | Field | v25.09 | v26.09 | `channel_hint_update`, `pay_failure` and `pay_success` notifications now wrap members in an object of the same name | +| channel_state_changed.null_message | Notification Field | v25.12 | v26.12 | In channel_state_changed notification, `message` will be missing instead of `null` | Inevitably there are features which need to change: either to be generalized, or removed when they can no longer be supported. diff --git a/doc/schemas/notification/channel_state_changed.json b/doc/schemas/notification/channel_state_changed.json index 72e470d2bbb4..c2f1aba18353 100644 --- a/doc/schemas/notification/channel_state_changed.json +++ b/doc/schemas/notification/channel_state_changed.json @@ -87,10 +87,12 @@ "DUALOPEND_AWAITING_LOCKIN", "CHANNELD_AWAITING_SPLICE", "DUALOPEND_OPEN_COMMITTED", - "DUALOPEND_OPEN_COMMIT_READY" + "DUALOPEND_OPEN_COMMIT_READY", + "CLOSED" ], "description": [ - "The channel state, in particular \"CHANNELD_NORMAL\" and \"CHANNELD_AWAITING_SPLICE\" mean the channel can be used normally" + "The channel state, in particular \"CHANNELD_NORMAL\" and \"CHANNELD_AWAITING_SPLICE\" mean the channel can be used normally", + "Note: *CLOSED* state was only added in v25.12." ], "added": "pre-v0.10.1" }, diff --git a/lightningd/channel.c b/lightningd/channel.c index 354249a4c24c..8eeb1f68b713 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -111,6 +111,16 @@ void delete_channel(struct channel *channel STEALS, bool completely_eliminate) fatal("HSM gave bad hsm_forget_channel_reply %s", tal_hex(msg, msg)); } + notify_channel_state_changed(channel->peer->ld, + &channel->peer->id, + &channel->cid, + channel->scid, + time_now(), + channel->state, + CLOSED, + channel->state_change_cause, + NULL); + tal_free(channel); maybe_delete_peer(peer); diff --git a/lightningd/notification.c b/lightningd/notification.c index 22761aabd396..1980cf835495 100644 --- a/lightningd/notification.c +++ b/lightningd/notification.c @@ -275,6 +275,12 @@ void notify_channel_opened(struct lightningd *ld, notify_send(ld, n); } +/* Don't use this: omit fields instead! */ +static void json_add_null(struct json_stream *stream, const char *fieldname) +{ + json_add_primitive(stream, fieldname, "null"); +} + static void channel_state_changed_notification_serialize(struct json_stream *stream, struct lightningd *ld, const struct node_id *peer_id, @@ -304,8 +310,12 @@ static void channel_state_changed_notification_serialize(struct json_stream *str json_add_string(stream, "cause", channel_change_state_reason_str(cause)); if (message != NULL) json_add_string(stream, "message", message); - else + else if (lightningd_deprecated_out_ok(ld, ld->deprecated_ok, + "channel_state_changed", + "null_message", + "v25.12", "v26.12")) { json_add_null(stream, "message"); + } } REGISTER_NOTIFICATION(channel_state_changed) diff --git a/plugins/bcli.c b/plugins/bcli.c index fdd5a7c6e228..b3addff21b07 100644 --- a/plugins/bcli.c +++ b/plugins/bcli.c @@ -396,6 +396,12 @@ static struct command_result *command_err_bcli_badjson(struct bitcoin_cli *bcli, return command_done_err(bcli->cmd, BCLI_ERROR, err, NULL); } +/* Don't use this in general: it's better to omit fields. */ +static void json_add_null(struct json_stream *stream, const char *fieldname) +{ + json_add_primitive(stream, fieldname, "null"); +} + static struct command_result *process_getutxout(struct bitcoin_cli *bcli) { const jsmntok_t *tokens; diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 788942c6dcb0..d266df0c21bb 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -938,7 +938,7 @@ def wait_for_event(node): assert(event2['cause'] == "remote") assert(event2['message'] == "Closing complete") - bitcoind.generate_block(100, wait_for_mempool=1) # so it gets settled + bitcoind.generate_block(99, wait_for_mempool=1) # so it gets settled event1 = wait_for_event(l1) assert(event1['old_state'] == "CLOSINGD_COMPLETE") @@ -962,6 +962,18 @@ def wait_for_event(node): assert(event2['cause'] == "remote") assert(event2['message'] == "Onchain init reply") + bitcoind.generate_block(1) + event1 = wait_for_event(l1) + assert(event1['old_state'] == "ONCHAIN") + assert(event1['new_state'] == "CLOSED") + assert(event1['cause'] == "onchain") + assert('message' not in event1) + event2 = wait_for_event(l2) + assert(event2['old_state'] == "ONCHAIN") + assert(event2['new_state'] == "CLOSED") + assert(event2['cause'] == "onchain") + assert('message' not in event2) + @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @@ -1056,8 +1068,8 @@ def wait_for_event(node): assert(event1['cause'] == "protocol") assert(event1['message'] == "channeld: received ERROR channel {}: Forcibly closed by `close` command timeout".format(cid)) - # settle the channel closure - bitcoind.generate_block(100) + # Almost settle the channel closure + bitcoind.generate_block(99, wait_for_mempool=1) event2 = wait_for_event(l2) assert(event2['old_state'] == "AWAITING_UNILATERAL") @@ -1085,6 +1097,19 @@ def wait_for_event(node): assert(event1['cause'] == "onchain") assert(event1['message'] == "Onchain init reply") + bitcoind.generate_block(1) + event1 = wait_for_event(l1) + assert(event1['old_state'] == "ONCHAIN") + assert(event1['new_state'] == "CLOSED") + assert(event1['cause'] == "onchain") + assert('message' not in event1) + + event2 = wait_for_event(l2) + assert(event2['old_state'] == "ONCHAIN") + assert(event2['new_state'] == "CLOSED") + assert(event2['cause'] == "onchain") + assert('message' not in event2) + @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2')