Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit db6e7f1

Browse files
Fix backfilled events being rejected for no state_groups (#10439)
Reproducible on a federated homeserver when there is a membership auth event as a floating outlier. Then when we try to backfill one of that persons messages, it has missing membership auth to fetch which caused us to mistakenly replace the `context` for the message with that of the floating membership `outlier` event. Since `outliers` have no `state` or `state_group`, the error bubbles up when we continue down the persisting route: `sqlite3.IntegrityError: NOT NULL constraint failed: event_to_state_groups.state_group` Call stack: ``` backfill _auth_and_persist_event _check_event_auth _update_auth_events_and_context_for_auth ```
1 parent 858363d commit db6e7f1

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

changelog.d/10439.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix events with floating outlier state being rejected over federation.

tests/handlers/test_federation.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import logging
15+
from typing import List
1516
from unittest import TestCase
1617

1718
from synapse.api.constants import EventTypes
@@ -22,6 +23,7 @@
2223
from synapse.logging.context import LoggingContext, run_in_background
2324
from synapse.rest import admin
2425
from synapse.rest.client.v1 import login, room
26+
from synapse.util.stringutils import random_string
2527

2628
from tests import unittest
2729

@@ -39,6 +41,8 @@ def make_homeserver(self, reactor, clock):
3941
hs = self.setup_test_homeserver(federation_http_client=None)
4042
self.handler = hs.get_federation_handler()
4143
self.store = hs.get_datastore()
44+
self.state_store = hs.get_storage().state
45+
self._event_auth_handler = hs.get_event_auth_handler()
4246
return hs
4347

4448
def test_exchange_revoked_invite(self):
@@ -190,6 +194,133 @@ def test_rejected_state_event_state(self):
190194

191195
self.assertEqual(sg, sg2)
192196

197+
def test_backfill_floating_outlier_membership_auth(self):
198+
"""
199+
As the local homeserver, check that we can properly process a federated
200+
event from the OTHER_SERVER with auth_events that include a floating
201+
membership event from the OTHER_SERVER.
202+
203+
Regression test, see #10439.
204+
"""
205+
OTHER_SERVER = "otherserver"
206+
OTHER_USER = "@otheruser:" + OTHER_SERVER
207+
208+
# create the room
209+
user_id = self.register_user("kermit", "test")
210+
tok = self.login("kermit", "test")
211+
room_id = self.helper.create_room_as(
212+
room_creator=user_id,
213+
is_public=True,
214+
tok=tok,
215+
extra_content={
216+
"preset": "public_chat",
217+
},
218+
)
219+
room_version = self.get_success(self.store.get_room_version(room_id))
220+
221+
prev_event_ids = self.get_success(self.store.get_prev_events_for_room(room_id))
222+
(
223+
most_recent_prev_event_id,
224+
most_recent_prev_event_depth,
225+
) = self.get_success(self.store.get_max_depth_of(prev_event_ids))
226+
# mapping from (type, state_key) -> state_event_id
227+
prev_state_map = self.get_success(
228+
self.state_store.get_state_ids_for_event(most_recent_prev_event_id)
229+
)
230+
# List of state event ID's
231+
prev_state_ids = list(prev_state_map.values())
232+
auth_event_ids = prev_state_ids
233+
auth_events = list(
234+
self.get_success(self.store.get_events(auth_event_ids)).values()
235+
)
236+
237+
# build a floating outlier member state event
238+
fake_prev_event_id = "$" + random_string(43)
239+
member_event_dict = {
240+
"type": EventTypes.Member,
241+
"content": {
242+
"membership": "join",
243+
},
244+
"state_key": OTHER_USER,
245+
"room_id": room_id,
246+
"sender": OTHER_USER,
247+
"depth": most_recent_prev_event_depth,
248+
"prev_events": [fake_prev_event_id],
249+
"origin_server_ts": self.clock.time_msec(),
250+
"signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
251+
}
252+
builder = self.hs.get_event_builder_factory().for_room_version(
253+
room_version, member_event_dict
254+
)
255+
member_event = self.get_success(
256+
builder.build(
257+
prev_event_ids=member_event_dict["prev_events"],
258+
auth_event_ids=self._event_auth_handler.compute_auth_events(
259+
builder,
260+
prev_state_map,
261+
for_verification=False,
262+
),
263+
depth=member_event_dict["depth"],
264+
)
265+
)
266+
# Override the signature added from "test" homeserver that we created the event with
267+
member_event.signatures = member_event_dict["signatures"]
268+
269+
# Add the new member_event to the StateMap
270+
prev_state_map[
271+
(member_event.type, member_event.state_key)
272+
] = member_event.event_id
273+
auth_events.append(member_event)
274+
275+
# build and send an event authed based on the member event
276+
message_event_dict = {
277+
"type": EventTypes.Message,
278+
"content": {},
279+
"room_id": room_id,
280+
"sender": OTHER_USER,
281+
"depth": most_recent_prev_event_depth,
282+
"prev_events": prev_event_ids.copy(),
283+
"origin_server_ts": self.clock.time_msec(),
284+
"signatures": {OTHER_SERVER: {"ed25519:key_version": "SomeSignatureHere"}},
285+
}
286+
builder = self.hs.get_event_builder_factory().for_room_version(
287+
room_version, message_event_dict
288+
)
289+
message_event = self.get_success(
290+
builder.build(
291+
prev_event_ids=message_event_dict["prev_events"],
292+
auth_event_ids=self._event_auth_handler.compute_auth_events(
293+
builder,
294+
prev_state_map,
295+
for_verification=False,
296+
),
297+
depth=message_event_dict["depth"],
298+
)
299+
)
300+
# Override the signature added from "test" homeserver that we created the event with
301+
message_event.signatures = message_event_dict["signatures"]
302+
303+
# Stub the /event_auth response from the OTHER_SERVER
304+
async def get_event_auth(
305+
destination: str, room_id: str, event_id: str
306+
) -> List[EventBase]:
307+
return auth_events
308+
309+
self.handler.federation_client.get_event_auth = get_event_auth
310+
311+
with LoggingContext("receive_pdu"):
312+
# Fake the OTHER_SERVER federating the message event over to our local homeserver
313+
d = run_in_background(
314+
self.handler.on_receive_pdu, OTHER_SERVER, message_event
315+
)
316+
self.get_success(d)
317+
318+
# Now try and get the events on our local homeserver
319+
stored_event = self.get_success(
320+
self.store.get_event(message_event.event_id, allow_none=True)
321+
)
322+
self.assertTrue(stored_event is not None)
323+
193324
@unittest.override_config(
194325
{"rc_invites": {"per_user": {"per_second": 0.5, "burst_count": 3}}}
195326
)

0 commit comments

Comments
 (0)