Skip to content

Commit c0924fb

Browse files
authored
MSC4140: put delay_id in unsigned data for sender (#19479)
Implements matrix-org/matrix-spec-proposals@49b200d
1 parent 4c475dc commit c0924fb

8 files changed

Lines changed: 199 additions & 40 deletions

File tree

changelog.d/19479.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): When persisting a delayed event to the timeline, include its `delay_id` in the event's `unsigned` section in `/sync` responses to the event sender.

rust/src/events/internal_metadata.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ enum EventInternalMetadataData {
5757
PolicyServerSpammy(bool),
5858
Redacted(bool),
5959
TxnId(Box<str>),
60+
DelayId(Box<str>),
6061
TokenId(i64),
6162
DeviceId(Box<str>),
6263
}
@@ -115,6 +116,10 @@ impl EventInternalMetadataData {
115116
pyo3::intern!(py, "txn_id"),
116117
o.into_pyobject(py).unwrap_infallible().into_any(),
117118
),
119+
EventInternalMetadataData::DelayId(o) => (
120+
pyo3::intern!(py, "delay_id"),
121+
o.into_pyobject(py).unwrap_infallible().into_any(),
122+
),
118123
EventInternalMetadataData::TokenId(o) => (
119124
pyo3::intern!(py, "token_id"),
120125
o.into_pyobject(py).unwrap_infallible().into_any(),
@@ -179,6 +184,12 @@ impl EventInternalMetadataData {
179184
.map(String::into_boxed_str)
180185
.with_context(|| format!("'{key_str}' has invalid type"))?,
181186
),
187+
"delay_id" => EventInternalMetadataData::DelayId(
188+
value
189+
.extract()
190+
.map(String::into_boxed_str)
191+
.with_context(|| format!("'{key_str}' has invalid type"))?,
192+
),
182193
"token_id" => EventInternalMetadataData::TokenId(
183194
value
184195
.extract()
@@ -472,6 +483,17 @@ impl EventInternalMetadata {
472483
set_property!(self, TxnId, obj.into_boxed_str());
473484
}
474485

486+
/// The delay ID, set only if the event was a delayed event.
487+
#[getter]
488+
fn get_delay_id(&self) -> PyResult<&str> {
489+
let s = get_property!(self, DelayId)?;
490+
Ok(s)
491+
}
492+
#[setter]
493+
fn set_delay_id(&mut self, obj: String) {
494+
set_property!(self, DelayId, obj.into_boxed_str());
495+
}
496+
475497
/// The access token ID of the user who sent this event, if any.
476498
#[getter]
477499
fn get_token_id(&self) -> PyResult<i64> {

synapse/events/utils.py

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ class SerializeEventConfig:
420420
# Function to convert from federation format to client format
421421
event_format: Callable[[JsonDict], JsonDict] = format_event_for_client_v1
422422
# The entity that requested the event. This is used to determine whether to include
423-
# the transaction_id in the unsigned section of the event.
423+
# the transaction_id and delay_id in the unsigned section of the event.
424424
requester: Requester | None = None
425425
# List of event fields to include. If empty, all fields will be returned.
426426
only_event_fields: list[str] | None = None
@@ -483,44 +483,49 @@ def serialize_event(
483483
config=config,
484484
)
485485

486-
# If we have a txn_id saved in the internal_metadata, we should include it in the
487-
# unsigned section of the event if it was sent by the same session as the one
488-
# requesting the event.
489-
txn_id: str | None = getattr(e.internal_metadata, "txn_id", None)
490-
if (
491-
txn_id is not None
492-
and config.requester is not None
493-
and config.requester.user.to_string() == e.sender
494-
):
495-
# Some events do not have the device ID stored in the internal metadata,
496-
# this includes old events as well as those created by appservice, guests,
497-
# or with tokens minted with the admin API. For those events, fallback
498-
# to using the access token instead.
499-
event_device_id: str | None = getattr(e.internal_metadata, "device_id", None)
500-
if event_device_id is not None:
501-
if event_device_id == config.requester.device_id:
502-
d["unsigned"]["transaction_id"] = txn_id
503-
504-
else:
505-
# Fallback behaviour: only include the transaction ID if the event
506-
# was sent from the same access token.
507-
#
508-
# For regular users, the access token ID can be used to determine this.
509-
# This includes access tokens minted with the admin API.
510-
#
511-
# For guests and appservice users, we can't check the access token ID
512-
# so assume it is the same session.
513-
event_token_id: int | None = getattr(e.internal_metadata, "token_id", None)
514-
if (
515-
(
516-
event_token_id is not None
517-
and config.requester.access_token_id is not None
518-
and event_token_id == config.requester.access_token_id
486+
# If we have applicable fields saved in the internal_metadata, include them in the
487+
# unsigned section of the event if the event was sent by the same session (or when
488+
# appropriate, just the same sender) as the one requesting the event.
489+
if config.requester is not None and config.requester.user.to_string() == e.sender:
490+
txn_id: str | None = getattr(e.internal_metadata, "txn_id", None)
491+
if txn_id is not None:
492+
# Some events do not have the device ID stored in the internal metadata,
493+
# this includes old events as well as those created by appservice, guests,
494+
# or with tokens minted with the admin API. For those events, fallback
495+
# to using the access token instead.
496+
event_device_id: str | None = getattr(
497+
e.internal_metadata, "device_id", None
498+
)
499+
if event_device_id is not None:
500+
if event_device_id == config.requester.device_id:
501+
d["unsigned"]["transaction_id"] = txn_id
502+
503+
else:
504+
# Fallback behaviour: only include the transaction ID if the event
505+
# was sent from the same access token.
506+
#
507+
# For regular users, the access token ID can be used to determine this.
508+
# This includes access tokens minted with the admin API.
509+
#
510+
# For guests and appservice users, we can't check the access token ID
511+
# so assume it is the same session.
512+
event_token_id: int | None = getattr(
513+
e.internal_metadata, "token_id", None
519514
)
520-
or config.requester.is_guest
521-
or config.requester.app_service
522-
):
523-
d["unsigned"]["transaction_id"] = txn_id
515+
if (
516+
(
517+
event_token_id is not None
518+
and config.requester.access_token_id is not None
519+
and event_token_id == config.requester.access_token_id
520+
)
521+
or config.requester.is_guest
522+
or config.requester.app_service
523+
):
524+
d["unsigned"]["transaction_id"] = txn_id
525+
526+
delay_id: str | None = getattr(e.internal_metadata, "delay_id", None)
527+
if delay_id is not None:
528+
d["unsigned"]["org.matrix.msc4140.delay_id"] = delay_id
524529

525530
# invite_room_state and knock_room_state are a list of stripped room state events
526531
# that are meant to provide metadata about a room to an invitee/knocker. They are

synapse/handlers/delayed_events.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ async def _send_event(
560560
action=membership,
561561
content=event.content,
562562
origin_server_ts=event.origin_server_ts,
563+
delay_id=event.delay_id,
563564
)
564565
else:
565566
event_dict: JsonDict = {
@@ -585,6 +586,7 @@ async def _send_event(
585586
requester,
586587
event_dict,
587588
txn_id=txn_id,
589+
delay_id=event.delay_id,
588590
)
589591
event_id = sent_event.event_id
590592
except ShadowBanError:

synapse/handlers/message.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ async def create_event(
585585
state_map: StateMap[str] | None = None,
586586
for_batch: bool = False,
587587
current_state_group: int | None = None,
588+
delay_id: str | None = None,
588589
) -> tuple[EventBase, UnpersistedEventContextBase]:
589590
"""
590591
Given a dict from a client, create a new event. If bool for_batch is true, will
@@ -600,7 +601,7 @@ async def create_event(
600601
Args:
601602
requester
602603
event_dict: An entire event
603-
txn_id
604+
txn_id: The transaction ID.
604605
prev_event_ids:
605606
the forward extremities to use as the prev_events for the
606607
new event.
@@ -639,6 +640,8 @@ async def create_event(
639640
current_state_group: the current state group, used only for creating events for
640641
batch persisting
641642
643+
delay_id: The delay ID of this event, if it was a delayed event.
644+
642645
Raises:
643646
ResourceLimitError if server is blocked to some resource being
644647
exceeded
@@ -726,6 +729,9 @@ async def create_event(
726729
if txn_id is not None:
727730
builder.internal_metadata.txn_id = txn_id
728731

732+
if delay_id is not None:
733+
builder.internal_metadata.delay_id = delay_id
734+
729735
builder.internal_metadata.outlier = outlier
730736

731737
event, unpersisted_context = await self.create_new_client_event(
@@ -966,6 +972,7 @@ async def create_and_send_nonmember_event(
966972
ignore_shadow_ban: bool = False,
967973
outlier: bool = False,
968974
depth: int | None = None,
975+
delay_id: str | None = None,
969976
) -> tuple[EventBase, int]:
970977
"""
971978
Creates an event, then sends it.
@@ -994,6 +1001,7 @@ async def create_and_send_nonmember_event(
9941001
depth: Override the depth used to order the event in the DAG.
9951002
Should normally be set to None, which will cause the depth to be calculated
9961003
based on the prev_events.
1004+
delay_id: The delay ID of this event, if it was a delayed event.
9971005
9981006
Returns:
9991007
The event, and its stream ordering (if deduplication happened,
@@ -1090,6 +1098,7 @@ async def create_and_send_nonmember_event(
10901098
ignore_shadow_ban=ignore_shadow_ban,
10911099
outlier=outlier,
10921100
depth=depth,
1101+
delay_id=delay_id,
10931102
)
10941103

10951104
async def _create_and_send_nonmember_event_locked(
@@ -1103,6 +1112,7 @@ async def _create_and_send_nonmember_event_locked(
11031112
ignore_shadow_ban: bool = False,
11041113
outlier: bool = False,
11051114
depth: int | None = None,
1115+
delay_id: str | None = None,
11061116
) -> tuple[EventBase, int]:
11071117
room_id = event_dict["room_id"]
11081118

@@ -1131,6 +1141,7 @@ async def _create_and_send_nonmember_event_locked(
11311141
state_event_ids=state_event_ids,
11321142
outlier=outlier,
11331143
depth=depth,
1144+
delay_id=delay_id,
11341145
)
11351146
context = await unpersisted_context.persist(event)
11361147

synapse/handlers/room_member.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ async def _local_membership_update(
408408
require_consent: bool = True,
409409
outlier: bool = False,
410410
origin_server_ts: int | None = None,
411+
delay_id: str | None = None,
411412
) -> tuple[str, int]:
412413
"""
413414
Internal membership update function to get an existing event or create
@@ -440,6 +441,7 @@ async def _local_membership_update(
440441
opposed to being inline with the current DAG.
441442
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
442443
the current timestamp if set to None.
444+
delay_id: The delay ID of this event, if it was a delayed event.
443445
444446
Returns:
445447
Tuple of event ID and stream ordering position
@@ -492,6 +494,7 @@ async def _local_membership_update(
492494
depth=depth,
493495
require_consent=require_consent,
494496
outlier=outlier,
497+
delay_id=delay_id,
495498
)
496499
context = await unpersisted_context.persist(event)
497500
prev_state_ids = await context.get_prev_state_ids(
@@ -587,6 +590,7 @@ async def update_membership(
587590
state_event_ids: list[str] | None = None,
588591
depth: int | None = None,
589592
origin_server_ts: int | None = None,
593+
delay_id: str | None = None,
590594
) -> tuple[str, int]:
591595
"""Update a user's membership in a room.
592596
@@ -617,6 +621,7 @@ async def update_membership(
617621
based on the prev_events.
618622
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
619623
the current timestamp if set to None.
624+
delay_id: The delay ID of this event, if it was a delayed event.
620625
621626
Returns:
622627
A tuple of the new event ID and stream ID.
@@ -679,6 +684,7 @@ async def update_membership(
679684
state_event_ids=state_event_ids,
680685
depth=depth,
681686
origin_server_ts=origin_server_ts,
687+
delay_id=delay_id,
682688
)
683689

684690
return result
@@ -701,6 +707,7 @@ async def update_membership_locked(
701707
state_event_ids: list[str] | None = None,
702708
depth: int | None = None,
703709
origin_server_ts: int | None = None,
710+
delay_id: str | None = None,
704711
) -> tuple[str, int]:
705712
"""Helper for update_membership.
706713
@@ -733,6 +740,7 @@ async def update_membership_locked(
733740
based on the prev_events.
734741
origin_server_ts: The origin_server_ts to use if a new event is created. Uses
735742
the current timestamp if set to None.
743+
delay_id: The delay ID of this event, if it was a delayed event.
736744
737745
Returns:
738746
A tuple of the new event ID and stream ID.
@@ -943,6 +951,7 @@ async def update_membership_locked(
943951
require_consent=require_consent,
944952
outlier=outlier,
945953
origin_server_ts=origin_server_ts,
954+
delay_id=delay_id,
946955
)
947956

948957
latest_event_ids = await self.store.get_prev_events_for_room(room_id)
@@ -1201,6 +1210,7 @@ async def update_membership_locked(
12011210
require_consent=require_consent,
12021211
outlier=outlier,
12031212
origin_server_ts=origin_server_ts,
1213+
delay_id=delay_id,
12041214
)
12051215

12061216
async def check_for_any_membership_in_room(

synapse/synapse_rust/events.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class EventInternalMetadata:
3838

3939
txn_id: str
4040
"""The transaction ID, if it was set when the event was created."""
41+
delay_id: str
42+
"""The delay ID, set only if the event was a delayed event."""
4143
token_id: int
4244
"""The access token ID of the user who sent this event, if any."""
4345
device_id: str

0 commit comments

Comments
 (0)