diff --git a/api/geth_backend.go b/api/geth_backend.go index 851660d65b7..90a22592484 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -2359,7 +2359,7 @@ func (b *GethStatusBackend) initProtocol() error { messenger := st.Messenger() // Init public status api b.statusNode.StatusPublicService().Init(messenger) - b.statusNode.AccountService().Init(messenger) + b.statusNode.AccountService().Init(messenger, acc) // Init chat service accDB, err := accounts.NewDB(b.appDB) if err != nil { diff --git a/node/status_node_services.go b/node/status_node_services.go index 38ac7985f24..4b6c96a54bc 100644 --- a/node/status_node_services.go +++ b/node/status_node_services.go @@ -189,6 +189,7 @@ func (b *StatusNode) accountsService(accDB *accounts.Database, mediaServer *serv b.config, b.accountsPublisher, mediaServer, + b.logger.Named("AccountsService"), ) } diff --git a/protocol/wakusync/profile_response.go b/protocol/backupsync/profile_response.go similarity index 65% rename from protocol/wakusync/profile_response.go rename to protocol/backupsync/profile_response.go index 06cddcc82e1..a8b2e7095e2 100644 --- a/protocol/wakusync/profile_response.go +++ b/protocol/backupsync/profile_response.go @@ -1,4 +1,4 @@ -package wakusync +package backupsync import ( "github.com/status-im/status-go/images" @@ -13,18 +13,18 @@ type BackedUpProfile struct { ProfileShowcasePreferences identity.ProfileShowcasePreferences `json:"profile_showcase_preferences,omitempty"` } -func (sfwr *WakuBackedUpDataResponse) SetDisplayName(displayName string) { +func (sfwr *BackedUpDataResponse) SetDisplayName(displayName string) { sfwr.Profile.DisplayName = displayName } -func (sfwr *WakuBackedUpDataResponse) SetImages(images []images.IdentityImage) { +func (sfwr *BackedUpDataResponse) SetImages(images []images.IdentityImage) { sfwr.Profile.Images = images } -func (sfwr *WakuBackedUpDataResponse) SetEnsUsernameDetails(ensUsernameDetails []*ens.UsernameDetail) { +func (sfwr *BackedUpDataResponse) SetEnsUsernameDetails(ensUsernameDetails []*ens.UsernameDetail) { sfwr.Profile.EnsUsernameDetails = ensUsernameDetails } -func (sfwr *WakuBackedUpDataResponse) SetProfileShowcasePreferences(profileShowcasePreferences *identity.ProfileShowcasePreferences) { +func (sfwr *BackedUpDataResponse) SetProfileShowcasePreferences(profileShowcasePreferences *identity.ProfileShowcasePreferences) { sfwr.Profile.ProfileShowcasePreferences = *profileShowcasePreferences } diff --git a/protocol/backupsync/response.go b/protocol/backupsync/response.go new file mode 100644 index 00000000000..75342450bf1 --- /dev/null +++ b/protocol/backupsync/response.go @@ -0,0 +1,34 @@ +package backupsync + +import ( + "encoding/json" + + accsmanagementtypes "github.com/status-im/status-go/accounts-management/types" + "github.com/status-im/status-go/multiaccounts/settings" +) + +type BackedUpDataResponse struct { + Clock uint64 + Profile *BackedUpProfile + Setting *settings.SyncSettingField + Keypair *accsmanagementtypes.Keypair + WatchOnlyAccount *accsmanagementtypes.Account +} + +func (sfwr *BackedUpDataResponse) MarshalJSON() ([]byte, error) { + responseItem := struct { + Clock uint64 `json:"clock,omitempty"` + Profile *BackedUpProfile `json:"backedUpProfile,omitempty"` + Setting *settings.SyncSettingField `json:"backedUpSettings,omitempty"` + Keypair *accsmanagementtypes.Keypair `json:"backedUpKeypair,omitempty"` + WatchOnlyAccount *accsmanagementtypes.Account `json:"backedUpWatchOnlyAccount,omitempty"` + }{ + Clock: sfwr.Clock, + Profile: sfwr.Profile, + Setting: sfwr.Setting, + Keypair: sfwr.Keypair, + WatchOnlyAccount: sfwr.WatchOnlyAccount, + } + + return json.Marshal(responseItem) +} diff --git a/protocol/messenger_backup_handler.go b/protocol/messenger_backup_handler.go index e4e03b8532e..6b65d886999 100644 --- a/protocol/messenger_backup_handler.go +++ b/protocol/messenger_backup_handler.go @@ -12,9 +12,9 @@ import ( messagingtypes "github.com/status-im/status-go/messaging/types" "github.com/status-im/status-go/multiaccounts/errors" "github.com/status-im/status-go/multiaccounts/settings" + "github.com/status-im/status-go/protocol/backupsync" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/protobuf" - "github.com/status-im/status-go/protocol/wakusync" ensservice "github.com/status-im/status-go/services/ens" "github.com/status-im/status-go/signal" ) @@ -70,8 +70,8 @@ func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, bac return nil } - response := wakusync.WakuBackedUpDataResponse{ - Profile: &wakusync.BackedUpProfile{}, + response := backupsync.BackedUpDataResponse{ + Profile: &backupsync.BackedUpProfile{}, } err := utils.ValidateDisplayName(&message.DisplayName) @@ -168,59 +168,12 @@ func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, bac response.SetEnsUsernameDetails(ensUsernameDetails) if m.config.messengerSignalsHandler != nil { - m.config.messengerSignalsHandler.SendWakuBackedUpProfile(&response) + m.config.messengerSignalsHandler.SendBackedUpProfile(&response) } return err } -// TODO move this to the AccountsService -func (m *Messenger) HandleBackedUpSettings(message *protobuf.SyncSetting) error { - if message == nil { - return nil - } - - // DisplayName is recovered via `protobuf.BackedUpProfile` message - if message.GetType() == protobuf.SyncSetting_DISPLAY_NAME { - return nil - } - - settingField, err := m.extractAndSaveSyncSetting(message) - if err != nil { - m.logger.Warn("failed to handle SyncSetting from backed up message", zap.Error(err)) - return nil - } - - if settingField != nil { - if message.GetType() == protobuf.SyncSetting_PREFERRED_NAME && message.GetValueString() != "" { - displayNameClock, err := m.settings.GetSettingLastSynced(settings.DisplayName) - if err != nil { - m.logger.Warn("failed to get last synced clock for display name", zap.Error(err)) - return nil - } - // there is a race condition between display name and preferred name on updating m.account.Name, so we need to check the clock - // there is also a similar check within SaveSyncDisplayName - if displayNameClock < message.GetClock() { - m.account.Name = message.GetValueString() - err = m.multiAccounts.SaveAccount(*m.account) - if err != nil { - m.logger.Warn("[HandleBackedUpSettings] failed to save account", zap.Error(err)) - return nil - } - } - } - - if m.config.messengerSignalsHandler != nil { - response := wakusync.WakuBackedUpDataResponse{ - Setting: settingField, - } - m.config.messengerSignalsHandler.SendWakuBackedUpSettings(&response) - } - } - - return nil -} - func syncInstallationCommunitiesSet(communities []*protobuf.SyncInstallationCommunity) map[string]*protobuf.SyncInstallationCommunity { ret := map[string]*protobuf.SyncInstallationCommunity{} for _, c := range communities { @@ -250,6 +203,15 @@ func (m *Messenger) handleLocalBackupCommunities(state *ReceivedMessageState, co return errors } +func (m *Messenger) PublishSettingEvent(settingField *settings.SyncSettingField) { + if m.config.messengerSignalsHandler != nil { + response := backupsync.BackedUpDataResponse{ + Setting: settingField, + } + m.config.messengerSignalsHandler.SendBackedUpSettings(&response) + } +} + func (m *Messenger) requestCommunityKeysAndSharedAddresses(syncCommunity *protobuf.SyncInstallationCommunity) error { if !syncCommunity.Joined { return nil diff --git a/protocol/messenger_config.go b/protocol/messenger_config.go index 0a1d63f6e3b..b39531b8f97 100644 --- a/protocol/messenger_config.go +++ b/protocol/messenger_config.go @@ -14,13 +14,13 @@ import ( "github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/params" + "github.com/status-im/status-go/protocol/backupsync" "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/discord" "github.com/status-im/status-go/protocol/ens" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/pushnotificationclient" - "github.com/status-im/status-go/protocol/wakusync" "github.com/status-im/status-go/services/mailservers" "github.com/status-im/status-go/services/wallet" ) @@ -54,11 +54,8 @@ type MessengerSignalsHandler interface { DiscordChannelImportProgress(importProgress *discord.ImportProgress) DiscordChannelImportFinished(communityID string, channelID string) DiscordChannelImportCancelled(channelID string) - SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) - SendWakuBackedUpProfile(response *wakusync.WakuBackedUpDataResponse) - SendWakuBackedUpSettings(response *wakusync.WakuBackedUpDataResponse) - SendWakuBackedUpKeypair(response *wakusync.WakuBackedUpDataResponse) - SendWakuBackedUpWatchOnlyAccount(response *wakusync.WakuBackedUpDataResponse) + SendBackedUpProfile(response *backupsync.BackedUpDataResponse) + SendBackedUpSettings(response *backupsync.BackedUpDataResponse) SendCuratedCommunitiesUpdate(response *communities.KnownCommunitiesResponse) } diff --git a/protocol/messenger_handler.go b/protocol/messenger_handler.go index bcca2c97cf7..4fb44b9468f 100644 --- a/protocol/messenger_handler.go +++ b/protocol/messenger_handler.go @@ -35,6 +35,7 @@ import ( "github.com/status-im/status-go/protocol/peersyncing" "github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/requests" + "github.com/status-im/status-go/protocol/syncing" v1protocol "github.com/status-im/status-go/protocol/v1" "github.com/status-im/status-go/protocol/verification" @@ -2417,7 +2418,7 @@ func (m *Messenger) HandleChatMessage(state *ReceivedMessageState, message *prot } func (m *Messenger) HandleSyncSetting(messageState *ReceivedMessageState, message *protobuf.SyncSetting, statusMessage *messagingtypes.Message) error { - settingField, err := m.extractAndSaveSyncSetting(message) + settingField, err := syncing.ExtractAndSaveSyncSetting(m.settings, m.logger, message) if err != nil { return err } @@ -2999,28 +3000,6 @@ func (m *Messenger) updateUnviewedCounts(chat *Chat, message *common.Message) { } } -func mapSyncAccountToAccount(message *protobuf.SyncAccount, accountOperability accsmanagementtypes.AccountOperable, accType accsmanagementtypes.AccountType) *accsmanagementtypes.Account { - return &accsmanagementtypes.Account{ - Address: types.BytesToAddress(message.Address), - KeyUID: message.KeyUid, - PublicKey: types.HexBytes(message.PublicKey), - Type: accType, - Path: message.Path, - Name: message.Name, - ColorID: multiaccountscommon.CustomizationColor(message.ColorId), - Emoji: message.Emoji, - Wallet: message.Wallet, - Chat: message.Chat, - Hidden: message.Hidden, - Clock: message.Clock, - Operable: accountOperability, - Removed: message.Removed, - Position: message.Position, - ProdPreferredChainIDs: message.ProdPreferredChainIDs, - TestPreferredChainIDs: message.TestPreferredChainIDs, - } -} - func (m *Messenger) resolveAccountOperability(syncAcc *protobuf.SyncAccount, syncKpMigratedToKeycard bool, dbKpMigratedToKeycard bool, accountReceivedFromLocalPairing bool) (accsmanagementtypes.AccountOperable, error) { if accountReceivedFromLocalPairing { @@ -3077,61 +3056,6 @@ func (m *Messenger) resolveAccountOperability(syncAcc *protobuf.SyncAccount, return accountsOperability, nil } -func (m *Messenger) handleSyncWatchOnlyAccount(message *protobuf.SyncAccount) (*accsmanagementtypes.Account, error) { - if message.KeyUid != "" { - return nil, ErrNotWatchOnlyAccount - } - - accountOperability := accsmanagementtypes.AccountFullyOperable - - accAddress := types.BytesToAddress(message.Address) - dbAccount, err := m.settings.GetAccountByAddress(accAddress) - if err != nil && err != accounts.ErrDbAccountNotFound { - return nil, err - } - - if dbAccount != nil { - if message.Clock <= dbAccount.Clock { - return nil, ErrTryingToStoreOldWalletAccount - } - - if message.Removed { - err = m.settings.RemoveAccount(accAddress, message.Clock) - if err != nil { - return nil, err - } - - err = m.settings.ResolveAccountsPositions(message.Clock) - if err != nil { - return nil, err - } - dbAccount.Removed = true - return dbAccount, nil - } - } - - acc := mapSyncAccountToAccount(message, accountOperability, accsmanagementtypes.AccountTypeWatch) - - err = m.settings.SaveOrUpdateAccounts([]*accsmanagementtypes.Account{acc}, false) - if err != nil { - return nil, err - } - - if m.config.accountsPublisher != nil { - payload := []gethcommon.Address{gethcommon.Address(acc.Address)} - if acc.Removed { - pubsub.Publish(m.config.accountsPublisher, accountsevent.AccountsRemovedEvent{ - Accounts: payload, - }) - } else { - pubsub.Publish(m.config.accountsPublisher, accountsevent.AccountsAddedEvent{ - Accounts: payload, - }) - } - } - return acc, nil -} - func (m *Messenger) handleSyncTokenPreferences(message *protobuf.SyncTokenPreferences) ([]walletsettings.TokenPreferences, error) { if len(message.Preferences) == 0 { return nil, nil @@ -3330,7 +3254,7 @@ func (m *Messenger) handleSyncKeypair(message *protobuf.SyncKeypair, fromLocalPa if err != nil { return nil, err } - acc := mapSyncAccountToAccount(sAcc, accountOperability, accsmanagementtypes.GetAccountTypeForKeypairType(kp.Type)) + acc := syncing.MapSyncAccountToAccount(sAcc, accountOperability, accsmanagementtypes.GetAccountTypeForKeypairType(kp.Type)) kp.Accounts = append(kp.Accounts, acc) } @@ -3484,9 +3408,9 @@ func (m *Messenger) HandleSyncCollectiblePreferences(state *ReceivedMessageState } func (m *Messenger) HandleSyncAccount(state *ReceivedMessageState, message *protobuf.SyncAccount, statusMessage *messagingtypes.Message) error { - acc, err := m.handleSyncWatchOnlyAccount(message) + acc, err := syncing.HandleSyncWatchOnlyAccount(m.settings, message, m.config.accountsPublisher) if err != nil { - if err == ErrTryingToStoreOldWalletAccount { + if err == syncing.ErrTryingToStoreOldWalletAccount { return nil } return err diff --git a/protocol/messenger_sync_settings.go b/protocol/messenger_sync_settings.go index 74726b6df91..ded7476c0bd 100644 --- a/protocol/messenger_sync_settings.go +++ b/protocol/messenger_sync_settings.go @@ -2,14 +2,12 @@ package protocol import ( "context" - "encoding/json" errorsLib "errors" "go.uber.org/zap" gocommon "github.com/status-im/status-go/common" messagingtypes "github.com/status-im/status-go/messaging/types" - "github.com/status-im/status-go/multiaccounts/errors" "github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/protocol/protobuf" ) @@ -83,46 +81,6 @@ func (m *Messenger) syncSettings(rawMessageHandler RawMessageHandler) error { return nil } -// extractSyncSetting parses incoming *protobuf.SyncSetting and stores the setting data if needed -func (m *Messenger) extractAndSaveSyncSetting(syncSetting *protobuf.SyncSetting) (*settings.SyncSettingField, error) { - sf, err := settings.GetFieldFromProtobufType(syncSetting.Type) - if err != nil { - m.logger.Error( - "extractSyncSetting - settings.GetFieldFromProtobufType", - zap.Error(err), - zap.Any("syncSetting", syncSetting), - ) - return nil, err - } - - spf := sf.SyncProtobufFactory() - if spf == nil { - m.logger.Warn("extractSyncSetting - received protobuf for setting with no SyncProtobufFactory", zap.Any("SettingField", sf)) - return nil, nil - } - if spf.Inactive() { - m.logger.Warn("extractSyncSetting - received protobuf for inactive sync setting", zap.Any("SettingField", sf)) - return nil, nil - } - - value := spf.ExtractValueFromProtobuf()(syncSetting) - - err = m.settings.SaveSyncSetting(sf, value, syncSetting.Clock) - if err == errors.ErrNewClockOlderThanCurrent { - m.logger.Info("extractSyncSetting - SaveSyncSetting :", zap.Error(err)) - return nil, nil - } - if err != nil { - return nil, err - } - - if v, ok := value.([]byte); ok { - value = json.RawMessage(v) - } - - return &settings.SyncSettingField{SettingField: sf, Value: value}, nil -} - // startSyncSettingsLoop watches the m.settings.SyncQueue and sends a sync message in response to a settings update func (m *Messenger) startSyncSettingsLoop() { go func() { diff --git a/protocol/messenger_testing_utils.go b/protocol/messenger_testing_utils.go index e68fe3849cf..905cf9f158e 100644 --- a/protocol/messenger_testing_utils.go +++ b/protocol/messenger_testing_utils.go @@ -10,7 +10,7 @@ import ( "time" gocommon "github.com/status-im/status-go/common" - "github.com/status-im/status-go/protocol/wakusync" + "github.com/status-im/status-go/protocol/backupsync" "github.com/status-im/status-go/protocol/identity" @@ -50,19 +50,12 @@ func WaitOnMessengerResponse(m *Messenger, condition func(*MessengerResponse) bo type MessengerSignalsHandlerMock struct { MessengerSignalsHandler - responseChan chan *MessengerResponse - communityFoundChan chan *communities.Community - wakuBackedUpDataResponseChan chan *wakusync.WakuBackedUpDataResponse + responseChan chan *MessengerResponse + communityFoundChan chan *communities.Community } -func (m *MessengerSignalsHandlerMock) SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) { - m.wakuBackedUpDataResponseChan <- response -} -func (m *MessengerSignalsHandlerMock) SendWakuBackedUpProfile(*wakusync.WakuBackedUpDataResponse) {} -func (m *MessengerSignalsHandlerMock) SendWakuBackedUpSettings(*wakusync.WakuBackedUpDataResponse) {} -func (m *MessengerSignalsHandlerMock) SendWakuBackedUpKeypair(*wakusync.WakuBackedUpDataResponse) {} -func (m *MessengerSignalsHandlerMock) SendWakuBackedUpWatchOnlyAccount(*wakusync.WakuBackedUpDataResponse) { -} +func (m *MessengerSignalsHandlerMock) SendBackedUpProfile(*backupsync.BackedUpDataResponse) {} +func (m *MessengerSignalsHandlerMock) SendBackedUpSettings(*backupsync.BackedUpDataResponse) {} func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolEnabled() {} func (m *MessengerSignalsHandlerMock) HistoryArchivesProtocolDisabled() {} @@ -93,42 +86,6 @@ func (m *MessengerSignalsHandlerMock) CommunityInfoFound(community *communities. } } -func WaitOnSignaledSendWakuFetchingBackupProgress(m *Messenger, condition func(*wakusync.WakuBackedUpDataResponse) bool, errorMessage string) (*wakusync.WakuBackedUpDataResponse, error) { - interval := 500 * time.Millisecond - timeoutChan := time.After(10 * time.Second) - - if m.config.messengerSignalsHandler != nil { - return nil, errors.New("messengerSignalsHandler already provided/mocked") - } - - responseChan := make(chan *wakusync.WakuBackedUpDataResponse, 1000) - m.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{ - wakuBackedUpDataResponseChan: responseChan, - } - - defer func() { - m.config.messengerSignalsHandler = nil - }() - - for { - _, err := m.RetrieveAll() - if err != nil { - return nil, err - } - - select { - case r := <-responseChan: - if condition(r) { - return r, nil - } - case <-timeoutChan: - return nil, errors.New("timed out: " + errorMessage) - default: // No immediate response, rest & loop back to retrieve again - time.Sleep(interval) - } - } -} - func WaitOnSignaledMessengerResponse(m *Messenger, condition func(*MessengerResponse) bool, errorMessage string) (*MessengerResponse, error) { interval := 500 * time.Millisecond timeoutChan := time.After(10 * time.Second) diff --git a/protocol/syncing/syncing.go b/protocol/syncing/syncing.go new file mode 100644 index 00000000000..60884aa930c --- /dev/null +++ b/protocol/syncing/syncing.go @@ -0,0 +1,146 @@ +package syncing + +import ( + "encoding/json" + "errors" + + "go.uber.org/zap" + + gethcommon "github.com/ethereum/go-ethereum/common" + + accsmanagementtypes "github.com/status-im/status-go/accounts-management/types" + "github.com/status-im/status-go/crypto/types" + "github.com/status-im/status-go/multiaccounts/accounts" + multiaccountscommon "github.com/status-im/status-go/multiaccounts/common" + maErrors "github.com/status-im/status-go/multiaccounts/errors" + "github.com/status-im/status-go/multiaccounts/settings" + "github.com/status-im/status-go/pkg/pubsub" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/services/accounts/accountsevent" +) + +// TODO move this code out of the protocol package +// https://github.com/status-im/status-go/pull/6967#discussion_r2391383496 + +var ( + ErrNotWatchOnlyAccount = errors.New("an account is not a watch only account") + ErrTryingToStoreOldWalletAccount = errors.New("trying to store an old wallet account") +) + +func MapSyncAccountToAccount(message *protobuf.SyncAccount, accountOperability accsmanagementtypes.AccountOperable, + accType accsmanagementtypes.AccountType) *accsmanagementtypes.Account { + return &accsmanagementtypes.Account{ + Address: types.BytesToAddress(message.Address), + KeyUID: message.KeyUid, + PublicKey: types.HexBytes(message.PublicKey), + Type: accType, + Path: message.Path, + Name: message.Name, + ColorID: multiaccountscommon.CustomizationColor(message.ColorId), + Emoji: message.Emoji, + Wallet: message.Wallet, + Chat: message.Chat, + Hidden: message.Hidden, + Clock: message.Clock, + Operable: accountOperability, + Removed: message.Removed, + Position: message.Position, + ProdPreferredChainIDs: message.ProdPreferredChainIDs, + TestPreferredChainIDs: message.TestPreferredChainIDs, + } +} + +func HandleSyncWatchOnlyAccount(accountsDB *accounts.Database, message *protobuf.SyncAccount, accountsPublisher *pubsub.Publisher) (*accsmanagementtypes.Account, error) { + if message.KeyUid != "" { + return nil, ErrNotWatchOnlyAccount + } + + accountOperability := accsmanagementtypes.AccountFullyOperable + + accAddress := types.BytesToAddress(message.Address) + dbAccount, err := accountsDB.GetAccountByAddress(accAddress) + if err != nil && err != accounts.ErrDbAccountNotFound { + return nil, err + } + + if dbAccount != nil { + if message.Clock <= dbAccount.Clock { + return nil, ErrTryingToStoreOldWalletAccount + } + + if message.Removed { + err = accountsDB.RemoveAccount(accAddress, message.Clock) + if err != nil { + return nil, err + } + + err = accountsDB.ResolveAccountsPositions(message.Clock) + if err != nil { + return nil, err + } + dbAccount.Removed = true + return dbAccount, nil + } + } + + acc := MapSyncAccountToAccount(message, accountOperability, accsmanagementtypes.AccountTypeWatch) + + err = accountsDB.SaveOrUpdateAccounts([]*accsmanagementtypes.Account{acc}, false) + if err != nil { + return nil, err + } + + if accountsPublisher != nil { + payload := []gethcommon.Address{gethcommon.Address(acc.Address)} + if acc.Removed { + pubsub.Publish(accountsPublisher, accountsevent.AccountsRemovedEvent{ + Accounts: payload, + }) + } else { + pubsub.Publish(accountsPublisher, accountsevent.AccountsAddedEvent{ + Accounts: payload, + }) + } + } + return acc, nil +} + +// extractSyncSetting parses incoming *protobuf.SyncSetting and stores the setting data if needed +func ExtractAndSaveSyncSetting(accountsDB *accounts.Database, logger *zap.Logger, syncSetting *protobuf.SyncSetting) (*settings.SyncSettingField, error) { + sf, err := settings.GetFieldFromProtobufType(syncSetting.Type) + if err != nil { + logger.Error( + "extractSyncSetting - settings.GetFieldFromProtobufType", + zap.Error(err), + zap.Any("syncSetting", syncSetting), + ) + return nil, err + } + + spf := sf.SyncProtobufFactory() + if spf == nil { + logger.Warn("extractSyncSetting - received protobuf for setting with no SyncProtobufFactory") + return nil, nil + } + if spf.Inactive() { + logger.Warn("extractSyncSetting - received protobuf for inactive sync setting") + return nil, nil + } + + value := spf.ExtractValueFromProtobuf()(syncSetting) + + err = accountsDB.SaveSyncSetting(sf, value, syncSetting.Clock) + if err == maErrors.ErrNewClockOlderThanCurrent { + logger.Info("extractSyncSetting - SaveSyncSetting :", zap.Error(err)) + return nil, nil + } + if err != nil { + return nil, err + } + + if v, ok := value.([]byte); ok { + value = json.RawMessage(v) + } + + return &settings.SyncSettingField{SettingField: sf, Value: value}, nil +} diff --git a/protocol/wakusync/progress_response.go b/protocol/wakusync/progress_response.go deleted file mode 100644 index b15846c18b2..00000000000 --- a/protocol/wakusync/progress_response.go +++ /dev/null @@ -1,36 +0,0 @@ -package wakusync - -import ( - "github.com/status-im/status-go/protocol/protobuf" -) - -type FetchingBackupedDataDetails struct { - DataNumber uint32 `json:"dataNumber,omitempty"` - TotalNumber uint32 `json:"totalNumber,omitempty"` -} - -func (sfwr *WakuBackedUpDataResponse) AddFetchingBackedUpDataDetails(section string, details *protobuf.FetchingBackedUpDataDetails) { - if details == nil { - return - } - if sfwr.FetchingDataProgress == nil { - sfwr.FetchingDataProgress = make(map[string]*protobuf.FetchingBackedUpDataDetails) - } - - sfwr.FetchingDataProgress[section] = details -} - -func (sfwr *WakuBackedUpDataResponse) FetchingBackedUpDataDetails() map[string]FetchingBackupedDataDetails { - if len(sfwr.FetchingDataProgress) == 0 { - return nil - } - - result := make(map[string]FetchingBackupedDataDetails) - for section, details := range sfwr.FetchingDataProgress { - result[section] = FetchingBackupedDataDetails{ - DataNumber: details.DataNumber, - TotalNumber: details.TotalNumber, - } - } - return result -} diff --git a/protocol/wakusync/response.go b/protocol/wakusync/response.go deleted file mode 100644 index 191ead353e1..00000000000 --- a/protocol/wakusync/response.go +++ /dev/null @@ -1,39 +0,0 @@ -package wakusync - -import ( - "encoding/json" - - accsmanagementtypes "github.com/status-im/status-go/accounts-management/types" - "github.com/status-im/status-go/multiaccounts/settings" - "github.com/status-im/status-go/protocol/protobuf" -) - -type WakuBackedUpDataResponse struct { - Clock uint64 - FetchingDataProgress map[string]*protobuf.FetchingBackedUpDataDetails // key represents the data/section backup details refer to - Profile *BackedUpProfile - Setting *settings.SyncSettingField - Keypair *accsmanagementtypes.Keypair - WatchOnlyAccount *accsmanagementtypes.Account -} - -func (sfwr *WakuBackedUpDataResponse) MarshalJSON() ([]byte, error) { - responseItem := struct { - Clock uint64 `json:"clock,omitempty"` - FetchingDataProgress map[string]FetchingBackupedDataDetails `json:"fetchingBackedUpDataProgress,omitempty"` - Profile *BackedUpProfile `json:"backedUpProfile,omitempty"` - Setting *settings.SyncSettingField `json:"backedUpSettings,omitempty"` - Keypair *accsmanagementtypes.Keypair `json:"backedUpKeypair,omitempty"` - WatchOnlyAccount *accsmanagementtypes.Account `json:"backedUpWatchOnlyAccount,omitempty"` - }{ - Clock: sfwr.Clock, - Profile: sfwr.Profile, - Setting: sfwr.Setting, - Keypair: sfwr.Keypair, - WatchOnlyAccount: sfwr.WatchOnlyAccount, - } - - responseItem.FetchingDataProgress = sfwr.FetchingBackedUpDataDetails() - - return json.Marshal(responseItem) -} diff --git a/services/accounts/service.go b/services/accounts/service.go index 795aa4e9d7d..a40b8023ba4 100644 --- a/services/accounts/service.go +++ b/services/accounts/service.go @@ -4,6 +4,8 @@ import ( "errors" "time" + "go.uber.org/zap" + "github.com/ethereum/go-ethereum/rpc" "github.com/golang/protobuf/proto" @@ -19,12 +21,20 @@ import ( "github.com/status-im/status-go/pkg/pubsub" "github.com/status-im/status-go/protocol" "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/syncing" "github.com/status-im/status-go/server" ) // NewService initializes service instance. -func NewService(db *accounts.Database, mdb *multiaccounts.Database, manager *accsmanagement.AccountsManager, - config *params.NodeConfig, publisher *pubsub.Publisher, mediaServer *server.MediaServer) *Service { +func NewService( + db *accounts.Database, + mdb *multiaccounts.Database, + manager *accsmanagement.AccountsManager, + config *params.NodeConfig, + publisher *pubsub.Publisher, + mediaServer *server.MediaServer, + logger *zap.Logger, +) *Service { s := &Service{ db: db, mdb: mdb, @@ -32,6 +42,7 @@ func NewService(db *accounts.Database, mdb *multiaccounts.Database, manager *acc config: config, mediaServer: mediaServer, publisher: publisher, + logger: logger, } db.SetSettingsNotifier(func(setting settings.SettingField, val interface{}) { if s.publisher != nil { @@ -50,13 +61,16 @@ type Service struct { mdb *multiaccounts.Database manager *accsmanagement.AccountsManager config *params.NodeConfig + account *multiaccounts.Account messenger *protocol.Messenger mediaServer *server.MediaServer publisher *pubsub.Publisher + logger *zap.Logger } -func (s *Service) Init(messenger *protocol.Messenger) { +func (s *Service) Init(messenger *protocol.Messenger, account *multiaccounts.Account) { s.messenger = messenger + s.account = account } // Start a service. @@ -175,6 +189,48 @@ func (s *Service) ExportBackup() ([]byte, error) { return proto.Marshal(backup) } +func (s *Service) handleBackedUpSettings(message *protobuf.SyncSetting) error { + if message == nil { + return nil + } + + // DisplayName is recovered via `protobuf.BackedUpProfile` message + if message.GetType() == protobuf.SyncSetting_DISPLAY_NAME { + return nil + } + + settingField, err := syncing.ExtractAndSaveSyncSetting(s.db, s.logger, message) + if err != nil { + s.logger.Warn("failed to handle SyncSetting from backed up message", zap.Error(err)) + return nil + } + + if settingField != nil && s.account != nil { + if message.GetType() == protobuf.SyncSetting_PREFERRED_NAME && message.GetValueString() != "" { + displayNameClock, err := s.db.GetSettingLastSynced(settings.DisplayName) + if err != nil { + s.logger.Warn("failed to get last synced clock for display name", zap.Error(err)) + return nil + } + // there is a race condition between display name and preferred name on updating m.account.Name, so we need to check the clock + // there is also a similar check within SaveSyncDisplayName + if displayNameClock < message.GetClock() { + s.account.Name = message.GetValueString() + err = s.mdb.SaveAccount(*s.account) + if err != nil { + s.logger.Warn("[HandleBackedUpSettings] failed to save account", zap.Error(err)) + return nil + } + } + } + + // TODO use a new feed for accounts service events + s.messenger.PublishSettingEvent(settingField) + } + + return nil +} + func (s *Service) ImportBackup(data []byte) error { var backup protobuf.AccountsLocalBackup err := proto.Unmarshal(data, &backup) @@ -184,8 +240,7 @@ func (s *Service) ImportBackup(data []byte) error { var errs []error for _, setting := range backup.Settings { - // TODO is it ok to use the messenger here? Otherwise, I have to duplicate a lot of code - err = s.messenger.HandleBackedUpSettings(setting) + err = s.handleBackedUpSettings(setting) if err != nil { errs = append(errs, err) } diff --git a/services/ext/signal.go b/services/ext/signal.go index 724c55662f8..3d6d513734a 100644 --- a/services/ext/signal.go +++ b/services/ext/signal.go @@ -4,9 +4,9 @@ import ( "github.com/status-im/status-go/crypto/types" messagingtypes "github.com/status-im/status-go/messaging/types" "github.com/status-im/status-go/protocol" + "github.com/status-im/status-go/protocol/backupsync" "github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/discord" - "github.com/status-im/status-go/protocol/wakusync" "github.com/status-im/status-go/signal" ) @@ -157,24 +157,12 @@ func (m *MessengerSignalsHandler) DiscordChannelImportCancelled(id string) { signal.SendDiscordChannelImportCancelled(id) } -func (m *MessengerSignalsHandler) SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) { - signal.SendWakuFetchingBackupProgress(response) +func (m *MessengerSignalsHandler) SendBackedUpProfile(response *backupsync.BackedUpDataResponse) { + signal.SendBackedUpProfile(response) } -func (m *MessengerSignalsHandler) SendWakuBackedUpProfile(response *wakusync.WakuBackedUpDataResponse) { - signal.SendWakuBackedUpProfile(response) -} - -func (m *MessengerSignalsHandler) SendWakuBackedUpSettings(response *wakusync.WakuBackedUpDataResponse) { - signal.SendWakuBackedUpSettings(response) -} - -func (m *MessengerSignalsHandler) SendWakuBackedUpKeypair(response *wakusync.WakuBackedUpDataResponse) { - signal.SendWakuBackedUpKeypair(response) -} - -func (m *MessengerSignalsHandler) SendWakuBackedUpWatchOnlyAccount(response *wakusync.WakuBackedUpDataResponse) { - signal.SendWakuBackedUpWatchOnlyAccount(response) +func (m *MessengerSignalsHandler) SendBackedUpSettings(response *backupsync.BackedUpDataResponse) { + signal.SendBackedUpSettings(response) } func (m *MessengerSignalsHandler) SendCuratedCommunitiesUpdate(response *communities.KnownCommunitiesResponse) { diff --git a/services/wallet/service.go b/services/wallet/service.go index de5620f9f2e..a8ea648c740 100644 --- a/services/wallet/service.go +++ b/services/wallet/service.go @@ -9,7 +9,6 @@ import ( "github.com/golang/protobuf/proto" - "github.com/status-im/status-go/crypto/types" "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/services/wallet/thirdparty/market/cryptocompare" @@ -20,12 +19,12 @@ import ( accsmanagementtypes "github.com/status-im/status-go/accounts-management/types" "github.com/status-im/status-go/logutils" "github.com/status-im/status-go/multiaccounts/accounts" - multiaccountscommon "github.com/status-im/status-go/multiaccounts/common" "github.com/status-im/status-go/params" "github.com/status-im/status-go/pkg/pubsub" + "github.com/status-im/status-go/protocol/backupsync" protocolCommon "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" - "github.com/status-im/status-go/protocol/wakusync" + "github.com/status-im/status-go/protocol/syncing" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/server" "github.com/status-im/status-go/services/ens/ensresolver" @@ -53,12 +52,6 @@ import ( "github.com/status-im/status-go/transactions" ) -// TODO this is duplicated -var ( - ErrNotWatchOnlyAccount = errors.New("an account is not a watch only account") - ErrTryingToStoreOldWalletAccount = errors.New("trying to store an old wallet account") -) - const ( EventWatchOnlyAccountRetrieved walletevent.EventType = "wallet-watch-only-account-retrieved" ) @@ -508,79 +501,16 @@ func (s *Service) ExportBackup() ([]byte, error) { return proto.Marshal(backup) } -func mapSyncAccountToAccount(message *protobuf.SyncAccount, accountOperability accsmanagementtypes.AccountOperable, - accType accsmanagementtypes.AccountType) *accsmanagementtypes.Account { - return &accsmanagementtypes.Account{ - Address: types.BytesToAddress(message.Address), - KeyUID: message.KeyUid, - PublicKey: types.HexBytes(message.PublicKey), - Type: accType, - Path: message.Path, - Name: message.Name, - ColorID: multiaccountscommon.CustomizationColor(message.ColorId), - Emoji: message.Emoji, - Wallet: message.Wallet, - Chat: message.Chat, - Hidden: message.Hidden, - Clock: message.Clock, - Operable: accountOperability, - Removed: message.Removed, - Position: message.Position, - ProdPreferredChainIDs: message.ProdPreferredChainIDs, - TestPreferredChainIDs: message.TestPreferredChainIDs, - } -} - -// TODO this is a duplicate of the code in messenger_handler. Should it be moved to a common place? -func (s *Service) handleSyncWatchOnlyAccount(message *protobuf.SyncAccount) (*accsmanagementtypes.Account, error) { - if message.KeyUid != "" { - return nil, ErrNotWatchOnlyAccount - } - - accountOperability := accsmanagementtypes.AccountFullyOperable - - accAddress := types.BytesToAddress(message.Address) - dbAccount, err := s.accountsDB.GetAccountByAddress(accAddress) - if err != nil && err != accounts.ErrDbAccountNotFound { - return nil, err - } - - if dbAccount != nil { - if message.Clock <= dbAccount.Clock { - // ignore this old message - return nil, nil - } - - if message.Removed { - err = s.accountsDB.RemoveAccount(accAddress, message.Clock) - if err != nil { - return nil, err - } - dbAccount.Removed = true - return dbAccount, nil - } - } - - acc := mapSyncAccountToAccount(message, accountOperability, accsmanagementtypes.AccountTypeWatch) - - err = s.accountsDB.SaveOrUpdateAccounts([]*accsmanagementtypes.Account{acc}, false) - if err != nil { - return nil, err - } - - return acc, nil -} - func (s *Service) handleWatchOnlyAccount(message *protobuf.SyncAccount) error { if message == nil { return nil } - acc, err := s.handleSyncWatchOnlyAccount(message) - if err != nil { + acc, err := syncing.HandleSyncWatchOnlyAccount(s.accountsDB, message, nil) + if err != nil && !errors.Is(err, syncing.ErrTryingToStoreOldWalletAccount) { return err } - response := wakusync.WakuBackedUpDataResponse{ + response := backupsync.BackedUpDataResponse{ WatchOnlyAccount: acc, } encodedmessage, err := json.Marshal(response) diff --git a/signal/events_backup_sync.go b/signal/events_backup_sync.go new file mode 100644 index 00000000000..946bdd42e11 --- /dev/null +++ b/signal/events_backup_sync.go @@ -0,0 +1,19 @@ +package signal + +import "encoding/json" + +const ( + // EventSyncFromWakuProfile is emitted while applying backed up profile data + EventBackedUpProfile = "backedup.profile" + + // EventBackedUpSettings is emitted while applying backed up settings + EventBackedUpSettings = "backedup.settings" +) + +func SendBackedUpProfile(obj json.Marshaler) { + send(EventBackedUpProfile, obj) +} + +func SendBackedUpSettings(obj json.Marshaler) { + send(EventBackedUpSettings, obj) +} diff --git a/signal/events_sync_from_waku.go b/signal/events_sync_from_waku.go deleted file mode 100644 index d9558282b91..00000000000 --- a/signal/events_sync_from_waku.go +++ /dev/null @@ -1,40 +0,0 @@ -package signal - -import "encoding/json" - -const ( - // EventWakuFetchingBackupProgress is emitted while applying fetched data is ongoing - EventWakuFetchingBackupProgress = "waku.fetching.backup.progress" - - // EventSyncFromWakuProfile is emitted while applying fetched profile data from waku - EventWakuBackedUpProfile = "waku.backedup.profile" - - // EventWakuBackedUpSettings is emitted while applying fetched settings from waku - EventWakuBackedUpSettings = "waku.backedup.settings" - - // EventWakuBackedUpKeypair is emitted while applying fetched keypair data from waku - EventWakuBackedUpKeypair = "waku.backedup.keypair" - - // EventWakuBackedUpWatchOnlyAccount is emitted while applying fetched watch only account data from waku - EventWakuBackedUpWatchOnlyAccount = "waku.backedup.watch-only-account" // #nosec G101 -) - -func SendWakuFetchingBackupProgress(obj json.Marshaler) { - send(EventWakuFetchingBackupProgress, obj) -} - -func SendWakuBackedUpProfile(obj json.Marshaler) { - send(EventWakuBackedUpProfile, obj) -} - -func SendWakuBackedUpSettings(obj json.Marshaler) { - send(EventWakuBackedUpSettings, obj) -} - -func SendWakuBackedUpKeypair(obj json.Marshaler) { - send(EventWakuBackedUpKeypair, obj) -} - -func SendWakuBackedUpWatchOnlyAccount(obj json.Marshaler) { - send(EventWakuBackedUpWatchOnlyAccount, obj) -}