status-go/api/geth_backend.go

2836 lines
78 KiB
Go
Raw Permalink Normal View History

package api
import (
"context"
"crypto/ecdsa"
"database/sql"
"encoding/json"
"fmt"
"math/big"
2020-07-13 10:45:36 +00:00
"os"
"path/filepath"
2021-08-13 07:24:33 +00:00
"strings"
"sync"
"time"
"go.uber.org/zap"
"github.com/afex/hystrix-go/hystrix"
"github.com/pkg/errors"
"github.com/imdario/mergo"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
2022-10-25 14:25:08 +00:00
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
2020-01-02 09:10:19 +00:00
"github.com/status-im/status-go/account"
2023-03-21 17:02:04 +00:00
"github.com/status-im/status-go/account/generator"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/centralizedmetrics"
centralizedmetricscommon "github.com/status-im/status-go/centralizedmetrics/common"
"github.com/status-im/status-go/common/dbsetup"
2021-05-14 10:55:42 +00:00
"github.com/status-im/status-go/connection"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/internal/version"
"github.com/status-im/status-go/logutils"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts"
multiacccommon "github.com/status-im/status-go/multiaccounts/common"
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/node"
"github.com/status-im/status-go/nodecfg"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
identityutils "github.com/status-im/status-go/protocol/identity"
"github.com/status-im/status-go/protocol/identity/colorhash"
2023-03-16 14:49:25 +00:00
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/server/pairing/statecontrol"
"github.com/status-im/status-go/services/ens"
2022-02-10 15:15:27 +00:00
"github.com/status-im/status-go/services/ext"
"github.com/status-im/status-go/services/personal"
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/sqlite"
"github.com/status-im/status-go/transactions"
"github.com/status-im/status-go/walletdatabase"
)
var (
// ErrWhisperClearIdentitiesFailure clearing whisper identities has failed.
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
// ErrWhisperIdentityInjectionFailure injecting whisper identities has failed.
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
// ErrWakuIdentityInjectionFailure injecting whisper identities has failed.
ErrWakuIdentityInjectionFailure = errors.New("failed to inject identity into waku")
// ErrUnsupportedRPCMethod is for methods not supported by the RPC interface
ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface")
// ErrRPCClientUnavailable is returned if an RPC client can't be retrieved.
// This is a normal situation when a node is stopped.
ErrRPCClientUnavailable = errors.New("JSON-RPC client is unavailable")
// ErrDBNotAvailable is returned if a method is called before the DB is available for usage
ErrDBNotAvailable = errors.New("DB is unavailable")
)
var _ StatusBackend = (*GethStatusBackend)(nil)
// GethStatusBackend implements the Status.im service over go-ethereum
type GethStatusBackend struct {
mu sync.Mutex
// rootDataDir is the same for all networks.
rootDataDir string
appDB *sql.DB
walletDB *sql.DB
config *params.NodeConfig
statusNode *node.StatusNode
personalAPI *personal.PublicAPI
multiaccountsDB *multiaccounts.Database
account *multiaccounts.Account
accountManager *account.GethManager
transactor *transactions.Transactor
connectionState connection.State
appState appState
selectedAccountKeyID string
allowAllRPC bool // used only for tests, disables api method restrictions
LocalPairingStateManager *statecontrol.ProcessStateManager
centralizedMetrics *centralizedmetrics.MetricService
logger *zap.Logger
}
// NewGethStatusBackend create a new GethStatusBackend instance
func NewGethStatusBackend(logger *zap.Logger) *GethStatusBackend {
logger = logger.Named("GethStatusBackend")
backend := &GethStatusBackend{
logger: logger,
}
2021-07-07 06:11:09 +00:00
backend.initialize()
logger.Info("Status backend initialized",
test_: Code Migration from status-cli-tests author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530 committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530 test: Code Migration from status-cli-tests fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments fix(ci)_: remove workspace and tmp dir This ensures we do not encounter weird errors like: ``` + ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists script returned exit code 1 ``` Signed-off-by: Jakub Sokołowski <jakub@status.im> chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 cleanup added logger and cleanup review comments changes fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 test_: remove port bind chore(wallet)_: move route execution code to separate module chore_: replace geth logger with zap logger (#5962) closes: #6002 feat(telemetry)_: add metrics for message reliability (#5899) * feat(telemetry)_: track message reliability Add metrics for dial errors, missed messages, missed relevant messages, and confirmed delivery. * fix_: handle error from json marshal chore_: use zap logger as request logger iterates: status-im/status-desktop#16536 test_: unique project per run test_: use docker compose v2, more concrete project name fix(codecov)_: ignore folders without tests Otherwise Codecov reports incorrect numbers when making changes. https://docs.codecov.com/docs/ignoring-paths Signed-off-by: Jakub Sokołowski <jakub@status.im> test_: verify schema of signals during init; fix schema verification warnings (#5947) fix_: update defaultGorushURL (#6011) fix(tests)_: use non-standard port to avoid conflicts We have observed `nimbus-eth2` build failures reporting this port: ```json { "lvl": "NTC", "ts": "2024-10-28 13:51:32.308+00:00", "msg": "REST HTTP server could not be started", "topics": "beacnde", "address": "127.0.0.1:5432", "reason": "(98) Address already in use" } ``` https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/ Signed-off-by: Jakub Sokołowski <jakub@status.im> fix_: create request logger ad-hoc in tests Fixes `TestCall` failing when run concurrently. chore_: configure codecov (#6005) * chore_: configure codecov * fix_: after_n_builds
2024-10-24 14:29:15 +00:00
zap.String("backend geth version", params.Version),
zap.String("commit", params.GitCommit),
zap.String("IpfsGatewayURL", params.IpfsGatewayURL))
2021-07-07 06:11:09 +00:00
return backend
}
func (b *GethStatusBackend) initialize() {
accountManager := account.NewGethManager(b.logger)
transactor := transactions.NewTransactor()
personalAPI := personal.NewAPI()
statusNode := node.New(transactor, b.logger)
2021-07-07 06:11:09 +00:00
b.statusNode = statusNode
b.accountManager = accountManager
b.transactor = transactor
b.personalAPI = personalAPI
b.statusNode.SetMultiaccountsDB(b.multiaccountsDB)
b.LocalPairingStateManager = new(statecontrol.ProcessStateManager)
b.LocalPairingStateManager.SetPairing(false)
}
// StatusNode returns reference to node manager
func (b *GethStatusBackend) StatusNode() *node.StatusNode {
return b.statusNode
}
// AccountManager returns reference to account manager
func (b *GethStatusBackend) AccountManager() *account.GethManager {
return b.accountManager
}
// Transactor returns reference to a status transactor
func (b *GethStatusBackend) Transactor() *transactions.Transactor {
return b.transactor
}
// SelectedAccountKeyID returns a Whisper key ID of the selected chat key pair.
func (b *GethStatusBackend) SelectedAccountKeyID() string {
return b.selectedAccountKeyID
}
// IsNodeRunning confirm that node is running
func (b *GethStatusBackend) IsNodeRunning() bool {
return b.statusNode.IsRunning()
}
// StartNode start Status node, fails if node is already started
func (b *GethStatusBackend) StartNode(config *params.NodeConfig) error {
b.mu.Lock()
defer b.mu.Unlock()
if err := b.startNode(config); err != nil {
signal.SendNodeCrashed(err)
return err
}
return nil
}
func (b *GethStatusBackend) UpdateRootDataDir(datadir string) {
b.mu.Lock()
defer b.mu.Unlock()
b.rootDataDir = datadir
}
func (b *GethStatusBackend) GetMultiaccountDB() *multiaccounts.Database {
return b.multiaccountsDB
}
func (b *GethStatusBackend) OpenAccounts() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB != nil {
return nil
}
db, err := multiaccounts.InitializeDB(filepath.Join(b.rootDataDir, "accounts.sql"))
if err != nil {
b.logger.Error("failed to initialize accounts db", zap.Error(err))
return err
}
b.multiaccountsDB = db
b.centralizedMetrics = centralizedmetrics.NewDefaultMetricService(b.multiaccountsDB.DB(), b.logger)
err = b.centralizedMetrics.EnsureStarted()
if err != nil {
return err
}
2021-07-07 06:11:09 +00:00
// Probably we should iron out a bit better how to create/dispose of the status-service
b.statusNode.SetMultiaccountsDB(db)
err = b.statusNode.StartMediaServerWithoutDB()
if err != nil {
b.logger.Error("failed to start media server without app db", zap.Error(err))
return err
}
return nil
}
func (b *GethStatusBackend) CentralizedMetricsInfo() (*centralizedmetrics.MetricsInfo, error) {
if b.centralizedMetrics == nil {
return nil, errors.New("centralized metrics not initialized")
}
return b.centralizedMetrics.Info()
}
func (b *GethStatusBackend) ToggleCentralizedMetrics(isEnabled bool) error {
if b.centralizedMetrics == nil {
return errors.New("centralized metrics nil")
}
return b.centralizedMetrics.ToggleEnabled(isEnabled)
}
func (b *GethStatusBackend) AddCentralizedMetric(metric centralizedmetricscommon.Metric) error {
if b.centralizedMetrics == nil {
return errors.New("centralized metrics nil")
}
return b.centralizedMetrics.AddMetric(metric)
}
func (b *GethStatusBackend) GetAccounts() ([]multiaccounts.Account, error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return nil, errors.New("accounts db wasn't initialized")
}
return b.multiaccountsDB.GetAccounts()
}
func (b *GethStatusBackend) AcceptTerms() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return errors.New("accounts db wasn't initialized")
}
accounts, err := b.multiaccountsDB.GetAccounts()
if err != nil {
return err
}
if len(accounts) == 0 {
return errors.New("accounts is empty")
}
return b.multiaccountsDB.UpdateHasAcceptedTerms(accounts[0].KeyUID, true)
}
func (b *GethStatusBackend) getAccountByKeyUID(keyUID string) (*multiaccounts.Account, error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return nil, errors.New("accounts db wasn't initialized")
}
as, err := b.multiaccountsDB.GetAccounts()
if err != nil {
return nil, err
}
for _, acc := range as {
if acc.KeyUID == keyUID {
return &acc, nil
}
}
return nil, fmt.Errorf("account with keyUID %s not found", keyUID)
}
func (b *GethStatusBackend) SaveAccount(account multiaccounts.Account) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return errors.New("accounts db wasn't initialized")
}
return b.multiaccountsDB.SaveAccount(account)
}
2022-07-06 16:12:49 +00:00
func (b *GethStatusBackend) DeleteMultiaccount(keyUID string, keyStoreDir string) error {
2020-07-13 10:45:36 +00:00
b.mu.Lock()
defer b.mu.Unlock()
if b.multiaccountsDB == nil {
return errors.New("accounts db wasn't initialized")
}
err := b.multiaccountsDB.DeleteAccount(keyUID)
if err != nil {
return err
}
appDbPath, err := b.getAppDBPath(keyUID)
if err != nil {
return err
}
walletDbPath, err := b.getWalletDBPath(keyUID)
if err != nil {
return err
}
2020-07-13 10:45:36 +00:00
dbFiles := []string{
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", keyUID)),
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql-shm", keyUID)),
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql-wal", keyUID)),
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", keyUID)),
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db-shm", keyUID)),
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db-wal", keyUID)),
appDbPath,
appDbPath + "-shm",
appDbPath + "-wal",
walletDbPath,
walletDbPath + "-shm",
walletDbPath + "-wal",
2020-07-13 10:45:36 +00:00
}
for _, path := range dbFiles {
if _, err := os.Stat(path); err == nil {
err = os.Remove(path)
if err != nil {
return err
}
}
}
if b.account != nil && b.account.KeyUID == keyUID {
// reset active account
b.account = nil
}
2020-07-13 10:45:36 +00:00
return os.RemoveAll(keyStoreDir)
}
2021-08-13 07:24:33 +00:00
func (b *GethStatusBackend) DeleteImportedKey(address, password, keyStoreDir string) error {
b.mu.Lock()
defer b.mu.Unlock()
err := filepath.Walk(keyStoreDir, func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.Contains(fileInfo.Name(), address) {
_, err := b.accountManager.VerifyAccountPassword(keyStoreDir, "0x"+address, password)
if err != nil {
b.logger.Error("failed to verify account", zap.String("account", address), zap.Error(err))
2021-08-13 07:24:33 +00:00
return err
}
return os.Remove(path)
}
return nil
})
return err
}
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
func (b *GethStatusBackend) runDBFileMigrations(account multiaccounts.Account, password string) (string, error) {
// Migrate file path to fix issue https://github.com/status-im/status-go/issues/2027
unsupportedPath := filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", account.KeyUID))
v3Path := filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", account.KeyUID))
v4Path, err := b.getAppDBPath(account.KeyUID)
if err != nil {
return "", err
}
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
_, err = os.Stat(unsupportedPath)
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
if err == nil {
err := os.Rename(unsupportedPath, v3Path)
if err != nil {
return "", err
}
// rename journals as well, but ignore errors
_ = os.Rename(unsupportedPath+"-shm", v3Path+"-shm")
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
_ = os.Rename(unsupportedPath+"-wal", v3Path+"-wal")
}
if _, err = os.Stat(v3Path); err == nil {
2023-07-05 15:56:34 +00:00
if err := appdatabase.MigrateV3ToV4(v3Path, v4Path, password, account.KDFIterations, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished); err != nil {
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
_ = os.Remove(v4Path)
_ = os.Remove(v4Path + "-shm")
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
_ = os.Remove(v4Path + "-wal")
return "", errors.New("Failed to migrate v3 db to v4: " + err.Error())
}
_ = os.Remove(v3Path)
_ = os.Remove(v3Path + "-shm")
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
_ = os.Remove(v3Path + "-wal")
}
return v4Path, nil
}
func (b *GethStatusBackend) ensureDBsOpened(account multiaccounts.Account, password string) (err error) {
// After wallet DB initial migration, the tables moved to wallet DB are removed from appDB
// so better migrate wallet DB first to avoid removal if wallet DB migration fails
if err = b.ensureWalletDBOpened(account, password); err != nil {
return err
}
if err = b.ensureAppDBOpened(account, password); err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) ensureAppDBOpened(account multiaccounts.Account, password string) (err error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.appDB != nil {
return nil
}
if len(b.rootDataDir) == 0 {
return errors.New("root datadir wasn't provided")
}
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
dbFilePath, err := b.runDBFileMigrations(account, password)
if err != nil {
return errors.New("Failed to migrate db file: " + err.Error())
}
2024-05-28 11:59:54 +00:00
appdatabase.CurrentAppDBKeyUID = account.KeyUID
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
b.appDB, err = appdatabase.InitializeDB(dbFilePath, password, account.KDFIterations)
if err != nil {
b.logger.Error("failed to initialize db", zap.Error(err))
return err
}
b.statusNode.SetAppDB(b.appDB)
return nil
}
func fileExists(path string) bool {
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}
func (b *GethStatusBackend) walletDBExists(keyUID string) bool {
path, err := b.getWalletDBPath(keyUID)
if err != nil {
return false
}
return fileExists(path)
}
func (b *GethStatusBackend) appDBExists(keyUID string) bool {
path, err := b.getAppDBPath(keyUID)
if err != nil {
return false
}
return fileExists(path)
}
func (b *GethStatusBackend) ensureWalletDBOpened(account multiaccounts.Account, password string) (err error) {
b.mu.Lock()
defer b.mu.Unlock()
if b.walletDB != nil {
return nil
}
dbWalletPath, err := b.getWalletDBPath(account.KeyUID)
if err != nil {
return err
}
b.walletDB, err = walletdatabase.InitializeDB(dbWalletPath, password, account.KDFIterations)
if err != nil {
b.logger.Error("failed to initialize wallet db", zap.Error(err))
return err
}
b.statusNode.SetWalletDB(b.walletDB)
return nil
}
2022-02-27 15:23:07 +00:00
func (b *GethStatusBackend) setupLogSettings() error {
logSettings := logutils.LogSettings{
Enabled: b.config.LogEnabled,
MobileSystem: b.config.LogMobileSystem,
Level: b.config.LogLevel,
File: b.config.LogFile,
MaxSize: b.config.LogMaxSize,
MaxBackups: b.config.LogMaxBackups,
CompressRotated: b.config.LogCompressRotated,
}
if err := logutils.OverrideRootLogWithConfig(logSettings, false); err != nil {
return err
}
return nil
}
// Deprecated: Use StartNodeWithAccount instead.
func (b *GethStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, nodecfg *params.NodeConfig) error {
if acc.KDFIterations == 0 {
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(acc.KeyUID)
if err != nil {
return err
}
acc.KDFIterations = kdfIterations
}
chatKey, err := ethcrypto.HexToECDSA(keyHex)
if err != nil {
return err
}
err = b.startNodeWithAccount(acc, password, nodecfg, chatKey)
if err != nil {
// Stop node for clean up
_ = b.StopNode()
}
2023-07-05 15:56:34 +00:00
// get logged in
if b.LocalPairingStateManager.IsPairing() {
return nil
}
return b.LoggedIn(acc.KeyUID, err)
}
func (b *GethStatusBackend) OverwriteNodeConfigValues(conf *params.NodeConfig, n *params.NodeConfig) (*params.NodeConfig, error) {
if err := mergo.Merge(conf, n, mergo.WithOverride); err != nil {
return nil, err
}
conf.Networks = n.Networks
if err := b.saveNodeConfig(conf); err != nil {
return nil, err
}
return conf, nil
}
func (b *GethStatusBackend) updateAccountColorHashAndColorID(keyUID string, accountsDB *accounts.Database) (*multiaccounts.Account, error) {
multiAccount, err := b.getAccountByKeyUID(keyUID)
if err != nil {
return nil, err
}
if multiAccount.ColorHash == nil {
keypair, err := accountsDB.GetKeypairByKeyUID(keyUID)
if err != nil {
return nil, err
}
publicKey := keypair.GetChatPublicKey()
if publicKey == nil {
return nil, errors.New("chat public key not found")
}
if err = enrichMultiAccountByPublicKey(multiAccount, publicKey); err != nil {
return nil, err
}
if err = b.multiaccountsDB.UpdateAccount(*multiAccount); err != nil {
return nil, err
}
}
return multiAccount, nil
}
func (b *GethStatusBackend) overrideNetworks(conf *params.NodeConfig, request *requests.Login) {
conf.Networks = setRPCs(defaultNetworks(request.WalletSecretsConfig.StatusProxyStageName), &request.WalletSecretsConfig)
}
func (b *GethStatusBackend) LoginAccount(request *requests.Login) error {
err := b.loginAccount(request)
if err != nil {
// Stop node for clean up
_ = b.StopNode()
}
if b.LocalPairingStateManager.IsPairing() {
return nil
}
2023-07-05 15:56:34 +00:00
return b.LoggedIn(request.KeyUID, err)
}
func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
if err := request.Validate(); err != nil {
return err
}
if request.Mnemonic != "" {
info, err := b.generateAccountInfo(request.Mnemonic)
if err != nil {
return errors.Wrap(err, "failed to generate account info")
}
derivedAddresses, err := b.getDerivedAddresses(info.ID)
if err != nil {
return errors.Wrap(err, "failed to get derived addresses")
}
request.Password = derivedAddresses[pathEncryption].PublicKey
request.KeycardWhisperPrivateKey = derivedAddresses[pathDefaultChat].PrivateKey
}
acc := multiaccounts.Account{
KeyUID: request.KeyUID,
KDFIterations: request.KdfIterations,
}
if acc.KDFIterations == 0 {
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
}
err := b.ensureDBsOpened(acc, request.Password)
if err != nil {
return errors.Wrap(err, "failed to open database")
}
defaultCfg := &params.NodeConfig{
// why we need this? relate PR: https://github.com/status-im/status-go/pull/4014
KeycardPairingDataFile: DefaultKeycardPairingDataFile,
}
2023-09-27 17:26:10 +00:00
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig, request.StatusProxyEnabled)
err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg)
2023-09-27 17:26:10 +00:00
if err != nil {
return errors.Wrap(err, "failed to update node config fleet")
2023-09-27 17:26:10 +00:00
}
err = b.loadNodeConfig(defaultCfg)
if err != nil {
return errors.Wrap(err, "failed to load node config")
}
2024-03-28 15:01:44 +00:00
if request.RuntimeLogLevel != "" {
b.config.LogLevel = request.RuntimeLogLevel
}
if b.config.WakuV2Config.Enabled && request.WakuV2Nameserver != "" {
b.config.WakuV2Config.Nameserver = request.WakuV2Nameserver
}
b.config.ShhextConfig.BandwidthStatsEnabled = request.BandwidthStatsEnabled
b.overrideNetworks(b.config, request)
if request.APIConfig != nil {
overrideApiConfig(b.config, request.APIConfig)
}
err = b.setupLogSettings()
if err != nil {
return errors.Wrap(err, "failed to setup log settings")
}
accountsDB, err := accounts.NewDB(b.appDB)
if err != nil {
return errors.Wrap(err, "failed to create accounts db")
}
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
if err != nil {
return errors.Wrap(err, "failed to update account color hash and color id")
}
b.account = multiAccount
chatAddr, err := accountsDB.GetChatAddress()
if err != nil {
return errors.Wrap(err, "failed to get chat address")
}
walletAddr, err := accountsDB.GetWalletAddress()
if err != nil {
return errors.Wrap(err, "failed to get wallet address")
}
watchAddrs, err := accountsDB.GetWalletAddresses()
if err != nil {
return errors.Wrap(err, "failed to get wallet addresses")
}
login := account.LoginParams{
Password: request.Password,
ChatAddress: chatAddr,
WatchAddresses: watchAddrs,
MainAccount: walletAddr,
}
err = b.StartNode(b.config)
if err != nil {
b.logger.Info("failed to start node")
return errors.Wrap(err, "failed to start node")
}
if chatKey := request.ChatPrivateKey(); chatKey == nil {
err = b.SelectAccount(login)
if err != nil {
return errors.Wrap(err, "failed to select account")
}
} else {
// In case of keycard, we don't have a keystore, instead we have private key loaded from the keycard
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
return errors.Wrap(err, "failed to set chat account")
}
_, err = b.accountManager.SelectedChatAccount()
if err != nil {
return errors.Wrap(err, "failed to get selected chat account")
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountsIntoServices()
if err != nil {
return errors.Wrap(err, "failed to inject accounts into services")
}
}
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
if err != nil {
b.logger.Error("failed to update account")
return errors.Wrap(err, "failed to update account")
}
return nil
}
// UpdateNodeConfigFleet loads the fleet from the settings and updates the node configuration
// If the fleet in settings is empty, or not supported anymore, it will be overridden with the default fleet.
// In that case settings fleet value remain the same, only runtime node configuration is updated.
func (b *GethStatusBackend) UpdateNodeConfigFleet(acc multiaccounts.Account, password string, config *params.NodeConfig) error {
if config == nil {
return nil
}
err := b.ensureDBsOpened(acc, password)
if err != nil {
return err
}
accountSettings, err := b.GetSettings()
if err != nil {
return err
}
fleet := accountSettings.GetFleet()
if !params.IsFleetSupported(fleet) {
b.logger.Warn("fleet is not supported, overriding with default value",
zap.String("fleet", fleet),
zap.String("defaultFleet", DefaultFleet))
fleet = DefaultFleet
}
err = SetFleet(fleet, config)
if err != nil {
return err
}
return nil
}
// Deprecated: Use loginAccount instead
func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, password string, inputNodeCfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
err := b.ensureDBsOpened(acc, password)
if err != nil {
return err
}
err = b.loadNodeConfig(inputNodeCfg)
if err != nil {
return err
}
2022-02-27 15:23:07 +00:00
err = b.setupLogSettings()
if err != nil {
return err
}
2022-02-27 15:23:07 +00:00
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accountsDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
if acc.ColorHash == nil {
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
if err != nil {
return err
}
acc = *multiAccount
}
b.account = &acc
chatAddr, err := accountsDB.GetChatAddress()
if err != nil {
return err
}
walletAddr, err := accountsDB.GetWalletAddress()
if err != nil {
return err
}
status-im/status-react#9203 Faster tx fetching with less request *** How it worked before this PR on multiaccount creation: - On multiacc creation we scanned chain for eth and erc20 transfers. For each address of a new empty multiaccount this scan required 1. two `eth_getBalance` requests to find out that there is no any balance change between zero and the last block, for eth transfers 2. and `chain-size/100000` (currently ~100) `eth_getLogs` requests, for erc20 transfers - For some reason we scanned an address of the chat account as well, and also accounts were not deduplicated. So even for an empty multiacc we scanned chain twice for each chat and main wallet addresses, in result app had to execute about 400 requests. - As mentioned above, `eth_getBalance` requests were used to check if there were any eth transfers, and that caused empty history in case if user already used all available eth (so that both zero and latest blocks show 0 eth for an address). There might have been transactions but we wouldn't fetch/show them. - There was no upper limit for the number of rpc requests during the scan, so it could require indefinite number of requests; the scanning algorithm was written so that we persisted the whole history of transactions or tried to scan form the beginning again in case of failure, giving up only after 10 minutes of failures. In result addresses with sufficient number of transactions would never be fully scanned and during these 10 minutes app could use gigabytes of internet data. - Failures were caused by `eth_getBlockByNumber`/`eth_getBlockByHash` requests. These requests return significantly bigger responses than `eth_getBalance`/`eth_transactionsCount` and it is likely that execution of thousands of them in parallel caused failures for accounts with hundreds of transactions. Even for an account with 12k we could successfully determine blocks with transaction in a few minutes using `eth_getBalance` requests, but `eth_getBlock...` couldn't be processed for this acc. - There was no caching for for `eth_getBalance` requests, and this caused in average 3-4 times more such requests than is needed. *** How it works now on multiaccount creation: - On multiacc creation we scan chain for last ~30 eth transactions and then check erc20 in the range where these eth transactions were found. For an empty address in multiacc this means: 1. two `eth_getBalance` transactions to determine that there was no balance change between zero and the last block; two `eth_transactionsCount` requests to determine there are no outgoing transactions for this address; total 4 requests for eth transfers 2. 20 `eth_getLogs` for erc20 transfers. This number can be lowered, but that's not a big deal - Deduplication of addresses is added and also we don't scan chat account, so a new multiacc requires ~25 (we also request latest block number and probably execute a few other calls) request to determine that multiacc is empty (comparing to ~400 before) - In case if address contains transactions we: 1. determine the range which contains 20-25 outgoing eth/erc20 transactions. This usually requires up to 10 `eth_transactionCount` requests 2. then we scan chain for eth transfers using `eth_getBalance` and `eth_transactionCount` (for double checking zero balances) 3. we make sure that we do not scan db for more than 30 blocks with transfers. That's important for accounts with mostly incoming transactions, because the range found on the first step might contain any number of incoming transfers, but only 20-25 outgoing transactions 4. when we found ~30 blocks in a given range, we update initial range `from` block using the oldest found block 5. and now we scan db for erc20transfers using `eth_getLogs` `oldest-found-eth-block`-`latest-block`, we make not more than 20 calls 6. when all blocks which contain incoming/outgoing transfers for a given address are found, we save these blocks to db and mark that transfers from these blocks are still to be fetched 7. Then we select latest ~30 (the number can be adjusted) blocks from these which were found and fetch transfers, this requires 3-4 requests per transfer. 8. we persist scanned range so that we know were to start next time 9. we dispatch an event which tells client that transactions are found 10. client fetches latest 20 transfers - when user presses "fetch more" button we check if app's db contains next 20 transfers, if not we scan chain again and return transfers after small fixes
2019-12-18 11:01:46 +00:00
watchAddrs, err := accountsDB.GetWalletAddresses()
if err != nil {
return err
}
login := account.LoginParams{
Password: password,
ChatAddress: chatAddr,
WatchAddresses: watchAddrs,
MainAccount: walletAddr,
}
err = b.StartNode(b.config)
if err != nil {
b.logger.Info("failed to start node")
return err
}
if chatKey == nil {
// Load account from keystore
err = b.SelectAccount(login)
if err != nil {
return err
}
} else {
// In case of keycard, we don't have keystore, but we directly have the private key
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
return err
}
_, err = b.accountManager.SelectedChatAccount()
if err != nil {
return err
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountsIntoServices()
if err != nil {
return err
}
}
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
if err != nil {
b.logger.Info("failed to update account")
return err
}
return nil
}
2023-11-10 15:12:54 +00:00
func (b *GethStatusBackend) accountsDB() (*accounts.Database, error) {
return accounts.NewDB(b.appDB)
}
2023-07-05 15:56:34 +00:00
func (b *GethStatusBackend) GetSettings() (*settings.Settings, error) {
2023-11-10 15:12:54 +00:00
accountsDB, err := b.accountsDB()
2023-07-05 15:56:34 +00:00
if err != nil {
return nil, err
}
2023-11-10 15:12:54 +00:00
settings, err := accountsDB.GetSettings()
2023-07-05 15:56:34 +00:00
if err != nil {
return nil, err
}
return &settings, nil
}
func (b *GethStatusBackend) GetEnsUsernames() ([]*ens.UsernameDetail, error) {
db := ens.NewEnsDatabase(b.appDB)
removed := false
return db.GetEnsUsernames(&removed)
}
func (b *GethStatusBackend) MigrateKeyStoreDir(acc multiaccounts.Account, password, oldDir, newDir string) error {
err := b.ensureDBsOpened(acc, password)
if err != nil {
return err
}
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accountDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
accounts, err := accountDB.GetActiveAccounts()
if err != nil {
return err
}
settings, err := accountDB.GetSettings()
if err != nil {
return err
}
addresses := []string{settings.EIP1581Address.Hex(), settings.WalletRootAddress.Hex()}
for _, account := range accounts {
addresses = append(addresses, account.Address.Hex())
}
err = b.accountManager.MigrateKeyStoreDir(oldDir, newDir, addresses)
if err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) Login(keyUID, password string) error {
return b.startNodeWithAccount(multiaccounts.Account{KeyUID: keyUID}, password, nil, nil)
}
func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string, nodecfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
err := b.startNodeWithAccount(acc, password, nodecfg, chatKey)
if err != nil {
// Stop node for clean up
_ = b.StopNode()
}
2023-07-05 15:56:34 +00:00
// get logged in
if !b.LocalPairingStateManager.IsPairing() {
2023-07-05 15:56:34 +00:00
return b.LoggedIn(acc.KeyUID, err)
}
return err
2023-07-05 15:56:34 +00:00
}
func (b *GethStatusBackend) LoggedIn(keyUID string, err error) error {
if err != nil {
signal.SendLoggedIn(nil, nil, nil, err)
return err
2023-07-05 15:56:34 +00:00
}
settings, err := b.GetSettings()
if err != nil {
return err
}
account, err := b.getAccountByKeyUID(keyUID)
if err != nil {
return err
}
ensUsernames, err := b.GetEnsUsernames()
if err != nil {
return err
}
var ensUsernamesJSON json.RawMessage
if ensUsernames != nil {
ensUsernamesJSON, err = json.Marshal(ensUsernames)
if err != nil {
return err
}
}
signal.SendLoggedIn(account, settings, ensUsernamesJSON, nil)
2023-07-05 15:56:34 +00:00
return nil
}
2021-01-07 11:15:02 +00:00
func (b *GethStatusBackend) ExportUnencryptedDatabase(acc multiaccounts.Account, password, directory string) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.appDB != nil {
return nil
}
if len(b.rootDataDir) == 0 {
return errors.New("root datadir wasn't provided")
}
perf(sqlCipher): Increase cipher page size to 8192 (#3591) * perf(sqlCipher): Increase cipher page size to 8192 Increasing the cipher page size to 8192 requires DB re-encryption. The process is as follows: //Login to v3 DB PRAGMA key = 'key'; PRAGMA cipher_page_size = 1024"; // old Page size PRAGMA cipher_hmac_algorithm = HMAC_SHA1"; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; PRAGMA kdf_iter = kdfIterationsNumber"; //Create V4 DB with increased page size ATTACH DATABASE 'newdb.db' AS newdb KEY 'key'; PRAGMA newdb.cipher_page_size = 8192; // new Page size PRAGMA newdb.cipher_hmac_algorithm = HMAC_SHA1"; // same as in v3 PRAGMA newdb.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"; // same as in v3 PRAGMA newdb.kdf_iter = kdfIterationsNumber"; // same as in v3 SELECT sqlcipher_export('newdb'); DETACH DATABASE newdb; //Login to V4 DB ... Worth noting: The DB migration will happen on the first successful login. The new DB version will have a different name to be able to distinguish between different DB versions.Versions naming mirrors sqlcipher major version (naming conventions used by sqlcipher), meaning that we're migrating from V3 to V4 DB (even if we're not fully aligned with V4 standards). The DB is not migrated to the v4 standard `SHA512` due to performance reasons. Our custom `SHA1` implementation is fully optimised for perfomance. * perf(sqlCipher): Fixing failing tests Update the new DB file format in Delete account, Change password and Decrypt database flows * perf(SQLCipher): Increase page size - send events to notify when the DB re-encryption starts/ends
2023-06-13 15:20:21 +00:00
dbPath, err := b.runDBFileMigrations(acc, password)
if err != nil {
return err
2021-01-07 11:15:02 +00:00
}
err = sqlite.DecryptDB(dbPath, directory, password, acc.KDFIterations)
2021-01-07 11:15:02 +00:00
if err != nil {
b.logger.Error("failed to initialize db", zap.Error(err))
2021-01-07 11:15:02 +00:00
return err
}
return nil
}
func (b *GethStatusBackend) ImportUnencryptedDatabase(acc multiaccounts.Account, password, databasePath string) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.appDB != nil {
return nil
}
path, err := b.getAppDBPath(acc.KeyUID)
if err != nil {
return err
}
2021-01-07 11:15:02 +00:00
err = sqlite.EncryptDB(databasePath, path, password, acc.KDFIterations, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
2021-01-07 11:15:02 +00:00
if err != nil {
b.logger.Error("failed to initialize db", zap.Error(err))
2021-01-07 11:15:02 +00:00
return err
}
return nil
}
func (b *GethStatusBackend) reEncryptKeyStoreDir(currentPassword string, newPassword string) error {
config := b.StatusNode().Config()
keyDir := ""
if config == nil {
keyDir = b.accountManager.Keydir
} else {
keyDir = config.KeyStoreDir
}
if keyDir != "" {
err := b.accountManager.ReEncryptKeyStoreDir(keyDir, currentPassword, newPassword)
if err != nil {
return fmt.Errorf("ReEncryptKeyStoreDir error: %v", err)
}
}
return nil
}
func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password string, newPassword string) error {
account, err := b.multiaccountsDB.GetAccount(keyUID)
if err != nil {
return err
}
internalDbPath, err := dbsetup.GetDBFilename(b.appDB)
if err != nil {
return fmt.Errorf("failed to get database file name, %w", err)
}
appDBPath, err := b.getAppDBPath(keyUID)
if err != nil {
return err
}
isCurrentAccount := appDBPath == internalDbPath
restartNode := func() {
if isCurrentAccount {
if err != nil {
// TODO https://github.com/status-im/status-go/issues/3906
// Fix restarting node, as it always fails but the error is ignored
// because UI calls Logout and Quit afterwards. It should not be UI-dependent
// and should be handled gracefully here if it makes sense to run dummy node after
// logout
_ = b.startNodeWithAccount(*account, password, nil, nil)
} else {
_ = b.startNodeWithAccount(*account, newPassword, nil, nil)
}
}
}
defer restartNode()
logout := func() {
if isCurrentAccount {
_ = b.Logout()
}
}
noLogout := func() {}
// First change app DB password, because it also reencrypts the keystore,
// otherwise if we call changeWalletDbPassword first and logout, we will fail
// to reencrypt the keystore
err = b.changeAppDBPassword(account, logout, password, newPassword)
if err != nil {
return err
}
// Already logged out but pass a param to decouple the logic for testing
err = b.changeWalletDBPassword(account, noLogout, password, newPassword)
if err != nil {
// Revert the password to original
err2 := b.changeAppDBPassword(account, noLogout, newPassword, password)
if err2 != nil {
b.logger.Error("failed to revert app db password", zap.Error(err2))
}
return err
}
return nil
}
func (b *GethStatusBackend) changeAppDBPassword(account *multiaccounts.Account, logout func(), password string, newPassword string) error {
tmpDbPath, cleanup, err := b.createTempDBFile("v4.db")
if err != nil {
return err
}
defer cleanup()
dbPath, err := b.getAppDBPath(account.KeyUID)
if err != nil {
return err
}
// Exporting database to a temporary file with a new password
err = sqlite.ExportDB(dbPath, password, account.KDFIterations, tmpDbPath, newPassword, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
if err != nil {
return err
}
err = b.reEncryptKeyStoreDir(password, newPassword)
if err != nil {
return err
}
// Replacing the old database with the new one requires closing all connections to the database
// This is done by stopping the node and restarting it with the new DB
logout()
// Replacing the old database files with the new ones, ignoring the wal and shm errors
replaceCleanup, err := replaceDBFile(dbPath, tmpDbPath)
if replaceCleanup != nil {
defer replaceCleanup()
}
if err != nil {
// Restore the old account
_ = b.reEncryptKeyStoreDir(newPassword, password)
return err
}
return nil
}
func (b *GethStatusBackend) changeWalletDBPassword(account *multiaccounts.Account, logout func(), password string, newPassword string) error {
tmpDbPath, cleanup, err := b.createTempDBFile("wallet.db")
if err != nil {
return err
}
defer cleanup()
dbPath, err := b.getWalletDBPath(account.KeyUID)
if err != nil {
return err
}
// Exporting database to a temporary file with a new password
err = sqlite.ExportDB(dbPath, password, account.KDFIterations, tmpDbPath, newPassword, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
if err != nil {
return err
}
// Replacing the old database with the new one requires closing all connections to the database
// This is done by stopping the node and restarting it with the new DB
logout()
// Replacing the old database files with the new ones, ignoring the wal and shm errors
replaceCleanup, err := replaceDBFile(dbPath, tmpDbPath)
if replaceCleanup != nil {
defer replaceCleanup()
}
return err
}
func (b *GethStatusBackend) createTempDBFile(pattern string) (tmpDbPath string, cleanup func(), err error) {
if len(b.rootDataDir) == 0 {
err = errors.New("root datadir wasn't provided")
return
}
rootDataDir := b.rootDataDir
//On iOS, the rootDataDir value does not contain a trailing slash.
//This is causing an incorrectly formatted temporary file path to be generated, leading to an "operation not permitted" error.
//e.g. value of rootDataDir is `/var/mobile/.../12906D5A-E831-49E9-BBE7-5FFE8E805D8A/Library`,
//the file path generated is something like `/var/mobile/.../12906D5A-E831-49E9-BBE7-5FFE8E805D8A/123-v4.db`
//which removed `Library` from the path.
if !strings.HasSuffix(rootDataDir, "/") {
rootDataDir += "/"
}
file, err := os.CreateTemp(filepath.Dir(rootDataDir), "*-"+pattern)
if err != nil {
return
}
err = file.Close()
if err != nil {
return
}
tmpDbPath = file.Name()
cleanup = func() {
filePath := file.Name()
_ = os.Remove(filePath)
_ = os.Remove(filePath + "-wal")
_ = os.Remove(filePath + "-shm")
_ = os.Remove(filePath + "-journal")
}
return
}
func replaceDBFile(dbPath string, newDBPath string) (cleanup func(), err error) {
err = os.Rename(newDBPath, dbPath)
if err != nil {
return
}
cleanup = func() {
_ = os.Remove(dbPath + "-wal")
_ = os.Remove(dbPath + "-shm")
_ = os.Rename(newDBPath+"-wal", dbPath+"-wal")
_ = os.Rename(newDBPath+"-shm", dbPath+"-shm")
}
return
}
func (b *GethStatusBackend) ConvertToKeycardAccount(account multiaccounts.Account, s settings.Settings, keycardUID string, password string, newPassword string) error {
messenger := b.Messenger()
if messenger == nil {
return errors.New("cannot resolve messenger instance")
}
err := b.multiaccountsDB.UpdateAccountKeycardPairing(account.KeyUID, account.KeycardPairing)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = b.ensureDBsOpened(account, password)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accountDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
keypair, err := accountDB.GetKeypairByKeyUID(account.KeyUID)
2021-07-20 11:48:10 +00:00
if err != nil {
if err == accounts.ErrDbKeypairNotFound {
return errors.New("cannot convert an unknown keypair")
}
2021-07-20 11:48:10 +00:00
return err
}
err = accountDB.SaveSettingField(settings.KeycardInstanceUID, s.KeycardInstanceUID)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = accountDB.SaveSettingField(settings.KeycardPairedOn, s.KeycardPairedOn)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = accountDB.SaveSettingField(settings.KeycardPairing, s.KeycardPairing)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = accountDB.SaveSettingField(settings.Mnemonic, nil)
if err != nil {
return err
}
err = accountDB.SaveSettingField(settings.ProfileMigrationNeeded, false)
if err != nil {
return err
}
// This check is added due to mobile app cause it doesn't support a Keycard features as desktop app.
// We should remove the following line once mobile and desktop app align.
if len(keycardUID) > 0 {
displayName, err := accountDB.DisplayName()
if err != nil {
return err
}
position, err := accountDB.GetPositionForNextNewKeycard()
if err != nil {
return err
}
kc := accounts.Keycard{
KeycardUID: keycardUID,
KeycardName: displayName,
KeycardLocked: false,
KeyUID: account.KeyUID,
Position: position,
}
for _, acc := range keypair.Accounts {
kc.AccountsAddresses = append(kc.AccountsAddresses, acc.Address)
}
err = messenger.SaveOrUpdateKeycard(context.Background(), &kc)
if err != nil {
return err
}
}
masterAddress, err := accountDB.GetMasterAddress()
if err != nil {
return err
}
eip1581Address, err := accountDB.GetEIP1581Address()
if err != nil {
return err
}
walletRootAddress, err := accountDB.GetWalletRootAddress()
if err != nil {
return err
}
err = b.closeDBs()
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = b.ChangeDatabasePassword(account.KeyUID, password, newPassword)
if err != nil {
return err
}
// We need to delete all accounts for the Keycard which is being added
for _, acc := range keypair.Accounts {
err = b.accountManager.DeleteAccount(acc.Address)
if err != nil {
return err
}
}
err = b.accountManager.DeleteAccount(masterAddress)
if err != nil {
return err
}
err = b.accountManager.DeleteAccount(eip1581Address)
if err != nil {
return err
}
err = b.accountManager.DeleteAccount(walletRootAddress)
if err != nil {
return err
}
return nil
2021-07-20 11:48:10 +00:00
}
2023-10-10 16:12:38 +00:00
func (b *GethStatusBackend) RestoreAccountAndLogin(request *requests.RestoreAccount) (*multiaccounts.Account, error) {
2023-03-16 14:49:25 +00:00
if err := request.Validate(); err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-16 14:49:25 +00:00
}
response, err := b.generateOrImportAccount(request.Mnemonic, 0, request.FetchBackup, &request.CreateAccount)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey,
)
if err != nil {
b.logger.Error("start node", zap.Error(err))
return nil, err
}
return response.account, nil
}
func (b *GethStatusBackend) RestoreKeycardAccountAndLogin(request *requests.RestoreAccount) (*multiaccounts.Account, error) {
if err := request.Validate(); err != nil {
return nil, err
}
keyStoreDir, err := b.InitKeyStoreDirWithAccount(request.RootDataDir, request.Keycard.KeyUID)
if err != nil {
return nil, err
}
derivedAddresses := map[string]generator.AccountInfo{
pathDefaultChat: {
Address: request.Keycard.WhisperAddress,
PublicKey: request.Keycard.WhisperPublicKey,
PrivateKey: request.Keycard.WhisperPrivateKey,
},
pathWalletRoot: {
Address: request.Keycard.WalletRootAddress,
},
pathDefaultWallet: {
Address: request.Keycard.WalletAddress,
PublicKey: request.Keycard.WalletPublicKey,
},
pathEIP1581: {
Address: request.Keycard.Eip1581Address,
},
pathEncryption: {
PublicKey: request.Keycard.EncryptionPublicKey,
},
}
input := &prepareAccountInput{
customizationColorClock: 0,
accountID: "", // empty for keycard
keyUID: request.Keycard.KeyUID,
address: request.Keycard.Address,
mnemonic: "",
restoringAccount: true,
derivedAddresses: derivedAddresses,
fetchBackup: request.FetchBackup, // WARNING: Ensure this value is correct
keyStoreDir: keyStoreDir,
}
response, err := b.prepareNodeAccount(&request.CreateAccount, input)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey, //request.WhisperPrivateKey,
)
if err != nil {
b.logger.Error("start node", zap.Error(err))
return nil, errors.Wrap(err, "failed to start node")
}
return response.account, nil
2023-03-21 17:02:04 +00:00
}
func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error) {
accountGenerator := b.accountManager.AccountsGenerator()
info, err := accountGenerator.ImportMnemonic(mnemonic, "")
if err != nil {
return "", err
}
return info.KeyUID, nil
}
type prepareAccountInput struct {
customizationColorClock uint64
accountID string
keyUID string
address string
mnemonic string
restoringAccount bool
derivedAddresses map[string]generator.AccountInfo
fetchBackup bool
keyStoreDir string
opts []params.Option
}
type accountBundle struct {
account *multiaccounts.Account
settings *settings.Settings
nodeConfig *params.NodeConfig
subAccounts []*accounts.Account
chatPrivateKey *ecdsa.PrivateKey
}
func (b *GethStatusBackend) generateOrImportAccount(mnemonic string, customizationColorClock uint64, fetchBackup bool, request *requests.CreateAccount, opts ...params.Option) (*accountBundle, error) {
info, err := b.generateAccountInfo(mnemonic)
if err != nil {
return nil, err
}
2023-03-16 14:49:25 +00:00
keyStoreDir, err := b.InitKeyStoreDirWithAccount(request.RootDataDir, info.KeyUID)
2023-03-16 14:49:25 +00:00
if err != nil {
return nil, err
}
derivedAddresses, err := b.getDerivedAddresses(info.ID)
if err != nil {
return nil, err
2023-03-16 14:49:25 +00:00
}
input := &prepareAccountInput{
customizationColorClock: customizationColorClock,
accountID: info.ID,
keyUID: info.KeyUID,
address: info.Address,
mnemonic: info.Mnemonic,
restoringAccount: mnemonic != "",
derivedAddresses: derivedAddresses,
fetchBackup: fetchBackup,
keyStoreDir: keyStoreDir,
opts: opts,
}
return b.prepareNodeAccount(request, input)
}
func (b *GethStatusBackend) prepareNodeAccount(request *requests.CreateAccount, input *prepareAccountInput) (*accountBundle, error) {
var err error
response := &accountBundle{}
if request.KeycardInstanceUID != "" {
request.Password = input.derivedAddresses[pathEncryption].PublicKey
}
// NOTE: I intentionally left this condition separately and not an `else` branch. Technically it's an `else`,
// but the statements inside are not the opposite statement of the first statement. It's just kinda like this:
// - replace password when we're using keycard
// - store account when we're not using keycard
if request.KeycardInstanceUID == "" {
err = b.storeAccount(input.accountID, request.Password, paths)
if err != nil {
return nil, err
}
}
response.account, err = b.buildAccount(request, input)
if err != nil {
return nil, errors.Wrap(err, "failed to build account")
}
response.settings, err = b.prepareSettings(request, input)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare settings")
}
if response.account.Name == "" {
response.account.Name = response.settings.Name
}
response.nodeConfig, err = b.prepareConfig(request, input, response.settings.InstallationID)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare node config")
}
response.subAccounts, err = b.prepareSubAccounts(request, input)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare sub accounts")
}
response, err = b.prepareForKeycard(request, input, response)
if err != nil {
return nil, errors.Wrap(err, "failed to prepare for keycard")
}
return response, nil
}
func (b *GethStatusBackend) InitKeyStoreDirWithAccount(rootDataDir, keyUID string) (string, error) {
b.UpdateRootDataDir(rootDataDir)
keyStoreRelativePath, keystoreAbsolutePath := DefaultKeystorePath(rootDataDir, keyUID)
// Initialize keystore dir with account
return keyStoreRelativePath, b.accountManager.InitKeystore(keystoreAbsolutePath)
}
func (b *GethStatusBackend) generateAccountInfo(mnemonic string) (*generator.GeneratedAccountInfo, error) {
2023-03-21 17:02:04 +00:00
accountGenerator := b.accountManager.AccountsGenerator()
2023-03-16 14:49:25 +00:00
2023-03-21 17:02:04 +00:00
var info generator.GeneratedAccountInfo
var err error
2023-03-21 17:02:04 +00:00
if mnemonic == "" {
// generate 1(n) account with default mnemonic length and no passphrase
generatedAccountInfos, err := accountGenerator.Generate(defaultMnemonicLength, 1, "")
info = generatedAccountInfos[0]
2023-03-16 14:49:25 +00:00
2023-03-21 17:02:04 +00:00
if err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-21 17:02:04 +00:00
}
} else {
2023-03-16 14:49:25 +00:00
2023-03-21 17:02:04 +00:00
info, err = accountGenerator.ImportMnemonic(mnemonic, "")
if err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-21 17:02:04 +00:00
}
}
2023-03-16 14:49:25 +00:00
return &info, nil
}
func (b *GethStatusBackend) storeAccount(id string, password string, paths []string) error {
accountGenerator := b.accountManager.AccountsGenerator()
_, err := accountGenerator.StoreAccount(id, password)
2023-03-16 14:49:25 +00:00
if err != nil {
return err
2023-03-16 14:49:25 +00:00
}
_, err = accountGenerator.StoreDerivedAccounts(id, password, paths)
2023-10-10 16:12:38 +00:00
if err != nil {
return err
2023-03-29 11:36:13 +00:00
}
return nil
}
func (b *GethStatusBackend) buildAccount(request *requests.CreateAccount, input *prepareAccountInput) (*multiaccounts.Account, error) {
err := b.OpenAccounts()
2023-03-16 14:49:25 +00:00
if err != nil {
return nil, err
2023-03-16 14:49:25 +00:00
}
acc := &multiaccounts.Account{
KeyUID: input.keyUID,
Name: request.DisplayName,
CustomizationColor: multiacccommon.CustomizationColor(request.CustomizationColor),
CustomizationColorClock: input.customizationColorClock,
2024-03-28 15:01:44 +00:00
KDFIterations: request.KdfIterations,
Timestamp: time.Now().Unix(),
2024-03-28 15:01:44 +00:00
}
if acc.KDFIterations == 0 {
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
2023-03-16 14:49:25 +00:00
}
2024-03-28 15:01:44 +00:00
count, err := b.multiaccountsDB.GetAccountsCount()
if err != nil {
return nil, err
}
if count == 0 {
acc.HasAcceptedTerms = true
}
if request.ImagePath != "" {
2024-03-28 15:01:44 +00:00
imageCropRectangle := request.ImageCropRectangle
if imageCropRectangle == nil {
// Default crop rectangle used by mobile
imageCropRectangle = &requests.ImageCropRectangle{
Ax: 0,
Ay: 0,
Bx: 1000,
By: 1000,
}
}
iis, err := images.GenerateIdentityImages(request.ImagePath,
imageCropRectangle.Ax, imageCropRectangle.Ay, imageCropRectangle.Bx, imageCropRectangle.By)
if err != nil {
return nil, err
}
acc.Images = iis
}
2023-03-16 14:49:25 +00:00
return acc, nil
}
func (b *GethStatusBackend) prepareSettings(request *requests.CreateAccount, input *prepareAccountInput) (*settings.Settings, error) {
settings, err := defaultSettings(input.keyUID, input.address, input.derivedAddresses)
2023-03-16 14:49:25 +00:00
if err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-16 14:49:25 +00:00
}
settings.DeviceName = request.DeviceName
settings.DisplayName = request.DisplayName
settings.PreviewPrivacy = request.PreviewPrivacy
settings.CurrentNetwork = request.CurrentNetwork
settings.TestNetworksEnabled = request.TestNetworksEnabled
if !input.restoringAccount {
settings.Mnemonic = &input.mnemonic
// TODO(rasom): uncomment it as soon as address will be properly
// marked as shown on mobile client
//settings.MnemonicWasNotShown = true
2023-03-21 17:02:04 +00:00
}
2023-03-16 14:49:25 +00:00
if request.WakuV2Fleet != "" {
settings.Fleet = &request.WakuV2Fleet
}
return settings, nil
}
func (b *GethStatusBackend) prepareConfig(request *requests.CreateAccount, input *prepareAccountInput, installationID string) (*params.NodeConfig, error) {
nodeConfig, err := DefaultNodeConfig(installationID, request, input.opts...)
2023-03-16 14:49:25 +00:00
if err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-16 14:49:25 +00:00
}
nodeConfig.ProcessBackedupMessages = input.fetchBackup
2023-03-16 14:49:25 +00:00
// when we set nodeConfig.KeyStoreDir, value of nodeConfig.KeyStoreDir should not contain the rootDataDir
// loadNodeConfig will add rootDataDir to nodeConfig.KeyStoreDir
nodeConfig.KeyStoreDir = input.keyStoreDir
2023-03-29 11:36:13 +00:00
return nodeConfig, nil
}
func (b *GethStatusBackend) prepareSubAccounts(request *requests.CreateAccount, input *prepareAccountInput) ([]*accounts.Account, error) {
emoji, err := randomWalletEmoji()
if err != nil {
return nil, errors.Wrap(err, "failed to generate random emoji")
}
walletDerivedAccount := input.derivedAddresses[pathDefaultWallet]
2023-03-16 14:49:25 +00:00
walletAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
KeyUID: input.keyUID,
Address: types.HexToAddress(walletDerivedAccount.Address),
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
Emoji: emoji,
Wallet: true,
Path: pathDefaultWallet,
Name: walletAccountDefaultName,
AddressWasNotShown: !input.restoringAccount,
}
chatDerivedAccount := input.derivedAddresses[pathDefaultChat]
2023-03-16 14:49:25 +00:00
chatAccount := &accounts.Account{
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
KeyUID: input.keyUID,
2023-03-16 14:49:25 +00:00
Address: types.HexToAddress(chatDerivedAccount.Address),
Name: request.DisplayName,
2023-03-16 14:49:25 +00:00
Chat: true,
Path: pathDefaultChat,
}
return []*accounts.Account{walletAccount, chatAccount}, nil
}
func (b *GethStatusBackend) prepareForKeycard(request *requests.CreateAccount, input *prepareAccountInput, response *accountBundle) (*accountBundle, error) {
if request.KeycardInstanceUID == "" {
return response, nil
}
kp := wallet.NewKeycardPairings()
kp.SetKeycardPairingsFile(response.nodeConfig.KeycardPairingDataFile)
pairings, err := kp.GetPairings()
2023-03-16 14:49:25 +00:00
if err != nil {
return nil, errors.Wrap(err, "failed to get keycard pairings")
}
keycard, ok := pairings[request.KeycardInstanceUID]
if !ok {
return nil, errors.New("keycard not found in pairings file")
}
response.settings.KeycardInstanceUID = request.KeycardInstanceUID
response.settings.KeycardPairedOn = time.Now().Unix()
response.settings.KeycardPairing = keycard.Key
response.account.KeycardPairing = keycard.Key
privateKeyHex := strings.TrimPrefix(input.derivedAddresses[pathDefaultChat].PrivateKey, "0x")
response.chatPrivateKey, err = crypto.HexToECDSA(privateKeyHex)
if err != nil {
return nil, errors.Wrap(err, "failed to parse chat private key hex")
2023-03-16 14:49:25 +00:00
}
return response, nil
}
func (b *GethStatusBackend) getDerivedAddresses(id string) (map[string]generator.AccountInfo, error) {
accountGenerator := b.accountManager.AccountsGenerator()
return accountGenerator.DeriveAddresses(id, paths)
2023-03-21 17:02:04 +00:00
}
2024-03-22 10:55:09 +00:00
// CreateAccountAndLogin creates a new account and logs in with it.
// NOTE: requests.CreateAccount is used for public, params.Option maybe used for internal usage.
func (b *GethStatusBackend) CreateAccountAndLogin(request *requests.CreateAccount, opts ...params.Option) (*multiaccounts.Account, error) {
2024-03-28 15:01:44 +00:00
validation := &requests.CreateAccountValidation{
AllowEmptyDisplayName: false,
}
if err := request.Validate(validation); err != nil {
2023-10-10 16:12:38 +00:00
return nil, err
2023-03-21 17:02:04 +00:00
}
response, err := b.generateOrImportAccount("", 1, false, request, opts...)
if err != nil {
return nil, err
}
err = b.StartNodeWithAccountAndInitialConfig(
*response.account,
request.Password,
*response.settings,
response.nodeConfig,
response.subAccounts,
response.chatPrivateKey,
)
if err != nil {
b.logger.Error("start node", zap.Error(err))
return nil, err
}
return response.account, nil
2023-03-16 14:49:25 +00:00
}
func (b *GethStatusBackend) ConvertToRegularAccount(mnemonic string, currPassword string, newPassword string) error {
messenger := b.Messenger()
if messenger == nil {
return errors.New("cannot resolve messenger instance")
}
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
accountInfo, err := b.accountManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
if err != nil {
return err
}
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(accountInfo.KeyUID)
if err != nil {
return err
}
err = b.ensureDBsOpened(multiaccounts.Account{KeyUID: accountInfo.KeyUID, KDFIterations: kdfIterations}, currPassword)
if err != nil {
return err
}
db, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
knownAccounts, err := db.GetActiveAccounts()
if err != nil {
return err
}
// We add these two paths, cause others will be added via `StoreAccount` function call
const pathWalletRoot = "m/44'/60'/0'/0"
const pathEIP1581 = "m/43'/60'/1581'"
var paths []string
paths = append(paths, pathWalletRoot, pathEIP1581)
for _, acc := range knownAccounts {
if accountInfo.KeyUID == acc.KeyUID {
paths = append(paths, acc.Path)
}
}
_, err = b.accountManager.AccountsGenerator().StoreAccount(accountInfo.ID, currPassword)
if err != nil {
return err
}
_, err = b.accountManager.AccountsGenerator().StoreDerivedAccounts(accountInfo.ID, currPassword, paths)
if err != nil {
return err
}
err = b.multiaccountsDB.UpdateAccountKeycardPairing(accountInfo.KeyUID, "")
if err != nil {
return err
}
err = messenger.DeleteAllKeycardsWithKeyUID(context.Background(), accountInfo.KeyUID)
if err != nil {
return err
}
err = db.SaveSettingField(settings.KeycardInstanceUID, "")
if err != nil {
return err
}
err = db.SaveSettingField(settings.KeycardPairedOn, 0)
if err != nil {
return err
}
err = db.SaveSettingField(settings.KeycardPairing, "")
if err != nil {
return err
}
err = db.SaveSettingField(settings.ProfileMigrationNeeded, false)
if err != nil {
return err
}
err = b.closeDBs()
if err != nil {
return err
}
return b.ChangeDatabasePassword(accountInfo.KeyUID, currPassword, newPassword)
}
2021-07-20 11:48:10 +00:00
func (b *GethStatusBackend) VerifyDatabasePassword(keyUID string, password string) error {
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(keyUID)
if err != nil {
return err
}
if !b.appDBExists(keyUID) || !b.walletDBExists(keyUID) {
return errors.New("One or more databases not created")
}
err = b.ensureDBsOpened(multiaccounts.Account{KeyUID: keyUID, KDFIterations: kdfIterations}, password)
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
err = b.closeDBs()
2021-07-20 11:48:10 +00:00
if err != nil {
return err
}
return nil
}
func enrichMultiAccountBySubAccounts(account *multiaccounts.Account, subaccs []*accounts.Account) error {
if account.ColorHash != nil && account.ColorID != 0 {
return nil
}
for i, acc := range subaccs {
subaccs[i].KeyUID = account.KeyUID
if acc.Chat {
pk := string(acc.PublicKey.Bytes())
colorHash, err := colorhash.GenerateFor(pk)
if err != nil {
return err
}
account.ColorHash = colorHash
colorID, err := identityutils.ToColorID(pk)
if err != nil {
return err
}
account.ColorID = colorID
break
}
}
return nil
}
func enrichMultiAccountByPublicKey(account *multiaccounts.Account, publicKey types.HexBytes) error {
pk := string(publicKey.Bytes())
colorHash, err := colorhash.GenerateFor(pk)
if err != nil {
return err
}
account.ColorHash = colorHash
colorID, err := identityutils.ToColorID(pk)
if err != nil {
return err
}
account.ColorID = colorID
return nil
}
// Deprecated: Use CreateAccountAndLogin instead
func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(
account multiaccounts.Account,
password string,
settings settings.Settings,
nodecfg *params.NodeConfig,
subaccs []*accounts.Account,
keyHex string,
) error {
err := enrichMultiAccountBySubAccounts(&account, subaccs)
if err != nil {
return err
}
err = b.SaveAccount(account)
if err != nil {
return err
}
err = b.ensureDBsOpened(account, password)
if err != nil {
return err
}
err = b.saveAccountsAndSettings(settings, nodecfg, subaccs)
if err != nil {
return err
}
return b.StartNodeWithKey(account, password, keyHex, nodecfg)
}
// StartNodeWithAccountAndInitialConfig is used after account and config was generated.
// In current setup account name and config is generated on the client side. Once/if it will be generated on
// status-go side this flow can be simplified.
// TODO: Consider passing accountBundle here directly
func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
account multiaccounts.Account,
password string,
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
settings settings.Settings,
nodecfg *params.NodeConfig,
2022-05-18 10:42:51 +00:00
subaccs []*accounts.Account,
chatKey *ecdsa.PrivateKey,
) error {
err := enrichMultiAccountBySubAccounts(&account, subaccs)
if err != nil {
return err
}
err = b.SaveAccount(account)
if err != nil {
return err
}
err = b.ensureDBsOpened(account, password)
if err != nil {
return err
}
err = b.saveAccountsAndSettings(settings, nodecfg, subaccs)
if err != nil {
return err
}
return b.StartNodeWithAccount(account, password, nodecfg, chatKey)
}
// TODO: change in `saveAccountsAndSettings` function param `subaccs []*accounts.Account` parameter to `profileKeypair *accounts.Keypair` parameter
2022-05-18 10:42:51 +00:00
func (b *GethStatusBackend) saveAccountsAndSettings(settings settings.Settings, nodecfg *params.NodeConfig, subaccs []*accounts.Account) error {
b.mu.Lock()
defer b.mu.Unlock()
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accdb, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
err = accdb.CreateSettings(settings, *nodecfg)
if err != nil {
return err
}
// In case of setting up new account either way (creating new, importing seed phrase, keycard account...) we should not
// back up any data after login, as it was the case before, that's the reason why we're setting last backup time to the time
// when an account was created.
now := time.Now().Unix()
err = accdb.SetLastBackup(uint64(now))
if err != nil {
return err
}
keypair := &accounts.Keypair{
KeyUID: settings.KeyUID,
Name: settings.DisplayName,
Type: accounts.KeypairTypeProfile,
DerivedFrom: settings.Address.String(),
LastUsedDerivationIndex: 0,
}
// When creating a new account, the chat account should have position -1, cause it doesn't participate
// in the wallet view and default wallet account should be at position 0.
for _, acc := range subaccs {
if acc.Chat {
acc.Position = -1
}
if acc.Wallet {
acc.Position = 0
}
acc.Operable = accounts.AccountFullyOperable
keypair.Accounts = append(keypair.Accounts, acc)
}
return accdb.SaveOrUpdateKeypair(keypair)
}
func (b *GethStatusBackend) loadNodeConfig(inputNodeCfg *params.NodeConfig) error {
b.mu.Lock()
defer b.mu.Unlock()
conf, err := nodecfg.GetNodeConfigFromDB(b.appDB)
if err != nil {
return err
}
if inputNodeCfg != nil {
2022-10-14 08:50:36 +00:00
// If an installationID is provided, we override it
if conf != nil && conf.ShhextConfig.InstallationID != "" {
inputNodeCfg.ShhextConfig.InstallationID = conf.ShhextConfig.InstallationID
}
conf, err = b.OverwriteNodeConfigValues(conf, inputNodeCfg)
if err != nil {
return err
}
}
// Start WakuV1 if WakuV2 is not enabled
conf.WakuConfig.Enabled = !conf.WakuV2Config.Enabled
// NodeConfig.Version should be taken from version.Version
// which is set at the compile time.
// What's cached is usually outdated so we overwrite it here.
conf.Version = version.Version()
conf.RootDataDir = b.rootDataDir
conf.DataDir = filepath.Join(b.rootDataDir, conf.DataDir)
conf.KeyStoreDir = filepath.Join(b.rootDataDir, conf.KeyStoreDir)
if _, err = os.Stat(conf.RootDataDir); os.IsNotExist(err) {
if err := os.MkdirAll(conf.RootDataDir, os.ModePerm); err != nil {
b.logger.Warn("failed to create data directory", zap.Error(err))
return err
}
}
if len(conf.LogDir) == 0 {
conf.LogFile = filepath.Join(b.rootDataDir, conf.LogFile)
} else {
conf.LogFile = filepath.Join(conf.LogDir, conf.LogFile)
}
b.config = conf
if inputNodeCfg != nil && inputNodeCfg.RuntimeLogLevel != "" {
b.config.LogLevel = inputNodeCfg.RuntimeLogLevel
}
return nil
}
func (b *GethStatusBackend) saveNodeConfig(n *params.NodeConfig) error {
err := nodecfg.SaveNodeConfig(b.appDB, n)
if err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) GetNodeConfig() (*params.NodeConfig, error) {
return nodecfg.GetNodeConfigFromDB(b.appDB)
}
func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("node crashed on start: %v", err)
}
}()
test_: Code Migration from status-cli-tests author shashankshampi <shashank.sanket1995@gmail.com> 1729780155 +0530 committer shashankshampi <shashank.sanket1995@gmail.com> 1730274350 +0530 test: Code Migration from status-cli-tests fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments fix(ci)_: remove workspace and tmp dir This ensures we do not encounter weird errors like: ``` + ln -s /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907 /home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go ln: failed to create symbolic link '/home/jenkins/workspace/go_prs_linux_x86_64_main_PR-5907@tmp/go/src/github.com/status-im/status-go': File exists script returned exit code 1 ``` Signed-off-by: Jakub Sokołowski <jakub@status.im> chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 cleanup added logger and cleanup review comments changes fix_: functional tests (#5979) * fix_: generate on test-functional * chore(test)_: fix functional test assertion --------- Co-authored-by: Siddarth Kumar <siddarthkay@gmail.com> feat(accounts)_: cherry-pick Persist acceptance of Terms of Use & Privacy policy (#5766) (#5977) * feat(accounts)_: Persist acceptance of Terms of Use & Privacy policy (#5766) The original GH issue https://github.com/status-im/status-mobile/issues/21113 came from a request from the Legal team. We must show to Status v1 users the new terms (Terms of Use & Privacy Policy) right after they upgrade to Status v2 from the stores. The solution we use is to create a flag in the accounts table, named hasAcceptedTerms. The flag will be set to true on the first account ever created in v2 and we provide a native call in mobile/status.go#AcceptTerms, which allows the client to persist the user's choice in case they are upgrading (from v1 -> v2, or from a v2 older than this PR). This solution is not the best because we should store the setting in a separate table, not in the accounts table. Related Mobile PR https://github.com/status-im/status-mobile/pull/21124 * fix(test)_: Compare addresses using uppercased strings --------- Co-authored-by: Icaro Motta <icaro.ldm@gmail.com> test_: restore account (#5960) feat_: `LogOnPanic` linter (#5969) * feat_: LogOnPanic linter * fix_: add missing defer LogOnPanic * chore_: make vendor * fix_: tests, address pr comments * fix_: address pr comments chore_: enable windows and macos CI build (#5840) - Added support for Windows and macOS in CI pipelines - Added missing dependencies for Windows and x86-64-darwin - Resolved macOS SDK version compatibility for darwin-x86_64 The `mkShell` override was necessary to ensure compatibility with the newer macOS SDK (version 11.0) for x86_64. The default SDK (10.12) was causing build failures because of the missing libs and frameworks. OverrideSDK creates a mapping from the default SDK in all package categories to the requested SDK (11.0). fix(contacts)_: fix trust status not being saved to cache when changed (#5965) Fixes https://github.com/status-im/status-desktop/issues/16392 test_: remove port bind chore(wallet)_: move route execution code to separate module chore_: replace geth logger with zap logger (#5962) closes: #6002 feat(telemetry)_: add metrics for message reliability (#5899) * feat(telemetry)_: track message reliability Add metrics for dial errors, missed messages, missed relevant messages, and confirmed delivery. * fix_: handle error from json marshal chore_: use zap logger as request logger iterates: status-im/status-desktop#16536 test_: unique project per run test_: use docker compose v2, more concrete project name fix(codecov)_: ignore folders without tests Otherwise Codecov reports incorrect numbers when making changes. https://docs.codecov.com/docs/ignoring-paths Signed-off-by: Jakub Sokołowski <jakub@status.im> test_: verify schema of signals during init; fix schema verification warnings (#5947) fix_: update defaultGorushURL (#6011) fix(tests)_: use non-standard port to avoid conflicts We have observed `nimbus-eth2` build failures reporting this port: ```json { "lvl": "NTC", "ts": "2024-10-28 13:51:32.308+00:00", "msg": "REST HTTP server could not be started", "topics": "beacnde", "address": "127.0.0.1:5432", "reason": "(98) Address already in use" } ``` https://ci.status.im/job/nimbus-eth2/job/platforms/job/linux/job/x86_64/job/main/job/PR-6683/3/ Signed-off-by: Jakub Sokołowski <jakub@status.im> fix_: create request logger ad-hoc in tests Fixes `TestCall` failing when run concurrently. chore_: configure codecov (#6005) * chore_: configure codecov * fix_: after_n_builds
2024-10-24 14:29:15 +00:00
b.logger.Info("status-go version details", zap.String("version", params.Version), zap.String("commit", params.GitCommit))
b.logger.Debug("starting node with config", zap.Stringer("config", config))
// Update config with some defaults.
if err := config.UpdateWithDefaults(); err != nil {
return err
}
// Updating node config
b.config = config
b.logger.Debug("updated config with defaults", zap.Stringer("config", config))
// Start by validating configuration
if err := config.Validate(); err != nil {
return err
}
if b.accountManager.GetManager() == nil {
err = b.accountManager.InitKeystore(config.KeyStoreDir)
if err != nil {
return err
}
}
manager := b.accountManager.GetManager()
if manager == nil {
return errors.New("ethereum accounts.Manager is nil")
}
if err = b.statusNode.StartWithOptions(config, node.StartOptions{
// The peers discovery protocols are started manually after
// `node.ready` signal is sent.
// It was discussed in https://github.com/status-im/status-go/pull/1333.
StartDiscovery: false,
AccountsManager: manager,
}); err != nil {
return
}
b.accountManager.SetRPCClient(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
signal.SendNodeStarted()
b.transactor.SetNetworkID(config.NetworkID)
b.transactor.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
2021-07-12 09:44:26 +00:00
b.personalAPI.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
if err = b.registerHandlers(); err != nil {
b.logger.Error("Handler registration failed", zap.Error(err))
return
}
b.logger.Info("Handlers registered")
// Handle a case when a node is stopped and resumed.
// If there is no account selected, an error is returned.
if _, err := b.accountManager.SelectedChatAccount(); err == nil {
if err := b.injectAccountsIntoServices(); err != nil {
return err
}
} else if err != account.ErrNoAccountSelected {
return err
}
if b.statusNode.WalletService() != nil {
b.statusNode.WalletService().KeycardPairings().SetKeycardPairingsFile(config.KeycardPairingDataFile)
}
signal.SendNodeReady()
if err := b.statusNode.StartDiscovery(); err != nil {
return err
}
return nil
}
// StopNode stop Status node. Stopped node cannot be resumed.
func (b *GethStatusBackend) StopNode() error {
b.mu.Lock()
defer b.mu.Unlock()
return b.stopNode()
}
func (b *GethStatusBackend) stopNode() error {
2021-07-07 06:11:09 +00:00
if b.statusNode == nil || !b.IsNodeRunning() {
return nil
}
if !b.LocalPairingStateManager.IsPairing() {
defer signal.SendNodeStopped()
}
2021-07-07 06:11:09 +00:00
return b.statusNode.Stop()
}
// RestartNode restart running Status node, fails if node is not running
func (b *GethStatusBackend) RestartNode() error {
b.mu.Lock()
defer b.mu.Unlock()
if !b.IsNodeRunning() {
return node.ErrNoRunningNode
}
if err := b.stopNode(); err != nil {
return err
}
return b.startNode(b.config)
}
// ResetChainData remove chain data from data directory.
// Node is stopped, and new node is started, with clean data directory.
func (b *GethStatusBackend) ResetChainData() error {
b.mu.Lock()
defer b.mu.Unlock()
if err := b.stopNode(); err != nil {
return err
}
// config is cleaned when node is stopped
if err := b.statusNode.ResetChainData(b.config); err != nil {
return err
}
signal.SendChainDataRemoved()
return b.startNode(b.config)
}
// CallRPC executes public RPC requests on node's in-proc RPC server.
func (b *GethStatusBackend) CallRPC(inputJSON string) (string, error) {
client := b.statusNode.RPCClient()
if client == nil {
return "", ErrRPCClientUnavailable
}
return client.CallRaw(inputJSON), nil
}
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
func (b *GethStatusBackend) CallPrivateRPC(inputJSON string) (string, error) {
client := b.statusNode.RPCClient()
if client == nil {
return "", ErrRPCClientUnavailable
}
return client.CallRaw(inputJSON), nil
}
// SendTransaction creates a new transaction and waits until it's complete.
func (b *GethStatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) {
verifiedAccount, err := b.getVerifiedWalletAccount(sendArgs.From.String(), password)
if err != nil {
return hash, err
}
hash, _, err = b.transactor.SendTransaction(sendArgs, verifiedAccount, -1)
return hash, err
}
func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) {
verifiedAccount, err := b.getVerifiedWalletAccount(sendArgs.From.String(), password)
if err != nil {
return hash, err
}
hash, _, err = b.transactor.SendTransactionWithChainID(chainID, sendArgs, -1, verifiedAccount)
return hash, err
}
func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) {
txWithSignature, err := b.transactor.BuildTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig)
if err != nil {
return hash, err
}
return b.transactor.SendTransactionWithSignature(common.Address(sendArgs.From), sendArgs.Symbol, sendArgs.MultiTransactionID, txWithSignature)
}
// HashTransaction validate the transaction and returns new sendArgs and the transaction hash.
func (b *GethStatusBackend) HashTransaction(sendArgs transactions.SendTxArgs) (transactions.SendTxArgs, types.Hash, error) {
return b.transactor.HashTransaction(sendArgs)
}
// SignMessage checks the pwd vs the selected account and passes on the signParams
// to personalAPI for message signature
func (b *GethStatusBackend) SignMessage(rpcParams personal.SignParams) (types.HexBytes, error) {
verifiedAccount, err := b.getVerifiedWalletAccount(rpcParams.Address, rpcParams.Password)
if err != nil {
return types.HexBytes{}, err
}
return b.personalAPI.Sign(rpcParams, verifiedAccount)
}
// Recover calls the personalAPI to return address associated with the private
// key that was used to calculate the signature in the message
func (b *GethStatusBackend) Recover(rpcParams personal.RecoverParams) (types.Address, error) {
return b.personalAPI.Recover(rpcParams)
}
// SignTypedData accepts data and password. Gets verified account and signs typed data.
func (b *GethStatusBackend) SignTypedData(typed typeddata.TypedData, address string, password string) (types.HexBytes, error) {
account, err := b.getVerifiedWalletAccount(address, password)
if err != nil {
return types.HexBytes{}, err
}
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
sig, err := typeddata.Sign(typed, account.AccountKey.PrivateKey, chain)
if err != nil {
return types.HexBytes{}, err
}
return types.HexBytes(sig), err
}
2020-10-29 07:46:02 +00:00
// SignTypedDataV4 accepts data and password. Gets verified account and signs typed data.
func (b *GethStatusBackend) SignTypedDataV4(typed signercore.TypedData, address string, password string) (types.HexBytes, error) {
account, err := b.getVerifiedWalletAccount(address, password)
if err != nil {
return types.HexBytes{}, err
}
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
sig, err := typeddata.SignTypedDataV4(typed, account.AccountKey.PrivateKey, chain)
if err != nil {
return types.HexBytes{}, err
}
return types.HexBytes(sig), err
}
// HashTypedData generates the hash of TypedData.
func (b *GethStatusBackend) HashTypedData(typed typeddata.TypedData) (types.Hash, error) {
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
hash, err := typeddata.ValidateAndHash(typed, chain)
if err != nil {
return types.Hash{}, err
}
return types.Hash(hash), err
}
2020-10-29 07:46:02 +00:00
// HashTypedDataV4 generates the hash of TypedData.
func (b *GethStatusBackend) HashTypedDataV4(typed signercore.TypedData) (types.Hash, error) {
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
hash, err := typeddata.HashTypedDataV4(typed, chain)
if err != nil {
return types.Hash{}, err
}
return types.Hash(hash), err
}
func (b *GethStatusBackend) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) {
config := b.StatusNode().Config()
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
db, err := accounts.NewDB(b.appDB)
if err != nil {
b.logger.Error("failed to create new *Database instance", zap.Error(err))
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
return nil, err
}
2019-12-11 13:59:37 +00:00
exists, err := db.AddressExists(types.HexToAddress(address))
if err != nil {
b.logger.Error("failed to query db for a given address", zap.String("address", address), zap.Error(err))
return nil, err
}
if !exists {
b.logger.Error("failed to get a selected account", zap.Error(transactions.ErrInvalidTxSender))
return nil, transactions.ErrAccountDoesntExist
}
key, err := b.accountManager.VerifyAccountPassword(config.KeyStoreDir, address, password)
2022-07-06 16:12:49 +00:00
if _, ok := err.(*account.ErrCannotLocateKeyFile); ok {
key, err = b.generatePartialAccountKey(db, address, password)
if err != nil {
return nil, err
}
}
if err != nil {
b.logger.Error("failed to verify account", zap.String("account", address), zap.Error(err))
return nil, err
}
return &account.SelectedExtKey{
Address: key.Address,
AccountKey: key,
}, nil
}
2022-07-06 16:12:49 +00:00
func (b *GethStatusBackend) generatePartialAccountKey(db *accounts.Database, address string, password string) (*types.Key, error) {
dbPath, err := db.GetPath(types.HexToAddress(address))
path := "m/" + dbPath[strings.LastIndex(dbPath, "/")+1:]
if err != nil {
b.logger.Error("failed to get path for given account address", zap.String("account", address), zap.Error(err))
2022-07-06 16:12:49 +00:00
return nil, err
}
rootAddress, err := db.GetWalletRootAddress()
if err != nil {
return nil, err
}
info, err := b.accountManager.AccountsGenerator().LoadAccount(rootAddress.Hex(), password)
if err != nil {
return nil, err
}
masterID := info.ID
accInfosMap, err := b.accountManager.AccountsGenerator().StoreDerivedAccounts(masterID, password, []string{path})
if err != nil {
return nil, err
}
_, key, err := b.accountManager.AddressToDecryptedAccount(accInfosMap[path].Address, password)
if err != nil {
return nil, err
}
return key, nil
}
// registerHandlers attaches Status callback handlers to running node
func (b *GethStatusBackend) registerHandlers() error {
var clients []*rpc.Client
if c := b.StatusNode().RPCClient(); c != nil {
clients = append(clients, c)
} else {
return errors.New("RPC client unavailable")
}
for _, client := range clients {
client.RegisterHandler(
params.AccountsMethodName,
func(context.Context, uint64, ...interface{}) (interface{}, error) {
return b.accountManager.Accounts()
},
)
if b.allowAllRPC {
// this should only happen in unit-tests, this variable is not available outside this package
continue
}
client.RegisterHandler(params.SendTransactionMethodName, unsupportedMethodHandler)
client.RegisterHandler(params.PersonalSignMethodName, unsupportedMethodHandler)
client.RegisterHandler(params.PersonalRecoverMethodName, unsupportedMethodHandler)
}
return nil
}
func unsupportedMethodHandler(ctx context.Context, chainID uint64, rpcParams ...interface{}) (interface{}, error) {
return nil, ErrUnsupportedRPCMethod
}
// ConnectionChange handles network state changes logic.
func (b *GethStatusBackend) ConnectionChange(typ string, expensive bool) {
b.mu.Lock()
defer b.mu.Unlock()
2021-05-14 10:55:42 +00:00
state := connection.State{
2022-03-28 10:10:40 +00:00
Type: connection.NewType(typ),
Expensive: expensive,
}
2021-05-14 10:55:42 +00:00
if typ == connection.None {
state.Offline = true
}
b.logger.Info("Network state change", zap.Stringer("old", b.connectionState), zap.Stringer("new", state))
if b.connectionState.Offline && !state.Offline {
// flush hystrix if we are going again online, since it doesn't behave
// well when offline
hystrix.Flush()
}
b.connectionState = state
b.statusNode.ConnectionChanged(state)
// logic of handling state changes here
// restart node? force peers reconnect? etc
}
// AppStateChange handles app state changes (background/foreground).
// state values: see https://facebook.github.io/react-native/docs/appstate.html
func (b *GethStatusBackend) AppStateChange(state string) {
var messenger *protocol.Messenger
s, err := parseAppState(state)
if err != nil {
b.logger.Error("AppStateChange failed, ignoring", zap.Error(err))
return
}
b.appState = s
if b.statusNode == nil {
b.logger.Warn("statusNode nil, not reporting app state change")
return
}
if b.statusNode.WakuExtService() != nil {
messenger = b.statusNode.WakuExtService().Messenger()
}
if b.statusNode.WakuV2ExtService() != nil {
messenger = b.statusNode.WakuV2ExtService().Messenger()
}
if messenger == nil {
b.logger.Warn("messenger nil, not reporting app state change")
return
}
if s == appStateForeground {
messenger.ToForeground()
} else {
messenger.ToBackground()
}
// TODO: put node in low-power mode if the app is in background (or inactive)
// and normal mode if the app is in foreground.
}
func (b *GethStatusBackend) StopLocalNotifications() error {
if b.statusNode == nil {
return nil
}
return b.statusNode.StopLocalNotifications()
}
func (b *GethStatusBackend) StartLocalNotifications() error {
if b.statusNode == nil {
return nil
}
return b.statusNode.StartLocalNotifications()
}
// Logout clears whisper identities.
func (b *GethStatusBackend) Logout() error {
b.mu.Lock()
defer b.mu.Unlock()
b.logger.Debug("logging out")
err := b.cleanupServices()
if err != nil {
return err
}
err = b.closeDBs()
if err != nil {
return err
}
b.AccountManager().Logout()
b.account = nil
2021-07-07 06:11:09 +00:00
if b.statusNode != nil {
if err := b.statusNode.Stop(); err != nil {
return err
}
b.statusNode = nil
}
2024-01-30 11:45:08 +00:00
if !b.LocalPairingStateManager.IsPairing() {
signal.SendNodeStopped()
}
2021-07-07 06:11:09 +00:00
// re-initialize the node, at some point we should better manage the lifecycle
b.initialize()
err = b.statusNode.StartMediaServerWithoutDB()
if err != nil {
b.logger.Error("failed to start media server without app db", zap.Error(err))
return err
}
return nil
}
// cleanupServices stops parts of services that doesn't managed by a node and removes injected data from services.
func (b *GethStatusBackend) cleanupServices() error {
b.selectedAccountKeyID = ""
if b.statusNode == nil {
return nil
}
return b.statusNode.Cleanup()
}
func (b *GethStatusBackend) closeDBs() error {
err := b.closeWalletDB()
if err != nil {
return err
}
return b.closeAppDB()
}
func (b *GethStatusBackend) closeAppDB() error {
if b.appDB != nil {
err := b.appDB.Close()
if err != nil {
return err
}
b.appDB = nil
return nil
}
return nil
}
func (b *GethStatusBackend) closeWalletDB() error {
if b.walletDB != nil {
err := b.walletDB.Close()
if err != nil {
return err
}
b.walletDB = nil
}
return nil
}
// SelectAccount selects current wallet and chat accounts, by verifying that each address has corresponding account which can be decrypted
// using provided password. Once verification is done, the decrypted chat key is injected into Whisper (as a single identity,
// all previous identities are removed).
func (b *GethStatusBackend) SelectAccount(loginParams account.LoginParams) error {
b.mu.Lock()
defer b.mu.Unlock()
b.AccountManager().RemoveOnboarding()
err := b.accountManager.SelectAccount(loginParams)
if err != nil {
return err
}
if loginParams.MultiAccount != nil {
b.account = loginParams.MultiAccount
}
if err := b.injectAccountsIntoServices(); err != nil {
return err
}
return nil
}
2020-12-09 14:03:43 +00:00
func (b *GethStatusBackend) GetActiveAccount() (*multiaccounts.Account, error) {
2020-12-09 14:45:59 +00:00
if b.account == nil {
2020-12-09 14:36:40 +00:00
return nil, errors.New("master key account is nil in the GethStatusBackend")
2020-12-09 14:45:59 +00:00
}
2020-12-09 14:03:43 +00:00
return b.account, nil
}
func (b *GethStatusBackend) LocalPairingStarted() error {
if b.account == nil {
return errors.New("master key account is nil in the GethStatusBackend")
}
accountDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
return accountDB.MnemonicWasShown()
}
2022-02-10 15:15:27 +00:00
func (b *GethStatusBackend) injectAccountsIntoWakuService(w types.WakuKeyManager, st *ext.Service) error {
chatAccount, err := b.accountManager.SelectedChatAccount()
if err != nil {
return err
}
identity := chatAccount.AccountKey.PrivateKey
2020-12-09 14:03:43 +00:00
acc, err := b.GetActiveAccount()
if err != nil {
return err
}
2022-02-10 15:15:27 +00:00
if err := w.DeleteKeyPairs(); err != nil { // err is not possible; method return value is incorrect
return err
}
b.selectedAccountKeyID, err = w.AddKeyPair(identity)
if err != nil {
return ErrWakuIdentityInjectionFailure
}
2022-02-10 15:15:27 +00:00
if st != nil {
if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.walletDB, b.statusNode.HTTPServer(), b.multiaccountsDB, acc, b.accountManager, b.statusNode.RPCClient(), b.statusNode.WalletService(), b.statusNode.CommunityTokensService(), b.statusNode.WakuV2Service(), logutils.ZapLogger(), b.statusNode.AccountsFeed()); err != nil {
return err
}
2022-02-10 15:15:27 +00:00
// Set initial connection state
st.ConnectionChanged(b.connectionState)
2022-02-10 15:15:27 +00:00
messenger := st.Messenger()
// Init public status api
b.statusNode.StatusPublicService().Init(messenger)
2022-07-06 16:12:49 +00:00
b.statusNode.AccountService().Init(messenger)
2022-02-10 15:15:27 +00:00
// Init chat service
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
b.statusNode.ChatService(accDB).Init(messenger)
b.statusNode.EnsService().Init(messenger.SyncEnsNamesWithDispatchMessage)
b.statusNode.CommunityTokensService().Init(messenger)
2022-02-10 15:15:27 +00:00
}
2021-08-05 13:27:47 +00:00
2022-02-10 15:15:27 +00:00
return nil
}
2021-05-14 10:55:42 +00:00
feat: fallback pairing seed (#5614) * feat(pairing)!: Add extra parameters and remove v2 compatibility This commit includes the following changes: I have added a flag to maintain 2.29 compatibility. Breaking change in connection string The local pairing code that was parsing the connection string had a few non-upgradable features: It was strictly checking the number of parameters, throwing an error if the number was different. This made it impossible to add parameters to it without breaking. It was strictly checking the version number. This made increasing the version number impossible as older client would just refuse to connect. The code has been changed so that: Two parameters have been added, installation-id and key-uid. Those are needed for the fallback flow. I have also removed version from the payload, since it wasn't used. This means that we don't support v1 anymore. V2 parsing is supported . Going forward there's a clear strategy on how to update the protocol (append parameters, don't change existing one). https://www.youtube.com/watch?v=oyLBGkS5ICk Is a must watch video for understanding the strategy Changed MessengerResponse to use internally a map of installations rather than an array (minor) Just moving towards maps as arrays tend to lead to subtle bugs. Moved pairing methods to messenger_pairing.go Just moved some methods Added 2 new methods for the fallback flow FinishPairingThroughSeedPhraseProcess https://github.com/status-im/status-go/pull/5567/files#diff-1ad620b07fa3bd5fbc96c9f459d88829938a162bf1aaf41c61dea6e38b488d54R29 EnableAndSyncInstallation https://github.com/status-im/status-go/pull/5567/files#diff-1ad620b07fa3bd5fbc96c9f459d88829938a162bf1aaf41c61dea6e38b488d54R18 Flow for clients Client A1 is logged in Client A2 is logged out Client A1 shows a QR code Client A2 scans a QR code If connection fails on A2, the user will be prompted to enter a seed phrase. If the generated account matches the key-uid from the QR code, A2 should call FinishPairingThroughSeedPhraseProcess with the installation id passed in the QR code. This will send installation information over waku. The user should be shown its own installation id and prompted to check the other device. Client A1 will receive new installation data through waku, if they are still on the qr code page, they should show a popup to the user showing the received installation id, and a way to Enable and Sync, which should call the EnableAndSyncInstallation endpoint. This should finish the fallback syncing flow. Current issues Currently I haven't tested that all the data is synced after finishing the flow. I see that the two devices are paired correctly, but for example the DisplayName is not changed on the receiving device. I haven't had time to look into it further. * test_: add more test for connection string parser * fix_: fix panic when parse old connection string * test_: add comments for TestMessengerPairAfterSeedPhrase * fix_: correct error description * feat_:rename FinishPairingThroughSeedPhraseProcess to EnableInstallationAndPair * fix_: delete leftover * fix_: add UniqueKey method * fix_: unify the response for InputConnectionStringForBootstrapping * fix_: remove fields installationID and keyUID in GethStatusBackend * fix_: rename messenger_pairing to messenger_pairing_and_syncing --------- Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2024-07-30 09:14:05 +00:00
func (b *GethStatusBackend) InstallationID() string {
m := b.Messenger()
if m != nil {
return m.InstallationID()
}
return ""
}
func (b *GethStatusBackend) KeyUID() string {
m := b.Messenger()
if m != nil {
return m.KeyUID()
}
return ""
}
2022-02-10 15:15:27 +00:00
func (b *GethStatusBackend) injectAccountsIntoServices() error {
if b.statusNode.WakuService() != nil {
return b.injectAccountsIntoWakuService(b.statusNode.WakuService(), func() *ext.Service {
if b.statusNode.WakuExtService() == nil {
return nil
}
return b.statusNode.WakuExtService().Service
}())
}
2022-02-10 15:15:27 +00:00
if b.statusNode.WakuV2Service() != nil {
return b.injectAccountsIntoWakuService(b.statusNode.WakuV2Service(), func() *ext.Service {
if b.statusNode.WakuV2ExtService() == nil {
return nil
}
return b.statusNode.WakuV2ExtService().Service
}())
}
return nil
}
// ExtractGroupMembershipSignatures extract signatures from tuples of content/signature
func (b *GethStatusBackend) ExtractGroupMembershipSignatures(signaturePairs [][2]string) ([]string, error) {
return crypto.ExtractSignatures(signaturePairs)
}
// SignGroupMembership signs a piece of data containing membership information
func (b *GethStatusBackend) SignGroupMembership(content string) (string, error) {
selectedChatAccount, err := b.accountManager.SelectedChatAccount()
if err != nil {
return "", err
}
return crypto.SignStringAsHex(content, selectedChatAccount.AccountKey.PrivateKey)
}
func (b *GethStatusBackend) Messenger() *protocol.Messenger {
node := b.StatusNode()
if node != nil {
accountService := node.AccountService()
if accountService != nil {
return accountService.GetMessenger()
}
}
return nil
}
// SignHash exposes vanilla ECDSA signing for signing a message for Swarm
func (b *GethStatusBackend) SignHash(hexEncodedHash string) (string, error) {
hash, err := hexutil.Decode(hexEncodedHash)
if err != nil {
return "", fmt.Errorf("SignHash: could not unmarshal the input: %v", err)
}
chatAccount, err := b.accountManager.SelectedChatAccount()
if err != nil {
return "", fmt.Errorf("SignHash: could not select account: %v", err.Error())
}
signature, err := ethcrypto.Sign(hash, chatAccount.AccountKey.PrivateKey)
if err != nil {
return "", fmt.Errorf("SignHash: could not sign the hash: %v", err)
}
hexEncodedSignature := types.EncodeHex(signature)
return hexEncodedSignature, nil
}
2022-03-17 18:06:02 +00:00
func (b *GethStatusBackend) SwitchFleet(fleet string, conf *params.NodeConfig) error {
if b.appDB == nil {
return ErrDBNotAvailable
}
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
accountDB, err := accounts.NewDB(b.appDB)
if err != nil {
return err
}
err = accountDB.SaveSetting("fleet", fleet)
2022-03-17 18:06:02 +00:00
if err != nil {
return err
}
err = nodecfg.SaveNodeConfig(b.appDB, conf)
if err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) getAppDBPath(keyUID string) (string, error) {
if len(b.rootDataDir) == 0 {
return "", errors.New("root datadir wasn't provided")
}
return filepath.Join(b.rootDataDir, fmt.Sprintf("%s-v4.db", keyUID)), nil
}
func (b *GethStatusBackend) getWalletDBPath(keyUID string) (string, error) {
if len(b.rootDataDir) == 0 {
return "", errors.New("root datadir wasn't provided")
}
return filepath.Join(b.rootDataDir, fmt.Sprintf("%s-wallet.db", keyUID)), nil
}