package api import ( "context" "crypto/sha256" "database/sql" "encoding/hex" "encoding/json" "errors" "fmt" "io/ioutil" "math/rand" "os" "path" "path/filepath" "strings" "sync" "testing" "time" "github.com/status-im/status-go/protocol/tt" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" gethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/status-im/status-go/appdatabase" "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/multiaccounts" "github.com/status-im/status-go/multiaccounts/accounts" "github.com/status-im/status-go/multiaccounts/settings" "github.com/status-im/status-go/node" "github.com/status-im/status-go/params" "github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/rpc" "github.com/status-im/status-go/services/typeddata" "github.com/status-im/status-go/services/wallet" walletservice "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/t/helpers" "github.com/status-im/status-go/t/utils" "github.com/status-im/status-go/transactions" "github.com/status-im/status-go/walletdatabase" ) var ( networks = json.RawMessage("{}") testSettings = settings.Settings{ Address: types.HexToAddress("0xeC540f3745Ff2964AFC1171a5A0DD726d1F6B472"), DisplayName: "UserDisplayName", CurrentNetwork: "mainnet_rpc", DappsAddress: types.HexToAddress("0xe1300f99fDF7346986CbC766903245087394ecd0"), EIP1581Address: types.HexToAddress("0xe1DDDE9235a541d1344550d969715CF43982de9f"), InstallationID: "d3efcff6-cffa-560e-a547-21d3858cbc51", KeyUID: "0x4e8129f3edfc004875be17bf468a784098a9f69b53c095be1f52deff286935ab", LatestDerivedPath: 0, Name: "Jittery Cornflowerblue Kingbird", Networks: &networks, PhotoPath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==", PreviewPrivacy: false, PublicKey: "0x04211fe0f69772ecf7eb0b5bfc7678672508a9fb01f2d699096f0d59ef7fe1a0cb1e648a80190db1c0f5f088872444d846f2956d0bd84069f3f9f69335af852ac0", SigningPhrase: "yurt joey vibe", WalletRootAddress: types.HexToAddress("0xeB591fd819F86D0A6a2EF2Bcb94f77807a7De1a6")} ) func setupTestDB() (*sql.DB, func() error, error) { return helpers.SetupTestSQLDB(appdatabase.DbInitializer{}, "tests") } func setupTestWalletDB() (*sql.DB, func() error, error) { return helpers.SetupTestSQLDB(walletdatabase.DbInitializer{}, "tests") } func setupTestMultiDB() (*multiaccounts.Database, func() error, error) { tmpfile, err := ioutil.TempFile("", "tests") if err != nil { return nil, nil, err } db, err := multiaccounts.InitializeDB(tmpfile.Name()) if err != nil { return nil, nil, err } return db, func() error { err := db.Close() if err != nil { return err } return os.Remove(tmpfile.Name()) }, nil } func setupGethStatusBackend() (*GethStatusBackend, func() error, func() error, func() error, error) { db, stop1, err := setupTestDB() if err != nil { return nil, nil, nil, nil, err } backend := NewGethStatusBackend() backend.StatusNode().SetAppDB(db) ma, stop2, err := setupTestMultiDB() if err != nil { return nil, nil, nil, nil, err } backend.StatusNode().SetMultiaccountsDB(ma) walletDb, stop3, err := setupTestWalletDB() if err != nil { return nil, nil, nil, nil, err } backend.StatusNode().SetWalletDB(walletDb) return backend, stop1, stop2, stop3, err } func TestBackendStartNodeConcurrently(t *testing.T) { utils.Init() backend, stop1, stop2, stop3, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop3() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) count := 2 resultCh := make(chan error) var wg sync.WaitGroup wg.Add(count) for i := 0; i < count; i++ { go func() { resultCh <- backend.StartNode(config) wg.Done() }() } // close channel as otherwise for loop never finishes go func() { wg.Wait(); close(resultCh) }() var results []error for err := range resultCh { results = append(results, err) } require.Contains(t, results, nil) require.Contains(t, results, node.ErrNodeRunning) err = backend.StopNode() require.NoError(t, err) } func TestBackendRestartNodeConcurrently(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) count := 3 require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) require.NoError(t, backend.StartNode(config)) defer func() { require.NoError(t, backend.StopNode()) }() var wg sync.WaitGroup wg.Add(count) for i := 0; i < count; i++ { go func(idx int) { assert.NoError(t, backend.RestartNode()) wg.Done() }(i) } wg.Wait() } // TODO(adam): add concurrent tests for ResetChainData() func TestBackendGettersConcurrently(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) err = backend.StartNode(config) require.NoError(t, err) defer func() { require.NoError(t, backend.StopNode()) }() var wg sync.WaitGroup wg.Add(1) go func() { assert.NotNil(t, backend.StatusNode()) wg.Done() }() wg.Add(1) go func() { assert.NotNil(t, backend.AccountManager()) wg.Done() }() wg.Add(1) go func() { assert.NotNil(t, backend.personalAPI) wg.Done() }() wg.Add(1) go func() { assert.NotNil(t, backend.Transactor()) wg.Done() }() wg.Add(1) go func() { assert.True(t, backend.IsNodeRunning()) wg.Done() }() wg.Add(1) go func() { assert.True(t, backend.IsNodeRunning()) wg.Done() }() wg.Wait() } func TestBackendConnectionChangesConcurrently(t *testing.T) { connections := [...]string{connection.Wifi, connection.Cellular, connection.Unknown} backend := NewGethStatusBackend() count := 3 var wg sync.WaitGroup for i := 0; i < count; i++ { wg.Add(1) go func() { connIdx := rand.Intn(len(connections)) // nolint: gosec backend.ConnectionChange(connections[connIdx], false) wg.Done() }() } wg.Wait() } func TestBackendConnectionChangesToOffline(t *testing.T) { b := NewGethStatusBackend() b.ConnectionChange(connection.None, false) assert.True(t, b.connectionState.Offline) b.ConnectionChange(connection.Wifi, false) assert.False(t, b.connectionState.Offline) b.ConnectionChange("unknown-state", false) assert.False(t, b.connectionState.Offline) } func TestBackendCallRPCConcurrently(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) count := 3 err = backend.StartNode(config) require.NoError(t, err) defer func() { require.NoError(t, backend.StopNode()) }() var wg sync.WaitGroup for i := 0; i < count; i++ { wg.Add(1) go func(idx int) { result, err := backend.CallRPC(fmt.Sprintf( `{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`, idx+1, )) assert.NoError(t, err) assert.NotContains(t, result, "error") wg.Done() }(i) wg.Add(1) go func(idx int) { result, err := backend.CallPrivateRPC(fmt.Sprintf( `{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":%d}`, idx+1, )) assert.NoError(t, err) assert.NotContains(t, result, "error") wg.Done() }(i) } wg.Wait() } func TestAppStateChange(t *testing.T) { backend := NewGethStatusBackend() var testCases = []struct { name string fromState appState toState appState expectedState appState }{ { name: "success", fromState: appStateInactive, toState: appStateBackground, expectedState: appStateBackground, }, { name: "invalid state", fromState: appStateInactive, toState: "unexisting", expectedState: appStateInactive, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { backend.appState = tc.fromState backend.AppStateChange(tc.toState.String()) assert.Equal(t, tc.expectedState.String(), backend.appState.String()) }) } } func TestBlockedRPCMethods(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) err = backend.StartNode(config) require.NoError(t, err) defer func() { require.NoError(t, backend.StopNode()) }() for idx, m := range rpc.BlockedMethods() { result, err := backend.CallRPC(fmt.Sprintf( `{"jsonrpc":"2.0","method":"%s","params":[],"id":%d}`, m, idx+1, )) assert.NoError(t, err) assert.Contains(t, result, fmt.Sprintf(`{"code":-32700,"message":"%s"}`, rpc.ErrMethodNotFound)) } } func TestCallRPCWithStoppedNode(t *testing.T) { backend := NewGethStatusBackend() resp, err := backend.CallRPC( `{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}`, ) assert.Equal(t, ErrRPCClientUnavailable, err) assert.Equal(t, "", resp) resp, err = backend.CallPrivateRPC( `{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}`, ) assert.Equal(t, ErrRPCClientUnavailable, err) assert.Equal(t, "", resp) } // TODO(adam): add concurrent tests for: SendTransaction func TestStartStopMultipleTimes(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) config.NoDiscovery = false // doesn't have to be running. just any valid enode to bypass validation. config.ClusterConfig.BootNodes = []string{ "enode://e8a7c03b58911e98bbd66accb2a55d57683f35b23bf9dfca89e5e244eb5cc3f25018b4112db507faca34fb69ffb44b362f79eda97a669a8df29c72e654416784@0.0.0.0:30404", } require.NoError(t, err) require.NoError(t, backend.StartNode(config)) require.NoError(t, backend.StopNode()) require.NoError(t, backend.StartNode(config)) require.NoError(t, backend.StopNode()) } func TestHashTypedData(t *testing.T) { utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID) require.NoError(t, err) require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir)) err = backend.StartNode(config) require.NoError(t, err) defer func() { require.NoError(t, backend.StopNode()) }() eip712Domain := "EIP712Domain" mytypes := typeddata.Types{ eip712Domain: []typeddata.Field{ {Name: "name", Type: "string"}, {Name: "version", Type: "string"}, {Name: "chainId", Type: "uint256"}, {Name: "verifyingContract", Type: "address"}, }, "Text": []typeddata.Field{ {Name: "body", Type: "string"}, }, } domain := map[string]json.RawMessage{ "name": json.RawMessage(`"Ether Text"`), "version": json.RawMessage(`"1"`), "chainId": json.RawMessage(fmt.Sprintf("%d", params.StatusChainNetworkID)), "verifyingContract": json.RawMessage(`"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"`), } msg := map[string]json.RawMessage{ "body": json.RawMessage(`"Hello, Bob!"`), } typed := typeddata.TypedData{ Types: mytypes, PrimaryType: "Text", Domain: domain, Message: msg, } hash, err := backend.HashTypedData(typed) require.NoError(t, err) assert.NotEqual(t, types.Hash{}, hash) } func TestBackendGetVerifiedAccount(t *testing.T) { utils.Init() password := "test" backend, defers, err := setupWalletTest(t, password) require.NoError(t, err) defer defers() t.Run("AccountDoesntExist", func(t *testing.T) { pkey, err := gethcrypto.GenerateKey() require.NoError(t, err) address := gethcrypto.PubkeyToAddress(pkey.PublicKey) key, err := backend.getVerifiedWalletAccount(address.String(), password) require.EqualError(t, err, transactions.ErrAccountDoesntExist.Error()) require.Nil(t, key) }) t.Run("PasswordDoesntMatch", func(t *testing.T) { pkey, err := crypto.GenerateKey() require.NoError(t, err) address := crypto.PubkeyToAddress(pkey.PublicKey) keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&pkey.PublicKey)) keyUID := types.EncodeHex(keyUIDHex[:]) db, err := accounts.NewDB(backend.appDB) require.NoError(t, err) _, err = backend.AccountManager().ImportAccount(pkey, password) require.NoError(t, err) require.NoError(t, db.SaveOrUpdateKeypair(&accounts.Keypair{ KeyUID: keyUID, Name: "private key keypair", Type: accounts.KeypairTypeKey, Accounts: []*accounts.Account{ &accounts.Account{ Address: address, KeyUID: keyUID, }, }, })) key, err := backend.getVerifiedWalletAccount(address.String(), "wrong-password") require.EqualError(t, err, "could not decrypt key with given password") require.Nil(t, key) }) t.Run("PartialAccount", func(t *testing.T) { // Create a derived wallet account without storing the keys db, err := accounts.NewDB(backend.appDB) require.NoError(t, err) newPath := "m/0" walletRootAddress, err := db.GetWalletRootAddress() require.NoError(t, err) walletInfo, err := backend.AccountManager().AccountsGenerator().LoadAccount(walletRootAddress.String(), password) require.NoError(t, err) derivedInfos, err := backend.AccountManager().AccountsGenerator().DeriveAddresses(walletInfo.ID, []string{newPath}) require.NoError(t, err) derivedInfo := derivedInfos[newPath] keypair := &accounts.Keypair{ KeyUID: walletInfo.KeyUID, Name: "profile keypair", Type: accounts.KeypairTypeProfile, Accounts: []*accounts.Account{ &accounts.Account{ Address: types.HexToAddress(derivedInfo.Address), KeyUID: walletInfo.KeyUID, Type: accounts.AccountTypeGenerated, PublicKey: types.Hex2Bytes(derivedInfo.PublicKey), Path: newPath, Wallet: false, Name: "PartialAccount", }, }, } require.NoError(t, db.SaveOrUpdateKeypair(keypair)) // With partial account we need to dynamically generate private key key, err := backend.getVerifiedWalletAccount(keypair.Accounts[0].Address.Hex(), password) require.NoError(t, err) require.Equal(t, keypair.Accounts[0].Address, key.Address) }) t.Run("Success", func(t *testing.T) { pkey, err := crypto.GenerateKey() require.NoError(t, err) address := crypto.PubkeyToAddress(pkey.PublicKey) keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&pkey.PublicKey)) keyUID := types.EncodeHex(keyUIDHex[:]) db, err := accounts.NewDB(backend.appDB) require.NoError(t, err) defer db.Close() _, err = backend.AccountManager().ImportAccount(pkey, password) require.NoError(t, err) require.NoError(t, db.SaveOrUpdateKeypair(&accounts.Keypair{ KeyUID: keyUID, Name: "private key keypair", Type: accounts.KeypairTypeKey, Accounts: []*accounts.Account{ &accounts.Account{ Address: address, KeyUID: keyUID, }, }, })) key, err := backend.getVerifiedWalletAccount(address.String(), password) require.NoError(t, err) require.Equal(t, address, key.Address) }) } func TestRuntimeLogLevelIsNotWrittenToDatabase(t *testing.T) { utils.Init() b := NewGethStatusBackend() chatKey, err := gethcrypto.GenerateKey() require.NoError(t, err) walletKey, err := gethcrypto.GenerateKey() require.NoError(t, err) keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&chatKey.PublicKey)) keyUID := types.EncodeHex(keyUIDHex[:]) main := multiaccounts.Account{ KeyUID: keyUID, } tmpdir := t.TempDir() json := `{ "NetworkId": 3, "DataDir": "` + tmpdir + `", "KeyStoreDir": "` + tmpdir + `", "KeycardPairingDataFile": "` + path.Join(tmpdir, "keycard/pairings.json") + `", "NoDiscovery": true, "TorrentConfig": { "Port": 9025, "Enabled": false, "DataDir": "` + tmpdir + `/archivedata", "TorrentDir": "` + tmpdir + `/torrents" }, "RuntimeLogLevel": "INFO", "LogLevel": "DEBUG" }` conf, err := params.NewConfigFromJSON(json) require.NoError(t, err) require.Equal(t, "INFO", conf.RuntimeLogLevel) keyhex := hex.EncodeToString(gethcrypto.FromECDSA(chatKey)) require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir)) b.UpdateRootDataDir(conf.DataDir) require.NoError(t, b.OpenAccounts()) require.NotNil(t, b.statusNode.HTTPServer()) address := crypto.PubkeyToAddress(walletKey.PublicKey) settings := testSettings settings.KeyUID = keyUID settings.Address = crypto.PubkeyToAddress(walletKey.PublicKey) chatPubKey := crypto.FromECDSAPub(&chatKey.PublicKey) require.NoError(t, b.SaveAccountAndStartNodeWithKey(main, "test-pass", settings, conf, []*accounts.Account{ {Address: address, KeyUID: keyUID, Wallet: true}, {Address: crypto.PubkeyToAddress(chatKey.PublicKey), KeyUID: keyUID, Chat: true, PublicKey: chatPubKey}}, keyhex)) require.NoError(t, b.Logout()) require.NoError(t, b.StopNode()) require.NoError(t, b.StartNodeWithKey(main, "test-pass", keyhex, conf)) defer func() { assert.NoError(t, b.Logout()) assert.NoError(t, b.StopNode()) }() c, err := b.GetNodeConfig() require.NoError(t, err) require.Equal(t, "", c.RuntimeLogLevel) require.Equal(t, "DEBUG", c.LogLevel) } func TestLoginWithKey(t *testing.T) { utils.Init() b := NewGethStatusBackend() chatKey, err := gethcrypto.GenerateKey() require.NoError(t, err) walletKey, err := gethcrypto.GenerateKey() require.NoError(t, err) keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&chatKey.PublicKey)) keyUID := types.EncodeHex(keyUIDHex[:]) main := multiaccounts.Account{ KeyUID: keyUID, } tmpdir := t.TempDir() conf, err := params.NewNodeConfig(tmpdir, 1777) require.NoError(t, err) keyhex := hex.EncodeToString(gethcrypto.FromECDSA(chatKey)) require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir)) b.UpdateRootDataDir(conf.DataDir) require.NoError(t, b.OpenAccounts()) require.NotNil(t, b.statusNode.HTTPServer()) address := crypto.PubkeyToAddress(walletKey.PublicKey) settings := testSettings settings.KeyUID = keyUID settings.Address = crypto.PubkeyToAddress(walletKey.PublicKey) chatPubKey := crypto.FromECDSAPub(&chatKey.PublicKey) require.NoError(t, b.SaveAccountAndStartNodeWithKey(main, "test-pass", settings, conf, []*accounts.Account{ {Address: address, KeyUID: keyUID, Wallet: true}, {Address: crypto.PubkeyToAddress(chatKey.PublicKey), KeyUID: keyUID, Chat: true, PublicKey: chatPubKey}}, keyhex)) require.NoError(t, b.Logout()) require.NoError(t, b.StopNode()) require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir)) b.UpdateRootDataDir(conf.DataDir) require.NoError(t, b.OpenAccounts()) require.NoError(t, b.StartNodeWithKey(main, "test-pass", keyhex, conf)) defer func() { assert.NoError(t, b.Logout()) assert.NoError(t, b.StopNode()) }() extkey, err := b.accountManager.SelectedChatAccount() require.NoError(t, err) require.Equal(t, crypto.PubkeyToAddress(chatKey.PublicKey), extkey.Address) activeAccount, err := b.GetActiveAccount() require.NoError(t, err) require.NotNil(t, activeAccount.ColorHash) } func TestLoginAccount(t *testing.T) { utils.Init() password := "some-password" tmpdir := t.TempDir() nameserver := "8.8.8.8" b := NewGethStatusBackend() createAccountRequest := &requests.CreateAccount{ DisplayName: "some-display-name", CustomizationColor: "#ffffff", Password: password, RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", WakuV2Nameserver: &nameserver, WakuV2Fleet: "status.staging", } c := make(chan interface{}, 10) signal.SetMobileSignalHandler(func(data []byte) { if strings.Contains(string(data), signal.EventLoggedIn) { require.Contains(t, string(data), "status.staging") c <- struct{}{} } }) defer signal.SetMobileSignalHandler(nil) waitForLogin := func(chan interface{}) { select { case <-c: break case <-time.After(5 * time.Second): t.FailNow() } } acc, err := b.CreateAccountAndLogin(createAccountRequest) require.NoError(t, err) require.Equal(t, nameserver, b.config.WakuV2Config.Nameserver) waitForLogin(c) require.NoError(t, b.Logout()) require.NoError(t, b.StopNode()) accounts, err := b.GetAccounts() require.NoError(t, err) require.Len(t, accounts, 1) require.NotEmpty(t, accounts[0].KeyUID) require.Equal(t, acc.KeyUID, accounts[0].KeyUID) loginAccountRequest := &requests.Login{ KeyUID: accounts[0].KeyUID, Password: password, WakuV2Nameserver: nameserver, } err = b.LoginAccount(loginAccountRequest) require.NoError(t, err) waitForLogin(c) require.Equal(t, nameserver, b.config.WakuV2Config.Nameserver) } func TestEnableInstallationAndPair(t *testing.T) { // create account acc utils.Init() displayName := "some-display-name" password := "some-password" tmpdir := t.TempDir() nameserver := "8.8.8.8" b := NewGethStatusBackend() createAccountRequest := &requests.CreateAccount{ DisplayName: displayName, CustomizationColor: "#ffffff", Password: password, RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", WakuV2Nameserver: &nameserver, WakuV2Fleet: "status.staging", } acc, err := b.CreateAccountAndLogin(createAccountRequest) require.NoError(t, err) require.NotNil(t, acc) _, err = b.Messenger().Start() require.NoError(t, err) s, err := b.GetSettings() require.NoError(t, err) mn := *s.Mnemonic db, err := accounts.NewDB(b.appDB) require.NoError(t, err) n, err := db.GetSettingLastSynced(settings.DisplayName) require.NoError(t, err) require.True(t, n > 0) // restore account acc as acc2 use Mnemonic from acc restoreRequest := &requests.RestoreAccount{ Mnemonic: mn, FetchBackup: true, CreateAccount: requests.CreateAccount{ Password: password, CustomizationColor: "0x000000", RootDataDir: t.TempDir(), }, } b2 := NewGethStatusBackend() acc2, err := b2.RestoreAccountAndLogin(restoreRequest) require.NoError(t, err) require.NotNil(t, acc2) _, err = b2.Messenger().Start() require.NoError(t, err) s2, err := b2.GetSettings() require.NoError(t, err) t.Logf("acc2 settings.name: %s", s2.Name) // should be 3 words random name require.Len(t, strings.Split(s2.Name, " "), 3) require.Empty(t, acc2.Name) require.Empty(t, s2.DisplayName) db2, err := accounts.NewDB(b2.appDB) require.NoError(t, err) n, err = db2.GetSettingLastSynced(settings.DisplayName) require.NoError(t, err) require.True(t, n == 0) // pair installation _, err = b2.Messenger().EnableInstallationAndPair(&requests.EnableInstallationAndPair{InstallationID: s.InstallationID}) require.NoError(t, err) // ensure acc received the installation from acc2 err = tt.RetryWithBackOff(func() error { r, err := b.Messenger().RetrieveAll() require.NoError(t, err) if len(r.Installations()) > 0 { return nil } return errors.New("new installation not received yet") }) require.NoError(t, err) // sync data from acc to acc2 err = b.Messenger().EnableAndSyncInstallation(&requests.EnableAndSyncInstallation{InstallationID: s2.InstallationID}) require.NoError(t, err) // ensure acc2's display name get synced err = tt.RetryWithBackOff(func() error { r, err := b2.Messenger().RetrieveAll() require.NoError(t, err) for _, ss := range r.Settings { if ss.GetDBName() == "display_name" { return nil } } return errors.New("display name setting not received yet") }) require.NoError(t, err) // check display name for acc2 s2, err = b2.GetSettings() require.NoError(t, err) require.Equal(t, displayName, s2.DisplayName) acc2, err = b2.GetActiveAccount() require.NoError(t, err) require.Equal(t, displayName, acc2.Name) } func TestVerifyDatabasePassword(t *testing.T) { utils.Init() b := NewGethStatusBackend() chatKey, err := gethcrypto.GenerateKey() require.NoError(t, err) walletKey, err := gethcrypto.GenerateKey() require.NoError(t, err) keyUIDHex := sha256.Sum256(gethcrypto.FromECDSAPub(&chatKey.PublicKey)) keyUID := types.EncodeHex(keyUIDHex[:]) main := multiaccounts.Account{ KeyUID: keyUID, } tmpdir := t.TempDir() conf, err := params.NewNodeConfig(tmpdir, 1777) require.NoError(t, err) keyhex := hex.EncodeToString(gethcrypto.FromECDSA(chatKey)) require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir)) b.UpdateRootDataDir(conf.DataDir) require.NoError(t, b.OpenAccounts()) address := crypto.PubkeyToAddress(walletKey.PublicKey) settings := testSettings settings.KeyUID = keyUID settings.Address = crypto.PubkeyToAddress(walletKey.PublicKey) chatPubKey := crypto.FromECDSAPub(&chatKey.PublicKey) require.NoError(t, b.SaveAccountAndStartNodeWithKey(main, "test-pass", settings, conf, []*accounts.Account{ {Address: address, KeyUID: keyUID, Wallet: true}, {Address: crypto.PubkeyToAddress(chatKey.PublicKey), KeyUID: keyUID, Chat: true, PublicKey: chatPubKey}}, keyhex)) require.NoError(t, b.Logout()) require.NoError(t, b.StopNode()) require.Error(t, b.VerifyDatabasePassword(main.KeyUID, "wrong-pass")) require.NoError(t, b.VerifyDatabasePassword(main.KeyUID, "test-pass")) } func TestDeleteMultiaccount(t *testing.T) { backend := NewGethStatusBackend() rootDataDir := t.TempDir() keyStoreDir := filepath.Join(rootDataDir, "keystore") backend.rootDataDir = rootDataDir err := backend.AccountManager().InitKeystore(keyStoreDir) require.NoError(t, err) backend.AccountManager() accs, err := backend.AccountManager(). AccountsGenerator(). GenerateAndDeriveAddresses(12, 1, "", []string{"m/44'/60'/0'/0"}) require.NoError(t, err) generateAccount := accs[0] accountInfo, err := backend.AccountManager(). AccountsGenerator(). StoreAccount(generateAccount.ID, "123123") require.NoError(t, err) account := multiaccounts.Account{ Name: "foo", Timestamp: 1, KeycardPairing: "pairing", KeyUID: generateAccount.KeyUID, } err = backend.ensureAppDBOpened(account, "123123") require.NoError(t, err) s := settings.Settings{ Address: types.HexToAddress(accountInfo.Address), CurrentNetwork: "mainnet_rpc", DappsAddress: types.HexToAddress(accountInfo.Address), EIP1581Address: types.HexToAddress(accountInfo.Address), InstallationID: "d3efcff6-cffa-560e-a547-21d3858cbc51", KeyUID: account.KeyUID, LatestDerivedPath: 0, Name: "Jittery Cornflowerblue Kingbird", Networks: &networks, PhotoPath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==", PreviewPrivacy: false, PublicKey: accountInfo.PublicKey, SigningPhrase: "yurt joey vibe", WalletRootAddress: types.HexToAddress(accountInfo.Address)} err = backend.saveAccountsAndSettings( s, ¶ms.NodeConfig{}, nil) require.Error(t, err) require.True(t, err == accounts.ErrKeypairWithoutAccounts) err = backend.OpenAccounts() require.NoError(t, err) err = backend.SaveAccount(account) require.NoError(t, err) files, err := ioutil.ReadDir(rootDataDir) require.NoError(t, err) require.NotEqual(t, 3, len(files)) err = backend.DeleteMultiaccount(account.KeyUID, keyStoreDir) require.NoError(t, err) files, err = ioutil.ReadDir(rootDataDir) require.NoError(t, err) require.Equal(t, 3, len(files)) } func TestConvertAccount(t *testing.T) { const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" const password = "111111" // represents password for a regular user const keycardPassword = "222222" // represents password for a keycard user const keycardUID = "1234" const pathEIP1581Root = "m/43'/60'/1581'" const pathEIP1581Chat = pathEIP1581Root + "/0'/0" const pathWalletRoot = "m/44'/60'/0'/0" const pathDefaultWalletAccount = pathWalletRoot + "/0" const customWalletPath1 = pathWalletRoot + "/1" const customWalletPath2 = pathWalletRoot + "/2" var allGeneratedPaths []string allGeneratedPaths = append(allGeneratedPaths, pathEIP1581Root, pathEIP1581Chat, pathWalletRoot, pathDefaultWalletAccount, customWalletPath1, customWalletPath2) var err error keystoreContainsFileForAccount := func(keyStoreDir string, hexAddress string) bool { addrWithoutPrefix := strings.ToLower(hexAddress[2:]) found := false err = filepath.Walk(keyStoreDir, func(path string, fileInfo os.FileInfo, err error) error { if err != nil { return err } if !fileInfo.IsDir() && strings.Contains(strings.ToUpper(path), strings.ToUpper(addrWithoutPrefix)) { found = true } return nil }) return found } rootDataDir := t.TempDir() keyStoreDir := filepath.Join(rootDataDir, "keystore") utils.Init() backend, stop1, stop2, stopWallet, err := setupGethStatusBackend() defer func() { err := stop1() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stop2() if err != nil { require.NoError(t, backend.StopNode()) } }() defer func() { err := stopWallet() if err != nil { require.NoError(t, backend.StopNode()) } }() require.NoError(t, err) backend.rootDataDir = rootDataDir require.NoError(t, backend.AccountManager().InitKeystore(keyStoreDir)) err = backend.OpenAccounts() require.NoError(t, err) genAccInfo, err := backend.AccountManager().AccountsGenerator().ImportMnemonic(mnemonic, "") assert.NoError(t, err) masterAddress := genAccInfo.Address accountInfo, err := backend.AccountManager().AccountsGenerator().StoreAccount(genAccInfo.ID, password) assert.NoError(t, err) found := keystoreContainsFileForAccount(keyStoreDir, accountInfo.Address) require.True(t, found) derivedAccounts, err := backend.AccountManager().AccountsGenerator().StoreDerivedAccounts(genAccInfo.ID, password, allGeneratedPaths) assert.NoError(t, err) chatKey := derivedAccounts[pathEIP1581Chat].PrivateKey[2:] chatAddress := derivedAccounts[pathEIP1581Chat].Address found = keystoreContainsFileForAccount(keyStoreDir, chatAddress) require.True(t, found) defaultSettings, err := defaultSettings(genAccInfo.KeyUID, genAccInfo.Address, derivedAccounts) require.NoError(t, err) nodeConfig, err := defaultNodeConfig(defaultSettings.InstallationID, &requests.CreateAccount{ LogLevel: defaultSettings.LogLevel, }) require.NoError(t, err) nodeConfig.DataDir = rootDataDir nodeConfig.KeyStoreDir = keyStoreDir profileKeypair := &accounts.Keypair{ KeyUID: genAccInfo.KeyUID, Name: "Profile Name", Type: accounts.KeypairTypeProfile, DerivedFrom: masterAddress, } profileKeypair.Accounts = append(profileKeypair.Accounts, &accounts.Account{ Address: types.HexToAddress(chatAddress), KeyUID: profileKeypair.KeyUID, Type: accounts.AccountTypeGenerated, PublicKey: types.Hex2Bytes(accountInfo.PublicKey), Path: pathEIP1581Chat, Wallet: false, Chat: true, Name: "GeneratedAccount", }) for p, dAccInfo := range derivedAccounts { found = keystoreContainsFileForAccount(keyStoreDir, dAccInfo.Address) require.NoError(t, err) require.True(t, found) if p == pathDefaultWalletAccount || p == customWalletPath1 || p == customWalletPath2 { wAcc := &accounts.Account{ Address: types.HexToAddress(dAccInfo.Address), KeyUID: genAccInfo.KeyUID, Wallet: false, Chat: false, Type: accounts.AccountTypeGenerated, Path: p, Name: "derivacc" + p, Hidden: false, Removed: false, } if p == pathDefaultWalletAccount { wAcc.Wallet = true } profileKeypair.Accounts = append(profileKeypair.Accounts, wAcc) } } account := multiaccounts.Account{ Name: profileKeypair.Name, Timestamp: 1, KeyUID: profileKeypair.KeyUID, } err = backend.ensureAppDBOpened(account, password) require.NoError(t, err) err = backend.StartNodeWithAccountAndInitialConfig(account, password, *defaultSettings, nodeConfig, profileKeypair.Accounts, nil) require.NoError(t, err) multiaccounts, err := backend.GetAccounts() require.NoError(t, err) require.NotEmpty(t, multiaccounts[0].ColorHash) serverMessenger := backend.Messenger() require.NotNil(t, serverMessenger) files, err := ioutil.ReadDir(rootDataDir) require.NoError(t, err) require.NotEqual(t, 3, len(files)) keycardAccount := account keycardAccount.KeycardPairing = "pairing" keycardSettings := settings.Settings{ KeycardInstanceUID: "0xdeadbeef", KeycardPairedOn: 1, KeycardPairing: "pairing", } // Ensure we're able to open the DB err = backend.ensureAppDBOpened(keycardAccount, keycardPassword) require.NoError(t, err) // db creation db, err := accounts.NewDB(backend.appDB) require.NoError(t, err) // Check that there is no registered keycards keycards, err := db.GetKeycardsWithSameKeyUID(genAccInfo.KeyUID) require.NoError(t, err) require.Equal(t, 0, len(keycards)) // Converting to a keycard account err = backend.ConvertToKeycardAccount(keycardAccount, keycardSettings, keycardUID, password, keycardPassword) require.NoError(t, err) // Validating results of converting to a keycard account. // All keystore files for the account which is migrated need to be removed. found = keystoreContainsFileForAccount(keyStoreDir, masterAddress) require.False(t, found) for _, dAccInfo := range derivedAccounts { found = keystoreContainsFileForAccount(keyStoreDir, dAccInfo.Address) require.False(t, found) } require.NoError(t, backend.Logout()) require.NoError(t, backend.StopNode()) require.NoError(t, backend.AccountManager().InitKeystore(keyStoreDir)) require.NoError(t, backend.OpenAccounts()) require.NoError(t, backend.StartNodeWithKey(account, keycardPassword, chatKey, nodeConfig)) defer func() { assert.NoError(t, backend.Logout()) assert.NoError(t, backend.StopNode()) }() // Ensure we're able to open the DB err = backend.ensureAppDBOpened(keycardAccount, keycardPassword) require.NoError(t, err) // db creation after re-encryption db1, err := accounts.NewDB(backend.appDB) require.NoError(t, err) // Check that there is a registered keycard keycards, err = db1.GetKeycardsWithSameKeyUID(genAccInfo.KeyUID) require.NoError(t, err) require.Equal(t, 1, len(keycards)) // Converting to a regular account err = backend.ConvertToRegularAccount(mnemonic, keycardPassword, password) require.NoError(t, err) // Validating results of converting to a regular account. // All keystore files for need to be created. found = keystoreContainsFileForAccount(keyStoreDir, accountInfo.Address) require.True(t, found) for _, dAccInfo := range derivedAccounts { found = keystoreContainsFileForAccount(keyStoreDir, dAccInfo.Address) require.True(t, found) } found = keystoreContainsFileForAccount(keyStoreDir, masterAddress) require.True(t, found) // Ensure we're able to open the DB err = backend.ensureAppDBOpened(keycardAccount, password) require.NoError(t, err) // db creation after re-encryption db2, err := accounts.NewDB(backend.appDB) require.NoError(t, err) // Check that there is no registered keycards keycards, err = db2.GetKeycardsWithSameKeyUID(genAccInfo.KeyUID) require.NoError(t, err) require.Equal(t, 0, len(keycards)) } func copyFile(srcFolder string, dstFolder string, fileName string, t *testing.T) { data, err := ioutil.ReadFile(path.Join(srcFolder, fileName)) if err != nil { t.Fail() } err = ioutil.WriteFile(path.Join(dstFolder, fileName), data, 0600) if err != nil { t.Fail() } } func copyDir(srcFolder string, dstFolder string, t *testing.T) { files, err := ioutil.ReadDir(srcFolder) require.NoError(t, err) for _, file := range files { if !file.IsDir() { copyFile(srcFolder, dstFolder, file.Name(), t) } else { childFolder := path.Join(srcFolder, file.Name()) newFolder := path.Join(dstFolder, file.Name()) err = os.MkdirAll(newFolder, os.ModePerm) require.NoError(t, err) copyDir(childFolder, newFolder, t) } } } func loginDesktopUser(t *testing.T, conf *params.NodeConfig) { // The following passwords and DB used in this test unit are only // used to determine if login process works correctly after a migration // Expected account data: keyUID := "0x7c46c8f6f059ab72d524f2a6d356904db30bb0392636172ab3929a6bd2220f84" // #nosec G101 username := "TestUser" passwd := "0xC888C9CE9E098D5864D3DED6EBCC140A12142263BACE3A23A36F9905F12BD64A" // #nosec G101 b := NewGethStatusBackend() require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir)) b.UpdateRootDataDir(conf.DataDir) require.NoError(t, b.OpenAccounts()) accounts, err := b.GetAccounts() require.NoError(t, err) require.Len(t, accounts, 1) require.Equal(t, username, accounts[0].Name) require.Equal(t, keyUID, accounts[0].KeyUID) wg := sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() err := b.StartNodeWithAccount(accounts[0], passwd, conf, nil) require.NoError(t, err) }() wg.Wait() require.NoError(t, b.Logout()) require.NotNil(t, b.statusNode.HTTPServer()) require.NoError(t, b.StopNode()) } func TestLoginAndMigrationsStillWorkWithExistingDesktopUser(t *testing.T) { utils.Init() srcFolder := "../static/test-0.132.0-account/" tmpdir := t.TempDir() copyDir(srcFolder, tmpdir, t) conf, err := params.NewNodeConfig(tmpdir, 1777) require.NoError(t, err) loginDesktopUser(t, conf) loginDesktopUser(t, conf) // Login twice to catch weird errors that only appear after logout } func TestChangeDatabasePassword(t *testing.T) { oldPassword := "password" newPassword := "newPassword" backend := NewGethStatusBackend() backend.UpdateRootDataDir(t.TempDir()) // Setup keystore to test decryption of it keyStoreDir := t.TempDir() require.NoError(t, backend.accountManager.InitKeystore(keyStoreDir)) _, accountInfo, _, err := backend.accountManager.CreateAccount(oldPassword) require.NoError(t, err) account := multiaccounts.Account{ Name: "TestAccount", Timestamp: 1, KeyUID: "0x7c46c8f6f059ab72d524f2a6d356904db30bb0392636172ab3929a6bd2220f84", KDFIterations: 1, } // Initialize accounts DB err = backend.OpenAccounts() require.NoError(t, err) err = backend.SaveAccount(account) require.NoError(t, err) // Created DBs with old password err = backend.ensureDBsOpened(account, oldPassword) require.NoError(t, err) // Change password err = backend.ChangeDatabasePassword(account.KeyUID, oldPassword, newPassword) require.NoError(t, err) // Test that DBs can be opened with new password appDbPath, err := backend.getAppDBPath(account.KeyUID) require.NoError(t, err) appDb, err := sqlite.OpenDB(appDbPath, newPassword, account.KDFIterations) require.NoError(t, err) appDb.Close() walletDbPath, err := backend.getWalletDBPath(account.KeyUID) require.NoError(t, err) walletDb, err := sqlite.OpenDB(walletDbPath, newPassword, account.KDFIterations) require.NoError(t, err) walletDb.Close() // Test that keystore can be decrypted with the new password acc, key, err := backend.accountManager.AddressToDecryptedAccount(accountInfo.WalletAddress, newPassword) require.NoError(t, err) require.NotNil(t, acc) require.NotNil(t, key) require.Equal(t, acc.Address, key.Address) } func TestCreateWallet(t *testing.T) { utils.Init() password := "some-password2" // nolint: goconst tmpdir := t.TempDir() b := NewGethStatusBackend() defer func() { require.NoError(t, b.StopNode()) }() createAccountRequest := &requests.CreateAccount{ DisplayName: "some-display-name", CustomizationColor: "#ffffff", Password: password, RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", } c := make(chan interface{}, 10) signal.SetMobileSignalHandler(func(data []byte) { if strings.Contains(string(data), "node.login") { c <- struct{}{} } }) account, err := b.CreateAccountAndLogin(createAccountRequest) require.NoError(t, err) statusNode := b.statusNode require.NotNil(t, statusNode) walletService := statusNode.WalletService() require.NotNil(t, walletService) walletAPI := walletservice.NewAPI(walletService) paths := []string{"m/44'/60'/0'/0/1"} db, err := accounts.NewDB(b.appDB) require.NoError(t, err) walletRootAddress, err := db.GetWalletRootAddress() require.NoError(t, err) mnemonic, err := db.Mnemonic() require.NoError(t, err) require.NotEmpty(t, mnemonic) derivedAddress, err := walletAPI.GetDerivedAddresses(context.Background(), password, walletRootAddress.String(), paths) require.NoError(t, err) require.Len(t, derivedAddress, 1) accountsService := statusNode.AccountService() require.NotNil(t, accountsService) accountsAPI := accountsService.AccountsAPI() err = accountsAPI.AddAccount(context.Background(), password, &accounts.Account{ KeyUID: account.KeyUID, Type: accounts.AccountTypeGenerated, PublicKey: derivedAddress[0].PublicKey, Emoji: "some", ColorID: "so", Name: "some name", Path: derivedAddress[0].Path, }) require.NoError(t, err) } func TestSetFleet(t *testing.T) { utils.Init() password := "some-password2" // nolint: goconst tmpdir := t.TempDir() b := NewGethStatusBackend() createAccountRequest := &requests.CreateAccount{ DisplayName: "some-display-name", CustomizationColor: "#ffffff", Password: password, RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", } c := make(chan interface{}, 10) signal.SetMobileSignalHandler(func(data []byte) { if strings.Contains(string(data), "node.login") { c <- struct{}{} } }) newAccount, err := b.CreateAccountAndLogin(createAccountRequest) require.NoError(t, err) statusNode := b.statusNode require.NotNil(t, statusNode) savedSettings, err := b.GetSettings() require.NoError(t, err) require.Empty(t, savedSettings.Fleet) accountsDB, err := b.accountsDB() require.NoError(t, err) err = accountsDB.SaveSettingField(settings.Fleet, params.FleetStatusProd) require.NoError(t, err) savedSettings, err = b.GetSettings() require.NoError(t, err) require.NotEmpty(t, savedSettings.Fleet) require.Equal(t, params.FleetStatusProd, *savedSettings.Fleet) require.NoError(t, b.Logout()) loginAccountRequest := &requests.Login{ KeyUID: newAccount.KeyUID, Password: password, } require.NoError(t, b.LoginAccount(loginAccountRequest)) select { case <-c: break case <-time.After(5 * time.Second): t.FailNow() } // Check is using the right fleet require.Equal(t, b.config.ClusterConfig.WakuNodes, params.DefaultWakuNodes(params.FleetStatusProd)) require.NoError(t, b.Logout()) } func TestWalletConfigOnLoginAccount(t *testing.T) { utils.Init() password := "some-password2" // nolint: goconst tmpdir := t.TempDir() poktToken := "grove-token" // nolint: goconst infuraToken := "infura-token" // nolint: goconst alchemyEthereumMainnetToken := "alchemy-ethereum-mainnet-token" alchemyEthereumSepoliaToken := "alchemy-ethereum-sepolia-token" alchemyArbitrumMainnetToken := "alchemy-arbitrum-mainnet-token" alchemyArbitrumSepoliaToken := "alchemy-arbitrum-sepolia-token" alchemyOptimismMainnetToken := "alchemy-optimism-mainnet-token" alchemyOptimismSepoliaToken := "alchemy-optimism-sepolia-token" raribleMainnetAPIKey := "rarible-mainnet-api-key" // nolint: gosec raribleTestnetAPIKey := "rarible-testnet-api-key" // nolint: gosec b := NewGethStatusBackend() createAccountRequest := &requests.CreateAccount{ DisplayName: "some-display-name", CustomizationColor: "#ffffff", Password: password, RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", } c := make(chan interface{}, 10) signal.SetMobileSignalHandler(func(data []byte) { if strings.Contains(string(data), "node.login") { c <- struct{}{} } }) newAccount, err := b.CreateAccountAndLogin(createAccountRequest) require.NoError(t, err) statusNode := b.statusNode require.NotNil(t, statusNode) require.NoError(t, b.Logout()) loginAccountRequest := &requests.Login{ KeyUID: newAccount.KeyUID, Password: password, WalletSecretsConfig: requests.WalletSecretsConfig{ PoktToken: poktToken, InfuraToken: infuraToken, AlchemyEthereumMainnetToken: alchemyEthereumMainnetToken, AlchemyEthereumSepoliaToken: alchemyEthereumSepoliaToken, AlchemyArbitrumMainnetToken: alchemyArbitrumMainnetToken, AlchemyArbitrumSepoliaToken: alchemyArbitrumSepoliaToken, AlchemyOptimismMainnetToken: alchemyOptimismMainnetToken, AlchemyOptimismSepoliaToken: alchemyOptimismSepoliaToken, RaribleMainnetAPIKey: raribleMainnetAPIKey, RaribleTestnetAPIKey: raribleTestnetAPIKey, }, } require.NoError(t, b.LoginAccount(loginAccountRequest)) select { case <-c: break case <-time.After(5 * time.Second): t.FailNow() } require.Equal(t, b.config.WalletConfig.InfuraAPIKey, infuraToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[mainnetChainID], alchemyEthereumMainnetToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[sepoliaChainID], alchemyEthereumSepoliaToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[arbitrumChainID], alchemyArbitrumMainnetToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[arbitrumSepoliaChainID], alchemyArbitrumSepoliaToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[optimismChainID], alchemyOptimismMainnetToken) require.Equal(t, b.config.WalletConfig.AlchemyAPIKeys[optimismSepoliaChainID], alchemyOptimismSepoliaToken) require.Equal(t, b.config.WalletConfig.RaribleMainnetAPIKey, raribleMainnetAPIKey) require.Equal(t, b.config.WalletConfig.RaribleTestnetAPIKey, raribleTestnetAPIKey) require.NoError(t, b.Logout()) } func TestTestnetEnabledSettingOnCreateAccount(t *testing.T) { utils.Init() tmpdir := t.TempDir() b := NewGethStatusBackend() // Creating an account with test networks enabled createAccountRequest1 := &requests.CreateAccount{ DisplayName: "User-1", CustomizationColor: "#ffffff", Password: "password123", RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", TestNetworksEnabled: true, } _, err := b.CreateAccountAndLogin(createAccountRequest1) require.NoError(t, err) statusNode := b.statusNode require.NotNil(t, statusNode) settings, err := b.GetSettings() require.NoError(t, err) require.True(t, settings.TestNetworksEnabled) require.NoError(t, b.Logout()) // Creating an account with test networks disabled createAccountRequest2 := &requests.CreateAccount{ DisplayName: "User-2", CustomizationColor: "#ffffff", Password: "password", RootDataDir: tmpdir, LogFilePath: tmpdir + "/log", } _, err = b.CreateAccountAndLogin(createAccountRequest2) require.NoError(t, err) statusNode = b.statusNode require.NotNil(t, statusNode) settings, err = b.GetSettings() require.NoError(t, err) require.False(t, settings.TestNetworksEnabled) require.NoError(t, b.Logout()) } func TestRestoreAccountAndLogin(t *testing.T) { utils.Init() tmpdir := t.TempDir() backend := NewGethStatusBackend() // Test case 1: Valid restore account request restoreRequest := &requests.RestoreAccount{ Mnemonic: "test test test test test test test test test test test test", FetchBackup: false, CreateAccount: requests.CreateAccount{ DisplayName: "Account1", DeviceName: "StatusIM", Password: "password", CustomizationColor: "0x000000", BackupDisabledDataDir: tmpdir, }, } account, err := backend.RestoreAccountAndLogin(restoreRequest) require.NoError(t, err) require.NotNil(t, account) // Test case 2: Invalid restore account request invalidRequest := &requests.RestoreAccount{} _, err = backend.RestoreAccountAndLogin(invalidRequest) require.Error(t, err) db, err := accounts.NewDB(backend.appDB) require.NoError(t, err) mnemonic, err := db.Mnemonic() require.NoError(t, err) require.Empty(t, mnemonic) } func TestCreateAccountPathsValidation(t *testing.T) { tmpdir := t.TempDir() validation := &requests.CreateAccountValidation{ AllowEmptyDisplayName: false, } request := &requests.CreateAccount{ DisplayName: "User-1", Password: "password123", CustomizationColor: "#ffffff", RootDataDir: tmpdir, } err := request.Validate(validation) require.NoError(t, err) request.RootDataDir = "" request.BackupDisabledDataDir = tmpdir err = request.Validate(validation) require.NoError(t, err) require.Equal(t, tmpdir, request.RootDataDir) } func TestRestoreKeycardAccountAndLogin(t *testing.T) { utils.Init() tmpdir := t.TempDir() exampleKeycardEvent := map[string]interface{}{ "error": "", "instanceUID": "a84599394887b742eed9a99d3834a797", "applicationInfo": map[string]interface{}{ "initialized": false, "instanceUID": "", "version": 0, "availableSlots": 0, "keyUID": "", }, "seedPhraseIndexes": []interface{}{}, "freePairingSlots": 0, "keyUid": "0x579324c53f347e18961c775a00ec13ed7d59a225b1859d5125ff36b450b8778d", "pinRetries": 0, "pukRetries": 0, "cardMetadata": map[string]interface{}{ "name": "", "walletAccounts": []interface{}{}, }, "generatedWalletAccount": map[string]interface{}{ "address": "", "publicKey": "", "privateKey": "", }, "generatedWalletAccounts": []interface{}{}, "txSignature": map[string]interface{}{ "r": "", "s": "", "v": "", }, "eip1581Key": map[string]interface{}{ "address": "0xA8d50f0B3bc581298446be8FBfF5c71684Ea6c01", "publicKey": "0x040d7e6e3761ab3d17c220e484ede2f3fa02998b859d4d0e9d34216c6e41b03dc94996fdea23a9233092cee50a768e7428d5de7bd42e8e32c10d6b0e36b10f0e7a", "privateKey": "", }, "encryptionKey": map[string]interface{}{ "address": "0x1ec12f2b323ddDD076A1127cEc8FA0B592c46cD3", "publicKey": "0x04c4b16f670b51702dc130673bf9c64ffd1f69383cef2127dfa05031b9b1359120f7342134af9a350465126a85e87cb003b7c4f93d2ba2ff98bb73277b119c7a87", "privateKey": "68c830d5b327382a65e6c302594744ec0d28b01d1ea8124f49714f05c9625ddd"}, "masterKey": map[string]interface{}{ "address": "0xbf9dE86774051537b2192Ce9c8d2496f129bA24b", "publicKey": "0x040d909a07ecca18bbfa7d53d10a86bd956f54b8b446eabd94940e642ae18421b516ec5b63677c4ce65e0e266b58bdb716d8266b25356154eb61713ecb23824075", "privateKey": "", }, "walletKey": map[string]interface{}{ "address": "0xB9E1998e1A8854887CA327D1aF5894B6CB0AC07D", "publicKey": "0x04c16e7748f34e0ab2c9c13350d7872d928e942934dd8b8abd3fb12b8c742a5ee8cf0919731e800907068afec25f577bde3a9c534795e359ee48097e4e55f4aaca", "privateKey": "", }, "walletRootKey": map[string]interface{}{ "address": "0xFf59db9F2f97Db7104A906C390D33C342a1309C8", "publicKey": "0x04c436532398e19ed14b4eb41545b82014435d60e7db4449a371fd80d0d5cd557f60d81f6c2b35ca5440aa60934c23b70489b0e7963e63ec66b51a7e52db711262", "privateKey": "", }, "whisperKey": map[string]interface{}{ "address": "0xBa122B9c0Ef560813b5D2C0961094aC36289f846", "publicKey": "0x0441468c39b579259676350b9736b01cdadb740f67bfd022fa2b985123b1d66fc3191cfe73205e3d3d84148f0248f9a2978afeeda16d7c3db90bd2579f0de33459", "privateKey": "5a42b4f15ff1a5da95d116442ce11a31e9020f562224bf60b1d8d3a99d90653d", }, "masterKeyAddress": "", } exampleRequest := map[string]interface{}{ "mnemonic": "", "fetchBackup": true, "createAccountRequest": map[string]interface{}{ "rootDataDir": tmpdir, "kdfIterations": 256000, "deviceName": "", "displayName": "", "password": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "imagePath": "", "imageCropRectangle": map[string]interface{}{ "ax": 0, "ay": 0, "bx": 0, "by": 0}, "customizationColor": "primary", "emoji": "", "wakuV2Nameserver": nil, "wakuV2LightClient": false, "logLevel": "DEBUG", "logFilePath": "", "logEnabled": false, "previewPrivacy": true, "verifyTransactionURL": nil, "verifyENSURL": nil, "verifyENSContractAddress": nil, "verifyTransactionChainID": nil, "upstreamConfig": "", "networkID": nil, "walletSecretsConfig": map[string]interface{}{ "poktToken": "1234567890", "infuraToken": "1234567890", "infuraSecret": "", "openseaApiKey": "", "raribleMainnetApiKey": "", "raribleTestnetApiKey": "", "alchemyEthereumMainnetToken": "", "alchemyEthereumGoerliToken": "", "alchemyEthereumSepoliaToken": "", "alchemyArbitrumMainnetToken": "", "alchemyArbitrumGoerliToken": "", "alchemyArbitrumSepoliaToken": "", "alchemyOptimismMainnetToken": "", "alchemyOptimismGoerliToken": "", "alchemyOptimismSepoliaToken": "", }, "torrentConfigEnabled": false, "torrentConfigPort": 0, "keycardInstanceUID": "a84599394887b742eed9a99d3834a797", "keycardPairingDataFile": path.Join(tmpdir, DefaultKeycardPairingDataFile), }, } require.NotNil(t, exampleKeycardEvent) require.NotNil(t, exampleRequest) conf, err := params.NewNodeConfig(tmpdir, 1777) require.NoError(t, err) backend := NewGethStatusBackend() require.NoError(t, backend.AccountManager().InitKeystore(conf.KeyStoreDir)) backend.UpdateRootDataDir(conf.DataDir) require.NoError(t, backend.OpenAccounts()) keycardPairingDataFile := exampleRequest["createAccountRequest"].(map[string]interface{})["keycardPairingDataFile"].(string) kp := wallet.NewKeycardPairings() kp.SetKeycardPairingsFile(keycardPairingDataFile) err = kp.SetPairingsJSONFileContent([]byte(`{"a84599394887b742eed9a99d3834a797":{"key":"785d52957b05482477728380d9b4bbb5dc9a8ed978ab4a4098e1a279c855d3c6","index":1}}`)) require.NoError(t, err) request := &requests.RestoreAccount{ Keycard: &requests.KeycardData{ KeyUID: exampleKeycardEvent["keyUid"].(string), Address: exampleKeycardEvent["masterKey"].(map[string]interface{})["address"].(string), WhisperPrivateKey: exampleKeycardEvent["whisperKey"].(map[string]interface{})["privateKey"].(string), WhisperPublicKey: exampleKeycardEvent["whisperKey"].(map[string]interface{})["publicKey"].(string), WhisperAddress: exampleKeycardEvent["whisperKey"].(map[string]interface{})["address"].(string), WalletPublicKey: exampleKeycardEvent["walletKey"].(map[string]interface{})["publicKey"].(string), WalletAddress: exampleKeycardEvent["walletKey"].(map[string]interface{})["address"].(string), WalletRootAddress: exampleKeycardEvent["walletRootKey"].(map[string]interface{})["address"].(string), Eip1581Address: exampleKeycardEvent["eip1581Key"].(map[string]interface{})["address"].(string), EncryptionPublicKey: exampleKeycardEvent["encryptionKey"].(map[string]interface{})["publicKey"].(string), }, CreateAccount: requests.CreateAccount{ DisplayName: "User-1", Password: "password123", CustomizationColor: "#ffffff", RootDataDir: tmpdir, KeycardInstanceUID: exampleKeycardEvent["instanceUID"].(string), KeycardPairingDataFile: &keycardPairingDataFile, }, } acc, err := backend.RestoreKeycardAccountAndLogin(request) require.NoError(t, err) require.NotNil(t, acc) }