status-go/api/backend_test.go
Roman Volosovskyi 4c0d8dedea
Replace address with keyUid in accounts db
Account's address was used as a primary key in accounts db and as a
deterministic id of an account in some API calls. Also it was used as a
part of the name of the account specific database. This revealed some
extra information about the account and wasn't necessary.
At first the hash of the address was planned to be used as a
deterministic id, but we already have a keyUid which is calculated as
sha256 hash of account's public key and has similar properties:
- it is deterministic
- doesn't reveal accounts public key or address in plain
2019-12-09 11:20:12 +02:00

599 lines
16 KiB
Go

package api
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"sync"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/account"
"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/node"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/typeddata"
"github.com/status-im/status-go/t/utils"
"github.com/status-im/status-go/transactions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBackendStartNodeConcurrently(t *testing.T) {
utils.Init()
backend := NewGethStatusBackend()
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 := NewGethStatusBackend()
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 := NewGethStatusBackend()
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 TestBackendAccountsConcurrently(t *testing.T) {
utils.Init()
backend := NewGethStatusBackend()
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 wgCreateAccounts sync.WaitGroup
count := 3
addressCh := make(chan [3]string, count) // use buffered channel to avoid blocking
// create new accounts concurrently
for i := 0; i < count; i++ {
wgCreateAccounts.Add(1)
go func(pass string) {
accountInfo, _, err := backend.AccountManager().CreateAccount(pass)
assert.NoError(t, err)
addressCh <- [...]string{accountInfo.WalletAddress, accountInfo.ChatAddress, pass}
wgCreateAccounts.Done()
}("password-00" + string(i))
}
// close addressCh as otherwise for loop never finishes
go func() { wgCreateAccounts.Wait(); close(addressCh) }()
// select, reselect or logout concurrently
var wg sync.WaitGroup
for tuple := range addressCh {
wg.Add(1)
go func(tuple [3]string) {
loginParams := account.LoginParams{
MainAccount: common.HexToAddress(tuple[0]),
ChatAddress: common.HexToAddress(tuple[1]),
Password: tuple[2],
}
assert.NoError(t, backend.SelectAccount(loginParams))
wg.Done()
}(tuple)
wg.Add(1)
go func() {
assert.NoError(t, backend.reSelectAccount())
wg.Done()
}()
wg.Add(1)
go func() {
assert.NoError(t, backend.Logout())
wg.Done()
}()
}
wg.Wait()
}
func TestBackendInjectChatAccount(t *testing.T) {
utils.Init()
backend := NewGethStatusBackend()
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())
}()
chatPrivKey, err := crypto.GenerateKey()
require.NoError(t, err)
encryptionPrivKey, err := crypto.GenerateKey()
require.NoError(t, err)
chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey))
chatPubKeyHex := types.EncodeHex(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey))
whisperService, err := backend.StatusNode().WhisperService()
require.NoError(t, err)
// public key should not be already in whisper
require.False(t, whisperService.HasKeyPair(chatPubKeyHex), "identity already present in whisper")
// call InjectChatAccount
require.NoError(t, backend.InjectChatAccount(chatPrivKeyHex, encryptionPrivKeyHex))
// public key should now be in whisper
require.True(t, whisperService.HasKeyPair(chatPubKeyHex), "identity not injected into whisper")
// wallet account should not be selected
mainAccountAddress, err := backend.AccountManager().MainAccountAddress()
require.Equal(t, common.Address{}, mainAccountAddress)
require.Equal(t, account.ErrNoAccountSelected, err)
// selected chat account should have the key injected previously
chatAcc, err := backend.AccountManager().SelectedChatAccount()
require.Nil(t, err)
require.Equal(t, chatPrivKey, chatAcc.AccountKey.PrivateKey)
}
func TestBackendConnectionChangesConcurrently(t *testing.T) {
connections := [...]string{wifi, cellular, 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))
backend.ConnectionChange(connections[connIdx], false)
wg.Done()
}()
}
wg.Wait()
}
func TestBackendConnectionChangesToOffline(t *testing.T) {
b := NewGethStatusBackend()
b.ConnectionChange(none, false)
assert.True(t, b.connectionState.Offline)
b.ConnectionChange(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 := NewGethStatusBackend()
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 := NewGethStatusBackend()
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 := NewGethStatusBackend()
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 TestSignHash(t *testing.T) {
utils.Init()
backend := NewGethStatusBackend()
config, err := utils.MakeTestNodeConfig(params.StatusChainNetworkID)
require.NoError(t, err)
require.NoError(t, backend.AccountManager().InitKeystore(config.KeyStoreDir))
require.NoError(t, backend.StartNode(config))
defer func() {
require.NoError(t, backend.StopNode())
}()
var testCases = []struct {
name string
chatPrivKeyHex string
hashHex string
expectedSignatureHex string
}{
{
name: "tc1",
chatPrivKeyHex: "facadefacadefacadefacadefacadefacadefacadefacadefacadefacadefaca",
hashHex: "0xa4735de5193362fe856416000105cdfa6ce56265607311cebae93b26e5adf438",
expectedSignatureHex: "0x176c971bae188c663614fc535ac9dbf62871dfeaadb38645809a510d28b3c4b0245415d5547c1b27f7cfea3341564f9c6981421144d3606b455346be69bd078c01",
},
}
const dummyEncKey = "facadefacadefacadefacadefacadefacadefacadefacadefacadefacadefaca"
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
require.NoError(t, backend.InjectChatAccount(tc.chatPrivKeyHex, dummyEncKey))
signature, err := backend.SignHash(tc.hashHex)
require.NoError(t, err)
require.Equal(t, signature, tc.expectedSignatureHex)
})
}
}
func TestHashTypedData(t *testing.T) {
utils.Init()
backend := NewGethStatusBackend()
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"
tmpdir, err := ioutil.TempDir("", "verified-account-test-")
require.NoError(t, err)
defer os.Remove(tmpdir)
backend := NewGethStatusBackend()
backend.UpdateRootDataDir(tmpdir)
require.NoError(t, backend.AccountManager().InitKeystore(filepath.Join(tmpdir, "keystore")))
require.NoError(t, backend.ensureAppDBOpened(multiaccounts.Account{KeyUID: "0x1"}, password))
config, err := params.NewNodeConfig(tmpdir, 178733)
require.NoError(t, err)
// this is for StatusNode().Config() call inside of the getVerifiedWalletAccount
require.NoError(t, backend.StartNode(config))
defer func() {
require.NoError(t, backend.StopNode())
}()
t.Run("AccountDoesntExist", func(t *testing.T) {
pkey, err := crypto.GenerateKey()
require.NoError(t, err)
address := crypto.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)
db := accounts.NewDB(backend.appDB)
_, err = backend.AccountManager().ImportAccount(pkey, password)
require.NoError(t, err)
require.NoError(t, db.SaveAccounts([]accounts.Account{{Address: address}}))
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("Success", func(t *testing.T) {
pkey, err := crypto.GenerateKey()
require.NoError(t, err)
address := crypto.PubkeyToAddress(pkey.PublicKey)
db := accounts.NewDB(backend.appDB)
_, err = backend.AccountManager().ImportAccount(pkey, password)
require.NoError(t, err)
require.NoError(t, db.SaveAccounts([]accounts.Account{{Address: address}}))
key, err := backend.getVerifiedWalletAccount(address.String(), password)
require.NoError(t, err)
require.Equal(t, address, key.Address)
})
}
func TestLoginWithKey(t *testing.T) {
utils.Init()
b := NewGethStatusBackend()
pkey, err := crypto.GenerateKey()
require.NoError(t, err)
keyUIDHex := sha256.Sum256(crypto.FromECDSAPub(&pkey.PublicKey))
keyUID := types.EncodeHex(keyUIDHex[:])
main := multiaccounts.Account{
KeyUID: keyUID,
}
tmpdir, err := ioutil.TempDir("", "login-with-key-test-")
require.NoError(t, err)
defer os.Remove(tmpdir)
conf, err := params.NewNodeConfig(tmpdir, 1777)
require.NoError(t, err)
keyhex := hex.EncodeToString(crypto.FromECDSA(pkey))
require.NoError(t, b.AccountManager().InitKeystore(conf.KeyStoreDir))
b.UpdateRootDataDir(conf.DataDir)
require.NoError(t, b.OpenAccounts())
require.NoError(t, b.SaveAccountAndStartNodeWithKey(main, conf, "test-pass", keyhex))
require.NoError(t, b.Logout())
require.NoError(t, b.StopNode())
require.NoError(t, b.StartNodeWithKey(main, "test-pass", keyhex))
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(pkey.PublicKey), extkey.Address)
}