Extract e2e tests to a separate package (#375)

This change moves our e2e tests into a separate package to make room for proper unit and integration tests.

This is Phase 1 described in #371.

Changes:

Makefile has separate directives to run unit/integration tests and e2e tests,
CI runs unit/integration tests first and then e2e tests,
E2e tests are in reliability order, i.e. the least reliable tests are run in the end to be sure that nothing else is broken,
Some tests are fixed or quarantined.
This commit is contained in:
Adam Babik 2017-10-11 16:20:51 +02:00 committed by Ivan Tomilov
parent 90acfedf7a
commit 281b304edb
39 changed files with 2474 additions and 2760 deletions

View File

@ -11,7 +11,7 @@ install:
- go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/cover
script: script:
- make ci - travis_wait make ci
cache: cache:
directories: directories:

View File

@ -30,6 +30,8 @@ help: ##@other Show this help
# Main targets # Main targets
UNIT_TEST_PACKAGES := $(shell go list ./... | grep -v /vendor | grep -v /e2e | grep -v /cmd)
statusgo: ##@build Build status-go as statusd server statusgo: ##@build Build status-go as statusd server
build/env.sh go build -i -o $(GOBIN)/statusd -v $(shell build/testnet-flags.sh) ./cmd/statusd build/env.sh go build -i -o $(GOBIN)/statusd -v $(shell build/testnet-flags.sh) ./cmd/statusd
@echo "\nCompilation done.\nRun \"build/bin/statusd help\" to view available commands." @echo "\nCompilation done.\nRun \"build/bin/statusd help\" to view available commands."
@ -72,15 +74,6 @@ statusgo-ios-simulator-mainnet: xgo
build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./cmd/statusd build/env.sh $(GOBIN)/xgo --image farazdagi/xgo-ios-simulator --go=$(GO) -out statusgo --dest=$(GOBIN) --targets=ios-9.3/framework -v $(shell build/mainnet-flags.sh) ./cmd/statusd
@echo "iOS framework cross compilation done (mainnet)." @echo "iOS framework cross compilation done (mainnet)."
ci: mock
build/env.sh go test -timeout 40m -v ./geth/api/...
build/env.sh go test -timeout 40m -v ./geth/common
build/env.sh go test -timeout 40m -v ./geth/jail
build/env.sh go test -timeout 40m -v ./geth/node
build/env.sh go test -timeout 40m -v ./geth/params
build/env.sh go test -timeout 40m -v ./extkeys
build/env.sh go test -timeout 1m -v ./helpers/...
generate: ##@other Regenerate assets and other auto-generated stuff generate: ##@other Regenerate assets and other auto-generated stuff
cp ./node_modules/web3/dist/web3.js ./static/scripts/web3.js cp ./node_modules/web3/dist/web3.js ./static/scripts/web3.js
build/env.sh go generate ./static build/env.sh go generate ./static
@ -137,65 +130,30 @@ lint: ##@tests Run meta linter on code
@echo "Linter: gosimple\n--------------------" @echo "Linter: gosimple\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=gosimple extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!" @gometalinter --disable-all --deadline 45s --enable=gosimple extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
mock-install: mock-install: ##@other Install mocking tools
go get -u github.com/golang/mock/mockgen go get -u github.com/golang/mock/mockgen
mock: mock-install ##@other Regenerate mocks mock: ##@other Regenerate mocks
mockgen -source=geth/common/types.go -destination=geth/common/types_mock.go -package=common mockgen -source=geth/common/types.go -destination=geth/common/types_mock.go -package=common
test: ##@tests Run tests test: ##@tests Run unit and integration tests
@build/env.sh echo "mode: set" > coverage-all.out build/env.sh go test $(UNIT_TEST_PACKAGES)
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/api
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/common
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/jail
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/node
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth/params
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./extkeys
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./cmd/statusd
@build/env.sh tail -n +2 coverage.out >> coverage-all.out
@build/env.sh go tool cover -html=coverage-all.out -o coverage.html
@build/env.sh go tool cover -func=coverage-all.out
test-api: test-coverage: ##@tests Run unit and integration tests with covevare
build/env.sh go test -v -coverprofile=coverage.out -coverpkg=./geth/node ./geth/api build/env.sh go test -coverpkg= $(UNIT_TEST_PACKAGES)
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
test-common: test-e2e: ##@tests Run e2e tests
build/env.sh go test -v -coverprofile=coverage.out ./geth/common # order: reliability then alphabetical
@build/env.sh go tool cover -html=coverage.out -o coverage.html build/env.sh go test -timeout 1m ./e2e/accounts/...
@build/env.sh go tool cover -func=coverage.out build/env.sh go test -timeout 1m ./e2e/api/...
build/env.sh go test -timeout 1m ./e2e/node/...
build/env.sh go test -timeout 15m ./e2e/jail/...
build/env.sh go test -timeout 20m ./e2e/rpc/...
build/env.sh go test -timeout 10m ./e2e/whisper/...
build/env.sh go test -timeout 10m ./e2e/transactions/...
build/env.sh go test -timeout 10m ./cmd/statusd
test-jail: ci: mock-install mock test-coverage test-e2e ##@tests Run all tests in CI
build/env.sh go test -v -coverprofile=coverage.out ./geth/jail
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
test-node:
build/env.sh go test -v -coverprofile=coverage.out ./geth/node
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
test-params:
build/env.sh go test -v -coverprofile=coverage.out ./geth/params
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
test-extkeys:
build/env.sh go test -v -coverprofile=coverage.out ./extkeys
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
test-cmd:
build/env.sh go test -v -coverprofile=coverage.out ./cmd/statusd
@build/env.sh go tool cover -html=coverage.out -o coverage.html
@build/env.sh go tool cover -func=coverage.out
clean: ##@other Cleanup clean: ##@other Cleanup
rm -fr build/bin/* rm -fr build/bin/*

View File

@ -23,9 +23,9 @@ import (
"github.com/status-im/status-go/geth/common" "github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal" "github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/status-im/status-go/geth/txqueue" "github.com/status-im/status-go/geth/txqueue"
"github.com/status-im/status-go/static" "github.com/status-im/status-go/static"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -201,7 +201,10 @@ func testGetDefaultConfig(t *testing.T) bool {
return true return true
} }
// @TODO(adam): quarantined this test until it uses a different directory.
func testResetChainData(t *testing.T) bool { func testResetChainData(t *testing.T) bool {
t.Skip()
resetChainDataResponse := common.APIResponse{} resetChainDataResponse := common.APIResponse{}
rawResponse := ResetChainData() rawResponse := ResetChainData()
@ -322,7 +325,7 @@ func testStopResumeNode(t *testing.T) bool {
} }
func testCallRPC(t *testing.T) bool { func testCallRPC(t *testing.T) bool {
expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}` + "\n" expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}`
rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`)) rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`))
received := C.GoString(rawResponse) received := C.GoString(rawResponse)
if expected != received { if expected != received {

12
e2e/README.md Normal file
View File

@ -0,0 +1,12 @@
e2e
===
This package contains all e2e tests divided into subpackages which represents (or should represent) business domains like transactions, chat etc.
These tests are run against public testnets: Ropsten and Rinkeby.
e2e package contains a few utilities which are described in a [godoc](https://godoc.org/github.com/status-im/status-go/e2e).
## Run
`make test-e2e`

View File

@ -0,0 +1,64 @@
package accounts
import (
"strings"
"testing"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite"
)
func TestAccountsRPCTestSuite(t *testing.T) {
suite.Run(t, new(AccountsRPCTestSuite))
}
type AccountsRPCTestSuite struct {
e2e.BackendTestSuite
}
func (s *AccountsTestSuite) TestRPCEthAccounts() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// log into test account
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err)
rpcClient := s.Backend.NodeManager().RPCClient()
s.NotNil(rpcClient)
expectedResponse := `{"jsonrpc":"2.0","id":1,"result":["` + strings.ToLower(TestConfig.Account1.Address) + `"]}`
resp := rpcClient.CallRaw(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_accounts",
"params": []
}`)
s.Equal(expectedResponse, resp)
}
func (s *AccountsTestSuite) TestRPCEthAccountsWithUpstream() {
s.StartTestBackend(
params.RopstenNetworkID,
e2e.WithUpstream("https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4"),
)
defer s.StopTestBackend()
// log into test account
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err)
rpcClient := s.Backend.NodeManager().RPCClient()
s.NotNil(rpcClient)
expectedResponse := `{"jsonrpc":"2.0","id":1,"result":["` + strings.ToLower(TestConfig.Account1.Address) + `"]}`
resp := rpcClient.CallRaw(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_accounts",
"params": []
}`)
s.Equal(expectedResponse, resp)
}

View File

@ -0,0 +1,302 @@
package accounts
import (
"errors"
"fmt"
"testing"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite"
)
func TestAccountsTestSuite(t *testing.T) {
suite.Run(t, new(AccountsTestSuite))
}
type AccountsTestSuite struct {
e2e.BackendTestSuite
}
func (s *AccountsTestSuite) TestAccountsList() {
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
accounts, err := s.Backend.AccountManager().Accounts()
s.NoError(err)
// make sure that we start with empty accounts list (nobody has logged in yet)
s.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
// create an account
address, _, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
// ensure that there is still no accounts returned
accounts, err = s.Backend.AccountManager().Accounts()
s.NoError(err)
s.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
// select account (sub-accounts will be created for this key)
err = s.Backend.AccountManager().SelectAccount(address, TestConfig.Account1.Password)
s.NoError(err, "account selection failed")
// at this point main account should show up
accounts, err = s.Backend.AccountManager().Accounts()
s.NoError(err)
s.Equal(1, len(accounts), "exactly single account is expected (main account)")
s.Equal(string(accounts[0].Hex()), address,
fmt.Sprintf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), "0x"+address))
// create sub-account 1
subAccount1, subPubKey1, err := s.Backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
s.NoError(err, "cannot create sub-account")
// now we expect to see both main account and sub-account 1
accounts, err = s.Backend.AccountManager().Accounts()
s.NoError(err)
s.Equal(2, len(accounts), "exactly 2 accounts are expected (main + sub-account 1)")
s.Equal(string(accounts[0].Hex()), address, "main account is not retured as the first key")
s.Equal(string(accounts[1].Hex()), subAccount1, "subAcount1 not returned")
// create sub-account 2, index automatically progresses
subAccount2, subPubKey2, err := s.Backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
s.NoError(err, "cannot create sub-account")
s.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
// finally, all 3 accounts should show up (main account, sub-accounts 1 and 2)
accounts, err = s.Backend.AccountManager().Accounts()
s.NoError(err)
s.Equal(3, len(accounts), "unexpected number of accounts")
s.Equal(string(accounts[0].Hex()), address, "main account is not retured as the first key")
subAccount1MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount1
subAccount1MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount1
s.False(!subAccount1MatchesKey1 && !subAccount1MatchesKey2, "subAcount1 not returned")
subAccount2MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount2
subAccount2MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount2
s.False(!subAccount2MatchesKey1 && !subAccount2MatchesKey2, "subAcount2 not returned")
}
func (s *AccountsTestSuite) TestCreateChildAccount() {
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
keyStore, err := s.Backend.NodeManager().AccountKeyStore()
s.NoError(err)
s.NotNil(keyStore)
// create an account
address, pubKey, mnemonic, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
acct, err := common.ParseAccountString(address)
s.NoError(err, "can not get account from address")
// 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)
s.NoError(err, "can not obtain decrypted account key")
s.NotNil(key.ExtendedKey, "CKD#2 has not been generated for new account")
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
_, _, err = s.Backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
s.EqualError(account.ErrNoAccountSelected, err.Error(), "expected error is not returned (tried to create sub-account w/o login)")
err = s.Backend.AccountManager().SelectAccount(address, TestConfig.Account1.Password)
s.NoError(err, "cannot select account")
// try to create sub-account with wrong password
_, _, err = s.Backend.AccountManager().CreateChildAccount("", "wrong password")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
s.EqualError(expectedErr, err.Error(), "create sub-account with wrong password")
// create sub-account (from implicit parent)
subAccount1, subPubKey1, err := s.Backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
s.NoError(err, "cannot create sub-account")
// make sure that sub-account index automatically progresses
subAccount2, subPubKey2, err := s.Backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
s.NoError(err)
s.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
// create sub-account (from explicit parent)
subAccount3, subPubKey3, err := s.Backend.AccountManager().CreateChildAccount(subAccount2, TestConfig.Account1.Password)
s.NoError(err)
s.False(subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3)
}
func (s *AccountsTestSuite) TestRecoverAccount() {
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
keyStore, err := s.Backend.NodeManager().AccountKeyStore()
s.NoError(err)
// create an account
address, pubKey, mnemonic, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
// try recovering using password + mnemonic
addressCheck, pubKeyCheck, err := s.Backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
s.NoError(err, "recover account failed")
s.False(address != addressCheck || pubKey != pubKeyCheck, "incorrect accound details recovered")
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
account, err := common.ParseAccountString(address)
s.NoError(err, "can not get account from address")
account, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
s.NoError(err, "can not obtain decrypted account key")
extChild2String := key.ExtendedKey.String()
s.NoError(keyStore.Delete(account, TestConfig.Account1.Password), "cannot remove account")
addressCheck, pubKeyCheck, err = s.Backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
s.NoError(err, "recover account failed (for non-cached account)")
s.False(address != addressCheck || pubKey != pubKeyCheck,
"incorrect account details recovered (for non-cached account)")
// make sure that extended key exists and is imported ok too
_, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
s.NoError(err)
s.Equal(extChild2String, key.ExtendedKey.String(), "CKD#2 key mismatch")
// make sure that calling import several times, just returns from cache (no error is expected)
addressCheck, pubKeyCheck, err = s.Backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
s.NoError(err, "recover account failed (for non-cached account)")
s.False(address != addressCheck || pubKey != pubKeyCheck,
"incorrect account details recovered (for non-cached account)")
// time to login with recovered data
whisperService := s.WhisperService()
// make sure that identity is not (yet injected)
s.False(whisperService.HasKeyPair(pubKeyCheck), "identity already present in whisper")
s.NoError(s.Backend.AccountManager().SelectAccount(addressCheck, TestConfig.Account1.Password))
s.True(whisperService.HasKeyPair(pubKeyCheck), "identity not injected into whisper")
}
func (s *AccountsTestSuite) TestSelectAccount() {
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
// test to see if the account was injected in whisper
whisperService := s.WhisperService()
// create an account
address1, pubKey1, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s}", address1, pubKey1)
address2, pubKey2, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s}", address2, pubKey2)
// make sure that identity is not (yet injected)
s.False(whisperService.HasKeyPair(pubKey1), "identity already present in whisper")
// try selecting with wrong password
err = s.Backend.AccountManager().SelectAccount(address1, "wrongPassword")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
s.EqualError(expectedErr, err.Error(), "select account is expected to throw error: wrong password used")
err = s.Backend.AccountManager().SelectAccount(address1, TestConfig.Account1.Password)
s.NoError(err)
s.True(whisperService.HasKeyPair(pubKey1), "identity not injected into whisper")
// select another account, make sure that previous account is wiped out from Whisper cache
s.False(whisperService.HasKeyPair(pubKey2), "identity already present in whisper")
s.NoError(s.Backend.AccountManager().SelectAccount(address2, TestConfig.Account1.Password))
s.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
s.False(whisperService.HasKeyPair(pubKey1), "identity should be removed, but it is still present in whisper")
}
func (s *AccountsTestSuite) TestSelectedAccountOnRestart() {
s.StartTestBackend(params.RinkebyNetworkID)
// we need to make sure that selected account is injected as identity into Whisper
whisperService := s.WhisperService()
// create test accounts
address1, pubKey1, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
address2, pubKey2, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
// make sure that identity is not (yet injected)
s.False(whisperService.HasKeyPair(pubKey1), "identity already present in whisper")
// make sure that no account is selected by default
selectedAccount, err := s.Backend.AccountManager().SelectedAccount()
s.EqualError(account.ErrNoAccountSelected, err.Error(), "account selected, but should not be")
s.Nil(selectedAccount)
// select account
err = s.Backend.AccountManager().SelectAccount(address1, "wrongPassword")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
s.EqualError(expectedErr, err.Error())
s.NoError(s.Backend.AccountManager().SelectAccount(address1, TestConfig.Account1.Password))
s.True(whisperService.HasKeyPair(pubKey1), "identity not injected into whisper")
// select another account, make sure that previous account is wiped out from Whisper cache
s.False(whisperService.HasKeyPair(pubKey2), "identity already present in whisper")
s.NoError(s.Backend.AccountManager().SelectAccount(address2, TestConfig.Account1.Password))
s.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
s.False(whisperService.HasKeyPair(pubKey1), "identity should be removed, but it is still present in whisper")
// stop node (and all of its sub-protocols)
nodeConfig, err := s.Backend.NodeManager().NodeConfig()
s.NoError(err)
preservedNodeConfig := *nodeConfig
nodeStoped, err := s.Backend.StopNode()
s.NoError(err)
<-nodeStoped
// make sure that account is still selected
selectedAccount, err = s.Backend.AccountManager().SelectedAccount()
s.NoError(err)
s.NotNil(selectedAccount)
s.Equal(selectedAccount.Address.Hex(), address2, "incorrect address selected")
// resume node
nodeStarted, err := s.Backend.StartNode(&preservedNodeConfig)
s.NoError(err)
<-nodeStarted
// re-check selected account (account2 MUST be selected)
selectedAccount, err = s.Backend.AccountManager().SelectedAccount()
s.NoError(err)
s.NotNil(selectedAccount)
s.Equal(selectedAccount.Address.Hex(), address2, "incorrect address selected")
// make sure that Whisper gets identity re-injected
whisperService = s.WhisperService()
s.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
s.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
// now restart node using RestartNode() method, and make sure that account is still available
s.RestartTestNode()
defer s.StopTestBackend()
whisperService = s.WhisperService()
s.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
s.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
// now logout, and make sure that on restart no account is selected (i.e. logout works properly)
s.NoError(s.Backend.AccountManager().Logout())
s.RestartTestNode()
whisperService = s.WhisperService()
s.False(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
s.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
selectedAccount, err = s.Backend.AccountManager().SelectedAccount()
s.EqualError(account.ErrNoAccountSelected, err.Error())
s.Nil(selectedAccount)
}

View File

@ -8,10 +8,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/api" "github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/log" "github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -25,19 +25,13 @@ type APITestSuite struct {
} }
func (s *APITestSuite) SetupTest() { func (s *APITestSuite) SetupTest() {
require := s.Require() s.api = api.NewStatusAPI()
statusAPI := api.NewStatusAPI() s.NotNil(s.api)
require.NotNil(statusAPI)
require.IsType(&api.StatusAPI{}, statusAPI)
s.api = statusAPI
} }
func (s *APITestSuite) TestCHTUpdate() { func (s *APITestSuite) TestCHTUpdate() {
require := s.Require()
require.NotNil(s.api)
tmpDir, err := ioutil.TempDir(os.TempDir(), "cht-updates") tmpDir, err := ioutil.TempDir(os.TempDir(), "cht-updates")
require.NoError(err) s.NoError(err)
defer os.RemoveAll(tmpDir) defer os.RemoveAll(tmpDir)
configJSON := `{ configJSON := `{
@ -48,7 +42,7 @@ func (s *APITestSuite) TestCHTUpdate() {
}` }`
//nodeConfig, err := params.LoadNodeConfig(configJSON) //nodeConfig, err := params.LoadNodeConfig(configJSON)
_, err = params.LoadNodeConfig(configJSON) _, err = params.LoadNodeConfig(configJSON)
require.NoError(err) s.NoError(err)
// start node // start node
//nodeConfig.DevMode = true //nodeConfig.DevMode = true
@ -58,18 +52,15 @@ func (s *APITestSuite) TestCHTUpdate() {
} }
func (s *APITestSuite) TestRaceConditions() { func (s *APITestSuite) TestRaceConditions() {
require := s.Require()
require.NotNil(s.api)
cnt := 25 cnt := 25
progress := make(chan struct{}, cnt) progress := make(chan struct{}, cnt)
rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
nodeConfig1, err := MakeTestNodeConfig(params.RopstenNetworkID) nodeConfig1, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err) s.NoError(err)
nodeConfig2, err := MakeTestNodeConfig(params.RinkebyNetworkID) nodeConfig2, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err) s.NoError(err)
nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2} nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2}
@ -92,12 +83,14 @@ func (s *APITestSuite) TestRaceConditions() {
s.T().Logf("RestartNodeAsync(), error: %v", err) s.T().Logf("RestartNodeAsync(), error: %v", err)
progress <- struct{}{} progress <- struct{}{}
}, },
func(config *params.NodeConfig) { // TODO(adam): quarantined until it uses a different datadir
log.Info("ResetChainDataAsync()") // as otherwise it wipes out cached blockchain data.
_, err := s.api.ResetChainDataAsync() // func(config *params.NodeConfig) {
s.T().Logf("ResetChainDataAsync(), error: %v", err) // log.Info("ResetChainDataAsync()")
progress <- struct{}{} // _, err := s.api.ResetChainDataAsync()
}, // s.T().Logf("ResetChainDataAsync(), error: %v", err)
// progress <- struct{}{}
// },
} }
// increase StartNode()/StopNode() population // increase StartNode()/StopNode() population

277
e2e/api/backend_test.go Normal file
View File

@ -0,0 +1,277 @@
package api_test
import (
"math/rand"
"testing"
"time"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
"github.com/stretchr/testify/suite"
)
func TestAPIBackendTestSuite(t *testing.T) {
suite.Run(t, new(APIBackendTestSuite))
}
type APIBackendTestSuite struct {
e2e.BackendTestSuite
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *APIBackendTestSuite) TestRaceConditions() {
require := s.Require()
require.NotNil(s.Backend)
cnt := 25
progress := make(chan struct{}, cnt)
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
nodeConfig1, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
nodeConfig2, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2}
var funcsToTest = []func(*params.NodeConfig){
func(config *params.NodeConfig) {
log.Info("StartNode()")
_, err := s.Backend.StartNode(config)
s.T().Logf("StartNode() for network: %d, error: %v", config.NetworkID, err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("StopNode()")
_, err := s.Backend.StopNode()
s.T().Logf("StopNode() for network: %d, error: %v", config.NetworkID, err)
progress <- struct{}{}
},
// func(config *params.NodeConfig) {
// log.Info("ResetChainData()")
// _, err := s.Backend.ResetChainData()
// s.T().Logf("ResetChainData(), error: %v", err)
// progress <- struct{}{}
// },
func(config *params.NodeConfig) {
log.Info("RestartNode()")
_, err := s.Backend.RestartNode()
s.T().Logf("RestartNode(), error: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("NodeManager()")
instance := s.Backend.NodeManager()
s.NotNil(instance)
s.IsType(&node.NodeManager{}, instance)
s.T().Logf("NodeManager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("AccountManager()")
instance := s.Backend.AccountManager()
s.NotNil(instance)
s.IsType(&account.Manager{}, instance)
s.T().Logf("Manager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("JailManager()")
instance := s.Backend.JailManager()
s.NotNil(instance)
s.IsType(&jail.Jail{}, instance)
s.T().Logf("JailManager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CreateAccount()")
address, pubKey, mnemonic, err := s.Backend.AccountManager().CreateAccount("password")
s.T().Logf("CreateAccount(), error: %v (address: %v, pubKey: %v, mnemonic: %v)", err, address, pubKey, mnemonic)
if err == nil {
// SelectAccount
log.Info("CreateAccount()")
err = s.Backend.AccountManager().SelectAccount(address, "password")
s.T().Logf("SelectAccount(%v, %v), error: %v", address, "password", err)
// CreateChildAccount
log.Info("CreateChildAccount()")
address, pubKey, err := s.Backend.AccountManager().CreateChildAccount(address, "password")
s.T().Logf("CreateAccount(), error: %v (address: %v, pubKey: %v)", err, address, pubKey)
// RecoverAccount
log.Info("RecoverAccount()")
address, pubKey, err = s.Backend.AccountManager().RecoverAccount("password", mnemonic)
s.T().Logf("RecoverAccount(), error: %v (address: %v, pubKey: %v)", err, address, pubKey)
}
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("VerifyAccountPassword()")
_, err := s.Backend.AccountManager().VerifyAccountPassword(config.KeyStoreDir, "0x0", "bar")
s.T().Logf("VerifyAccountPassword(), err: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("Logout()")
s.T().Logf("Logout(), result: %v", s.Backend.AccountManager().Logout())
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("IsNodeRunning()")
s.T().Logf("IsNodeRunning(), result: %v", s.Backend.IsNodeRunning())
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CompleteTransaction()")
_, err := s.Backend.CompleteTransaction("id", "password")
s.T().Logf("CompleteTransaction(), error: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("DiscardTransaction()")
s.T().Logf("DiscardTransaction(), error: %v", s.Backend.DiscardTransaction("id"))
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CompleteTransactions()")
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("CompleteTransactions(), result: %v", s.Backend.CompleteTransactions(ids, "password"))
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("DiscardTransactions()")
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("DiscardTransactions(), result: %v", s.Backend.DiscardTransactions(ids))
progress <- struct{}{}
},
}
// increase StartNode()/StopNode() population
for i := 0; i < 5; i++ {
funcsToTest = append(funcsToTest, funcsToTest[0], funcsToTest[1])
}
for i := 0; i < cnt; i++ {
randConfig := nodeConfigs[rnd.Intn(len(nodeConfigs))]
randFunc := funcsToTest[rnd.Intn(len(funcsToTest))]
if rnd.Intn(100) > 75 { // introduce random delays
time.Sleep(500 * time.Millisecond)
}
go randFunc(randConfig)
}
for range progress {
cnt -= 1
if cnt <= 0 {
break
}
}
time.Sleep(2 * time.Second) // so that we see some logs
nodeStopped, _ := s.Backend.StopNode() // just in case we have a node running
if nodeStopped != nil {
<-nodeStopped
}
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *APIBackendTestSuite) TestNetworkSwitching() {
// get Ropsten config
nodeConfig, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
s.NoError(err)
s.False(s.Backend.IsNodeRunning())
nodeStarted, err := s.Backend.StartNode(nodeConfig)
s.NoError(err)
<-nodeStarted // wait till node is started
s.True(s.Backend.IsNodeRunning())
firstHash, err := e2e.FirstBlockHash(s.Backend.NodeManager())
s.NoError(err)
s.Equal("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", firstHash)
// now stop node, and make sure that a new node, on different network can be started
nodeStopped, err := s.Backend.StopNode()
s.NoError(err)
<-nodeStopped
// start new node with completely different config
nodeConfig, err = e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
s.NoError(err)
s.False(s.Backend.IsNodeRunning())
nodeStarted, err = s.Backend.StartNode(nodeConfig)
s.NoError(err)
<-nodeStarted
s.True(s.Backend.IsNodeRunning())
// make sure we are on another network indeed
firstHash, err = e2e.FirstBlockHash(s.Backend.NodeManager())
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
nodeStopped, err = s.Backend.StopNode()
s.NoError(err)
<-nodeStopped
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *APIBackendTestSuite) TestResetChainData() {
s.T().Skip()
require := s.Require()
require.NotNil(s.Backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
time.Sleep(2 * time.Second) // allow to sync for some time
s.True(s.Backend.IsNodeRunning())
nodeReady, err := s.Backend.ResetChainData()
require.NoError(err)
<-nodeReady
s.True(s.Backend.IsNodeRunning()) // new node, with previous config should be running
// make sure we can read the first byte, and it is valid (for Rinkeby)
firstHash, err := e2e.FirstBlockHash(s.Backend.NodeManager())
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *APIBackendTestSuite) TestRestartNode() {
require := s.Require()
require.NotNil(s.Backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
firstHash, err := e2e.FirstBlockHash(s.Backend.NodeManager())
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
s.True(s.Backend.IsNodeRunning())
nodeRestarted, err := s.Backend.RestartNode()
require.NoError(err)
<-nodeRestarted
s.True(s.Backend.IsNodeRunning()) // new node, with previous config should be running
// make sure we can read the first byte, and it is valid (for Rinkeby)
firstHash, err = e2e.FirstBlockHash(s.Backend.NodeManager())
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
}

343
e2e/jail/jail_rpc_test.go Normal file
View File

@ -0,0 +1,343 @@
package jail
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
"testing"
"time"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal"
"github.com/status-im/status-go/geth/txqueue"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite"
)
func TestJailRPCTestSuite(t *testing.T) {
suite.Run(t, new(JailRPCTestSuite))
}
type JailRPCTestSuite struct {
e2e.BackendTestSuite
jail common.JailManager
}
func (s *JailRPCTestSuite) SetupTest() {
s.BackendTestSuite.SetupTest()
s.jail = s.Backend.JailManager()
s.NotNil(s.jail)
}
func (s *JailRPCTestSuite) TestJailRPCSend() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// load Status JS and add test command to it
s.jail.BaseJS(baseStatusJSCode)
s.jail.Parse(testChatID, ``)
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
s.NoError(err)
s.NotNil(cell)
// internally (since we replaced `web3.send` with `jail.Send`)
// all requests to web3 are forwarded to `jail.Send`
_, err = cell.Run(`
var balance = web3.eth.getBalance("` + TestConfig.Account1.Address + `");
var sendResult = web3.fromWei(balance, "ether")
`)
s.NoError(err)
value, err := cell.Get("sendResult")
s.NoError(err, "cannot obtain result of balance check operation")
balance, err := value.ToFloat()
s.NoError(err)
s.T().Logf("Balance of %.2f ETH found on '%s' account", balance, TestConfig.Account1.Address)
s.False(balance < 100, "wrong balance (there should be lots of test Ether on that account)")
}
func (s *JailRPCTestSuite) TestIsConnected() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
s.jail.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
s.NoError(err)
_, err = cell.Run(`
var responseValue = web3.isConnected();
responseValue = JSON.stringify(responseValue);
`)
s.NoError(err)
responseValue, err := cell.Get("responseValue")
s.NoError(err, "cannot obtain result of isConnected()")
response, err := responseValue.ToString()
s.NoError(err, "cannot parse result")
expectedResponse := `{"jsonrpc":"2.0","result":true}`
s.Equal(expectedResponse, response)
}
// regression test: eth_getTransactionReceipt with invalid transaction hash should return null
func (s *JailRPCTestSuite) TestRegressionGetTransactionReceipt() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
rpcClient := s.Backend.NodeManager().RPCClient()
s.NotNil(rpcClient)
// note: transaction hash is assumed to be invalid
got := rpcClient.CallRaw(`{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xbbebf28d0a3a3cbb38e6053a5b21f08f82c62b0c145a17b1c4313cac3f68ae7c"],"id":7}`)
expected := `{"jsonrpc":"2.0","id":7,"result":null}`
s.Equal(expected, got)
}
func (s *JailRPCTestSuite) TestContractDeployment() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// Allow to sync, otherwise you'll get "Nonce too low."
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
// obtain VM for a given chat (to send custom JS to jailed version of Send())
s.jail.Parse(testChatID, "")
cell, err := s.jail.Cell(testChatID)
s.NoError(err)
completeQueuedTransaction := make(chan struct{})
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
var err error
err = json.Unmarshal([]byte(jsonEvent), &envelope)
s.NoError(err, "cannot unmarshal JSON: %s", jsonEvent)
if envelope.Type == txqueue.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
s.T().Logf("transaction queued and will be completed shortly, id: %v", event["id"])
s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txID := event["id"].(string)
txHash, err = s.Backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
if s.NoError(err, event["id"]) {
s.T().Logf("contract transaction complete, URL: %s", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
}
close(completeQueuedTransaction)
}
})
_, err = cell.Run(`
var responseValue = null;
var errorValue = null;
var testContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"int256"}],"name":"double","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"}]);
var test = testContract.new(
{
from: '` + TestConfig.Account1.Address + `',
data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029',
gas: '` + strconv.Itoa(params.DefaultGas) + `'
}, function (e, contract) {
// NOTE: The callback will fire twice!
if (e) {
errorValue = e;
return;
}
// Once the contract has the transactionHash property set and once its deployed on an address.
if (!contract.address) {
responseValue = contract.transactionHash;
}
})
`)
s.NoError(err)
select {
case <-completeQueuedTransaction:
case <-time.After(time.Minute):
s.FailNow("test timed out")
}
// Wait until callback is fired and `responseValue` is set. Hacky but simple.
time.Sleep(2 * time.Second)
errorValue, err := cell.Get("errorValue")
s.NoError(err)
s.Equal("null", errorValue.String())
responseValue, err := cell.Get("responseValue")
s.NoError(err)
response, err := responseValue.ToString()
s.NoError(err)
expectedResponse := txHash.Hex()
s.Equal(expectedResponse, response)
}
func (s *JailRPCTestSuite) TestJailVMPersistence() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
// log into account from which transactions will be sent
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err, "cannot select account: %v", TestConfig.Account1.Address)
type testCase struct {
command string
params string
validator func(response string) error
}
var testCases = []testCase{
{
`["sendTestTx"]`,
`{"amount": "0.000001", "from": "` + TestConfig.Account1.Address + `"}`,
func(response string) error {
if strings.Contains(response, "error") {
return fmt.Errorf("unexpected response: %v", response)
}
return nil
},
},
{
`["sendTestTx"]`,
`{"amount": "0.000002", "from": "` + TestConfig.Account1.Address + `"}`,
func(response string) error {
if strings.Contains(response, "error") {
return fmt.Errorf("unexpected response: %v", response)
}
return nil
},
},
{
`["ping"]`,
`{"pong": "Ping1", "amount": 0.42}`,
func(response string) error {
expectedResponse := `{"result": "Ping1"}`
if response != expectedResponse {
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
}
return nil
},
},
{
`["ping"]`,
`{"pong": "Ping2", "amount": 0.42}`,
func(response string) error {
expectedResponse := `{"result": "Ping2"}`
if response != expectedResponse {
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
}
return nil
},
},
}
jail := s.Backend.JailManager()
jail.BaseJS(baseStatusJSCode)
parseResult := jail.Parse(testChatID, `
var total = 0;
_status_catalog['ping'] = function(params) {
total += Number(params.amount);
return params.pong;
}
_status_catalog['sendTestTx'] = function(params) {
var amount = params.amount;
var transaction = {
"from": params.from,
"to": "`+TestConfig.Account2.Address+`",
"value": web3.toWei(amount, "ether")
};
web3.eth.sendTransaction(transaction, function (error, result) {
if(!error) {
total += Number(amount);
}
});
}
`)
s.NotContains(parseResult, "error", "further will fail if initial parsing failed")
var wg sync.WaitGroup
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
s.T().Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
if envelope.Type == txqueue.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
s.T().Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
//var txHash common.Hash
txID := event["id"].(string)
txHash, err := s.Backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
s.NoError(err, "cannot complete queued transaction[%v]: %v", event["id"], err)
s.T().Logf("Transaction complete: https://ropsten.etherscan.io/tx/%s", txHash.Hex())
}
})
// run commands concurrently
for _, tc := range testCases {
wg.Add(1)
go func(tc testCase) {
defer wg.Done() // ensure we don't forget it
s.T().Logf("CALL START: %v %v", tc.command, tc.params)
response := jail.Call(testChatID, tc.command, tc.params)
if err := tc.validator(response); err != nil {
s.T().Errorf("failed test validation: %v, err: %v", tc.command, err)
}
s.T().Logf("CALL END: %v %v", tc.command, tc.params)
}(tc)
}
finishTestCases := make(chan struct{})
go func() {
wg.Wait()
close(finishTestCases)
}()
select {
case <-finishTestCases:
case <-time.After(time.Minute):
s.FailNow("some tests failed to finish in time")
}
// Wait till eth_sendTransaction callbacks have been executed.
// FIXME(tiabc): more reliable means of testing that.
time.Sleep(5 * time.Second)
// Validate total.
cell, err := jail.Cell(testChatID)
s.NoError(err)
totalOtto, err := cell.Get("total")
s.NoError(err)
total, err := totalOtto.ToFloat()
s.NoError(err)
s.T().Log(total)
s.InDelta(0.840003, total, 0.0000001)
}

159
e2e/jail/jail_test.go Normal file
View File

@ -0,0 +1,159 @@
package jail
import (
"encoding/json"
"errors"
"testing"
"time"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/geth/signal"
"github.com/status-im/status-go/static"
"github.com/stretchr/testify/suite"
)
const (
testChatID = "testChat"
)
var (
baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
txJSCode = string(static.MustAsset("testdata/jail/tx-send/tx-send.js"))
)
func TestJailTestSuite(t *testing.T) {
suite.Run(t, new(JailTestSuite))
}
type JailTestSuite struct {
suite.Suite
jail common.JailManager
}
func (s *JailTestSuite) SetupTest() {
s.jail = jail.New(nil)
s.NotNil(s.jail)
}
func (s *JailTestSuite) TestInit() {
errorWrapper := func(err error) string {
return `{"error":"` + err.Error() + `"}`
}
// get cell VM w/o defining cell first
cell, err := s.jail.Cell(testChatID)
s.EqualError(err, "cell["+testChatID+"] doesn't exist")
s.Nil(cell)
// create VM (w/o properly initializing base JS script)
err = errors.New("ReferenceError: '_status_catalog' is not defined")
s.Equal(errorWrapper(err), s.jail.Parse(testChatID, ``))
err = errors.New("ReferenceError: 'call' is not defined")
s.Equal(errorWrapper(err), s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`))
// get existing cell (even though we got errors, cell was still created)
cell, err = s.jail.Cell(testChatID)
s.NoError(err)
s.NotNil(cell)
statusJS := baseStatusJSCode + `;
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
s.jail.BaseJS(statusJS)
// now no error should occur
response := s.jail.Parse(testChatID, ``)
expectedResponse := `{"result": {"commands":{},"responses":{}}}`
s.Equal(expectedResponse, response)
// make sure that Call succeeds even w/o running node
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse = `{"result": 144}`
s.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestParse() {
extraCode := `
var _status_catalog = {
foo: 'bar'
};`
response := s.jail.Parse("newChat", extraCode)
expectedResponse := `{"result": {"foo":"bar"}}`
s.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestFunctionCall() {
// load Status JS and add test command to it
statusJS := baseStatusJSCode + `;
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
s.jail.Parse(testChatID, statusJS)
// call with wrong chat id
response := s.jail.Call("chatIDNonExistent", "", "")
expectedError := `{"error":"cell[chatIDNonExistent] doesn't exist"}`
s.Equal(expectedError, response)
// call extraFunc()
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse := `{"result": 144}`
s.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestEventSignal() {
s.jail.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
s.NoError(err)
testData := "foobar"
opCompletedSuccessfully := make(chan struct{}, 1)
// replace transaction notification handler
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
s.NoError(err)
if envelope.Type == jail.EventSignal {
event := envelope.Event.(map[string]interface{})
chatID, ok := event["chat_id"].(string)
s.True(ok, "chat id is required, but not found")
s.Equal(testChatID, chatID, "incorrect chat ID")
actualData, ok := event["data"].(string)
s.True(ok, "data field is required, but not found")
s.Equal(testData, actualData, "incorrect data")
close(opCompletedSuccessfully)
}
})
_, err = cell.Run(`
var responseValue = statusSignals.sendSignal("` + testData + `");
responseValue = JSON.stringify(responseValue);
`)
s.NoError(err)
// make sure that signal is sent (and its parameters are correct)
select {
case <-opCompletedSuccessfully:
// pass
case <-time.After(5 * time.Second):
s.FailNow("test timed out")
}
responseValue, err := cell.Get("responseValue")
s.NoError(err, "cannot obtain result of localStorage.set()")
response, err := responseValue.ToString()
s.NoError(err, "cannot parse result")
expectedResponse := `{"jsonrpc":"2.0","result":true}`
s.Equal(expectedResponse, response)
}

View File

@ -2,7 +2,6 @@ package node_test
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@ -13,11 +12,11 @@ import (
gethnode "github.com/ethereum/go-ethereum/node" gethnode "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/log" "github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/node" "github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal" "github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -26,20 +25,15 @@ func TestManagerTestSuite(t *testing.T) {
} }
type ManagerTestSuite struct { type ManagerTestSuite struct {
BaseTestSuite e2e.NodeManagerTestSuite
} }
func (s *ManagerTestSuite) SetupTest() { func (s *ManagerTestSuite) SetupTest() {
s.NodeManager = node.NewNodeManager() s.NodeManager = node.NewNodeManager()
s.Require().NotNil(s.NodeManager)
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
} }
func (s *ManagerTestSuite) TestReferences() { func (s *ManagerTestSuite) TestReferencesWithoutStartedNode() {
s.Require().NotNil(s.NodeManager) var testCases = []struct {
// test for nil values of nodeManager
var noNodeTests = []struct {
name string name string
initFn func() (interface{}, error) initFn func() (interface{}, error)
expectedErr error expectedErr error
@ -122,17 +116,19 @@ func (s *ManagerTestSuite) TestReferences() {
nil, nil,
}, },
} }
for _, testCase := range noNodeTests { for _, tc := range testCases {
s.T().Log(testCase.name) s.T().Log(tc.name)
obj, err := testCase.initFn() obj, err := tc.initFn()
s.Nil(obj) s.Nil(obj)
s.Equal(testCase.expectedErr, err) s.Equal(tc.expectedErr, err)
} }
}
// test with node fully started func (s *ManagerTestSuite) TestReferencesWithStartedNode() {
s.StartTestNode(params.RinkebyNetworkID) s.StartTestNode(params.RinkebyNetworkID)
defer s.StopTestNode() defer s.StopTestNode()
var nodeReadyTestCases = []struct {
var testCases = []struct {
name string name string
initFn func() (interface{}, error) initFn func() (interface{}, error)
expectedType interface{} expectedType interface{}
@ -187,142 +183,163 @@ func (s *ManagerTestSuite) TestReferences() {
&rpc.Client{}, &rpc.Client{},
}, },
} }
for _, testCase := range nodeReadyTestCases { for _, tc := range testCases {
obj, err := testCase.initFn() s.T().Log(tc.name)
s.T().Log(testCase.name) obj, err := tc.initFn()
s.NoError(err) s.NoError(err)
s.NotNil(obj) s.NotNil(obj)
s.IsType(testCase.expectedType, obj) s.IsType(tc.expectedType, obj)
} }
} }
func (s *ManagerTestSuite) TestNodeStartStop() { func (s *ManagerTestSuite) TestNodeStartStop() {
require := s.Require() nodeConfig, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
require.NotNil(s.NodeManager) s.NoError(err)
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
// try stopping non-started node // try stopping non-started node
require.False(s.NodeManager.IsNodeRunning()) s.False(s.NodeManager.IsNodeRunning())
_, err = s.NodeManager.StopNode() _, err = s.NodeManager.StopNode()
require.EqualError(err, node.ErrNoRunningNode.Error()) s.Equal(err, node.ErrNoRunningNode)
require.False(s.NodeManager.IsNodeRunning()) // start node
s.False(s.NodeManager.IsNodeRunning())
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err) s.NoError(err)
// wait till node is started
<-nodeStarted // wait till node is started <-nodeStarted
require.True(s.NodeManager.IsNodeRunning()) s.True(s.NodeManager.IsNodeRunning())
// try starting another node (w/o stopping the previously started node) // try starting another node (w/o stopping the previously started node)
_, err = s.NodeManager.StartNode(nodeConfig) _, err = s.NodeManager.StartNode(nodeConfig)
require.EqualError(err, node.ErrNodeExists.Error()) s.Equal(err, node.ErrNodeExists)
// now stop node, and make sure that a new node, on different network can be started // now stop node
nodeStopped, err := s.NodeManager.StopNode() nodeStopped, err := s.NodeManager.StopNode()
require.NoError(err) s.NoError(err)
<-nodeStopped <-nodeStopped
s.False(s.NodeManager.IsNodeRunning())
// start new node with exactly the same config // start new node with exactly the same config
require.False(s.NodeManager.IsNodeRunning())
nodeStarted, err = s.NodeManager.StartNode(nodeConfig) nodeStarted, err = s.NodeManager.StartNode(nodeConfig)
require.NoError(err) s.NoError(err)
// wait till node is started
<-nodeStarted <-nodeStarted
require.True(s.NodeManager.IsNodeRunning()) s.True(s.NodeManager.IsNodeRunning())
s.StopTestNode() // finally stop the node
nodeStopped, err = s.NodeManager.StopNode()
s.NoError(err)
<-nodeStopped
} }
func (s *ManagerTestSuite) TestNetworkSwitching() { func (s *ManagerTestSuite) TestNetworkSwitching() {
require := s.Require()
require.NotNil(s.NodeManager)
// get Ropsten config // get Ropsten config
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID) nodeConfig, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err) s.NoError(err)
s.False(s.NodeManager.IsNodeRunning())
require.False(s.NodeManager.IsNodeRunning())
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err) s.NoError(err)
// wait till node is started
<-nodeStarted
s.True(s.NodeManager.IsNodeRunning())
<-nodeStarted // wait till node is started firstHash, err := e2e.FirstBlockHash(s.NodeManager)
require.True(s.NodeManager.IsNodeRunning()) s.NoError(err)
s.Equal("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", firstHash)
FirstBlockHash(require, s.NodeManager, "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
// now stop node, and make sure that a new node, on different network can be started // now stop node, and make sure that a new node, on different network can be started
nodeStopped, err := s.NodeManager.StopNode() nodeStopped, err := s.NodeManager.StopNode()
require.NoError(err) s.NoError(err)
<-nodeStopped <-nodeStopped
s.False(s.NodeManager.IsNodeRunning())
// start new node with completely different config // start new node with completely different config
nodeConfig, err = MakeTestNodeConfig(params.RinkebyNetworkID) nodeConfig, err = e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err) s.NoError(err)
require.False(s.NodeManager.IsNodeRunning())
nodeStarted, err = s.NodeManager.StartNode(nodeConfig) nodeStarted, err = s.NodeManager.StartNode(nodeConfig)
require.NoError(err) s.NoError(err)
// wait till node is started
<-nodeStarted <-nodeStarted
require.True(s.NodeManager.IsNodeRunning()) s.True(s.NodeManager.IsNodeRunning())
// make sure we are on another network indeed // make sure we are on another network indeed
FirstBlockHash(require, s.NodeManager, "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") firstHash, err = e2e.FirstBlockHash(s.NodeManager)
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
s.StopTestNode() nodeStopped, err = s.NodeManager.StopNode()
s.NoError(err)
<-nodeStopped
} }
func (s *ManagerTestSuite) TestStartNodeWithUpstreamEnabled() {
nodeConfig, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
s.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
nodeConfig.UpstreamConfig.URL = "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
s.NoError(err)
<-nodeStarted
s.True(s.NodeManager.IsNodeRunning())
nodeStopped, err := s.NodeManager.StopNode()
s.NoError(err)
<-nodeStopped
}
// TODO(adam): fix this test to not use a different directory for blockchain data
func (s *ManagerTestSuite) TestResetChainData() { func (s *ManagerTestSuite) TestResetChainData() {
require := s.Require() s.T().Skip()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID) s.StartTestNode(params.RinkebyNetworkID)
defer s.StopTestNode() defer s.StopTestNode()
time.Sleep(2 * time.Second) // allow to sync for some time // allow to sync for some time
time.Sleep(10 * time.Second)
s.True(s.NodeManager.IsNodeRunning()) // reset chain data
nodeReady, err := s.NodeManager.ResetChainData() nodeReady, err := s.NodeManager.ResetChainData()
require.NoError(err) s.NoError(err)
// new node, with previous config should be running
<-nodeReady <-nodeReady
s.True(s.NodeManager.IsNodeRunning()) // new node, with previous config should be running s.True(s.NodeManager.IsNodeRunning())
// make sure we can read the first byte, and it is valid (for Rinkeby) // make sure we can read the first byte, and it is valid (for Rinkeby)
FirstBlockHash(require, s.NodeManager, "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") firstHash, err := e2e.FirstBlockHash(s.NodeManager)
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
} }
func (s *ManagerTestSuite) TestRestartNode() { func (s *ManagerTestSuite) TestRestartNode() {
require := s.Require()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID) s.StartTestNode(params.RinkebyNetworkID)
defer s.StopTestNode() defer s.StopTestNode()
s.True(s.NodeManager.IsNodeRunning()) s.True(s.NodeManager.IsNodeRunning())
nodeReady, err := s.NodeManager.RestartNode() nodeReady, err := s.NodeManager.RestartNode()
require.NoError(err) s.NoError(err)
// new node, with previous config should be running
<-nodeReady <-nodeReady
s.True(s.NodeManager.IsNodeRunning()) // new node, with previous config should be running s.True(s.NodeManager.IsNodeRunning())
// make sure we can read the first byte, and it is valid (for Rinkeby) // make sure we can read the first byte, and it is valid (for Rinkeby)
FirstBlockHash(require, s.NodeManager, "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") firstHash, err := e2e.FirstBlockHash(s.NodeManager)
s.NoError(err)
s.Equal("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177", firstHash)
} }
// TODO(adam): race conditions should be tested with -race flag and unit tests, if possible.
// Research if it's possible to do the same with unit tests.
func (s *ManagerTestSuite) TestRaceConditions() { func (s *ManagerTestSuite) TestRaceConditions() {
require := s.Require()
require.NotNil(s.NodeManager)
cnt := 25 cnt := 25
progress := make(chan struct{}, cnt) progress := make(chan struct{}, cnt)
rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
nodeConfig1, err := MakeTestNodeConfig(params.RopstenNetworkID) nodeConfig1, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err) s.NoError(err)
nodeConfig2, err := MakeTestNodeConfig(params.RinkebyNetworkID) nodeConfig2, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err) s.NoError(err)
nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2} nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2}
@ -355,12 +372,14 @@ func (s *ManagerTestSuite) TestRaceConditions() {
s.T().Logf("PopulateStaticPeers(), error: %v", s.NodeManager.PopulateStaticPeers()) s.T().Logf("PopulateStaticPeers(), error: %v", s.NodeManager.PopulateStaticPeers())
progress <- struct{}{} progress <- struct{}{}
}, },
func(config *params.NodeConfig) { // TODO(adam): quarantined until it uses a different datadir
log.Info("ResetChainData()") // as otherwise it wipes out cached blockchain data.
_, err := s.NodeManager.ResetChainData() // func(config *params.NodeConfig) {
s.T().Logf("ResetChainData(), error: %v", err) // log.Info("ResetChainData()")
progress <- struct{}{} // _, err := s.NodeManager.ResetChainData()
}, // s.T().Logf("ResetChainData(), error: %v", err)
// progress <- struct{}{}
// },
func(config *params.NodeConfig) { func(config *params.NodeConfig) {
log.Info("RestartNode()") log.Info("RestartNode()")
_, err := s.NodeManager.RestartNode() _, err := s.NodeManager.RestartNode()
@ -434,48 +453,53 @@ func (s *ManagerTestSuite) TestRaceConditions() {
} }
func (s *ManagerTestSuite) TestNodeStartCrash() { func (s *ManagerTestSuite) TestNodeStartCrash() {
require := s.Require()
require.NotNil(s.NodeManager)
nodeConfig, err := MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
// start node outside the manager (on the same port), so that manager node.Start() method fails
outsideNode, err := node.MakeNode(nodeConfig)
require.NoError(outsideNode.Start())
// let's listen for node.crashed signal // let's listen for node.crashed signal
signalReceived := false signalReceived := make(chan struct{})
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) { signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
log.Info("Notification Received", "event", jsonEvent)
var envelope signal.Envelope var envelope signal.Envelope
err := json.Unmarshal([]byte(jsonEvent), &envelope) err := json.Unmarshal([]byte(jsonEvent), &envelope)
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent)) s.NoError(err)
if envelope.Type == signal.EventNodeCrashed { if envelope.Type == signal.EventNodeCrashed {
signalReceived = true close(signalReceived)
} }
}) })
nodeConfig, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
s.NoError(err)
// start node outside the manager (on the same port), so that manager node.Start() method fails
outsideNode, err := node.MakeNode(nodeConfig)
s.NoError(err)
err = outsideNode.Start()
s.NoError(err)
// now try starting using node manager // now try starting using node manager
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err) // no error is thrown, as node is started in separate routine s.NoError(err) // no error is thrown, as node is started in separate routine
<-nodeStarted // no deadlock either, as manager should close the channel on error <-nodeStarted // no deadlock either, as manager should close the channel on error
require.False(s.NodeManager.IsNodeRunning()) s.False(s.NodeManager.IsNodeRunning())
time.Sleep(2 * time.Second) // allow signal to propagate select {
require.True(signalReceived, "node crash signal is expected") case <-time.After(5 * time.Second):
s.FailNow("timed out waiting for signal")
case <-signalReceived:
}
// stop outside node, and re-try // stop outside node, and re-try
require.NoError(outsideNode.Stop()) err = outsideNode.Stop()
signalReceived = false s.NoError(err)
signalReceived = make(chan struct{})
nodeStarted, err = s.NodeManager.StartNode(nodeConfig) nodeStarted, err = s.NodeManager.StartNode(nodeConfig)
require.NoError(err) // again, no error s.NoError(err) // again, no error
<-nodeStarted // no deadlock, and no signal this time, manager should be able to start node <-nodeStarted // no deadlock, and no signal this time, manager should be able to start node
require.True(s.NodeManager.IsNodeRunning()) s.True(s.NodeManager.IsNodeRunning())
time.Sleep(2 * time.Second) // allow signal to propagate select {
require.False(signalReceived, "node should start w/o crash signal") case <-time.After(5 * time.Second):
case <-signalReceived:
s.FailNow("signal should not be received")
}
// cleanup // cleanup
s.NodeManager.StopNode() s.NodeManager.StopNode()

63
e2e/rpc/client_test.go Normal file
View File

@ -0,0 +1,63 @@
package rpc
import (
"testing"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/rpc"
"github.com/stretchr/testify/suite"
)
type RPCClientTestSuite struct {
e2e.NodeManagerTestSuite
}
func TestRPCClientTestSuite(t *testing.T) {
suite.Run(t, new(RPCClientTestSuite))
}
func (s *RPCClientTestSuite) SetupTest() {
s.NodeManager = node.NewNodeManager()
s.NotNil(s.NodeManager)
}
func (s *RPCClientTestSuite) TestNewClient() {
config, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
s.NoError(err)
nodeStarted, err := s.NodeManager.StartNode(config)
s.NoError(err)
<-nodeStarted
node, err := s.NodeManager.Node()
s.NoError(err)
// upstream disabled, local node ok
s.False(config.UpstreamConfig.Enabled)
_, err = rpc.NewClient(node, config.UpstreamConfig)
s.NoError(err)
// upstream enabled with incorrect URL, local node ok
upstreamBad := config.UpstreamConfig
upstreamBad.Enabled = true
upstreamBad.URL = "///__httphh://///incorrect_urlxxx"
_, err = rpc.NewClient(node, upstreamBad)
s.Error(err)
// upstream enabled with correct URL, local node ok
upstreamGood := config.UpstreamConfig
upstreamGood.Enabled = true
upstreamGood.URL = "http://example.com/rpc"
_, err = rpc.NewClient(node, upstreamGood)
s.NoError(err)
// upstream disabled, local node failed (stopped)
nodeStopped, err := s.NodeManager.StopNode()
s.NoError(err)
<-nodeStopped
_, err = rpc.NewClient(node, config.UpstreamConfig)
s.Error(err)
}

View File

@ -1,4 +1,4 @@
package rpc_test package rpc
import ( import (
"context" "context"
@ -9,10 +9,9 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/node" "github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/rpc"
. "github.com/status-im/status-go/geth/testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -36,100 +35,18 @@ func TestRPCTestSuite(t *testing.T) {
} }
type RPCTestSuite struct { type RPCTestSuite struct {
BaseTestSuite e2e.NodeManagerTestSuite
} }
func (s *RPCTestSuite) SetupTest() { func (s *RPCTestSuite) SetupTest() {
require := s.Require() s.NodeManager = node.NewNodeManager()
s.NotNil(s.NodeManager)
nodeManager := node.NewNodeManager()
require.NotNil(nodeManager)
s.NodeManager = nodeManager
}
func (s *RPCTestSuite) TestNewClient() {
require := s.Require()
config, err := MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
nodeStarted, err := s.NodeManager.StartNode(config)
require.NoError(err)
require.NotNil(config)
<-nodeStarted
node, err := s.NodeManager.Node()
require.NoError(err)
// upstream disabled, local node ok
_, err = rpc.NewClient(node, config.UpstreamConfig)
require.NoError(err)
// upstream enabled with incorrect URL, local node ok
upstreamBad := config.UpstreamConfig
upstreamBad.Enabled = true
upstreamBad.URL = "///__httphh://///incorrect_urlxxx"
_, err = rpc.NewClient(node, upstreamBad)
require.NotNil(err)
// upstream enabled with correct URL, local node ok
upstreamGood := config.UpstreamConfig
upstreamGood.Enabled = true
upstreamGood.URL = "http://example.com/rpc"
_, err = rpc.NewClient(node, upstreamGood)
require.Nil(err)
// upstream disabled, local node failed (stopped)
nodeStopped, err := s.NodeManager.StopNode()
require.NoError(err)
<-nodeStopped
_, err = rpc.NewClient(node, config.UpstreamConfig)
require.NotNil(err)
}
func (s *RPCTestSuite) TestRPCClientHandler() {
require := s.Require()
s.StartTestNode(params.RopstenNetworkID)
defer s.StopTestNode()
rpcClient := s.NodeManager.RPCClient()
require.NotNil(rpcClient)
rpcClient.RegisterHandler("eth_sendTransaction", func(ctx context.Context, args ...interface{}) (interface{}, error) {
return map[string]interface{}{"done": true}, nil
})
response := rpcClient.CallRaw(`{
"jsonrpc": "2.0",
"id":10,
"method": "eth_sendTransaction",
"params": [{
"from": "` + TestConfig.Account1.Address + `",
"to": "` + TestConfig.Account2.Address + `",
"value": "0x200",
"nonce": "0x100",
"data": "` + hexutil.Encode([]byte("Will-power")) + `",
"gasPrice": "0x4a817c800",
"gasLimit": "0x5208",
"chainId": 3391
}]
}`)
require.Equal(`{"jsonrpc":"2.0","id":10,"result":{"done":true}}`, response)
} }
func (s *RPCTestSuite) TestCallRPC() { func (s *RPCTestSuite) TestCallRPC() {
require := s.Require()
require.NotNil(s.NodeManager)
for _, upstreamEnabled := range []bool{false, true} { for _, upstreamEnabled := range []bool{false, true} {
s.T().Logf("TestCallRPC with upstream: %t", upstreamEnabled) nodeConfig, err := e2e.MakeTestNodeConfig(params.RinkebyNetworkID)
s.NoError(err)
nodeConfig, err := MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
nodeConfig.IPCEnabled = false nodeConfig.IPCEnabled = false
nodeConfig.WSEnabled = false nodeConfig.WSEnabled = false
@ -141,12 +58,11 @@ func (s *RPCTestSuite) TestCallRPC() {
} }
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err) s.NoError(err)
<-nodeStarted <-nodeStarted
rpcClient := s.NodeManager.RPCClient() rpcClient := s.NodeManager.RPCClient()
require.NotNil(rpcClient) s.NotNil(rpcClient)
type rpcCall struct { type rpcCall struct {
inputJSON string inputJSON string
@ -208,47 +124,51 @@ func (s *RPCTestSuite) TestCallRPC() {
select { select {
case <-time.After(time.Second * 30): case <-time.After(time.Second * 30):
s.NodeManager.StopNode() s.Fail("test timed out")
s.FailNow("test timed out")
case <-done: case <-done:
s.NodeManager.StopNode()
} }
stoppedNode, err := s.NodeManager.StopNode()
s.NoError(err)
<-stoppedNode
} }
} }
// TestCallRawResult checks if returned response is a valid JSON-RPC response. // TestCallRawResult checks if returned response is a valid JSON-RPC response.
func (s *RPCTestSuite) TestCallRawResult() { func (s *RPCTestSuite) TestCallRawResult() {
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID) nodeConfig, err := e2e.MakeTestNodeConfig(params.RopstenNetworkID)
s.NoError(err) s.NoError(err)
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
s.NoError(err) s.NoError(err)
defer s.NodeManager.StopNode()
<-nodeStarted <-nodeStarted
client := s.NodeManager.RPCClient() client := s.NodeManager.RPCClient()
s.NotNil(client)
jsonResult := client.CallRaw(`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`) jsonResult := client.CallRaw(`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`)
s.Equal(`{"jsonrpc":"2.0","id":67,"result":"5.0"}`, jsonResult) s.Equal(`{"jsonrpc":"2.0","id":67,"result":"5.0"}`, jsonResult)
s.NodeManager.StopNode()
} }
// TestCallContextResult checks if result passed to CallContext // TestCallContextResult checks if result passed to CallContext
// is set accordingly to its underlying memory layout. // is set accordingly to its underlying memory layout.
func (s *RPCTestSuite) TestCallContextResult() { func (s *RPCTestSuite) TestCallContextResult() {
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID) s.StartTestNode(
s.NoError(err) params.RopstenNetworkID,
e2e.WithUpstream("https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"),
nodeStarted, err := s.NodeManager.StartNode(nodeConfig) )
s.NoError(err) defer s.StopTestNode()
defer s.NodeManager.StopNode()
<-nodeStarted
client := s.NodeManager.RPCClient() client := s.NodeManager.RPCClient()
s.NotNil(client)
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
var blockNumber hexutil.Uint var blockNumber hexutil.Uint
err = client.CallContext(context.Background(), &blockNumber, "eth_blockNumber") err := client.CallContext(ctx, &blockNumber, "eth_blockNumber")
s.NoError(err) s.NoError(err)
s.True(blockNumber > 0, "blockNumber should be higher than 0") s.True(blockNumber > 0, "blockNumber should be higher than 0")
} }

137
e2e/suites.go Normal file
View File

@ -0,0 +1,137 @@
package e2e
import (
"github.com/ethereum/go-ethereum/les"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/signal"
"github.com/stretchr/testify/suite"
)
// NodeManagerTestSuite defines a test suit with NodeManager.
type NodeManagerTestSuite struct {
suite.Suite
NodeManager common.NodeManager
}
// StartTestNode initiazes a NodeManager instances with configuration retrieved
// from the test config.
func (s *NodeManagerTestSuite) StartTestNode(networkID int, opts ...TestNodeOption) {
nodeConfig, err := MakeTestNodeConfig(networkID)
s.NoError(err)
// Apply any options altering node config.
for i := range opts {
opts[i](nodeConfig)
}
// import account keys
s.NoError(importTestAccouns(nodeConfig.KeyStoreDir))
s.False(s.NodeManager.IsNodeRunning())
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
s.NoError(err)
s.NotNil(nodeStarted)
<-nodeStarted
s.True(s.NodeManager.IsNodeRunning())
}
// StopTestNode attempts to stop initialized NodeManager.
func (s *NodeManagerTestSuite) StopTestNode() {
s.NotNil(s.NodeManager)
s.True(s.NodeManager.IsNodeRunning())
nodeStopped, err := s.NodeManager.StopNode()
s.NoError(err)
<-nodeStopped
s.False(s.NodeManager.IsNodeRunning())
}
// BackendTestSuite is a test suite with api.StatusBackend initialized
// and a few utility methods to start and stop node or get various services.
type BackendTestSuite struct {
suite.Suite
Backend *api.StatusBackend
}
// SetupTest initializes Backend.
func (s *BackendTestSuite) SetupTest() {
s.Backend = api.NewStatusBackend()
s.NotNil(s.Backend)
}
// TearDownTest cleans up the packages state.
func (s *BackendTestSuite) TearDownTest() {
signal.ResetDefaultNodeNotificationHandler()
}
// StartTestBackend imports some keys and starts a node.
func (s *BackendTestSuite) StartTestBackend(networkID int, opts ...TestNodeOption) {
nodeConfig, err := MakeTestNodeConfig(networkID)
s.NoError(err)
// Apply any options altering node config.
for i := range opts {
opts[i](nodeConfig)
}
// import account keys
s.NoError(importTestAccouns(nodeConfig.KeyStoreDir))
// start node
s.False(s.Backend.IsNodeRunning())
nodeStarted, err := s.Backend.StartNode(nodeConfig)
s.NoError(err)
<-nodeStarted
s.True(s.Backend.IsNodeRunning())
}
// StopTestBackend stops the node.
func (s *BackendTestSuite) StopTestBackend() {
s.True(s.Backend.IsNodeRunning())
backendStopped, err := s.Backend.StopNode()
s.NoError(err)
<-backendStopped
s.False(s.Backend.IsNodeRunning())
}
// RestartTestNode restarts a currently running node.
func (s *BackendTestSuite) RestartTestNode() {
s.True(s.Backend.IsNodeRunning())
nodeRestarted, err := s.Backend.RestartNode()
s.NoError(err)
<-nodeRestarted
s.True(s.Backend.IsNodeRunning())
}
// WhisperService returns a reference to the Whisper service.
func (s *BackendTestSuite) WhisperService() *whisper.Whisper {
whisperService, err := s.Backend.NodeManager().WhisperService()
s.NoError(err)
s.NotNil(whisperService)
return whisperService
}
// LightEthereumService returns a reference to the LES service.
func (s *BackendTestSuite) LightEthereumService() *les.LightEthereum {
lightEthereum, err := s.Backend.NodeManager().LightEthereumService()
s.NoError(err)
s.NotNil(lightEthereum)
return lightEthereum
}
// TxQueueManager returns a reference to the TxQueueManager.
func (s *BackendTestSuite) TxQueueManager() common.TxQueueManager {
return s.Backend.TxQueueManager()
}
func importTestAccouns(keyStoreDir string) (err error) {
err = common.ImportTestAccount(keyStoreDir, "test-account1.pk")
if err != nil {
return
}
return common.ImportTestAccount(keyStoreDir, "test-account2.pk")
}

82
e2e/testing.go Normal file
View File

@ -0,0 +1,82 @@
package e2e
import (
"context"
"path/filepath"
"runtime"
"strconv"
"testing"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/testing"
)
// TestNodeOption is a callback passed to StartTestNode which alters its config.
type TestNodeOption func(config *params.NodeConfig)
// WithUpstream returns TestNodeOption which enabled UpstreamConfig.
func WithUpstream(url string) TestNodeOption {
return func(config *params.NodeConfig) {
config.UpstreamConfig.Enabled = true
config.UpstreamConfig.URL = url
}
}
// MakeTestNodeConfig defines a function to return a giving params.NodeConfig
// where specific network addresses are assigned based on provieded network id.
func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
testDir := filepath.Join(TestDataDir, TestNetworkNames[networkID])
if runtime.GOOS == "windows" {
testDir = filepath.ToSlash(testDir)
}
// run tests with "INFO" log level only
// when `go test` invoked with `-v` flag
errorLevel := "ERROR"
if testing.Verbose() {
errorLevel = "INFO"
}
configJSON := `{
"NetworkId": ` + strconv.Itoa(networkID) + `,
"DataDir": "` + testDir + `",
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
"LogLevel": "` + errorLevel + `"
}`
nodeConfig, err := params.LoadNodeConfig(configJSON)
if err != nil {
return nil, err
}
return nodeConfig, nil
}
// FirstBlockHash validates Attach operation for the NodeManager.
func FirstBlockHash(nodeManager common.NodeManager) (string, error) {
// obtain RPC client for running node
runningNode, err := nodeManager.Node()
if err != nil {
return "", err
}
rpcClient, err := runningNode.Attach()
if err != nil {
return "", err
}
// get first block
var firstBlock struct {
Hash gethcommon.Hash `json:"hash"`
}
err = rpcClient.CallContext(context.Background(), &firstBlock, "eth_getBlockByNumber", "0x0", true)
if err != nil {
return "", err
}
return firstBlock.Hash.Hex(), nil
}

View File

@ -1,36 +1,156 @@
package api_test package transactions
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"reflect" "reflect"
"testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/account" "github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common" "github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal" "github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/status-im/status-go/geth/txqueue" "github.com/status-im/status-go/geth/txqueue"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite"
) )
// FIXME(tiabc): Sometimes it fails due to "no suitable peers found". const (
func (s *BackendTestSuite) TestSendContractTx() { txSendFolder = "testdata/jail/tx-send/"
require := s.Require() testChatID = "testChat"
require.NotNil(s.backend) )
func TestTransactionsTestSuite(t *testing.T) {
suite.Run(t, new(TransactionsTestSuite))
}
type TransactionsTestSuite struct {
e2e.BackendTestSuite
}
func (s *TransactionsTestSuite) TestCallRPCSendTransaction() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// Allow to sync the blockchain.
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err)
transactionCompleted := make(chan struct{})
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
var signal signal.Envelope
err := json.Unmarshal([]byte(rawSignal), &signal)
s.NoError(err)
if signal.Type == txqueue.EventTransactionQueued {
event := signal.Event.(map[string]interface{})
txID := event["id"].(string)
txHash, err = s.Backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
s.NoError(err, "cannot complete queued transaction %s", txID)
close(transactionCompleted)
}
})
result := s.Backend.CallRPC(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendTransaction",
"params": [{
"from": "` + TestConfig.Account1.Address + `",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"value": "0x9184e72a"
}]
}`)
s.NotContains(result, "error")
select {
case <-transactionCompleted:
case <-time.After(time.Minute):
s.FailNow("sending transaction timed out")
}
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
}
func (s *TransactionsTestSuite) TestCallRPCSendTransactionUpstream() {
s.StartTestBackend(
params.RopstenNetworkID,
e2e.WithUpstream("https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"),
)
defer s.StopTestBackend()
// Allow to sync the blockchain.
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
err := s.Backend.AccountManager().SelectAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
s.NoError(err)
transactionCompleted := make(chan struct{})
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
var signal signal.Envelope
err := json.Unmarshal([]byte(rawSignal), &signal)
s.NoError(err)
if signal.Type == txqueue.EventTransactionQueued {
event := signal.Event.(map[string]interface{})
txID := event["id"].(string)
// Complete with a wrong passphrase.
txHash, err = s.Backend.CompleteTransaction(common.QueuedTxID(txID), "some-invalid-passphrase")
s.EqualError(err, keystore.ErrDecrypt.Error(), "should return an error as the passphrase was invalid")
// Complete with a correct passphrase.
txHash, err = s.Backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account2.Password)
s.NoError(err, "cannot complete queued transaction %s", txID)
close(transactionCompleted)
}
})
result := s.Backend.CallRPC(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendTransaction",
"params": [{
"from": "` + TestConfig.Account2.Address + `",
"to": "` + TestConfig.Account1.Address + `",
"value": "0x9184e72a"
}]
}`)
s.NotContains(result, "error")
select {
case <-transactionCompleted:
case <-time.After(time.Minute):
s.FailNow("sending transaction timed out")
}
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
}
// FIXME(tiabc): Sometimes it fails due to "no suitable peers found".
func (s *TransactionsTestSuite) TestSendContractTx() {
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
sampleAddress, _, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password) sampleAddress, _, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err) s.NoError(err)
completeQueuedTransaction := make(chan struct{}) completeQueuedTransaction := make(chan struct{})
@ -47,7 +167,7 @@ func (s *BackendTestSuite) TestSendContractTx() {
// the first call will fail (we are not logged in, but trying to complete tx) // the first call will fail (we are not logged in, but trying to complete tx)
log.Info("trying to complete with no user logged in") log.Info("trying to complete with no user logged in")
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -59,9 +179,9 @@ func (s *BackendTestSuite) TestSendContractTx() {
// the second call will also fail (we are logged in as different user) // the second call will also fail (we are logged in as different user)
log.Info("trying to complete with invalid user") log.Info("trying to complete with invalid user")
err = s.backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password) err = s.Backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password)
s.NoError(err) s.NoError(err)
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -73,8 +193,8 @@ func (s *BackendTestSuite) TestSendContractTx() {
// the third call will work as expected (as we are logged in with correct credentials) // the third call will work as expected (as we are logged in with correct credentials)
log.Info("trying to complete with correct user, this should succeed") log.Info("trying to complete with correct user, this should succeed")
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -88,9 +208,9 @@ func (s *BackendTestSuite) TestSendContractTx() {
// this call blocks, up until Complete Transaction is called // this call blocks, up until Complete Transaction is called
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`) byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
require.NoError(err) s.NoError(err)
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: nil, // marker, contract creation is expected To: nil, // marker, contract creation is expected
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), gethcommon.Ether)), //Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), gethcommon.Ether)),
@ -110,21 +230,18 @@ func (s *BackendTestSuite) TestSendContractTx() {
s.Zero(s.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
} }
func (s *BackendTestSuite) TestSendEtherTx() { func (s *TransactionsTestSuite) TestSendEtherTx() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// create an account // create an account
sampleAddress, _, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password) sampleAddress, _, _, err := s.Backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err) s.NoError(err)
completeQueuedTransaction := make(chan struct{}) completeQueuedTransaction := make(chan struct{})
@ -141,7 +258,7 @@ func (s *BackendTestSuite) TestSendEtherTx() {
// the first call will fail (we are not logged in, but trying to complete tx) // the first call will fail (we are not logged in, but trying to complete tx)
log.Info("trying to complete with no user logged in") log.Info("trying to complete with no user logged in")
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -153,9 +270,9 @@ func (s *BackendTestSuite) TestSendEtherTx() {
// the second call will also fail (we are logged in as different user) // the second call will also fail (we are logged in as different user)
log.Info("trying to complete with invalid user") log.Info("trying to complete with invalid user")
err = s.backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password) err = s.Backend.AccountManager().SelectAccount(sampleAddress, TestConfig.Account1.Password)
s.NoError(err) s.NoError(err)
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), TestConfig.Account1.Password) common.QueuedTxID(event["id"].(string)), TestConfig.Account1.Password)
s.EqualError( s.EqualError(
err, err,
@ -165,8 +282,8 @@ func (s *BackendTestSuite) TestSendEtherTx() {
// the third call will work as expected (as we are logged in with correct credentials) // the third call will work as expected (as we are logged in with correct credentials)
log.Info("trying to complete with correct user, this should succeed") log.Info("trying to complete with correct user, this should succeed")
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -179,7 +296,7 @@ func (s *BackendTestSuite) TestSendEtherTx() {
}) })
// this call blocks, up until Complete Transaction is called // this call blocks, up until Complete Transaction is called
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -194,16 +311,19 @@ func (s *BackendTestSuite) TestSendEtherTx() {
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid") s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed") s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.Backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
} }
func (s *BackendTestSuite) TestSendEtherTxUpstream() { func (s *TransactionsTestSuite) TestSendEtherTxUpstream() {
s.StartTestBackend(params.RopstenNetworkID, WithUpstream("https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4")) s.StartTestBackend(
params.RopstenNetworkID,
e2e.WithUpstream("https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4"),
)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password) err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err) s.NoError(err)
completeQueuedTransaction := make(chan struct{}) completeQueuedTransaction := make(chan struct{})
@ -219,7 +339,7 @@ func (s *BackendTestSuite) TestSendEtherTxUpstream() {
event := envelope.Event.(map[string]interface{}) event := envelope.Event.(map[string]interface{})
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string)) log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
txHash, err = s.backend.CompleteTransaction( txHash, err = s.Backend.CompleteTransaction(
common.QueuedTxID(event["id"].(string)), common.QueuedTxID(event["id"].(string)),
TestConfig.Account1.Password, TestConfig.Account1.Password,
) )
@ -232,7 +352,7 @@ func (s *BackendTestSuite) TestSendEtherTxUpstream() {
// This call blocks, up until Complete Transaction is called. // This call blocks, up until Complete Transaction is called.
// Explicitly not setting Gas to get it estimated. // Explicitly not setting Gas to get it estimated.
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
GasPrice: (*hexutil.Big)(big.NewInt(28000000000)), GasPrice: (*hexutil.Big)(big.NewInt(28000000000)),
@ -247,23 +367,20 @@ func (s *BackendTestSuite) TestSendEtherTxUpstream() {
} }
s.Equal(txHash.Hex(), txHashCheck.Hex(), "transaction hash returned from SendTransaction is invalid") s.Equal(txHash.Hex(), txHashCheck.Hex(), "transaction hash returned from SendTransaction is invalid")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.Backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
} }
func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() { func (s *TransactionsTestSuite) TestDoubleCompleteQueuedTransactions() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// log into account from which transactions will be sent // log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
completeQueuedTransaction := make(chan struct{}) completeQueuedTransaction := make(chan struct{})
@ -282,13 +399,13 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
// try with wrong password // try with wrong password
// make sure that tx is NOT removed from the queue (by re-trying with the correct password) // make sure that tx is NOT removed from the queue (by re-trying with the correct password)
_, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password+"wrong") _, err = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password+"wrong")
s.EqualError(err, keystore.ErrDecrypt.Error()) s.EqualError(err, keystore.ErrDecrypt.Error())
s.Equal(1, s.TxQueueManager().TransactionQueue().Count(), "txqueue cannot be empty, as tx has failed") s.Equal(1, s.TxQueueManager().TransactionQueue().Count(), "txqueue cannot be empty, as tx has failed")
// now try to complete transaction, but with the correct password // now try to complete transaction, but with the correct password
txHash, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password) txHash, err = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
s.NoError(err) s.NoError(err)
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex()) log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
@ -311,7 +428,7 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
}) })
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password) // this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -326,27 +443,24 @@ func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid") s.Equal(txHashCheck.Hex(), txHash.Hex(), "transaction hash returned from SendTransaction is invalid")
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed") s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction was never queued or completed")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.Backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
s.True(txFailedEventCalled, "expected tx failure signal is not received") s.True(txFailedEventCalled, "expected tx failure signal is not received")
} }
func (s *BackendTestSuite) TestDiscardQueuedTransaction() { func (s *TransactionsTestSuite) TestDiscardQueuedTransaction() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// reset queue // reset queue
s.backend.TxQueueManager().TransactionQueue().Reset() s.Backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent // log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
completeQueuedTransaction := make(chan struct{}) completeQueuedTransaction := make(chan struct{})
@ -362,18 +476,18 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
txID := common.QueuedTxID(event["id"].(string)) txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be discarded soon)", "id", txID) log.Info("transaction queued (will be discarded soon)", "id", txID)
s.True(s.backend.TxQueueManager().TransactionQueue().Has(txID), "txqueue should still have test tx") s.True(s.Backend.TxQueueManager().TransactionQueue().Has(txID), "txqueue should still have test tx")
// discard // discard
err := s.backend.DiscardTransaction(txID) err := s.Backend.DiscardTransaction(txID)
s.NoError(err, "cannot discard tx") s.NoError(err, "cannot discard tx")
// try completing discarded transaction // try completing discarded transaction
_, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password) _, err = s.Backend.CompleteTransaction(txID, TestConfig.Account1.Password)
s.EqualError(err, "transaction hash not found", "expects tx not found, but call to CompleteTransaction succeeded") s.EqualError(err, "transaction hash not found", "expects tx not found, but call to CompleteTransaction succeeded")
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
s.False(s.backend.TxQueueManager().TransactionQueue().Has(txID), s.False(s.Backend.TxQueueManager().TransactionQueue().Has(txID),
fmt.Sprintf("txqueue should not have test tx at this point (it should be discarded): %s", txID)) fmt.Sprintf("txqueue should not have test tx at this point (it should be discarded): %s", txID))
close(completeQueuedTransaction) close(completeQueuedTransaction)
@ -395,7 +509,7 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
}) })
// this call blocks, and should return when DiscardQueuedTransaction() is called // this call blocks, and should return when DiscardQueuedTransaction() is called
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -409,14 +523,11 @@ func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
} }
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't") s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
s.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.Backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
s.True(txFailedEventCalled, "expected tx failure signal is not received") s.True(txFailedEventCalled, "expected tx failure signal is not received")
} }
func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() { func (s *TransactionsTestSuite) TestCompleteMultipleQueuedTransactions() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
@ -426,8 +537,8 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
s.TxQueueManager().TransactionQueue().Reset() s.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent // log into account from which transactions will be sent
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password) err := s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err) s.NoError(err)
testTxCount := 3 testTxCount := 3
txIDs := make(chan common.QueuedTxID, testTxCount) txIDs := make(chan common.QueuedTxID, testTxCount)
@ -450,7 +561,7 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called // this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() { sendTx := func() {
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -462,16 +573,16 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
// wait for transactions, and complete them in a single call // wait for transactions, and complete them in a single call
completeTxs := func(txIDs []common.QueuedTxID) { completeTxs := func(txIDs []common.QueuedTxID) {
txIDs = append(txIDs, "invalid-tx-id") txIDs = append(txIDs, "invalid-tx-id")
results := s.backend.CompleteTransactions(txIDs, TestConfig.Account1.Password) results := s.Backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
require.Len(results, testTxCount+1) s.Len(results, testTxCount+1)
require.EqualError(results["invalid-tx-id"].Error, "transaction hash not found") s.EqualError(results["invalid-tx-id"].Error, "transaction hash not found")
for txID, txResult := range results { for txID, txResult := range results {
require.False( s.False(
txResult.Error != nil && txID != "invalid-tx-id", txResult.Error != nil && txID != "invalid-tx-id",
"invalid error for %s", txID, "invalid error for %s", txID,
) )
require.False( s.False(
txResult.Hash == (gethcommon.Hash{}) && txID != "invalid-tx-id", txResult.Hash == (gethcommon.Hash{}) && txID != "invalid-tx-id",
"invalid hash (expected non empty hash): %s", txID, "invalid hash (expected non empty hash): %s", txID,
) )
@ -481,8 +592,8 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range txIDs { for _, txID := range txIDs {
require.False( s.False(
s.backend.TxQueueManager().TransactionQueue().Has(txID), s.Backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should not have test tx at this point (it should be completed)", "txqueue should not have test tx at this point (it should be completed)",
) )
} }
@ -508,26 +619,23 @@ func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
s.FailNow("test timed out") s.FailNow("test timed out")
} }
require.Zero(s.TxQueueManager().TransactionQueue().Count(), "queue should be empty") s.Zero(s.TxQueueManager().TransactionQueue().Count(), "queue should be empty")
} }
func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() { func (s *TransactionsTestSuite) TestDiscardMultipleQueuedTransactions() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// reset queue // reset queue
s.backend.TxQueueManager().TransactionQueue().Reset() s.Backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent // log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
testTxCount := 3 testTxCount := 3
txIDs := make(chan common.QueuedTxID, testTxCount) txIDs := make(chan common.QueuedTxID, testTxCount)
@ -544,7 +652,7 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
txID := common.QueuedTxID(event["id"].(string)) txID := common.QueuedTxID(event["id"].(string))
log.Info("transaction queued (will be discarded soon)", "id", txID) log.Info("transaction queued (will be discarded soon)", "id", txID)
s.True(s.backend.TxQueueManager().TransactionQueue().Has(txID), s.True(s.Backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should still have test tx") "txqueue should still have test tx")
txIDs <- txID txIDs <- txID
} }
@ -569,7 +677,7 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called // this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
sendTx := func() { sendTx := func() {
txHashCheck, err := s.backend.SendTransaction(nil, common.SendTxArgs{ txHashCheck, err := s.Backend.SendTransaction(nil, common.SendTxArgs{
From: common.FromAddress(TestConfig.Account1.Address), From: common.FromAddress(TestConfig.Account1.Address),
To: common.ToAddress(TestConfig.Account2.Address), To: common.ToAddress(TestConfig.Account2.Address),
Value: (*hexutil.Big)(big.NewInt(1000000000000)), Value: (*hexutil.Big)(big.NewInt(1000000000000)),
@ -584,24 +692,24 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
txIDs = append(txIDs, "invalid-tx-id") txIDs = append(txIDs, "invalid-tx-id")
// discard // discard
discardResults := s.backend.DiscardTransactions(txIDs) discardResults := s.Backend.DiscardTransactions(txIDs)
require.Len(discardResults, 1, "cannot discard txs: %v", discardResults) s.Len(discardResults, 1, "cannot discard txs: %v", discardResults)
require.Error(discardResults["invalid-tx-id"].Error, "transaction hash not found", "cannot discard txs: %v", discardResults) s.Error(discardResults["invalid-tx-id"].Error, "transaction hash not found", "cannot discard txs: %v", discardResults)
// try completing discarded transaction // try completing discarded transaction
completeResults := s.backend.CompleteTransactions(txIDs, TestConfig.Account1.Password) completeResults := s.Backend.CompleteTransactions(txIDs, TestConfig.Account1.Password)
require.Len(completeResults, testTxCount+1, "unexpected number of errors (call to CompleteTransaction should not succeed)") s.Len(completeResults, testTxCount+1, "unexpected number of errors (call to CompleteTransaction should not succeed)")
for _, txResult := range completeResults { for _, txResult := range completeResults {
require.Error(txResult.Error, "transaction hash not found", "invalid error for %s", txResult.Hash.Hex()) s.Error(txResult.Error, "transaction hash not found", "invalid error for %s", txResult.Hash.Hex())
require.Equal("0x0000000000000000000000000000000000000000000000000000000000000000", txResult.Hash.Hex(), "invalid hash (expected zero): %s", txResult.Hash.Hex()) s.Equal("0x0000000000000000000000000000000000000000000000000000000000000000", txResult.Hash.Hex(), "invalid hash (expected zero): %s", txResult.Hash.Hex())
} }
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txID := range txIDs { for _, txID := range txIDs {
require.False( s.False(
s.backend.TxQueueManager().TransactionQueue().Has(txID), s.Backend.TxQueueManager().TransactionQueue().Has(txID),
"txqueue should not have test tx at this point (it should be discarded): %s", "txqueue should not have test tx at this point (it should be discarded): %s",
txID, txID,
) )
@ -624,54 +732,48 @@ func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
select { select {
case <-allTestTxDiscarded: case <-allTestTxDiscarded:
case <-time.After(1 * time.Minute): case <-time.After(1 * time.Minute):
require.FailNow("test timed out") s.FailNow("test timed out")
} }
require.Zero(s.backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point") s.Zero(s.Backend.TxQueueManager().TransactionQueue().Count(), "tx queue must be empty at this point")
} }
func (s *BackendTestSuite) TestNonExistentQueuedTransactions() { func (s *TransactionsTestSuite) TestNonExistentQueuedTransactions() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// log into account from which transactions will be sent // log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
// replace transaction notification handler // replace transaction notification handler
signal.SetDefaultNodeNotificationHandler(func(string) {}) signal.SetDefaultNodeNotificationHandler(func(string) {})
// try completing non-existing transaction // try completing non-existing transaction
_, err := s.backend.CompleteTransaction("some-bad-transaction-id", TestConfig.Account1.Password) _, err := s.Backend.CompleteTransaction("some-bad-transaction-id", TestConfig.Account1.Password)
s.Error(err, "error expected and not received") s.Error(err, "error expected and not received")
s.EqualError(err, txqueue.ErrQueuedTxIDNotFound.Error()) s.EqualError(err, txqueue.ErrQueuedTxIDNotFound.Error())
} }
func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() { func (s *TransactionsTestSuite) TestEvictionOfQueuedTransactions() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID) s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend() defer s.StopTestBackend()
backend := s.LightEthereumService().StatusBackend backend := s.LightEthereumService().StatusBackend
require.NotNil(backend) s.NotNil(backend)
// reset queue // reset queue
s.backend.TxQueueManager().TransactionQueue().Reset() s.Backend.TxQueueManager().TransactionQueue().Reset()
// log into account from which transactions will be sent // log into account from which transactions will be sent
require.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)) s.NoError(s.Backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txQueue := s.backend.TxQueueManager().TransactionQueue() txQueue := s.Backend.TxQueueManager().TransactionQueue()
var i = 0 var i = 0
txIDs := [txqueue.DefaultTxQueueCap + 5 + 10]common.QueuedTxID{} txIDs := [txqueue.DefaultTxQueueCap + 5 + 10]common.QueuedTxID{}
s.backend.TxQueueManager().SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) { s.Backend.TxQueueManager().SetTransactionQueueHandler(func(queuedTx *common.QueuedTx) {
log.Info("tx enqueued", "i", i+1, "queue size", txQueue.Count(), "id", queuedTx.ID) log.Info("tx enqueued", "i", i+1, "queue size", txQueue.Count(), "id", queuedTx.ID)
txIDs[i] = queuedTx.ID txIDs[i] = queuedTx.ID
i++ i++
@ -680,7 +782,7 @@ func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
s.Zero(txQueue.Count(), "transaction count should be zero") s.Zero(txQueue.Count(), "transaction count should be zero")
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
go s.backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck go s.Backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck
} }
time.Sleep(2 * time.Second) // FIXME(tiabc): more reliable synchronization to ensure all transactions are enqueued time.Sleep(2 * time.Second) // FIXME(tiabc): more reliable synchronization to ensure all transactions are enqueued
@ -690,15 +792,15 @@ func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
s.Equal(10, txQueue.Count(), "transaction count should be 10") s.Equal(10, txQueue.Count(), "transaction count should be 10")
for i := 0; i < txqueue.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines for i := 0; i < txqueue.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
go s.backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck go s.Backend.SendTransaction(nil, common.SendTxArgs{}) // nolint: errcheck
} }
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
require.True(txQueue.Count() <= txqueue.DefaultTxQueueCap, "transaction count should be %d (or %d): got %d", txqueue.DefaultTxQueueCap, txqueue.DefaultTxQueueCap-1, txQueue.Count()) s.True(txQueue.Count() <= txqueue.DefaultTxQueueCap, "transaction count should be %d (or %d): got %d", txqueue.DefaultTxQueueCap, txqueue.DefaultTxQueueCap-1, txQueue.Count())
for _, txID := range txIDs { for _, txID := range txIDs {
txQueue.Remove(txID) txQueue.Remove(txID)
} }
require.Zero(txQueue.Count(), "transaction count should be zero: %d", txQueue.Count()) s.Zero(txQueue.Count(), "transaction count should be zero: %d", txQueue.Count())
} }

View File

@ -0,0 +1,431 @@
package whisper
import (
"context"
"crypto/rand"
"errors"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/static"
. "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite"
)
const (
whisperMessage1 = `test message 1 (K1 -> K2, signed+encrypted, from us)`
whisperMessage2 = `test message 3 (K1 -> "", signed broadcast)`
whisperMessage3 = `test message 4 ("" -> "", anon broadcast)`
whisperMessage4 = `test message 5 ("" -> K1, encrypted anon broadcast)`
whisperMessage5 = `test message 6 (K2 -> K1, signed+encrypted, to us)`
testChatID = "testChat"
)
var (
baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
)
func TestWhisperJailTestSuite(t *testing.T) {
s := new(WhisperJailTestSuite)
s.Timeout = time.Minute * 5
suite.Run(t, s)
}
type WhisperJailTestSuite struct {
e2e.BackendTestSuite
Timeout time.Duration
WhisperAPI *whisper.PublicWhisperAPI
Jail common.JailManager
}
func (s *WhisperJailTestSuite) StartTestBackend(networkID int, opts ...e2e.TestNodeOption) {
s.BackendTestSuite.StartTestBackend(networkID, opts...)
s.WhisperAPI = whisper.NewPublicWhisperAPI(s.WhisperService())
s.Jail = s.Backend.JailManager()
s.NotNil(s.Jail)
s.Jail.BaseJS(baseStatusJSCode)
}
func (s *WhisperJailTestSuite) GetAccountKey(account struct {
Address string
Password string
}) (*keystore.Key, string, error) {
accountManager := s.Backend.AccountManager()
_, accountKey1, err := accountManager.AddressToDecryptedAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
if err != nil {
return nil, "", err
}
accountKey1Hex := gethcommon.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
_, err = s.WhisperService().AddKeyPair(accountKey1.PrivateKey)
if err != nil {
return nil, "", err
}
if ok := s.WhisperAPI.HasKeyPair(context.Background(), accountKey1Hex); !ok {
return nil, "", errors.New("KeyPair should be injected in Whisper")
}
return accountKey1, accountKey1Hex, nil
}
func (s *WhisperJailTestSuite) TestJailWhisper() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
_, accountKey1Hex, err := s.GetAccountKey(TestConfig.Account1)
s.NoError(err)
_, accountKey2Hex, err := s.GetAccountKey(TestConfig.Account2)
s.NoError(err)
testCases := []struct {
name string
code string
useFilter bool
}{
{
"test 0: ensure correct version of Whisper is used",
`
var expectedVersion = '5.0';
if (web3.version.whisper != expectedVersion) {
throw 'unexpected shh version, expected: ' + expectedVersion + ', got: ' + web3.version.whisper;
}
`,
false,
},
{
"test 1: encrypted signed message from us (From != nil && To != nil)",
`
var identity1 = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity1)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var identity2 = '` + accountKey2Hex + `';
if (!shh.hasKeyPair(identity2)) {
throw 'identitity "` + accountKey2Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage1 + `';
// start watching for messages
var filter = shh.newMessageFilter({
sig: identity1,
privateKeyID: identity2,
topics: [topic]
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
sig: identity1,
pubKey: identity2,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
`,
true,
},
{
"test 2: signed (known sender) broadcast (From != nil && To == nil)",
`
var identity = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage2 + `';
// generate symmetric key
var keyid = shh.newSymKey();
if (!shh.hasSymKey(keyid)) {
throw new Error('key not found');
}
// start watching for messages
var filter = shh.newMessageFilter({
sig: identity,
topics: [topic],
symKeyID: keyid
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
sig: identity,
symKeyID: keyid,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
`,
true,
},
{
"test 3: anonymous broadcast (From == nil && To == nil)",
`
var topic = makeTopic();
var payload = '` + whisperMessage3 + `';
// generate symmetric key
var keyid = shh.newSymKey();
if (!shh.hasSymKey(keyid)) {
throw new Error('key not found');
}
// start watching for messages
var filter = shh.newMessageFilter({
topics: [topic],
symKeyID: keyid
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
symKeyID: keyid,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
`,
true,
},
// @TODO(adam): quarantined as always failing. Check out TestEncryptedAnonymousMessage
// as an equivalent test in pure Go which passes. Bug in web3?
// {
// "test 4: encrypted anonymous message (From == nil && To != nil)",
// `
// var identity = '` + accountKey2Hex + `';
// if (!shh.hasKeyPair(identity)) {
// throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
// }
// var topic = makeTopic();
// var payload = '` + whisperMessage4 + `';
// // start watching for messages
// var filter = shh.newMessageFilter({
// privateKeyID: identity,
// topics: [topic],
// });
// // post message
// var message = {
// ttl: 20,
// powTarget: 0.01,
// powTime: 20,
// topic: topic,
// pubKey: identity,
// payload: web3.toHex(payload),
// };
// var sent = shh.post(message)
// if (!sent) {
// throw 'message not sent: ' + JSON.stringify(message);
// }
// `,
// true,
// },
{
"test 5: encrypted signed response to us (From != nil && To != nil)",
`
var identity1 = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity1)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var identity2 = '` + accountKey2Hex + `';
if (!shh.hasKeyPair(identity2)) {
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage5 + `';
// start watching for messages
var filter = shh.newMessageFilter({
privateKeyID: identity1,
sig: identity2,
topics: [topic],
});
// post message
var message = {
sig: identity2,
pubKey: identity1,
topic: topic,
payload: web3.toHex(payload),
ttl: 20,
powTime: 20,
powTarget: 0.01,
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + message;
}
`,
true,
},
}
for _, tc := range testCases {
s.T().Log(tc.name)
chatID := crypto.Keccak256Hash([]byte(tc.name)).Hex()
s.Jail.Parse(chatID, `
var shh = web3.shh;
// topic must be 4-byte long
var makeTopic = function () {
var topic = '0x';
for (var i = 0; i < 8; i++) {
topic += Math.floor(Math.random() * 16).toString(16);
}
return topic;
};
`)
cell, err := s.Jail.Cell(chatID)
s.NoError(err, "cannot get VM")
// Setup filters and post messages.
_, err = cell.Run(tc.code)
s.NoError(err)
if !tc.useFilter {
continue
}
done := make(chan struct{})
timedOut := make(chan struct{})
go func() {
select {
case <-done:
case <-time.After(s.Timeout):
close(timedOut)
}
}()
poll_loop:
for {
// Use polling because:
// (1) filterId is not assigned immediately,
// (2) messages propagate with some delay.
select {
case <-done:
break poll_loop
case <-timedOut:
s.FailNow("polling for messages timed out")
case <-time.After(time.Second):
}
filter, err := cell.Get("filter")
s.NoError(err, "cannot get filter")
filterID, err := filter.Object().Get("filterId")
s.NoError(err, "cannot get filterId")
// FilterID is not assigned yet.
if filterID.IsNull() {
continue
}
payload, err := cell.Get("payload")
s.NoError(err, "cannot get payload")
messages, err := s.WhisperAPI.GetFilterMessages(filterID.String())
s.NoError(err)
for _, m := range messages {
s.Equal(payload.String(), string(m.Payload))
close(done)
}
}
}
}
func (s *WhisperJailTestSuite) TestEncryptedAnonymousMessage() {
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
accountKey2, accountKey2Hex, err := s.GetAccountKey(TestConfig.Account2)
s.NoError(err)
topicSlice := make([]byte, whisper.TopicLength)
_, err = rand.Read(topicSlice)
s.NoError(err)
topic := whisper.BytesToTopic(topicSlice)
filter, err := s.WhisperAPI.NewMessageFilter(whisper.Criteria{
PrivateKeyID: accountKey2Hex,
Topics: []whisper.TopicType{topic},
})
s.NoError(err)
ok, err := s.WhisperAPI.Post(context.Background(), whisper.NewMessage{
TTL: 20,
PowTarget: 0.01,
PowTime: 20,
Topic: topic,
PublicKey: crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey),
Payload: []byte(whisperMessage4),
})
s.NoError(err)
s.True(ok)
done := make(chan struct{})
timedOut := make(chan struct{})
go func() {
select {
case <-done:
case <-time.After(s.Timeout):
close(timedOut)
}
}()
for {
select {
case <-done:
return
case <-timedOut:
s.FailNow("polling for messages timed out")
case <-time.After(time.Second):
}
messages, err := s.WhisperAPI.GetFilterMessages(filter)
s.NoError(err)
for _, m := range messages {
s.Equal(whisperMessage4, string(m.Payload))
close(done)
}
}
}

View File

@ -1,4 +1,4 @@
package node_test package whisper
import ( import (
"context" "context"
@ -6,10 +6,11 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/e2e"
"github.com/status-im/status-go/geth/account" "github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/node" "github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing" . "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -18,49 +19,45 @@ func TestWhisperTestSuite(t *testing.T) {
} }
type WhisperTestSuite struct { type WhisperTestSuite struct {
BaseTestSuite e2e.NodeManagerTestSuite
} }
func (s *WhisperTestSuite) SetupTest() { func (s *WhisperTestSuite) SetupTest() {
s.NodeManager = node.NewNodeManager() s.NodeManager = node.NewNodeManager()
s.Require().NotNil(s.NodeManager) s.NotNil(s.NodeManager)
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
} }
// TODO(adam): can anyone explain what this test is testing?
// I don't see any race condition testing here.
func (s *WhisperTestSuite) TestWhisperFilterRace() { func (s *WhisperTestSuite) TestWhisperFilterRace() {
require := s.Require()
require.NotNil(s.NodeManager)
s.StartTestNode(params.RinkebyNetworkID) s.StartTestNode(params.RinkebyNetworkID)
defer s.StopTestNode() defer s.StopTestNode()
whisperService, err := s.NodeManager.WhisperService() whisperService, err := s.NodeManager.WhisperService()
require.NoError(err) s.NoError(err)
require.NotNil(whisperService)
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
require.NotNil(whisperAPI)
accountManager := account.NewManager(s.NodeManager) accountManager := account.NewManager(s.NodeManager)
require.NotNil(accountManager) s.NotNil(accountManager)
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
// account1 // account1
_, accountKey1, err := accountManager.AddressToDecryptedAccount(TestConfig.Account1.Address, TestConfig.Account1.Password) _, accountKey1, err := accountManager.AddressToDecryptedAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err) s.NoError(err)
accountKey1Byte := crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey) accountKey1Byte := crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey)
key1ID, err := whisperService.AddKeyPair(accountKey1.PrivateKey) key1ID, err := whisperService.AddKeyPair(accountKey1.PrivateKey)
require.NoError(err) s.NoError(err)
ok := whisperAPI.HasKeyPair(context.Background(), key1ID) ok := whisperAPI.HasKeyPair(context.Background(), key1ID)
require.True(ok, "identity not injected") s.True(ok, "identity not injected")
// account2 // account2
_, accountKey2, err := accountManager.AddressToDecryptedAccount(TestConfig.Account2.Address, TestConfig.Account2.Password) _, accountKey2, err := accountManager.AddressToDecryptedAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
require.NoError(err) s.NoError(err)
key2ID, err := whisperService.AddKeyPair(accountKey2.PrivateKey) key2ID, err := whisperService.AddKeyPair(accountKey2.PrivateKey)
require.NoError(err) s.NoError(err)
ok = whisperAPI.HasKeyPair(context.Background(), key2ID) ok = whisperAPI.HasKeyPair(context.Background(), key2ID)
require.True(ok, "identity not injected") s.True(ok, "identity not injected")
// race filter addition // race filter addition
filterAdded := make(chan struct{}) filterAdded := make(chan struct{})
@ -96,3 +93,26 @@ func (s *WhisperTestSuite) TestWhisperFilterRace() {
<-allFiltersAdded <-allFiltersAdded
} }
func (s *WhisperTestSuite) TestLogout() {
s.StartTestNode(params.RinkebyNetworkID)
defer s.StopTestNode()
whisperService, err := s.NodeManager.WhisperService()
s.NoError(err)
accountManager := account.NewManager(s.NodeManager)
s.NotNil(accountManager)
// create an account
address, pubKey, _, err := accountManager.CreateAccount(TestConfig.Account1.Password)
s.NoError(err)
// make sure that identity doesn't exist (yet) in Whisper
s.False(whisperService.HasKeyPair(pubKey), "identity already present in whisper")
s.NoError(accountManager.SelectAccount(address, TestConfig.Account1.Password))
s.True(whisperService.HasKeyPair(pubKey), "identity not injected into whisper")
s.NoError(accountManager.Logout())
s.False(whisperService.HasKeyPair(pubKey), "identity not cleared from whisper")
}

View File

@ -12,44 +12,23 @@ import (
gethcommon "github.com/ethereum/go-ethereum/common" gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/geth/account" "github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common" "github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/node" . "github.com/status-im/status-go/testing"
. "github.com/status-im/status-go/geth/testing" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
) )
func TestAccountsTestSuite(t *testing.T) { func TestVerifyAccountPassword(t *testing.T) {
suite.Run(t, new(AccountsTestSuite)) acctManager := account.NewManager(nil)
}
type AccountsTestSuite struct {
BaseTestSuite
}
func (s *AccountsTestSuite) SetupTest() {
require := s.Require()
s.NodeManager = node.NewNodeManager()
require.NotNil(s.NodeManager)
require.IsType(&node.NodeManager{}, s.NodeManager)
}
func (s *AccountsTestSuite) TestVerifyAccountPassword() {
require := s.Require()
require.NotNil(s.NodeManager)
accountManager := account.NewManager(nil)
require.NotNil(accountManager)
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts") keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
require.NoError(err) require.NoError(t, err)
defer os.RemoveAll(keyStoreDir) // nolint: errcheck defer os.RemoveAll(keyStoreDir)
emptyKeyStoreDir, err := ioutil.TempDir(os.TempDir(), "empty") emptyKeyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts_empty")
require.NoError(err) require.NoError(t, err)
defer os.RemoveAll(emptyKeyStoreDir) // nolint: errcheck defer os.RemoveAll(emptyKeyStoreDir)
// import account keys // import account keys
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk")) require.NoError(t, common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk")) require.NoError(t, common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
account1Address := gethcommon.BytesToAddress(gethcommon.FromHex(TestConfig.Account1.Address)) account1Address := gethcommon.BytesToAddress(gethcommon.FromHex(TestConfig.Account1.Address))
@ -97,18 +76,17 @@ func (s *AccountsTestSuite) TestVerifyAccountPassword() {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
s.T().Log(testCase.name) accountKey, err := acctManager.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
accountKey, err := accountManager.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
if !reflect.DeepEqual(err, testCase.expectedError) { if !reflect.DeepEqual(err, testCase.expectedError) {
s.FailNow(fmt.Sprintf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err)) require.FailNow(t, fmt.Sprintf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err))
} }
if err == nil { if err == nil {
if accountKey == nil { if accountKey == nil {
s.T().Error("no error reported, but account key is missing") require.Fail(t, "no error reported, but account key is missing")
} }
accountAddress := gethcommon.BytesToAddress(gethcommon.FromHex(testCase.address)) accountAddress := gethcommon.BytesToAddress(gethcommon.FromHex(testCase.address))
if accountKey.Address != accountAddress { if accountKey.Address != accountAddress {
s.T().Fatalf("account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex()) require.Fail(t, "account mismatch: have %s, want %s", accountKey.Address.Hex(), accountAddress.Hex())
} }
} }
} }
@ -116,18 +94,18 @@ func (s *AccountsTestSuite) TestVerifyAccountPassword() {
// TestVerifyAccountPasswordWithAccountBeforeEIP55 verifies if VerifyAccountPassword // TestVerifyAccountPasswordWithAccountBeforeEIP55 verifies if VerifyAccountPassword
// can handle accounts before introduction of EIP55. // can handle accounts before introduction of EIP55.
func (s *AccountsTestSuite) TestVerifyAccountPasswordWithAccountBeforeEIP55() { func TestVerifyAccountPasswordWithAccountBeforeEIP55(t *testing.T) {
keyStoreDir, err := ioutil.TempDir("", "status-accounts-test") keyStoreDir, err := ioutil.TempDir("", "status-accounts-test")
s.NoError(err) require.NoError(t, err)
defer os.RemoveAll(keyStoreDir) defer os.RemoveAll(keyStoreDir)
// Import keys and make sure one was created before EIP55 introduction. // Import keys and make sure one was created before EIP55 introduction.
err = common.ImportTestAccount(keyStoreDir, "test-account1-before-eip55.pk") err = common.ImportTestAccount(keyStoreDir, "test-account1-before-eip55.pk")
s.NoError(err) require.NoError(t, err)
acctManager := account.NewManager(nil) acctManager := account.NewManager(nil)
address := gethcommon.HexToAddress(TestConfig.Account1.Address) address := gethcommon.HexToAddress(TestConfig.Account1.Address)
_, err = acctManager.VerifyAccountPassword(keyStoreDir, address.Hex(), TestConfig.Account1.Password) _, err = acctManager.VerifyAccountPassword(keyStoreDir, address.Hex(), TestConfig.Account1.Password)
s.NoError(err) require.NoError(t, err)
} }

View File

@ -118,14 +118,14 @@ func (m *StatusBackend) StopNode() (<-chan struct{}, error) {
} }
<-m.nodeReady <-m.nodeReady
m.txQueueManager.Stop()
m.jailManager.Stop()
nodeStopped, err := m.nodeManager.StopNode() nodeStopped, err := m.nodeManager.StopNode()
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.txQueueManager.Stop()
m.jailManager.Stop()
backendStopped := make(chan struct{}, 1) backendStopped := make(chan struct{}, 1)
go func() { go func() {
<-nodeStopped <-nodeStopped

View File

@ -1,394 +0,0 @@
package api_test
import (
"errors"
"fmt"
"strings"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing"
)
func (s *BackendTestSuite) TestAccountsList() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
runningNode, err := s.backend.NodeManager().Node()
require.NoError(err)
require.NotNil(runningNode)
accounts, err := s.backend.AccountManager().Accounts()
require.NoError(err)
// make sure that we start with empty accounts list (nobody has logged in yet)
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
// create an account
address, _, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
// ensure that there is still no accounts returned
accounts, err = s.backend.AccountManager().Accounts()
require.NoError(err)
require.Zero(len(accounts), "accounts returned, while there should be none (we haven't logged in yet)")
// select account (sub-accounts will be created for this key)
err = s.backend.AccountManager().SelectAccount(address, TestConfig.Account1.Password)
require.NoError(err, "account selection failed")
// at this point main account should show up
accounts, err = s.backend.AccountManager().Accounts()
require.NoError(err)
require.Equal(1, len(accounts), "exactly single account is expected (main account)")
require.Equal(string(accounts[0].Hex()), address,
fmt.Sprintf("main account is not retured as the first key: got %s, expected %s", accounts[0].Hex(), address))
// create sub-account 1
subAccount1, subPubKey1, err := s.backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
require.NoError(err, "cannot create sub-account")
// now we expect to see both main account and sub-account 1
accounts, err = s.backend.AccountManager().Accounts()
require.NoError(err)
require.Equal(2, len(accounts), "exactly 2 accounts are expected (main + sub-account 1)")
require.Equal(string(accounts[0].Hex()), address, "main account is not retured as the first key")
require.Equal(string(accounts[1].Hex()), subAccount1, "subAcount1 not returned")
// create sub-account 2, index automatically progresses
subAccount2, subPubKey2, err := s.backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
require.NoError(err, "cannot create sub-account")
require.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
// finally, all 3 accounts should show up (main account, sub-accounts 1 and 2)
accounts, err = s.backend.AccountManager().Accounts()
require.NoError(err)
require.Equal(3, len(accounts), "unexpected number of accounts")
require.Equal(string(accounts[0].Hex()), address, "main account is not retured as the first key")
subAccount1MatchesKey1 := string(accounts[1].Hex()) != subAccount1
subAccount1MatchesKey2 := string(accounts[2].Hex()) != subAccount1
require.False(!subAccount1MatchesKey1 && !subAccount1MatchesKey2, "subAcount1 not returned")
subAccount2MatchesKey1 := string(accounts[1].Hex()) != subAccount2
subAccount2MatchesKey2 := string(accounts[2].Hex()) != subAccount2
require.False(!subAccount2MatchesKey1 && !subAccount2MatchesKey2, "subAcount2 not returned")
}
func (s *BackendTestSuite) TestCreateChildAccount() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
keyStore, err := s.backend.NodeManager().AccountKeyStore()
require.NoError(err)
require.NotNil(keyStore)
// create an account
address, pubKey, mnemonic, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
acct, err := common.ParseAccountString(address)
require.NoError(err, "can not get account from address")
// 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)
require.NoError(err, "can not obtain decrypted account key")
require.NotNil(key.ExtendedKey, "CKD#2 has not been generated for new account")
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
_, _, err = s.backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
require.EqualError(account.ErrNoAccountSelected, err.Error(), "expected error is not returned (tried to create sub-account w/o login)")
err = s.backend.AccountManager().SelectAccount(address, TestConfig.Account1.Password)
require.NoError(err, "cannot select account")
// try to create sub-account with wrong password
_, _, err = s.backend.AccountManager().CreateChildAccount("", "wrong password")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
require.EqualError(expectedErr, err.Error(), "create sub-account with wrong password")
// create sub-account (from implicit parent)
subAccount1, subPubKey1, err := s.backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
require.NoError(err, "cannot create sub-account")
// make sure that sub-account index automatically progresses
subAccount2, subPubKey2, err := s.backend.AccountManager().CreateChildAccount("", TestConfig.Account1.Password)
require.NoError(err)
require.False(subAccount1 == subAccount2 || subPubKey1 == subPubKey2, "sub-account index auto-increament failed")
// create sub-account (from explicit parent)
subAccount3, subPubKey3, err := s.backend.AccountManager().CreateChildAccount(subAccount2, TestConfig.Account1.Password)
require.NoError(err)
require.False(subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3)
}
func (s *BackendTestSuite) TestRecoverAccount() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
keyStore, err := s.backend.NodeManager().AccountKeyStore()
require.NoError(err)
require.NotNil(keyStore)
// create an account
address, pubKey, mnemonic, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
// try recovering using password + mnemonic
addressCheck, pubKeyCheck, err := s.backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
require.NoError(err, "recover account failed")
require.False(address != addressCheck || pubKey != pubKeyCheck, "incorrect accound details recovered")
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
account, err := common.ParseAccountString(address)
require.NoError(err, "can not get account from address")
account, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
require.NoError(err, "can not obtain decrypted account key")
extChild2String := key.ExtendedKey.String()
require.NoError(keyStore.Delete(account, TestConfig.Account1.Password), "cannot remove account")
addressCheck, pubKeyCheck, err = s.backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
require.NoError(err, "recover account failed (for non-cached account)")
require.False(address != addressCheck || pubKey != pubKeyCheck,
"incorrect account details recovered (for non-cached account)")
// make sure that extended key exists and is imported ok too
_, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
require.NoError(err)
require.Equal(extChild2String, key.ExtendedKey.String(), "CKD#2 key mismatch")
// make sure that calling import several times, just returns from cache (no error is expected)
addressCheck, pubKeyCheck, err = s.backend.AccountManager().RecoverAccount(TestConfig.Account1.Password, mnemonic)
require.NoError(err, "recover account failed (for non-cached account)")
require.False(address != addressCheck || pubKey != pubKeyCheck,
"incorrect account details recovered (for non-cached account)")
// time to login with recovered data
whisperService := s.WhisperService()
// make sure that identity is not (yet injected)
require.False(whisperService.HasKeyPair(pubKeyCheck), "identity already present in whisper")
require.NoError(s.backend.AccountManager().SelectAccount(addressCheck, TestConfig.Account1.Password))
require.True(whisperService.HasKeyPair(pubKeyCheck), "identity not injected into whisper")
}
func (s *BackendTestSuite) TestSelectAccount() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
// test to see if the account was injected in whisper
whisperService := s.WhisperService()
// create an account
address1, pubKey1, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s}", address1, pubKey1)
address2, pubKey2, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
s.T().Logf("Account created: {address: %s, key: %s}", address2, pubKey2)
// make sure that identity is not (yet injected)
require.False(whisperService.HasKeyPair(pubKey1), "identity already present in whisper")
// try selecting with wrong password
err = s.backend.AccountManager().SelectAccount(address1, "wrongPassword")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
require.EqualError(expectedErr, err.Error(), "select account is expected to throw error: wrong password used")
err = s.backend.AccountManager().SelectAccount(address1, TestConfig.Account1.Password)
require.NoError(err)
require.True(whisperService.HasKeyPair(pubKey1), "identity not injected into whisper")
// select another account, make sure that previous account is wiped out from Whisper cache
require.False(whisperService.HasKeyPair(pubKey2), "identity already present in whisper")
require.NoError(s.backend.AccountManager().SelectAccount(address2, TestConfig.Account1.Password))
require.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
require.False(whisperService.HasKeyPair(pubKey1), "identity should be removed, but it is still present in whisper")
}
func (s *BackendTestSuite) TestLogout() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
whisperService := s.WhisperService()
// create an account
address, pubKey, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
// make sure that identity doesn't exist (yet) in Whisper
require.False(whisperService.HasKeyPair(pubKey), "identity already present in whisper")
require.NoError(s.backend.AccountManager().SelectAccount(address, TestConfig.Account1.Password))
require.True(whisperService.HasKeyPair(pubKey), "identity not injected into whisper")
require.NoError(s.backend.AccountManager().Logout())
require.False(whisperService.HasKeyPair(pubKey), "identity not cleared from whisper")
}
func (s *BackendTestSuite) TestSelectedAccountOnRestart() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
// we need to make sure that selected account is injected as identity into Whisper
whisperService := s.WhisperService()
// create test accounts
address1, pubKey1, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
address2, pubKey2, _, err := s.backend.AccountManager().CreateAccount(TestConfig.Account1.Password)
require.NoError(err)
// make sure that identity is not (yet injected)
require.False(whisperService.HasKeyPair(pubKey1), "identity already present in whisper")
// make sure that no account is selected by default
selectedAccount, err := s.backend.AccountManager().SelectedAccount()
require.EqualError(account.ErrNoAccountSelected, err.Error(), "account selected, but should not be")
require.Nil(selectedAccount)
// select account
err = s.backend.AccountManager().SelectAccount(address1, "wrongPassword")
expectedErr := errors.New("cannot retrieve a valid key for a given account: could not decrypt key with given passphrase")
require.EqualError(expectedErr, err.Error())
require.NoError(s.backend.AccountManager().SelectAccount(address1, TestConfig.Account1.Password))
require.True(whisperService.HasKeyPair(pubKey1), "identity not injected into whisper")
// select another account, make sure that previous account is wiped out from Whisper cache
require.False(whisperService.HasKeyPair(pubKey2), "identity already present in whisper")
require.NoError(s.backend.AccountManager().SelectAccount(address2, TestConfig.Account1.Password))
require.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
require.False(whisperService.HasKeyPair(pubKey1), "identity should be removed, but it is still present in whisper")
// stop node (and all of its sub-protocols)
nodeConfig, err := s.backend.NodeManager().NodeConfig()
require.NoError(err)
require.NotNil(nodeConfig)
preservedNodeConfig := *nodeConfig
nodeStoped, err := s.backend.StopNode()
require.NoError(err)
<-nodeStoped
// make sure that account is still selected
selectedAccount, err = s.backend.AccountManager().SelectedAccount()
require.NoError(err)
require.NotNil(selectedAccount)
require.Equal(selectedAccount.Address.Hex(), address2, "incorrect address selected")
// resume node
nodeStarted, err := s.backend.StartNode(&preservedNodeConfig)
require.NoError(err)
<-nodeStarted
// re-check selected account (account2 MUST be selected)
selectedAccount, err = s.backend.AccountManager().SelectedAccount()
require.NoError(err)
require.NotNil(selectedAccount)
require.Equal(selectedAccount.Address.Hex(), address2, "incorrect address selected")
// make sure that Whisper gets identity re-injected
whisperService = s.WhisperService()
require.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
require.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
// now restart node using RestartNode() method, and make sure that account is still available
s.RestartTestNode()
defer s.StopTestBackend()
whisperService = s.WhisperService()
require.True(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
require.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
// now logout, and make sure that on restart no account is selected (i.e. logout works properly)
require.NoError(s.backend.AccountManager().Logout())
s.RestartTestNode()
whisperService = s.WhisperService()
require.False(whisperService.HasKeyPair(pubKey2), "identity not injected into whisper")
require.False(whisperService.HasKeyPair(pubKey1), "identity should not be present, but it is still present in whisper")
selectedAccount, err = s.backend.AccountManager().SelectedAccount()
require.EqualError(account.ErrNoAccountSelected, err.Error())
require.Nil(selectedAccount)
}
func (s *BackendTestSuite) TestRPCEthAccounts() {
require := s.Require()
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// log into test account
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err)
rpcClient := s.backend.NodeManager().RPCClient()
expected := `{"jsonrpc":"2.0","id":1,"result":["` + strings.ToLower(TestConfig.Account1.Address) + `"]}`
resp := rpcClient.CallRaw(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_accounts",
"params": []
}`)
require.Equal(expected, resp)
}
func (s *BackendTestSuite) TestRPCEthAccountsWithUpstream() {
require := s.Require()
s.StartTestBackend(params.RopstenNetworkID, WithUpstream("https://ropsten.infura.io/z6GCTmjdP3FETEJmMBI4"))
defer s.StopTestBackend()
// log into test account
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err)
rpcClient := s.backend.NodeManager().RPCClient()
expected := `{"jsonrpc":"2.0","id":1,"result":["` + strings.ToLower(TestConfig.Account1.Address) + `"]}`
resp := rpcClient.CallRaw(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_accounts",
"params": []
}`)
require.Equal(expected, resp)
}
// regression test: eth_getTransactionReceipt with invalid transaction hash should return null
func (s *BackendTestSuite) TestRegressionGetTransactionReceipt() {
require := s.Require()
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
rpcClient := s.backend.NodeManager().RPCClient()
// note: transaction hash is assumed to be invalid
got := rpcClient.CallRaw(`{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xbbebf28d0a3a3cbb38e6053a5b21f08f82c62b0c145a17b1c4313cac3f68ae7c"],"id":7}`)
expected := `{"jsonrpc":"2.0","id":7,"result":null}`
require.Equal(expected, got)
}

View File

@ -1,628 +0,0 @@
package api_test
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"sync"
"time"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/status-im/status-go/geth/txqueue"
"github.com/status-im/status-go/static"
)
const (
whisperMessage1 = `test message 1 (K1 -> K2, signed+encrypted, from us)`
whisperMessage2 = `test message 3 (K1 -> "", signed broadcast)`
whisperMessage3 = `test message 4 ("" -> "", anon broadcast)`
whisperMessage4 = `test message 5 ("" -> K1, encrypted anon broadcast)`
whisperMessage5 = `test message 6 (K2 -> K1, signed+encrypted, to us)`
txSendFolder = "testdata/jail/tx-send/"
testChatID = "testChat"
)
var (
baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
)
func (s *BackendTestSuite) TestContractDeployment() {
require := s.Require()
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
// Allow to sync, otherwise you'll get "Nonce too low."
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
// obtain VM for a given chat (to send custom JS to jailed version of Send())
jailInstance := s.backend.JailManager()
jailInstance.Parse(testChatID, "")
cell, err := jailInstance.Cell(testChatID)
require.NoError(err)
completeQueuedTransaction := make(chan struct{})
// replace transaction notification handler
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
var err error
err = json.Unmarshal([]byte(jsonEvent), &envelope)
require.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
if envelope.Type == txqueue.EventTransactionQueued {
// Use s.* for assertions - require leaves the channel unclosed.
event := envelope.Event.(map[string]interface{})
s.T().Logf("transaction queued and will be completed shortly, id: %v", event["id"])
s.NoError(s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
txID := event["id"].(string)
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
if s.NoError(err, event["id"]) {
s.T().Logf("contract transaction complete, URL: %s", "https://ropsten.etherscan.io/tx/"+txHash.Hex())
}
close(completeQueuedTransaction)
}
})
_, err = cell.Run(`
var responseValue = null;
var errorValue = null;
var testContract = web3.eth.contract([{"constant":true,"inputs":[{"name":"a","type":"int256"}],"name":"double","outputs":[{"name":"","type":"int256"}],"payable":false,"type":"function"}]);
var test = testContract.new(
{
from: '` + TestConfig.Account1.Address + `',
data: '0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029',
gas: '` + strconv.Itoa(params.DefaultGas) + `'
}, function (e, contract) {
// NOTE: The callback will fire twice!
if (e) {
errorValue = e;
return
}
// Once the contract has the transactionHash property set and once its deployed on an address.
if (!contract.address) {
responseValue = contract.transactionHash;
}
})
`)
require.NoError(err)
select {
case <-completeQueuedTransaction:
case <-time.After(time.Minute):
s.FailNow("test timed out")
}
// Wait until callback is fired and `responseValue` is set. Hacky but simple.
time.Sleep(2 * time.Second)
errorValue, err := cell.Get("errorValue")
require.NoError(err)
require.Equal("null", errorValue.String())
responseValue, err := cell.Get("responseValue")
require.NoError(err)
response, err := responseValue.ToString()
require.NoError(err)
expectedResponse := txHash.Hex()
require.Equal(expectedResponse, response)
}
func (s *BackendTestSuite) TestJailWhisper() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
jailInstance := s.backend.JailManager()
require.NotNil(jailInstance)
jailInstance.BaseJS(baseStatusJSCode)
whisperService := s.WhisperService()
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
accountManager := s.backend.AccountManager()
// account1
_, accountKey1, err := accountManager.AddressToDecryptedAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err)
accountKey1Hex := gethcommon.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
_, err = whisperService.AddKeyPair(accountKey1.PrivateKey)
require.NoError(err, fmt.Sprintf("identity not injected: %v", accountKey1Hex))
if ok := whisperAPI.HasKeyPair(context.Background(), accountKey1Hex); !ok {
require.FailNow(fmt.Sprintf("identity not injected: %v", accountKey1Hex))
}
// account2
_, accountKey2, err := accountManager.AddressToDecryptedAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
require.NoError(err)
accountKey2Hex := gethcommon.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey))
_, err = whisperService.AddKeyPair(accountKey2.PrivateKey)
require.NoError(err, fmt.Sprintf("identity not injected: %v", accountKey2Hex))
if ok := whisperAPI.HasKeyPair(context.Background(), accountKey2Hex); !ok {
require.FailNow(fmt.Sprintf("identity not injected: %v", accountKey2Hex))
}
passedTests := map[string]bool{
whisperMessage1: false,
whisperMessage2: false,
whisperMessage3: false,
whisperMessage4: false,
whisperMessage5: false,
}
installedFilters := map[string]string{
whisperMessage1: "",
whisperMessage2: "",
whisperMessage3: "",
whisperMessage4: "",
whisperMessage5: "",
}
testCases := []struct {
name string
testCode string
useFilter bool
}{
{
"test 0: ensure correct version of Whisper is used",
`
var expectedVersion = '5.0';
if (web3.version.whisper != expectedVersion) {
throw 'unexpected shh version, expected: ' + expectedVersion + ', got: ' + web3.version.whisper;
}
`,
false,
},
{
"test 1: encrypted signed message from us (From != nil && To != nil)",
`
var identity1 = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity1)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var identity2 = '` + accountKey2Hex + `';
if (!shh.hasKeyPair(identity2)) {
throw 'identitity "` + accountKey2Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage1 + `';
// start watching for messages
var filter = shh.newMessageFilter({
sig: identity1,
privateKeyID: identity2,
topics: [topic]
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
sig: identity1,
pubKey: identity2,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
var filterName = '` + whisperMessage1 + `';
var filterId = filter.filterId;
if (!filterId) {
throw 'filter not installed properly';
}
`,
true,
},
{
"test 2: signed (known sender) broadcast (From != nil && To == nil)",
`
var identity = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage2 + `';
// generate symmetric key
var keyid = shh.newSymKey();
if (!shh.hasSymKey(keyid)) {
throw new Error('key not found');
}
// start watching for messages
var filter = shh.newMessageFilter({
sig: identity,
topics: [topic],
symKeyID: keyid
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
sig: identity,
symKeyID: keyid,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
var filterName = '` + whisperMessage2 + `';
var filterId = filter.filterId;
if (!filterId) {
throw 'filter not installed properly';
}
`,
true,
},
{
"test 3: anonymous broadcast (From == nil && To == nil)",
`
var topic = makeTopic();
var payload = '` + whisperMessage3 + `';
// generate symmetric key
var keyid = shh.newSymKey();
if (!shh.hasSymKey(keyid)) {
throw new Error('key not found');
}
// start watching for messages
var filter = shh.newMessageFilter({
topics: [topic],
symKeyID: keyid
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
symKeyID: keyid,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
var filterName = '` + whisperMessage3 + `';
var filterId = filter.filterId;
if (!filterId) {
throw 'filter not installed properly';
}
`,
true,
},
{
"test 4: encrypted anonymous message (From == nil && To != nil)",
`
var identity = '` + accountKey2Hex + `';
if (!shh.hasKeyPair(identity)) {
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage4 + `';
// start watching for messages
var filter = shh.newMessageFilter({
privateKeyID: identity,
topics: [topic],
});
// post message
var message = {
ttl: 20,
powTarget: 0.01,
powTime: 20,
topic: topic,
pubKey: identity,
payload: web3.toHex(payload),
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + JSON.stringify(message);
}
var filterName = '` + whisperMessage4 + `';
var filterId = filter.filterId;
if (!filterId) {
throw 'filter not installed properly';
}
`,
true,
},
{
"test 5: encrypted signed response to us (From != nil && To != nil)",
`
var identity1 = '` + accountKey1Hex + `';
if (!shh.hasKeyPair(identity1)) {
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
}
var identity2 = '` + accountKey2Hex + `';
if (!shh.hasKeyPair(identity2)) {
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
}
var topic = makeTopic();
var payload = '` + whisperMessage5 + `';
// start watching for messages
var filter = shh.newMessageFilter({
privateKeyID: identity1,
sig: identity2,
topics: [topic],
});
// post message
var message = {
sig: identity2,
pubKey: identity1,
topic: topic,
payload: web3.toHex(payload),
ttl: 20,
powTime: 20,
powTarget: 0.01,
};
var sent = shh.post(message)
if (!sent) {
throw 'message not sent: ' + message;
}
var filterName = '` + whisperMessage5 + `';
var filterId = filter.filterId;
if (!filterId) {
throw 'filter not installed properly';
}
`,
true,
},
}
for _, testCase := range testCases {
s.T().Log(testCase.name)
testCaseKey := crypto.Keccak256Hash([]byte(testCase.name)).Hex()
jailInstance.Parse(testCaseKey, `
var shh = web3.shh;
// topic must be 4-byte long
var makeTopic = function () {
var topic = '0x';
for (var i = 0; i < 8; i++) {
topic += Math.floor(Math.random() * 16).toString(16);
}
return topic;
};
`)
cell, err := jailInstance.Cell(testCaseKey)
require.NoError(err, "cannot get VM")
// post messages
_, err = cell.Run(testCase.testCode)
require.NoError(err)
if !testCase.useFilter {
continue
}
// update installed filters
filterId, err := cell.Get("filterId")
require.NoError(err, "cannot get filterId")
filterName, err := cell.Get("filterName")
require.NoError(err, "cannot get filterName")
_, ok := installedFilters[filterName.String()]
require.True(ok, "unrecognized filter")
installedFilters[filterName.String()] = filterId.String()
}
time.Sleep(2 * time.Second) // allow whisper to poll
for testKey, filter := range installedFilters {
if filter != "" {
s.T().Logf("filter found: %v", filter)
messages, err := whisperAPI.GetFilterMessages(filter)
require.NoError(err)
for _, message := range messages {
s.T().Logf("message found: %s", string(message.Payload))
passedTests[testKey] = true
}
}
}
for testName, passedTest := range passedTests {
s.True(passedTest, "test not passed: %v", testName)
}
}
func (s *BackendTestSuite) TestJailVMPersistence() {
require := s.Require()
s.StartTestBackend(params.RopstenNetworkID)
defer s.StopTestBackend()
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
// log into account from which transactions will be sent
err := s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
require.NoError(err, "cannot select account: %v", TestConfig.Account1.Address)
type testCase struct {
command string
params string
validator func(response string) error
}
var testCases = []testCase{
{
`["sendTestTx"]`,
`{"amount": "0.000001", "from": "` + TestConfig.Account1.Address + `"}`,
func(response string) error {
if strings.Contains(response, "error") {
return fmt.Errorf("unexpected response: %v", response)
}
return nil
},
},
{
`["sendTestTx"]`,
`{"amount": "0.000002", "from": "` + TestConfig.Account1.Address + `"}`,
func(response string) error {
if strings.Contains(response, "error") {
return fmt.Errorf("unexpected response: %v", response)
}
return nil
},
},
{
`["ping"]`,
`{"pong": "Ping1", "amount": 0.42}`,
func(response string) error {
expectedResponse := `{"result": "Ping1"}`
if response != expectedResponse {
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
}
return nil
},
},
{
`["ping"]`,
`{"pong": "Ping2", "amount": 0.42}`,
func(response string) error {
expectedResponse := `{"result": "Ping2"}`
if response != expectedResponse {
return fmt.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
}
return nil
},
},
}
jailInstance := s.backend.JailManager()
jailInstance.BaseJS(baseStatusJSCode)
parseResult := jailInstance.Parse(testChatID, `
var total = 0;
_status_catalog['ping'] = function(params) {
total += Number(params.amount);
return params.pong;
}
_status_catalog['sendTestTx'] = function(params) {
var amount = params.amount;
var transaction = {
"from": params.from,
"to": "`+TestConfig.Account2.Address+`",
"value": web3.toWei(amount, "ether")
};
web3.eth.sendTransaction(transaction, function (error, result) {
if(!error) {
total += Number(amount);
}
});
}
`)
require.NotContains(parseResult, "error", "further will fail if initial parsing failed")
var wg sync.WaitGroup
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
s.T().Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
if envelope.Type == txqueue.EventTransactionQueued {
event := envelope.Event.(map[string]interface{})
s.T().Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
//var txHash common.Hash
txID := event["id"].(string)
txHash, err := s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
require.NoError(err, "cannot complete queued transaction[%v]: %v", event["id"], err)
s.T().Logf("Transaction complete: https://ropsten.etherscan.io/tx/%s", txHash.Hex())
}
})
// run commands concurrently
for _, tc := range testCases {
wg.Add(1)
go func(tc testCase) {
defer wg.Done() // ensure we don't forget it
s.T().Logf("CALL START: %v %v", tc.command, tc.params)
response := jailInstance.Call(testChatID, tc.command, tc.params)
if err := tc.validator(response); err != nil {
s.T().Errorf("failed test validation: %v, err: %v", tc.command, err)
}
s.T().Logf("CALL END: %v %v", tc.command, tc.params)
}(tc)
}
finishTestCases := make(chan struct{})
go func() {
wg.Wait()
close(finishTestCases)
}()
select {
case <-finishTestCases:
case <-time.After(time.Minute):
s.FailNow("some tests failed to finish in time")
}
// Wait till eth_sendTransaction callbacks have been executed.
// FIXME(tiabc): more reliable means of testing that.
time.Sleep(5 * time.Second)
// Validate total.
cell, err := jailInstance.Cell(testChatID)
require.NoError(err)
totalOtto, err := cell.Get("total")
require.NoError(err)
total, err := totalOtto.ToFloat()
require.NoError(err)
s.T().Log(total)
require.InDelta(0.840003, total, 0.0000001)
}

View File

@ -1,626 +0,0 @@
package api_test
import (
"encoding/json"
"math/rand"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/keystore"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/les"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/api"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/geth/log"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/status-im/status-go/geth/txqueue"
"github.com/stretchr/testify/suite"
)
func TestBackendTestSuite(t *testing.T) {
suite.Run(t, new(BackendTestSuite))
}
type BackendTestSuite struct {
suite.Suite
backend *api.StatusBackend
}
func (s *BackendTestSuite) SetupTest() {
require := s.Require()
backend := api.NewStatusBackend()
require.NotNil(backend)
require.IsType(&api.StatusBackend{}, backend)
s.backend = backend
}
func (s *BackendTestSuite) StartTestBackend(networkID int, opts ...TestNodeOption) {
require := s.Require()
require.NotNil(s.backend)
nodeConfig, err := MakeTestNodeConfig(networkID)
require.NoError(err)
// Apply any options altering node config.
for i := range opts {
opts[i](nodeConfig)
}
// import account keys
require.NoError(common.ImportTestAccount(nodeConfig.KeyStoreDir, "test-account1.pk"))
require.NoError(common.ImportTestAccount(nodeConfig.KeyStoreDir, "test-account2.pk"))
require.False(s.backend.IsNodeRunning())
nodeStarted, err := s.backend.StartNode(nodeConfig)
require.NoError(err)
<-nodeStarted
require.True(s.backend.IsNodeRunning())
}
func (s *BackendTestSuite) StopTestBackend() {
require := s.Require()
require.NotNil(s.backend)
require.True(s.backend.IsNodeRunning())
backendStopped, err := s.backend.StopNode()
require.NoError(err)
<-backendStopped
require.False(s.backend.IsNodeRunning())
}
func (s *BackendTestSuite) WhisperService() *whisper.Whisper {
require := s.Require()
require.NotNil(s.backend)
whisperService, err := s.backend.NodeManager().WhisperService()
require.NoError(err)
require.NotNil(whisperService)
return whisperService
}
func (s *BackendTestSuite) LightEthereumService() *les.LightEthereum {
require := s.Require()
require.NotNil(s.backend)
lightEthereum, err := s.backend.NodeManager().LightEthereumService()
require.NoError(err)
require.NotNil(lightEthereum)
return lightEthereum
}
func (s *BackendTestSuite) TxQueueManager() common.TxQueueManager {
return s.backend.TxQueueManager()
}
func (s *BackendTestSuite) RestartTestNode() {
require := s.Require()
require.NotNil(s.backend)
require.True(s.backend.IsNodeRunning())
require.True(s.backend.IsNodeRunning())
nodeRestarted, err := s.backend.RestartNode()
require.NoError(err)
require.NotNil(nodeRestarted)
<-nodeRestarted
require.True(s.backend.IsNodeRunning())
}
func (s *BackendTestSuite) TestNewBackend() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
}
func (s *BackendTestSuite) TestNodeStartStop() {
require := s.Require()
require.NotNil(s.backend)
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
// try stopping non-started node
require.False(s.backend.IsNodeRunning())
nodeStopped, err := s.backend.StopNode()
require.EqualError(err, node.ErrNoRunningNode.Error())
require.Nil(nodeStopped)
require.False(s.backend.IsNodeRunning())
nodeStarted, err := s.backend.StartNode(nodeConfig)
require.NoError(err)
require.NotNil(nodeStarted)
<-nodeStarted // wait till node is started
require.True(s.backend.IsNodeRunning())
// try starting another node (w/o stopping the previously started node)
nodeStarted, err = s.backend.StartNode(nodeConfig)
require.EqualError(err, node.ErrNodeExists.Error())
require.Nil(nodeStarted)
// now stop node, and make sure that a new node, on different network can be started
nodeStopped, err = s.backend.StopNode()
require.NoError(err)
require.NotNil(nodeStopped)
<-nodeStopped
// start new node with exactly the same config
require.False(s.backend.IsNodeRunning())
nodeStarted, err = s.backend.StartNode(nodeConfig)
require.NoError(err)
require.NotNil(nodeStarted)
defer s.backend.StopNode()
<-nodeStarted
require.True(s.backend.IsNodeRunning())
}
func (s *BackendTestSuite) TestStartNodeWithUpstreamEnabled() {
require := s.Require()
backend := api.NewStatusBackend()
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
nodeStarted, err := backend.StartNode(nodeConfig)
require.NoError(err)
defer backend.StopNode()
<-nodeStarted
require.True(backend.IsNodeRunning())
}
// FIXME(tiabc): There's also a test with the same name in geth/node/rpc_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *BackendTestSuite) TestCallRPC() {
require := s.Require()
require.NotNil(s.backend)
nodeConfig, err := MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
nodeStarted, err := s.backend.StartNode(nodeConfig)
require.NoError(err)
require.NotNil(nodeStarted)
defer s.backend.StopNode()
<-nodeStarted
progress := make(chan struct{}, 25)
type rpcCall struct {
inputJSON string
validator func(resultJSON string)
}
var rpcCalls = []rpcCall{
{
`{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{
"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"gas": "0x76c0",
"gasPrice": "0x9184e72a000",
"value": "0x9184e72a",
"data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}`,
func(resultJSON string) {
log.Info("eth_sendTransaction")
s.NotContains(resultJSON, "error")
progress <- struct{}{}
},
},
{
`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`,
func(resultJSON string) {
expected := `{"jsonrpc":"2.0","id":67,"result":"5.0"}`
s.Equal(expected, resultJSON)
s.T().Log("shh_version: ", resultJSON)
progress <- struct{}{}
},
},
{
`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`,
func(resultJSON string) {
expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}`
s.Equal(expected, resultJSON)
s.T().Log("web3_sha3: ", resultJSON)
progress <- struct{}{}
},
},
{
`{"jsonrpc":"2.0","method":"net_version","params":[],"id":67}`,
func(resultJSON string) {
expected := `{"jsonrpc":"2.0","id":67,"result":"4"}`
s.Equal(expected, resultJSON)
s.T().Log("net_version: ", resultJSON)
progress <- struct{}{}
},
},
}
cnt := len(rpcCalls) - 1 // send transaction blocks up until complete/discarded/times out
for _, r := range rpcCalls {
go func(r rpcCall) {
s.T().Logf("Run test: %v", r.inputJSON)
resultJSON := s.backend.CallRPC(r.inputJSON)
r.validator(resultJSON)
}(r)
}
for range progress {
cnt -= 1
if cnt <= 0 {
break
}
}
}
func (s *BackendTestSuite) TestCallRPCSendTransaction() {
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
s.NoError(err)
nodeStarted, err := s.backend.StartNode(nodeConfig)
s.NoError(err)
defer s.backend.StopNode()
<-nodeStarted
// Allow to sync the blockchain.
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
err = s.backend.AccountManager().SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
s.NoError(err)
transactionCompleted := make(chan struct{})
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
var signal signal.Envelope
err := json.Unmarshal([]byte(rawSignal), &signal)
s.NoError(err)
if signal.Type == txqueue.EventTransactionQueued {
event := signal.Event.(map[string]interface{})
txID := event["id"].(string)
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account1.Password)
s.NoError(err, "cannot complete queued transaction %s", txID)
close(transactionCompleted)
}
})
result := s.backend.CallRPC(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendTransaction",
"params": [{
"from": "` + TestConfig.Account1.Address + `",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"value": "0x9184e72a"
}]
}`)
s.NotContains(result, "error")
select {
case <-transactionCompleted:
case <-time.After(time.Minute):
s.FailNow("sending transaction timed out")
}
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
}
func (s *BackendTestSuite) TestCallRPCSendTransactionUpstream() {
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
s.NoError(err)
nodeConfig.UpstreamConfig.Enabled = true
nodeConfig.UpstreamConfig.URL = "https://ropsten.infura.io/nKmXgiFgc2KqtoQ8BCGJ"
nodeStarted, err := s.backend.StartNode(nodeConfig)
s.NoError(err)
defer s.backend.StopNode()
<-nodeStarted
// Allow to sync the blockchain.
time.Sleep(TestConfig.Node.SyncSeconds * time.Second)
err = s.backend.AccountManager().SelectAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
s.NoError(err)
transactionCompleted := make(chan struct{})
var txHash gethcommon.Hash
signal.SetDefaultNodeNotificationHandler(func(rawSignal string) {
var signal signal.Envelope
err := json.Unmarshal([]byte(rawSignal), &signal)
s.NoError(err)
if signal.Type == txqueue.EventTransactionQueued {
event := signal.Event.(map[string]interface{})
txID := event["id"].(string)
// Complete with a wrong passphrase.
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), "some-invalid-passphrase")
s.EqualError(err, keystore.ErrDecrypt.Error(), "should return an error as the passphrase was invalid")
// Complete with a correct passphrase.
txHash, err = s.backend.CompleteTransaction(common.QueuedTxID(txID), TestConfig.Account2.Password)
s.NoError(err, "cannot complete queued transaction %s", txID)
close(transactionCompleted)
}
})
result := s.backend.CallRPC(`{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_sendTransaction",
"params": [{
"from": "` + TestConfig.Account2.Address + `",
"to": "` + TestConfig.Account1.Address + `",
"value": "0x9184e72a"
}]
}`)
s.NotContains(result, "error")
select {
case <-transactionCompleted:
case <-time.After(time.Minute):
s.FailNow("sending transaction timed out")
}
s.Equal(`{"jsonrpc":"2.0","id":1,"result":"`+txHash.String()+`"}`, result)
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *BackendTestSuite) TestRaceConditions() {
require := s.Require()
require.NotNil(s.backend)
cnt := 25
progress := make(chan struct{}, cnt)
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
nodeConfig1, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
nodeConfig2, err := MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
nodeConfigs := []*params.NodeConfig{nodeConfig1, nodeConfig2}
var funcsToTest = []func(*params.NodeConfig){
func(config *params.NodeConfig) {
log.Info("StartNode()")
_, err := s.backend.StartNode(config)
s.T().Logf("StartNode() for network: %d, error: %v", config.NetworkID, err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("StopNode()")
_, err := s.backend.StopNode()
s.T().Logf("StopNode() for network: %d, error: %v", config.NetworkID, err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("ResetChainData()")
_, err := s.backend.ResetChainData()
s.T().Logf("ResetChainData(), error: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("RestartNode()")
_, err := s.backend.RestartNode()
s.T().Logf("RestartNode(), error: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("NodeManager()")
instance := s.backend.NodeManager()
s.NotNil(instance)
s.IsType(&node.NodeManager{}, instance)
s.T().Logf("NodeManager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("AccountManager()")
instance := s.backend.AccountManager()
s.NotNil(instance)
s.IsType(&account.Manager{}, instance)
s.T().Logf("Manager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("JailManager()")
instance := s.backend.JailManager()
s.NotNil(instance)
s.IsType(&jail.Jail{}, instance)
s.T().Logf("JailManager(), result: %v", instance)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CreateAccount()")
address, pubKey, mnemonic, err := s.backend.AccountManager().CreateAccount("password")
s.T().Logf("CreateAccount(), error: %v (address: %v, pubKey: %v, mnemonic: %v)", err, address, pubKey, mnemonic)
if err == nil {
// SelectAccount
log.Info("CreateAccount()")
err = s.backend.AccountManager().SelectAccount(address, "password")
s.T().Logf("SelectAccount(%v, %v), error: %v", address, "password", err)
// CreateChildAccount
log.Info("CreateChildAccount()")
address, pubKey, err := s.backend.AccountManager().CreateChildAccount(address, "password")
s.T().Logf("CreateAccount(), error: %v (address: %v, pubKey: %v)", err, address, pubKey)
// RecoverAccount
log.Info("RecoverAccount()")
address, pubKey, err = s.backend.AccountManager().RecoverAccount("password", mnemonic)
s.T().Logf("RecoverAccount(), error: %v (address: %v, pubKey: %v)", err, address, pubKey)
}
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("VerifyAccountPassword()")
_, err := s.backend.AccountManager().VerifyAccountPassword(config.KeyStoreDir, "0x0", "bar")
s.T().Logf("VerifyAccountPassword(), err: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("Logout()")
s.T().Logf("Logout(), result: %v", s.backend.AccountManager().Logout())
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("IsNodeRunning()")
s.T().Logf("IsNodeRunning(), result: %v", s.backend.IsNodeRunning())
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CompleteTransaction()")
_, err := s.backend.CompleteTransaction("id", "password")
s.T().Logf("CompleteTransaction(), error: %v", err)
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("DiscardTransaction()")
s.T().Logf("DiscardTransaction(), error: %v", s.backend.DiscardTransaction("id"))
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("CompleteTransactions()")
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("CompleteTransactions(), result: %v", s.backend.CompleteTransactions(ids, "password"))
progress <- struct{}{}
},
func(config *params.NodeConfig) {
log.Info("DiscardTransactions()")
ids := []common.QueuedTxID{"id1", "id2"}
s.T().Logf("DiscardTransactions(), result: %v", s.backend.DiscardTransactions(ids))
progress <- struct{}{}
},
}
// increase StartNode()/StopNode() population
for i := 0; i < 5; i++ {
funcsToTest = append(funcsToTest, funcsToTest[0], funcsToTest[1])
}
for i := 0; i < cnt; i++ {
randConfig := nodeConfigs[rnd.Intn(len(nodeConfigs))]
randFunc := funcsToTest[rnd.Intn(len(funcsToTest))]
if rnd.Intn(100) > 75 { // introduce random delays
time.Sleep(500 * time.Millisecond)
}
go randFunc(randConfig)
}
for range progress {
cnt -= 1
if cnt <= 0 {
break
}
}
time.Sleep(2 * time.Second) // so that we see some logs
nodeStopped, _ := s.backend.StopNode() // just in case we have a node running
if nodeStopped != nil {
<-nodeStopped
}
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *BackendTestSuite) TestNetworkSwitching() {
require := s.Require()
require.NotNil(s.backend)
// get Ropsten config
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
require.NoError(err)
require.False(s.backend.IsNodeRunning())
nodeStarted, err := s.backend.StartNode(nodeConfig)
require.NoError(err)
<-nodeStarted // wait till node is started
require.True(s.backend.IsNodeRunning())
FirstBlockHash(require, s.backend.NodeManager(), "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
// now stop node, and make sure that a new node, on different network can be started
nodeStopped, err := s.backend.StopNode()
require.NoError(err)
<-nodeStopped
// start new node with completely different config
nodeConfig, err = MakeTestNodeConfig(params.RinkebyNetworkID)
require.NoError(err)
require.False(s.backend.IsNodeRunning())
nodeStarted, err = s.backend.StartNode(nodeConfig)
require.NoError(err)
<-nodeStarted
require.True(s.backend.IsNodeRunning())
// make sure we are on another network indeed
FirstBlockHash(require, s.backend.NodeManager(), "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
nodeStopped, err = s.backend.StopNode()
require.NoError(err)
<-nodeStopped
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *BackendTestSuite) TestResetChainData() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
time.Sleep(2 * time.Second) // allow to sync for some time
s.True(s.backend.IsNodeRunning())
nodeReady, err := s.backend.ResetChainData()
require.NoError(err)
<-nodeReady
s.True(s.backend.IsNodeRunning()) // new node, with previous config should be running
// make sure we can read the first byte, and it is valid (for Rinkeby)
FirstBlockHash(require, s.backend.NodeManager(), "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
}
// FIXME(tiabc): There's also a test with the same name in geth/node/manager_test.go
// so this test should only check StatusBackend logic with a mocked version of the underlying NodeManager.
func (s *BackendTestSuite) TestRestartNode() {
require := s.Require()
require.NotNil(s.backend)
s.StartTestBackend(params.RinkebyNetworkID)
defer s.StopTestBackend()
FirstBlockHash(require, s.backend.NodeManager(), "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
s.True(s.backend.IsNodeRunning())
nodeRestarted, err := s.backend.RestartNode()
require.NoError(err)
<-nodeRestarted
s.True(s.backend.IsNodeRunning()) // new node, with previous config should be running
// make sure we can read the first byte, and it is valid (for Rinkeby)
FirstBlockHash(require, s.backend.NodeManager(), "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
}

View File

@ -6,40 +6,30 @@ import (
"time" "time"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/static"
"github.com/stretchr/testify/suite"
) )
func (s *JailTestSuite) TestJailTimeoutFailure() { const (
require := s.Require() testChatID = "testChat"
)
cell, err := s.jail.NewCell(testChatID) var (
require.NoError(err) baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
require.NotNil(cell) )
defer cell.Stop()
// Attempt to run a timeout string against a Cell. type CellTestSuite struct {
_, err = cell.Run(` suite.Suite
var timerCounts = 0; jail *jail.Jail
setTimeout(function(n){
if (Date.now() - n < 50) {
throw new Error("Timed out");
}
timerCounts++;
}, 30, Date.now());
`)
require.NoError(err)
// wait at least 10x longer to decrease probability
// of false negatives as we using real clock here
time.Sleep(300 * time.Millisecond)
value, err := cell.Get("timerCounts")
require.NoError(err)
require.True(value.IsNumber())
require.Equal("0", value.String())
} }
func (s *JailTestSuite) TestJailTimeout() { func (s *CellTestSuite) SetupTest() {
s.jail = jail.New(nil)
s.NotNil(s.jail)
}
func (s *CellTestSuite) TestJailTimeout() {
require := s.Require() require := s.Require()
cell, err := s.jail.NewCell(testChatID) cell, err := s.jail.NewCell(testChatID)
@ -50,7 +40,7 @@ func (s *JailTestSuite) TestJailTimeout() {
// Attempt to run a timeout string against a Cell. // Attempt to run a timeout string against a Cell.
_, err = cell.Run(` _, err = cell.Run(`
var timerCounts = 0; var timerCounts = 0;
setTimeout(function(n){ setTimeout(function(n){
if (Date.now() - n < 50) { if (Date.now() - n < 50) {
throw new Error("Timed out"); throw new Error("Timed out");
} }
@ -70,7 +60,7 @@ func (s *JailTestSuite) TestJailTimeout() {
require.Equal("1", value.String()) require.Equal("1", value.String())
} }
func (s *JailTestSuite) TestJailLoopInCall() { func (s *CellTestSuite) TestJailLoopInCall() {
require := s.Require() require := s.Require()
// load Status JS and add test command to it // load Status JS and add test command to it
@ -113,7 +103,7 @@ func (s *JailTestSuite) TestJailLoopInCall() {
// TestJailLoopRace tests multiple setTimeout callbacks, // TestJailLoopRace tests multiple setTimeout callbacks,
// supposed to be run with '-race' flag. // supposed to be run with '-race' flag.
func (s *JailTestSuite) TestJailLoopRace() { func (s *CellTestSuite) TestJailLoopRace() {
require := s.Require() require := s.Require()
cell, err := s.jail.NewCell(testChatID) cell, err := s.jail.NewCell(testChatID)
@ -152,7 +142,7 @@ func (s *JailTestSuite) TestJailLoopRace() {
} }
} }
func (s *JailTestSuite) TestJailFetchPromise() { func (s *CellTestSuite) TestJailFetchPromise() {
body := `{"key": "value"}` body := `{"key": "value"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
@ -196,7 +186,7 @@ func (s *JailTestSuite) TestJailFetchPromise() {
} }
} }
func (s *JailTestSuite) TestJailFetchCatch() { func (s *CellTestSuite) TestJailFetchCatch() {
require := s.Require() require := s.Require()
cell, err := s.jail.NewCell(testChatID) cell, err := s.jail.NewCell(testChatID)
@ -239,7 +229,7 @@ func (s *JailTestSuite) TestJailFetchCatch() {
// TestJailFetchRace tests multiple fetch callbacks, // TestJailFetchRace tests multiple fetch callbacks,
// supposed to be run with '-race' flag. // supposed to be run with '-race' flag.
func (s *JailTestSuite) TestJailFetchRace() { func (s *CellTestSuite) TestJailFetchRace() {
body := `{"key": "value"}` body := `{"key": "value"}`
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
@ -302,7 +292,7 @@ func (s *JailTestSuite) TestJailFetchRace() {
// TestJailLoopCancel tests that cell.Stop() really cancels event // TestJailLoopCancel tests that cell.Stop() really cancels event
// loop and pending tasks. // loop and pending tasks.
func (s *JailTestSuite) TestJailLoopCancel() { func (s *CellTestSuite) TestJailLoopCancel() {
require := s.Require() require := s.Require()
// load Status JS and add test command to it // load Status JS and add test command to it

View File

@ -1,6 +1,7 @@
package fetch_test package fetch_test
import ( import (
"context"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
@ -210,7 +211,7 @@ func (s *FetchSuite) SetupTest() {
s.vm = vm.New(o) s.vm = vm.New(o)
s.loop = loop.New(s.vm) s.loop = loop.New(s.vm)
go s.loop.Run() go s.loop.Run(context.Background())
} }
func (s *FetchSuite) TearDownSuite() { func (s *FetchSuite) TearDownSuite() {

View File

@ -115,6 +115,12 @@ func (c CallTask) Cancel() {}
// pushing the resultant return value and error (or nil) into the associated // pushing the resultant return value and error (or nil) into the associated
// channels. If the call results in an error, it will return that error. // channels. If the call results in an error, it will return that error.
func (c CallTask) Execute(vm *vm.VM, l *loop.Loop) error { func (c CallTask) Execute(vm *vm.VM, l *loop.Loop) error {
// vm is not used directly here, but underlying
// FunctionCall in CallTask likely does use it,
// so we must to guard it here
vm.Lock()
defer vm.Unlock()
v, err := c.Function.Call(otto.NullValue(), c.Args...) v, err := c.Function.Call(otto.NullValue(), c.Args...)
c.Value <- v c.Value <- v
c.Error <- err c.Error <- err

View File

@ -1,6 +1,7 @@
package promise_test package promise_test
import ( import (
"context"
"testing" "testing"
"time" "time"
@ -88,7 +89,7 @@ func (s *PromiseSuite) SetupTest() {
s.vm = vm.New(o) s.vm = vm.New(o)
s.loop = loop.New(s.vm) s.loop = loop.New(s.vm)
go s.loop.Run() go s.loop.Run(context.Background())
err := promise.Define(s.vm, s.loop) err := promise.Define(s.vm, s.loop)
s.NoError(err) s.NoError(err)

View File

@ -1,6 +1,7 @@
package timers_test package timers_test
import ( import (
"context"
"testing" "testing"
"time" "time"
@ -106,7 +107,7 @@ func (s *TimersSuite) SetupTest() {
s.vm = vm.New(o) s.vm = vm.New(o)
s.loop = loop.New(s.vm) s.loop = loop.New(s.vm)
go s.loop.Run() go s.loop.Run(context.Background())
err := timers.Define(s.vm, s.loop) err := timers.Define(s.vm, s.loop)
s.NoError(err) s.NoError(err)

View File

@ -35,9 +35,6 @@ type Jail struct {
// New returns new Jail environment with the associated NodeManager. // New returns new Jail environment with the associated NodeManager.
// It's caller responsibility to call jail.Stop() when jail is not needed. // It's caller responsibility to call jail.Stop() when jail is not needed.
func New(nodeManager common.NodeManager) *Jail { func New(nodeManager common.NodeManager) *Jail {
if nodeManager == nil {
panic("Jail is missing mandatory dependencies")
}
return &Jail{ return &Jail{
nodeManager: nodeManager, nodeManager: nodeManager,
cells: make(map[string]*Cell), cells: make(map[string]*Cell),

View File

@ -1,345 +0,0 @@
package jail_test
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/status-im/status-go/geth/account"
"github.com/status-im/status-go/geth/jail"
"github.com/status-im/status-go/geth/node"
"github.com/status-im/status-go/geth/params"
"github.com/status-im/status-go/geth/signal"
. "github.com/status-im/status-go/geth/testing"
"github.com/status-im/status-go/static"
"github.com/stretchr/testify/suite"
)
const (
testChatID = "testChat"
)
var (
baseStatusJSCode = string(static.MustAsset("testdata/jail/status.js"))
txJSCode = string(static.MustAsset("testdata/jail/tx-send/tx-send.js"))
)
func TestJailTestSuite(t *testing.T) {
suite.Run(t, new(JailTestSuite))
}
type JailTestSuite struct {
BaseTestSuite
jail *jail.Jail
}
func (s *JailTestSuite) SetupTest() {
require := s.Require()
nodeManager := node.NewNodeManager()
require.NotNil(nodeManager)
accountManager := account.NewManager(nodeManager)
require.NotNil(accountManager)
jail := jail.New(nodeManager)
require.NotNil(jail)
s.jail = jail
s.NodeManager = nodeManager
}
func (s *JailTestSuite) TearDownTest() {
s.jail.Stop()
}
func (s *JailTestSuite) TestInit() {
require := s.Require()
errorWrapper := func(err error) string {
return `{"error":"` + err.Error() + `"}`
}
// get cell VM w/o defining cell first
cell, err := s.jail.Cell(testChatID)
require.EqualError(err, "cell[testChat] doesn't exist")
require.Nil(cell)
// create VM (w/o properly initializing base JS script)
err = errors.New("ReferenceError: '_status_catalog' is not defined")
require.Equal(errorWrapper(err), s.jail.Parse(testChatID, ``))
err = errors.New("ReferenceError: 'call' is not defined")
require.Equal(errorWrapper(err), s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`))
// get existing cell (even though we got errors, cell was still created)
cell, err = s.jail.Cell(testChatID)
require.NoError(err)
require.NotNil(cell)
statusJS := baseStatusJSCode + `;
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
s.jail.BaseJS(statusJS)
// now no error should occur
response := s.jail.Parse(testChatID, ``)
expectedResponse := `{"result": {"commands":{},"responses":{}}}`
require.Equal(expectedResponse, response)
// make sure that Call succeeds even w/o running node
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse = `{"result": 144}`
require.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestParse() {
require := s.Require()
extraCode := `
var _status_catalog = {
foo: 'bar'
};
`
response := s.jail.Parse("newChat", extraCode)
expectedResponse := `{"result": {"foo":"bar"}}`
require.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestFunctionCall() {
require := s.Require()
// load Status JS and add test command to it
statusJS := baseStatusJSCode + `;
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
s.jail.Parse(testChatID, statusJS)
// call with wrong chat id
response := s.jail.Call("chatIDNonExistent", "", "")
expectedError := `{"error":"cell[chatIDNonExistent] doesn't exist"}`
require.Equal(expectedError, response)
// call extraFunc()
response = s.jail.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse := `{"result": 144}`
require.Equal(expectedResponse, response)
}
// TestJailRPCAsyncSend was written to catch race conditions with a weird error message
// starting from `ReferenceError` as if otto vm were losing symbols.
func (s *JailTestSuite) TestJailRPCAsyncSend() {
require := s.Require()
s.StartTestNode(params.RopstenNetworkID)
defer s.StopTestNode()
// load Status JS and add test command to it
s.jail.BaseJS(baseStatusJSCode)
s.jail.Parse(testChatID, txJSCode)
cell, err := s.jail.Cell(testChatID)
require.NoError(err)
require.NotNil(cell)
// internally (since we replaced `web3.send` with `jail.Send`)
// all requests to web3 are forwarded to `jail.Send`
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
_, err = cell.Run(`_status_catalog.commands.sendAsync({
"from": "` + TestConfig.Account1.Address + `",
"to": "` + TestConfig.Account2.Address + `",
"value": "0.000001"
})`)
require.NoError(err, "Request failed to process")
}()
}
wg.Wait()
// TODO(divan): revisit this test. sendAsync now returns immediately,
// and we need no way here to halt jail loop, which executes actual
// transaction send in background. For now, just wait a couple of secs
// to let tests pass.
time.Sleep(2 * time.Second)
}
func (s *JailTestSuite) TestJailRPCSend() {
require := s.Require()
s.StartTestNode(params.RopstenNetworkID)
defer s.StopTestNode()
// load Status JS and add test command to it
s.jail.BaseJS(baseStatusJSCode)
s.jail.Parse(testChatID, ``)
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
require.NoError(err)
require.NotNil(cell)
// internally (since we replaced `web3.send` with `jail.Send`)
// all requests to web3 are forwarded to `jail.Send`
_, err = cell.Run(`
var balance = web3.eth.getBalance("` + TestConfig.Account1.Address + `");
var sendResult = web3.fromWei(balance, "ether")
`)
require.NoError(err)
value, err := cell.Get("sendResult")
require.NoError(err, "cannot obtain result of balance check operation")
balance, err := value.ToFloat()
require.NoError(err)
require.False(balance < 100, "wrong balance (there should be lots of test Ether on that account)")
}
func (s *JailTestSuite) TestIsConnected() {
require := s.Require()
s.StartTestNode(params.RopstenNetworkID)
defer s.StopTestNode()
s.jail.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
require.NoError(err)
_, err = cell.Run(`
var responseValue = web3.isConnected();
responseValue = JSON.stringify(responseValue);
`)
require.NoError(err)
responseValue, err := cell.Get("responseValue")
require.NoError(err, "cannot obtain result of isConnected()")
response, err := responseValue.ToString()
require.NoError(err, "cannot parse result")
expectedResponse := `{"jsonrpc":"2.0","result":true}`
require.Equal(expectedResponse, response)
}
func (s *JailTestSuite) TestEventSignal() {
require := s.Require()
s.jail.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
cell, err := s.jail.Cell(testChatID)
require.NoError(err)
testData := "foobar"
opCompletedSuccessfully := make(chan struct{}, 1)
// replace transaction notification handler
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope signal.Envelope
err := json.Unmarshal([]byte(jsonEvent), &envelope)
require.NoError(err)
if envelope.Type == jail.EventSignal {
event := envelope.Event.(map[string]interface{})
chatID, ok := event["chat_id"].(string)
require.True(ok, "chat id is required, but not found")
require.Equal(testChatID, chatID, "incorrect chat ID")
actualData, ok := event["data"].(string)
require.True(ok, "data field is required, but not found")
require.Equal(testData, actualData, "incorrect data")
s.T().Logf("event processed: %s", jsonEvent)
close(opCompletedSuccessfully)
}
})
_, err = cell.Run(`
var responseValue = statusSignals.sendSignal("` + testData + `");
responseValue = JSON.stringify(responseValue);
`)
s.NoError(err)
// make sure that signal is sent (and its parameters are correct)
select {
case <-opCompletedSuccessfully:
// pass
case <-time.After(3 * time.Second):
require.Fail("operation timed out")
}
responseValue, err := cell.Get("responseValue")
require.NoError(err, "cannot obtain result of localStorage.set()")
response, err := responseValue.ToString()
require.NoError(err, "cannot parse result")
expectedResponse := `{"jsonrpc":"2.0","result":true}`
require.Equal(expectedResponse, response)
}
// TestCallResponseOrder tests for problem in
// https://github.com/status-im/status-go/issues/372
func (s *JailTestSuite) TestCallResponseOrder() {
require := s.Require()
s.StartTestNode(params.RopstenNetworkID)
defer s.StopTestNode()
statusJS := baseStatusJSCode + `;
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};
_status_catalog.commands["calculateGasPrice"] = function (n) {
var gasMultiplicator = Math.pow(1.4, n).toFixed(3);
var price = 211000000000;
try {
price = web3.eth.gasPrice;
} catch (err) {}
return price * gasMultiplicator;
};
`
s.jail.Parse(testChatID, statusJS)
N := 1000
errCh := make(chan error, N)
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(2)
go func(i int) {
defer wg.Done()
res := s.jail.Call(testChatID, `["commands", "testCommand"]`, fmt.Sprintf(`{"val": %d}`, i))
if !strings.Contains(res, fmt.Sprintf("result\": %d", i*i)) {
errCh <- fmt.Errorf("result should be '%d', got %s", i*i, res)
}
}(i)
go func(i int) {
defer wg.Done()
res := s.jail.Call(testChatID, `["commands", "calculateGasPrice"]`, fmt.Sprintf(`%d`, i))
if strings.Contains(res, "error") {
errCh <- fmt.Errorf("result should not contain 'error', got %s", res)
}
}(i)
}
wg.Wait()
close(errCh)
for e := range errCh {
require.NoError(e)
}
}

View File

@ -13,7 +13,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
gethparams "github.com/ethereum/go-ethereum/params" gethparams "github.com/ethereum/go-ethereum/params"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing" . "github.com/status-im/status-go/testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -245,6 +245,7 @@ var loadConfigTestCases = []struct {
"enode://ce6854c2c77a8800fcc12600206c344b8053bb90ee3ba280e6c4f18f3141cdc5ee80bcc3bdb24cbc0e96dffd4b38d7b57546ed528c00af6cd604ab65c4d528f6@163.172.153.124:30303", "enode://ce6854c2c77a8800fcc12600206c344b8053bb90ee3ba280e6c4f18f3141cdc5ee80bcc3bdb24cbc0e96dffd4b38d7b57546ed528c00af6cd604ab65c4d528f6@163.172.153.124:30303",
"enode://00ae60771d9815daba35766d463a82a7b360b3a80e35ab2e0daa25bdc6ca6213ff4c8348025e7e1a908a8f58411a364fe02a0fb3c2aa32008304f063d8aaf1a2@163.172.132.85:30303", "enode://00ae60771d9815daba35766d463a82a7b360b3a80e35ab2e0daa25bdc6ca6213ff4c8348025e7e1a908a8f58411a364fe02a0fb3c2aa32008304f063d8aaf1a2@163.172.132.85:30303",
"enode://86ebc843aa51669e08e27400e435f957918e39dc540b021a2f3291ab776c88bbda3d97631639219b6e77e375ab7944222c47713bdeb3251b25779ce743a39d70@212.47.254.155:30303", "enode://86ebc843aa51669e08e27400e435f957918e39dc540b021a2f3291ab776c88bbda3d97631639219b6e77e375ab7944222c47713bdeb3251b25779ce743a39d70@212.47.254.155:30303",
"enode://a1ef9ba5550d5fac27f7cbd4e8d20a643ad75596f307c91cd6e7f85b548b8a6bf215cca436d6ee436d6135f9fe51398f8dd4c0bd6c6a0c332ccb41880f33ec12@51.15.218.125:30303",
} }
require.Equal(t, expectedEnodes, enodes) require.Equal(t, expectedEnodes, enodes)
}, },
@ -295,6 +296,7 @@ var loadConfigTestCases = []struct {
"enode://ce6854c2c77a8800fcc12600206c344b8053bb90ee3ba280e6c4f18f3141cdc5ee80bcc3bdb24cbc0e96dffd4b38d7b57546ed528c00af6cd604ab65c4d528f6@163.172.153.124:30303", "enode://ce6854c2c77a8800fcc12600206c344b8053bb90ee3ba280e6c4f18f3141cdc5ee80bcc3bdb24cbc0e96dffd4b38d7b57546ed528c00af6cd604ab65c4d528f6@163.172.153.124:30303",
"enode://00ae60771d9815daba35766d463a82a7b360b3a80e35ab2e0daa25bdc6ca6213ff4c8348025e7e1a908a8f58411a364fe02a0fb3c2aa32008304f063d8aaf1a2@163.172.132.85:30303", "enode://00ae60771d9815daba35766d463a82a7b360b3a80e35ab2e0daa25bdc6ca6213ff4c8348025e7e1a908a8f58411a364fe02a0fb3c2aa32008304f063d8aaf1a2@163.172.132.85:30303",
"enode://86ebc843aa51669e08e27400e435f957918e39dc540b021a2f3291ab776c88bbda3d97631639219b6e77e375ab7944222c47713bdeb3251b25779ce743a39d70@212.47.254.155:30303", "enode://86ebc843aa51669e08e27400e435f957918e39dc540b021a2f3291ab776c88bbda3d97631639219b6e77e375ab7944222c47713bdeb3251b25779ce743a39d70@212.47.254.155:30303",
"enode://a1ef9ba5550d5fac27f7cbd4e8d20a643ad75596f307c91cd6e7f85b548b8a6bf215cca436d6ee436d6135f9fe51398f8dd4c0bd6c6a0c332ccb41880f33ec12@51.15.218.125:30303",
} }
require.Equal(t, expectedEnodes, enodes) require.Equal(t, expectedEnodes, enodes)
}, },
@ -457,7 +459,7 @@ func TestConfigWriteRead(t *testing.T) {
refConfigData = strings.Replace(refConfigData, "$TMPDIR", nodeConfig.DataDir, -1) refConfigData = strings.Replace(refConfigData, "$TMPDIR", nodeConfig.DataDir, -1)
refConfigData = strings.Replace(refConfigData, "$VERSION", params.Version, -1) refConfigData = strings.Replace(refConfigData, "$VERSION", params.Version, -1)
require.EqualValues(t, refConfigData, loadedConfigData) require.Equal(t, strings.TrimSpace(string(refConfigData[:])), strings.TrimSpace(string(loadedConfigData[:])))
} }
configReadWrite(params.RinkebyNetworkID, "testdata/config.rinkeby.json") configReadWrite(params.RinkebyNetworkID, "testdata/config.rinkeby.json")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,45 +0,0 @@
package rpc
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestSetResultFromRPCResponse(t *testing.T) {
var err error
var resultRawMessage json.RawMessage
err = setResultFromRPCResponse(&resultRawMessage, []string{"one", "two", "three"})
require.NoError(t, err)
require.Equal(t, json.RawMessage(`["one","two","three"]`), resultRawMessage)
var resultSlice []int
err = setResultFromRPCResponse(&resultSlice, []int{1, 2, 3})
require.NoError(t, err)
require.Equal(t, []int{1, 2, 3}, resultSlice)
var resultMap map[string]interface{}
err = setResultFromRPCResponse(&resultMap, map[string]interface{}{"test": true})
require.NoError(t, err)
require.Equal(t, map[string]interface{}{"test": true}, resultMap)
var resultStruct struct {
A int
B string
}
err = setResultFromRPCResponse(&resultStruct, struct {
A int
B string
}{5, "test"})
require.NoError(t, err)
require.Equal(t, struct {
A int
B string
}{5, "test"}, resultStruct)
var resultIncorrectType []int
err = setResultFromRPCResponse(&resultIncorrectType, []string{"a", "b"})
require.Error(t, err)
}

View File

@ -1,183 +0,0 @@
// Package testing implements the base level testing types needed.
package testing
import (
"bytes"
"context"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
assertions "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"testing"
)
var (
// TestConfig defines the default config usable at package-level.
TestConfig *common.TestConfig
// RootDir is the main application directory
RootDir string
// TestDataDir is data directory used for tests
TestDataDir string
// TestNetworkNames network ID to name mapping
TestNetworkNames = map[int]string{
params.MainNetworkID: "Mainnet",
params.RopstenNetworkID: "Ropsten",
params.RinkebyNetworkID: "Rinkeby",
}
)
func init() {
pwd, err := os.Getwd()
if err != nil {
panic(err)
}
// setup root directory
RootDir = filepath.Dir(pwd)
if strings.HasSuffix(RootDir, "geth") || strings.HasSuffix(RootDir, "cmd") { // we need to hop one more level
RootDir = filepath.Join(RootDir, "..")
}
// setup auxiliary directories
TestDataDir = filepath.Join(RootDir, ".ethereumtest")
TestConfig, err = common.LoadTestConfig()
if err != nil {
panic(err)
}
}
// BaseTestSuite defines a base tests suit which others suites can embedded to
// access initialization methods useful for testing.
type BaseTestSuite struct {
suite.Suite
NodeManager common.NodeManager
}
// StartTestNode initiazes a NodeManager instances with configuration retrieved
// from the test config.
func (s *BaseTestSuite) StartTestNode(networkID int, opts ...TestNodeOption) {
require := s.Require()
require.NotNil(s.NodeManager)
nodeConfig, err := MakeTestNodeConfig(networkID)
require.NoError(err)
// Apply any options altering node config.
for i := range opts {
opts[i](nodeConfig)
}
keyStoreDir := filepath.Join(TestDataDir, TestNetworkNames[networkID], "keystore")
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
require.False(s.NodeManager.IsNodeRunning())
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
require.NoError(err)
require.NotNil(nodeStarted)
<-nodeStarted
require.True(s.NodeManager.IsNodeRunning())
}
// TestNodeOption is a callback passed to StartTestNode which alters its config.
type TestNodeOption func(config *params.NodeConfig)
// WithUpstream returns TestNodeOption which enabled UpstreamConfig.
func WithUpstream(url string) TestNodeOption {
return func(config *params.NodeConfig) {
config.UpstreamConfig.Enabled = true
config.UpstreamConfig.URL = url
}
}
// StopTestNode attempts to stop initialized NodeManager.
func (s *BaseTestSuite) StopTestNode() {
require := s.Require()
require.NotNil(s.NodeManager)
require.True(s.NodeManager.IsNodeRunning())
nodeStopped, err := s.NodeManager.StopNode()
require.NoError(err)
<-nodeStopped
require.False(s.NodeManager.IsNodeRunning())
}
// FirstBlockHash validates Attach operation for the NodeManager.
func FirstBlockHash(require *assertions.Assertions, nodeManager common.NodeManager, expectedHash string) {
require.NotNil(nodeManager)
var firstBlock struct {
Hash gethcommon.Hash `json:"hash"`
}
// obtain RPC client for running node
runningNode, err := nodeManager.Node()
require.NoError(err)
require.NotNil(runningNode)
rpcClient, err := runningNode.Attach()
require.NoError(err)
// get first block
err = rpcClient.CallContext(context.Background(), &firstBlock, "eth_getBlockByNumber", "0x0", true)
require.NoError(err)
require.Equal(expectedHash, firstBlock.Hash.Hex())
}
// MakeTestNodeConfig defines a function to return a giving params.NodeConfig
// where specific network addresses are assigned based on provieded network id.
func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
testDir := filepath.Join(TestDataDir, TestNetworkNames[networkID])
if runtime.GOOS == "windows" {
testDir = filepath.ToSlash(testDir)
}
// run tests with "INFO" log level only
// when `go test` invoked with `-v` flag
errorLevel := "ERROR"
if testing.Verbose() {
errorLevel = "INFO"
}
configJSON := `{
"NetworkId": ` + strconv.Itoa(networkID) + `,
"DataDir": "` + testDir + `",
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
"LogLevel": "` + errorLevel + `"
}`
nodeConfig, err := params.LoadNodeConfig(configJSON)
if err != nil {
return nil, err
}
return nodeConfig, nil
}
// LoadFromFile is useful for loading test data, from testdata/filename into a variable
// nolint: errcheck
func LoadFromFile(filename string) string {
f, err := os.Open(filename)
if err != nil {
return ""
}
buf := bytes.NewBuffer(nil)
io.Copy(buf, f)
f.Close()
return string(buf.Bytes())
}

View File

@ -198,7 +198,10 @@ func (m *Manager) completeLocalTransaction(queuedTx *common.QueuedTx, password s
return gethcommon.Hash{}, err return gethcommon.Hash{}, err
} }
return les.StatusBackend.SendTransaction(context.Background(), status.SendTxArgs(queuedTx.Args), password) ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
return les.StatusBackend.SendTransaction(ctx, status.SendTxArgs(queuedTx.Args), password)
} }
func (m *Manager) completeRemoteTransaction(queuedTx *common.QueuedTx, password string) (gethcommon.Hash, error) { func (m *Manager) completeRemoteTransaction(queuedTx *common.QueuedTx, password string) (gethcommon.Hash, error) {

View File

@ -13,7 +13,7 @@ import (
"github.com/status-im/status-go/geth/common" "github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params" "github.com/status-im/status-go/geth/params"
. "github.com/status-im/status-go/geth/testing" . "github.com/status-im/status-go/testing"
) )
var errTxAssumedSent = errors.New("assume tx is done") var errTxAssumedSent = errors.New("assume tx is done")

66
testing/testing.go Normal file
View File

@ -0,0 +1,66 @@
package integration
import (
"bytes"
"io"
"os"
"path/filepath"
"strings"
"github.com/status-im/status-go/geth/common"
"github.com/status-im/status-go/geth/params"
)
var (
// TestConfig defines the default config usable at package-level.
TestConfig *common.TestConfig
// RootDir is the main application directory
RootDir string
// TestDataDir is data directory used for tests
TestDataDir string
// TestNetworkNames network ID to name mapping
TestNetworkNames = map[int]string{
params.MainNetworkID: "Mainnet",
params.RopstenNetworkID: "Ropsten",
params.RinkebyNetworkID: "Rinkeby",
}
)
func init() {
pwd, err := os.Getwd()
if err != nil {
panic(err)
}
// setup root directory
RootDir = filepath.Dir(pwd)
if strings.HasSuffix(RootDir, "geth") || strings.HasSuffix(RootDir, "cmd") { // we need to hop one more level
RootDir = filepath.Join(RootDir, "..")
}
// setup auxiliary directories
TestDataDir = filepath.Join(RootDir, ".ethereumtest")
TestConfig, err = common.LoadTestConfig()
if err != nil {
panic(err)
}
}
// LoadFromFile is useful for loading test data, from testdata/filename into a variable
// nolint: errcheck
func LoadFromFile(filename string) string {
f, err := os.Open(filename)
if err != nil {
return ""
}
buf := bytes.NewBuffer(nil)
io.Copy(buf, f)
f.Close()
return string(buf.Bytes())
}