2017-05-16 12:09:52 +00:00
|
|
|
package common
|
|
|
|
|
|
|
|
import (
|
2017-08-10 15:31:29 +00:00
|
|
|
"bytes"
|
2017-09-04 12:56:58 +00:00
|
|
|
"context"
|
2017-05-16 12:09:52 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
2017-08-10 15:31:29 +00:00
|
|
|
"fmt"
|
2017-11-07 17:46:11 +00:00
|
|
|
"os"
|
2017-08-10 15:31:29 +00:00
|
|
|
"strings"
|
2017-05-16 12:09:52 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2017-09-04 12:56:58 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2017-05-16 12:09:52 +00:00
|
|
|
"github.com/ethereum/go-ethereum/les"
|
|
|
|
"github.com/ethereum/go-ethereum/node"
|
|
|
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
|
|
|
"github.com/robertkrimen/otto"
|
|
|
|
"github.com/status-im/status-go/geth/params"
|
2017-09-14 20:14:31 +00:00
|
|
|
"github.com/status-im/status-go/geth/rpc"
|
2017-05-16 12:09:52 +00:00
|
|
|
"github.com/status-im/status-go/static"
|
|
|
|
)
|
|
|
|
|
2017-05-28 13:57:30 +00:00
|
|
|
// errors
|
2017-05-16 12:09:52 +00:00
|
|
|
var (
|
|
|
|
ErrDeprecatedMethod = errors.New("Method is depricated and will be removed in future release")
|
|
|
|
)
|
|
|
|
|
|
|
|
// SelectedExtKey is a container for currently selected (logged in) account
|
|
|
|
type SelectedExtKey struct {
|
|
|
|
Address common.Address
|
|
|
|
AccountKey *keystore.Key
|
|
|
|
SubAccounts []accounts.Account
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hex dumps address of a given extended key as hex string
|
|
|
|
func (k *SelectedExtKey) Hex() string {
|
|
|
|
if k == nil {
|
|
|
|
return "0x0"
|
|
|
|
}
|
|
|
|
|
|
|
|
return k.Address.Hex()
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodeManager defines expected methods for managing Status node
|
|
|
|
type NodeManager interface {
|
|
|
|
// StartNode start Status node, fails if node is already started
|
2018-02-09 13:37:56 +00:00
|
|
|
StartNode(config *params.NodeConfig) error
|
2017-05-16 12:09:52 +00:00
|
|
|
|
2018-02-07 10:48:03 +00:00
|
|
|
// EnsureSync waits until blockchain is synchronized.
|
|
|
|
EnsureSync(ctx context.Context) error
|
|
|
|
|
2018-02-09 13:37:56 +00:00
|
|
|
// StopNode stop the running Status node.
|
|
|
|
// Stopped node cannot be resumed, one starts a new node instead.
|
|
|
|
StopNode() error
|
2017-05-16 12:09:52 +00:00
|
|
|
|
|
|
|
// IsNodeRunning confirm that node is running
|
|
|
|
IsNodeRunning() bool
|
|
|
|
|
|
|
|
// NodeConfig returns reference to running node's configuration
|
|
|
|
NodeConfig() (*params.NodeConfig, error)
|
|
|
|
|
|
|
|
// Node returns underlying Status node
|
|
|
|
Node() (*node.Node, error)
|
|
|
|
|
|
|
|
// PopulateStaticPeers populates node's list of static bootstrap peers
|
|
|
|
PopulateStaticPeers() error
|
|
|
|
|
|
|
|
// AddPeer adds URL of static peer
|
|
|
|
AddPeer(url string) error
|
|
|
|
|
2018-01-25 07:26:34 +00:00
|
|
|
// PeerCount returns number of connected peers
|
|
|
|
PeerCount() int
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// LightEthereumService exposes reference to LES service running on top of the node
|
|
|
|
LightEthereumService() (*les.LightEthereum, error)
|
|
|
|
|
|
|
|
// WhisperService returns reference to running Whisper service
|
|
|
|
WhisperService() (*whisper.Whisper, error)
|
|
|
|
|
|
|
|
// AccountManager returns reference to node's account manager
|
|
|
|
AccountManager() (*accounts.Manager, error)
|
|
|
|
|
|
|
|
// AccountKeyStore returns reference to account manager's keystore
|
|
|
|
AccountKeyStore() (*keystore.KeyStore, error)
|
|
|
|
|
|
|
|
// RPCClient exposes reference to RPC client connected to the running node
|
2017-09-14 20:14:31 +00:00
|
|
|
RPCClient() *rpc.Client
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AccountManager defines expected methods for managing Status accounts
|
|
|
|
type AccountManager interface {
|
|
|
|
// CreateAccount creates an internal geth account
|
|
|
|
// BIP44-compatible keys are generated: CKD#1 is stored as account key, CKD#2 stored as sub-account root
|
|
|
|
// Public key of CKD#1 is returned, with CKD#2 securely encoded into account key file (to be used for
|
|
|
|
// sub-account derivations)
|
|
|
|
CreateAccount(password string) (address, pubKey, mnemonic string, err error)
|
|
|
|
|
|
|
|
// CreateChildAccount creates sub-account for an account identified by parent address.
|
|
|
|
// CKD#2 is used as root for master accounts (when parentAddress is "").
|
|
|
|
// Otherwise (when parentAddress != ""), child is derived directly from parent.
|
|
|
|
CreateChildAccount(parentAddress, password string) (address, pubKey string, err error)
|
|
|
|
|
|
|
|
// RecoverAccount re-creates master key using given details.
|
|
|
|
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
|
|
|
RecoverAccount(password, mnemonic string) (address, pubKey string, err error)
|
|
|
|
|
|
|
|
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
|
|
|
// If no error is returned, then account is considered verified.
|
|
|
|
VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error)
|
|
|
|
|
|
|
|
// SelectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
|
|
|
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
|
|
|
// all previous identities are removed).
|
|
|
|
SelectAccount(address, password string) error
|
|
|
|
|
|
|
|
// ReSelectAccount selects previously selected account, often, after node restart.
|
|
|
|
ReSelectAccount() error
|
|
|
|
|
|
|
|
// SelectedAccount returns currently selected account
|
|
|
|
SelectedAccount() (*SelectedExtKey, error)
|
|
|
|
|
|
|
|
// Logout clears whisper identities
|
|
|
|
Logout() error
|
|
|
|
|
2017-09-25 16:04:40 +00:00
|
|
|
// Accounts returns handler to process account list request
|
|
|
|
Accounts() ([]common.Address, error)
|
|
|
|
|
|
|
|
// AccountsRPCHandler returns RPC wrapper for Accounts()
|
|
|
|
AccountsRPCHandler() rpc.Handler
|
2017-05-16 12:09:52 +00:00
|
|
|
|
|
|
|
// AddressToDecryptedAccount tries to load decrypted key for a given account.
|
|
|
|
// The running node, has a keystore directory which is loaded on start. Key file
|
|
|
|
// for a given address is expected to be in that directory prior to node start.
|
|
|
|
AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error)
|
|
|
|
}
|
|
|
|
|
2018-01-05 20:58:17 +00:00
|
|
|
// TransactionResult is a JSON returned from transaction complete function (used internally)
|
|
|
|
type TransactionResult struct {
|
2017-05-16 12:09:52 +00:00
|
|
|
Hash common.Hash
|
|
|
|
Error error
|
|
|
|
}
|
|
|
|
|
|
|
|
// RawDiscardTransactionResult is list of results from CompleteTransactions() (used internally)
|
|
|
|
type RawDiscardTransactionResult struct {
|
|
|
|
Error error
|
|
|
|
}
|
|
|
|
|
2017-09-04 12:56:58 +00:00
|
|
|
// QueuedTxID queued transaction identifier
|
|
|
|
type QueuedTxID string
|
|
|
|
|
|
|
|
// QueuedTx holds enough information to complete the queued transaction.
|
|
|
|
type QueuedTx struct {
|
2018-01-26 05:59:21 +00:00
|
|
|
ID QueuedTxID
|
|
|
|
Context context.Context
|
|
|
|
Args SendTxArgs
|
2018-01-05 20:58:17 +00:00
|
|
|
Result chan TransactionResult
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
|
|
|
|
type SendTxArgs struct {
|
|
|
|
From common.Address `json:"from"`
|
|
|
|
To *common.Address `json:"to"`
|
|
|
|
Gas *hexutil.Big `json:"gas"`
|
|
|
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
|
|
|
Value *hexutil.Big `json:"value"`
|
|
|
|
Data hexutil.Bytes `json:"data"`
|
|
|
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// JailCell represents single jail cell, which is basically a JavaScript VM.
|
2017-09-08 11:55:17 +00:00
|
|
|
// It's designed to be a transparent wrapper around otto.VM's methods.
|
2017-05-16 12:09:52 +00:00
|
|
|
type JailCell interface {
|
2017-09-08 11:55:17 +00:00
|
|
|
// Set a value inside VM.
|
2017-08-04 16:14:17 +00:00
|
|
|
Set(string, interface{}) error
|
2017-09-08 11:55:17 +00:00
|
|
|
// Get a value from VM.
|
2017-08-04 16:14:17 +00:00
|
|
|
Get(string) (otto.Value, error)
|
2018-02-21 12:18:32 +00:00
|
|
|
// GetObjectValue returns the given name's otto.Value from the given otto.Value v. Should only be needed in tests.
|
|
|
|
GetObjectValue(otto.Value, string) (otto.Value, error)
|
2017-09-08 11:55:17 +00:00
|
|
|
// Run an arbitrary JS code. Input maybe string or otto.Script.
|
|
|
|
Run(interface{}) (otto.Value, error)
|
|
|
|
// Call an arbitrary JS function by name and args.
|
2017-09-02 17:04:23 +00:00
|
|
|
Call(item string, this interface{}, args ...interface{}) (otto.Value, error)
|
2017-10-06 16:52:26 +00:00
|
|
|
// Stop stops background execution of cell.
|
2017-11-07 17:36:42 +00:00
|
|
|
Stop() error
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// JailManager defines methods for managing jailed environments
|
|
|
|
type JailManager interface {
|
|
|
|
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
2017-09-02 17:04:23 +00:00
|
|
|
Call(chatID, this, args string) string
|
2017-05-16 12:09:52 +00:00
|
|
|
|
2017-11-07 17:36:42 +00:00
|
|
|
// CreateCell creates a new jail cell.
|
|
|
|
CreateCell(chatID string) (JailCell, error)
|
|
|
|
|
2017-11-23 12:37:59 +00:00
|
|
|
// Parse creates a new jail cell context, with the given chatID as identifier.
|
|
|
|
// New context executes provided JavaScript code, right after the initialization.
|
2017-11-23 12:51:52 +00:00
|
|
|
// DEPRECATED in favour of CreateAndInitCell.
|
2017-11-23 12:37:59 +00:00
|
|
|
Parse(chatID, js string) string
|
|
|
|
|
2017-11-07 17:36:42 +00:00
|
|
|
// CreateAndInitCell creates a new jail cell and initialize it
|
|
|
|
// with web3 and other handlers.
|
|
|
|
CreateAndInitCell(chatID string, code ...string) string
|
2017-05-16 12:09:52 +00:00
|
|
|
|
2017-09-02 17:04:23 +00:00
|
|
|
// Cell returns an existing instance of JailCell.
|
|
|
|
Cell(chatID string) (JailCell, error)
|
2017-05-16 12:09:52 +00:00
|
|
|
|
2017-11-07 17:36:42 +00:00
|
|
|
// Execute allows to run arbitrary JS code within a cell.
|
|
|
|
Execute(chatID, code string) string
|
|
|
|
|
|
|
|
// SetBaseJS allows to setup initial JavaScript to be loaded on each jail.CreateAndInitCell().
|
|
|
|
SetBaseJS(js string)
|
2017-10-06 16:52:26 +00:00
|
|
|
|
|
|
|
// Stop stops all background activity of jail
|
|
|
|
Stop()
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// APIResponse generic response from API
|
|
|
|
type APIResponse struct {
|
|
|
|
Error string `json:"error"`
|
|
|
|
}
|
|
|
|
|
2017-08-10 15:31:29 +00:00
|
|
|
// APIDetailedResponse represents a generic response
|
|
|
|
// with possible errors.
|
|
|
|
type APIDetailedResponse struct {
|
|
|
|
Status bool `json:"status"`
|
|
|
|
Message string `json:"message,omitempty"`
|
|
|
|
FieldErrors []APIFieldError `json:"field_errors,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r APIDetailedResponse) Error() string {
|
|
|
|
buf := bytes.NewBufferString("")
|
|
|
|
|
|
|
|
for _, err := range r.FieldErrors {
|
2018-01-17 16:46:21 +00:00
|
|
|
buf.WriteString(err.Error() + "\n") // nolint: gas
|
2017-08-10 15:31:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// APIFieldError represents a set of errors
|
|
|
|
// related to a parameter.
|
|
|
|
type APIFieldError struct {
|
|
|
|
Parameter string `json:"parameter,omitempty"`
|
|
|
|
Errors []APIError `json:"errors"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e APIFieldError) Error() string {
|
|
|
|
if len(e.Errors) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := bytes.NewBufferString(fmt.Sprintf("Parameter: %s\n", e.Parameter))
|
|
|
|
|
|
|
|
for _, err := range e.Errors {
|
2018-01-17 16:46:21 +00:00
|
|
|
buf.WriteString(err.Error() + "\n") // nolint: gas
|
2017-08-10 15:31:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// APIError represents a single error.
|
|
|
|
type APIError struct {
|
|
|
|
Message string `json:"message"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e APIError) Error() string {
|
|
|
|
return fmt.Sprintf("message=%s", e.Message)
|
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// AccountInfo represents account's info
|
|
|
|
type AccountInfo struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
PubKey string `json:"pubkey"`
|
|
|
|
Mnemonic string `json:"mnemonic"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
}
|
|
|
|
|
2017-08-15 10:27:12 +00:00
|
|
|
// StopRPCCallError defines a error type specific for killing a execution process.
|
|
|
|
type StopRPCCallError struct {
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns the internal error associated with the critical error.
|
|
|
|
func (c StopRPCCallError) Error() string {
|
|
|
|
return c.Err.Error()
|
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// CompleteTransactionResult is a JSON returned from transaction complete function (used in exposed method)
|
|
|
|
type CompleteTransactionResult struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Hash string `json:"hash"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// CompleteTransactionsResult is list of results from CompleteTransactions() (used in exposed method)
|
|
|
|
type CompleteTransactionsResult struct {
|
|
|
|
Results map[string]CompleteTransactionResult `json:"results"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiscardTransactionResult is a JSON returned from transaction discard function
|
|
|
|
type DiscardTransactionResult struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Error string `json:"error"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// DiscardTransactionsResult is a list of results from DiscardTransactions()
|
|
|
|
type DiscardTransactionsResult struct {
|
|
|
|
Results map[string]DiscardTransactionResult `json:"results"`
|
|
|
|
}
|
|
|
|
|
2017-11-07 17:46:11 +00:00
|
|
|
type account struct {
|
|
|
|
Address string
|
|
|
|
Password string
|
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// TestConfig contains shared (among different test packages) parameters
|
|
|
|
type TestConfig struct {
|
|
|
|
Node struct {
|
|
|
|
SyncSeconds time.Duration
|
|
|
|
HTTPPort int
|
|
|
|
WSPort int
|
|
|
|
}
|
2017-11-07 17:46:11 +00:00
|
|
|
Account1 account
|
|
|
|
Account2 account
|
|
|
|
Account3 account
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
2017-10-12 14:31:39 +00:00
|
|
|
// NotifyResult is a JSON returned from notify message
|
|
|
|
type NotifyResult struct {
|
|
|
|
Status bool `json:"status"`
|
2017-10-18 20:29:23 +00:00
|
|
|
Error string `json:"error,omitempty"`
|
2017-10-12 14:31:39 +00:00
|
|
|
}
|
|
|
|
|
2017-11-07 17:46:11 +00:00
|
|
|
const passphraseEnvName = "ACCOUNT_PASSWORD"
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// LoadTestConfig loads test configuration values from disk
|
2018-01-24 08:25:28 +00:00
|
|
|
func LoadTestConfig(networkID int) (*TestConfig, error) {
|
2017-05-16 12:09:52 +00:00
|
|
|
var testConfig TestConfig
|
|
|
|
|
2017-11-20 18:21:30 +00:00
|
|
|
configData := static.MustAsset("config/test-data.json")
|
|
|
|
if err := json.Unmarshal(configData, &testConfig); err != nil {
|
2017-05-16 12:09:52 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-01-24 08:25:28 +00:00
|
|
|
if networkID == params.StatusChainNetworkID {
|
2017-11-20 18:21:30 +00:00
|
|
|
accountsData := static.MustAsset("config/status-chain-accounts.json")
|
|
|
|
if err := json.Unmarshal(accountsData, &testConfig); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
accountsData := static.MustAsset("config/public-chain-accounts.json")
|
|
|
|
if err := json.Unmarshal(accountsData, &testConfig); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pass := os.Getenv(passphraseEnvName)
|
|
|
|
testConfig.Account1.Password = pass
|
|
|
|
testConfig.Account2.Password = pass
|
|
|
|
}
|
2017-11-07 17:46:11 +00:00
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
return &testConfig, nil
|
|
|
|
}
|