From dbed69d1551e3fc9b29a125a36f97cc3fa8af6f3 Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Mon, 10 Jun 2024 12:02:42 -0300 Subject: [PATCH] perf(login)!: Set-up messenger filters outside login flow (#5229) Fixes the slow login in mobile devices when users have joined large communities, such as the Status one. A user would get stuck for almost 20s in some devices. We identified that the step to set-up filters in the messenger is potentially expensive and that it is not critical to happen before the node.login signal is emitted. The solution presented in this PR is to set-up filters inside messenger.Start(), which the client already calls immediately after login. With this change, users of the mobile app can login pretty fast even when they joined large communities. They can immediately interact with other parts of the app even if filter initialization is running in the background, like Wallet, Activity Center, Settings, and Profile. Breaking changes: in the mobile repository, we had to change where the endpoint wakuext_startMessenger was called and the order of a few events to process chats. So essentially ordering, but no data changes. - Root issue https://github.com/status-im/status-mobile/issues/20059 - Related mobile PR https://github.com/status-im/status-mobile/pull/20173 --- cmd/statusd/main.go | 20 ++++++++---- protocol/messenger.go | 41 +++++++++++++++--------- protocol/messenger_builder_test.go | 7 +++- protocol/messenger_identity.go | 2 ++ protocol/messenger_installations_test.go | 22 +++++++++++++ protocol/messenger_test.go | 4 +-- services/ext/service.go | 6 +++- 7 files changed, 75 insertions(+), 27 deletions(-) diff --git a/cmd/statusd/main.go b/cmd/statusd/main.go index b084c89a9..ecfd99ee7 100644 --- a/cmd/statusd/main.go +++ b/cmd/statusd/main.go @@ -287,9 +287,15 @@ func main() { return } - err = messenger.Init() + err = messenger.InitInstallations() if err != nil { - logger.Error("failed to init messenger", "error", err) + logger.Error("failed to init messenger installations", "error", err) + return + } + + err = messenger.InitFilters() + if err != nil { + logger.Error("failed to init messenger filters", "error", err) return } @@ -424,11 +430,11 @@ func printUsage() { usage := ` Usage: statusd [options] Examples: - statusd # run regular Whisper node that joins Status network - statusd -c ./default.json # run node with configuration specified in ./default.json file - statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file - statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call - statusd -c ./default.json -log DEBUG --seed-phrase="test test test test test test test test test test test junk" --password=password # run node with configuration specified in ./default.json file, and import account with seed phrase and password + statusd # run regular Whisper node that joins Status network + statusd -c ./default.json # run node with configuration specified in ./default.json file + statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file + statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call + statusd -c ./default.json -log DEBUG --seed-phrase="test test test test test test test test test test test junk" --password=password # run node with configuration specified in ./default.json file, and import account with seed phrase and password Options: ` diff --git a/protocol/messenger.go b/protocol/messenger.go index 232610ef0..6f36a579f 100644 --- a/protocol/messenger.go +++ b/protocol/messenger.go @@ -726,6 +726,11 @@ func (m *Messenger) Start() (*MessengerResponse, error) { } m.started = true + err := m.InitFilters() + if err != nil { + return nil, err + } + now := time.Now().UnixMilli() if err := m.settings.CheckAndDeleteExpiredKeypairsAndAccounts(uint64(now)); err != nil { return nil, err @@ -1663,9 +1668,27 @@ func (m *Messenger) handlePushNotificationClientRegistrations(c chan struct{}) { }() } -// Init analyzes chats and contacts in order to setup filters +func (m *Messenger) InitInstallations() error { + installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey) + if err != nil { + return err + } + + for _, installation := range installations { + m.allInstallations.Store(installation.ID, installation) + } + + err = m.setInstallationHostname() + if err != nil { + return err + } + + return nil +} + +// InitFilters analyzes chats and contacts in order to setup filters // which are responsible for retrieving messages. -func (m *Messenger) Init() error { +func (m *Messenger) InitFilters() error { // Seed the for color generation rand.Seed(time.Now().Unix()) @@ -1852,20 +1875,6 @@ func (m *Messenger) Init() error { publicKeys = append(publicKeys, publicKey) } - installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey) - if err != nil { - return err - } - - for _, installation := range installations { - m.allInstallations.Store(installation.ID, installation) - } - - err = m.setInstallationHostname() - if err != nil { - return err - } - _, err = m.transport.InitFilters(filtersToInit, publicKeys) if err != nil { return err diff --git a/protocol/messenger_builder_test.go b/protocol/messenger_builder_test.go index b1f3528f6..d438373bd 100644 --- a/protocol/messenger_builder_test.go +++ b/protocol/messenger_builder_test.go @@ -106,7 +106,12 @@ func newTestMessenger(waku types.Waku, config testMessengerConfig) (*Messenger, m.retrievedMessagesIteratorFactory = config.messagesOrderController.newMessagesIterator } - err = m.Init() + err = m.InitInstallations() + if err != nil { + return nil, err + } + + err = m.InitFilters() if err != nil { return nil, err } diff --git a/protocol/messenger_identity.go b/protocol/messenger_identity.go index 84e503ff9..8c20c41e4 100644 --- a/protocol/messenger_identity.go +++ b/protocol/messenger_identity.go @@ -150,6 +150,8 @@ func (m *Messenger) setInstallationHostname() error { if err != nil { return err } + // NOTE: imd.Name is always empty in this else branch, which leads to the + // result of Sprintf having a trailing whitespace. imd.Name = fmt.Sprintf("%s %s", hn, imd.Name) } } diff --git a/protocol/messenger_installations_test.go b/protocol/messenger_installations_test.go index b9dcedb91..b222da293 100644 --- a/protocol/messenger_installations_test.go +++ b/protocol/messenger_installations_test.go @@ -6,9 +6,11 @@ import ( "image" "image/png" "os" + "runtime" "testing" userimage "github.com/status-im/status-go/images" + "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/browsers" "github.com/stretchr/testify/suite" @@ -403,3 +405,23 @@ func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() { s.Require().NoError(bob2.Shutdown()) s.Require().NoError(alice.Shutdown()) } + +func (s *MessengerInstallationSuite) TestInitInstallations() { + m, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil) + s.Require().NoError(err) + + // m.InitInstallations is already called when we set-up the messenger for + // testing, thus this test has no act phase. + // err = m.InitInstallations() + + // We get one installation when the messenger initializes installations + // correctly. + s.Require().Equal(1, m.allInstallations.Len()) + + deviceName, err := server.GetDeviceName() + s.Require().NoError(err) + installation, ok := m.allInstallations.Load(m.installationID) + s.Require().True(ok) + s.Require().Equal(deviceName+" ", installation.InstallationMetadata.Name) + s.Require().Equal(runtime.GOOS, installation.InstallationMetadata.DeviceType) +} diff --git a/protocol/messenger_test.go b/protocol/messenger_test.go index 6e8cb41f2..80f0505dd 100644 --- a/protocol/messenger_test.go +++ b/protocol/messenger_test.go @@ -81,7 +81,7 @@ func (n *testNode) PeersCount() int { return 1 } -func (s *MessengerSuite) TestInit() { +func (s *MessengerSuite) TestInitFilters() { testCases := []struct { Name string Prep func() @@ -174,7 +174,7 @@ func (s *MessengerSuite) TestInit() { for _, tc := range testCases { s.Run(tc.Name, func() { tc.Prep() - err := s.m.Init() + err := s.m.InitFilters() s.Require().NoError(err) filters := s.m.transport.Filters() expectedFilters += tc.AddedFilters diff --git a/services/ext/service.go b/services/ext/service.go index af66e03af..9c6759b1c 100644 --- a/services/ext/service.go +++ b/services/ext/service.go @@ -184,7 +184,11 @@ func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appD if s.config.ProcessBackedupMessages { s.messenger.EnableBackedupMessagesProcessing() } - return messenger.Init() + + // Be mindful of adding more initialization code, as it can easily + // impact login times for mobile users. For example, we avoid calling + // messenger.InitFilters here. + return s.messenger.InitInstallations() } func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) {