Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding)_: add a notification when importing old account #6255

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions protocol/activity_center.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const (
ActivityCenterNotificationTypeCommunityUnbanned
ActivityCenterNotificationTypeNewInstallationReceived
ActivityCenterNotificationTypeNewInstallationCreated
ActivityCenterNotificationTypeBackupSyncingFetching
ActivityCenterNotificationTypeBackupSyncingSuccess
ActivityCenterNotificationTypeBackupSyncingPartialFailure
ActivityCenterNotificationTypeBackupSyncingFailure
)

type ActivityCenterMembershipStatus int
Expand Down
14 changes: 14 additions & 0 deletions protocol/messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ type Messenger struct {
peersyncingRequests map[string]uint64

mvdsStatusChangeEvent chan datasyncnode.PeerStatusChangeEvent

backedUpFetchingStatus *BackupFetchingStatus
}

type EnvelopeEventsInterceptor struct {
Expand Down Expand Up @@ -917,6 +919,18 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
}
}

if m.processBackedupMessages {
m.backedUpFetchingStatus = &BackupFetchingStatus{
dataProgress: make(map[string]FetchingBackedUpDataTracking),
lastKnownMsgClock: 0,
fetchingCompleted: false,
}
err = m.startBackupFetchingTracking(response)
if err != nil {
return nil, err
}
}

return response, nil
}

Expand Down
139 changes: 139 additions & 0 deletions protocol/messenger_backup_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package protocol

import (
"database/sql"
"sync"
"time"

"go.uber.org/zap"

"github.com/golang/protobuf/proto"

utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/multiaccounts/settings"
Expand All @@ -28,6 +31,20 @@ const (
SyncWakuSectionKeyWatchOnlyAccounts = "watchOnlyAccounts"
)

const backupSyncingNotificationID = "BACKUP_SYNCING"

type FetchingBackedUpDataTracking struct {
LoadedItems map[uint32]bool
TotalNumber uint32
}

type BackupFetchingStatus struct {
dataProgress map[string]FetchingBackedUpDataTracking
lastKnownMsgClock uint64
fetchingCompleted bool
fetchingCompletedMutex sync.Mutex
}

func (m *Messenger) HandleBackup(state *ReceivedMessageState, message *protobuf.Backup, statusMessage *v1protocol.StatusMessage) error {
if !m.processBackedupMessages {
return nil
Expand Down Expand Up @@ -96,6 +113,11 @@ func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyKeypairs, message.KeypairDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyWatchOnlyAccounts, message.WatchOnlyAccountDetails)

err = m.updateBackupFetchProgress(message, &response, state)
if err != nil {
errors = append(errors, err)
}

m.config.messengerSignalsHandler.SendWakuFetchingBackupProgress(&response)
}

Expand All @@ -104,6 +126,123 @@ func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.
return errors
}

func (m *Messenger) updateBackupFetchProgress(message *protobuf.Backup, response *wakusync.WakuBackedUpDataResponse, state *ReceivedMessageState) error {
m.backedUpFetchingStatus.fetchingCompletedMutex.Lock()
defer m.backedUpFetchingStatus.fetchingCompletedMutex.Unlock()

if m.backedUpFetchingStatus.fetchingCompleted {
return nil
}

if m.backedUpFetchingStatus.lastKnownMsgClock > message.Clock {
return nil
}

if m.backedUpFetchingStatus.lastKnownMsgClock < message.Clock {
// Reset the progress tracker because we have access to a more recent copy of the backup
m.backedUpFetchingStatus.lastKnownMsgClock = message.Clock
m.backedUpFetchingStatus.dataProgress = make(map[string]FetchingBackedUpDataTracking)
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.backedUpFetchingStatus.dataProgress[backupName] = FetchingBackedUpDataTracking{
LoadedItems: make(map[uint32]bool),
TotalNumber: details.TotalNumber,
}
}

if len(m.backedUpFetchingStatus.dataProgress) == 0 {
return nil
}
}

// Evaluate the progress of the backup

// Set the new items before evaluating
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.backedUpFetchingStatus.dataProgress[backupName].LoadedItems[details.DataNumber] = true
}

for _, tracker := range m.backedUpFetchingStatus.dataProgress {
if len(tracker.LoadedItems)-1 < int(tracker.TotalNumber) {
// have not received everything yet
return nil
}
}

m.backedUpFetchingStatus.fetchingCompleted = true

// Update the AC notification and add it to the response
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(backupSyncingNotificationID))
if err != nil {
return err
}

if notification == nil {
return nil
}

notification.UpdatedAt = m.GetCurrentTimeInMillis()
notification.Type = ActivityCenterNotificationTypeBackupSyncingSuccess
_, err = m.persistence.SaveActivityCenterNotification(notification, true)
if err != nil {
return err
}

state.Response.AddActivityCenterNotification(notification)
return nil
}

func (m *Messenger) startBackupFetchingTracking(response *MessengerResponse) error {
// Add an acivity center notification to show that we are fetching back up messages
notification := &ActivityCenterNotification{
ID: types.FromHex(backupSyncingNotificationID),
Type: ActivityCenterNotificationTypeBackupSyncingFetching,
Timestamp: m.getTimesource().GetCurrentTime(),
Read: false,
Deleted: false,
UpdatedAt: m.GetCurrentTimeInMillis(),
}
err := m.addActivityCenterNotification(response, notification, nil)

if err != nil {
return err
}

// Add a timeout to mark the backup syncing as failed after 1 minute and 30 seconds
time.AfterFunc(1*time.Minute+30*time.Second, func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, there should be no timeout.
Backup fetching is done with a store node request(s). We know if any data was found, and we know if there was a timeout fetching, which id defined on the store node request level.
Do you think we can take that out here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a good idea. I'm not sure how to hook to that though. I don't think we specifically call "fetchAllBackups". We seem to only set processBackedupMessages which tells the Messenger to handle those backup messages.

I'm not sure where we do the call to fetch all those backup messages. In theory, we should call that only on restore, but I don't see it using the fetchBackup flag, so maybe we have a small issue?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah now when I think of this, there's probably no simple way to do it in current architecture...

m.backedUpFetchingStatus.fetchingCompletedMutex.Lock()
defer m.backedUpFetchingStatus.fetchingCompletedMutex.Unlock()

if m.backedUpFetchingStatus.fetchingCompleted {
// Nothing to do, the fetching has completed successfully
return
}
// Update the AC notification to the failure state
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(backupSyncingNotificationID))
if err != nil {
m.logger.Error("failed to get activity center notification", zap.Error(err))
} else if notification != nil {
notification.UpdatedAt = m.GetCurrentTimeInMillis()
if m.backedUpFetchingStatus.dataProgress == nil || len(m.backedUpFetchingStatus.dataProgress) == 0 {
notification.Type = ActivityCenterNotificationTypeBackupSyncingFailure
} else {
notification.Type = ActivityCenterNotificationTypeBackupSyncingPartialFailure
}
_, err = m.persistence.SaveActivityCenterNotification(notification, true)
if err != nil {
m.logger.Error("failed to save activity center notification", zap.Error(err))
} else {
if m.config.messengerSignalsHandler != nil {
resp := &MessengerResponse{}
resp.AddActivityCenterNotification(notification)
m.config.messengerSignalsHandler.MessengerResponse(resp)
}
}
}
})

return nil
}

func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, backupTime uint64) error {
if message == nil {
return nil
Expand Down
154 changes: 154 additions & 0 deletions protocol/messenger_backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,160 @@ func (s *MessengerBackupSuite) TestBackupProfileWithInvalidDisplayName() {
s.Require().Equal("", storedBob1DisplayName)
}

func (s *MessengerBackupSuite) TestFetchingDuringBackup() {
bob1 := s.m
bob1.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}

state := ReceivedMessageState{
Response: &MessengerResponse{},
}

backup := &protobuf.Backup{
Clock: 1,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}

err := bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
s.Require().Len(bob1.backedUpFetchingStatus.dataProgress, 3)
s.Require().Equal(uint32(1), bob1.backedUpFetchingStatus.dataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Parse a backup with a higher clock so reset the fetching
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering what will happen if the previous backup has completed before the next one kicks in?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I think in theory, it would mark the whole fetching as completed, so a false positive.

The old Nim logic had a minimum of 30 seconds before letting the user go to the next page, even if it said it received all data.

Maybe I should add that sort of timer too? Or maybe it doesn't matter, because the data will still get processed when the new backup comes in. Though the user would have a false sense of it being completed, so they could close the app too early.

backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(2),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
SettingsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
KeypairDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
WatchOnlyAccountDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
s.Require().Len(bob1.backedUpFetchingStatus.dataProgress, 6)
s.Require().Equal(uint32(2), bob1.backedUpFetchingStatus.dataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Backup with a smaller clock is ignored
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(5),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
// The values are gonna be the same as before as the backup was ignored
s.Require().Len(bob1.backedUpFetchingStatus.dataProgress, 6)
s.Require().Equal(uint32(2), bob1.backedUpFetchingStatus.dataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Parse the backup with almost all the correct data numbers
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(2),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
SettingsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
KeypairDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
WatchOnlyAccountDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)

// Parse the remaining backup so the notification should be sent now
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(2),
TotalNumber: uint32(2),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is done, so the signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 1)
jrainville marked this conversation as resolved.
Show resolved Hide resolved
s.Require().Equal(ActivityCenterNotificationTypeBackupSyncingSuccess, state.Response.ActivityCenterNotifications()[0].Type)
}

func (s *MessengerBackupSuite) TestBackupSettings() {
s.T().Skip("flaky test")
const (
Expand Down
7 changes: 6 additions & 1 deletion protocol/messenger_mailserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,13 @@ func (m *Messenger) RequestAllHistoricMessages(forceFetchingBackup, withRetries
return nil, nil
}

allResponses := &MessengerResponse{}
if forceFetchingBackup || !backupFetched {
err = m.startBackupFetchingTracking(allResponses)
if err != nil {
return nil, err
}

m.logger.Info("fetching backup")
err := m.syncBackup()
if err != nil {
Expand All @@ -336,7 +342,6 @@ func (m *Messenger) RequestAllHistoricMessages(forceFetchingBackup, withRetries
defer m.resetFiltersPriority(filters)

filtersByMs := m.SplitFiltersByStoreNode(filters)
allResponses := &MessengerResponse{}
for communityID, filtersForMs := range filtersByMs {
peerID := m.getCommunityStorenode(communityID)
if withRetries {
Expand Down
Loading