mirror of
https://github.com/status-im/status-go.git
synced 2025-01-09 22:26:30 +00:00
a9eb5a7d2b
We need to be able to sign more than just transactions to make DApps work properly. This change separates signing requests from the transactions and make it more general to prepare to intoduce different types of signing requests. This change is designed to preserve status APIs, so it is backward-comparible with the current API bindings.
1487 lines
46 KiB
Go
1487 lines
46 KiB
Go
// +build e2e_test
|
|
|
|
// This is a file with e2e tests for C bindings written in library.go.
|
|
// As a CGO file, it can't have `_test.go` suffix as it's not allowed by Go.
|
|
// At the same time, we don't want this file to be included in the binaries.
|
|
// This is why `e2e_test` tag was introduced. Without it, this file is excluded
|
|
// from the build. Providing this tag will include this file into the build
|
|
// and that's what is done while running e2e tests for `lib/` package.
|
|
|
|
package main
|
|
|
|
import "C"
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math/big"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/core"
|
|
gethparams "github.com/ethereum/go-ethereum/params"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/status-im/status-go/geth/account"
|
|
"github.com/status-im/status-go/geth/params"
|
|
"github.com/status-im/status-go/geth/signal"
|
|
"github.com/status-im/status-go/geth/transactions"
|
|
"github.com/status-im/status-go/sign"
|
|
"github.com/status-im/status-go/static"
|
|
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
|
)
|
|
|
|
const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
|
const initJS = `
|
|
var _status_catalog = {
|
|
foo: 'bar'
|
|
};`
|
|
|
|
var testChainDir string
|
|
var nodeConfigJSON string
|
|
|
|
func init() {
|
|
testChainDir = filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()])
|
|
|
|
nodeConfigJSON = `{
|
|
"NetworkId": ` + strconv.Itoa(GetNetworkID()) + `,
|
|
"DataDir": "` + testChainDir + `",
|
|
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
|
|
"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
|
|
"LogLevel": "INFO"
|
|
}`
|
|
}
|
|
|
|
// nolint: deadcode
|
|
func testExportedAPI(t *testing.T, done chan struct{}) {
|
|
<-startTestNode(t)
|
|
defer func() {
|
|
done <- struct{}{}
|
|
}()
|
|
|
|
// prepare accounts
|
|
testKeyDir := filepath.Join(testChainDir, "keystore")
|
|
if err := ImportTestAccount(testKeyDir, GetAccount1PKFile()); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := ImportTestAccount(testKeyDir, GetAccount2PKFile()); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// FIXME(tiabc): All of that is done because usage of cgo is not supported in tests.
|
|
// Probably, there should be a cleaner way, for example, test cgo bindings in e2e tests
|
|
// separately from other internal tests.
|
|
// FIXME(@jekamas): ATTENTION! this tests depends on each other!
|
|
tests := []struct {
|
|
name string
|
|
fn func(t *testing.T) bool
|
|
}{
|
|
{
|
|
"check default configuration",
|
|
testGetDefaultConfig,
|
|
},
|
|
{
|
|
"stop/resume node",
|
|
testStopResumeNode,
|
|
},
|
|
{
|
|
"call RPC on in-proc handler",
|
|
testCallRPC,
|
|
},
|
|
{
|
|
"create main and child accounts",
|
|
testCreateChildAccount,
|
|
},
|
|
{
|
|
"verify account password",
|
|
testVerifyAccountPassword,
|
|
},
|
|
{
|
|
"recover account",
|
|
testRecoverAccount,
|
|
},
|
|
{
|
|
"account select/login",
|
|
testAccountSelect,
|
|
},
|
|
{
|
|
"account logout",
|
|
testAccountLogout,
|
|
},
|
|
{
|
|
"complete single queued transaction",
|
|
testCompleteTransaction,
|
|
},
|
|
{
|
|
"test complete multiple queued transactions",
|
|
testCompleteMultipleQueuedTransactions,
|
|
},
|
|
{
|
|
"discard single queued transaction",
|
|
testDiscardTransaction,
|
|
},
|
|
{
|
|
"test discard multiple queued transactions",
|
|
testDiscardMultipleQueuedTransactions,
|
|
},
|
|
{
|
|
"test jail invalid initialization",
|
|
testJailInitInvalid,
|
|
},
|
|
{
|
|
"test jail invalid parse",
|
|
testJailParseInvalid,
|
|
},
|
|
{
|
|
"test jail initialization",
|
|
testJailInit,
|
|
},
|
|
{
|
|
"test jailed calls",
|
|
testJailFunctionCall,
|
|
},
|
|
{
|
|
"test ExecuteJS",
|
|
testExecuteJS,
|
|
},
|
|
{
|
|
"test deprecated Parse",
|
|
testJailParseDeprecated,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Logf("=== RUN %s", test.name)
|
|
if ok := test.fn(t); !ok {
|
|
t.Logf("=== FAILED %s", test.name)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func testVerifyAccountPassword(t *testing.T) bool {
|
|
tmpDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(tmpDir) // nolint: errcheck
|
|
|
|
if err = ImportTestAccount(tmpDir, GetAccount1PKFile()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = ImportTestAccount(tmpDir, GetAccount2PKFile()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// rename account file (to see that file's internals reviewed, when locating account key)
|
|
accountFilePathOriginal := filepath.Join(tmpDir, GetAccount1PKFile())
|
|
accountFilePath := filepath.Join(tmpDir, "foo"+TestConfig.Account1.Address+"bar.pk")
|
|
if err := os.Rename(accountFilePathOriginal, accountFilePath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
response := APIResponse{}
|
|
rawResponse := VerifyAccountPassword(
|
|
C.CString(tmpDir),
|
|
C.CString(TestConfig.Account1.Address),
|
|
C.CString(TestConfig.Account1.Password))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
|
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
if response.Error != "" {
|
|
t.Errorf("unexpected error: %s", response.Error)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testGetDefaultConfig(t *testing.T) bool {
|
|
networks := []struct {
|
|
chainID int
|
|
refChainConfig *gethparams.ChainConfig
|
|
}{
|
|
{params.MainNetworkID, gethparams.MainnetChainConfig},
|
|
{params.RopstenNetworkID, gethparams.TestnetChainConfig},
|
|
{params.RinkebyNetworkID, gethparams.RinkebyChainConfig},
|
|
// TODO(tiabc): The same for params.StatusChainNetworkID
|
|
}
|
|
for i := range networks {
|
|
network := networks[i]
|
|
|
|
t.Run(fmt.Sprintf("networkID=%d", network.chainID), func(t *testing.T) {
|
|
var (
|
|
nodeConfig = params.NodeConfig{}
|
|
rawResponse = GenerateConfig(C.CString("/tmp/data-folder"), C.int(network.chainID), 1)
|
|
)
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &nodeConfig); err != nil {
|
|
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
|
|
}
|
|
|
|
genesis := new(core.Genesis)
|
|
if err := json.Unmarshal([]byte(nodeConfig.LightEthConfig.Genesis), genesis); err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
require.Equal(t, network.refChainConfig, genesis.Config)
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
|
|
//@TODO(adam): quarantined this test until it uses a different directory.
|
|
//nolint: deadcode
|
|
func testResetChainData(t *testing.T) bool {
|
|
t.Skip()
|
|
|
|
resetChainDataResponse := APIResponse{}
|
|
rawResponse := ResetChainData()
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &resetChainDataResponse); err != nil {
|
|
t.Errorf("cannot decode ResetChainData response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
if resetChainDataResponse.Error != "" {
|
|
t.Errorf("unexpected error: %s", resetChainDataResponse.Error)
|
|
return false
|
|
}
|
|
|
|
EnsureNodeSync(statusAPI.StatusNode().EnsureSync)
|
|
testCompleteTransaction(t)
|
|
|
|
return true
|
|
}
|
|
|
|
func testStopResumeNode(t *testing.T) bool { //nolint: gocyclo
|
|
// to make sure that we start with empty account (which might have gotten populated during previous tests)
|
|
if err := statusAPI.Logout(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
whisperService, err := statusAPI.StatusNode().WhisperService()
|
|
if err != nil {
|
|
t.Errorf("whisper service not running: %v", err)
|
|
}
|
|
|
|
// create an account
|
|
address1, pubKey1, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return false
|
|
}
|
|
t.Logf("account created: {address: %s, key: %s}", address1, pubKey1)
|
|
|
|
// make sure that identity is not (yet injected)
|
|
if whisperService.HasKeyPair(pubKey1) {
|
|
t.Error("identity already present in whisper")
|
|
}
|
|
|
|
// select account
|
|
loginResponse := APIResponse{}
|
|
rawResponse := Login(C.CString(address1), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if loginResponse.Error != "" {
|
|
t.Errorf("could not select account: %v", err)
|
|
return false
|
|
}
|
|
if !whisperService.HasKeyPair(pubKey1) {
|
|
t.Errorf("identity not injected into whisper: %v", err)
|
|
}
|
|
|
|
// stop and resume node, then make sure that selected account is still selected
|
|
// nolint: dupl
|
|
stopNodeFn := func() bool {
|
|
response := APIResponse{}
|
|
// FIXME(tiabc): Implement https://github.com/status-im/status-go/issues/254 to avoid
|
|
// 9-sec timeout below after stopping the node.
|
|
rawResponse = StopNode()
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
|
t.Errorf("cannot decode StopNode response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
if response.Error != "" {
|
|
t.Errorf("unexpected error: %s", response.Error)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// nolint: dupl
|
|
resumeNodeFn := func() bool {
|
|
response := APIResponse{}
|
|
// FIXME(tiabc): Implement https://github.com/status-im/status-go/issues/254 to avoid
|
|
// 10-sec timeout below after resuming the node.
|
|
rawResponse = StartNode(C.CString(nodeConfigJSON))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
|
t.Errorf("cannot decode StartNode response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
if response.Error != "" {
|
|
t.Errorf("unexpected error: %s", response.Error)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
if !stopNodeFn() {
|
|
return false
|
|
}
|
|
|
|
time.Sleep(9 * time.Second) // allow to stop
|
|
|
|
if !resumeNodeFn() {
|
|
return false
|
|
}
|
|
|
|
time.Sleep(10 * time.Second) // allow to start (instead of using blocking version of start, of filter event)
|
|
|
|
// now, verify that we still have account logged in
|
|
whisperService, err = statusAPI.StatusNode().WhisperService()
|
|
if err != nil {
|
|
t.Errorf("whisper service not running: %v", err)
|
|
}
|
|
if !whisperService.HasKeyPair(pubKey1) {
|
|
t.Errorf("identity evicted from whisper on node restart: %v", err)
|
|
}
|
|
|
|
// additionally, let's complete transaction (just to make sure that node lives through pause/resume w/o issues)
|
|
testCompleteTransaction(t)
|
|
|
|
return true
|
|
}
|
|
|
|
func testCallRPC(t *testing.T) bool {
|
|
expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}`
|
|
rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`))
|
|
received := C.GoString(rawResponse)
|
|
if expected != received {
|
|
t.Errorf("unexpected response: expected: %v, got: %v", expected, received)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testCreateChildAccount(t *testing.T) bool { //nolint: gocyclo
|
|
// to make sure that we start with empty account (which might get populated during previous tests)
|
|
if err := statusAPI.Logout(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
keyStore, err := statusAPI.StatusNode().AccountKeyStore()
|
|
if err != nil {
|
|
t.Error(err)
|
|
return false
|
|
}
|
|
|
|
// create an account
|
|
createAccountResponse := AccountInfo{}
|
|
rawResponse := CreateAccount(C.CString(TestConfig.Account1.Password))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &createAccountResponse); err != nil {
|
|
t.Errorf("cannot decode CreateAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createAccountResponse.Error != "" {
|
|
t.Errorf("could not create account: %s", err)
|
|
return false
|
|
}
|
|
address, pubKey, mnemonic := createAccountResponse.Address, createAccountResponse.PubKey, createAccountResponse.Mnemonic
|
|
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
|
|
|
acct, err := account.ParseAccountString(address)
|
|
if err != nil {
|
|
t.Errorf("can not get account from address: %v", err)
|
|
return false
|
|
}
|
|
|
|
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
|
|
_, key, err := keyStore.AccountDecryptedKey(acct, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
return false
|
|
}
|
|
|
|
if key.ExtendedKey == nil {
|
|
t.Error("CKD#2 has not been generated for new account")
|
|
return false
|
|
}
|
|
|
|
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
|
|
createSubAccountResponse := AccountInfo{}
|
|
rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil {
|
|
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createSubAccountResponse.Error != account.ErrNoAccountSelected.Error() {
|
|
t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", createSubAccountResponse.Error)
|
|
return false
|
|
}
|
|
|
|
err = statusAPI.SelectAccount(address, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("Test failed: could not select account: %v", err)
|
|
return false
|
|
}
|
|
|
|
// try to create sub-account with wrong password
|
|
createSubAccountResponse = AccountInfo{}
|
|
rawResponse = CreateChildAccount(C.CString(""), C.CString("wrong password"))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil {
|
|
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createSubAccountResponse.Error != "cannot retrieve a valid key for a given account: could not decrypt key with given passphrase" {
|
|
t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", createSubAccountResponse.Error)
|
|
return false
|
|
}
|
|
|
|
// create sub-account (from implicit parent)
|
|
createSubAccountResponse1 := AccountInfo{}
|
|
rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse1); err != nil {
|
|
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createSubAccountResponse1.Error != "" {
|
|
t.Errorf("cannot create sub-account: %v", createSubAccountResponse1.Error)
|
|
return false
|
|
}
|
|
|
|
// make sure that sub-account index automatically progresses
|
|
createSubAccountResponse2 := AccountInfo{}
|
|
rawResponse = CreateChildAccount(C.CString(""), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse2); err != nil {
|
|
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createSubAccountResponse2.Error != "" {
|
|
t.Errorf("cannot create sub-account: %v", createSubAccountResponse2.Error)
|
|
}
|
|
|
|
if createSubAccountResponse1.Address == createSubAccountResponse2.Address || createSubAccountResponse1.PubKey == createSubAccountResponse2.PubKey {
|
|
t.Error("sub-account index auto-increament failed")
|
|
return false
|
|
}
|
|
|
|
// create sub-account (from explicit parent)
|
|
createSubAccountResponse3 := AccountInfo{}
|
|
rawResponse = CreateChildAccount(C.CString(createSubAccountResponse2.Address), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse3); err != nil {
|
|
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if createSubAccountResponse3.Error != "" {
|
|
t.Errorf("cannot create sub-account: %v", createSubAccountResponse3.Error)
|
|
}
|
|
|
|
subAccount1, subAccount2, subAccount3 := createSubAccountResponse1.Address, createSubAccountResponse2.Address, createSubAccountResponse3.Address
|
|
subPubKey1, subPubKey2, subPubKey3 := createSubAccountResponse1.PubKey, createSubAccountResponse2.PubKey, createSubAccountResponse3.PubKey
|
|
|
|
if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 {
|
|
t.Error("sub-account index auto-increament failed")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testRecoverAccount(t *testing.T) bool { //nolint: gocyclo
|
|
keyStore, _ := statusAPI.StatusNode().AccountKeyStore()
|
|
|
|
// create an account
|
|
address, pubKey, mnemonic, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return false
|
|
}
|
|
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
|
|
|
// try recovering using password + mnemonic
|
|
recoverAccountResponse := AccountInfo{}
|
|
rawResponse := RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if recoverAccountResponse.Error != "" {
|
|
t.Errorf("recover account failed: %v", recoverAccountResponse.Error)
|
|
return false
|
|
}
|
|
addressCheck, pubKeyCheck := recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
if address != addressCheck || pubKey != pubKeyCheck {
|
|
t.Error("recover account details failed to pull the correct details")
|
|
}
|
|
|
|
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
|
|
account, err := account.ParseAccountString(address)
|
|
if err != nil {
|
|
t.Errorf("can not get account from address: %v", err)
|
|
}
|
|
|
|
account, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
return false
|
|
}
|
|
extChild2String := key.ExtendedKey.String()
|
|
|
|
if err = keyStore.Delete(account, TestConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot remove account: %v", err)
|
|
}
|
|
|
|
recoverAccountResponse = AccountInfo{}
|
|
rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if recoverAccountResponse.Error != "" {
|
|
t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error)
|
|
return false
|
|
}
|
|
addressCheck, pubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
if address != addressCheck || pubKey != pubKeyCheck {
|
|
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
|
}
|
|
|
|
// make sure that extended key exists and is imported ok too
|
|
_, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
return false
|
|
}
|
|
if extChild2String != key.ExtendedKey.String() {
|
|
t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String())
|
|
}
|
|
|
|
// make sure that calling import several times, just returns from cache (no error is expected)
|
|
recoverAccountResponse = AccountInfo{}
|
|
rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if recoverAccountResponse.Error != "" {
|
|
t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error)
|
|
return false
|
|
}
|
|
addressCheck, pubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
if address != addressCheck || pubKey != pubKeyCheck {
|
|
t.Error("recover account details failed to pull the correct details (for non-cached account)")
|
|
}
|
|
|
|
// time to login with recovered data
|
|
whisperService, err := statusAPI.StatusNode().WhisperService()
|
|
if err != nil {
|
|
t.Errorf("whisper service not running: %v", err)
|
|
}
|
|
|
|
// make sure that identity is not (yet injected)
|
|
if whisperService.HasKeyPair(pubKeyCheck) {
|
|
t.Error("identity already present in whisper")
|
|
}
|
|
err = statusAPI.SelectAccount(addressCheck, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("Test failed: could not select account: %v", err)
|
|
return false
|
|
}
|
|
if !whisperService.HasKeyPair(pubKeyCheck) {
|
|
t.Errorf("identity not injected into whisper: %v", err)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testAccountSelect(t *testing.T) bool { //nolint: gocyclo
|
|
// test to see if the account was injected in whisper
|
|
whisperService, err := statusAPI.StatusNode().WhisperService()
|
|
if err != nil {
|
|
t.Errorf("whisper service not running: %v", err)
|
|
}
|
|
|
|
// create an account
|
|
address1, pubKey1, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return false
|
|
}
|
|
t.Logf("Account created: {address: %s, key: %s}", address1, pubKey1)
|
|
|
|
address2, pubKey2, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Error("Test failed: could not create account")
|
|
return false
|
|
}
|
|
t.Logf("Account created: {address: %s, key: %s}", address2, pubKey2)
|
|
|
|
// make sure that identity is not (yet injected)
|
|
if whisperService.HasKeyPair(pubKey1) {
|
|
t.Error("identity already present in whisper")
|
|
}
|
|
|
|
// try selecting with wrong password
|
|
loginResponse := APIResponse{}
|
|
rawResponse := Login(C.CString(address1), C.CString("wrongPassword"))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if loginResponse.Error == "" {
|
|
t.Error("select account is expected to throw error: wrong password used")
|
|
return false
|
|
}
|
|
|
|
loginResponse = APIResponse{}
|
|
rawResponse = Login(C.CString(address1), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if loginResponse.Error != "" {
|
|
t.Errorf("Test failed: could not select account: %v", err)
|
|
return false
|
|
}
|
|
if !whisperService.HasKeyPair(pubKey1) {
|
|
t.Errorf("identity not injected into whisper: %v", err)
|
|
}
|
|
|
|
// select another account, make sure that previous account is wiped out from Whisper cache
|
|
if whisperService.HasKeyPair(pubKey2) {
|
|
t.Error("identity already present in whisper")
|
|
}
|
|
|
|
loginResponse = APIResponse{}
|
|
rawResponse = Login(C.CString(address2), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if loginResponse.Error != "" {
|
|
t.Errorf("Test failed: could not select account: %v", loginResponse.Error)
|
|
return false
|
|
}
|
|
if !whisperService.HasKeyPair(pubKey2) {
|
|
t.Errorf("identity not injected into whisper: %v", err)
|
|
}
|
|
if whisperService.HasKeyPair(pubKey1) {
|
|
t.Error("identity should be removed, but it is still present in whisper")
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testAccountLogout(t *testing.T) bool {
|
|
whisperService, err := statusAPI.StatusNode().WhisperService()
|
|
if err != nil {
|
|
t.Errorf("whisper service not running: %v", err)
|
|
return false
|
|
}
|
|
|
|
// create an account
|
|
address, pubKey, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("could not create account: %v", err)
|
|
return false
|
|
}
|
|
|
|
// make sure that identity doesn't exist (yet) in Whisper
|
|
if whisperService.HasKeyPair(pubKey) {
|
|
t.Error("identity already present in whisper")
|
|
return false
|
|
}
|
|
|
|
// select/login
|
|
err = statusAPI.SelectAccount(address, TestConfig.Account1.Password)
|
|
if err != nil {
|
|
t.Errorf("Test failed: could not select account: %v", err)
|
|
return false
|
|
}
|
|
if !whisperService.HasKeyPair(pubKey) {
|
|
t.Error("identity not injected into whisper")
|
|
return false
|
|
}
|
|
|
|
logoutResponse := APIResponse{}
|
|
rawResponse := Logout()
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &logoutResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
return false
|
|
}
|
|
|
|
if logoutResponse.Error != "" {
|
|
t.Errorf("cannot logout: %v", logoutResponse.Error)
|
|
return false
|
|
}
|
|
|
|
// now, logout and check if identity is removed indeed
|
|
if whisperService.HasKeyPair(pubKey) {
|
|
t.Error("identity not cleared from whisper")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testCompleteTransaction(t *testing.T) bool {
|
|
signRequests := statusAPI.PendingSignRequests()
|
|
|
|
EnsureNodeSync(statusAPI.StatusNode().EnsureSync)
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v. Error %q", TestConfig.Account1.Address, err)
|
|
return false
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
queuedTxCompleted := make(chan struct{}, 1)
|
|
abortPanic := make(chan struct{}, 1)
|
|
PanicAfter(10*time.Second, abortPanic, "testCompleteTransaction")
|
|
|
|
// replace transaction notification handler
|
|
var txHash = ""
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope signal.Envelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s. Error %q", jsonEvent, err)
|
|
return
|
|
}
|
|
if envelope.Type == sign.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
|
|
|
completeTxResponse := CompleteTransactionResult{}
|
|
rawResponse := CompleteTransaction(C.CString(event["id"].(string)), C.CString(TestConfig.Account1.Password))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &completeTxResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
}
|
|
|
|
if completeTxResponse.Error != "" {
|
|
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], completeTxResponse.Error)
|
|
}
|
|
|
|
txHash = completeTxResponse.Hash
|
|
|
|
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash)
|
|
abortPanic <- struct{}{} // so that timeout is aborted
|
|
queuedTxCompleted <- struct{}{}
|
|
}
|
|
})
|
|
|
|
// this call blocks, up until Complete Transaction is called
|
|
txCheckHash, err := statusAPI.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
|
From: account.FromAddress(TestConfig.Account1.Address),
|
|
To: account.ToAddress(TestConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != nil {
|
|
t.Errorf("Failed to SendTransaction: %s", err)
|
|
return false
|
|
}
|
|
|
|
<-queuedTxCompleted // make sure that complete transaction handler completes its magic, before we proceed
|
|
|
|
if txHash != txCheckHash.Hex() {
|
|
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s",
|
|
txCheckHash.Hex(), txHash)
|
|
return false
|
|
}
|
|
|
|
if reflect.DeepEqual(txCheckHash, gethcommon.Hash{}) {
|
|
t.Error("Test failed: transaction was never queued or completed")
|
|
return false
|
|
}
|
|
|
|
if signRequests.Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testCompleteMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyclo
|
|
signRequests := statusAPI.PendingSignRequests()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
|
return false
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
testTxCount := 3
|
|
txIDs := make(chan string, testTxCount)
|
|
allTestTxCompleted := make(chan struct{}, 1)
|
|
|
|
// replace transaction notification handler
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var txID string
|
|
var envelope signal.Envelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == sign.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txID)
|
|
|
|
txIDs <- txID
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
|
sendTx := func() {
|
|
txHashCheck, err := statusAPI.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
|
From: account.FromAddress(TestConfig.Account1.Address),
|
|
To: account.ToAddress(TestConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != nil {
|
|
t.Errorf("unexpected error thrown: %v", err)
|
|
return
|
|
}
|
|
|
|
if reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
|
t.Error("transaction returned empty hash")
|
|
return
|
|
}
|
|
}
|
|
|
|
// wait for transactions, and complete them in a single call
|
|
completeTxs := func(txIDStrings string) {
|
|
var parsedIDs []string
|
|
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
|
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
|
|
|
// complete
|
|
resultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
|
resultsStruct := CompleteTransactionsResult{}
|
|
if err := json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
results := resultsStruct.Results
|
|
|
|
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != sign.ErrSignReqNotFound.Error() {
|
|
t.Errorf("cannot complete txs: %v", results)
|
|
return
|
|
}
|
|
for txID, txResult := range results {
|
|
if txID != txResult.ID {
|
|
t.Errorf("tx id not set in result: expected id is %s", txID)
|
|
return
|
|
}
|
|
if txResult.Error != "" && txID != "invalid-tx-id" {
|
|
t.Errorf("invalid error for %s", txID)
|
|
return
|
|
}
|
|
if txResult.Hash == zeroHash && txID != "invalid-tx-id" {
|
|
t.Errorf("invalid hash (expected non empty hash): %s", txID)
|
|
return
|
|
}
|
|
|
|
if txResult.Hash != zeroHash {
|
|
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash)
|
|
}
|
|
}
|
|
|
|
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
|
for _, txID := range parsedIDs {
|
|
if signRequests.Has(string(txID)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
go func() {
|
|
var txIDStrings []string
|
|
for i := 0; i < testTxCount; i++ {
|
|
txIDStrings = append(txIDStrings, <-txIDs)
|
|
}
|
|
|
|
txIDJSON, _ := json.Marshal(txIDStrings)
|
|
completeTxs(string(txIDJSON))
|
|
allTestTxCompleted <- struct{}{}
|
|
}()
|
|
|
|
// send multiple transactions
|
|
for i := 0; i < testTxCount; i++ {
|
|
go sendTx()
|
|
}
|
|
|
|
select {
|
|
case <-allTestTxCompleted:
|
|
// pass
|
|
case <-time.After(20 * time.Second):
|
|
t.Error("test timed out")
|
|
return false
|
|
}
|
|
|
|
if signRequests.Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testDiscardTransaction(t *testing.T) bool { //nolint: gocyclo
|
|
signRequests := statusAPI.PendingSignRequests()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
|
return false
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
completeQueuedTransaction := make(chan struct{}, 1)
|
|
PanicAfter(20*time.Second, completeQueuedTransaction, "testDiscardTransaction")
|
|
|
|
// replace transaction notification handler
|
|
var txID string
|
|
txFailedEventCalled := false
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var envelope signal.Envelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == sign.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
|
|
|
if !signRequests.Has(string(txID)) {
|
|
t.Errorf("txqueue should still have test tx: %s", txID)
|
|
return
|
|
}
|
|
|
|
// discard
|
|
discardResponse := DiscardTransactionResult{}
|
|
rawResponse := DiscardTransaction(C.CString(txID))
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
|
|
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
}
|
|
|
|
if discardResponse.Error != "" {
|
|
t.Errorf("cannot discard tx: %v", discardResponse.Error)
|
|
return
|
|
}
|
|
|
|
// try completing discarded transaction
|
|
_, err := statusAPI.CompleteTransaction(string(txID), TestConfig.Account1.Password)
|
|
if err != sign.ErrSignReqNotFound {
|
|
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
|
|
return
|
|
}
|
|
|
|
if signRequests.Has(string(txID)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
|
return
|
|
}
|
|
|
|
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
|
}
|
|
|
|
if envelope.Type == sign.EventTransactionFailed {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
|
|
|
receivedErrMessage := event["error_message"].(string)
|
|
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
|
if receivedErrMessage != expectedErrMessage {
|
|
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
|
return
|
|
}
|
|
|
|
receivedErrCode := event["error_code"].(string)
|
|
if receivedErrCode != strconv.Itoa(sign.SendTransactionDiscardedErrorCode) {
|
|
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
|
return
|
|
}
|
|
|
|
txFailedEventCalled = true
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
|
txHashCheck, err := statusAPI.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
|
From: account.FromAddress(TestConfig.Account1.Address),
|
|
To: account.ToAddress(TestConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
time.Sleep(1 * time.Second)
|
|
if err != sign.ErrSignReqDiscarded {
|
|
t.Errorf("expected error not thrown: %v", err)
|
|
return false
|
|
}
|
|
|
|
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
|
t.Error("transaction returned hash, while it shouldn't")
|
|
return false
|
|
}
|
|
|
|
if signRequests.Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return false
|
|
}
|
|
|
|
if !txFailedEventCalled {
|
|
t.Error("expected tx failure signal is not received")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testDiscardMultipleQueuedTransactions(t *testing.T) bool { //nolint: gocyclo
|
|
signRequests := statusAPI.PendingSignRequests()
|
|
|
|
// log into account from which transactions will be sent
|
|
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
|
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
|
return false
|
|
}
|
|
|
|
// make sure you panic if transaction complete doesn't return
|
|
testTxCount := 3
|
|
txIDs := make(chan string, testTxCount)
|
|
allTestTxDiscarded := make(chan struct{}, 1)
|
|
|
|
// replace transaction notification handler
|
|
txFailedEventCallCount := 0
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
var txID string
|
|
var envelope signal.Envelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == sign.EventTransactionQueued {
|
|
event := envelope.Event.(map[string]interface{})
|
|
txID = event["id"].(string)
|
|
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
|
|
|
if !signRequests.Has(string(txID)) {
|
|
t.Errorf("txqueue should still have test tx: %s", txID)
|
|
return
|
|
}
|
|
|
|
txIDs <- txID
|
|
}
|
|
|
|
if envelope.Type == sign.EventTransactionFailed {
|
|
event := envelope.Event.(map[string]interface{})
|
|
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
|
|
|
receivedErrMessage := event["error_message"].(string)
|
|
expectedErrMessage := sign.ErrSignReqDiscarded.Error()
|
|
if receivedErrMessage != expectedErrMessage {
|
|
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
|
return
|
|
}
|
|
|
|
receivedErrCode := event["error_code"].(string)
|
|
if receivedErrCode != strconv.Itoa(sign.SendTransactionDiscardedErrorCode) {
|
|
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
|
return
|
|
}
|
|
|
|
txFailedEventCallCount++
|
|
if txFailedEventCallCount == testTxCount {
|
|
allTestTxDiscarded <- struct{}{}
|
|
}
|
|
}
|
|
})
|
|
|
|
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
|
sendTx := func() {
|
|
txHashCheck, err := statusAPI.SendTransaction(context.TODO(), transactions.SendTxArgs{
|
|
From: account.FromAddress(TestConfig.Account1.Address),
|
|
To: account.ToAddress(TestConfig.Account2.Address),
|
|
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
})
|
|
if err != sign.ErrSignReqDiscarded {
|
|
t.Errorf("expected error not thrown: %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
|
t.Error("transaction returned hash, while it shouldn't")
|
|
return
|
|
}
|
|
}
|
|
|
|
// wait for transactions, and discard immediately
|
|
discardTxs := func(txIDStrings string) {
|
|
var parsedIDs []string
|
|
if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
|
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
|
|
|
// discard
|
|
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIDStrings)))
|
|
discardResultsStruct := DiscardTransactionsResult{}
|
|
if err := json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
discardResults := discardResultsStruct.Results
|
|
|
|
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != sign.ErrSignReqNotFound.Error() {
|
|
t.Errorf("cannot discard txs: %v", discardResults)
|
|
return
|
|
}
|
|
|
|
// try completing discarded transaction
|
|
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
|
completeResultsStruct := CompleteTransactionsResult{}
|
|
if err := json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct); err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
completeResults := completeResultsStruct.Results
|
|
|
|
if len(completeResults) != (testTxCount + 1) {
|
|
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
|
|
}
|
|
for txID, txResult := range completeResults {
|
|
if txID != txResult.ID {
|
|
t.Errorf("tx id not set in result: expected id is %s", txID)
|
|
return
|
|
}
|
|
if txResult.Error != sign.ErrSignReqNotFound.Error() {
|
|
t.Errorf("invalid error for %s", txResult.Hash)
|
|
return
|
|
}
|
|
if txResult.Hash != zeroHash {
|
|
t.Errorf("invalid hash (expected zero): %s", txResult.Hash)
|
|
return
|
|
}
|
|
}
|
|
|
|
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
|
for _, txID := range parsedIDs {
|
|
if signRequests.Has(string(txID)) {
|
|
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
go func() {
|
|
var txIDStrings []string
|
|
for i := 0; i < testTxCount; i++ {
|
|
txIDStrings = append(txIDStrings, <-txIDs)
|
|
}
|
|
|
|
txIDJSON, _ := json.Marshal(txIDStrings)
|
|
discardTxs(string(txIDJSON))
|
|
}()
|
|
|
|
// send multiple transactions
|
|
for i := 0; i < testTxCount; i++ {
|
|
go sendTx()
|
|
}
|
|
|
|
select {
|
|
case <-allTestTxDiscarded:
|
|
// pass
|
|
case <-time.After(20 * time.Second):
|
|
t.Error("test timed out")
|
|
return false
|
|
}
|
|
|
|
if signRequests.Count() != 0 {
|
|
t.Error("tx queue must be empty at this point")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func testJailInitInvalid(t *testing.T) bool {
|
|
// Arrange.
|
|
initInvalidCode := `
|
|
var _status_catalog = {
|
|
foo: 'bar'
|
|
`
|
|
|
|
// Act.
|
|
InitJail(C.CString(initInvalidCode))
|
|
response := C.GoString(CreateAndInitCell(C.CString("CHAT_ID_INIT_INVALID_TEST"), C.CString(``)))
|
|
|
|
// Assert.
|
|
expectedSubstr := `"error":"(anonymous): Line 4:3 Unexpected identifier`
|
|
if !strings.Contains(response, expectedSubstr) {
|
|
t.Errorf("unexpected response, didn't find '%s' in '%s'", expectedSubstr, response)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func testJailParseInvalid(t *testing.T) bool {
|
|
// Arrange.
|
|
InitJail(C.CString(initJS))
|
|
// Act.
|
|
extraInvalidCode := `
|
|
var extraFunc = function (x) {
|
|
return x * x;
|
|
`
|
|
response := C.GoString(CreateAndInitCell(C.CString("CHAT_ID_PARSE_INVALID_TEST"), C.CString(extraInvalidCode)))
|
|
|
|
// Assert.
|
|
expectedResponse := `{"error":"(anonymous): Line 4:2 Unexpected end of input (and 1 more errors)"}`
|
|
if expectedResponse != response {
|
|
t.Errorf("unexpected response, expected: %v, got: %v", expectedResponse, response)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func testJailInit(t *testing.T) bool {
|
|
InitJail(C.CString(initJS))
|
|
|
|
chatID := C.CString("CHAT_ID_INIT_TEST")
|
|
|
|
// Cell initialization return the result of the last JS operation provided to it.
|
|
response := CreateAndInitCell(chatID, C.CString(`var extraFunc = function (x) { return x * x; }; extraFunc(2);`))
|
|
require.Equal(t, `{"result":4}`, C.GoString(response), "Unexpected response from jail.CreateAndInitCell()")
|
|
|
|
// Commands from the jail initialization are available in any of the created cells.
|
|
response = ExecuteJS(chatID, C.CString(`JSON.stringify({ result: _status_catalog });`))
|
|
require.Equal(t, `{"result":{"foo":"bar"}}`, C.GoString(response), "Environment from `InitJail` is not available in the created cell")
|
|
|
|
return true
|
|
}
|
|
|
|
func testJailParseDeprecated(t *testing.T) bool {
|
|
InitJail(C.CString(initJS))
|
|
|
|
extraCode := `
|
|
var extraFunc = function (x) {
|
|
return x * x;
|
|
};
|
|
|
|
extraFunc(2);
|
|
`
|
|
chatID := C.CString("CHAT_ID_PARSE_TEST")
|
|
|
|
response := Parse(chatID, C.CString(extraCode))
|
|
require.Equal(t, `{"result":4}`, C.GoString(response))
|
|
|
|
// cell already exists but Parse should not complain
|
|
response = Parse(chatID, C.CString(extraCode))
|
|
require.Equal(t, `{"result":4}`, C.GoString(response))
|
|
|
|
// test extraCode
|
|
response = ExecuteJS(chatID, C.CString(`extraFunc(10)`))
|
|
require.Equal(t, `100`, C.GoString(response))
|
|
|
|
return true
|
|
}
|
|
|
|
func testJailFunctionCall(t *testing.T) bool {
|
|
InitJail(C.CString(""))
|
|
|
|
// load Status JS and add test command to it
|
|
statusJS := string(static.MustAsset("testdata/jail/status.js")) + `;
|
|
_status_catalog.commands["testCommand"] = function (params) {
|
|
return params.val * params.val;
|
|
};`
|
|
CreateAndInitCell(C.CString("CHAT_ID_CALL_TEST"), C.CString(statusJS))
|
|
|
|
// call with wrong chat id
|
|
rawResponse := Call(C.CString("CHAT_IDNON_EXISTENT"), C.CString(""), C.CString(""))
|
|
parsedResponse := C.GoString(rawResponse)
|
|
expectedError := `{"error":"cell 'CHAT_IDNON_EXISTENT' not found"}`
|
|
if parsedResponse != expectedError {
|
|
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, parsedResponse)
|
|
return false
|
|
}
|
|
|
|
// call extraFunc()
|
|
rawResponse = Call(C.CString("CHAT_ID_CALL_TEST"), C.CString(`["commands", "testCommand"]`), C.CString(`{"val": 12}`))
|
|
parsedResponse = C.GoString(rawResponse)
|
|
expectedResponse := `{"result":144}`
|
|
if parsedResponse != expectedResponse {
|
|
t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, parsedResponse)
|
|
return false
|
|
}
|
|
|
|
t.Logf("jailed method called: %s", parsedResponse)
|
|
|
|
return true
|
|
}
|
|
|
|
func testExecuteJS(t *testing.T) bool {
|
|
InitJail(C.CString(""))
|
|
|
|
// cell does not exist
|
|
response := C.GoString(ExecuteJS(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString("('some string')")))
|
|
expectedResponse := `{"error":"cell 'CHAT_ID_EXECUTE_TEST' not found"}`
|
|
if response != expectedResponse {
|
|
t.Errorf("expected '%s' but got '%s'", expectedResponse, response)
|
|
return false
|
|
}
|
|
|
|
CreateAndInitCell(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString(`var obj = { status: true }`))
|
|
|
|
// cell does not exist
|
|
response = C.GoString(ExecuteJS(C.CString("CHAT_ID_EXECUTE_TEST"), C.CString(`JSON.stringify(obj)`)))
|
|
expectedResponse = `{"status":true}`
|
|
if response != expectedResponse {
|
|
t.Errorf("expected '%s' but got '%s'", expectedResponse, response)
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func startTestNode(t *testing.T) <-chan struct{} {
|
|
testDir := filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()])
|
|
|
|
syncRequired := false
|
|
if _, err := os.Stat(testDir); os.IsNotExist(err) {
|
|
syncRequired = true
|
|
}
|
|
|
|
// inject test accounts
|
|
testKeyDir := filepath.Join(testDir, "keystore")
|
|
if err := ImportTestAccount(testKeyDir, GetAccount1PKFile()); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := ImportTestAccount(testKeyDir, GetAccount2PKFile()); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
waitForNodeStart := make(chan struct{}, 1)
|
|
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
t.Log(jsonEvent)
|
|
var envelope signal.Envelope
|
|
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
|
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
|
return
|
|
}
|
|
if envelope.Type == signal.EventNodeCrashed {
|
|
signal.TriggerDefaultNodeNotificationHandler(jsonEvent)
|
|
return
|
|
}
|
|
|
|
if envelope.Type == sign.EventTransactionQueued {
|
|
}
|
|
if envelope.Type == signal.EventNodeStarted {
|
|
t.Log("Node started, but we wait till it be ready")
|
|
}
|
|
if envelope.Type == signal.EventNodeReady {
|
|
// sync
|
|
if syncRequired {
|
|
t.Logf("Sync is required")
|
|
EnsureNodeSync(statusAPI.StatusNode().EnsureSync)
|
|
} else {
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
|
|
// now we can proceed with tests
|
|
waitForNodeStart <- struct{}{}
|
|
}
|
|
})
|
|
|
|
go func() {
|
|
response := StartNode(C.CString(nodeConfigJSON))
|
|
responseErr := APIResponse{}
|
|
|
|
if err := json.Unmarshal([]byte(C.GoString(response)), &responseErr); err != nil {
|
|
panic(err)
|
|
}
|
|
if responseErr.Error != "" {
|
|
panic("cannot start node: " + responseErr.Error)
|
|
}
|
|
}()
|
|
|
|
return waitForNodeStart
|
|
}
|
|
|
|
//nolint: deadcode
|
|
func testValidateNodeConfig(t *testing.T, config string, fn func(APIDetailedResponse)) {
|
|
result := ValidateNodeConfig(C.CString(config))
|
|
|
|
var resp APIDetailedResponse
|
|
|
|
err := json.Unmarshal([]byte(C.GoString(result)), &resp)
|
|
require.NoError(t, err)
|
|
|
|
fn(resp)
|
|
}
|
|
|
|
// PanicAfter throws panic() after waitSeconds, unless abort channel receives
|
|
// notification.
|
|
func PanicAfter(waitSeconds time.Duration, abort chan struct{}, desc string) {
|
|
go func() {
|
|
select {
|
|
case <-abort:
|
|
return
|
|
case <-time.After(waitSeconds):
|
|
panic("whatever you were doing takes toooo long: " + desc)
|
|
}
|
|
}()
|
|
}
|