Skip to content

Commit 529b1da

Browse files
authored
Merge pull request #134 from matrix-org/poljar/notification-duplicate-olm-machine-fix
Add TestNotificationClientDupeOTKUpload
2 parents 45a7c16 + 2a837f9 commit 529b1da

File tree

2 files changed

+40
-41
lines changed

2 files changed

+40
-41
lines changed

internal/api/rust/rust.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type RustRoomInfo struct {
4949

5050
type RustClient struct {
5151
FFIClient *matrix_sdk_ffi.Client
52+
syncService *matrix_sdk_ffi.SyncService
5253
roomsListener *RoomsListener
5354
allRooms *matrix_sdk_ffi.RoomList
5455
rooms map[string]*RustRoomInfo
@@ -57,7 +58,7 @@ type RustClient struct {
5758
persistentStoragePath string
5859
opts api.ClientCreationOpts
5960

60-
// for NSE tests
61+
// for push notification tests (single/multi-process)
6162
notifClient *matrix_sdk_ffi.NotificationClient
6263
}
6364

@@ -132,9 +133,14 @@ func (c *RustClient) Opts() api.ClientCreationOpts {
132133

133134
func (c *RustClient) GetNotification(t ct.TestLike, roomID, eventID string) (*api.Notification, error) {
134135
if c.notifClient == nil {
135-
t.Errorf("RustClient misconfigured. You can only call GetNotification if this is an NSE process. " +
136-
"Ensure opts.EnableCrossProcessRefreshLockProcessName and opts.AccessToken are set!")
137-
return nil, fmt.Errorf("misconfigured rust client")
136+
var err error
137+
c.Logf(t, "creating NotificationClient")
138+
c.notifClient, err = c.FFIClient.NotificationClient(matrix_sdk_ffi.NotificationProcessSetupSingleProcess{
139+
SyncService: c.syncService,
140+
})
141+
if err != nil {
142+
return nil, fmt.Errorf("GetNotification: failed to create NotificationClient: %s", err)
143+
}
138144
}
139145
notifItem, err := c.notifClient.GetNotification(roomID, eventID)
140146
if err != nil {
@@ -350,6 +356,7 @@ func (c *RustClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error)
350356
}
351357
go syncService.Start()
352358
c.allRooms = roomList
359+
c.syncService = syncService
353360
// track new rooms when they are made
354361
allRoomsListener := newGenericStateListener[[]matrix_sdk_ffi.RoomListEntriesUpdate]()
355362
go func() {

tests/notification_test.go

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package tests
33
import (
44
"fmt"
55
"math/rand"
6-
"sync"
76
"testing"
87
"time"
98

@@ -535,35 +534,42 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) {
535534
})
536535
}
537536

538-
func TestMultiprocessDupeOTKUpload(t *testing.T) {
537+
// Test that the notification client doesn't cause duplicate OTK uploads.
538+
// Regression test for https://github.com/matrix-org/matrix-rust-sdk/issues/1415
539+
//
540+
// This test creates a normal Alice rust client and lets it upload OTKs. It then:
541+
// - hooks into /keys/upload requests and artificially delays them by adding 4s of latency
542+
// - creates a Bob client who sends a message to Alice, consuming 1 OTK in the process
543+
// - immediately calls GetNotification on Bob's event as soon as it 200 OKs, which creates
544+
// a NotificationClient inside the same process.
545+
//
546+
// This means there are 2 sync loops: the main Client and the NotificationClient. Both clients
547+
// will see the OTK count being lowered so both may try to upload a new OTK. Because we are
548+
// delaying upload requests by 4s, this increases the chance of both uploads being in-flight at
549+
// the same time. If they do not synchronise this operation, they will both try to upload
550+
// _different keys_ with the _same_ key ID, which causes synapse to HTTP 400 with:
551+
//
552+
// > One time key signed_curve25519:AAAAAAAAADI already exists
553+
//
554+
// Which will fail the test.
555+
func TestNotificationClientDupeOTKUpload(t *testing.T) {
539556
if !Instance().ShouldTest(api.ClientTypeRust) {
540557
t.Skipf("rust only")
541558
return
542559
}
543-
t.Skipf("skipped until it is no longer flakey")
544560
tc, roomID := createAndJoinRoom(t)
545561

546562
// start the "main" app
547563
alice := tc.MustLoginClient(t, &cc.ClientCreationRequest{
548564
User: tc.Alice,
549565
Opts: api.ClientCreationOpts{
550-
PersistentStorage: true,
551-
EnableCrossProcessRefreshLockProcessName: "main",
566+
PersistentStorage: true,
552567
},
553568
})
569+
stopSyncing := alice.MustStartSyncing(t)
570+
defer stopSyncing()
554571
aliceAccessToken := alice.Opts().AccessToken
555572

556-
// prep nse process
557-
nseAlice := tc.MustCreateClient(t, &cc.ClientCreationRequest{
558-
User: tc.Alice,
559-
Multiprocess: true,
560-
Opts: api.ClientCreationOpts{
561-
PersistentStorage: true,
562-
EnableCrossProcessRefreshLockProcessName: api.ProcessNameNSE,
563-
AccessToken: aliceAccessToken,
564-
},
565-
})
566-
567573
aliceUploadedNewKeys := false
568574
// artificially slow down the HTTP responses, such that we will potentially have 2 in-flight /keys/upload requests
569575
// at once. If the NSE and main apps are talking to each other, they should be using the same key ID + key.
@@ -588,35 +594,21 @@ func TestMultiprocessDupeOTKUpload(t *testing.T) {
588594
return nil
589595
},
590596
}, func() {
591-
var eventID string
592597
// Bob appears and sends a message, causing Bob to claim one of Alice's OTKs.
593598
// The main app will see this in /sync and then try to upload another OTK, which we will tarpit.
594599
tc.WithClientSyncing(t, &cc.ClientCreationRequest{
595600
User: tc.Bob,
596601
}, func(bob api.Client) {
597-
eventID = bob.SendMessage(t, roomID, "Hello world!")
598-
})
599-
var wg sync.WaitGroup
600-
wg.Add(2)
601-
go func() { // nse process
602-
defer wg.Done()
603-
// wake up NSE process as if it got a push notification. Calling this function
604-
// should cause the NSE process to upload a OTK as it would have seen 1 has been used.
605-
// The NSE and main app must talk to each other to ensure they use the same key.
606-
nseAlice.Logf(t, "GetNotification %s, %s", roomID, eventID)
607-
notif, err := nseAlice.GetNotification(t, roomID, eventID)
602+
eventID := bob.SendMessage(t, roomID, "Hello world!")
603+
// create a NotificationClient in the same process to fetch this "push notification".
604+
// It might make the NotificationClient upload a OTK as it would have seen 1 has been used.
605+
// The NotificationClient and main Client must talk to each other to ensure they use the same key.
606+
alice.Logf(t, "GetNotification %s, %s", roomID, eventID)
607+
notif, err := alice.GetNotification(t, roomID, eventID)
608608
must.NotError(t, "failed to get notification", err)
609609
must.Equal(t, notif.Text, "Hello world!", "failed to decrypt msg body")
610610
must.Equal(t, notif.FailedToDecrypt, false, "FailedToDecrypt but we should be able to decrypt")
611-
}()
612-
go func() { // app process
613-
defer wg.Done()
614-
stopSyncing := alice.MustStartSyncing(t)
615-
// let alice upload new OTK
616-
time.Sleep(5 * time.Second)
617-
stopSyncing()
618-
}()
619-
wg.Wait()
611+
})
620612
})
621613
if !aliceUploadedNewKeys {
622614
t.Errorf("Alice did not upload new OTKs")

0 commit comments

Comments
 (0)