Skip to content

Commit 20c9de3

Browse files
authored
MSC4242: State DAGs (#464)
Add enough MSC4242 to make it possible to write Complement tests.
1 parent ffcbbd3 commit 20c9de3

9 files changed

Lines changed: 121 additions & 8 deletions

File tree

eventV1.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ func (e *eventV1) AuthEventIDs() []string {
144144
return result
145145
}
146146

147+
func (e *eventV1) PrevStateEventIDs() []string {
148+
panic("not implemented in this room version")
149+
}
150+
147151
func (e *eventV1) OriginServerTS() spec.Timestamp {
148152
return e.eventFields.OriginServerTS
149153
}

eventV2.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import (
1515

1616
type eventV2 struct {
1717
eventV1
18-
PrevEvents []string `json:"prev_events"`
19-
AuthEvents []string `json:"auth_events"`
18+
PrevEvents []string `json:"prev_events"`
19+
AuthEvents []string `json:"auth_events"`
20+
PrevStateEvents []string `json:"prev_state_events"`
2021
}
2122

2223
func (e *eventV2) PrevEventIDs() []string {
@@ -27,6 +28,10 @@ func (e *eventV2) AuthEventIDs() []string {
2728
return e.AuthEvents
2829
}
2930

31+
func (e *eventV2) PrevStateEventIDs() []string {
32+
return e.PrevStateEvents
33+
}
34+
3035
// MarshalJSON implements json.Marshaller
3136
func (e *eventV2) MarshalJSON() ([]byte, error) {
3237
if e.eventJSON == nil {
@@ -185,6 +190,8 @@ func newEventFromUntrustedJSONV2(eventJSON []byte, roomVersion IRoomVersion) (PD
185190
return res, err
186191
}
187192

193+
// This has to exist outside the RoomVersion interface due to init cycles caused
194+
// when you try to MustGetRoomVersion inside CheckFields. Ideally we'd refactor that...
188195
var lenientByteLimitRoomVersions = map[RoomVersion]struct{}{
189196
RoomVersionV1: {},
190197
RoomVersionV2: {},
@@ -204,10 +211,24 @@ var lenientByteLimitRoomVersions = map[RoomVersion]struct{}{
204211
"org.matrix.msc3667": {},
205212
}
206213

214+
var stateDAGRoomVersions = map[RoomVersion]struct{}{
215+
RoomVersionStateDAGs: {},
216+
}
217+
207218
func CheckFields(input PDU) error { // nolint: gocyclo
208-
if input.AuthEventIDs() == nil || input.PrevEventIDs() == nil {
209-
return errors.New("gomatrixserverlib: auth events and prev events must not be nil")
219+
// don't check auth event IDs in auth DAG rooms as it doesn't exist.
220+
_, stateDAGs := stateDAGRoomVersions[input.Version()]
221+
if !stateDAGs && input.AuthEventIDs() == nil {
222+
return errors.New("gomatrixserverlib: auth events must not be nil")
210223
}
224+
if input.PrevEventIDs() == nil {
225+
return errors.New("gomatrixserverlib: prev events must not be nil")
226+
}
227+
if stateDAGs && input.PrevStateEventIDs() == nil {
228+
// create event should be []
229+
return errors.New("gomatrixserverlib: prev_state_events must not be nil")
230+
}
231+
211232
if l := len(input.JSON()); l > maxEventLength {
212233
return EventValidationError{
213234
Code: EventValidationTooLarge,

eventV2_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func TestCheckFields(t *testing.T) {
146146
for _, tt := range tests {
147147
t.Run(tt.name, func(t *testing.T) {
148148
for roomVersion := range roomVersionMeta {
149-
if roomVersion == RoomVersionPseudoIDs {
149+
if roomVersion == RoomVersionPseudoIDs || roomVersion == RoomVersionStateDAGs {
150150
continue
151151
}
152152
t.Run(tt.name+"-"+string(roomVersion), func(t *testing.T) {

event_builder.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ type EventBuilder struct {
2929
// The events needed to authenticate this event. This can be
3030
// either []eventReference for room v1/v2, and []string for room v3 onwards.
3131
AuthEvents interface{} `json:"auth_events"`
32+
// Previous state events, for MSC4242 state dag rooms only. Pointer to allow [] to encode for the create event.
33+
PrevStateEvents *[]string `json:"prev_state_events,omitempty"`
3234
// The event ID of the event being redacted if this event is a "m.room.redaction".
3335
Redacts string `json:"redacts,omitempty"`
3436
// The depth of the event, This should be one greater than the maximum depth of the previous events.
@@ -145,6 +147,10 @@ func (eb *EventBuilder) Build(
145147
if eb.version == nil {
146148
return nil, fmt.Errorf("EventBuilder.Build: unknown version, did you create this via NewEventBuilder?")
147149
}
150+
isStateDAGs := eb.version.StateDAGs()
151+
if !isStateDAGs && eb.PrevStateEvents != nil {
152+
return nil, fmt.Errorf("prev_state_events can only be set on RoomVersionStateDAGs")
153+
}
148154

149155
eventFormat := eb.version.EventFormat()
150156
eventIDFormat := eb.version.EventIDFormat()
@@ -213,6 +219,12 @@ func (eb *EventBuilder) Build(
213219
}
214220
}
215221

222+
if isStateDAGs {
223+
if eventJSON, err = sjson.DeleteBytes(eventJSON, "auth_events"); err != nil {
224+
return
225+
}
226+
}
227+
216228
if eventJSON, err = addContentHashesToEvent(eventJSON); err != nil {
217229
return
218230
}

eventversion.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type IRoomVersion interface {
3838
CheckCreateEvent(event PDU, sender spec.UserID, knownRoomVersion KnownRoomVersionFunc) error
3939
DomainlessRoomIDs() bool
4040
PrivilegedCreators() bool
41+
StateDAGs() bool
4142
}
4243

4344
type KnownRoomVersionFunc func(RoomVersion) bool
@@ -69,6 +70,7 @@ const (
6970
RoomVersionV12 RoomVersion = "12"
7071
RoomVersionPseudoIDs RoomVersion = "org.matrix.msc4014"
7172
RoomVersionHydra RoomVersion = "org.matrix.hydra.11"
73+
RoomVersionStateDAGs RoomVersion = "org.matrix.msc4242.12"
7274
)
7375

7476
// Event format constants.
@@ -417,6 +419,31 @@ var roomVersionMeta = map[RoomVersion]IRoomVersion{
417419
domainlessRoomID: true,
418420
privilegedCreators: true,
419421
},
422+
// Based on v12
423+
RoomVersionStateDAGs: RoomVersionImpl{
424+
ver: RoomVersionStateDAGs,
425+
stable: true,
426+
stateResAlgorithm: StateResV2_1,
427+
eventFormat: EventFormatV2,
428+
eventIDFormat: EventIDFormatV3,
429+
redactionAlgorithm: redactEventJSONVStateDAGs,
430+
signatureValidityCheckFunc: StrictValiditySignatureCheck,
431+
canonicalJSONCheck: verifyEnforcedCanonicalJSON,
432+
checkPowerLevelEvent: checkPowerLevelEventV3,
433+
restrictedJoinServernameFunc: extractAuthorisedViaServerName,
434+
checkRestrictedJoin: checkRestrictedJoin,
435+
parsePowerLevelsFunc: parseIntegerPowerLevels,
436+
checkKnockingAllowedFunc: checkKnocking,
437+
checkRestrictedJoinAllowedFunc: allowRestrictedJoins,
438+
checkCreateEvent: checkCreateEventV3,
439+
// v3 versions relax the room ID check as the room ID has no domain now.
440+
newEventFromUntrustedJSONFunc: newEventFromUntrustedJSONV3,
441+
newEventFromTrustedJSONFunc: newEventFromTrustedJSONV3,
442+
newEventFromTrustedJSONWithEventIDFunc: newEventFromTrustedJSONWithEventIDV3,
443+
domainlessRoomID: true,
444+
privilegedCreators: true,
445+
stateDAGs: true,
446+
},
420447
}
421448

422449
// RoomVersions returns information about room versions currently
@@ -501,7 +528,9 @@ type RoomVersionImpl struct {
501528
// whether auth_events should include the create event
502529
domainlessRoomID bool
503530
// creators have infinite PL
504-
privilegedCreators bool
531+
privilegedCreators bool
532+
// Events form two graphs, a state DAG and an event DAG.
533+
stateDAGs bool
505534
checkRestrictedJoin func(ctx context.Context, localServerName spec.ServerName, roomQuerier RestrictedRoomJoinQuerier, roomID spec.RoomID, senderID spec.SenderID, privilegedCreators bool) (string, error)
506535
restrictedJoinServernameFunc func(content []byte) (spec.ServerName, error)
507536
checkRestrictedJoinAllowedFunc func() error
@@ -525,6 +554,10 @@ func (v RoomVersionImpl) DomainlessRoomIDs() bool {
525554
return v.domainlessRoomID
526555
}
527556

557+
func (v RoomVersionImpl) StateDAGs() bool {
558+
return v.stateDAGs
559+
}
560+
528561
func (v RoomVersionImpl) PrivilegedCreators() bool {
529562
return v.privilegedCreators
530563
}
@@ -630,6 +663,7 @@ func (v RoomVersionImpl) NewEventBuilderFromProtoEvent(pe *ProtoEvent) *EventBui
630663
eb.StateKey = pe.StateKey
631664
eb.Type = pe.Type
632665
eb.Unsigned = pe.Unsigned
666+
eb.PrevStateEvents = pe.PrevStateEvents
633667
return eb
634668
}
635669

fclient/federationtypes.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type MissingEvents struct {
9090
EarliestEvents []string `json:"earliest_events"`
9191
// The event IDs to retrieve the previous events for.
9292
LatestEvents []string `json:"latest_events"`
93+
// If true, walks the state DAG. Only for MSC4242 State DAG rooms.
94+
StateDAG bool `json:"org.matrix.msc4242.state_dag"`
9395
}
9496

9597
// A RespMissingEvents is the content of a response to GET /_matrix/federation/v1/get_missing_events/{roomID}
@@ -269,6 +271,8 @@ type RespSendJoin struct {
269271
StateEvents gomatrixserverlib.EventJSONs `json:"state"`
270272
// A list of events needed to authenticate the state events.
271273
AuthEvents gomatrixserverlib.EventJSONs `json:"auth_chain"`
274+
// MSC4242: The entire state DAG for the room
275+
StateDAG gomatrixserverlib.EventJSONs `json:"state_dag"`
272276
// The server that originated the event.
273277
Origin spec.ServerName `json:"origin"`
274278
// The returned join event from the remote server. Used for restricted joins,
@@ -318,6 +322,9 @@ func (r RespSendJoin) MarshalJSON() ([]byte, error) {
318322
if len(fields.StateEvents) == 0 {
319323
fields.StateEvents = gomatrixserverlib.EventJSONs{}
320324
}
325+
if len(r.StateDAG) > 0 {
326+
fields.StateDAG = r.StateDAG
327+
}
321328

322329
if !r.MembersOmitted {
323330
return json.Marshal(fields)
@@ -350,6 +357,7 @@ type RespMakeKnock struct {
350357
type respSendJoinFields struct {
351358
StateEvents gomatrixserverlib.EventJSONs `json:"state"`
352359
AuthEvents gomatrixserverlib.EventJSONs `json:"auth_chain"`
360+
StateDAG gomatrixserverlib.EventJSONs `json:"state_dag,omitempty"`
353361
Origin spec.ServerName `json:"origin"`
354362
Event spec.RawJSON `json:"event,omitempty"`
355363
}

join.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ type ProtoEvent struct {
4343
PrevEvents interface{} `json:"prev_events"`
4444
// The events needed to authenticate this event. This can be
4545
// either []eventReference for room v1/v2, and []string for room v3 onwards.
46-
AuthEvents interface{} `json:"auth_events"`
46+
AuthEvents interface{} `json:"auth_events,omitempty"`
47+
// Previous state events, for MSC4242 state dag rooms. Pointer to allow [] to encode for the create event.
48+
PrevStateEvents *[]string `json:"prev_state_events,omitempty"`
4749
// The event ID of the event being redacted if this event is a "m.room.redaction".
4850
Redacts string `json:"redacts,omitempty"`
4951
// The depth of the event, This should be one greater than the maximum depth of the previous events.

pdu.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type PDU interface {
5353
JSON() []byte // TODO: remove
5454
AuthEventIDs() []string // TODO: remove
5555
ToHeaderedJSON() ([]byte, error) // TODO: remove
56+
PrevStateEventIDs() []string
5657
// IsSticky returns true if the event is *currently* considered "sticky" given the received time.
5758
// Sticky events are annotated as sticky and carry strong delivery guarantees to clients (and
5859
// therefore servers). `received` should be specified as the time the event was received by the

redactevent.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func redactEventJSONV1(eventJSON []byte) ([]byte, error) {
141141
}
142142

143143
type unredactableEvent interface {
144-
*unredactableEventFieldsV1 | *unredactableEventFieldsV2
144+
*unredactableEventFieldsV1 | *unredactableEventFieldsV2 | *unredactableEventFieldsVStateDAGs
145145
GetType() string
146146
GetContent() map[string]interface{}
147147
SetContent(map[string]interface{})
@@ -172,3 +172,34 @@ func redactEventJSON[T unredactableEvent](eventJSON []byte, unredactableEvent T,
172172
// Return the redacted event encoded as JSON.
173173
return json.Marshal(&unredactableEvent)
174174
}
175+
176+
type unredactableEventFieldsVStateDAGs struct {
177+
EventID spec.RawJSON `json:"event_id,omitempty"`
178+
Type string `json:"type"`
179+
RoomID spec.RawJSON `json:"room_id,omitempty"`
180+
Sender spec.RawJSON `json:"sender,omitempty"`
181+
StateKey spec.RawJSON `json:"state_key,omitempty"`
182+
Content map[string]interface{} `json:"content"`
183+
Hashes spec.RawJSON `json:"hashes,omitempty"`
184+
Signatures spec.RawJSON `json:"signatures,omitempty"`
185+
Depth spec.RawJSON `json:"depth,omitempty"`
186+
PrevEvents spec.RawJSON `json:"prev_events,omitempty"`
187+
PrevStateEvents spec.RawJSON `json:"prev_state_events,omitempty"`
188+
OriginServerTS spec.RawJSON `json:"origin_server_ts,omitempty"`
189+
}
190+
191+
func (u *unredactableEventFieldsVStateDAGs) GetType() string {
192+
return u.Type
193+
}
194+
195+
func (u *unredactableEventFieldsVStateDAGs) GetContent() map[string]interface{} {
196+
return u.Content
197+
}
198+
199+
func (u *unredactableEventFieldsVStateDAGs) SetContent(content map[string]interface{}) {
200+
u.Content = content
201+
}
202+
203+
func redactEventJSONVStateDAGs(eventJSON []byte) ([]byte, error) {
204+
return redactEventJSON(eventJSON, &unredactableEventFieldsVStateDAGs{}, unredactableContentFieldsV5)
205+
}

0 commit comments

Comments
 (0)