mirror of
https://codefloe.com/pat-s/zendrite.git
synced 2026-11-02 21:16:42 +00:00
[Pull Mirror] An opinionated fork of element-hq/dendrite
https://zendrite.pat-s.me
- Go 99%
- Shell 0.4%
- Astro 0.2%
- JavaScript 0.1%
- Go Template 0.1%
- Other 0.1%
Follow-up to #563, #564 and #565. ## Problem On matrix.devxy.io the state key repair finished (59 events in 7 rooms, 42 memberships and 92 messages accepted in the Codeberg room), but the Codeberg room still holds 94 rejected messages and 13 rejected memberships with state since event NID 700000. A copy of the room's roomserver rows, run locally, shows three causes. 1. **Partial state resolution.** The message re-check resolved the sender's accepted membership against only the state entries the message's auth needs. State resolution checks the conflicting events' own auth against the partially resolved state, which then has no join rules, so it refused the sender's restricted join and kept their old leave. Example: `@wolftune`'s messages after NID 807849 hold his 2025 leave in the state before them. Resolved over the full state, his join wins. 2. **Pass scope.** A rejected message was re-checked only in the pass that accepted its sender's membership. 807849, whose prev event is the accepted join itself, passes, but was never checked again. 3. **Stale snapshots.** A restricted join authorized by a user whose own join the repair accepted was checked against a snapshot without that join (817139 via `@zwopdrek`, 839725 via `@pepperonijail77`). The same applies to joins that only update a member's profile and cite their own earlier join. ## Changes - `roomserver/state/state.go`: `StateAfterEventsWith` loads the full state after the given events, adds the given entries, settles keys held more than once with state resolution over the full state, and returns the requested tuples. - `roomserver/internal/input/input_state_key_repair.go`: - `stateBeforeForAuth` builds the state before a rejected event from the full state after its prev events, with the memberships among its accepted auth events added. A later leave or ban of the same user still wins through state resolution. - The message re-check considers every rejected timeline event, not only those citing a membership accepted in the same pass; the auth checks decide. - The membership re-check takes no repaired events to depend on when called with nil, and then checks every rejected membership. - `acceptRecheckedEvents` holds the accept logic the repair and the new pass share. - `roomserver/internal/input/input_rejected_recheck.go`: `RecheckRejectedEvents` runs at startup after the state key repair, room by room, in one room transaction per room. It accepts what passes, adds accepted memberships that also pass against the current state to it, updates the membership table, announces the memberships as `OutputNewRoomEvent` and the timeline events as `OutputOldRoomEvent` (redactions applied), as the repair does. Progress is recorded per room, and each room with accepted events is logged at warning level. - `roomserver/storage/{postgres,sqlite3}/rejected_recheck_table.go`, `deltas/20261103120000_rejected_recheck.go`, `shared/rejected_recheck.go`, `interface.go`, `tables/interface.go`: a `roomserver_rejected_recheck` cursor table, a migration that schedules the pass once, and listing the rooms with rejected events that have state. `RoomIDFromNID` is added to `RoomDatabase`. - `roomserver/internal/api.go`: run the pass between `RepairStateKeys` and `RepairResyncedRooms`. - `setup/ha`: contract level 20, `surface.txt` and `levels.json` regenerated on PostgreSQL 18. The table is additive, so `upgrade_from` and `rollback_to` stay at 1. - `roomserver/rejected_recheck_test.go`: a restricted room left in the shape found on matrix.devxy.io: a member's two messages after his accepted rejoin, with his leave in the state stored before them, and a restricted join authorized by a user whose join is missing from the state stored before it, all rejected. After the pass all three must be accepted and the messages announced. A message from a user banned before it, citing her earlier join, must stay rejected. ## Testing - `go test -tags goolm ./roomserver/... ./setup/ha/... ./federationapi/... ./syncapi/...` passes against SQLite and PostgreSQL 18. - `TestRecheckRejectedEvents` and `TestRepairStateKeys` pass five runs in a row on both backends. - Without adding the cited memberships, `TestRecheckRejectedEvents` fails. - Against a local copy of the Codeberg room's roomserver rows from matrix.devxy.io, the pass took 25 s and accepted 13 memberships and 1337 timeline events; 87 rejected events with state remain (49 messages, 6 memberships, the rest other state events). Without the cited memberships it accepts 186 events and no membership. The 13 memberships are 2 restricted joins and 11 joins that cite the sender's own earlier accepted join (profile changes), some of them chained. ## Behaviour change On the first start after upgrading, the roomserver checks the rejected memberships and timeline events of every room once more and accepts those that pass. On matrix.devxy.io this restores more than a thousand older messages in the Codeberg room and messages of long-time members in other rooms such as the Forgejo room, which appear in the room history, and replies to them load again. Many of them were rejected because the sender's join was stored as an outlier and never entered the room state; the pass accepts their messages, but does not add such joins to the current state. Rejected state events other than memberships, outliers and redactions are left alone, and rooms in partial state are skipped. Reviewed-on: https://codefloe.com/pat-s/zendrite/pulls/566 |
||
|---|---|---|
| .crow | ||
| .forgejo | ||
| .gitsv | ||
| appservice | ||
| build | ||
| clientapi | ||
| cmd | ||
| federationapi | ||
| internal | ||
| mediaapi | ||
| relayapi | ||
| roomserver | ||
| scripts | ||
| setup | ||
| syncapi | ||
| test | ||
| userapi | ||
| website | ||
| .commitlintrc.json | ||
| .dockerignore | ||
| .editorconfig | ||
| .editorconfig-checker.json | ||
| .gitignore | ||
| .golangci.yaml | ||
| .hadolint.yaml | ||
| .markdownlint.yaml | ||
| .pre-commit-config.yaml | ||
| .prettierignore | ||
| .prettierrc.json | ||
| .trivyignore | ||
| .yamllint.yaml | ||
| are-we-synapse-yet.list | ||
| build-zendritejs.sh | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| justfile | ||
| LICENSE | ||
| LICENSE-COMMERCIAL | ||
| README.md | ||
| renovate.json | ||
| sytest-blacklist | ||
| test-zendritejs.sh | ||
| zendrite-sample.yaml | ||
Zendrite
Zendrite is a second-generation Matrix homeserver written in Go, designed to be efficient, reliable, and scalable.
Forked from element-hq/dendrite in February 2026
The existing Dendrite Matrix rooms are used for chatting:
Getting Started
See the documentation site for installation, administration, and development guides.
Downloads
- Container images:
- Binaries: Releases
The first public release of this fork is v0.15.3.
Migrating from Dendrite
If you are upgrading from an existing Dendrite installation, see the migration guide for details on config, API endpoint, and JetStream changes.
License
Copyright 2017 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2017-2025 New Vector Ltd
This software is dual-licensed by New Vector Ltd (Element):
- GNU AGPL v3 (or later); or
- A paid-for Element Commercial License (terms may vary).