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"
|
2018-03-02 09:25:30 +00:00
|
|
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
2017-05-16 12:09:52 +00:00
|
|
|
"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.
|
2018-02-27 10:39:30 +00:00
|
|
|
// This struct is based on go-ethereum's type in internal/ethapi/api.go, but we have freedom
|
|
|
|
// over the exact layout of this struct.
|
2017-09-04 12:56:58 +00:00
|
|
|
type SendTxArgs struct {
|
|
|
|
From common.Address `json:"from"`
|
|
|
|
To *common.Address `json:"to"`
|
2018-02-27 10:39:30 +00:00
|
|
|
Gas *hexutil.Uint64 `json:"gas"`
|
2017-09-04 12:56:58 +00:00
|
|
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
|
|
|
Value *hexutil.Big `json:"value"`
|
|
|
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
2018-02-27 10:39:30 +00:00
|
|
|
Input hexutil.Bytes `json:"input"`
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|