// +build e2e_test // This is a file with e2e tests for C bindings written in library.go. // As a CGO file, it can't have `_test.go` suffix as it's not allowed by Go. // At the same time, we don't want this file to be included in the binaries. // This is why `e2e_test` tag was introduced. Without it, this file is excluded // from the build. Providing this tag will include this file into the build // and that's what is done while running e2e tests for `lib/` package. package main import "C" import ( "encoding/hex" "encoding/json" "io/ioutil" "math/big" "os" "path" "path/filepath" "reflect" "strconv" "strings" "testing" "time" "github.com/ethereum/go-ethereum/accounts/keystore" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/status-im/status-go/account" "github.com/status-im/status-go/signal" . "github.com/status-im/status-go/t/utils" //nolint: golint "github.com/status-im/status-go/transactions" "github.com/stretchr/testify/require" ) const initJS = ` var _status_catalog = { foo: 'bar' };` var ( zeroHash = gethcommon.Hash{} testChainDir string nodeConfigJSON string ) func init() { testChainDir = filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()]) nodeConfigJSON = `{ "NetworkId": ` + strconv.Itoa(GetNetworkID()) + `, "DataDir": "` + testChainDir + `", "KeyStoreDir": "` + filepath.Join(testChainDir, "keystore") + `", "HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `, "LogLevel": "INFO", "NoDiscovery": true, "LightEthConfig": { "Enabled": true }, "WhisperConfig": { "Enabled": true, "DataDir": "` + path.Join(testChainDir, "wnode") + `", "EnableNTPSync": false }, "ShhextConfig": { "BackupDisabledDataDir": "` + testChainDir + `" } }` } // nolint: deadcode func testExportedAPI(t *testing.T, done chan struct{}) { <-startTestNode(t) defer func() { done <- struct{}{} }() // prepare accounts testKeyDir := filepath.Join(testChainDir, "keystore") if err := ImportTestAccount(testKeyDir, GetAccount1PKFile()); err != nil { panic(err) } if err := ImportTestAccount(testKeyDir, GetAccount2PKFile()); err != nil { panic(err) } // FIXME(tiabc): All of that is done because usage of cgo is not supported in tests. // Probably, there should be a cleaner way, for example, test cgo bindings in e2e tests // separately from other internal tests. // FIXME(@jekamas): ATTENTION! this tests depends on each other! tests := []struct { name string fn func(t *testing.T) bool }{ { "stop/resume node", testStopResumeNode, }, { "call RPC on in-proc handler", testCallRPC, }, { "call private API using RPC", testCallRPCWithPrivateAPI, }, { "call private API using private RPC client", testCallPrivateRPCWithPrivateAPI, }, { "create main and child accounts", testCreateChildAccount, }, { "verify account password", testVerifyAccountPassword, }, { "recover account", testRecoverAccount, }, { "account select/login", testAccountSelect, }, { "login with keycard", testLoginWithKeycard, }, { "account logout", testAccountLogout, }, { "send transaction", testSendTransaction, }, { "send transaction with invalid password", testSendTransactionInvalidPassword, }, { "failed single transaction", testFailedTransaction, }, } for _, test := range tests { t.Logf("=== RUN %s", test.name) if ok := test.fn(t); !ok { t.Logf("=== FAILED %s", test.name) break } } } func testVerifyAccountPassword(t *testing.T) bool { tmpDir, err := ioutil.TempDir(os.TempDir(), "accounts") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpDir) // nolint: errcheck if err = ImportTestAccount(tmpDir, GetAccount1PKFile()); err != nil { t.Fatal(err) } if err = ImportTestAccount(tmpDir, GetAccount2PKFile()); err != nil { t.Fatal(err) } // rename account file (to see that file's internals reviewed, when locating account key) accountFilePathOriginal := filepath.Join(tmpDir, GetAccount1PKFile()) accountFilePath := filepath.Join(tmpDir, "foo"+TestConfig.Account1.WalletAddress+"bar.pk") if err := os.Rename(accountFilePathOriginal, accountFilePath); err != nil { t.Fatal(err) } response := APIResponse{} rawResponse := VerifyAccountPassword( C.CString(tmpDir), C.CString(TestConfig.Account1.WalletAddress), C.CString(TestConfig.Account1.Password)) if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil { t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err) return false } if response.Error != "" { t.Errorf("unexpected error: %s", response.Error) return false } return true } //@TODO(adam): quarantined this test until it uses a different directory. //nolint: deadcode func testResetChainData(t *testing.T) bool { t.Skip() resetChainDataResponse := APIResponse{} rawResponse := ResetChainData() if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &resetChainDataResponse); err != nil { t.Errorf("cannot decode ResetChainData response (%s): %v", C.GoString(rawResponse), err) return false } if resetChainDataResponse.Error != "" { t.Errorf("unexpected error: %s", resetChainDataResponse.Error) return false } EnsureNodeSync(statusBackend.StatusNode().EnsureSync) testSendTransaction(t) return true } func testStopResumeNode(t *testing.T) bool { //nolint: gocyclo // to make sure that we start with empty account (which might have gotten populated during previous tests) if err := statusBackend.Logout(); err != nil { t.Fatal(err) } whisperService, err := statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create an account account1, _, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return false } t.Logf("account created: {address: %s, key: %s}", account1.WalletAddress, account1.WalletPubKey) // make sure that identity is not (yet injected) if whisperService.HasKeyPair(account1.ChatPubKey) { t.Error("identity already present in whisper") } // select account loginResponse := APIResponse{} rawResponse := Login(C.CString(account1.WalletAddress), C.CString(TestConfig.Account1.Password)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if loginResponse.Error != "" { t.Errorf("could not select account: %v", err) return false } if !whisperService.HasKeyPair(account1.ChatPubKey) { t.Errorf("identity not injected into whisper: %v", err) } // stop and resume node, then make sure that selected account is still selected // nolint: dupl stopNodeFn := func() bool { response := APIResponse{} // FIXME(tiabc): Implement https://github.com/status-im/status-go/issues/254 to avoid // 9-sec timeout below after stopping the node. rawResponse = StopNode() if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil { t.Errorf("cannot decode StopNode response (%s): %v", C.GoString(rawResponse), err) return false } if response.Error != "" { t.Errorf("unexpected error: %s", response.Error) return false } return true } // nolint: dupl resumeNodeFn := func() bool { response := APIResponse{} // FIXME(tiabc): Implement https://github.com/status-im/status-go/issues/254 to avoid // 10-sec timeout below after resuming the node. rawResponse = StartNode(C.CString(nodeConfigJSON)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil { t.Errorf("cannot decode StartNode response (%s): %v", C.GoString(rawResponse), err) return false } if response.Error != "" { t.Errorf("unexpected error: %s", response.Error) return false } return true } if !stopNodeFn() { return false } time.Sleep(9 * time.Second) // allow to stop if !resumeNodeFn() { return false } time.Sleep(10 * time.Second) // allow to start (instead of using blocking version of start, of filter event) // now, verify that we still have account logged in whisperService, err = statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } if !whisperService.HasKeyPair(account1.ChatPubKey) { t.Errorf("identity evicted from whisper on node restart: %v", err) } // additionally, let's complete transaction (just to make sure that node lives through pause/resume w/o issues) testSendTransaction(t) return true } func testCallRPC(t *testing.T) bool { expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}` rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`)) received := C.GoString(rawResponse) if expected != received { t.Errorf("unexpected response: expected: %v, got: %v", expected, received) return false } return true } func testCallRPCWithPrivateAPI(t *testing.T) bool { expected := `{"jsonrpc":"2.0","id":64,"error":{"code":-32601,"message":"The method admin_nodeInfo does not exist/is not available"}}` rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`)) received := C.GoString(rawResponse) if expected != received { t.Errorf("unexpected response: expected: %v, got: %v", expected, received) return false } return true } func testCallPrivateRPCWithPrivateAPI(t *testing.T) bool { rawResponse := CallPrivateRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`)) received := C.GoString(rawResponse) if strings.Contains(received, "error") { t.Errorf("unexpected response containing error: %v", received) return false } return true } func testCreateChildAccount(t *testing.T) bool { //nolint: gocyclo // to make sure that we start with empty account (which might get populated during previous tests) if err := statusBackend.Logout(); err != nil { t.Fatal(err) } keyStore, err := statusBackend.StatusNode().AccountKeyStore() if err != nil { t.Error(err) return false } // create an account createAccountResponse := AccountInfo{} rawResponse := CreateAccount(C.CString(TestConfig.Account1.Password)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &createAccountResponse); err != nil { t.Errorf("cannot decode CreateAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createAccountResponse.Error != "" { t.Errorf("could not create account: %s", err) return false } if createAccountResponse.Address != createAccountResponse.WalletAddress || createAccountResponse.PubKey != createAccountResponse.WalletPubKey { t.Error("for backward compatibility pubkey/address should be equal to walletAddress/walletPubKey") } walletAddress, walletPubKey, chatAddress, _, mnemonic := createAccountResponse.Address, createAccountResponse.PubKey, createAccountResponse.ChatAddress, createAccountResponse.ChatPubKey, createAccountResponse.Mnemonic t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", walletAddress, walletPubKey, mnemonic) acct, err := account.ParseAccountString(walletAddress) if err != nil { t.Errorf("can not get account from address: %v", err) return false } // obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present _, key, err := keyStore.AccountDecryptedKey(acct, TestConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return false } if key.ExtendedKey == nil { t.Error("CKD#2 has not been generated for new account") return false } // try creating sub-account, w/o selecting main account i.e. w/o login to main account createSubAccountResponse := AccountInfo{} rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil { t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createSubAccountResponse.Error != account.ErrNoAccountSelected.Error() { t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", createSubAccountResponse.Error) return false } err = statusBackend.SelectAccount(walletAddress, chatAddress, TestConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return false } // try to create sub-account with wrong password createSubAccountResponse = AccountInfo{} rawResponse = CreateChildAccount(C.CString(""), C.CString("wrong password")) if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil { t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createSubAccountResponse.Error != "cannot retrieve a valid key for a given account: could not decrypt key with given passphrase" { t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", createSubAccountResponse.Error) return false } // create sub-account (from implicit parent) createSubAccountResponse1 := AccountInfo{} rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password)) if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse1); err != nil { t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createSubAccountResponse1.Error != "" { t.Errorf("cannot create sub-account: %v", createSubAccountResponse1.Error) return false } // make sure that sub-account index automatically progresses createSubAccountResponse2 := AccountInfo{} rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password)) if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse2); err != nil { t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createSubAccountResponse2.Error != "" { t.Errorf("cannot create sub-account: %v", createSubAccountResponse2.Error) } if createSubAccountResponse1.Address == createSubAccountResponse2.Address || createSubAccountResponse1.PubKey == createSubAccountResponse2.PubKey { t.Error("sub-account index auto-increament failed") return false } // create sub-account (from explicit parent) createSubAccountResponse3 := AccountInfo{} rawResponse = CreateChildAccount(C.CString(createSubAccountResponse2.Address), C.CString(TestConfig.Account1.Password)) if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse3); err != nil { t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err) return false } if createSubAccountResponse3.Error != "" { t.Errorf("cannot create sub-account: %v", createSubAccountResponse3.Error) } subAccount1, subAccount2, subAccount3 := createSubAccountResponse1.Address, createSubAccountResponse2.Address, createSubAccountResponse3.Address subPubKey1, subPubKey2, subPubKey3 := createSubAccountResponse1.PubKey, createSubAccountResponse2.PubKey, createSubAccountResponse3.PubKey if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 { t.Error("sub-account index auto-increament failed") return false } return true } func testRecoverAccount(t *testing.T) bool { //nolint: gocyclo keyStore, _ := statusBackend.StatusNode().AccountKeyStore() // create an account accountInfo, mnemonic, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return false } t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", accountInfo.WalletAddress, accountInfo.WalletPubKey, mnemonic) // try recovering using password + mnemonic recoverAccountResponse := AccountInfo{} rawResponse := RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if recoverAccountResponse.Error != "" { t.Errorf("recover account failed: %v", recoverAccountResponse.Error) return false } if recoverAccountResponse.Address != recoverAccountResponse.WalletAddress || recoverAccountResponse.PubKey != recoverAccountResponse.WalletPubKey { t.Error("for backward compatibility pubkey/address should be equal to walletAddress/walletPubKey") } walletAddressCheck, walletPubKeyCheck := recoverAccountResponse.Address, recoverAccountResponse.PubKey chatAddressCheck, chatPubKeyCheck := recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck { t.Error("recover wallet account details failed to pull the correct details") } if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck { t.Error("recover chat account details failed to pull the correct details") } // now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device account, err := account.ParseAccountString(accountInfo.WalletAddress) if err != nil { t.Errorf("can not get account from address: %v", err) } account, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return false } extChild2String := key.ExtendedKey.String() if err = keyStore.Delete(account, TestConfig.Account1.Password); err != nil { t.Errorf("cannot remove account: %v", err) } recoverAccountResponse = AccountInfo{} rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if recoverAccountResponse.Error != "" { t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error) return false } walletAddressCheck, walletPubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck { t.Error("recover wallet account details failed to pull the correct details (for non-cached account)") } chatAddressCheck, chatPubKeyCheck = recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck { t.Error("recover chat account details failed to pull the correct details (for non-cached account)") } // make sure that extended key exists and is imported ok too _, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password) if err != nil { t.Errorf("can not obtain decrypted account key: %v", err) return false } if extChild2String != key.ExtendedKey.String() { t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String()) } // make sure that calling import several times, just returns from cache (no error is expected) recoverAccountResponse = AccountInfo{} rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if recoverAccountResponse.Error != "" { t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error) return false } walletAddressCheck, walletPubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck { t.Error("recover wallet account details failed to pull the correct details (for non-cached account)") } chatAddressCheck, chatPubKeyCheck = recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck { t.Error("recover chat account details failed to pull the correct details (for non-cached account)") } // time to login with recovered data whisperService, err := statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // make sure that identity is not (yet injected) if whisperService.HasKeyPair(chatPubKeyCheck) { t.Error("identity already present in whisper") } err = statusBackend.SelectAccount(walletAddressCheck, chatAddressCheck, TestConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return false } if !whisperService.HasKeyPair(chatPubKeyCheck) { t.Errorf("identity not injected into whisper: %v", err) } return true } func testAccountSelect(t *testing.T) bool { //nolint: gocyclo // test to see if the account was injected in whisper whisperService, err := statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } // create an account accountInfo1, _, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return false } t.Logf("Account created: {address: %s, key: %s}", accountInfo1.WalletAddress, accountInfo1.WalletPubKey) accountInfo2, _, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password) if err != nil { t.Error("Test failed: could not create account") return false } t.Logf("Account created: {address: %s, key: %s}", accountInfo2.WalletAddress, accountInfo2.WalletPubKey) // make sure that identity is not (yet injected) if whisperService.HasKeyPair(accountInfo1.ChatPubKey) { t.Error("identity already present in whisper") } // try selecting with wrong password loginResponse := APIResponse{} rawResponse := Login(C.CString(accountInfo1.WalletAddress), C.CString("wrongPassword")) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if loginResponse.Error == "" { t.Error("select account is expected to throw error: wrong password used") return false } loginResponse = APIResponse{} rawResponse = Login(C.CString(accountInfo1.WalletAddress), C.CString(TestConfig.Account1.Password)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if loginResponse.Error != "" { t.Errorf("Test failed: could not select account: %v", err) return false } if !whisperService.HasKeyPair(accountInfo1.ChatPubKey) { t.Errorf("identity not injected into whisper: %v", err) } // select another account, make sure that previous account is wiped out from Whisper cache if whisperService.HasKeyPair(accountInfo2.ChatPubKey) { t.Error("identity already present in whisper") } loginResponse = APIResponse{} rawResponse = Login(C.CString(accountInfo2.WalletAddress), C.CString(TestConfig.Account1.Password)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if loginResponse.Error != "" { t.Errorf("Test failed: could not select account: %v", loginResponse.Error) return false } if !whisperService.HasKeyPair(accountInfo2.ChatPubKey) { t.Errorf("identity not injected into whisper: %v", err) } if whisperService.HasKeyPair(accountInfo1.ChatPubKey) { t.Error("identity should be removed, but it is still present in whisper") } return true } func testLoginWithKeycard(t *testing.T) bool { //nolint: gocyclo chatPrivKey, err := crypto.GenerateKey() if err != nil { t.Errorf("error generating chat key") return false } chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey)) encryptionPrivKey, err := crypto.GenerateKey() if err != nil { t.Errorf("error generating encryption key") return false } encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey)) whisperService, err := statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) } chatPubKeyHex := hexutil.Encode(crypto.FromECDSAPub(&chatPrivKey.PublicKey)) if whisperService.HasKeyPair(chatPubKeyHex) { t.Error("identity already present in whisper") return false } loginResponse := APIResponse{} rawResponse := LoginWithKeycard(C.CString(chatPrivKeyHex), C.CString(encryptionPrivKeyHex)) if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil { t.Errorf("cannot decode LoginWithKeycard response (%s): %v", C.GoString(rawResponse), err) return false } if loginResponse.Error != "" { t.Errorf("Test failed: could not login with keycard: %v", err) return false } if !whisperService.HasKeyPair(chatPubKeyHex) { t.Error("identity not present in whisper after logging in with keycard") return false } return true } func testAccountLogout(t *testing.T) bool { whisperService, err := statusBackend.StatusNode().WhisperService() if err != nil { t.Errorf("whisper service not running: %v", err) return false } // create an account accountInfo, _, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password) if err != nil { t.Errorf("could not create account: %v", err) return false } // make sure that identity doesn't exist (yet) in Whisper if whisperService.HasKeyPair(accountInfo.ChatPubKey) { t.Error("identity already present in whisper") return false } // select/login err = statusBackend.SelectAccount(accountInfo.WalletAddress, accountInfo.ChatAddress, TestConfig.Account1.Password) if err != nil { t.Errorf("Test failed: could not select account: %v", err) return false } if !whisperService.HasKeyPair(accountInfo.ChatPubKey) { t.Error("identity not injected into whisper") return false } logoutResponse := APIResponse{} rawResponse := Logout() if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &logoutResponse); err != nil { t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err) return false } if logoutResponse.Error != "" { t.Errorf("cannot logout: %v", logoutResponse.Error) return false } // now, logout and check if identity is removed indeed if whisperService.HasKeyPair(accountInfo.ChatPubKey) { t.Error("identity not cleared from whisper") return false } return true } type jsonrpcAnyResponse struct { Result json.RawMessage `json:"result"` jsonrpcErrorResponse } func testSendTransaction(t *testing.T) bool { EnsureNodeSync(statusBackend.StatusNode().EnsureSync) // log into account from which transactions will be sent if err := statusBackend.SelectAccount(TestConfig.Account1.WalletAddress, TestConfig.Account1.ChatAddress, TestConfig.Account1.Password); err != nil { t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.WalletAddress, err) return false } args, err := json.Marshal(transactions.SendTxArgs{ From: account.FromAddress(TestConfig.Account1.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress), Value: (*hexutil.Big)(big.NewInt(1000000000000)), }) if err != nil { t.Errorf("failed to marshal errors: %v", err) return false } rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password)) var result jsonrpcAnyResponse if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil { t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err) return false } if result.Error.Message != "" { t.Errorf("failed to send transaction: %v", result.Error) return false } hash := gethcommon.BytesToHash(result.Result) if reflect.DeepEqual(hash, gethcommon.Hash{}) { t.Errorf("response hash empty: %s", hash.Hex()) return false } return true } func testSendTransactionInvalidPassword(t *testing.T) bool { EnsureNodeSync(statusBackend.StatusNode().EnsureSync) // log into account from which transactions will be sent if err := statusBackend.SelectAccount( TestConfig.Account1.WalletAddress, TestConfig.Account1.ChatAddress, TestConfig.Account1.Password, ); err != nil { t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.WalletAddress, err) return false } args, err := json.Marshal(transactions.SendTxArgs{ From: account.FromAddress(TestConfig.Account1.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress), Value: (*hexutil.Big)(big.NewInt(1000000000000)), }) if err != nil { t.Errorf("failed to marshal errors: %v", err) return false } rawResult := SendTransaction(C.CString(string(args)), C.CString("invalid password")) var result jsonrpcAnyResponse if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil { t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err) return false } if result.Error.Message != keystore.ErrDecrypt.Error() { t.Errorf("invalid result: %q", result) return false } return true } func testFailedTransaction(t *testing.T) bool { EnsureNodeSync(statusBackend.StatusNode().EnsureSync) // log into wrong account in order to get selectedAccount error if err := statusBackend.SelectAccount(TestConfig.Account2.WalletAddress, TestConfig.Account2.ChatAddress, TestConfig.Account2.Password); err != nil { t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.WalletAddress, err) return false } args, err := json.Marshal(transactions.SendTxArgs{ From: account.FromAddress(TestConfig.Account1.WalletAddress), To: account.ToAddress(TestConfig.Account2.WalletAddress), Value: (*hexutil.Big)(big.NewInt(1000000000000)), }) if err != nil { t.Errorf("failed to marshal errors: %v", err) return false } rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password)) var result jsonrpcAnyResponse if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil { t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err) return false } if result.Error.Message != transactions.ErrInvalidTxSender.Error() { t.Errorf("expected error to be ErrInvalidTxSender, got %s", result.Error.Message) return false } if result.Result != nil { t.Errorf("expected result to be nil") return false } return true } func startTestNode(t *testing.T) <-chan struct{} { testDir := filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()]) syncRequired := false if _, err := os.Stat(testDir); os.IsNotExist(err) { syncRequired = true } // inject test accounts testKeyDir := filepath.Join(testDir, "keystore") if err := ImportTestAccount(testKeyDir, GetAccount1PKFile()); err != nil { panic(err) } if err := ImportTestAccount(testKeyDir, GetAccount2PKFile()); err != nil { panic(err) } waitForNodeStart := make(chan struct{}, 1) signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) { t.Log(jsonEvent) var envelope signal.Envelope if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil { t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent) return } if envelope.Type == signal.EventNodeCrashed { signal.TriggerDefaultNodeNotificationHandler(jsonEvent) return } if envelope.Type == signal.EventSignRequestAdded { } if envelope.Type == signal.EventNodeStarted { t.Log("Node started, but we wait till it be ready") } if envelope.Type == signal.EventNodeReady { // sync if syncRequired { t.Logf("Sync is required") EnsureNodeSync(statusBackend.StatusNode().EnsureSync) } else { time.Sleep(5 * time.Second) } // now we can proceed with tests waitForNodeStart <- struct{}{} } }) go func() { response := StartNode(C.CString(nodeConfigJSON)) responseErr := APIResponse{} if err := json.Unmarshal([]byte(C.GoString(response)), &responseErr); err != nil { panic(err) } if responseErr.Error != "" { panic("cannot start node: " + responseErr.Error) } }() return waitForNodeStart } //nolint: deadcode func testValidateNodeConfig(t *testing.T, config string, fn func(*testing.T, APIDetailedResponse)) { result := ValidateNodeConfig(C.CString(config)) var resp APIDetailedResponse err := json.Unmarshal([]byte(C.GoString(result)), &resp) require.NoError(t, err) fn(t, resp) } // PanicAfter throws panic() after waitSeconds, unless abort channel receives // notification. func PanicAfter(waitSeconds time.Duration, abort chan struct{}, desc string) { go func() { select { case <-abort: return case <-time.After(waitSeconds): panic("whatever you were doing takes toooo long: " + desc) } }() }