major refactoring: node manager, tx queue, accounts, jail
- node: signals and node reset, fixes #152 - tests update (testify is used) - node manager refactored, race conditions fixed - node wrapper has been removed, we rely on go-ethereum node now - tx queue refactored - jail refactored - backend and API introduced
This commit is contained in:
parent
9c679d6bb7
commit
349103de1a
32
Makefile
32
Makefile
|
@ -47,9 +47,11 @@ statusgo-ios-simulator-mainnet: xgo
|
|||
@echo "iOS framework cross compilation done (mainnet)."
|
||||
|
||||
ci:
|
||||
build/env.sh go test -v -cover ./geth
|
||||
build/env.sh go test -v -cover ./geth/params
|
||||
build/env.sh go test -v -cover ./geth/api
|
||||
build/env.sh go test -v -cover ./geth/common
|
||||
build/env.sh go test -v -cover ./geth/jail
|
||||
build/env.sh go test -v -cover ./geth/node
|
||||
build/env.sh go test -v -cover ./geth/params
|
||||
build/env.sh go test -v -cover ./extkeys
|
||||
|
||||
generate:
|
||||
|
@ -110,12 +112,16 @@ lint:
|
|||
|
||||
test:
|
||||
@build/env.sh echo "mode: set" > coverage-all.out
|
||||
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth
|
||||
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/params
|
||||
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
|
||||
|
@ -123,13 +129,13 @@ test:
|
|||
@build/env.sh go tool cover -html=coverage-all.out -o coverage.html
|
||||
@build/env.sh go tool cover -func=coverage-all.out
|
||||
|
||||
test-geth:
|
||||
build/env.sh go test -v -coverprofile=coverage.out ./geth
|
||||
test-api:
|
||||
build/env.sh go test -v -coverprofile=coverage.out -coverpkg=./geth/node ./geth/api
|
||||
@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
|
||||
test-common:
|
||||
build/env.sh go test -v -coverprofile=coverage.out ./geth/common
|
||||
@build/env.sh go tool cover -html=coverage.out -o coverage.html
|
||||
@build/env.sh go tool cover -func=coverage.out
|
||||
|
||||
|
@ -138,6 +144,16 @@ test-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
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
@ -27,12 +26,16 @@ func faucetCommandHandler(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
fmt.Println("Starting Status Faucet node..")
|
||||
if err = geth.CreateAndRunNode(config); err != nil {
|
||||
if err := statusAPI.StartNode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait till node has been stopped
|
||||
geth.NodeManagerInstance().Node().GethStack().Wait()
|
||||
node, err := statusAPI.NodeManager().Node()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
node.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
@ -31,12 +30,16 @@ func lesCommandHandler(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
fmt.Println("Starting Light Status node..")
|
||||
if err = geth.CreateAndRunNode(config); err != nil {
|
||||
if err := statusAPI.StartNode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait till node has been stopped
|
||||
geth.NodeManagerInstance().Node().GethStack().Wait()
|
||||
node, err := statusAPI.NodeManager().Node()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
node.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,16 +6,81 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/jail"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
//export GenerateConfig
|
||||
func GenerateConfig(datadir *C.char, networkID C.int, devMode C.int) *C.char {
|
||||
config, err := params.NewNodeConfig(C.GoString(datadir), uint64(networkID), devMode == 1)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
outBytes, err := json.Marshal(&config)
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export StartNode
|
||||
func StartNode(configJSON *C.char) *C.char {
|
||||
config, err := params.LoadNodeConfig(C.GoString(configJSON))
|
||||
if err != nil {
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
err = statusAPI.StartNodeNonBlocking(config)
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export StopNode
|
||||
func StopNode() *C.char {
|
||||
return makeJSONResponse(statusAPI.StopNode())
|
||||
}
|
||||
|
||||
//export ResumeNode
|
||||
func ResumeNode() *C.char {
|
||||
err := fmt.Errorf("%v: %v", common.ErrDeprecatedMethod.Error(), "ResumeNode")
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export ResetChainData
|
||||
func ResetChainData() *C.char {
|
||||
return makeJSONResponse(statusAPI.ResetChainData())
|
||||
}
|
||||
|
||||
//export StopNodeRPCServer
|
||||
func StopNodeRPCServer() *C.char {
|
||||
err := fmt.Errorf("%v: %v", common.ErrDeprecatedMethod.Error(), "StopNodeRPCServer")
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export StartNodeRPCServer
|
||||
func StartNodeRPCServer() *C.char {
|
||||
err := fmt.Errorf("%v: %v", common.ErrDeprecatedMethod.Error(), "StartNodeRPCServer")
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export PopulateStaticPeers
|
||||
func PopulateStaticPeers() *C.char {
|
||||
err := fmt.Errorf("%v: %v", common.ErrDeprecatedMethod.Error(), "PopulateStaticPeers")
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export AddPeer
|
||||
func AddPeer(url *C.char) *C.char {
|
||||
err := fmt.Errorf("%v: %v", common.ErrDeprecatedMethod.Error(), "AddPeer")
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export CreateAccount
|
||||
func CreateAccount(password *C.char) *C.char {
|
||||
// This is equivalent to creating an account from the command line,
|
||||
// just modified to handle the function arg passing
|
||||
address, pubKey, mnemonic, err := geth.CreateAccount(C.GoString(password))
|
||||
address, pubKey, mnemonic, err := statusAPI.CreateAccount(C.GoString(password))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -23,21 +88,19 @@ func CreateAccount(password *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.AccountInfo{
|
||||
out := common.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Mnemonic: mnemonic,
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export CreateChildAccount
|
||||
func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
||||
|
||||
address, pubKey, err := geth.CreateChildAccount(C.GoString(parentAddress), C.GoString(password))
|
||||
address, pubKey, err := statusAPI.CreateChildAccount(C.GoString(parentAddress), C.GoString(password))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -45,20 +108,18 @@ func CreateChildAccount(parentAddress, password *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.AccountInfo{
|
||||
out := common.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export RecoverAccount
|
||||
func RecoverAccount(password, mnemonic *C.char) *C.char {
|
||||
|
||||
address, pubKey, err := geth.RecoverAccount(C.GoString(password), C.GoString(mnemonic))
|
||||
address, pubKey, err := statusAPI.RecoverAccount(C.GoString(password), C.GoString(mnemonic))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -66,41 +127,40 @@ func RecoverAccount(password, mnemonic *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.AccountInfo{
|
||||
out := common.AccountInfo{
|
||||
Address: address,
|
||||
PubKey: pubKey,
|
||||
Mnemonic: C.GoString(mnemonic),
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export VerifyAccountPassword
|
||||
func VerifyAccountPassword(keyStoreDir, address, password *C.char) *C.char {
|
||||
_, err := geth.VerifyAccountPassword(C.GoString(keyStoreDir), C.GoString(address), C.GoString(password))
|
||||
return makeJSONErrorResponse(err)
|
||||
_, err := statusAPI.VerifyAccountPassword(C.GoString(keyStoreDir), C.GoString(address), C.GoString(password))
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export Login
|
||||
func Login(address, password *C.char) *C.char {
|
||||
// loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
||||
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
||||
err := geth.SelectAccount(C.GoString(address), C.GoString(password))
|
||||
return makeJSONErrorResponse(err)
|
||||
err := statusAPI.SelectAccount(C.GoString(address), C.GoString(password))
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export Logout
|
||||
func Logout() *C.char {
|
||||
// This is equivalent to clearing whisper identities
|
||||
err := geth.Logout()
|
||||
return makeJSONErrorResponse(err)
|
||||
err := statusAPI.Logout()
|
||||
return makeJSONResponse(err)
|
||||
}
|
||||
|
||||
//export CompleteTransaction
|
||||
func CompleteTransaction(id, password *C.char) *C.char {
|
||||
txHash, err := geth.CompleteTransaction(C.GoString(id), C.GoString(password))
|
||||
txHash, err := statusAPI.CompleteTransaction(C.GoString(id), C.GoString(password))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -108,7 +168,7 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.CompleteTransactionResult{
|
||||
out := common.CompleteTransactionResult{
|
||||
ID: C.GoString(id),
|
||||
Hash: txHash.Hex(),
|
||||
Error: errString,
|
||||
|
@ -120,12 +180,12 @@ func CompleteTransaction(id, password *C.char) *C.char {
|
|||
|
||||
//export CompleteTransactions
|
||||
func CompleteTransactions(ids, password *C.char) *C.char {
|
||||
out := geth.CompleteTransactionsResult{}
|
||||
out.Results = make(map[string]geth.CompleteTransactionResult)
|
||||
out := common.CompleteTransactionsResult{}
|
||||
out.Results = make(map[string]common.CompleteTransactionResult)
|
||||
|
||||
results := geth.CompleteTransactions(C.GoString(ids), C.GoString(password))
|
||||
results := statusAPI.CompleteTransactions(C.GoString(ids), C.GoString(password))
|
||||
for txID, result := range results {
|
||||
txResult := geth.CompleteTransactionResult{
|
||||
txResult := common.CompleteTransactionResult{
|
||||
ID: txID,
|
||||
Hash: result.Hash.Hex(),
|
||||
}
|
||||
|
@ -141,7 +201,7 @@ func CompleteTransactions(ids, password *C.char) *C.char {
|
|||
|
||||
//export DiscardTransaction
|
||||
func DiscardTransaction(id *C.char) *C.char {
|
||||
err := geth.DiscardTransaction(C.GoString(id))
|
||||
err := statusAPI.DiscardTransaction(C.GoString(id))
|
||||
|
||||
errString := ""
|
||||
if err != nil {
|
||||
|
@ -149,7 +209,7 @@ func DiscardTransaction(id *C.char) *C.char {
|
|||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.DiscardTransactionResult{
|
||||
out := common.DiscardTransactionResult{
|
||||
ID: C.GoString(id),
|
||||
Error: errString,
|
||||
}
|
||||
|
@ -160,12 +220,12 @@ func DiscardTransaction(id *C.char) *C.char {
|
|||
|
||||
//export DiscardTransactions
|
||||
func DiscardTransactions(ids *C.char) *C.char {
|
||||
out := geth.DiscardTransactionsResult{}
|
||||
out.Results = make(map[string]geth.DiscardTransactionResult)
|
||||
out := common.DiscardTransactionsResult{}
|
||||
out.Results = make(map[string]common.DiscardTransactionResult)
|
||||
|
||||
results := geth.DiscardTransactions(C.GoString(ids))
|
||||
results := statusAPI.DiscardTransactions(C.GoString(ids))
|
||||
for txID, result := range results {
|
||||
txResult := geth.DiscardTransactionResult{
|
||||
txResult := common.DiscardTransactionResult{
|
||||
ID: txID,
|
||||
}
|
||||
if result.Error != nil {
|
||||
|
@ -178,112 +238,31 @@ func DiscardTransactions(ids *C.char) *C.char {
|
|||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export GenerateConfig
|
||||
func GenerateConfig(datadir *C.char, networkID C.int, devMode C.int) *C.char {
|
||||
config, err := params.NewNodeConfig(C.GoString(datadir), uint64(networkID), devMode == 1)
|
||||
if err != nil {
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
outBytes, err := json.Marshal(&config)
|
||||
if err != nil {
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
//export StartNode
|
||||
func StartNode(configJSON *C.char) *C.char {
|
||||
config, err := params.LoadNodeConfig(C.GoString(configJSON))
|
||||
if err != nil {
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
err = geth.CreateAndRunNode(config)
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export StopNode
|
||||
func StopNode() *C.char {
|
||||
err := geth.NodeManagerInstance().StopNode()
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export ResumeNode
|
||||
func ResumeNode() *C.char {
|
||||
err := geth.NodeManagerInstance().ResumeNode()
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export ResetChainData
|
||||
func ResetChainData() *C.char {
|
||||
err := geth.NodeManagerInstance().ResetChainData()
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export StopNodeRPCServer
|
||||
func StopNodeRPCServer() *C.char {
|
||||
_, err := geth.NodeManagerInstance().StopNodeRPCServer()
|
||||
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export StartNodeRPCServer
|
||||
func StartNodeRPCServer() *C.char {
|
||||
_, err := geth.NodeManagerInstance().StartNodeRPCServer()
|
||||
|
||||
return makeJSONErrorResponse(err)
|
||||
}
|
||||
|
||||
//export InitJail
|
||||
func InitJail(js *C.char) {
|
||||
jail.Init(C.GoString(js))
|
||||
statusAPI.JailBaseJS(C.GoString(js))
|
||||
}
|
||||
|
||||
//export Parse
|
||||
func Parse(chatID *C.char, js *C.char) *C.char {
|
||||
res := jail.GetInstance().Parse(C.GoString(chatID), C.GoString(js))
|
||||
res := statusAPI.JailParse(C.GoString(chatID), C.GoString(js))
|
||||
return C.CString(res)
|
||||
}
|
||||
|
||||
//export Call
|
||||
func Call(chatID *C.char, path *C.char, params *C.char) *C.char {
|
||||
res := jail.GetInstance().Call(C.GoString(chatID), C.GoString(path), C.GoString(params))
|
||||
res := statusAPI.JailCall(C.GoString(chatID), C.GoString(path), C.GoString(params))
|
||||
return C.CString(res)
|
||||
}
|
||||
|
||||
//export PopulateStaticPeers
|
||||
func PopulateStaticPeers() {
|
||||
geth.NodeManagerInstance().PopulateStaticPeers()
|
||||
}
|
||||
|
||||
//export AddPeer
|
||||
func AddPeer(url *C.char) *C.char {
|
||||
success, err := geth.NodeManagerInstance().AddPeer(C.GoString(url))
|
||||
func makeJSONResponse(err error) *C.char {
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.AddPeerResult{
|
||||
Success: success,
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
||||
return C.CString(string(outBytes))
|
||||
}
|
||||
|
||||
func makeJSONErrorResponse(err error) *C.char {
|
||||
errString := ""
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
out := geth.JSONError{
|
||||
out := common.APIResponse{
|
||||
Error: errString,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&out)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/status-im/status-go/geth/api"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
@ -14,6 +15,7 @@ var (
|
|||
gitCommit = "rely on linker: -ldflags -X main.GitCommit"
|
||||
buildStamp = "rely on linker: -ldflags -X main.buildStamp"
|
||||
app = makeApp(gitCommit)
|
||||
statusAPI = api.NewStatusAPI()
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -12,24 +12,28 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"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/core"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
gethparams "github.com/ethereum/go-ethereum/params"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
var testConfig *geth.TestConfig
|
||||
|
||||
func init() {
|
||||
// error is ignored, as it will occur on non-test compilation only, and there testConfig is not used at all
|
||||
// (we have to use "main" package due to restrictions on including C imports into *_test packages)
|
||||
testConfig, _ = geth.LoadTestConfig()
|
||||
}
|
||||
var nodeConfigJSON = `{
|
||||
"NetworkId": ` + strconv.Itoa(params.RopstenNetworkID) + `,
|
||||
"DataDir": "` + TestDataDir + `",
|
||||
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
|
||||
"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
|
||||
"LogEnabled": true,
|
||||
"LogLevel": "INFO"
|
||||
}`
|
||||
|
||||
// nolint: deadcode
|
||||
func testExportedAPI(t *testing.T, done chan struct{}) {
|
||||
|
@ -48,13 +52,9 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
|||
testResetChainData,
|
||||
},
|
||||
{
|
||||
"pause node",
|
||||
"stop/resume node",
|
||||
testStopResumeNode,
|
||||
},
|
||||
{
|
||||
"restart node RPC",
|
||||
testRestartNodeRPC,
|
||||
},
|
||||
{
|
||||
"create main and child accounts",
|
||||
testCreateChildAccount,
|
||||
|
@ -102,6 +102,7 @@ func testExportedAPI(t *testing.T, done chan struct{}) {
|
|||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Logf("=== RUN %s", test.name)
|
||||
if ok := test.fn(t); !ok {
|
||||
break
|
||||
}
|
||||
|
@ -117,25 +118,25 @@ func testVerifyAccountPassword(t *testing.T) bool {
|
|||
}
|
||||
defer os.RemoveAll(tmpDir) // nolint: errcheck
|
||||
|
||||
if err = geth.ImportTestAccount(tmpDir, "test-account1.pk"); err != nil {
|
||||
if err = common.ImportTestAccount(tmpDir, "test-account1.pk"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = geth.ImportTestAccount(tmpDir, "test-account2.pk"); err != nil {
|
||||
if err = common.ImportTestAccount(tmpDir, "test-account2.pk"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// rename account file (to see that file's internals reviewed, when locating account key)
|
||||
accountFilePathOriginal := filepath.Join(tmpDir, "test-account1.pk")
|
||||
accountFilePath := filepath.Join(tmpDir, "foo"+testConfig.Account1.Address+"bar.pk")
|
||||
accountFilePath := filepath.Join(tmpDir, "foo"+TestConfig.Account1.Address+"bar.pk")
|
||||
if err := os.Rename(accountFilePathOriginal, accountFilePath); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
response := geth.JSONError{}
|
||||
response := common.APIResponse{}
|
||||
rawResponse := VerifyAccountPassword(
|
||||
C.CString(tmpDir),
|
||||
C.CString(testConfig.Account1.Address),
|
||||
C.CString(testConfig.Account1.Password))
|
||||
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)
|
||||
|
@ -251,7 +252,7 @@ func testGetDefaultConfig(t *testing.T) bool {
|
|||
}
|
||||
|
||||
func testResetChainData(t *testing.T) bool {
|
||||
resetChainDataResponse := geth.JSONError{}
|
||||
resetChainDataResponse := common.APIResponse{}
|
||||
rawResponse := ResetChainData()
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &resetChainDataResponse); err != nil {
|
||||
|
@ -263,7 +264,7 @@ func testResetChainData(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
time.Sleep(testConfig.Node.SyncSeconds * time.Second) // allow to re-sync blockchain
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to re-sync blockchain
|
||||
|
||||
testCompleteTransaction(t)
|
||||
|
||||
|
@ -272,17 +273,17 @@ func testResetChainData(t *testing.T) bool {
|
|||
|
||||
func testStopResumeNode(t *testing.T) bool {
|
||||
// to make sure that we start with empty account (which might get populated during previous tests)
|
||||
if err := geth.Logout(); err != nil {
|
||||
if err := statusAPI.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
whisperService, err := statusAPI.NodeManager().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create an account
|
||||
address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
address1, pubKey1, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return false
|
||||
|
@ -295,8 +296,8 @@ func testStopResumeNode(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// select account
|
||||
loginResponse := geth.JSONError{}
|
||||
rawResponse := Login(C.CString(address1), C.CString(testConfig.Account1.Password))
|
||||
loginResponse := common.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)
|
||||
|
@ -314,7 +315,7 @@ func testStopResumeNode(t *testing.T) bool {
|
|||
// stop and resume node, then make sure that selected account is still selected
|
||||
// nolint: dupl
|
||||
stopNodeFn := func() bool {
|
||||
response := geth.JSONError{}
|
||||
response := common.APIResponse{}
|
||||
rawResponse = StopNode()
|
||||
|
||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
||||
|
@ -331,11 +332,12 @@ func testStopResumeNode(t *testing.T) bool {
|
|||
|
||||
// nolint: dupl
|
||||
resumeNodeFn := func() bool {
|
||||
response := geth.JSONError{}
|
||||
rawResponse = ResumeNode()
|
||||
response := common.APIResponse{}
|
||||
rawResponse = StartNode(C.CString(nodeConfigJSON))
|
||||
|
||||
|
||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
||||
t.Errorf("cannot decode ResumeNode response (%s): %v", C.GoString(rawResponse), err)
|
||||
t.Errorf("cannot decode StartNode response (%s): %v", C.GoString(rawResponse), err)
|
||||
return false
|
||||
}
|
||||
if response.Error != "" {
|
||||
|
@ -353,8 +355,10 @@ func testStopResumeNode(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
time.Sleep(5 * 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 = geth.NodeManagerInstance().WhisperService()
|
||||
whisperService, err = statusAPI.NodeManager().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
@ -368,65 +372,21 @@ func testStopResumeNode(t *testing.T) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func testRestartNodeRPC(t *testing.T) bool {
|
||||
// stop RPC
|
||||
stopNodeRPCServerResponse := geth.JSONError{}
|
||||
rawResponse := StopNodeRPCServer()
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &stopNodeRPCServerResponse); err != nil {
|
||||
t.Errorf("cannot decode StopNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
|
||||
return false
|
||||
}
|
||||
if stopNodeRPCServerResponse.Error != "" {
|
||||
t.Errorf("unexpected error: %s", stopNodeRPCServerResponse.Error)
|
||||
return false
|
||||
}
|
||||
|
||||
// start again RPC
|
||||
startNodeRPCServerResponse := geth.JSONError{}
|
||||
rawResponse = StartNodeRPCServer()
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &startNodeRPCServerResponse); err != nil {
|
||||
t.Errorf("cannot decode StartNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
|
||||
return false
|
||||
}
|
||||
if startNodeRPCServerResponse.Error != "" {
|
||||
t.Errorf("unexpected error: %s", startNodeRPCServerResponse.Error)
|
||||
return false
|
||||
}
|
||||
|
||||
// start when we have RPC already running
|
||||
startNodeRPCServerResponse = geth.JSONError{}
|
||||
rawResponse = StartNodeRPCServer()
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &startNodeRPCServerResponse); err != nil {
|
||||
t.Errorf("cannot decode StartNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
|
||||
return false
|
||||
}
|
||||
expectedError := "HTTP RPC already running on localhost:8645"
|
||||
if startNodeRPCServerResponse.Error != expectedError {
|
||||
t.Errorf("expected error not thrown: %s", expectedError)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func testCreateChildAccount(t *testing.T) bool {
|
||||
// to make sure that we start with empty account (which might get populated during previous tests)
|
||||
if err := geth.Logout(); err != nil {
|
||||
if err := statusAPI.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keyStore, err := geth.NodeManagerInstance().AccountKeyStore()
|
||||
keyStore, err := statusAPI.NodeManager().AccountKeyStore()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// create an account
|
||||
createAccountResponse := geth.AccountInfo{}
|
||||
rawResponse := CreateAccount(C.CString(testConfig.Account1.Password))
|
||||
createAccountResponse := common.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)
|
||||
|
@ -440,14 +400,14 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
address, pubKey, mnemonic := createAccountResponse.Address, createAccountResponse.PubKey, createAccountResponse.Mnemonic
|
||||
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||
|
||||
account, err := geth.ParseAccountString(address)
|
||||
account, err := common.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(account, testConfig.Account1.Password)
|
||||
_, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||
return false
|
||||
|
@ -459,27 +419,27 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
|
||||
createSubAccountResponse := geth.AccountInfo{}
|
||||
rawResponse = CreateChildAccount(C.CString(""), C.CString(testConfig.Account1.Password))
|
||||
createSubAccountResponse := common.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 != geth.ErrNoAccountSelected.Error() {
|
||||
if createSubAccountResponse.Error != node.ErrNoAccountSelected.Error() {
|
||||
t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", createSubAccountResponse.Error)
|
||||
return false
|
||||
}
|
||||
|
||||
err = geth.SelectAccount(address, testConfig.Account1.Password)
|
||||
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 = geth.AccountInfo{}
|
||||
createSubAccountResponse = common.AccountInfo{}
|
||||
rawResponse = CreateChildAccount(C.CString(""), C.CString("wrong password"))
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil {
|
||||
|
@ -493,8 +453,8 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// create sub-account (from implicit parent)
|
||||
createSubAccountResponse1 := geth.AccountInfo{}
|
||||
rawResponse = CreateChildAccount(C.CString(""), C.CString(testConfig.Account1.Password))
|
||||
createSubAccountResponse1 := common.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)
|
||||
|
@ -507,8 +467,8 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// make sure that sub-account index automatically progresses
|
||||
createSubAccountResponse2 := geth.AccountInfo{}
|
||||
rawResponse = CreateChildAccount(C.CString(""), C.CString(testConfig.Account1.Password))
|
||||
createSubAccountResponse2 := common.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)
|
||||
|
@ -525,8 +485,8 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// create sub-account (from explicit parent)
|
||||
createSubAccountResponse3 := geth.AccountInfo{}
|
||||
rawResponse = CreateChildAccount(C.CString(createSubAccountResponse2.Address), C.CString(testConfig.Account1.Password))
|
||||
createSubAccountResponse3 := common.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)
|
||||
|
@ -549,10 +509,10 @@ func testCreateChildAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
func testRecoverAccount(t *testing.T) bool {
|
||||
keyStore, _ := geth.NodeManagerInstance().AccountKeyStore()
|
||||
keyStore, _ := statusAPI.NodeManager().AccountKeyStore()
|
||||
|
||||
// create an account
|
||||
address, pubKey, mnemonic, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
address, pubKey, mnemonic, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return false
|
||||
|
@ -560,8 +520,8 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||
|
||||
// try recovering using password + mnemonic
|
||||
recoverAccountResponse := geth.AccountInfo{}
|
||||
rawResponse := RecoverAccount(C.CString(testConfig.Account1.Password), C.CString(mnemonic))
|
||||
recoverAccountResponse := common.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)
|
||||
|
@ -578,24 +538,24 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
|
||||
account, err := geth.ParseAccountString(address)
|
||||
account, err := common.ParseAccountString(address)
|
||||
if err != nil {
|
||||
t.Errorf("can not get account from address: %v", err)
|
||||
}
|
||||
|
||||
account, key, err := keyStore.AccountDecryptedKey(account, testConfig.Account1.Password)
|
||||
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 {
|
||||
if err = keyStore.Delete(account, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot remove account: %v", err)
|
||||
}
|
||||
|
||||
recoverAccountResponse = geth.AccountInfo{}
|
||||
rawResponse = RecoverAccount(C.CString(testConfig.Account1.Password), C.CString(mnemonic))
|
||||
recoverAccountResponse = common.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)
|
||||
|
@ -612,7 +572,7 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// make sure that extended key exists and is imported ok too
|
||||
_, key, err = keyStore.AccountDecryptedKey(account, testConfig.Account1.Password)
|
||||
_, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||
return false
|
||||
|
@ -622,8 +582,8 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// make sure that calling import several times, just returns from cache (no error is expected)
|
||||
recoverAccountResponse = geth.AccountInfo{}
|
||||
rawResponse = RecoverAccount(C.CString(testConfig.Account1.Password), C.CString(mnemonic))
|
||||
recoverAccountResponse = common.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)
|
||||
|
@ -640,7 +600,7 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// time to login with recovered data
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
whisperService, err := statusAPI.NodeManager().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
@ -649,7 +609,7 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
if whisperService.HasKeyPair(pubKeyCheck) {
|
||||
t.Error("identity already present in whisper")
|
||||
}
|
||||
err = geth.SelectAccount(addressCheck, testConfig.Account1.Password)
|
||||
err = statusAPI.SelectAccount(addressCheck, TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return false
|
||||
|
@ -663,20 +623,20 @@ func testRecoverAccount(t *testing.T) bool {
|
|||
|
||||
func testAccountSelect(t *testing.T) bool {
|
||||
// test to see if the account was injected in whisper
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
whisperService, err := statusAPI.NodeManager().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create an account
|
||||
address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
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 := geth.CreateAccount(testConfig.Account1.Password)
|
||||
address2, pubKey2, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Error("Test failed: could not create account")
|
||||
return false
|
||||
|
@ -689,7 +649,7 @@ func testAccountSelect(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// try selecting with wrong password
|
||||
loginResponse := geth.JSONError{}
|
||||
loginResponse := common.APIResponse{}
|
||||
rawResponse := Login(C.CString(address1), C.CString("wrongPassword"))
|
||||
|
||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
||||
|
@ -702,8 +662,8 @@ func testAccountSelect(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
loginResponse = geth.JSONError{}
|
||||
rawResponse = Login(C.CString(address1), C.CString(testConfig.Account1.Password))
|
||||
loginResponse = common.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)
|
||||
|
@ -723,8 +683,8 @@ func testAccountSelect(t *testing.T) bool {
|
|||
t.Error("identity already present in whisper")
|
||||
}
|
||||
|
||||
loginResponse = geth.JSONError{}
|
||||
rawResponse = Login(C.CString(address2), C.CString(testConfig.Account1.Password))
|
||||
loginResponse = common.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)
|
||||
|
@ -746,14 +706,14 @@ func testAccountSelect(t *testing.T) bool {
|
|||
}
|
||||
|
||||
func testAccountLogout(t *testing.T) bool {
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
whisperService, err := statusAPI.NodeManager().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, pubKey, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
address, pubKey, _, err := statusAPI.CreateAccount(TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return false
|
||||
|
@ -766,7 +726,7 @@ func testAccountLogout(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// select/login
|
||||
err = geth.SelectAccount(address, testConfig.Account1.Password)
|
||||
err = statusAPI.SelectAccount(address, TestConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return false
|
||||
|
@ -776,7 +736,7 @@ func testAccountLogout(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
logoutResponse := geth.JSONError{}
|
||||
logoutResponse := common.APIResponse{}
|
||||
rawResponse := Logout()
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &logoutResponse); err != nil {
|
||||
|
@ -800,7 +760,7 @@ func testAccountLogout(t *testing.T) bool {
|
|||
|
||||
func testCompleteTransaction(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return false
|
||||
|
@ -810,31 +770,33 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
time.Sleep(5 * time.Second) // allow to sync
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err = geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
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
|
||||
queuedTxCompleted := make(chan struct{}, 1)
|
||||
abortPanic := make(chan struct{}, 1)
|
||||
geth.PanicAfter(10*time.Second, abortPanic, "testCompleteTransaction")
|
||||
common.PanicAfter(10*time.Second, abortPanic, "testCompleteTransaction")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = ""
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope geth.SignalEnvelope
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
if err = json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
completeTxResponse := geth.CompleteTransactionResult{}
|
||||
rawResponse := CompleteTransaction(C.CString(event["id"].(string)), C.CString(testConfig.Account1.Password))
|
||||
completeTxResponse := common.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)
|
||||
|
@ -854,8 +816,8 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -869,7 +831,7 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
if reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("Test failed: transaction was never queued or completed")
|
||||
return false
|
||||
}
|
||||
|
@ -884,7 +846,7 @@ func testCompleteTransaction(t *testing.T) bool {
|
|||
|
||||
func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return false
|
||||
|
@ -895,8 +857,8 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -906,14 +868,14 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
allTestTxCompleted := make(chan struct{}, 1)
|
||||
|
||||
// replace transaction notification handler
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope geth.SignalEnvelope
|
||||
var envelope node.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
if envelope.Type == node.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)
|
||||
|
@ -925,8 +887,8 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -934,7 +896,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
return
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
if reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned empty hash")
|
||||
return
|
||||
}
|
||||
|
@ -952,8 +914,8 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// complete
|
||||
resultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(testConfig.Account1.Password))
|
||||
resultsStruct := geth.CompleteTransactionsResult{}
|
||||
resultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
resultsStruct := common.CompleteTransactionsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -1025,7 +987,7 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
|
|||
|
||||
func testDiscardTransaction(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return false
|
||||
|
@ -1036,25 +998,25 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err = geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
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)
|
||||
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
|
||||
common.PanicAfter(20*time.Second, completeQueuedTransaction, "testDiscardTransaction")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := false
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope geth.SignalEnvelope
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
if err = json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
@ -1065,7 +1027,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// discard
|
||||
discardResponse := geth.DiscardTransactionResult{}
|
||||
discardResponse := common.DiscardTransactionResult{}
|
||||
rawResponse := DiscardTransaction(C.CString(txID))
|
||||
|
||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
|
||||
|
@ -1078,7 +1040,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
_, err = geth.CompleteTransaction(txID, testConfig.Account1.Password)
|
||||
_, err = statusAPI.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
if err != status.ErrQueuedTxIDNotFound {
|
||||
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
|
||||
return
|
||||
|
@ -1093,7 +1055,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
||||
}
|
||||
|
||||
if envelope.Type == geth.EventTransactionFailed {
|
||||
if envelope.Type == node.EventTransactionFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
||||
|
||||
|
@ -1105,7 +1067,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != geth.SendTransactionDiscardedErrorCode {
|
||||
if receivedErrCode != node.SendTransactionDiscardedErrorCode {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
@ -1116,8 +1078,8 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != status.ErrQueuedTxDiscarded {
|
||||
|
@ -1125,7 +1087,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned hash, while it shouldn't")
|
||||
return false
|
||||
}
|
||||
|
@ -1145,7 +1107,7 @@ func testDiscardTransaction(t *testing.T) bool {
|
|||
|
||||
func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
lightEthereum, err := statusAPI.NodeManager().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return false
|
||||
|
@ -1156,8 +1118,8 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
if err := statusAPI.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", TestConfig.Account1.Address)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -1168,14 +1130,14 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
|
||||
// replace transaction notification handler
|
||||
txFailedEventCallCount := 0
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope geth.SignalEnvelope
|
||||
var envelope node.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
@ -1188,7 +1150,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == geth.EventTransactionFailed {
|
||||
if envelope.Type == node.EventTransactionFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction return event received: {id: %s}\n", event["id"].(string))
|
||||
|
||||
|
@ -1200,7 +1162,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != geth.SendTransactionDiscardedErrorCode {
|
||||
if receivedErrCode != node.SendTransactionDiscardedErrorCode {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
@ -1215,8 +1177,8 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != status.ErrQueuedTxDiscarded {
|
||||
|
@ -1224,7 +1186,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
if !reflect.DeepEqual(txHashCheck, gethcommon.Hash{}) {
|
||||
t.Error("transaction returned hash, while it shouldn't")
|
||||
return
|
||||
}
|
||||
|
@ -1243,7 +1205,7 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
|
||||
// discard
|
||||
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIDStrings)))
|
||||
discardResultsStruct := geth.DiscardTransactionsResult{}
|
||||
discardResultsStruct := common.DiscardTransactionsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -1256,8 +1218,8 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
|
|||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(testConfig.Account1.Password))
|
||||
completeResultsStruct := geth.CompleteTransactionsResult{}
|
||||
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(TestConfig.Account1.Password))
|
||||
completeResultsStruct := common.CompleteTransactionsResult{}
|
||||
if err := json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
@ -1353,7 +1315,7 @@ func testJailFunctionCall(t *testing.T) bool {
|
|||
InitJail(C.CString(""))
|
||||
|
||||
// load Status JS and add test command to it
|
||||
statusJS := geth.LoadFromFile(filepath.Join(geth.RootDir, "geth/jail/testdata/status.js")) + `;
|
||||
statusJS := string(static.MustAsset("testdata/jail/status.js")) + `;
|
||||
_status_catalog.commands["testCommand"] = function (params) {
|
||||
return params.val * params.val;
|
||||
};`
|
||||
|
@ -1384,41 +1346,44 @@ func testJailFunctionCall(t *testing.T) bool {
|
|||
|
||||
func startTestNode(t *testing.T) <-chan struct{} {
|
||||
syncRequired := false
|
||||
if _, err := os.Stat(geth.TestDataDir); os.IsNotExist(err) {
|
||||
if _, err := os.Stat(TestDataDir); os.IsNotExist(err) {
|
||||
syncRequired = true
|
||||
}
|
||||
|
||||
// inject test accounts
|
||||
if err := geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account1.pk"); err != nil {
|
||||
if err := common.ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account1.pk"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account2.pk"); err != nil {
|
||||
if err := common.ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account2.pk"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
waitForNodeStart := make(chan struct{}, 1)
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
t.Log(jsonEvent)
|
||||
var envelope geth.SignalEnvelope
|
||||
var envelope node.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventNodeCrashed {
|
||||
geth.TriggerDefaultNodeNotificationHandler(jsonEvent)
|
||||
if envelope.Type == node.EventNodeCrashed {
|
||||
node.TriggerDefaultNodeNotificationHandler(jsonEvent)
|
||||
return
|
||||
}
|
||||
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
}
|
||||
if envelope.Type == geth.EventNodeStarted {
|
||||
if envelope.Type == node.EventNodeStarted {
|
||||
t.Log("Node started, but we wait till it be ready")
|
||||
}
|
||||
if envelope.Type == node.EventNodeReady {
|
||||
// manually add static nodes (LES auto-discovery is not stable yet)
|
||||
PopulateStaticPeers()
|
||||
|
||||
// sync
|
||||
if syncRequired {
|
||||
t.Logf("Sync is required, it will take %d seconds", testConfig.Node.SyncSeconds)
|
||||
time.Sleep(testConfig.Node.SyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||
t.Logf("Sync is required, it will take %d seconds", TestConfig.Node.SyncSeconds)
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||
} else {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
@ -1429,16 +1394,8 @@ func startTestNode(t *testing.T) <-chan struct{} {
|
|||
})
|
||||
|
||||
go func() {
|
||||
configJSON := `{
|
||||
"NetworkId": ` + strconv.Itoa(params.RopstenNetworkID) + `,
|
||||
"DataDir": "` + geth.TestDataDir + `",
|
||||
"HTTPPort": ` + strconv.Itoa(testConfig.Node.HTTPPort) + `,
|
||||
"WSPort": ` + strconv.Itoa(testConfig.Node.WSPort) + `,
|
||||
"LogEnabled": true,
|
||||
"LogLevel": "INFO"
|
||||
}`
|
||||
response := StartNode(C.CString(configJSON))
|
||||
responseErr := geth.JSONError{}
|
||||
response := StartNode(C.CString(nodeConfigJSON))
|
||||
responseErr := common.APIResponse{}
|
||||
|
||||
if err := json.Unmarshal([]byte(C.GoString(response)), &responseErr); err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
@ -125,21 +126,20 @@ func wnode(ctx *cli.Context) error {
|
|||
|
||||
// import test accounts
|
||||
if ctx.BoolT(WhisperInjectTestAccounts.Name) {
|
||||
if err = geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account1.pk"); err != nil {
|
||||
if err = common.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account1.pk"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account2.pk"); err != nil {
|
||||
if err = common.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account2.pk"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = geth.CreateAndRunNode(config); err != nil {
|
||||
if err := statusAPI.StartNode(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// inject test accounts into Whisper
|
||||
if ctx.BoolT(WhisperInjectTestAccounts.Name) {
|
||||
testConfig, _ := geth.LoadTestConfig()
|
||||
testConfig, _ := common.LoadTestConfig()
|
||||
if err = injectAccountIntoWhisper(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -149,7 +149,11 @@ func wnode(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
// wait till node has been stopped
|
||||
geth.NodeManagerInstance().Node().GethStack().Wait()
|
||||
node, err := statusAPI.NodeManager().Node()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
node.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -237,20 +241,20 @@ func makeWhisperNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
|
|||
// injectAccountIntoWhisper adds key pair into Whisper. Similar to Select/Login,
|
||||
// but allows multiple accounts to be injected.
|
||||
func injectAccountIntoWhisper(address, password string) error {
|
||||
nodeManager := geth.NodeManagerInstance()
|
||||
nodeManager := statusAPI.NodeManager()
|
||||
keyStore, err := nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account, err := geth.ParseAccountString(address)
|
||||
account, err := common.ParseAccountString(address)
|
||||
if err != nil {
|
||||
return geth.ErrAddressToAccountMappingFailure
|
||||
return node.ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
||||
_, accountKey, err := keyStore.AccountDecryptedKey(account, password)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %v", geth.ErrAccountToKeyMappingFailure.Error(), err)
|
||||
return fmt.Errorf("%s: %v", node.ErrAccountToKeyMappingFailure.Error(), err)
|
||||
}
|
||||
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
|
|
|
@ -1,679 +0,0 @@
|
|||
package geth_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/geth"
|
||||
)
|
||||
|
||||
func TestVerifyAccountPassword(t *testing.T) {
|
||||
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(keyStoreDir) // nolint: errcheck
|
||||
|
||||
emptyKeyStoreDir, err := ioutil.TempDir(os.TempDir(), "empty")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(emptyKeyStoreDir) // nolint: errcheck
|
||||
|
||||
// import account keys
|
||||
if err = geth.ImportTestAccount(keyStoreDir, "test-account1.pk"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = geth.ImportTestAccount(keyStoreDir, "test-account2.pk"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
account1Address := common.BytesToAddress(common.FromHex(testConfig.Account1.Address))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keyPath string
|
||||
address string
|
||||
password string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
"correct address, correct password (decrypt should succeed)",
|
||||
keyStoreDir,
|
||||
testConfig.Account1.Address,
|
||||
testConfig.Account1.Password,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"correct address, correct password, non-existent key store",
|
||||
filepath.Join(keyStoreDir, "non-existent-folder"),
|
||||
testConfig.Account1.Address,
|
||||
testConfig.Account1.Password,
|
||||
fmt.Errorf("cannot traverse key store folder: lstat %s/non-existent-folder: no such file or directory", keyStoreDir),
|
||||
},
|
||||
{
|
||||
"correct address, correct password, empty key store (pk is not there)",
|
||||
emptyKeyStoreDir,
|
||||
testConfig.Account1.Address,
|
||||
testConfig.Account1.Password,
|
||||
fmt.Errorf("cannot locate account for address: %x", account1Address),
|
||||
},
|
||||
{
|
||||
"wrong address, correct password",
|
||||
keyStoreDir,
|
||||
"0x79791d3e8f2daa1f7fec29649d152c0ada3cc535",
|
||||
testConfig.Account1.Password,
|
||||
fmt.Errorf("cannot locate account for address: %s", "79791d3e8f2daa1f7fec29649d152c0ada3cc535"),
|
||||
},
|
||||
{
|
||||
"correct address, wrong password",
|
||||
keyStoreDir,
|
||||
testConfig.Account1.Address,
|
||||
"wrong password", // wrong password
|
||||
errors.New("could not decrypt key with given passphrase"),
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
t.Log(testCase.name)
|
||||
accountKey, err := geth.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
|
||||
if !reflect.DeepEqual(err, testCase.expectedError) {
|
||||
t.Fatalf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err)
|
||||
}
|
||||
if err == nil {
|
||||
if accountKey == nil {
|
||||
t.Error("no error reported, but account key is missing")
|
||||
}
|
||||
accountAddress := common.BytesToAddress(common.FromHex(testCase.address))
|
||||
if accountKey.Address != accountAddress {
|
||||
t.Fatalf("account mismatch: have %x, want %x", accountKey.Address, accountAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsList(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
|
||||
les, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("expected LES service: %v", err)
|
||||
}
|
||||
accounts := les.StatusBackend.AccountManager().Accounts()
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure that we start with empty accounts list (nobody has logged in yet)
|
||||
if len(accounts) != 0 {
|
||||
t.Error("accounts returned, while there should be none (we haven't logged in yet)")
|
||||
return
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, _, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// ensure that there is still no accounts returned
|
||||
accounts = les.StatusBackend.AccountManager().Accounts()
|
||||
if len(accounts) != 0 {
|
||||
t.Error("accounts returned, while there should be none (we haven't logged in yet)")
|
||||
return
|
||||
}
|
||||
|
||||
// select account (sub-accounts will be created for this key)
|
||||
err = geth.SelectAccount(address, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
// at this point main account should show up
|
||||
accounts = les.StatusBackend.AccountManager().Accounts()
|
||||
if len(accounts) != 1 {
|
||||
t.Error("exactly single account is expected (main account)")
|
||||
return
|
||||
}
|
||||
if string(accounts[0].Hex()) != "0x"+address {
|
||||
t.Errorf("main account is not retured as the first key: got %s, expected %s",
|
||||
accounts[0].Hex(), "0x"+address)
|
||||
return
|
||||
}
|
||||
|
||||
// create sub-account 1
|
||||
subAccount1, subPubKey1, err := geth.CreateChildAccount("", testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create sub-account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// now we expect to see both main account and sub-account 1
|
||||
accounts = les.StatusBackend.AccountManager().Accounts()
|
||||
if len(accounts) != 2 {
|
||||
t.Error("exactly 2 accounts are expected (main + sub-account 1)")
|
||||
return
|
||||
}
|
||||
if string(accounts[0].Hex()) != "0x"+address {
|
||||
t.Errorf("main account is not retured as the first key: got %s, expected %s",
|
||||
accounts[0].Hex(), "0x"+address)
|
||||
return
|
||||
}
|
||||
if string(accounts[1].Hex()) != "0x"+subAccount1 {
|
||||
t.Errorf("subAcount1 not returned: got %s, expected %s", accounts[1].Hex(), "0x"+subAccount1)
|
||||
return
|
||||
}
|
||||
|
||||
// create sub-account 2, index automatically progresses
|
||||
subAccount2, subPubKey2, err := geth.CreateChildAccount("", testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create sub-account: %v", err)
|
||||
}
|
||||
if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 {
|
||||
t.Error("sub-account index auto-increament failed")
|
||||
return
|
||||
}
|
||||
|
||||
// finally, all 3 accounts should show up (main account, sub-accounts 1 and 2)
|
||||
accounts = les.StatusBackend.AccountManager().Accounts()
|
||||
if len(accounts) != 3 {
|
||||
t.Errorf("unexpected number of accounts: expected %d, got %d", 3, len(accounts))
|
||||
return
|
||||
}
|
||||
if string(accounts[0].Hex()) != "0x"+address {
|
||||
t.Errorf("main account is not retured as the first key: got %s, expected %s",
|
||||
accounts[0].Hex(), "0x"+address)
|
||||
return
|
||||
}
|
||||
subAccount1MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount1
|
||||
subAccount1MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount1
|
||||
if !subAccount1MatchesKey1 && !subAccount1MatchesKey2 {
|
||||
t.Errorf("subAcount1 not returned: got %s, expected %s", accounts[1].Hex(), "0x"+subAccount1)
|
||||
return
|
||||
}
|
||||
subAccount2MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount2
|
||||
subAccount2MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount2
|
||||
if !subAccount2MatchesKey1 && !subAccount2MatchesKey2 {
|
||||
t.Errorf("subAcount2 not returned: got %s, expected %s", accounts[2].Hex(), "0x"+subAccount1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsCreateChildAccount(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// to make sure that we start with empty account (which might get populated during previous tests)
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keyStore, err := geth.NodeManagerInstance().AccountKeyStore()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, pubKey, mnemonic, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||
|
||||
account, err := geth.ParseAccountString(address)
|
||||
if err != nil {
|
||||
t.Errorf("can not get account from address: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
|
||||
_, key, err := keyStore.AccountDecryptedKey(account, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if key.ExtendedKey == nil {
|
||||
t.Error("CKD#2 has not been generated for new account")
|
||||
return
|
||||
}
|
||||
|
||||
// try creating sub-account, w/o selecting main account i.e. w/o login to main account
|
||||
_, _, err = geth.CreateChildAccount("", testConfig.Account1.Password)
|
||||
if !reflect.DeepEqual(err, geth.ErrNoAccountSelected) {
|
||||
t.Errorf("expected error is not returned (tried to create sub-account w/o login): %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = geth.SelectAccount(address, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// try to create sub-account with wrong password
|
||||
_, _, err = geth.CreateChildAccount("", "wrong password")
|
||||
if !reflect.DeepEqual(err, errors.New("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", err)
|
||||
return
|
||||
}
|
||||
|
||||
// create sub-account (from implicit parent)
|
||||
subAccount1, subPubKey1, err := geth.CreateChildAccount("", testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create sub-account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that sub-account index automatically progresses
|
||||
subAccount2, subPubKey2, err := geth.CreateChildAccount("", testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create sub-account: %v", err)
|
||||
}
|
||||
if subAccount1 == subAccount2 || subPubKey1 == subPubKey2 {
|
||||
t.Error("sub-account index auto-increament failed")
|
||||
return
|
||||
}
|
||||
|
||||
// create sub-account (from explicit parent)
|
||||
subAccount3, subPubKey3, err := geth.CreateChildAccount(subAccount2, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("cannot create sub-account: %v", err)
|
||||
}
|
||||
if subAccount1 == subAccount3 || subPubKey1 == subPubKey3 || subAccount2 == subAccount3 || subPubKey2 == subPubKey3 {
|
||||
t.Error("sub-account index auto-increament failed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsRecoverAccount(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keyStore, _ := geth.NodeManagerInstance().AccountKeyStore()
|
||||
|
||||
// create an account
|
||||
address, pubKey, mnemonic, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||
|
||||
// try recovering using password + mnemonic
|
||||
addressCheck, pubKeyCheck, err := geth.RecoverAccount(testConfig.Account1.Password, mnemonic)
|
||||
if err != nil {
|
||||
t.Errorf("recover account failed: %v", err)
|
||||
return
|
||||
}
|
||||
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 := geth.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
|
||||
}
|
||||
extChild2String := key.ExtendedKey.String()
|
||||
|
||||
if err := keyStore.Delete(account, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot remove account: %v", err)
|
||||
}
|
||||
|
||||
addressCheck, pubKeyCheck, err = geth.RecoverAccount(testConfig.Account1.Password, mnemonic)
|
||||
if err != nil {
|
||||
t.Errorf("recover account failed (for non-cached account): %v", err)
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
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)
|
||||
addressCheck, pubKeyCheck, err = geth.RecoverAccount(testConfig.Account1.Password, mnemonic)
|
||||
if err != nil {
|
||||
t.Errorf("recover account failed (for non-cached account): %v", err)
|
||||
return
|
||||
}
|
||||
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 := geth.NodeManagerInstance().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 = geth.SelectAccount(addressCheck, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
if !whisperService.HasKeyPair(pubKeyCheck) {
|
||||
t.Errorf("identity not injected into whisper: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountSelect(t *testing.T) {
|
||||
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test to see if the account was injected in whisper
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create an account
|
||||
address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||
|
||||
address2, pubKey2, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Error("Test failed: could not create account")
|
||||
return
|
||||
}
|
||||
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
|
||||
err = geth.SelectAccount(address1, "wrongPassword")
|
||||
if err == nil {
|
||||
t.Error("select account is expected to throw error: wrong password used")
|
||||
return
|
||||
}
|
||||
err = geth.SelectAccount(address1, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
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")
|
||||
}
|
||||
err = geth.SelectAccount(address2, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsLogout(t *testing.T) {
|
||||
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create an account
|
||||
address, pubKey, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that identity doesn't exist (yet) in Whisper
|
||||
if whisperService.HasKeyPair(pubKey) {
|
||||
t.Error("identity already present in whisper")
|
||||
}
|
||||
|
||||
// select/login
|
||||
err = geth.SelectAccount(address, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
if !whisperService.HasKeyPair(pubKey) {
|
||||
t.Error("identity not injected into whisper")
|
||||
}
|
||||
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Errorf("cannot logout: %v", err)
|
||||
}
|
||||
|
||||
// now, logout and check if identity is removed indeed
|
||||
if whisperService.HasKeyPair(pubKey) {
|
||||
t.Error("identity not cleared from whisper")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// we need to make sure that selected account is injected as identity into Whisper
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create test accounts
|
||||
address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("account1 created: {address: %s, key: %s}", address1, pubKey1)
|
||||
address2, pubKey2, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("account2 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")
|
||||
}
|
||||
|
||||
// make sure that no account is selected by default
|
||||
if geth.NodeManagerInstance().SelectedAccount != nil {
|
||||
t.Error("account selected, but should not be")
|
||||
return
|
||||
}
|
||||
|
||||
// select account
|
||||
err = geth.SelectAccount(address1, "wrongPassword")
|
||||
if err == nil {
|
||||
t.Error("select account is expected to throw error: wrong password used")
|
||||
return
|
||||
}
|
||||
err = geth.SelectAccount(address1, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
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")
|
||||
}
|
||||
err = geth.SelectAccount(address2, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: could not select account: %v", err)
|
||||
return
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
// stop node (and all of its sub-protocols)
|
||||
if err := geth.NodeManagerInstance().StopNode(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure that account is still selected
|
||||
if geth.NodeManagerInstance().SelectedAccount == nil {
|
||||
t.Error("no selected account")
|
||||
return
|
||||
}
|
||||
if geth.NodeManagerInstance().SelectedAccount.Address.Hex() != "0x"+address2 {
|
||||
t.Error("incorrect address selected")
|
||||
return
|
||||
}
|
||||
|
||||
// resume node
|
||||
if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// re-check selected account (account2 MUST be selected)
|
||||
if geth.NodeManagerInstance().SelectedAccount == nil {
|
||||
t.Error("no selected account")
|
||||
return
|
||||
}
|
||||
if geth.NodeManagerInstance().SelectedAccount.Address.Hex() != "0x"+address2 {
|
||||
t.Error("incorrect address selected")
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that Whisper gets identity re-injected
|
||||
whisperService, err = geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
if !whisperService.HasKeyPair(pubKey2) {
|
||||
t.Errorf("identity not injected into whisper: %v", err)
|
||||
}
|
||||
if whisperService.HasKeyPair(pubKey1) {
|
||||
t.Error("identity should not be present, but it is still present in whisper")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// we need to make sure that selected account is injected as identity into Whisper
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
// create test accounts
|
||||
address1, pubKey1, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
t.Logf("account1 created: {address: %s, key: %s}", address1, pubKey1)
|
||||
|
||||
// make sure that identity is not present
|
||||
if whisperService.HasKeyPair(pubKey1) {
|
||||
t.Error("identity already present in whisper")
|
||||
}
|
||||
|
||||
// make sure that no account is selected
|
||||
if geth.NodeManagerInstance().SelectedAccount != nil {
|
||||
t.Error("account selected, but should not be")
|
||||
return
|
||||
}
|
||||
|
||||
// stop node (and all of its sub-protocols)
|
||||
if err := geth.NodeManagerInstance().StopNode(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure that no account is selected
|
||||
if geth.NodeManagerInstance().SelectedAccount != nil {
|
||||
t.Error("account selected, but should not be")
|
||||
return
|
||||
}
|
||||
|
||||
// resume node
|
||||
if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure that no account is selected
|
||||
if geth.NodeManagerInstance().SelectedAccount != nil {
|
||||
t.Error("account selected, but should not be")
|
||||
return
|
||||
}
|
||||
|
||||
// make sure that Whisper gets identity re-injected
|
||||
whisperService, err = geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
if whisperService.HasKeyPair(pubKey1) {
|
||||
t.Error("identity should not be present, but it is present in whisper")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
// StatusAPI provides API to access Status related functionality.
|
||||
type StatusAPI struct {
|
||||
b *StatusBackend
|
||||
}
|
||||
|
||||
// NewStatusAPI create a new StatusAPI instance
|
||||
func NewStatusAPI() *StatusAPI {
|
||||
return &StatusAPI{
|
||||
b: NewStatusBackend(),
|
||||
}
|
||||
}
|
||||
|
||||
// NodeManager returns reference to node manager
|
||||
func (api *StatusAPI) NodeManager() common.NodeManager {
|
||||
return api.b.NodeManager()
|
||||
}
|
||||
|
||||
// AccountManager returns reference to account manager
|
||||
func (api *StatusAPI) AccountManager() common.AccountManager {
|
||||
return api.b.AccountManager()
|
||||
}
|
||||
|
||||
// JailManager returns reference to jail
|
||||
func (api *StatusAPI) JailManager() common.JailManager {
|
||||
return api.b.JailManager()
|
||||
}
|
||||
|
||||
// StartNode start Status node, fails if node is already started
|
||||
func (api *StatusAPI) StartNode(config *params.NodeConfig) error {
|
||||
nodeStarted, err := api.b.StartNode(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
<-nodeStarted // do not return up until backend is ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartNodeNonBlocking start Status node, fails if node is already started
|
||||
// Returns immediately w/o waiting for node to start (relies on listening
|
||||
// for node.started signal)
|
||||
func (api *StatusAPI) StartNodeNonBlocking(config *params.NodeConfig) error {
|
||||
_, err := api.b.StartNode(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopNode stop Status node. Stopped node cannot be resumed.
|
||||
func (api *StatusAPI) StopNode() error {
|
||||
return api.b.StopNode()
|
||||
}
|
||||
|
||||
// RestartNode restart running Status node, fails if node is not running
|
||||
func (api *StatusAPI) RestartNode() error {
|
||||
nodeStarted, err := api.b.RestartNode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-nodeStarted // do not return up until backend is ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetChainData remove chain data from data directory.
|
||||
// Node is stopped, and new node is started, with clean data directory.
|
||||
func (api *StatusAPI) ResetChainData() error {
|
||||
nodeStarted, err := api.b.ResetChainData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-nodeStarted // do not return up until backend is ready
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopulateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
|
||||
func (api *StatusAPI) PopulateStaticPeers() error {
|
||||
return api.b.nodeManager.PopulateStaticPeers()
|
||||
}
|
||||
|
||||
// AddPeer adds new static peer node
|
||||
func (api *StatusAPI) AddPeer(url string) error {
|
||||
return api.b.nodeManager.AddPeer(url)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (api *StatusAPI) CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
return api.b.CreateAccount(password)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (api *StatusAPI) CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
return api.b.CreateChildAccount(parentAddress, password)
|
||||
}
|
||||
|
||||
// RecoverAccount re-creates master key using given details.
|
||||
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
||||
func (api *StatusAPI) RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||
return api.b.RecoverAccount(password, mnemonic)
|
||||
}
|
||||
|
||||
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
||||
// If no error is returned, then account is considered verified.
|
||||
func (api *StatusAPI) VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) {
|
||||
return api.b.VerifyAccountPassword(keyStoreDir, address, password)
|
||||
}
|
||||
|
||||
// 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).
|
||||
func (api *StatusAPI) SelectAccount(address, password string) error {
|
||||
return api.b.SelectAccount(address, password)
|
||||
}
|
||||
|
||||
// Logout clears whisper identities
|
||||
func (api *StatusAPI) Logout() error {
|
||||
return api.b.Logout()
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func (api *StatusAPI) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
|
||||
return api.b.CompleteTransaction(id, password)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func (api *StatusAPI) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
|
||||
return api.b.CompleteTransactions(ids, password)
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func (api *StatusAPI) DiscardTransaction(id string) error {
|
||||
return api.b.DiscardTransaction(id)
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func (api *StatusAPI) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
|
||||
return api.b.DiscardTransactions(ids)
|
||||
}
|
||||
|
||||
// Parse creates a new jail cell context, with the given chatID as identifier.
|
||||
// New context executes provided JavaScript code, right after the initialization.
|
||||
func (api *StatusAPI) JailParse(chatID string, js string) string {
|
||||
return api.b.jailManager.Parse(chatID, js)
|
||||
}
|
||||
|
||||
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
||||
// Jail cell is clonned before call is executed i.e. all calls execute w/i their own contexts.
|
||||
func (api *StatusAPI) JailCall(chatID string, path string, args string) string {
|
||||
return api.b.jailManager.Call(chatID, path, args)
|
||||
|
||||
}
|
||||
|
||||
// BaseJS allows to setup initial JavaScript to be loaded on each jail.Parse()
|
||||
func (api *StatusAPI) JailBaseJS(js string) {
|
||||
api.b.jailManager.BaseJS(js)
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/jail"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
// StatusBackend implements Status.im service
|
||||
type StatusBackend struct {
|
||||
nodeManager common.NodeManager
|
||||
accountManager common.AccountManager
|
||||
txQueueManager common.TxQueueManager
|
||||
jailManager common.JailManager
|
||||
}
|
||||
|
||||
// NewStatusBackend create a new NewStatusBackend instance
|
||||
func NewStatusBackend() *StatusBackend {
|
||||
defer log.Info("Status backend initialized")
|
||||
|
||||
nodeManager := node.NewNodeManager()
|
||||
accountManager := node.NewAccountManager(nodeManager)
|
||||
return &StatusBackend{
|
||||
nodeManager: nodeManager,
|
||||
accountManager: accountManager,
|
||||
txQueueManager: node.NewTxQueueManager(nodeManager, accountManager),
|
||||
jailManager: jail.New(nodeManager),
|
||||
}
|
||||
}
|
||||
|
||||
// NodeManager returns reference to node manager
|
||||
func (m *StatusBackend) NodeManager() common.NodeManager {
|
||||
return m.nodeManager
|
||||
}
|
||||
|
||||
// AccountManager returns reference to account manager
|
||||
func (m *StatusBackend) AccountManager() common.AccountManager {
|
||||
return m.accountManager
|
||||
}
|
||||
|
||||
// JailManager returns reference to jail
|
||||
func (m *StatusBackend) JailManager() common.JailManager {
|
||||
return m.jailManager
|
||||
}
|
||||
|
||||
// IsNodeRunning confirm that node is running
|
||||
func (m *StatusBackend) IsNodeRunning() bool {
|
||||
return m.nodeManager.IsNodeRunning()
|
||||
}
|
||||
|
||||
// StartNode start Status node, fails if node is already started
|
||||
func (m *StatusBackend) StartNode(config *params.NodeConfig) (<-chan struct{}, error) {
|
||||
backendReady := make(chan struct{})
|
||||
nodeStarted, err := m.nodeManager.StartNode(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go m.onNodeStart(backendReady, nodeStarted)
|
||||
return backendReady, err
|
||||
}
|
||||
|
||||
func (m *StatusBackend) onNodeStart(backendReady chan struct{}, nodeStarted <-chan struct{}) {
|
||||
defer close(backendReady)
|
||||
<-nodeStarted
|
||||
|
||||
if err := m.registerHandlers(); err != nil {
|
||||
log.Error("Handler registration failed", "err", err)
|
||||
}
|
||||
|
||||
m.accountManager.ReSelectAccount()
|
||||
}
|
||||
|
||||
// RestartNode restart running Status node, fails if node is not running
|
||||
func (m *StatusBackend) RestartNode() (<-chan struct{}, error) {
|
||||
backendReady := make(chan struct{})
|
||||
nodeRestarted, err := m.nodeManager.RestartNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go m.onNodeStart(backendReady, nodeRestarted)
|
||||
return backendReady, err
|
||||
}
|
||||
|
||||
// StopNode stop Status node. Stopped node cannot be resumed.
|
||||
func (m *StatusBackend) StopNode() error {
|
||||
return m.nodeManager.StopNode()
|
||||
}
|
||||
|
||||
// ResetChainData remove chain data from data directory.
|
||||
// Node is stopped, and new node is started, with clean data directory.
|
||||
func (m *StatusBackend) ResetChainData() (<-chan struct{}, error) {
|
||||
backendReady := make(chan struct{})
|
||||
nodeRestarted, err := m.nodeManager.ResetChainData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go m.onNodeStart(backendReady, nodeRestarted)
|
||||
return backendReady, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (m *StatusBackend) CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
return m.accountManager.CreateAccount(password)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *StatusBackend) CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
return m.accountManager.CreateChildAccount(parentAddress, password)
|
||||
}
|
||||
|
||||
// RecoverAccount re-creates master key using given details.
|
||||
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
||||
func (m *StatusBackend) RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||
return m.accountManager.RecoverAccount(password, mnemonic)
|
||||
}
|
||||
|
||||
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
||||
// If no error is returned, then account is considered verified.
|
||||
func (m *StatusBackend) VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) {
|
||||
return m.accountManager.VerifyAccountPassword(keyStoreDir, address, password)
|
||||
}
|
||||
|
||||
// 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).
|
||||
func (m *StatusBackend) SelectAccount(address, password string) error {
|
||||
return m.accountManager.SelectAccount(address, password)
|
||||
}
|
||||
|
||||
// ReSelectAccount selects previously selected account, often, after node restart.
|
||||
func (m *StatusBackend) ReSelectAccount() error {
|
||||
return m.accountManager.ReSelectAccount()
|
||||
}
|
||||
|
||||
// Logout clears whisper identities
|
||||
func (m *StatusBackend) Logout() error {
|
||||
return m.accountManager.Logout()
|
||||
}
|
||||
|
||||
// SelectedAccount returns currently selected account
|
||||
func (m *StatusBackend) SelectedAccount() (*common.SelectedExtKey, error) {
|
||||
return m.accountManager.SelectedAccount()
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func (m *StatusBackend) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
|
||||
return m.txQueueManager.CompleteTransaction(id, password)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func (m *StatusBackend) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
|
||||
return m.txQueueManager.CompleteTransactions(ids, password)
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func (m *StatusBackend) DiscardTransaction(id string) error {
|
||||
return m.txQueueManager.DiscardTransaction(id)
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func (m *StatusBackend) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
|
||||
return m.txQueueManager.DiscardTransactions(ids)
|
||||
}
|
||||
|
||||
// registerHandlers attaches Status callback handlers to running node
|
||||
func (m *StatusBackend) registerHandlers() error {
|
||||
runningNode, err := m.nodeManager.Node()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var lightEthereum *les.LightEthereum
|
||||
if err := runningNode.Service(&lightEthereum); err != nil {
|
||||
log.Error("Cannot get light ethereum service", "error", err)
|
||||
}
|
||||
|
||||
lightEthereum.StatusBackend.SetAccountsFilterHandler(m.accountManager.AccountsListRequestHandler())
|
||||
log.Info("Registered handler", "fn", "AccountsFilterHandler")
|
||||
|
||||
lightEthereum.StatusBackend.SetTransactionQueueHandler(m.txQueueManager.TransactionQueueHandler())
|
||||
log.Info("Registered handler", "fn", "TransactionQueueHandler")
|
||||
|
||||
lightEthereum.StatusBackend.SetTransactionReturnHandler(m.txQueueManager.TransactionReturnHandler())
|
||||
log.Info("Registered handler", "fn", "TransactionReturnHandler")
|
||||
|
||||
m.ReSelectAccount()
|
||||
log.Info("Account reselected")
|
||||
|
||||
node.SendSignal(node.SignalEnvelope{
|
||||
Type: node.EventNodeReady,
|
||||
Event: struct{}{},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,335 @@
|
|||
package api_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"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)
|
||||
|
||||
var lesService *les.LightEthereum
|
||||
require.NoError(runningNode.Service(&lesService))
|
||||
require.NotNil(lesService)
|
||||
|
||||
accounts := lesService.StatusBackend.AccountManager().Accounts()
|
||||
for _, acc := range accounts {
|
||||
fmt.Println(acc.Hex())
|
||||
}
|
||||
|
||||
// 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.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
|
||||
// ensure that there is still no accounts returned
|
||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
||||
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.SelectAccount(address, TestConfig.Account1.Password)
|
||||
require.NoError(err, "account selection failed")
|
||||
|
||||
// at this point main account should show up
|
||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
||||
require.Equal(1, len(accounts), "exactly single account is expected (main account)")
|
||||
require.Equal(string(accounts[0].Hex()), "0x"+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.CreateChildAccount("", TestConfig.Account1.Password)
|
||||
require.NoError(err, "cannot create sub-account")
|
||||
|
||||
// now we expect to see both main account and sub-account 1
|
||||
accounts = lesService.StatusBackend.AccountManager().Accounts()
|
||||
require.Equal(2, len(accounts), "exactly 2 accounts are expected (main + sub-account 1)")
|
||||
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
||||
require.Equal(string(accounts[1].Hex()), "0x"+subAccount1, "subAcount1 not returned")
|
||||
|
||||
// create sub-account 2, index automatically progresses
|
||||
subAccount2, subPubKey2, err := s.backend.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 = lesService.StatusBackend.AccountManager().Accounts()
|
||||
require.Equal(3, len(accounts), "unexpected number of accounts")
|
||||
require.Equal(string(accounts[0].Hex()), "0x"+address, "main account is not retured as the first key")
|
||||
|
||||
subAccount1MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount1
|
||||
subAccount1MatchesKey2 := string(accounts[2].Hex()) != "0x"+subAccount1
|
||||
require.False(!subAccount1MatchesKey1 && !subAccount1MatchesKey2, "subAcount1 not returned")
|
||||
|
||||
subAccount2MatchesKey1 := string(accounts[1].Hex()) != "0x"+subAccount2
|
||||
subAccount2MatchesKey2 := string(accounts[2].Hex()) != "0x"+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.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
s.T().Logf("Account created: {address: %s, key: %s, mnemonic:%s}", address, pubKey, mnemonic)
|
||||
|
||||
account, 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(account, 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.CreateChildAccount("", TestConfig.Account1.Password)
|
||||
require.EqualError(node.ErrNoAccountSelected, err.Error(), "expected error is not returned (tried to create sub-account w/o login)")
|
||||
|
||||
err = s.backend.SelectAccount(address, TestConfig.Account1.Password)
|
||||
require.NoError(err, "cannot select account")
|
||||
|
||||
// try to create sub-account with wrong password
|
||||
_, _, err = s.backend.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.CreateChildAccount("", TestConfig.Account1.Password)
|
||||
require.NoError(err, "cannot create sub-account")
|
||||
|
||||
// make sure that sub-account index automatically progresses
|
||||
subAccount2, subPubKey2, err := s.backend.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.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.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.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.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.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.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.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
s.T().Logf("Account created: {address: %s, key: %s}", address1, pubKey1)
|
||||
|
||||
address2, pubKey2, _, err := s.backend.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.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.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.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.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.SelectAccount(address, TestConfig.Account1.Password))
|
||||
require.True(whisperService.HasKeyPair(pubKey), "identity not injected into whisper")
|
||||
|
||||
require.NoError(s.backend.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.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
address2, pubKey2, _, err := s.backend.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.SelectedAccount()
|
||||
require.EqualError(node.ErrNoAccountSelected, err.Error(), "account selected, but should not be")
|
||||
require.Nil(selectedAccount)
|
||||
|
||||
// select account
|
||||
err = s.backend.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.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.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.NodeManager.NodeConfig()
|
||||
require.NoError(err)
|
||||
require.NotNil(nodeConfig)
|
||||
preservedNodeConfig := *nodeConfig
|
||||
require.NoError(s.NodeManager.StopNode())
|
||||
|
||||
// make sure that account is still selected
|
||||
selectedAccount, err = s.backend.SelectedAccount()
|
||||
require.NoError(err)
|
||||
require.NotNil(selectedAccount)
|
||||
require.Equal(selectedAccount.Address.Hex(), "0x"+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.SelectedAccount()
|
||||
require.NoError(err)
|
||||
require.NotNil(selectedAccount)
|
||||
require.Equal(selectedAccount.Address.Hex(), "0x"+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.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.SelectedAccount()
|
||||
require.EqualError(node.ErrNoAccountSelected, err.Error())
|
||||
require.Nil(selectedAccount)
|
||||
}
|
|
@ -0,0 +1,741 @@
|
|||
package api_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"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/testing"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
const (
|
||||
whisperMessage1 = `test message 1 (K1 -> K2, signed+encrypted, from us)`
|
||||
whisperMessage2 = `test message 2 (K1 -> K1, signed+encrypted to ourselves)`
|
||||
whisperMessage3 = `test message 3 (K1 -> "", signed broadcast)`
|
||||
whisperMessage4 = `test message 4 ("" -> "", anon broadcast)`
|
||||
whisperMessage5 = `test message 5 ("" -> K1, encrypted anon broadcast)`
|
||||
whisperMessage6 = `test message 6 (K2 -> K1, signed+encrypted, to us)`
|
||||
txSendFolder = "testdata/jail/tx-send/"
|
||||
testChatID = "testChat"
|
||||
)
|
||||
|
||||
func (s *BackendTestSuite) TestJailContractDeployment() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
jailInstance := s.backend.JailManager()
|
||||
require.NotNil(jailInstance)
|
||||
|
||||
jailInstance.Parse(testChatID, "")
|
||||
|
||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||
vm, err := jailInstance.JailCellVM(testChatID)
|
||||
require.NoError(err)
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
common.PanicAfter(30*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash gethcommon.Hash
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
//t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
s.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
var err error
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
||||
_, err = vm.Run(`
|
||||
var responseValue = 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){
|
||||
if (!e) {
|
||||
responseValue = contract.transactionHash
|
||||
}
|
||||
})
|
||||
`)
|
||||
require.NoError(err)
|
||||
|
||||
<-completeQueuedTransaction
|
||||
|
||||
responseValue, err := vm.Get("responseValue")
|
||||
require.NoError(err, "vm.Get() failed")
|
||||
|
||||
response, err := responseValue.ToString()
|
||||
require.NoError(err, "cannot parse result")
|
||||
|
||||
expectedResponse := txHash.Hex()
|
||||
require.Equal(expectedResponse, response, "expected response is not returned")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestJailSendQueuedTransaction() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
jailInstance := s.backend.JailManager()
|
||||
require.NotNil(jailInstance)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
txParams := `{
|
||||
"from": "` + TestConfig.Account1.Address + `",
|
||||
"to": "0xf82da7547534045b4e00442bc89e16186cf8c272",
|
||||
"value": "0.000001"
|
||||
}`
|
||||
|
||||
txCompletedSuccessfully := make(chan struct{})
|
||||
txCompletedCounter := make(chan struct{})
|
||||
txHashes := make(chan gethcommon.Hash)
|
||||
|
||||
// replace transaction notification handler
|
||||
requireMessageId := false
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
messageId, ok := event["message_id"].(string)
|
||||
s.True(ok, "Message id is required, but not found")
|
||||
if requireMessageId {
|
||||
if len(messageId) == 0 {
|
||||
s.Fail("Message id is required, but not provided")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if len(messageId) != 0 {
|
||||
s.Fail("Message id is not required, but provided")
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Info("Transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
var txHash gethcommon.Hash
|
||||
if txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password); err != nil {
|
||||
s.Fail(fmt.Sprintf("cannot complete queued transaction[%v]: %v", event["id"], err))
|
||||
} else {
|
||||
log.Info("Transaction complete", "URL", "https://rinkeby.etherscan.io/tx/%s"+txHash.Hex())
|
||||
}
|
||||
|
||||
txCompletedSuccessfully <- struct{}{} // so that timeout is aborted
|
||||
txHashes <- txHash
|
||||
txCompletedCounter <- struct{}{}
|
||||
}
|
||||
})
|
||||
|
||||
type testCommand struct {
|
||||
command string
|
||||
params string
|
||||
expectedResponse string
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
file string
|
||||
requireMessageId bool
|
||||
commands []testCommand
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
// no context or message id
|
||||
name: "Case 1: no message id or context in inited JS",
|
||||
file: "no-message-id-or-context.js",
|
||||
requireMessageId: false,
|
||||
commands: []testCommand{
|
||||
{
|
||||
`["commands", "send"]`,
|
||||
txParams,
|
||||
`{"result": {"transaction-hash":"TX_HASH"}}`,
|
||||
},
|
||||
{
|
||||
`["commands", "getBalance"]`,
|
||||
`{"address": "` + TestConfig.Account1.Address + `"}`,
|
||||
`{"result": {"balance":42}}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// context is present in inited JS (but no message id is there)
|
||||
name: "Case 2: context is present in inited JS (but no message id is there)",
|
||||
file: "context-no-message-id.js",
|
||||
requireMessageId: false,
|
||||
commands: []testCommand{
|
||||
{
|
||||
`["commands", "send"]`,
|
||||
txParams,
|
||||
`{"result": {"context":{"` + jail.SendTransactionRequest + `":true},"result":{"transaction-hash":"TX_HASH"}}}`,
|
||||
},
|
||||
{
|
||||
`["commands", "getBalance"]`,
|
||||
`{"address": "` + TestConfig.Account1.Address + `"}`,
|
||||
`{"result": {"context":{},"result":{"balance":42}}}`, // note empty (but present) context!
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// message id is present in inited JS, but no context is there
|
||||
name: "Case 3: message id is present, context is not present",
|
||||
file: "message-id-no-context.js",
|
||||
requireMessageId: true,
|
||||
commands: []testCommand{
|
||||
{
|
||||
`["commands", "send"]`,
|
||||
txParams,
|
||||
`{"result": {"transaction-hash":"TX_HASH"}}`,
|
||||
},
|
||||
{
|
||||
`["commands", "getBalance"]`,
|
||||
`{"address": "` + TestConfig.Account1.Address + `"}`,
|
||||
`{"result": {"balance":42}}`, // note empty context!
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// both message id and context are present in inited JS (this UC is what we normally expect to see)
|
||||
name: "Case 4: both message id and context are present",
|
||||
file: "tx-send.js",
|
||||
requireMessageId: true,
|
||||
commands: []testCommand{
|
||||
{
|
||||
`["commands", "send"]`,
|
||||
txParams,
|
||||
`{"result": {"context":{"eth_sendTransaction":true,"message_id":"foobar"},"result":{"transaction-hash":"TX_HASH"}}}`,
|
||||
},
|
||||
{
|
||||
`["commands", "getBalance"]`,
|
||||
`{"address": "` + TestConfig.Account1.Address + `"}`,
|
||||
`{"result": {"context":{"message_id":"42"},"result":{"balance":42}}}`, // message id in context, but default one is used!
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
jailInstance.BaseJS(string(static.MustAsset(txSendFolder + test.file)))
|
||||
common.PanicAfter(60*time.Second, txCompletedSuccessfully, test.name)
|
||||
jailInstance.Parse(testChatID, ``)
|
||||
|
||||
requireMessageId = test.requireMessageId
|
||||
|
||||
for _, command := range test.commands {
|
||||
go func(jail common.JailManager, test testCase, command testCommand) {
|
||||
log.Info(fmt.Sprintf("->%s: %s", test.name, command.command))
|
||||
response := jail.Call(testChatID, command.command, command.params)
|
||||
var txHash gethcommon.Hash
|
||||
if command.command == `["commands", "send"]` {
|
||||
txHash = <-txHashes
|
||||
}
|
||||
expectedResponse := strings.Replace(command.expectedResponse, "TX_HASH", txHash.Hex(), 1)
|
||||
s.Equal(expectedResponse, response)
|
||||
}(jailInstance, test, command)
|
||||
}
|
||||
<-txCompletedCounter
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
func (s *BackendTestSuite) TestGasEstimation() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
jailInstance := s.backend.JailManager()
|
||||
require.NotNil(jailInstance)
|
||||
jailInstance.Parse(testChatID, "")
|
||||
|
||||
// obtain VM for a given chat (to send custom JS to jailed version of Send())
|
||||
vm, err := jailInstance.JailCellVM(testChatID)
|
||||
require.NoError(err)
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
common.PanicAfter(30*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash gethcommon.Hash
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
//t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
s.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
var err error
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
||||
_, err = vm.Run(`
|
||||
var responseValue = 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',
|
||||
}, function (e, contract){
|
||||
if (!e) {
|
||||
responseValue = contract.transactionHash
|
||||
}
|
||||
})
|
||||
`)
|
||||
s.NoError(err)
|
||||
|
||||
<-completeQueuedTransaction
|
||||
|
||||
responseValue, err := vm.Get("responseValue")
|
||||
s.NoError(err, "vm.Get() failed")
|
||||
|
||||
response, err := responseValue.ToString()
|
||||
s.NoError(err, "cannot parse result")
|
||||
|
||||
expectedResponse := txHash.Hex()
|
||||
s.Equal(expectedResponse, response, "expected response is not returned")
|
||||
s.T().Logf("estimation complete: %s", 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)
|
||||
|
||||
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, err := whisperAPI.HasKeyPair(accountKey1Hex); err != nil || !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, err := whisperAPI.HasKeyPair(accountKey2Hex); err != nil || !ok {
|
||||
require.FailNow(fmt.Sprintf("identity not injected: %v", accountKey2Hex))
|
||||
}
|
||||
|
||||
passedTests := map[string]bool{
|
||||
whisperMessage1: false,
|
||||
whisperMessage2: false,
|
||||
whisperMessage3: false,
|
||||
whisperMessage4: false,
|
||||
whisperMessage5: false,
|
||||
whisperMessage6: false,
|
||||
}
|
||||
installedFilters := map[string]string{
|
||||
whisperMessage1: "",
|
||||
whisperMessage2: "",
|
||||
whisperMessage3: "",
|
||||
whisperMessage4: "",
|
||||
whisperMessage5: "",
|
||||
whisperMessage6: "",
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
testCode string
|
||||
useFilter bool
|
||||
}{
|
||||
{
|
||||
"test 0: ensure correct version of Whisper is used",
|
||||
`
|
||||
var expectedVersion = '0x5';
|
||||
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 (!web3.shh.hasKeyPair(identity1)) {
|
||||
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var identity2 = '` + accountKey2Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity2)) {
|
||||
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage1 + `';
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "asym",
|
||||
sig: identity1,
|
||||
key: identity2,
|
||||
topics: [topic]
|
||||
});
|
||||
console.log(JSON.stringify(filter));
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "asym",
|
||||
sig: identity1,
|
||||
key: identity2,
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20,
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + message;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage1 + `';
|
||||
var filterId = filter.filterId;
|
||||
if (!filterId) {
|
||||
throw 'filter not installed properly';
|
||||
}
|
||||
`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test 2: encrypted signed message to yourself (From != nil && To != nil)",
|
||||
`
|
||||
var identity = '` + accountKey1Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity)) {
|
||||
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage2 + `';
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "asym",
|
||||
sig: identity,
|
||||
key: identity,
|
||||
topics: [topic],
|
||||
});
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "asym",
|
||||
sig: identity,
|
||||
key: identity,
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20,
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + message;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage2 + `';
|
||||
var filterId = filter.filterId;
|
||||
if (!filterId) {
|
||||
throw 'filter not installed properly';
|
||||
}
|
||||
`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test 3: signed (known sender) broadcast (From != nil && To == nil)",
|
||||
`
|
||||
var identity = '` + accountKey1Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity)) {
|
||||
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage3 + `';
|
||||
|
||||
// generate symmetric key
|
||||
var keyid = shh.generateSymmetricKey();
|
||||
if (!shh.hasSymmetricKey(keyid)) {
|
||||
throw new Error('key not found');
|
||||
}
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "sym",
|
||||
sig: identity,
|
||||
topics: [topic],
|
||||
key: keyid
|
||||
});
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "sym",
|
||||
sig: identity,
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20,
|
||||
key: keyid
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + message;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage3 + `';
|
||||
var filterId = filter.filterId;
|
||||
if (!filterId) {
|
||||
throw 'filter not installed properly';
|
||||
}
|
||||
`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test 4: anonymous broadcast (From == nil && To == nil)",
|
||||
`
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage4 + `';
|
||||
|
||||
// generate symmetric key
|
||||
var keyid = shh.generateSymmetricKey();
|
||||
if (!shh.hasSymmetricKey(keyid)) {
|
||||
throw new Error('key not found');
|
||||
}
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "sym",
|
||||
topics: [topic],
|
||||
key: keyid
|
||||
});
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "sym",
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20,
|
||||
key: keyid
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + err;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage4 + `';
|
||||
var filterId = filter.filterId;
|
||||
if (!filterId) {
|
||||
throw 'filter not installed properly';
|
||||
}
|
||||
`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test 5: encrypted anonymous message (From == nil && To != nil)",
|
||||
`
|
||||
var identity = '` + accountKey2Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity)) {
|
||||
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage5 + `';
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "asym",
|
||||
key: identity,
|
||||
topics: [topic],
|
||||
});
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "asym",
|
||||
key: identity,
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + message;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage5 + `';
|
||||
var filterId = filter.filterId;
|
||||
if (!filterId) {
|
||||
throw 'filter not installed properly';
|
||||
}
|
||||
`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"test 6: encrypted signed response to us (From != nil && To != nil)",
|
||||
`
|
||||
var identity1 = '` + accountKey1Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity1)) {
|
||||
throw 'idenitity "` + accountKey1Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var identity2 = '` + accountKey2Hex + `';
|
||||
if (!web3.shh.hasKeyPair(identity2)) {
|
||||
throw 'idenitity "` + accountKey2Hex + `" not found in whisper';
|
||||
}
|
||||
|
||||
var topic = makeTopic();
|
||||
var payload = '` + whisperMessage6 + `';
|
||||
|
||||
// start watching for messages
|
||||
var filter = shh.filter({
|
||||
type: "asym",
|
||||
sig: identity2,
|
||||
key: identity1,
|
||||
topics: [topic]
|
||||
});
|
||||
|
||||
// post message
|
||||
var message = {
|
||||
type: "asym",
|
||||
sig: identity2,
|
||||
key: identity1,
|
||||
topic: topic,
|
||||
payload: payload,
|
||||
ttl: 20
|
||||
};
|
||||
var err = shh.post(message)
|
||||
if (err !== null) {
|
||||
throw 'message not sent: ' + message;
|
||||
}
|
||||
|
||||
var filterName = '` + whisperMessage6 + `';
|
||||
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;
|
||||
var makeTopic = function () {
|
||||
var min = 1;
|
||||
var max = Math.pow(16, 8);
|
||||
var randInt = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
return web3.toHex(randInt);
|
||||
};
|
||||
`)
|
||||
vm, err := jailInstance.JailCellVM(testCaseKey)
|
||||
require.NoError(err, "cannot get VM")
|
||||
|
||||
// post messages
|
||||
if _, err := vm.Run(testCase.testCode); err != nil {
|
||||
require.Fail(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !testCase.useFilter {
|
||||
continue
|
||||
}
|
||||
|
||||
// update installed filters
|
||||
filterId, err := vm.Get("filterId")
|
||||
require.NoError(err, "cannot get filterId")
|
||||
|
||||
filterName, err := vm.Get("filterName")
|
||||
require.NoError(err, "cannot get filterName")
|
||||
|
||||
if _, ok := installedFilters[filterName.String()]; !ok {
|
||||
require.FailNow("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)
|
||||
for _, message := range whisperAPI.GetNewSubscriptionMessages(filter) {
|
||||
s.T().Logf("message found: %s", gethcommon.FromHex(message.Payload))
|
||||
passedTests[testKey] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for testName, passedTest := range passedTests {
|
||||
if !passedTest {
|
||||
s.Fail(fmt.Sprintf("test not passed: %v", testName))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
package api_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestBackendTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(BackendTestSuite))
|
||||
}
|
||||
|
||||
type BackendTestSuite struct {
|
||||
BaseTestSuite
|
||||
backend *api.StatusBackend
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) SetupTest() {
|
||||
require := s.Require()
|
||||
backend := api.NewStatusBackend()
|
||||
require.NotNil(backend)
|
||||
require.IsType(&api.StatusBackend{}, backend)
|
||||
s.backend = backend
|
||||
s.NodeManager = backend.NodeManager()
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) StartTestBackend(networkID int) {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
nodeConfig, err := MakeTestNodeConfig(networkID)
|
||||
require.NoError(err)
|
||||
|
||||
// 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())
|
||||
require.NoError(s.backend.StopNode())
|
||||
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) RestartTestNode() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
require.True(s.backend.IsNodeRunning())
|
||||
nodeRestarted, err := s.backend.RestartNode()
|
||||
require.NoError(err)
|
||||
require.NotNil(nodeRestarted)
|
||||
require.True(s.backend.IsNodeRunning())
|
||||
<-nodeRestarted
|
||||
}
|
||||
|
||||
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())
|
||||
err = s.backend.StopNode()
|
||||
if s.Error(err) {
|
||||
require.IsType(node.ErrNoRunningNode, 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())
|
||||
|
||||
// try starting another node (w/o stopping the previously started node)
|
||||
_, err = s.backend.StartNode(nodeConfig)
|
||||
if s.Error(err) {
|
||||
require.IsType(node.ErrNodeAlreadyExists, err)
|
||||
}
|
||||
|
||||
// now stop node, and make sure that a new node, on different network can be started
|
||||
err = s.backend.StopNode()
|
||||
require.NoError(err)
|
||||
|
||||
// start new node with exactly the same config
|
||||
require.False(s.backend.IsNodeRunning())
|
||||
nodeStarted, err = s.backend.StartNode(nodeConfig)
|
||||
require.NoError(err)
|
||||
defer s.StopTestNode()
|
||||
|
||||
<-nodeStarted
|
||||
require.True(s.backend.IsNodeRunning())
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
s.FirstBlockHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||
|
||||
// now stop node, and make sure that a new node, on different network can be started
|
||||
err = s.backend.StopNode()
|
||||
require.NoError(err)
|
||||
|
||||
// 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
|
||||
s.FirstBlockHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
|
||||
require.NoError(s.backend.StopNode())
|
||||
}
|
||||
|
||||
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)
|
||||
s.FirstBlockHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestRestartNode() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RinkebyNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
s.FirstBlockHash("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)
|
||||
s.FirstBlockHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
}
|
|
@ -0,0 +1,663 @@
|
|||
package api_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
)
|
||||
|
||||
func (s *BackendTestSuite) TestSendContractTx() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// create an account
|
||||
sampleAddress, _, _, err := s.backend.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 10)
|
||||
common.PanicAfter(20*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = gethcommon.Hash{}
|
||||
var txHashCheck = gethcommon.Hash{}
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
// 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")
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.EqualError(err, node.ErrNoAccountSelected.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
|
||||
|
||||
// the second call will also fail (we are logged in as different user)
|
||||
log.Info("trying to complete with invalid user")
|
||||
err = s.backend.SelectAccount(sampleAddress, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.EqualError(err, status.ErrInvalidCompleteTxSender.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
|
||||
|
||||
// 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 suceed")
|
||||
s.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
|
||||
require.NoError(err)
|
||||
|
||||
// send transaction
|
||||
txHashCheck, err = backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: nil, // marker, contract creation is expected
|
||||
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), gethcommon.Ether)),
|
||||
Gas: (*hexutil.Big)(big.NewInt(params.DefaultGas)),
|
||||
Data: byteCode,
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
<-completeQueuedTransaction
|
||||
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.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestSendEtherTx() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// create an account
|
||||
sampleAddress, _, _, err := s.backend.CreateAccount(TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
common.PanicAfter(20*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = gethcommon.Hash{}
|
||||
var txHashCheck = gethcommon.Hash{}
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction queued (will be completed shortly)", "id", event["id"].(string))
|
||||
|
||||
// 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")
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.EqualError(err, node.ErrNoAccountSelected.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
|
||||
|
||||
// the second call will also fail (we are logged in as different user)
|
||||
log.Info("trying to complete with invalid user")
|
||||
err = s.backend.SelectAccount(sampleAddress, TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.EqualError(err, status.ErrInvalidCompleteTxSender.Error(),
|
||||
fmt.Sprintf("expected error on queued transaction[%v] not thrown", event["id"]))
|
||||
|
||||
// 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 suceed")
|
||||
s.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.NoError(err, fmt.Sprintf("cannot complete queued transaction[%v]", event["id"]))
|
||||
|
||||
log.Info("contract transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHashCheck, err = backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
<-completeQueuedTransaction
|
||||
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.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestDoubleCompleteQueuedTransactions() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
common.PanicAfter(20*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := false
|
||||
txHash := gethcommon.Hash{}
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
log.Info("transaction queued (will be failed and completed on the second call)", "id", txID)
|
||||
|
||||
// try with wrong 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")
|
||||
s.EqualError(err, keystore.ErrDecrypt.Error())
|
||||
|
||||
s.Equal(1, backend.TransactionQueue().Count(), "txqueue cannot be empty, as tx has failed")
|
||||
|
||||
// now try to complete transaction, but with the correct password
|
||||
txHash, err = s.backend.CompleteTransaction(event["id"].(string), TestConfig.Account1.Password)
|
||||
s.NoError(err)
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
s.Equal(0, backend.TransactionQueue().Count(), "txqueue must be empty, as tx has completed")
|
||||
|
||||
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/"+txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == node.EventTransactionFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := "could not decrypt key with given passphrase"
|
||||
s.Equal(receivedErrMessage, expectedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("2", receivedErrCode)
|
||||
|
||||
txFailedEventCalled = true
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
|
||||
<-completeQueuedTransaction
|
||||
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.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
|
||||
s.True(txFailedEventCalled, "expected tx failure signal is not received")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestDiscardQueuedTransaction() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
common.PanicAfter(20*time.Second, completeQueuedTransaction, s.T().Name())
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := false
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
||||
s.True(backend.TransactionQueue().Has(status.QueuedTxID(txID)), "txqueue should still have test tx")
|
||||
|
||||
// discard
|
||||
err := s.backend.DiscardTransaction(txID)
|
||||
s.NoError(err, "cannot discard tx")
|
||||
|
||||
// try completing discarded transaction
|
||||
_, err = s.backend.CompleteTransaction(txID, TestConfig.Account1.Password)
|
||||
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
|
||||
s.False(backend.TransactionQueue().Has(status.QueuedTxID(txID)),
|
||||
fmt.Sprintf("txqueue should not have test tx at this point (it should be discarded): %s", txID))
|
||||
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
|
||||
if envelope.Type == node.EventTransactionFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
|
||||
s.Equal(receivedErrMessage, expectedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("4", receivedErrCode)
|
||||
|
||||
txFailedEventCalled = true
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.EqualError(err, status.ErrQueuedTxDiscarded.Error(), "transaction is expected to be discarded")
|
||||
|
||||
<-completeQueuedTransaction
|
||||
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
|
||||
s.Zero(backend.TransactionQueue().Count(), "tx queue must be empty at this point")
|
||||
s.True(txFailedEventCalled, "expected tx failure signal is not received")
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestCompleteMultipleQueuedTransactions() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// 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
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err, fmt.Sprintf("cannot unmarshal JSON: %s", jsonEvent))
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
log.Info("transaction queued (will be completed in a single call, once aggregated)", "id", txID)
|
||||
|
||||
txIDs <- txID
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return when DiscardQueuedTransaction() for a given tx id is called
|
||||
sendTx := func() {
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.NoError(err, "cannot send transaction")
|
||||
s.False(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned empty hash")
|
||||
}
|
||||
|
||||
// wait for transactions, and complete them in a single call
|
||||
completeTxs := func(txIDStrings string) {
|
||||
var parsedIDs []string
|
||||
err := json.Unmarshal([]byte(txIDStrings), &parsedIDs)
|
||||
s.NoError(err)
|
||||
|
||||
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
||||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// complete
|
||||
results := s.backend.CompleteTransactions(string(updatedTxIDStrings), TestConfig.Account1.Password)
|
||||
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
||||
s.Fail(fmt.Sprintf("cannot complete txs: %v", results))
|
||||
return
|
||||
}
|
||||
for txID, txResult := range results {
|
||||
if txResult.Error != nil && txID != "invalid-tx-id" {
|
||||
s.Fail(fmt.Sprintf("invalid error for %s", txID))
|
||||
return
|
||||
}
|
||||
if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id" {
|
||||
s.Fail(fmt.Sprintf("invalid hash (expected non empty hash): %s", txID))
|
||||
return
|
||||
}
|
||||
|
||||
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
log.Info("transaction complete", "URL", "https://rinkeby.etherscan.io/tx/%s"+txResult.Hash.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
s.False(backend.TransactionQueue().Has(status.QueuedTxID(txID)),
|
||||
"txqueue should not have test tx at this point (it should be completed)")
|
||||
}
|
||||
}
|
||||
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):
|
||||
s.Fail("test timed out")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
s.Fail("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestDiscardMultipleQueuedTransactions() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
time.Sleep(TestConfig.Node.SyncSeconds * time.Second) // allow to sync
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// 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
|
||||
node.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope node.SignalEnvelope
|
||||
err := json.Unmarshal([]byte(jsonEvent), &envelope)
|
||||
s.NoError(err)
|
||||
if envelope.Type == node.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
log.Info("transaction queued (will be discarded soon)", "id", txID)
|
||||
|
||||
s.True(backend.TransactionQueue().Has(status.QueuedTxID(txID)), "txqueue should still have test tx")
|
||||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == node.EventTransactionFailed {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
log.Info("transaction return event received", "id", event["id"].(string))
|
||||
|
||||
receivedErrMessage := event["error_message"].(string)
|
||||
expectedErrMessage := status.ErrQueuedTxDiscarded.Error()
|
||||
s.Equal(receivedErrMessage, expectedErrMessage)
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
s.Equal("4", receivedErrCode)
|
||||
|
||||
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 := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: common.FromAddress(TestConfig.Account1.Address),
|
||||
To: common.ToAddress(TestConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
s.EqualError(err, status.ErrQueuedTxDiscarded.Error())
|
||||
|
||||
s.True(reflect.DeepEqual(txHashCheck, gethcommon.Hash{}), "transaction returned hash, while it shouldn't")
|
||||
}
|
||||
|
||||
// wait for transactions, and discard immediately
|
||||
discardTxs := func(txIDStrings string) {
|
||||
var parsedIDs []string
|
||||
err := json.Unmarshal([]byte(txIDStrings), &parsedIDs)
|
||||
s.NoError(err)
|
||||
|
||||
parsedIDs = append(parsedIDs, "invalid-tx-id")
|
||||
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
|
||||
|
||||
// discard
|
||||
discardResults := s.backend.DiscardTransactions(string(updatedTxIDStrings))
|
||||
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
||||
s.Fail(fmt.Sprintf("cannot discard txs: %v", discardResults))
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResults := s.backend.CompleteTransactions(string(updatedTxIDStrings), TestConfig.Account1.Password)
|
||||
if len(completeResults) != (testTxCount + 1) {
|
||||
s.Fail(fmt.Sprint("unexpected number of errors (call to CompleteTransaction should not succeed)"))
|
||||
}
|
||||
for _, txResult := range completeResults {
|
||||
if txResult.Error.Error() != "transaction hash not found" {
|
||||
s.Fail(fmt.Sprintf("invalid error for %s", txResult.Hash.Hex()))
|
||||
return
|
||||
}
|
||||
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
s.Fail(fmt.Sprintf("invalid hash (expected zero): %s", txResult.Hash.Hex()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
||||
s.Fail(fmt.Sprintf("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):
|
||||
s.Fail("test timed out")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
s.Fail("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestNonExistentQueuedTransactions() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
// replace transaction notification handler
|
||||
node.SetDefaultNodeNotificationHandler(func(string) {})
|
||||
|
||||
// try completing non-existing transaction
|
||||
_, err := s.backend.CompleteTransaction("some-bad-transaction-id", TestConfig.Account1.Password)
|
||||
s.Error(err, "error expected and not received")
|
||||
s.EqualError(err, status.ErrQueuedTxIDNotFound.Error())
|
||||
}
|
||||
|
||||
func (s *BackendTestSuite) TestEvictionOfQueuedTransactions() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.backend)
|
||||
|
||||
s.StartTestBackend(params.RopstenNetworkID)
|
||||
defer s.StopTestBackend()
|
||||
|
||||
backend := s.LightEthereumService().StatusBackend
|
||||
require.NotNil(backend)
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
require.NoError(s.backend.SelectAccount(TestConfig.Account1.Address, TestConfig.Account1.Password))
|
||||
|
||||
txQueue := backend.TransactionQueue()
|
||||
var i = 0
|
||||
txIDs := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxID{}
|
||||
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
||||
log.Info("tx enqueued", "i", i + 1, "queue size", txQueue.Count(), "id", queuedTx.ID)
|
||||
txIDs[i] = queuedTx.ID
|
||||
i++
|
||||
})
|
||||
|
||||
s.Zero(txQueue.Count(), "transaction count should be zero")
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
log.Info(fmt.Sprintf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d",
|
||||
i, status.DefaultTxQueueCap, txQueue.Count()))
|
||||
|
||||
s.Equal(10, txQueue.Count(), "transaction count should be 10")
|
||||
|
||||
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
|
||||
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
if txQueue.Count() > status.DefaultTxQueueCap {
|
||||
s.Fail(fmt.Sprintf("transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count()))
|
||||
return
|
||||
}
|
||||
|
||||
for _, txID := range txIDs {
|
||||
txQueue.Remove(txID)
|
||||
}
|
||||
|
||||
if txQueue.Count() != 0 {
|
||||
s.Fail(fmt.Sprintf("transaction count should be zero: %d", txQueue.Count()))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package params
|
||||
package common
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
@ -6,13 +6,14 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
// Logger is wrapper for custom logging
|
||||
type Logger struct {
|
||||
origHandler log.Handler
|
||||
handler log.Handler
|
||||
config *NodeConfig
|
||||
config *params.NodeConfig
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -21,7 +22,7 @@ var (
|
|||
)
|
||||
|
||||
// SetupLogger configs logger using parameters in config
|
||||
func SetupLogger(config *NodeConfig) (*Logger, error) {
|
||||
func SetupLogger(config *params.NodeConfig) (*Logger, error) {
|
||||
if !config.LogEnabled {
|
||||
return nil, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package params_test
|
||||
package common_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
)
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
|
@ -22,7 +23,7 @@ func TestLogger(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal("cannot create config object")
|
||||
}
|
||||
nodeLogger, err := params.SetupLogger(nodeConfig)
|
||||
nodeLogger, err := common.SetupLogger(nodeConfig)
|
||||
if err != nil {
|
||||
t.Fatal("cannot create logger object")
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ func TestLogger(t *testing.T) {
|
|||
|
||||
nodeConfig.LogEnabled = true
|
||||
nodeConfig.LogToStderr = false // just capture logs to file
|
||||
nodeLogger, err = params.SetupLogger(nodeConfig)
|
||||
nodeLogger, err = common.SetupLogger(nodeConfig)
|
||||
if err != nil {
|
||||
t.Fatal("cannot create logger object")
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
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
|
||||
StartNode(config *params.NodeConfig) (<-chan struct{}, error)
|
||||
|
||||
// StopNode stop the running Status node.
|
||||
// Stopped node cannot be resumed, one starts a new node instead.
|
||||
StopNode() error
|
||||
|
||||
// RestartNode restart running Status node, fails if node is not running
|
||||
RestartNode() (<-chan struct{}, error)
|
||||
|
||||
// ResetChainData remove chain data from data directory.
|
||||
// Node is stopped, and new node is started, with clean data directory.
|
||||
ResetChainData() (<-chan struct{}, error)
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
RPCClient() (*rpc.Client, error)
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// AccountsListRequestHandler returns handler to process account list request
|
||||
AccountsListRequestHandler() func(entities []common.Address) []common.Address
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// RawCompleteTransactionResult is a JSON returned from transaction complete function (used internally)
|
||||
type RawCompleteTransactionResult struct {
|
||||
Hash common.Hash
|
||||
Error error
|
||||
}
|
||||
|
||||
// RawDiscardTransactionResult is list of results from CompleteTransactions() (used internally)
|
||||
type RawDiscardTransactionResult struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
// TxQueueManager defines expected methods for managing transaction queue
|
||||
type TxQueueManager interface {
|
||||
// TransactionQueueHandler returns handler that processes incoming tx queue requests
|
||||
TransactionQueueHandler() func(queuedTx status.QueuedTx)
|
||||
|
||||
// TransactionReturnHandler returns handler that processes responses from internal tx manager
|
||||
TransactionReturnHandler() func(queuedTx *status.QueuedTx, err error)
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
CompleteTransaction(id, password string) (common.Hash, error)
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
DiscardTransaction(id string) error
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
DiscardTransactions(ids string) map[string]RawDiscardTransactionResult
|
||||
}
|
||||
|
||||
// JailCell represents single jail cell, which is basically a JavaScript VM.
|
||||
type JailCell interface {
|
||||
CellVM() *otto.Otto
|
||||
}
|
||||
|
||||
// JailManager defines methods for managing jailed environments
|
||||
type JailManager interface {
|
||||
// Parse creates a new jail cell context, with the given chatID as identifier.
|
||||
// New context executes provided JavaScript code, right after the initialization.
|
||||
Parse(chatID string, js string) string
|
||||
|
||||
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
||||
// Jail cell is clonned before call is executed i.e. all calls execute w/i their own contexts.
|
||||
Call(chatID string, path string, args string) string
|
||||
|
||||
// NewJailCell initializes and returns jail cell
|
||||
NewJailCell(id string) JailCell
|
||||
|
||||
// JailCellVM returns instance of Otto VM (which is persisted w/i jail cell) by chatID
|
||||
JailCellVM(chatID string) (*otto.Otto, error)
|
||||
|
||||
// BaseJS allows to setup initial JavaScript to be loaded on each jail.Parse()
|
||||
BaseJS(js string)
|
||||
}
|
||||
|
||||
// APIResponse generic response from API
|
||||
type APIResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// AccountInfo represents account's info
|
||||
type AccountInfo struct {
|
||||
Address string `json:"address"`
|
||||
PubKey string `json:"pubkey"`
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// TestConfig contains shared (among different test packages) parameters
|
||||
type TestConfig struct {
|
||||
Node struct {
|
||||
SyncSeconds time.Duration
|
||||
HTTPPort int
|
||||
WSPort int
|
||||
}
|
||||
Account1 struct {
|
||||
Address string
|
||||
Password string
|
||||
}
|
||||
Account2 struct {
|
||||
Address string
|
||||
Password string
|
||||
}
|
||||
}
|
||||
|
||||
// LoadTestConfig loads test configuration values from disk
|
||||
func LoadTestConfig() (*TestConfig, error) {
|
||||
var testConfig TestConfig
|
||||
|
||||
configData := string(static.MustAsset("config/test-data.json"))
|
||||
if err := json.Unmarshal([]byte(configData), &testConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &testConfig, nil
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
const (
|
||||
// MessageIDKey is a key for message ID
|
||||
// This ID is required to track from which chat a given send transaction request is coming.
|
||||
MessageIDKey = contextKey("message_id")
|
||||
)
|
||||
|
||||
type contextKey string // in order to make sure that our context key does not collide with keys from other packages
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrInvalidAccountAddressOrKey = errors.New("cannot parse address or key to valid account address")
|
||||
)
|
||||
|
||||
// ParseAccountString parses hex encoded string and returns is as accounts.Account.
|
||||
func ParseAccountString(account string) (accounts.Account, error) {
|
||||
// valid address, convert to account
|
||||
if common.IsHexAddress(account) {
|
||||
return accounts.Account{Address: common.HexToAddress(account)}, nil
|
||||
}
|
||||
|
||||
return accounts.Account{}, ErrInvalidAccountAddressOrKey
|
||||
}
|
||||
|
||||
// FromAddress converts account address from string to common.Address.
|
||||
// The function is useful to format "From" field of send transaction struct.
|
||||
func FromAddress(accountAddress string) common.Address {
|
||||
from, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
return from.Address
|
||||
}
|
||||
|
||||
// ToAddress converts account address from string to *common.Address.
|
||||
// The function is useful to format "To" field of send transaction struct.
|
||||
func ToAddress(accountAddress string) *common.Address {
|
||||
to, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &to.Address
|
||||
}
|
||||
|
||||
// ImportTestAccount checks if test account exists in keystore, and if not
|
||||
// tries to import it (from static resources, see "static/keys" folder)
|
||||
func ImportTestAccount(keystoreDir, accountFile string) error {
|
||||
// make sure that keystore folder exists
|
||||
if _, err := os.Stat(keystoreDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(keystoreDir, os.ModePerm) // nolint: errcheck
|
||||
}
|
||||
|
||||
dst := filepath.Join(keystoreDir, accountFile)
|
||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||
err = ioutil.WriteFile(dst, static.MustAsset("keys/"+accountFile), 0644)
|
||||
if err != nil {
|
||||
log.Warn("cannot copy test account PK", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// MessageIDFromContext returns message id from context (if exists)
|
||||
func MessageIDFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if messageID, ok := ctx.Value(MessageIDKey).(string); ok {
|
||||
return messageID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseJSONArray parses JSON array into Go array of string
|
||||
func ParseJSONArray(items string) ([]string, error) {
|
||||
var parsedItems []string
|
||||
err := json.Unmarshal([]byte(items), &parsedItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedItems, nil
|
||||
}
|
||||
|
||||
// Fatalf is used to halt the execution.
|
||||
// When called the function prints stack end exits.
|
||||
// Failure is logged into both StdErr and StdOut.
|
||||
func Fatalf(reason interface{}, args ...interface{}) {
|
||||
// decide on output stream
|
||||
w := io.MultiWriter(os.Stdout, os.Stderr)
|
||||
outf, _ := os.Stdout.Stat()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = os.Stderr
|
||||
}
|
||||
|
||||
// find out whether error or string has been passed as a reason
|
||||
r := reflect.ValueOf(reason)
|
||||
if r.Kind() == reflect.String {
|
||||
fmt.Fprintf(w, "Fatal Failure: "+reason.(string)+"\n", args)
|
||||
} else {
|
||||
fmt.Fprintf(w, "Fatal Failure: %v\n", reason.(error))
|
||||
}
|
||||
|
||||
debug.PrintStack()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
|
@ -2,7 +2,7 @@ package jail
|
|||
|
||||
import (
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -64,7 +64,7 @@ func makeSendHandler(jail *Jail, chatID string) func(call otto.FunctionCall) (re
|
|||
// makeJethIsConnectedHandler returns jeth.isConnected() handler
|
||||
func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (response otto.Value) {
|
||||
return func(call otto.FunctionCall) otto.Value {
|
||||
client, err := jail.RPCClient()
|
||||
client, err := jail.requestManager.RPCClient()
|
||||
if err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), nil)
|
||||
}
|
||||
|
@ -75,13 +75,19 @@ func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (respon
|
|||
}
|
||||
|
||||
if !netListeningResult {
|
||||
return newErrorResponse(call, -32603, geth.ErrInvalidGethNode.Error(), nil)
|
||||
return newErrorResponse(call, -32603, node.ErrNoRunningNode.Error(), nil)
|
||||
}
|
||||
|
||||
return newResultResponse(call, true)
|
||||
}
|
||||
}
|
||||
|
||||
// LocalStorageSetEvent is a signal sent whenever local storage Set method is called
|
||||
type LocalStorageSetEvent struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// makeLocalStorageSetHandler returns localStorage.set() handler
|
||||
func makeLocalStorageSetHandler(chatID string) func(call otto.FunctionCall) (response otto.Value) {
|
||||
return func(call otto.FunctionCall) otto.Value {
|
||||
|
@ -90,9 +96,9 @@ func makeLocalStorageSetHandler(chatID string) func(call otto.FunctionCall) (res
|
|||
data = data[:LocalStorageMaxDataLen]
|
||||
}
|
||||
|
||||
geth.SendSignal(geth.SignalEnvelope{
|
||||
node.SendSignal(node.SignalEnvelope{
|
||||
Type: EventLocalStorageSet,
|
||||
Event: geth.LocalStorageSetEvent{
|
||||
Event: LocalStorageSetEvent{
|
||||
ChatID: chatID,
|
||||
Data: data,
|
||||
},
|
||||
|
|
|
@ -11,102 +11,92 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
const (
|
||||
// JailedRuntimeRequestTimeout seconds before jailed request times out
|
||||
JailedRuntimeRequestTimeout = time.Second * 60
|
||||
// JailCellRequestTimeout seconds before jailed request times out
|
||||
JailCellRequestTimeout = 60
|
||||
)
|
||||
|
||||
var web3JSCode = static.MustAsset("scripts/web3.js")
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrInvalidJail = errors.New("jail environment is not properly initialized")
|
||||
)
|
||||
|
||||
// Jail represents jailed environment inside of which we hold
|
||||
// multiple cells. Each cell is separate JavaScript VM.
|
||||
type Jail struct {
|
||||
sync.RWMutex
|
||||
client *rpc.Client // lazy inited on the first call
|
||||
cells map[string]*JailedRuntime // jail supports running many isolated instances of jailed runtime
|
||||
statusJS string
|
||||
requestQueue *geth.JailedRequestQueue
|
||||
}
|
||||
|
||||
// JailedRuntime represents single jail cell, which is JavaScript VM.
|
||||
type JailedRuntime struct {
|
||||
// JailCell represents single jail cell, which is basically a JavaScript VM.
|
||||
type JailCell struct {
|
||||
id string
|
||||
vm *otto.Otto
|
||||
sem *semaphore.Semaphore
|
||||
}
|
||||
|
||||
var web3JS = static.MustAsset("scripts/web3.js")
|
||||
var jailInstance *Jail
|
||||
var once sync.Once
|
||||
|
||||
// New returns singleton jail environment
|
||||
func New() *Jail {
|
||||
once.Do(func() {
|
||||
jailInstance = &Jail{
|
||||
cells: make(map[string]*JailedRuntime),
|
||||
}
|
||||
})
|
||||
|
||||
return jailInstance
|
||||
// Jail represents jailed environment inside of which we hold multiple cells.
|
||||
// Each cell is a separate JavaScript VM.
|
||||
type Jail struct {
|
||||
sync.RWMutex
|
||||
requestManager *RequestManager
|
||||
cells map[string]common.JailCell // jail supports running many isolated instances of jailed runtime
|
||||
baseJSCode string // JavaScript used to initialize all new cells with
|
||||
}
|
||||
|
||||
// Init allows to setup initial JavaScript to be loaded on each jail.Parse()
|
||||
func Init(js string) *Jail {
|
||||
jailInstance = New() // singleton, we will always get the same reference
|
||||
jailInstance.statusJS = js
|
||||
|
||||
return jailInstance
|
||||
func (cell *JailCell) CellVM() *otto.Otto {
|
||||
return cell.vm
|
||||
}
|
||||
|
||||
// GetInstance returns singleton jail environment instance
|
||||
func GetInstance() *Jail {
|
||||
return New() // singleton, we will always get the same reference
|
||||
// New returns new Jail environment
|
||||
func New(nodeManager common.NodeManager) *Jail {
|
||||
return &Jail{
|
||||
requestManager: NewRequestManager(nodeManager),
|
||||
cells: make(map[string]common.JailCell),
|
||||
}
|
||||
}
|
||||
|
||||
// NewJailedRuntime initializes and returns jail cell
|
||||
func NewJailedRuntime(id string) *JailedRuntime {
|
||||
return &JailedRuntime{
|
||||
// BaseJS allows to setup initial JavaScript to be loaded on each jail.Parse()
|
||||
func (jail *Jail) BaseJS(js string) {
|
||||
jail.baseJSCode = js
|
||||
}
|
||||
|
||||
// NewJailCell initializes and returns jail cell
|
||||
func (jail *Jail) NewJailCell(id string) common.JailCell {
|
||||
return &JailCell{
|
||||
id: id,
|
||||
vm: otto.New(),
|
||||
sem: semaphore.New(1, JailedRuntimeRequestTimeout),
|
||||
sem: semaphore.New(1, JailCellRequestTimeout*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse creates a new jail cell context, with the given chatID as identifier
|
||||
// Parse creates a new jail cell context, with the given chatID as identifier.
|
||||
// New context executes provided JavaScript code, right after the initialization.
|
||||
func (jail *Jail) Parse(chatID string, js string) string {
|
||||
var err error
|
||||
if jail == nil {
|
||||
return printError(ErrInvalidJail.Error())
|
||||
return makeError(ErrInvalidJail.Error())
|
||||
}
|
||||
|
||||
jail.Lock()
|
||||
defer jail.Unlock()
|
||||
|
||||
jail.cells[chatID] = NewJailedRuntime(chatID)
|
||||
vm := jail.cells[chatID].vm
|
||||
jail.cells[chatID] = jail.NewJailCell(chatID)
|
||||
vm := jail.cells[chatID].CellVM()
|
||||
|
||||
initJjs := jail.statusJS + ";"
|
||||
initJjs := jail.baseJSCode + ";"
|
||||
if _, err = vm.Run(initJjs); err != nil {
|
||||
return printError(err.Error())
|
||||
return makeError(err.Error())
|
||||
}
|
||||
|
||||
// init jeth and its handlers
|
||||
if err = vm.Set("jeth", struct{}{}); err != nil {
|
||||
return printError(err.Error())
|
||||
return makeError(err.Error())
|
||||
}
|
||||
if err = registerHandlers(jail, vm, chatID); err != nil {
|
||||
return printError(err.Error())
|
||||
return makeError(err.Error())
|
||||
}
|
||||
|
||||
jjs := string(web3JS) + `
|
||||
jjs := string(web3JSCode) + `
|
||||
var Web3 = require('web3');
|
||||
var web3 = new Web3(jeth);
|
||||
var Bignumber = require("bignumber.js");
|
||||
|
@ -115,40 +105,36 @@ func (jail *Jail) Parse(chatID string, js string) string {
|
|||
}
|
||||
` + js + "; var catalog = JSON.stringify(_status_catalog);"
|
||||
if _, err = vm.Run(jjs); err != nil {
|
||||
return printError(err.Error())
|
||||
return makeError(err.Error())
|
||||
}
|
||||
|
||||
res, err := vm.Get("catalog")
|
||||
if err != nil {
|
||||
return printError(err.Error())
|
||||
return makeError(err.Error())
|
||||
}
|
||||
|
||||
return printResult(res.String(), err)
|
||||
return makeResult(res.String(), err)
|
||||
}
|
||||
|
||||
// Call executes given JavaScript function w/i a jail cell context identified by the chatID
|
||||
// Call executes given JavaScript function w/i a jail cell context identified by the chatID.
|
||||
// Jail cell is clonned before call is executed i.e. all calls execute w/i their own contexts.
|
||||
func (jail *Jail) Call(chatID string, path string, args string) string {
|
||||
_, err := jail.RPCClient()
|
||||
if err != nil {
|
||||
return printError(err.Error())
|
||||
}
|
||||
|
||||
jail.RLock()
|
||||
cell, ok := jail.cells[chatID]
|
||||
if !ok {
|
||||
jail.RUnlock()
|
||||
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatID))
|
||||
return makeError(fmt.Sprintf("Cell[%s] doesn't exist.", chatID))
|
||||
}
|
||||
jail.RUnlock()
|
||||
|
||||
vm := cell.vm.Copy() // isolate VM to allow concurrent access
|
||||
vm := cell.CellVM().Copy() // isolate VM to allow concurrent access
|
||||
res, err := vm.Call("call", nil, path, args)
|
||||
|
||||
return printResult(res.String(), err)
|
||||
return makeResult(res.String(), err)
|
||||
}
|
||||
|
||||
// GetVM returns instance of Otto VM (which is persisted w/i jail cell) by chatID
|
||||
func (jail *Jail) GetVM(chatID string) (*otto.Otto, error) {
|
||||
// JailCellVM returns instance of Otto VM (which is persisted w/i jail cell) by chatID
|
||||
func (jail *Jail) JailCellVM(chatID string) (*otto.Otto, error) {
|
||||
if jail == nil {
|
||||
return nil, ErrInvalidJail
|
||||
}
|
||||
|
@ -161,18 +147,13 @@ func (jail *Jail) GetVM(chatID string) (*otto.Otto, error) {
|
|||
return nil, fmt.Errorf("cell[%s] doesn't exist", chatID)
|
||||
}
|
||||
|
||||
return cell.vm, nil
|
||||
return cell.CellVM(), nil
|
||||
}
|
||||
|
||||
// Send will serialize the first argument, send it to the node and returns the response.
|
||||
// nolint: errcheck, unparam
|
||||
func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Value) {
|
||||
client, err := jail.RPCClient()
|
||||
if err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), nil)
|
||||
}
|
||||
|
||||
requestQueue, err := jail.RequestQueue()
|
||||
client, err := jail.requestManager.RPCClient()
|
||||
if err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), nil)
|
||||
}
|
||||
|
@ -185,7 +166,7 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
}
|
||||
var (
|
||||
rawReq = []byte(reqVal.String())
|
||||
reqs []geth.RPCCall
|
||||
reqs []RPCCall
|
||||
batch bool
|
||||
)
|
||||
if rawReq[0] == '[' {
|
||||
|
@ -193,7 +174,7 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
json.Unmarshal(rawReq, &reqs)
|
||||
} else {
|
||||
batch = false
|
||||
reqs = make([]geth.RPCCall, 1)
|
||||
reqs = make([]RPCCall, 1)
|
||||
json.Unmarshal(rawReq, &reqs[0])
|
||||
}
|
||||
|
||||
|
@ -205,8 +186,8 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
var result json.RawMessage
|
||||
|
||||
// execute directly w/o RPC call to node
|
||||
if req.Method == geth.SendTransactionRequest {
|
||||
txHash, err := requestQueue.ProcessSendTransactionRequest(call.Otto, req)
|
||||
if req.Method == SendTransactionRequest {
|
||||
txHash, err := jail.requestManager.ProcessSendTransactionRequest(call.Otto, req)
|
||||
resp.Set("result", txHash.Hex())
|
||||
if err != nil {
|
||||
resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
|
||||
|
@ -218,7 +199,7 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
// do extra request pre processing (persist message id)
|
||||
// within function semaphore will be acquired and released,
|
||||
// so that no more than one client (per cell) can enter
|
||||
messageID, err := requestQueue.PreProcessRequest(call.Otto, req)
|
||||
messageID, err := jail.requestManager.PreProcessRequest(call.Otto, req)
|
||||
if err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), nil)
|
||||
}
|
||||
|
@ -256,7 +237,7 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
resps.Call("push", resp)
|
||||
|
||||
// do extra request post processing (setting back tx context)
|
||||
requestQueue.PostProcessRequest(call.Otto, req, messageID)
|
||||
jail.requestManager.PostProcessRequest(call.Otto, req, messageID)
|
||||
}
|
||||
|
||||
// Return the responses either to the callback (if supplied)
|
||||
|
@ -273,59 +254,6 @@ func (jail *Jail) Send(chatID string, call otto.FunctionCall) (response otto.Val
|
|||
return response
|
||||
}
|
||||
|
||||
// RPCClient returns RPC client instance, creating it if necessary.
|
||||
// Returned instance is cached, so successive calls receive the same one.
|
||||
// nolint: dupl
|
||||
func (jail *Jail) RPCClient() (*rpc.Client, error) {
|
||||
if jail == nil {
|
||||
return nil, ErrInvalidJail
|
||||
}
|
||||
|
||||
if jail.client != nil {
|
||||
return jail.client, nil
|
||||
}
|
||||
|
||||
nodeManager := geth.NodeManagerInstance()
|
||||
if !nodeManager.NodeInited() {
|
||||
return nil, geth.ErrInvalidGethNode
|
||||
}
|
||||
|
||||
// obtain RPC client from running node
|
||||
client, err := nodeManager.RPCClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jail.client = client
|
||||
|
||||
return jail.client, nil
|
||||
}
|
||||
|
||||
// RequestQueue returns request queue instance, creating it if necessary.
|
||||
// Returned instance is cached, so successive calls receive the same one.
|
||||
// nolint: dupl
|
||||
func (jail *Jail) RequestQueue() (*geth.JailedRequestQueue, error) {
|
||||
if jail == nil {
|
||||
return nil, ErrInvalidJail
|
||||
}
|
||||
|
||||
if jail.requestQueue != nil {
|
||||
return jail.requestQueue, nil
|
||||
}
|
||||
|
||||
nodeManager := geth.NodeManagerInstance()
|
||||
if !nodeManager.NodeInited() {
|
||||
return nil, geth.ErrInvalidGethNode
|
||||
}
|
||||
|
||||
requestQueue, err := nodeManager.JailedRequestQueue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jail.requestQueue = requestQueue
|
||||
|
||||
return jail.requestQueue, nil
|
||||
}
|
||||
|
||||
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) otto.Value {
|
||||
// Bundle the error into a JSON RPC call response
|
||||
m := map[string]interface{}{"jsonrpc": "2.0", "id": id, "error": map[string]interface{}{"code": code, msg: msg}}
|
||||
|
@ -351,18 +279,23 @@ func throwJSException(msg interface{}) otto.Value {
|
|||
panic(val)
|
||||
}
|
||||
|
||||
func printError(error string) string {
|
||||
str := geth.JSONError{
|
||||
// JSONError is wrapper around errors, that are sent upwards
|
||||
type JSONError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func makeError(error string) string {
|
||||
str := JSONError{
|
||||
Error: error,
|
||||
}
|
||||
outBytes, _ := json.Marshal(&str)
|
||||
return string(outBytes)
|
||||
}
|
||||
|
||||
func printResult(res string, err error) string {
|
||||
func makeResult(res string, err error) string {
|
||||
var out string
|
||||
if err != nil {
|
||||
out = printError(err.Error())
|
||||
out = makeError(err.Error())
|
||||
} else {
|
||||
if "undefined" == res {
|
||||
res = "null"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,233 @@
|
|||
package jail
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/robertkrimen/otto"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// SendTransactionRequest is triggered on send transaction request
|
||||
SendTransactionRequest = "eth_sendTransaction"
|
||||
)
|
||||
|
||||
// RequestManager represents interface to manage jailed requests.
|
||||
// Whenever some request passed to a Jail, needs to be pre/post processed,
|
||||
// request manager is the right place for that.
|
||||
type RequestManager struct {
|
||||
nodeManager common.NodeManager
|
||||
}
|
||||
|
||||
func NewRequestManager(nodeManager common.NodeManager) *RequestManager {
|
||||
return &RequestManager{
|
||||
nodeManager: nodeManager,
|
||||
}
|
||||
}
|
||||
|
||||
// PreProcessRequest pre-processes a given RPC call to a given Otto VM
|
||||
func (m *RequestManager) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
|
||||
messageID := currentMessageID(vm.Context())
|
||||
|
||||
return messageID, nil
|
||||
}
|
||||
|
||||
// PostProcessRequest post-processes a given RPC call to a given Otto VM
|
||||
func (m *RequestManager) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
|
||||
if len(messageID) > 0 {
|
||||
vm.Call("addContext", nil, messageID, common.MessageIDKey, messageID) // nolint: errcheck
|
||||
}
|
||||
|
||||
// set extra markers for queued transaction requests
|
||||
if req.Method == SendTransactionRequest {
|
||||
vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessSendTransactionRequest processes send transaction request.
|
||||
// Both pre and post processing happens within this function. Pre-processing
|
||||
// happens before transaction is send to backend, and post processing occurs
|
||||
// when backend notifies that transaction sending is complete (either successfully
|
||||
// or with error)
|
||||
func (m *RequestManager) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (gethcommon.Hash, error) {
|
||||
lightEthereum, err := m.nodeManager.LightEthereumService()
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
messageID, err := m.PreProcessRequest(vm, req)
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
// onSendTransactionRequest() will use context to obtain and release ticket
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, common.MessageIDKey, messageID)
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
// invoke post processing
|
||||
m.PostProcessRequest(vm, req, messageID)
|
||||
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
// RPCClient returns RPC client instance, creating it if necessary.
|
||||
func (m *RequestManager) RPCClient() (*rpc.Client, error) {
|
||||
return m.nodeManager.RPCClient()
|
||||
}
|
||||
|
||||
// RPCCall represents RPC call parameters
|
||||
type RPCCall struct {
|
||||
ID int64
|
||||
Method string
|
||||
Params []interface{}
|
||||
}
|
||||
|
||||
func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
|
||||
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
|
||||
return status.SendTxArgs{}
|
||||
}
|
||||
|
||||
return status.SendTxArgs{
|
||||
From: req.parseFromAddress(),
|
||||
To: req.parseToAddress(),
|
||||
Value: req.parseValue(),
|
||||
Data: req.parseData(),
|
||||
Gas: req.parseGas(),
|
||||
GasPrice: req.parseGasPrice(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r RPCCall) parseFromAddress() gethcommon.Address {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return gethcommon.HexToAddress("0x")
|
||||
}
|
||||
|
||||
from, ok := params["from"].(string)
|
||||
if !ok {
|
||||
from = "0x"
|
||||
}
|
||||
|
||||
return gethcommon.HexToAddress(from)
|
||||
}
|
||||
|
||||
func (r RPCCall) parseToAddress() *gethcommon.Address {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
to, ok := params["to"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
address := gethcommon.HexToAddress(to)
|
||||
return &address
|
||||
}
|
||||
|
||||
func (r RPCCall) parseData() hexutil.Bytes {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return hexutil.Bytes("0x")
|
||||
}
|
||||
|
||||
data, ok := params["data"].(string)
|
||||
if !ok {
|
||||
data = "0x"
|
||||
}
|
||||
|
||||
byteCode, err := hexutil.Decode(data)
|
||||
if err != nil {
|
||||
byteCode = hexutil.Bytes(data)
|
||||
}
|
||||
|
||||
return byteCode
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseValue() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
//return (*hexutil.Big)(big.NewInt("0x0"))
|
||||
}
|
||||
|
||||
inputValue, ok := params["value"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseGas() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputValue, ok := params["gas"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseGasPrice() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputValue, ok := params["gasPrice"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
// currentMessageID looks for `status.message_id` variable in current JS context
|
||||
func currentMessageID(ctx otto.Context) string {
|
||||
if statusObj, ok := ctx.Symbols["status"]; ok {
|
||||
messageID, err := statusObj.Object().Get("message_id")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if messageID, err := messageID.ToString(); err == nil {
|
||||
return messageID
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package geth
|
||||
package node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -10,9 +10,10 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/status-im/status-go/extkeys"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
)
|
||||
|
||||
// errors
|
||||
|
@ -23,29 +24,41 @@ var (
|
|||
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
||||
ErrNoAccountSelected = errors.New("no account has been selected, please login")
|
||||
ErrInvalidMasterKeyCreated = errors.New("can not create master extended key")
|
||||
ErrInvalidAccountAddressOrKey = errors.New("cannot parse address or key to valid account address")
|
||||
)
|
||||
|
||||
// AccountManager represents account manager interface
|
||||
type AccountManager struct {
|
||||
nodeManager common.NodeManager
|
||||
selectedAccount *common.SelectedExtKey // account that was processed during the last call to SelectAccount()
|
||||
}
|
||||
|
||||
// NewAccountManager returns new node account manager
|
||||
func NewAccountManager(nodeManager common.NodeManager) *AccountManager {
|
||||
return &AccountManager{
|
||||
nodeManager: nodeManager,
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
func CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
func (m *AccountManager) CreateAccount(password string) (address, pubKey, mnemonic string, err error) {
|
||||
// generate mnemonic phrase
|
||||
m := extkeys.NewMnemonic(extkeys.Salt)
|
||||
mnemonic, err = m.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||
mn := extkeys.NewMnemonic(extkeys.Salt)
|
||||
mnemonic, err = mn.MnemonicPhrase(128, extkeys.EnglishLanguage)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("can not create mnemonic seed: %v", err)
|
||||
}
|
||||
|
||||
// generate extended master key (see BIP32)
|
||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||
extKey, err := extkeys.NewMaster(mn.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("can not create master extended key: %v", err)
|
||||
}
|
||||
|
||||
// import created key into account keystore
|
||||
address, pubKey, err = importExtendedKey(extKey, password)
|
||||
address, pubKey, err = m.importExtendedKey(extKey, password)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
@ -56,22 +69,21 @@ func 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.
|
||||
func CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
nodeManager := NodeManagerInstance()
|
||||
keyStore, err := nodeManager.AccountKeyStore()
|
||||
func (m *AccountManager) CreateChildAccount(parentAddress, password string) (address, pubKey string, err error) {
|
||||
keyStore, err := m.nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if parentAddress == "" && nodeManager.SelectedAccount != nil { // derive from selected account by default
|
||||
parentAddress = nodeManager.SelectedAccount.Address.Hex()
|
||||
if parentAddress == "" && m.selectedAccount != nil { // derive from selected account by default
|
||||
parentAddress = m.selectedAccount.Address.Hex()
|
||||
}
|
||||
|
||||
if parentAddress == "" {
|
||||
return "", "", ErrNoAccountSelected
|
||||
}
|
||||
|
||||
account, err := ParseAccountString(parentAddress)
|
||||
account, err := common.ParseAccountString(parentAddress)
|
||||
if err != nil {
|
||||
return "", "", ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
@ -98,14 +110,14 @@ func CreateChildAccount(parentAddress, password string) (address, pubKey string,
|
|||
accountKey.SubAccountIndex++
|
||||
|
||||
// import derived key into account keystore
|
||||
address, pubKey, err = importExtendedKey(childKey, password)
|
||||
address, pubKey, err = m.importExtendedKey(childKey, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// update in-memory selected account
|
||||
if nodeManager.SelectedAccount != nil {
|
||||
nodeManager.SelectedAccount.AccountKey = accountKey
|
||||
if m.selectedAccount != nil {
|
||||
m.selectedAccount.AccountKey = accountKey
|
||||
}
|
||||
|
||||
return address, pubKey, nil
|
||||
|
@ -113,16 +125,16 @@ func CreateChildAccount(parentAddress, password string) (address, pubKey string,
|
|||
|
||||
// RecoverAccount re-creates master key using given details.
|
||||
// Once master key is re-generated, it is inserted into keystore (if not already there).
|
||||
func RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||
func (m *AccountManager) RecoverAccount(password, mnemonic string) (address, pubKey string, err error) {
|
||||
// re-create extended key (see BIP32)
|
||||
m := extkeys.NewMnemonic(extkeys.Salt)
|
||||
extKey, err := extkeys.NewMaster(m.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||
mn := extkeys.NewMnemonic(extkeys.Salt)
|
||||
extKey, err := extkeys.NewMaster(mn.MnemonicSeed(mnemonic, password), []byte(extkeys.Salt))
|
||||
if err != nil {
|
||||
return "", "", ErrInvalidMasterKeyCreated
|
||||
}
|
||||
|
||||
// import re-created key into account keystore
|
||||
address, pubKey, err = importExtendedKey(extKey, password)
|
||||
address, pubKey, err = m.importExtendedKey(extKey, password)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -132,11 +144,11 @@ func RecoverAccount(password, mnemonic string) (address, pubKey string, err erro
|
|||
|
||||
// VerifyAccountPassword tries to decrypt a given account key file, with a provided password.
|
||||
// If no error is returned, then account is considered verified.
|
||||
func VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) {
|
||||
func (m *AccountManager) VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key, error) {
|
||||
var err error
|
||||
var keyJSON []byte
|
||||
|
||||
addressObj := common.BytesToAddress(common.FromHex(address))
|
||||
addressObj := gethcommon.BytesToAddress(gethcommon.FromHex(address))
|
||||
checkAccountKey := func(path string, fileInfo os.FileInfo) error {
|
||||
if len(keyJSON) > 0 || fileInfo.IsDir() {
|
||||
return nil
|
||||
|
@ -183,14 +195,13 @@ func VerifyAccountPassword(keyStoreDir, address, password string) (*keystore.Key
|
|||
// 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).
|
||||
func SelectAccount(address, password string) error {
|
||||
nodeManager := NodeManagerInstance()
|
||||
keyStore, err := nodeManager.AccountKeyStore()
|
||||
func (m *AccountManager) SelectAccount(address, password string) error {
|
||||
keyStore, err := m.nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account, err := ParseAccountString(address)
|
||||
account, err := common.ParseAccountString(address)
|
||||
if err != nil {
|
||||
return ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
@ -200,7 +211,7 @@ func SelectAccount(address, password string) error {
|
|||
return fmt.Errorf("%s: %v", ErrAccountToKeyMappingFailure.Error(), err)
|
||||
}
|
||||
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
whisperService, err := m.nodeManager.WhisperService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -210,11 +221,11 @@ func SelectAccount(address, password string) error {
|
|||
}
|
||||
|
||||
// persist account key for easier recovery of currently selected key
|
||||
subAccounts, err := findSubAccounts(accountKey.ExtendedKey, accountKey.SubAccountIndex)
|
||||
subAccounts, err := m.findSubAccounts(accountKey.ExtendedKey, accountKey.SubAccountIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeManager.SelectedAccount = &SelectedExtKey{
|
||||
m.selectedAccount = &common.SelectedExtKey{
|
||||
Address: account.Address,
|
||||
AccountKey: accountKey,
|
||||
SubAccounts: subAccounts,
|
||||
|
@ -223,16 +234,22 @@ func SelectAccount(address, password string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ReSelectAccount selects previously selected account, often, after node restart.
|
||||
func ReSelectAccount() error {
|
||||
nodeManager := NodeManagerInstance()
|
||||
// SelectedAccount returns currently selected account
|
||||
func (m *AccountManager) SelectedAccount() (*common.SelectedExtKey, error) {
|
||||
if m.selectedAccount == nil {
|
||||
return nil, ErrNoAccountSelected
|
||||
}
|
||||
return m.selectedAccount, nil
|
||||
}
|
||||
|
||||
selectedAccount := nodeManager.SelectedAccount
|
||||
// ReSelectAccount selects previously selected account, often, after node restart.
|
||||
func (m *AccountManager) ReSelectAccount() error {
|
||||
selectedAccount := m.selectedAccount
|
||||
if selectedAccount == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
whisperService, err := m.nodeManager.WhisperService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -245,9 +262,8 @@ func ReSelectAccount() error {
|
|||
}
|
||||
|
||||
// Logout clears whisper identities
|
||||
func Logout() error {
|
||||
nodeManager := NodeManagerInstance()
|
||||
whisperService, err := nodeManager.WhisperService()
|
||||
func (m *AccountManager) Logout() error {
|
||||
whisperService, err := m.nodeManager.WhisperService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -257,15 +273,15 @@ func Logout() error {
|
|||
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
||||
}
|
||||
|
||||
nodeManager.SelectedAccount = nil
|
||||
m.selectedAccount = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// importExtendedKey processes incoming extended key, extracts required info and creates corresponding account key.
|
||||
// Once account key is formed, that key is put (if not already) into keystore i.e. key is *encoded* into key file.
|
||||
func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
||||
keyStore, err := NodeManagerInstance().AccountKeyStore()
|
||||
func (m *AccountManager) importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {
|
||||
keyStore, err := m.nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
@ -282,28 +298,28 @@ func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, p
|
|||
if err != nil {
|
||||
return address, "", err
|
||||
}
|
||||
pubKey = common.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
pubKey = gethcommon.ToHex(crypto.FromECDSAPub(&key.PrivateKey.PublicKey))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func onAccountsListRequest(entities []common.Address) []common.Address {
|
||||
nodeManager := NodeManagerInstance()
|
||||
|
||||
if nodeManager.SelectedAccount == nil {
|
||||
return []common.Address{}
|
||||
// AccountsListRequestHandler returns handler to process account list request
|
||||
func (m *AccountManager) AccountsListRequestHandler() func(entities []gethcommon.Address) []gethcommon.Address {
|
||||
return func(entities []gethcommon.Address) []gethcommon.Address {
|
||||
if m.selectedAccount == nil {
|
||||
return []gethcommon.Address{}
|
||||
}
|
||||
|
||||
refreshSelectedAccount()
|
||||
m.refreshSelectedAccount()
|
||||
|
||||
filtered := make([]common.Address, 0)
|
||||
filtered := make([]gethcommon.Address, 0)
|
||||
for _, account := range entities {
|
||||
// main account
|
||||
if nodeManager.SelectedAccount.Address.Hex() == account.Hex() {
|
||||
if m.selectedAccount.Address.Hex() == account.Hex() {
|
||||
filtered = append(filtered, account)
|
||||
} else {
|
||||
// sub accounts
|
||||
for _, subAccount := range nodeManager.SelectedAccount.SubAccounts {
|
||||
for _, subAccount := range m.selectedAccount.SubAccounts {
|
||||
if subAccount.Address.Hex() == account.Hex() {
|
||||
filtered = append(filtered, account)
|
||||
}
|
||||
|
@ -313,28 +329,27 @@ func onAccountsListRequest(entities []common.Address) []common.Address {
|
|||
|
||||
return filtered
|
||||
}
|
||||
}
|
||||
|
||||
// refreshSelectedAccount re-populates list of sub-accounts of the currently selected account (if any)
|
||||
func refreshSelectedAccount() {
|
||||
nodeManager := NodeManagerInstance()
|
||||
|
||||
if nodeManager.SelectedAccount == nil {
|
||||
func (m *AccountManager) refreshSelectedAccount() {
|
||||
if m.selectedAccount == nil {
|
||||
return
|
||||
}
|
||||
|
||||
accountKey := nodeManager.SelectedAccount.AccountKey
|
||||
accountKey := m.selectedAccount.AccountKey
|
||||
if accountKey == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// re-populate list of sub-accounts
|
||||
subAccounts, err := findSubAccounts(accountKey.ExtendedKey, accountKey.SubAccountIndex)
|
||||
subAccounts, err := m.findSubAccounts(accountKey.ExtendedKey, accountKey.SubAccountIndex)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nodeManager.SelectedAccount = &SelectedExtKey{
|
||||
Address: nodeManager.SelectedAccount.Address,
|
||||
AccountKey: nodeManager.SelectedAccount.AccountKey,
|
||||
m.selectedAccount = &common.SelectedExtKey{
|
||||
Address: m.selectedAccount.Address,
|
||||
AccountKey: m.selectedAccount.AccountKey,
|
||||
SubAccounts: subAccounts,
|
||||
}
|
||||
}
|
||||
|
@ -342,9 +357,8 @@ func refreshSelectedAccount() {
|
|||
// findSubAccounts traverses cached accounts and adds as a sub-accounts any
|
||||
// that belong to the currently selected account.
|
||||
// The extKey is CKD#2 := root of sub-accounts of the main account
|
||||
func findSubAccounts(extKey *extkeys.ExtendedKey, subAccountIndex uint32) ([]accounts.Account, error) {
|
||||
nodeManager := NodeManagerInstance()
|
||||
keyStore, err := nodeManager.AccountKeyStore()
|
||||
func (m *AccountManager) findSubAccounts(extKey *extkeys.ExtendedKey, subAccountIndex uint32) ([]accounts.Account, error) {
|
||||
keyStore, err := m.nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return []accounts.Account{}, err
|
||||
}
|
||||
|
@ -352,7 +366,7 @@ func findSubAccounts(extKey *extkeys.ExtendedKey, subAccountIndex uint32) ([]acc
|
|||
subAccounts := make([]accounts.Account, 0)
|
||||
if extKey.Depth == 5 { // CKD#2 level
|
||||
// gather possible sub-account addresses
|
||||
subAccountAddresses := make([]common.Address, 0)
|
||||
subAccountAddresses := make([]gethcommon.Address, 0)
|
||||
for i := uint32(0); i < subAccountIndex; i++ {
|
||||
childKey, err := extKey.Child(i)
|
||||
if err != nil {
|
||||
|
@ -373,3 +387,20 @@ func findSubAccounts(extKey *extkeys.ExtendedKey, subAccountIndex uint32) ([]acc
|
|||
|
||||
return subAccounts, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *AccountManager) AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error) {
|
||||
keyStore, err := m.nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return accounts.Account{}, nil, err
|
||||
}
|
||||
|
||||
account, err := common.ParseAccountString(address)
|
||||
if err != nil {
|
||||
return accounts.Account{}, nil, ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
||||
return keyStore.AccountDecryptedKey(account, password)
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestAccountsTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(AccountsTestSuite))
|
||||
}
|
||||
|
||||
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 := node.NewAccountManager(nil)
|
||||
require.NotNil(accountManager)
|
||||
|
||||
keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
||||
require.NoError(err)
|
||||
defer os.RemoveAll(keyStoreDir) // nolint: errcheck
|
||||
|
||||
emptyKeyStoreDir, err := ioutil.TempDir(os.TempDir(), "empty")
|
||||
require.NoError(err)
|
||||
defer os.RemoveAll(emptyKeyStoreDir) // nolint: errcheck
|
||||
|
||||
// import account keys
|
||||
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account1.pk"))
|
||||
require.NoError(common.ImportTestAccount(keyStoreDir, "test-account2.pk"))
|
||||
|
||||
account1Address := gethcommon.BytesToAddress(gethcommon.FromHex(TestConfig.Account1.Address))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keyPath string
|
||||
address string
|
||||
password string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
"correct address, correct password (decrypt should succeed)",
|
||||
keyStoreDir,
|
||||
TestConfig.Account1.Address,
|
||||
TestConfig.Account1.Password,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"correct address, correct password, non-existent key store",
|
||||
filepath.Join(keyStoreDir, "non-existent-folder"),
|
||||
TestConfig.Account1.Address,
|
||||
TestConfig.Account1.Password,
|
||||
fmt.Errorf("cannot traverse key store folder: lstat %s/non-existent-folder: no such file or directory", keyStoreDir),
|
||||
},
|
||||
{
|
||||
"correct address, correct password, empty key store (pk is not there)",
|
||||
emptyKeyStoreDir,
|
||||
TestConfig.Account1.Address,
|
||||
TestConfig.Account1.Password,
|
||||
fmt.Errorf("cannot locate account for address: %x", account1Address),
|
||||
},
|
||||
{
|
||||
"wrong address, correct password",
|
||||
keyStoreDir,
|
||||
"0x79791d3e8f2daa1f7fec29649d152c0ada3cc535",
|
||||
TestConfig.Account1.Password,
|
||||
fmt.Errorf("cannot locate account for address: %s", "79791d3e8f2daa1f7fec29649d152c0ada3cc535"),
|
||||
},
|
||||
{
|
||||
"correct address, wrong password",
|
||||
keyStoreDir,
|
||||
TestConfig.Account1.Address,
|
||||
"wrong password", // wrong password
|
||||
errors.New("could not decrypt key with given passphrase"),
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
s.T().Log(testCase.name)
|
||||
accountKey, err := accountManager.VerifyAccountPassword(testCase.keyPath, testCase.address, testCase.password)
|
||||
if !reflect.DeepEqual(err, testCase.expectedError) {
|
||||
s.FailNow(fmt.Sprintf("unexpected error: expected \n'%v', got \n'%v'", testCase.expectedError, err))
|
||||
}
|
||||
if err == nil {
|
||||
if accountKey == nil {
|
||||
s.T().Error("no error reported, but account key is missing")
|
||||
}
|
||||
accountAddress := gethcommon.BytesToAddress(gethcommon.FromHex(testCase.address))
|
||||
if accountKey.Address != accountAddress {
|
||||
s.T().Fatalf("account mismatch: have %x, want %x", accountKey.Address, accountAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// +build darwin,cgo
|
||||
|
||||
package geth
|
||||
package node
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
|
@ -0,0 +1,498 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrNodeAlreadyExists = errors.New("there is a running node already, stop it before starting another one")
|
||||
ErrNoRunningNode = errors.New("there is no running node")
|
||||
ErrInvalidNodeManager = errors.New("node manager is not properly initialized")
|
||||
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
||||
ErrInvalidLightEthereumService = errors.New("LES service is unavailable")
|
||||
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
||||
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
|
||||
ErrInvalidRPCClient = errors.New("RPC service is unavailable")
|
||||
)
|
||||
|
||||
// NodeManager manages Status node (which abstracts contained geth node)
|
||||
type NodeManager struct {
|
||||
sync.RWMutex
|
||||
config *params.NodeConfig // Status node configuration
|
||||
node *node.Node // reference to Geth P2P stack/node
|
||||
whisperService *whisper.Whisper // reference to Whisper service
|
||||
lesService *les.LightEthereum // reference to LES service
|
||||
rpcClient *rpc.Client // reference to RPC client
|
||||
}
|
||||
|
||||
// NewNodeManager makes new instance of node manager
|
||||
func NewNodeManager() *NodeManager {
|
||||
m := &NodeManager{}
|
||||
|
||||
// allow interrupting running nodes
|
||||
go func() {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, os.Interrupt)
|
||||
defer signal.Stop(sigc)
|
||||
<-sigc
|
||||
if m.node == nil {
|
||||
return
|
||||
}
|
||||
log.Info("Got interrupt, shutting down...")
|
||||
go m.node.Stop() // nolint: errcheck
|
||||
for i := 3; i > 0; i-- {
|
||||
<-sigc
|
||||
if i > 1 {
|
||||
log.Info(fmt.Sprintf("Already shutting down, interrupt %d more times for panic.", i-1))
|
||||
}
|
||||
}
|
||||
panic("interrupted!")
|
||||
}()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// StartNode start Status node, fails if node is already started
|
||||
func (m *NodeManager) StartNode(config *params.NodeConfig) (<-chan struct{}, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.node != nil {
|
||||
return nil, ErrNodeAlreadyExists
|
||||
}
|
||||
|
||||
return m.startNode(config)
|
||||
}
|
||||
|
||||
// startNode start Status node, fails if node is already started
|
||||
func (m *NodeManager) startNode(config *params.NodeConfig) (<-chan struct{}, error) {
|
||||
if m.node != nil {
|
||||
return nil, ErrNodeAlreadyExists
|
||||
}
|
||||
|
||||
var err error
|
||||
m.node, err = MakeNode(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.config = config // preserve config of successfully created node
|
||||
|
||||
nodeStarted := make(chan struct{})
|
||||
go func() {
|
||||
defer HaltOnPanic()
|
||||
|
||||
if err := m.node.Start(); err != nil {
|
||||
m.Lock() // TODO potential deadlock (add test case to prove otherwise)
|
||||
m.config = nil
|
||||
m.lesService = nil
|
||||
m.whisperService = nil
|
||||
m.rpcClient = nil
|
||||
m.node = nil
|
||||
m.Unlock()
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeCrashed,
|
||||
Event: NodeCrashEvent{
|
||||
Error: fmt.Errorf("%v: %v", ErrNodeStartFailure, err).Error(),
|
||||
},
|
||||
})
|
||||
close(nodeStarted)
|
||||
return
|
||||
}
|
||||
|
||||
// node is ready, use it
|
||||
m.onNodeStarted(nodeStarted)
|
||||
}()
|
||||
|
||||
return nodeStarted, nil
|
||||
}
|
||||
|
||||
// onNodeStarted extra processing once we have running node
|
||||
func (m *NodeManager) onNodeStarted(nodeStarted chan struct{}) {
|
||||
// post-start processing
|
||||
if err := m.populateStaticPeers(); err != nil {
|
||||
log.Error("Static peers population", "error", err)
|
||||
}
|
||||
|
||||
// obtain node info
|
||||
var nodeInfo *p2p.NodeInfo
|
||||
if server := m.node.Server(); server != nil {
|
||||
if nodeInfo = server.NodeInfo(); nodeInfo != nil {
|
||||
log.Info("Node is ready", "enode", nodeInfo.Enode)
|
||||
}
|
||||
}
|
||||
|
||||
// notify all subscribers that node is started
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeStarted,
|
||||
Event: struct{}{},
|
||||
})
|
||||
close(nodeStarted)
|
||||
|
||||
// wait up until node is stopped
|
||||
m.node.Wait()
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeStopped,
|
||||
Event: struct{}{},
|
||||
})
|
||||
log.Info("Node is stopped", "enode", nodeInfo.Enode)
|
||||
}
|
||||
|
||||
// IsNodeRunning confirm that node is running
|
||||
func (m *NodeManager) IsNodeRunning() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.node != nil
|
||||
}
|
||||
|
||||
// StopNode stop Status node. Stopped node cannot be resumed.
|
||||
func (m *NodeManager) StopNode() error {
|
||||
if m == nil {
|
||||
return ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return m.stopNode()
|
||||
}
|
||||
|
||||
// stopNode stop Status node. Stopped node cannot be resumed.
|
||||
func (m *NodeManager) stopNode() error {
|
||||
if m.node == nil {
|
||||
return ErrNoRunningNode
|
||||
}
|
||||
|
||||
if err := m.node.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.config = nil
|
||||
m.lesService = nil
|
||||
m.whisperService = nil
|
||||
m.rpcClient = nil
|
||||
m.node = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Node returns underlying Status node
|
||||
func (m *NodeManager) Node() (*node.Node, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
return m.node, nil
|
||||
}
|
||||
|
||||
// PopulateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
|
||||
func (m *NodeManager) PopulateStaticPeers() error {
|
||||
if m == nil {
|
||||
return ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.populateStaticPeers()
|
||||
}
|
||||
|
||||
// populateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
|
||||
func (m *NodeManager) populateStaticPeers() error {
|
||||
if m.node == nil {
|
||||
return ErrNoRunningNode
|
||||
}
|
||||
|
||||
if !m.config.BootClusterConfig.Enabled {
|
||||
log.Info("Boot cluster is disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
enodes, err := m.config.LoadBootClusterNodes()
|
||||
if err != nil {
|
||||
log.Warn("Can not load boot nodes", "error", err)
|
||||
}
|
||||
for _, enode := range enodes {
|
||||
err := m.addPeer(enode)
|
||||
if err != nil {
|
||||
log.Warn("Boot node addition failed", "error", err)
|
||||
continue
|
||||
}
|
||||
log.Info("Boot node added", "enode", enode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPeer adds new static peer node
|
||||
func (m *NodeManager) AddPeer(url string) error {
|
||||
if m == nil {
|
||||
return ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.addPeer(url)
|
||||
}
|
||||
|
||||
// addPeer adds new static peer node
|
||||
func (m *NodeManager) addPeer(url string) error {
|
||||
if m == nil || m.node == nil {
|
||||
return ErrNoRunningNode
|
||||
}
|
||||
|
||||
server := m.node.Server()
|
||||
if server == nil {
|
||||
return ErrNoRunningNode
|
||||
}
|
||||
|
||||
// Try to add the url as a static peer and return
|
||||
parsedNode, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.AddPeer(parsedNode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetChainData remove chain data from data directory.
|
||||
// Node is stopped, and new node is started, with clean data directory.
|
||||
func (m *NodeManager) ResetChainData() (<-chan struct{}, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
prevConfig := *m.config
|
||||
if err := m.stopNode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chainDataDir := filepath.Join(prevConfig.DataDir, prevConfig.Name, "lightchaindata")
|
||||
if _, err := os.Stat(chainDataDir); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.RemoveAll(chainDataDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// send signal up to native app
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventChainDataRemoved,
|
||||
Event: struct{}{},
|
||||
})
|
||||
log.Info("chaindata removed", "dir", chainDataDir)
|
||||
|
||||
return m.startNode(&prevConfig)
|
||||
}
|
||||
|
||||
// RestartNode restart running Status node, fails if node is not running
|
||||
func (m *NodeManager) RestartNode() (<-chan struct{}, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
prevConfig := *m.config
|
||||
if err := m.stopNode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.startNode(&prevConfig)
|
||||
}
|
||||
|
||||
// NodeConfig exposes reference to running node's configuration
|
||||
func (m *NodeManager) NodeConfig() (*params.NodeConfig, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
return m.config, nil
|
||||
}
|
||||
|
||||
// LightEthereumService exposes reference to LES service running on top of the node
|
||||
func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
if m.lesService == nil {
|
||||
if err := m.node.Service(&m.lesService); err != nil {
|
||||
log.Warn("Cannot obtain LES service", "error", err)
|
||||
return nil, ErrInvalidLightEthereumService
|
||||
}
|
||||
}
|
||||
|
||||
if m.lesService == nil {
|
||||
return nil, ErrInvalidLightEthereumService
|
||||
}
|
||||
|
||||
return m.lesService, nil
|
||||
}
|
||||
|
||||
// WhisperService exposes reference to Whisper service running on top of the node
|
||||
func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
if m.whisperService == nil {
|
||||
if err := m.node.Service(&m.whisperService); err != nil {
|
||||
log.Warn("Cannot obtain whisper service", "error", err)
|
||||
return nil, ErrInvalidWhisperService
|
||||
}
|
||||
}
|
||||
|
||||
if m.whisperService == nil {
|
||||
return nil, ErrInvalidWhisperService
|
||||
}
|
||||
|
||||
return m.whisperService, nil
|
||||
}
|
||||
|
||||
// AccountManager exposes reference to node's accounts manager
|
||||
func (m *NodeManager) AccountManager() (*accounts.Manager, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
accountManager := m.node.AccountManager()
|
||||
if accountManager == nil {
|
||||
return nil, ErrInvalidAccountManager
|
||||
}
|
||||
|
||||
return accountManager, nil
|
||||
}
|
||||
|
||||
// AccountKeyStore exposes reference to accounts key store
|
||||
func (m *NodeManager) AccountKeyStore() (*keystore.KeyStore, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
accountManager := m.node.AccountManager()
|
||||
if accountManager == nil {
|
||||
return nil, ErrInvalidAccountManager
|
||||
}
|
||||
|
||||
backends := accountManager.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
keyStore, ok := backends[0].(*keystore.KeyStore)
|
||||
if !ok {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
return keyStore, nil
|
||||
}
|
||||
|
||||
// RPCClient exposes reference to RPC client connected to the running node
|
||||
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
|
||||
if m == nil {
|
||||
return nil, ErrInvalidNodeManager
|
||||
}
|
||||
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
if m.node == nil {
|
||||
return nil, ErrNoRunningNode
|
||||
}
|
||||
|
||||
if m.rpcClient == nil {
|
||||
var err error
|
||||
m.rpcClient, err = m.node.Attach()
|
||||
if err != nil {
|
||||
log.Error("Cannot attach RPC client to node", "error", err)
|
||||
return nil, ErrInvalidRPCClient
|
||||
}
|
||||
}
|
||||
|
||||
if m.rpcClient == nil {
|
||||
return nil, ErrInvalidRPCClient
|
||||
}
|
||||
|
||||
return m.rpcClient, nil
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/status-im/status-go/geth/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestManagerTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ManagerTestSuite))
|
||||
}
|
||||
|
||||
type ManagerTestSuite struct {
|
||||
BaseTestSuite
|
||||
}
|
||||
|
||||
func (s *ManagerTestSuite) SetupTest() {
|
||||
s.NodeManager = node.NewNodeManager()
|
||||
s.Require().NotNil(s.NodeManager)
|
||||
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
|
||||
}
|
||||
|
||||
func (s *ManagerTestSuite) TestGettingReferencedServices() {
|
||||
s.Require().NotNil(s.NodeManager)
|
||||
|
||||
var nilNodeManager *node.NodeManager
|
||||
|
||||
// test for nil values of nodeManager
|
||||
var noNodeTests = []struct {
|
||||
name string
|
||||
initFn func() (interface{}, error)
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"null manager, get NodeConfig",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.NodeConfig()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get Node",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.Node()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get LES",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.LightEthereumService()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get Whisper",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.WhisperService()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get AccountManager",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.AccountManager()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get AccountKeyStore",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.AccountKeyStore()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"null manager, get RPC Client",
|
||||
func() (interface{}, error) {
|
||||
return nilNodeManager.RPCClient()
|
||||
},
|
||||
node.ErrInvalidNodeManager,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get NodeConfig",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.NodeConfig()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get Node",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.Node()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get LES",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.LightEthereumService()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get Whisper",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.WhisperService()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get AccountManager",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.AccountManager()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get AccountKeyStore",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.AccountKeyStore()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
{
|
||||
"non-null manager, no running node, get RPC Client",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.RPCClient()
|
||||
},
|
||||
node.ErrNoRunningNode,
|
||||
},
|
||||
}
|
||||
for _, testCase := range noNodeTests {
|
||||
s.T().Log(testCase.name)
|
||||
obj, err := testCase.initFn()
|
||||
s.Nil(obj)
|
||||
s.EqualError(err, testCase.expectedErr.Error())
|
||||
}
|
||||
|
||||
// test with node fully started
|
||||
s.StartTestNode(params.RinkebyNetworkID)
|
||||
defer s.StopTestNode()
|
||||
var nodeReadyTestCases = []struct {
|
||||
name string
|
||||
initFn func() (interface{}, error)
|
||||
expectedType interface{}
|
||||
}{
|
||||
{
|
||||
"node is running, get NodeConfig",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.NodeConfig()
|
||||
},
|
||||
¶ms.NodeConfig{},
|
||||
},
|
||||
{
|
||||
"node is running, get Node",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.Node()
|
||||
},
|
||||
&gethnode.Node{},
|
||||
},
|
||||
{
|
||||
"node is running, get LES",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.LightEthereumService()
|
||||
},
|
||||
&les.LightEthereum{},
|
||||
},
|
||||
{
|
||||
"node is running, get Whisper",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.WhisperService()
|
||||
},
|
||||
&whisper.Whisper{},
|
||||
},
|
||||
{
|
||||
"node is running, get AccountManager",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.AccountManager()
|
||||
},
|
||||
&accounts.Manager{},
|
||||
},
|
||||
{
|
||||
"node is running, get AccountKeyStore",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.AccountKeyStore()
|
||||
},
|
||||
&keystore.KeyStore{},
|
||||
},
|
||||
{
|
||||
"node is running, get RPC Client",
|
||||
func() (interface{}, error) {
|
||||
return s.NodeManager.RPCClient()
|
||||
},
|
||||
&rpc.Client{},
|
||||
},
|
||||
}
|
||||
for _, testCase := range nodeReadyTestCases {
|
||||
obj, err := testCase.initFn()
|
||||
s.T().Log(testCase.name)
|
||||
s.NoError(err)
|
||||
s.NotNil(obj)
|
||||
s.IsType(testCase.expectedType, obj)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ManagerTestSuite) TestNodeStartStop() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||
require.NoError(err)
|
||||
|
||||
// try stopping non-started node
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
err = s.NodeManager.StopNode()
|
||||
if s.Error(err) {
|
||||
require.IsType(node.ErrNoRunningNode, err)
|
||||
}
|
||||
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
||||
require.NoError(err)
|
||||
|
||||
<-nodeStarted // wait till node is started
|
||||
require.True(s.NodeManager.IsNodeRunning())
|
||||
|
||||
// try starting another node (w/o stopping the previously started node)
|
||||
_, err = s.NodeManager.StartNode(nodeConfig)
|
||||
if s.Error(err) {
|
||||
require.IsType(node.ErrNodeAlreadyExists, err)
|
||||
}
|
||||
|
||||
// now stop node, and make sure that a new node, on different network can be started
|
||||
err = s.NodeManager.StopNode()
|
||||
require.NoError(err)
|
||||
|
||||
// start new node with exactly the same config
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
nodeStarted, err = s.NodeManager.StartNode(nodeConfig)
|
||||
require.NoError(err)
|
||||
|
||||
<-nodeStarted
|
||||
require.True(s.NodeManager.IsNodeRunning())
|
||||
|
||||
s.StopTestNode()
|
||||
}
|
||||
|
||||
func (s *ManagerTestSuite) TestNetworkSwitching() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
// get Ropsten config
|
||||
nodeConfig, err := MakeTestNodeConfig(params.RopstenNetworkID)
|
||||
require.NoError(err)
|
||||
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
nodeStarted, err := s.NodeManager.StartNode(nodeConfig)
|
||||
require.NoError(err)
|
||||
|
||||
<-nodeStarted // wait till node is started
|
||||
require.True(s.NodeManager.IsNodeRunning())
|
||||
|
||||
s.FirstBlockHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||
|
||||
// now stop node, and make sure that a new node, on different network can be started
|
||||
err = s.NodeManager.StopNode()
|
||||
require.NoError(err)
|
||||
|
||||
// start new node with completely different config
|
||||
nodeConfig, err = MakeTestNodeConfig(params.RinkebyNetworkID)
|
||||
require.NoError(err)
|
||||
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
nodeStarted, err = s.NodeManager.StartNode(nodeConfig)
|
||||
require.NoError(err)
|
||||
|
||||
<-nodeStarted
|
||||
require.True(s.NodeManager.IsNodeRunning())
|
||||
|
||||
// make sure we are on another network indeed
|
||||
s.FirstBlockHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
|
||||
s.StopTestNode()
|
||||
}
|
||||
|
||||
func (s *ManagerTestSuite) TestResetChainData() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
s.StartTestNode(params.RinkebyNetworkID)
|
||||
defer s.StopTestNode()
|
||||
|
||||
time.Sleep(2 * time.Second) // allow to sync for some time
|
||||
|
||||
s.True(s.NodeManager.IsNodeRunning())
|
||||
require.NoError(s.NodeManager.ResetChainData())
|
||||
s.True(s.NodeManager.IsNodeRunning()) // new node, with previous config should be running
|
||||
|
||||
// make sure we can read the first byte, and it is valid (for Rinkeby)
|
||||
s.FirstBlockHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
package geth
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
|
@ -29,56 +28,35 @@ import (
|
|||
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
||||
"github.com/ethereum/go-ethereum/whisper/notifications"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventNodeStarted is triggered when underlying node is fully started
|
||||
EventNodeStarted = "node.started"
|
||||
|
||||
// EventNodeCrashed is triggered when node crashes
|
||||
EventNodeCrashed = "node.crashed"
|
||||
)
|
||||
|
||||
// node-related errors
|
||||
var (
|
||||
ErrEthServiceRegistrationFailure = errors.New("failed to register the Ethereum service")
|
||||
ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
|
||||
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
|
||||
ErrNodeMakeFailure = errors.New("error creating p2p node")
|
||||
ErrNodeRunFailure = errors.New("error running p2p node")
|
||||
ErrNodeStartFailure = errors.New("error starting p2p node")
|
||||
)
|
||||
|
||||
// Node represents running node (serves as a wrapper around P2P node)
|
||||
type Node struct {
|
||||
config *params.NodeConfig // configuration used to create Status node
|
||||
geth *node.Node // reference to the running Geth node
|
||||
gethConfig *node.Config // configuration used to create P2P node
|
||||
started chan struct{} // channel to wait for node to start
|
||||
}
|
||||
|
||||
// Inited checks whether status node has been properly initialized
|
||||
func (n *Node) Inited() bool {
|
||||
return n != nil && n.geth != nil
|
||||
}
|
||||
|
||||
// GethStack returns reference to Geth stack
|
||||
func (n *Node) GethStack() *node.Node {
|
||||
return n.geth
|
||||
}
|
||||
|
||||
// MakeNode create a geth node entity
|
||||
func MakeNode(config *params.NodeConfig) *Node {
|
||||
func MakeNode(config *params.NodeConfig) (*node.Node, error) {
|
||||
// make sure data directory exists
|
||||
if err := os.MkdirAll(filepath.Join(config.DataDir), os.ModePerm); err != nil {
|
||||
Fatalf(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make sure keys directory exists
|
||||
if err := os.MkdirAll(filepath.Join(config.KeyStoreDir), os.ModePerm); err != nil {
|
||||
Fatalf(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// setup logging
|
||||
if _, err := params.SetupLogger(config); err != nil {
|
||||
Fatalf(err)
|
||||
if _, err := common.SetupLogger(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// configure required node (should you need to update node's config, e.g. add bootstrap nodes, see node.Config)
|
||||
|
@ -108,25 +86,20 @@ func MakeNode(config *params.NodeConfig) *Node {
|
|||
|
||||
stack, err := node.New(stackConfig)
|
||||
if err != nil {
|
||||
Fatalf(ErrNodeMakeFailure)
|
||||
return nil, ErrNodeMakeFailure
|
||||
}
|
||||
|
||||
// start Ethereum service
|
||||
if err := activateEthService(stack, config); err != nil {
|
||||
Fatalf(fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err))
|
||||
return nil, fmt.Errorf("%v: %v", ErrEthServiceRegistrationFailure, err)
|
||||
}
|
||||
|
||||
// start Whisper service
|
||||
if err := activateShhService(stack, config); err != nil {
|
||||
Fatalf(fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err))
|
||||
return nil, fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err)
|
||||
}
|
||||
|
||||
return &Node{
|
||||
geth: stack,
|
||||
gethConfig: stackConfig,
|
||||
started: make(chan struct{}),
|
||||
config: config,
|
||||
}
|
||||
return stack, nil
|
||||
}
|
||||
|
||||
// defaultEmbeddedNodeConfig returns default stack configuration for mobile client node
|
||||
|
@ -135,6 +108,7 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
|
|||
DataDir: config.DataDir,
|
||||
KeyStoreDir: config.KeyStoreDir,
|
||||
UseLightweightKDF: true,
|
||||
NoUSB: true,
|
||||
Name: config.Name,
|
||||
Version: config.Version,
|
||||
P2P: p2p.Config{
|
||||
|
@ -162,26 +136,90 @@ func defaultEmbeddedNodeConfig(config *params.NodeConfig) *node.Config {
|
|||
|
||||
// updateCHT changes trusted canonical hash trie root
|
||||
func updateCHT(eth *les.LightEthereum, config *params.NodeConfig) {
|
||||
// 0xabaa042dec1ee30e0e8323d010a9c7d9a09b848631acdf66f66e966903b67755
|
||||
bc := eth.BlockChain()
|
||||
|
||||
// TODO: Remove this thing as this is an ugly hack.
|
||||
// Once CHT sync sub-protocol is working in LES, we will rely on it, as it provides
|
||||
// decentralized solution. For now, in order to avoid forcing users to long sync times
|
||||
// we use central static resource
|
||||
type MsgCHTRoot struct {
|
||||
GenesisHash string `json:"net"`
|
||||
Number uint64 `json:"number"`
|
||||
Prod string `json:"prod"`
|
||||
Dev string `json:"dev"`
|
||||
}
|
||||
loadCHTLists := func() ([]MsgCHTRoot, error) {
|
||||
url := "https://gist.githubusercontent.com/farazdagi/a8d36e2818b3b2b6074d691da63a0c36/raw/?u=" + strconv.Itoa(int(time.Now().Unix()))
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
r, err := client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var roots []MsgCHTRoot
|
||||
err = json.NewDecoder(r.Body).Decode(&roots)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return roots, nil
|
||||
}
|
||||
if roots, err := loadCHTLists(); err == nil {
|
||||
for _, root := range roots {
|
||||
if bc.Genesis().Hash().Hex() == root.GenesisHash {
|
||||
log.Info("Loaded root", "root", root)
|
||||
if root.Number == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
chtRoot := root.Prod
|
||||
if config.DevMode {
|
||||
chtRoot = root.Dev
|
||||
}
|
||||
eth.WriteTrustedCht(light.TrustedCht{
|
||||
Number: root.Number,
|
||||
Root: gethcommon.HexToHash(chtRoot),
|
||||
})
|
||||
log.Info("Loaded CHT from net", "CHT", chtRoot, "number", root.Number)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resort to manually updated
|
||||
log.Info("Loading CHT from net failed, setting manually")
|
||||
if bc.Genesis().Hash() == params.MainNetGenesisHash {
|
||||
eth.WriteTrustedCht(light.TrustedCht{
|
||||
Number: 805,
|
||||
Root: common.HexToHash("85e4286fe0a730390245c49de8476977afdae0eb5530b277f62a52b12313d50f"),
|
||||
Root: gethcommon.HexToHash("85e4286fe0a730390245c49de8476977afdae0eb5530b277f62a52b12313d50f"),
|
||||
})
|
||||
log.Info("Added trusted CHT for mainnet")
|
||||
}
|
||||
|
||||
if bc.Genesis().Hash() == params.RopstenNetGenesisHash {
|
||||
root := "28bcafd5504326a34995efc36d3a9ba0b6a22f5832e8e58bacb646b54cb8911a"
|
||||
root := "fa851b5252cc48ab55f375833b0344cc5c7cacea69be7e2a57976c38d3bb3aef"
|
||||
if config.DevMode {
|
||||
root = "abaa042dec1ee30e0e8323d010a9c7d9a09b848631acdf66f66e966903b67755"
|
||||
root = "f2f862314509b22a773eedaaa7fa6452474eb71a3b72525a97dbf5060cbea88f"
|
||||
}
|
||||
eth.WriteTrustedCht(light.TrustedCht{
|
||||
Number: 226,
|
||||
Root: common.HexToHash(root),
|
||||
Number: 239,
|
||||
Root: gethcommon.HexToHash(root),
|
||||
})
|
||||
log.Info("Added trusted CHT for Ropsten", "CHT", root)
|
||||
}
|
||||
|
||||
//if bc.Genesis().Hash() == params.RinkebyNetGenesisHash {
|
||||
// root := "0xb100882d00a09292f15e712707649b24d019a47a509e83a00530ac542425c3bd"
|
||||
// if config.DevMode {
|
||||
// root = "0xb100882d00a09292f15e712707649b24d019a47a509e83a00530ac542425c3bd"
|
||||
// }
|
||||
// eth.WriteTrustedCht(light.TrustedCht{
|
||||
// Number: 55,
|
||||
// Root: gethcommon.HexToHash(root),
|
||||
// })
|
||||
// log.Info("Added trusted CHT for Rinkeby", "CHT", root)
|
||||
//}
|
||||
}
|
||||
|
||||
// activateEthService configures and registers the eth.Ethereum service with a given node.
|
||||
|
@ -205,7 +243,6 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
|
|||
ethConf.NetworkId = config.NetworkID
|
||||
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
|
||||
ethConf.MaxPeers = config.MaxPeers
|
||||
ethConf.DatabaseHandles = makeDatabaseHandles()
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
lightEth, err := les.New(ctx, ðConf)
|
||||
if err == nil {
|
||||
|
@ -273,37 +310,6 @@ func makeWSHost(config *params.NodeConfig) string {
|
|||
return config.WSHost
|
||||
}
|
||||
|
||||
// makeDatabaseHandles makes sure that enough file descriptors are available to the process
|
||||
// (and returns half of them for node's database to use)
|
||||
func makeDatabaseHandles() int {
|
||||
// current limit
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
Fatalf(err)
|
||||
}
|
||||
|
||||
// increase limit
|
||||
limit.Cur = limit.Max
|
||||
if limit.Cur > params.DefaultFileDescriptorLimit {
|
||||
limit.Cur = params.DefaultFileDescriptorLimit
|
||||
}
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
Fatalf(err)
|
||||
}
|
||||
|
||||
// re-query limit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
Fatalf(err)
|
||||
}
|
||||
|
||||
// cap limit
|
||||
if limit.Cur > params.DefaultFileDescriptorLimit {
|
||||
limit.Cur = params.DefaultFileDescriptorLimit
|
||||
}
|
||||
|
||||
return int(limit.Cur) / 2
|
||||
}
|
||||
|
||||
// makeBootstrapNodes returns default (hence bootstrap) list of peers
|
||||
func makeBootstrapNodes() []*discover.Node {
|
||||
// on desktops params.TestnetBootnodes and params.MainBootnodes,
|
||||
|
@ -331,45 +337,3 @@ func makeBootstrapNodesV5() []*discv5.Node {
|
|||
|
||||
return bootstapNodes
|
||||
}
|
||||
|
||||
// Fatalf is used to halt the execution.
|
||||
// When called the function prints stack end exits.
|
||||
// Failure is logged into both StdErr and StdOut.
|
||||
func Fatalf(reason interface{}, args ...interface{}) {
|
||||
// decide on output stream
|
||||
w := io.MultiWriter(os.Stdout, os.Stderr)
|
||||
outf, _ := os.Stdout.Stat()
|
||||
errf, _ := os.Stderr.Stat()
|
||||
if outf != nil && errf != nil && os.SameFile(outf, errf) {
|
||||
w = os.Stderr
|
||||
}
|
||||
|
||||
// find out whether error or string has been passed as a reason
|
||||
r := reflect.ValueOf(reason)
|
||||
if r.Kind() == reflect.String {
|
||||
fmt.Fprintf(w, "Fatal Failure: "+reason.(string)+"\n", args)
|
||||
} else {
|
||||
fmt.Fprintf(w, "Fatal Failure: %v\n", reason.(error))
|
||||
}
|
||||
|
||||
debug.PrintStack()
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// HaltOnPanic recovers from panic, logs issue, sends upward notification, and exits
|
||||
func HaltOnPanic() {
|
||||
if r := recover(); r != nil {
|
||||
err := fmt.Errorf("%v: %v", ErrNodeRunFailure, r)
|
||||
|
||||
// send signal up to native app
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeCrashed,
|
||||
Event: NodeCrashEvent{
|
||||
Error: err.Error(),
|
||||
},
|
||||
})
|
||||
|
||||
Fatalf(err) // os.exit(1) is called internally
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package node
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
extern bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventNodeStarted is triggered when underlying node is started
|
||||
EventNodeStarted = "node.started"
|
||||
|
||||
// EventNodeReady is triggered when underlying node is fully ready
|
||||
// (consider backend to be fully registered)
|
||||
EventNodeReady = "node.ready"
|
||||
|
||||
// EventNodeStopped is triggered when underlying node is fully stopped
|
||||
EventNodeStopped = "node.stopped"
|
||||
|
||||
// EventNodeCrashed is triggered when node crashes
|
||||
EventNodeCrashed = "node.crashed"
|
||||
|
||||
// EventChainDataRemoved is triggered when node's chain data is removed
|
||||
EventChainDataRemoved = "chaindata.removed"
|
||||
)
|
||||
|
||||
// SignalEnvelope is a general signal sent upward from node to RN app
|
||||
type SignalEnvelope struct {
|
||||
Type string `json:"type"`
|
||||
Event interface{} `json:"event"`
|
||||
}
|
||||
|
||||
// NodeCrashEvent is special kind of error, used to report node crashes
|
||||
type NodeCrashEvent struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// NodeNotificationHandler defines a handler able to process incoming node events.
|
||||
// Events are encoded as JSON strings.
|
||||
type NodeNotificationHandler func(jsonEvent string)
|
||||
|
||||
var notificationHandler NodeNotificationHandler = TriggerDefaultNodeNotificationHandler
|
||||
|
||||
// SetDefaultNodeNotificationHandler sets notification handler to invoke on SendSignal
|
||||
func SetDefaultNodeNotificationHandler(fn NodeNotificationHandler) {
|
||||
notificationHandler = fn
|
||||
}
|
||||
|
||||
// TriggerDefaultNodeNotificationHandler triggers default notification handler (helpful in tests)
|
||||
func TriggerDefaultNodeNotificationHandler(jsonEvent string) {
|
||||
log.Info("Notification received", "event", jsonEvent)
|
||||
}
|
||||
|
||||
// SendSignal sends application signal (JSON, normally) upwards to application (via default notification handler)
|
||||
func SendSignal(signal SignalEnvelope) {
|
||||
data, _ := json.Marshal(&signal)
|
||||
C.StatusServiceSignalEvent(C.CString(string(data)))
|
||||
}
|
||||
|
||||
//export NotifyNode
|
||||
func NotifyNode(jsonEvent *C.char) { // nolint: golint
|
||||
notificationHandler(C.GoString(jsonEvent))
|
||||
}
|
||||
|
||||
//export TriggerTestSignal
|
||||
func TriggerTestSignal() { // nolint: golint
|
||||
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventTransactionQueued is triggered when send transaction request is queued
|
||||
EventTransactionQueued = "transaction.queued"
|
||||
|
||||
// EventTransactionFailed is triggered when send transaction request fails
|
||||
EventTransactionFailed = "transaction.failed"
|
||||
|
||||
// SendTxDefaultErrorCode is sent by default, when error is not nil, but type is unknown/unexpected.
|
||||
SendTxDefaultErrorCode = SendTransactionDefaultErrorCode
|
||||
)
|
||||
|
||||
// Send transaction response codes
|
||||
const (
|
||||
SendTransactionNoErrorCode = "0"
|
||||
SendTransactionDefaultErrorCode = "1"
|
||||
SendTransactionPasswordErrorCode = "2"
|
||||
SendTransactionTimeoutErrorCode = "3"
|
||||
SendTransactionDiscardedErrorCode = "4"
|
||||
)
|
||||
|
||||
var txReturnCodes = map[error]string{ // deliberately strings, in case more meaningful codes are to be returned
|
||||
nil: SendTransactionNoErrorCode,
|
||||
keystore.ErrDecrypt: SendTransactionPasswordErrorCode,
|
||||
status.ErrQueuedTxTimedOut: SendTransactionTimeoutErrorCode,
|
||||
status.ErrQueuedTxDiscarded: SendTransactionDiscardedErrorCode,
|
||||
}
|
||||
|
||||
// TxQueueManager provides means to manage internal Status Backend (injected into LES)
|
||||
type TxQueueManager struct {
|
||||
nodeManager common.NodeManager
|
||||
accountManager common.AccountManager
|
||||
}
|
||||
|
||||
func NewTxQueueManager(nodeManager common.NodeManager, accountManager common.AccountManager) *TxQueueManager {
|
||||
return &TxQueueManager{
|
||||
nodeManager: nodeManager,
|
||||
accountManager: accountManager,
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func (m *TxQueueManager) CompleteTransaction(id, password string) (gethcommon.Hash, error) {
|
||||
lightEthereum, err := m.nodeManager.LightEthereumService()
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
selectedAccount, err := m.accountManager.SelectedAccount()
|
||||
if err != nil {
|
||||
return gethcommon.Hash{}, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, status.SelectedAccountKey, selectedAccount.Hex())
|
||||
|
||||
return backend.CompleteQueuedTransaction(ctx, status.QueuedTxID(id), password)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func (m *TxQueueManager) CompleteTransactions(ids, password string) map[string]common.RawCompleteTransactionResult {
|
||||
results := make(map[string]common.RawCompleteTransactionResult)
|
||||
|
||||
parsedIDs, err := common.ParseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = common.RawCompleteTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txID := range parsedIDs {
|
||||
txHash, txErr := m.CompleteTransaction(txID, password)
|
||||
results[txID] = common.RawCompleteTransactionResult{
|
||||
Hash: txHash,
|
||||
Error: txErr,
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func (m *TxQueueManager) DiscardTransaction(id string) error {
|
||||
lightEthereum, err := m.nodeManager.LightEthereumService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
return backend.DiscardQueuedTransaction(status.QueuedTxID(id))
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func (m *TxQueueManager) DiscardTransactions(ids string) map[string]common.RawDiscardTransactionResult {
|
||||
var parsedIDs []string
|
||||
results := make(map[string]common.RawDiscardTransactionResult)
|
||||
|
||||
parsedIDs, err := common.ParseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = common.RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txID := range parsedIDs {
|
||||
err := m.DiscardTransaction(txID)
|
||||
if err != nil {
|
||||
results[txID] = common.RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// SendTransactionEvent is a signal sent on a send transaction request
|
||||
type SendTransactionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Args status.SendTxArgs `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
}
|
||||
|
||||
// TransactionQueueHandler returns handler that processes incoming tx queue requests
|
||||
func (m *TxQueueManager) TransactionQueueHandler() func(queuedTx status.QueuedTx) {
|
||||
return func(queuedTx status.QueuedTx) {
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventTransactionQueued,
|
||||
Event: SendTransactionEvent{
|
||||
ID: string(queuedTx.ID),
|
||||
Args: queuedTx.Args,
|
||||
MessageID: common.MessageIDFromContext(queuedTx.Context),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
|
||||
type ReturnSendTransactionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Args status.SendTxArgs `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
ErrorCode string `json:"error_code"`
|
||||
}
|
||||
|
||||
// TransactionReturnHandler returns handler that processes responses from internal tx manager
|
||||
func (m *TxQueueManager) TransactionReturnHandler() func(queuedTx *status.QueuedTx, err error) {
|
||||
return func(queuedTx *status.QueuedTx, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// discard notifications with empty tx
|
||||
if queuedTx == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// error occurred, signal up to application
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventTransactionFailed,
|
||||
Event: ReturnSendTransactionEvent{
|
||||
ID: string(queuedTx.ID),
|
||||
Args: queuedTx.Args,
|
||||
MessageID: common.MessageIDFromContext(queuedTx.Context),
|
||||
ErrorMessage: err.Error(),
|
||||
ErrorCode: m.sendTransactionErrorCode(err),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TxQueueManager) sendTransactionErrorCode(err error) string {
|
||||
if code, ok := txReturnCodes[err]; ok {
|
||||
return code
|
||||
}
|
||||
|
||||
return SendTxDefaultErrorCode
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/geth/common"
|
||||
)
|
||||
|
||||
// HaltOnPanic recovers from panic, logs issue, sends upward notification, and exits
|
||||
func HaltOnPanic() {
|
||||
if r := recover(); r != nil {
|
||||
err := fmt.Errorf("%v: %v", ErrNodeRunFailure, r)
|
||||
|
||||
// send signal up to native app
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeCrashed,
|
||||
Event: NodeCrashEvent{
|
||||
Error: err.Error(),
|
||||
},
|
||||
})
|
||||
|
||||
common.Fatalf(err) // os.exit(1) is called internally
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package node_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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/node"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
func TestWhisperTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(WhisperTestSuite))
|
||||
}
|
||||
|
||||
type WhisperTestSuite struct {
|
||||
BaseTestSuite
|
||||
}
|
||||
|
||||
func (s *WhisperTestSuite) SetupTest() {
|
||||
s.NodeManager = node.NewNodeManager()
|
||||
s.Require().NotNil(s.NodeManager)
|
||||
s.Require().IsType(&node.NodeManager{}, s.NodeManager)
|
||||
}
|
||||
|
||||
func (s *WhisperTestSuite) TestWhisperFilterRace() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
s.StartTestNode(params.RinkebyNetworkID)
|
||||
defer s.StopTestNode()
|
||||
|
||||
whisperService, err := s.NodeManager.WhisperService()
|
||||
require.NoError(err)
|
||||
require.NotNil(whisperService)
|
||||
|
||||
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
|
||||
require.NotNil(whisperAPI)
|
||||
|
||||
accountManager := node.NewAccountManager(s.NodeManager)
|
||||
require.NotNil(accountManager)
|
||||
|
||||
// account1
|
||||
_, accountKey1, err := accountManager.AddressToDecryptedAccount(TestConfig.Account1.Address, TestConfig.Account1.Password)
|
||||
require.NoError(err)
|
||||
accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
|
||||
|
||||
_, err = whisperService.AddKeyPair(accountKey1.PrivateKey)
|
||||
require.NoError(err)
|
||||
ok, err := whisperAPI.HasKeyPair(accountKey1Hex)
|
||||
require.NoError(err)
|
||||
require.True(ok, "identity not injected")
|
||||
|
||||
// account2
|
||||
_, accountKey2, err := accountManager.AddressToDecryptedAccount(TestConfig.Account2.Address, TestConfig.Account2.Password)
|
||||
require.NoError(err)
|
||||
accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey))
|
||||
_, err = whisperService.AddKeyPair(accountKey2.PrivateKey)
|
||||
require.NoError(err)
|
||||
ok, err = whisperAPI.HasKeyPair(accountKey2Hex)
|
||||
require.NoError(err)
|
||||
require.True(ok, "identity not injected")
|
||||
|
||||
// race filter addition
|
||||
filterAdded := make(chan struct{})
|
||||
allFiltersAdded := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
counter := 10
|
||||
for range filterAdded {
|
||||
counter--
|
||||
if counter <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
close(allFiltersAdded)
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
// nolint: errcheck
|
||||
whisperAPI.Subscribe(whisper.WhisperFilterArgs{
|
||||
Sig: accountKey1Hex,
|
||||
Key: accountKey2Hex,
|
||||
Topics: [][]byte{
|
||||
{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9},
|
||||
},
|
||||
})
|
||||
filterAdded <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
<-allFiltersAdded
|
||||
}
|
|
@ -1,438 +0,0 @@
|
|||
package geth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
// SelectedExtKey is a container for currently selected (logged in) account
|
||||
type SelectedExtKey struct {
|
||||
Address common.Address
|
||||
AccountKey *keystore.Key
|
||||
SubAccounts []accounts.Account
|
||||
}
|
||||
|
||||
// NodeManager manages Status node (which abstracts contained geth node)
|
||||
type NodeManager struct {
|
||||
node *Node // reference to Status node
|
||||
services *NodeServiceStack // default stack of services running on geth node
|
||||
api *node.PrivateAdminAPI // exposes collection of administrative API methods
|
||||
SelectedAccount *SelectedExtKey // account that was processed during the last call to SelectAccount()
|
||||
}
|
||||
|
||||
// NodeServiceStack contains "standard" node services (which are always available)
|
||||
type NodeServiceStack struct {
|
||||
lightEthereum *les.LightEthereum // LES service
|
||||
whisperService *whisper.Whisper // Whisper service
|
||||
rpcClient *rpc.Client // RPC client
|
||||
jailedRequestQueue *JailedRequestQueue // bridge via which jail notifies node of incoming requests
|
||||
}
|
||||
|
||||
// errors
|
||||
var (
|
||||
ErrInvalidGethNode = errors.New("no running geth node detected")
|
||||
ErrInvalidAccountManager = errors.New("could not retrieve account manager")
|
||||
ErrInvalidWhisperService = errors.New("whisper service is unavailable")
|
||||
ErrInvalidLightEthereumService = errors.New("can not retrieve LES service")
|
||||
ErrInvalidClient = errors.New("RPC client is not properly initialized")
|
||||
ErrInvalidJailedRequestQueue = errors.New("jailed request queue is not properly initialized")
|
||||
ErrNodeMakeFailure = errors.New("error creating p2p node")
|
||||
ErrNodeStartFailure = errors.New("error starting p2p node")
|
||||
ErrNodeRunFailure = errors.New("error running p2p node")
|
||||
ErrInvalidNodeAPI = errors.New("no node API connected")
|
||||
ErrAccountKeyStoreMissing = errors.New("account key store is not set")
|
||||
)
|
||||
|
||||
var (
|
||||
nodeManagerInstance *NodeManager
|
||||
createOnce sync.Once
|
||||
)
|
||||
|
||||
// CreateAndRunNode creates and starts running Geth node locally (exposing given RPC port along the way)
|
||||
func CreateAndRunNode(config *params.NodeConfig) error {
|
||||
defer HaltOnPanic()
|
||||
|
||||
nodeManager := NewNodeManager(config)
|
||||
|
||||
if nodeManager.NodeInited() {
|
||||
nodeManager.RunNode()
|
||||
nodeManager.WaitNodeStarted()
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNodeStartFailure
|
||||
}
|
||||
|
||||
// NewNodeManager makes new instance of node manager
|
||||
func NewNodeManager(config *params.NodeConfig) *NodeManager {
|
||||
createOnce.Do(func() {
|
||||
nodeManagerInstance = &NodeManager{
|
||||
services: &NodeServiceStack{
|
||||
jailedRequestQueue: NewJailedRequestsQueue(),
|
||||
},
|
||||
}
|
||||
nodeManagerInstance.node = MakeNode(config)
|
||||
})
|
||||
|
||||
return nodeManagerInstance
|
||||
}
|
||||
|
||||
// NodeManagerInstance exposes node manager instance
|
||||
func NodeManagerInstance() *NodeManager {
|
||||
return nodeManagerInstance
|
||||
}
|
||||
|
||||
// RunNode starts Geth node
|
||||
func (m *NodeManager) RunNode() {
|
||||
go func() {
|
||||
defer HaltOnPanic()
|
||||
|
||||
m.StartNode()
|
||||
|
||||
if _, err := m.AccountManager(); err != nil {
|
||||
log.Warn(ErrInvalidAccountManager.Error())
|
||||
}
|
||||
if err := m.node.geth.Service(&m.services.whisperService); err != nil {
|
||||
log.Warn("cannot get whisper service", "error", err)
|
||||
}
|
||||
if err := m.node.geth.Service(&m.services.lightEthereum); err != nil {
|
||||
log.Warn("cannot get light ethereum service", "error", err)
|
||||
}
|
||||
|
||||
// setup handlers
|
||||
if lightEthereum, err := m.LightEthereumService(); err == nil {
|
||||
lightEthereum.StatusBackend.SetTransactionQueueHandler(onSendTransactionRequest)
|
||||
lightEthereum.StatusBackend.SetAccountsFilterHandler(onAccountsListRequest)
|
||||
lightEthereum.StatusBackend.SetTransactionReturnHandler(onSendTransactionReturn)
|
||||
}
|
||||
|
||||
var err error
|
||||
m.services.rpcClient, err = m.node.geth.Attach()
|
||||
if err != nil {
|
||||
log.Warn("cannot get RPC client service", "error", ErrInvalidClient)
|
||||
}
|
||||
|
||||
// expose API
|
||||
m.api = node.NewPrivateAdminAPI(m.node.geth)
|
||||
|
||||
m.PopulateStaticPeers()
|
||||
|
||||
m.onNodeStarted() // node started, notify listeners
|
||||
m.node.geth.Wait()
|
||||
|
||||
log.Info("node stopped")
|
||||
}()
|
||||
}
|
||||
|
||||
// StartNode starts running P2P node
|
||||
func (m *NodeManager) StartNode() {
|
||||
if m == nil || !m.NodeInited() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
|
||||
if err := m.node.geth.Start(); err != nil {
|
||||
panic(fmt.Sprintf("%v: %v", ErrNodeStartFailure, err))
|
||||
}
|
||||
|
||||
if server := m.node.geth.Server(); server != nil {
|
||||
if nodeInfo := server.NodeInfo(); nodeInfo != nil {
|
||||
log.Info(nodeInfo.Enode)
|
||||
}
|
||||
}
|
||||
|
||||
// allow interrupting running nodes
|
||||
go func() {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, os.Interrupt)
|
||||
defer signal.Stop(sigc)
|
||||
<-sigc
|
||||
log.Info("Got interrupt, shutting down...")
|
||||
go m.node.geth.Stop() // nolint: errcheck
|
||||
for i := 3; i > 0; i-- {
|
||||
<-sigc
|
||||
if i > 1 {
|
||||
log.Info(fmt.Sprintf("Already shutting down, interrupt %d more times for panic.", i-1))
|
||||
}
|
||||
}
|
||||
panic("interrupted!")
|
||||
}()
|
||||
}
|
||||
|
||||
// StopNode stops running P2P node
|
||||
func (m *NodeManager) StopNode() error {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if err := m.node.geth.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
m.node.started = make(chan struct{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartNode restarts P2P node
|
||||
func (m *NodeManager) RestartNode() error {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if err := m.StopNode(); err != nil {
|
||||
return err
|
||||
}
|
||||
m.RunNode()
|
||||
m.WaitNodeStarted()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResumeNode resumes previously stopped P2P node
|
||||
func (m *NodeManager) ResumeNode() error {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
|
||||
m.RunNode()
|
||||
m.WaitNodeStarted()
|
||||
|
||||
return ReSelectAccount()
|
||||
}
|
||||
|
||||
// ResetChainData purges chain data (by removing data directory). Safe to apply on running P2P node.
|
||||
func (m *NodeManager) ResetChainData() error {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if err := m.StopNode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chainDataDir := filepath.Join(m.node.gethConfig.DataDir, m.node.gethConfig.Name, "lightchaindata")
|
||||
if _, err := os.Stat(chainDataDir); os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(chainDataDir); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("chaindata removed", "dir", chainDataDir)
|
||||
|
||||
return m.ResumeNode()
|
||||
}
|
||||
|
||||
// StartNodeRPCServer starts HTTP RPC server
|
||||
func (m *NodeManager) StartNodeRPCServer() (bool, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return false, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.api == nil {
|
||||
return false, ErrInvalidNodeAPI
|
||||
}
|
||||
|
||||
config := m.node.gethConfig
|
||||
modules := strings.Join(config.HTTPModules, ",")
|
||||
cors := strings.Join(config.HTTPCors, ",")
|
||||
|
||||
return m.api.StartRPC(&config.HTTPHost, &config.HTTPPort, &cors, &modules)
|
||||
}
|
||||
|
||||
// StopNodeRPCServer stops HTTP RPC server attached to node
|
||||
func (m *NodeManager) StopNodeRPCServer() (bool, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return false, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.api == nil {
|
||||
return false, ErrInvalidNodeAPI
|
||||
}
|
||||
|
||||
return m.api.StopRPC()
|
||||
}
|
||||
|
||||
// NodeInited checks whether manager has initialized node attached
|
||||
func (m *NodeManager) NodeInited() bool {
|
||||
if m == nil || !m.node.Inited() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Node returns attached node if it has been initialized
|
||||
func (m *NodeManager) Node() *Node {
|
||||
if !m.NodeInited() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.node
|
||||
}
|
||||
|
||||
// AccountManager exposes reference to accounts manager
|
||||
func (m *NodeManager) AccountManager() (*accounts.Manager, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
return m.node.geth.AccountManager(), nil
|
||||
}
|
||||
|
||||
// AccountKeyStore exposes reference to accounts key store
|
||||
func (m *NodeManager) AccountKeyStore() (*keystore.KeyStore, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
accountManager, err := m.AccountManager()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backends := accountManager.Backends(keystore.KeyStoreType)
|
||||
if len(backends) == 0 {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
keyStore, ok := backends[0].(*keystore.KeyStore)
|
||||
if !ok {
|
||||
return nil, ErrAccountKeyStoreMissing
|
||||
}
|
||||
|
||||
return keyStore, nil
|
||||
}
|
||||
|
||||
// LightEthereumService exposes LES
|
||||
// nolint: dupl
|
||||
func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.services.lightEthereum == nil {
|
||||
return nil, ErrInvalidLightEthereumService
|
||||
}
|
||||
|
||||
return m.services.lightEthereum, nil
|
||||
}
|
||||
|
||||
// WhisperService exposes Whisper service
|
||||
// nolint: dupl
|
||||
func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.services.whisperService == nil {
|
||||
return nil, ErrInvalidWhisperService
|
||||
}
|
||||
|
||||
return m.services.whisperService, nil
|
||||
}
|
||||
|
||||
// RPCClient exposes Geth's RPC client
|
||||
// nolint: dupl
|
||||
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.services.rpcClient == nil {
|
||||
return nil, ErrInvalidClient
|
||||
}
|
||||
|
||||
return m.services.rpcClient, nil
|
||||
}
|
||||
|
||||
// JailedRequestQueue exposes reference to queue of jailed requests
|
||||
func (m *NodeManager) JailedRequestQueue() (*JailedRequestQueue, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return nil, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
if m.services.jailedRequestQueue == nil {
|
||||
return nil, ErrInvalidJailedRequestQueue
|
||||
}
|
||||
|
||||
return m.services.jailedRequestQueue, nil
|
||||
}
|
||||
|
||||
// AddPeer adds new peer node
|
||||
func (m *NodeManager) AddPeer(url string) (bool, error) {
|
||||
if m == nil || !m.NodeInited() {
|
||||
return false, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
server := m.node.geth.Server()
|
||||
if server == nil {
|
||||
return false, ErrInvalidGethNode
|
||||
}
|
||||
|
||||
// Try to add the url as a static peer and return
|
||||
parsedNode, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid enode: %v", err)
|
||||
}
|
||||
server.AddPeer(parsedNode)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// WaitNodeStarted blocks until node is started (start channel gets notified)
|
||||
func (m *NodeManager) WaitNodeStarted() {
|
||||
<-m.node.started // block until node is started
|
||||
}
|
||||
|
||||
// onNodeStarted sends upward notification letting the app know that Geth node is ready to be used
|
||||
func (m *NodeManager) onNodeStarted() {
|
||||
// notify local listener
|
||||
m.node.started <- struct{}{}
|
||||
close(m.node.started)
|
||||
|
||||
// send signal up to native app
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventNodeStarted,
|
||||
Event: struct{}{},
|
||||
})
|
||||
}
|
||||
|
||||
// PopulateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
|
||||
func (m *NodeManager) PopulateStaticPeers() {
|
||||
if !m.node.config.BootClusterConfig.Enabled {
|
||||
log.Info("Boot cluster is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
enodes, err := m.node.config.LoadBootClusterNodes()
|
||||
if err != nil {
|
||||
log.Warn("Can not load boot nodes", "error", err)
|
||||
}
|
||||
for _, enode := range enodes {
|
||||
m.AddPeer(enode) // nolint: errcheck
|
||||
log.Info("Boot node added", "enode", enode)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package geth_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/geth"
|
||||
)
|
||||
|
||||
var testConfig *geth.TestConfig
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// load shared test configuration
|
||||
var err error
|
||||
testConfig, err = geth.LoadTestConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// run tests
|
||||
retCode := m.Run()
|
||||
|
||||
//time.Sleep(25 * time.Second) // to give some time to propagate txs to the rest of the network
|
||||
os.Exit(retCode)
|
||||
}
|
||||
|
||||
func TestResetChainData(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := geth.NodeManagerInstance().ResetChainData(); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// allow some time to re-sync
|
||||
time.Sleep(testConfig.Node.SyncSeconds * time.Second)
|
||||
|
||||
// now make sure that everything is intact
|
||||
TestQueuedTransactions(t)
|
||||
}
|
|
@ -109,7 +109,7 @@ type SwarmConfig struct {
|
|||
Enabled bool
|
||||
}
|
||||
|
||||
// BootCluster holds configuration for supporting boot cluster, which is a temporary
|
||||
// BootClusterConfig holds configuration for supporting boot cluster, which is a temporary
|
||||
// means for mobile devices to get connected to Ethereum network (UDP-based discovery
|
||||
// may not be available, so we need means to discover the network manually).
|
||||
type BootClusterConfig struct {
|
||||
|
@ -329,7 +329,7 @@ func (c *NodeConfig) LoadBootClusterNodes() ([]string, error) {
|
|||
}
|
||||
|
||||
// parse JSON
|
||||
if err := json.Unmarshal([]byte(configData), &bootnodes); err != nil {
|
||||
if err := json.Unmarshal(configData, &bootnodes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bootnodes, nil
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
gethparams "github.com/ethereum/go-ethereum/params"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
. "github.com/status-im/status-go/geth/testing"
|
||||
)
|
||||
|
||||
var loadConfigTestCases = []struct {
|
||||
|
@ -312,7 +312,7 @@ var loadConfigTestCases = []struct {
|
|||
params.BootClusterConfigFile, nodeConfig.BootClusterConfig.ConfigFile)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("boot cluster is expected to be enabled by default")
|
||||
}
|
||||
|
||||
|
@ -349,7 +349,7 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != false {
|
||||
if nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("boot cluster is expected to be disabled")
|
||||
}
|
||||
},
|
||||
|
@ -588,11 +588,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != true {
|
||||
if !nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", true, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -614,11 +614,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != false {
|
||||
if nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", false, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -641,11 +641,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != true {
|
||||
if !nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", true, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -668,11 +668,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != false {
|
||||
if nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", false, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -694,11 +694,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != true {
|
||||
if !nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", true, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -721,11 +721,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != false {
|
||||
if nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", false, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -747,11 +747,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != true {
|
||||
if !nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", true, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -774,11 +774,11 @@ var loadConfigTestCases = []struct {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if nodeConfig.DevMode != false {
|
||||
if nodeConfig.DevMode {
|
||||
t.Fatalf("unexpected dev mode: expected: %v, got: %v", false, nodeConfig.DevMode)
|
||||
}
|
||||
|
||||
if nodeConfig.BootClusterConfig.Enabled != true {
|
||||
if !nodeConfig.BootClusterConfig.Enabled {
|
||||
t.Fatal("expected boot cluster to be enabled")
|
||||
}
|
||||
|
||||
|
@ -835,7 +835,7 @@ func TestConfigWriteRead(t *testing.T) {
|
|||
t.Fatalf("cannot read configuration from disk: %v", err)
|
||||
}
|
||||
|
||||
refConfigData := geth.LoadFromFile(refFile)
|
||||
refConfigData := LoadFromFile(refFile)
|
||||
|
||||
refConfigData = strings.Replace(refConfigData, "$TMPDIR", nodeConfig.DataDir, -1)
|
||||
refConfigData = strings.Replace(refConfigData, "$VERSION", params.Version, -1)
|
||||
|
|
|
@ -49,7 +49,7 @@ const (
|
|||
DefaultFileDescriptorLimit = uint64(2048)
|
||||
|
||||
// DatabaseCache is memory (in MBs) allocated to internal caching (min 16MB / database forced)
|
||||
DatabaseCache = 128
|
||||
DatabaseCache = 16
|
||||
|
||||
// LogFile defines where to write logs to
|
||||
LogFile = "geth.log"
|
||||
|
@ -57,6 +57,10 @@ const (
|
|||
// LogLevel defines the minimum log level to report
|
||||
LogLevel = "INFO"
|
||||
|
||||
// LogLevelSuccinct defines the log level when only errors are reported.
|
||||
// Useful when the default INFO level becomes too verbose.
|
||||
LogLevelSuccinct = "ERROR"
|
||||
|
||||
// LogToStderr defines whether logged info should also be output to os.Stderr
|
||||
LogToStderr = true
|
||||
|
||||
|
@ -89,6 +93,7 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
RopstenNetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") // Testnet genesis hash to enforce below configs on
|
||||
MainNetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") // Mainnet genesis hash to enforce below configs on
|
||||
RopstenNetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||
RinkebyNetGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
MainNetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||
)
|
||||
|
|
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
|
@ -0,0 +1,140 @@
|
|||
package testing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
var (
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
type BaseTestSuite struct {
|
||||
suite.Suite
|
||||
NodeManager common.NodeManager
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) StartTestNode(networkID int) {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
nodeConfig, err := MakeTestNodeConfig(networkID)
|
||||
require.NoError(err)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) StopTestNode() {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
require.True(s.NodeManager.IsNodeRunning())
|
||||
require.NoError(s.NodeManager.StopNode())
|
||||
require.False(s.NodeManager.IsNodeRunning())
|
||||
}
|
||||
|
||||
func (s *BaseTestSuite) FirstBlockHash(expectedHash string) {
|
||||
require := s.Require()
|
||||
require.NotNil(s.NodeManager)
|
||||
|
||||
var firstBlock struct {
|
||||
Hash gethcommon.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
// obtain RPC client for running node
|
||||
runningNode, err := s.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)
|
||||
|
||||
s.Equal(expectedHash, firstBlock.Hash.Hex())
|
||||
}
|
||||
|
||||
func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
|
||||
configJSON := `{
|
||||
"NetworkId": ` + strconv.Itoa(networkID) + `,
|
||||
"DataDir": "` + filepath.Join(TestDataDir, TestNetworkNames[networkID]) + `",
|
||||
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
|
||||
"WSPort": ` + strconv.Itoa(TestConfig.Node.WSPort) + `,
|
||||
"LogEnabled": true,
|
||||
"LogLevel": "ERROR"
|
||||
}`
|
||||
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())
|
||||
}
|
385
geth/txqueue.go
385
geth/txqueue.go
|
@ -1,385 +0,0 @@
|
|||
package geth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
const (
|
||||
// EventTransactionQueued is triggered whan send transaction request is queued
|
||||
EventTransactionQueued = "transaction.queued"
|
||||
|
||||
// EventTransactionFailed is triggered when send transaction request fails
|
||||
EventTransactionFailed = "transaction.failed"
|
||||
|
||||
// SendTransactionRequest is triggered on send transaction request
|
||||
SendTransactionRequest = "eth_sendTransaction"
|
||||
|
||||
// MessageIDKey is a key for message ID
|
||||
// This ID is required to track from which chat a given send transaction request is coming.
|
||||
MessageIDKey = contextKey("message_id")
|
||||
)
|
||||
|
||||
type contextKey string // in order to make sure that our context key does not collide with keys from other packages
|
||||
|
||||
// Send transaction response codes
|
||||
const (
|
||||
SendTransactionNoErrorCode = "0"
|
||||
SendTransactionDefaultErrorCode = "1"
|
||||
SendTransactionPasswordErrorCode = "2"
|
||||
SendTransactionTimeoutErrorCode = "3"
|
||||
SendTransactionDiscardedErrorCode = "4"
|
||||
)
|
||||
|
||||
func onSendTransactionRequest(queuedTx status.QueuedTx) {
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventTransactionQueued,
|
||||
Event: SendTransactionEvent{
|
||||
ID: string(queuedTx.ID),
|
||||
Args: queuedTx.Args,
|
||||
MessageID: messageIDFromContext(queuedTx.Context),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func onSendTransactionReturn(queuedTx *status.QueuedTx, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// discard notifications with empty tx
|
||||
if queuedTx == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// error occurred, signal up to application
|
||||
SendSignal(SignalEnvelope{
|
||||
Type: EventTransactionFailed,
|
||||
Event: ReturnSendTransactionEvent{
|
||||
ID: string(queuedTx.ID),
|
||||
Args: queuedTx.Args,
|
||||
MessageID: messageIDFromContext(queuedTx.Context),
|
||||
ErrorMessage: err.Error(),
|
||||
ErrorCode: sendTransactionErrorCode(err),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func sendTransactionErrorCode(err error) string {
|
||||
if err == nil {
|
||||
return SendTransactionNoErrorCode
|
||||
}
|
||||
|
||||
switch err {
|
||||
case keystore.ErrDecrypt:
|
||||
return SendTransactionPasswordErrorCode
|
||||
case status.ErrQueuedTxTimedOut:
|
||||
return SendTransactionTimeoutErrorCode
|
||||
case status.ErrQueuedTxDiscarded:
|
||||
return SendTransactionDiscardedErrorCode
|
||||
default:
|
||||
return SendTransactionDefaultErrorCode
|
||||
}
|
||||
}
|
||||
|
||||
// CompleteTransaction instructs backend to complete sending of a given transaction
|
||||
func CompleteTransaction(id, password string) (common.Hash, error) {
|
||||
lightEthereum, err := NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, status.SelectedAccountKey, NodeManagerInstance().SelectedAccount.Hex())
|
||||
|
||||
return backend.CompleteQueuedTransaction(ctx, status.QueuedTxID(id), password)
|
||||
}
|
||||
|
||||
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
||||
func CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult {
|
||||
results := make(map[string]RawCompleteTransactionResult)
|
||||
|
||||
parsedIDs, err := parseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = RawCompleteTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txID := range parsedIDs {
|
||||
txHash, txErr := CompleteTransaction(txID, password)
|
||||
results[txID] = RawCompleteTransactionResult{
|
||||
Hash: txHash,
|
||||
Error: txErr,
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// DiscardTransaction discards a given transaction from transaction queue
|
||||
func DiscardTransaction(id string) error {
|
||||
lightEthereum, err := NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
return backend.DiscardQueuedTransaction(status.QueuedTxID(id))
|
||||
}
|
||||
|
||||
// DiscardTransactions discards given multiple transactions from transaction queue
|
||||
func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
|
||||
var parsedIDs []string
|
||||
results := make(map[string]RawDiscardTransactionResult)
|
||||
|
||||
parsedIDs, err := parseJSONArray(ids)
|
||||
if err != nil {
|
||||
results["none"] = RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
for _, txID := range parsedIDs {
|
||||
err := DiscardTransaction(txID)
|
||||
if err != nil {
|
||||
results[txID] = RawDiscardTransactionResult{
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func messageIDFromContext(ctx context.Context) string {
|
||||
if ctx == nil {
|
||||
return ""
|
||||
}
|
||||
if messageID, ok := ctx.Value(MessageIDKey).(string); ok {
|
||||
return messageID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// JailedRequestQueue is used for allowing request pre and post processing.
|
||||
// Such processing may include validation, injection of params (like message ID) etc
|
||||
type JailedRequestQueue struct{}
|
||||
|
||||
// NewJailedRequestsQueue returns new instance of request queue
|
||||
func NewJailedRequestsQueue() *JailedRequestQueue {
|
||||
return &JailedRequestQueue{}
|
||||
}
|
||||
|
||||
// PreProcessRequest pre-processes a given RPC call to a given Otto VM
|
||||
func (q *JailedRequestQueue) PreProcessRequest(vm *otto.Otto, req RPCCall) (string, error) {
|
||||
messageID := currentMessageID(vm.Context())
|
||||
|
||||
return messageID, nil
|
||||
}
|
||||
|
||||
// PostProcessRequest post-processes a given RPC call to a given Otto VM
|
||||
func (q *JailedRequestQueue) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
|
||||
if len(messageID) > 0 {
|
||||
vm.Call("addContext", nil, messageID, MessageIDKey, messageID) // nolint: errcheck
|
||||
}
|
||||
|
||||
// set extra markers for queued transaction requests
|
||||
if req.Method == SendTransactionRequest {
|
||||
vm.Call("addContext", nil, messageID, SendTransactionRequest, true) // nolint: errcheck
|
||||
}
|
||||
}
|
||||
|
||||
// ProcessSendTransactionRequest processes send transaction request.
|
||||
// Both pre and post processing happens within this function. Pre-processing
|
||||
// happens before transaction is send to backend, and post processing occurs
|
||||
// when backend notifies that transaction sending is complete (either successfully
|
||||
// or with error)
|
||||
func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (common.Hash, error) {
|
||||
// obtain status backend from LES service
|
||||
lightEthereum, err := NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
messageID, err := q.PreProcessRequest(vm, req)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
// onSendTransactionRequest() will use context to obtain and release ticket
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, MessageIDKey, messageID)
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
// invoke post processing
|
||||
q.PostProcessRequest(vm, req, messageID)
|
||||
|
||||
return txHash, nil
|
||||
}
|
||||
|
||||
// currentMessageID looks for `status.message_id` variable in current JS context
|
||||
func currentMessageID(ctx otto.Context) string {
|
||||
if statusObj, ok := ctx.Symbols["status"]; ok {
|
||||
messageID, err := statusObj.Object().Get("message_id")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if messageID, err := messageID.ToString(); err == nil {
|
||||
return messageID
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func sendTxArgsFromRPCCall(req RPCCall) status.SendTxArgs {
|
||||
if req.Method != SendTransactionRequest { // no need to persist extra state for other requests
|
||||
return status.SendTxArgs{}
|
||||
}
|
||||
|
||||
return status.SendTxArgs{
|
||||
From: req.parseFromAddress(),
|
||||
To: req.parseToAddress(),
|
||||
Value: req.parseValue(),
|
||||
Data: req.parseData(),
|
||||
Gas: req.parseGas(),
|
||||
GasPrice: req.parseGasPrice(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r RPCCall) parseFromAddress() common.Address {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return common.HexToAddress("0x")
|
||||
}
|
||||
|
||||
from, ok := params["from"].(string)
|
||||
if !ok {
|
||||
from = "0x"
|
||||
}
|
||||
|
||||
return common.HexToAddress(from)
|
||||
}
|
||||
|
||||
func (r RPCCall) parseToAddress() *common.Address {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
to, ok := params["to"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
address := common.HexToAddress(to)
|
||||
return &address
|
||||
}
|
||||
|
||||
func (r RPCCall) parseData() hexutil.Bytes {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return hexutil.Bytes("0x")
|
||||
}
|
||||
|
||||
data, ok := params["data"].(string)
|
||||
if !ok {
|
||||
data = "0x"
|
||||
}
|
||||
|
||||
byteCode, err := hexutil.Decode(data)
|
||||
if err != nil {
|
||||
byteCode = hexutil.Bytes(data)
|
||||
}
|
||||
|
||||
return byteCode
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseValue() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
//return (*hexutil.Big)(big.NewInt("0x0"))
|
||||
}
|
||||
|
||||
inputValue, ok := params["value"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseGas() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputValue, ok := params["gas"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
// nolint: dupl
|
||||
func (r RPCCall) parseGasPrice() *hexutil.Big {
|
||||
params, ok := r.Params[0].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inputValue, ok := params["gasPrice"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedValue, err := hexutil.DecodeBig(inputValue)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(parsedValue)
|
||||
}
|
||||
|
||||
func parseJSONArray(items string) ([]string, error) {
|
||||
var parsedItems []string
|
||||
err := json.Unmarshal([]byte(items), &parsedItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parsedItems, nil
|
||||
}
|
|
@ -1,887 +0,0 @@
|
|||
package geth_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
"github.com/status-im/status-go/geth"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
)
|
||||
|
||||
func TestQueuedContracts(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// create an account
|
||||
sampleAddress, _, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(5*time.Second, completeQueuedTransaction, "TestQueuedContracts")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = common.Hash{}
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
// the first call will fail (we are not logged in, but trying to complete tx)
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
||||
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
||||
return
|
||||
}
|
||||
|
||||
// the second call will also fail (we are logged in as different user)
|
||||
if err := geth.SelectAccount(sampleAddress, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", sampleAddress)
|
||||
return
|
||||
}
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
||||
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
||||
return
|
||||
}
|
||||
|
||||
// the third call will work as expected (as we are logged in with correct credentials)
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("contract transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
byteCode, err := hexutil.Decode(`0x6060604052341561000c57fe5b5b60a58061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680636ffa1caa14603a575bfe5b3415604157fe5b60556004808035906020019091905050606b565b6040518082815260200191505060405180910390f35b60008160020290505b9190505600a165627a7a72305820ccdadd737e4ac7039963b54cee5e5afb25fa859a275252bdcf06f653155228210029`)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: nil, // marker, contract creation is expected
|
||||
//Value: (*hexutil.Big)(new(big.Int).Mul(big.NewInt(1), common.Ether)),
|
||||
Gas: (*hexutil.Big)(big.NewInt(params.DefaultGas)),
|
||||
Data: byteCode,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: cannot send transaction: %v", err)
|
||||
}
|
||||
|
||||
if txHashCheck.Hex() != txHash.Hex() {
|
||||
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
||||
return
|
||||
}
|
||||
|
||||
<-completeQueuedTransaction
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
t.Error("Test failed: transaction was never queued or completed")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// create an account
|
||||
sampleAddress, _, _, err := geth.CreateAccount(testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Errorf("could not create account: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = geth.Logout(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(5*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = common.Hash{}
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
// the first call will fail (we are not logged in, but trying to complete tx)
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
||||
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
||||
return
|
||||
}
|
||||
|
||||
// the second call will also fail (we are logged in as different user)
|
||||
if err := geth.SelectAccount(sampleAddress, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", sampleAddress)
|
||||
return
|
||||
}
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
|
||||
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
|
||||
return
|
||||
}
|
||||
|
||||
// the third call will work as expected (as we are logged in with correct credentials)
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||
close(completeQueuedTransaction)
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, up until Complete Transaction is called
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: cannot send transaction: %v", err)
|
||||
}
|
||||
|
||||
if txHashCheck.Hex() != txHash.Hex() {
|
||||
t.Errorf("Transaction hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
||||
return
|
||||
}
|
||||
|
||||
<-completeQueuedTransaction
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
t.Error("Test failed: transaction was never queued or completed")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoubleCompleteQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := false
|
||||
txHash := common.Hash{}
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be failed and completed on the second call): {id: %s}\n", txID)
|
||||
|
||||
// try with wrong password
|
||||
// make sure that tx is NOT removed from the queue (by re-trying with the correct password)
|
||||
if _, err = geth.CompleteTransaction(txID, testConfig.Account1.Password+"wrong"); err != keystore.ErrDecrypt {
|
||||
t.Errorf("expects wrong password error, but call succeeded (or got another error: %v)", err)
|
||||
return
|
||||
}
|
||||
|
||||
if txCount := backend.TransactionQueue().Count(); txCount != 1 {
|
||||
t.Errorf("txqueue cannot be empty, as tx has failed: expected = 1, got = %d", txCount)
|
||||
return
|
||||
}
|
||||
|
||||
// now try to complete transaction, but with the correct password
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
if txCount := backend.TransactionQueue().Count(); txCount != 0 {
|
||||
t.Errorf("txqueue must be empty, as tx has completed: expected = 0, got = %d", txCount)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
||||
}
|
||||
|
||||
if envelope.Type == geth.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 := "could not decrypt key with given passphrase"
|
||||
if receivedErrMessage != expectedErrMessage {
|
||||
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != geth.SendTransactionPasswordErrorCode {
|
||||
t.Errorf("unexpected error code received: got %v", receivedErrCode)
|
||||
return
|
||||
}
|
||||
|
||||
txFailedEventCalled = true
|
||||
}
|
||||
})
|
||||
|
||||
// this call blocks, and should return on *second* attempt to CompleteTransaction (w/ the correct password)
|
||||
txHashCheck, err := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("cannot send transaction: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if txHashCheck.Hex() != txHash.Hex() {
|
||||
t.Errorf("tx hash returned from SendTransaction is invalid: expected %s, got %s", txHashCheck.Hex(), txHash.Hex())
|
||||
return
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
t.Error("transaction was never queued or completed")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
|
||||
if !txFailedEventCalled {
|
||||
t.Error("expected tx failure signal is not received")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscardQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txID string
|
||||
txFailedEventCalled := false
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
||||
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
||||
t.Errorf("txqueue should still have test tx: %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
// discard
|
||||
err := geth.DiscardTransaction(txID)
|
||||
if err != nil {
|
||||
t.Errorf("cannot discard tx: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
_, err = geth.CompleteTransaction(txID, testConfig.Account1.Password)
|
||||
if err.Error() != "transaction hash not found" {
|
||||
t.Error("expects tx not found, but call to CompleteTransaction succeeded")
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
if backend.TransactionQueue().Has(status.QueuedTxID(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 == geth.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 := status.ErrQueuedTxDiscarded.Error()
|
||||
if receivedErrMessage != expectedErrMessage {
|
||||
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != geth.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 := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != status.ErrQueuedTxDiscarded {
|
||||
t.Errorf("expected error not thrown: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, common.Hash{}) {
|
||||
t.Error("transaction returned hash, while it shouldn't")
|
||||
return
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
|
||||
if !txFailedEventCalled {
|
||||
t.Error("expected tx failure signal is not received")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteMultipleQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.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 := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.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, common.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
|
||||
results := geth.CompleteTransactions(string(updatedTxIDStrings), testConfig.Account1.Password)
|
||||
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
||||
t.Errorf("cannot complete txs: %v", results)
|
||||
return
|
||||
}
|
||||
for txID, txResult := range results {
|
||||
if txResult.Error != nil && txID != "invalid-tx-id" {
|
||||
t.Errorf("invalid error for %s", txID)
|
||||
return
|
||||
}
|
||||
if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id" {
|
||||
t.Errorf("invalid hash (expected non empty hash): %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
if backend.TransactionQueue().Has(status.QueuedTxID(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
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscardMultipleQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// reset queue
|
||||
backend.TransactionQueue().Reset()
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var txID string
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
txID = event["id"].(string)
|
||||
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
|
||||
|
||||
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
|
||||
t.Errorf("txqueue should still have test tx: %s", txID)
|
||||
return
|
||||
}
|
||||
|
||||
txIDs <- txID
|
||||
}
|
||||
|
||||
if envelope.Type == geth.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 := status.ErrQueuedTxDiscarded.Error()
|
||||
if receivedErrMessage != expectedErrMessage {
|
||||
t.Errorf("unexpected error message received: got %v", receivedErrMessage)
|
||||
return
|
||||
}
|
||||
|
||||
receivedErrCode := event["error_code"].(string)
|
||||
if receivedErrCode != geth.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 := backend.SendTransaction(nil, status.SendTxArgs{
|
||||
From: geth.FromAddress(testConfig.Account1.Address),
|
||||
To: geth.ToAddress(testConfig.Account2.Address),
|
||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
||||
})
|
||||
if err != status.ErrQueuedTxDiscarded {
|
||||
t.Errorf("expected error not thrown: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(txHashCheck, common.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
|
||||
discardResults := geth.DiscardTransactions(string(updatedTxIDStrings))
|
||||
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error.Error() != "transaction hash not found" {
|
||||
t.Errorf("cannot discard txs: %v", discardResults)
|
||||
return
|
||||
}
|
||||
|
||||
// try completing discarded transaction
|
||||
completeResults := geth.CompleteTransactions(string(updatedTxIDStrings), testConfig.Account1.Password)
|
||||
if len(completeResults) != (testTxCount + 1) {
|
||||
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
|
||||
}
|
||||
for _, txResult := range completeResults {
|
||||
if txResult.Error.Error() != "transaction hash not found" {
|
||||
t.Errorf("invalid error for %s", txResult.Hash.Hex())
|
||||
return
|
||||
}
|
||||
if txResult.Hash.Hex() != "0x0000000000000000000000000000000000000000000000000000000000000000" {
|
||||
t.Errorf("invalid hash (expected zero): %s", txResult.Hash.Hex())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
|
||||
for _, txID := range parsedIDs {
|
||||
if backend.TransactionQueue().Has(status.QueuedTxID(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
|
||||
}
|
||||
|
||||
if backend.TransactionQueue().Count() != 0 {
|
||||
t.Error("tx queue must be empty at this point")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonExistentQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
geth.SetDefaultNodeNotificationHandler(func(string) {})
|
||||
|
||||
// try completing non-existing transaction
|
||||
if _, err = geth.CompleteTransaction("some-bad-transaction-id", testConfig.Account1.Password); err == nil {
|
||||
t.Error("error expected and not received")
|
||||
return
|
||||
}
|
||||
if err != status.ErrQueuedTxIDNotFound {
|
||||
t.Errorf("unexpected error received: expected '%s', got: '%s'", status.ErrQueuedTxIDNotFound.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvictionOfQueuedTransactions(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// obtain reference to status backend
|
||||
lightEthereum, err := geth.NodeManagerInstance().LightEthereumService()
|
||||
if err != nil {
|
||||
t.Errorf("Test failed: LES service is not running: %v", err)
|
||||
return
|
||||
}
|
||||
backend := lightEthereum.StatusBackend
|
||||
|
||||
// log into account from which transactions will be sent
|
||||
if err := geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure you panic if transaction complete doesn't return
|
||||
completeQueuedTransaction := make(chan struct{}, 1)
|
||||
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
|
||||
|
||||
// replace transaction notification handler
|
||||
var txHash = common.Hash{}
|
||||
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
||||
var envelope geth.SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == geth.EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
// next call is the very same one, but with the correct password
|
||||
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||
completeQueuedTransaction <- struct{}{} // so that timeout is aborted
|
||||
}
|
||||
})
|
||||
|
||||
txQueue := backend.TransactionQueue()
|
||||
var i = 0
|
||||
txIDs := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxID{}
|
||||
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
|
||||
t.Logf("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.ID)
|
||||
txIDs[i] = queuedTx.ID
|
||||
i++
|
||||
})
|
||||
|
||||
if txQueue.Count() != 0 {
|
||||
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
t.Logf("Number of transactions queued: %d. Queue size (shouldn't be more than %d): %d", i, status.DefaultTxQueueCap, txQueue.Count())
|
||||
|
||||
if txQueue.Count() != 10 {
|
||||
t.Errorf("transaction count should be 10: got %d", txQueue.Count())
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
|
||||
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
|
||||
}
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
if txQueue.Count() > status.DefaultTxQueueCap {
|
||||
t.Errorf("transaction count should be %d (or %d): got %d", status.DefaultTxQueueCap, status.DefaultTxQueueCap-1, txQueue.Count())
|
||||
return
|
||||
}
|
||||
|
||||
for _, txID := range txIDs {
|
||||
txQueue.Remove(txID)
|
||||
}
|
||||
|
||||
if txQueue.Count() != 0 {
|
||||
t.Errorf("transaction count should be zero: %d", txQueue.Count())
|
||||
return
|
||||
}
|
||||
}
|
109
geth/types.go
109
geth/types.go
|
@ -1,109 +0,0 @@
|
|||
package geth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/les/status"
|
||||
)
|
||||
|
||||
// SignalEnvelope is a general signal sent upward from node to RN app
|
||||
type SignalEnvelope struct {
|
||||
Type string `json:"type"`
|
||||
Event interface{} `json:"event"`
|
||||
}
|
||||
|
||||
// AccountInfo represents account's info
|
||||
type AccountInfo struct {
|
||||
Address string `json:"address"`
|
||||
PubKey string `json:"pubkey"`
|
||||
Mnemonic string `json:"mnemonic"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// JSONError is wrapper around errors, that are sent upwards
|
||||
type JSONError struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// NodeCrashEvent is special kind of error, used to report node crashes
|
||||
type NodeCrashEvent struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// AddPeerResult is a JSON returned as a response to AddPeer() request
|
||||
type AddPeerResult struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// WhisperMessageEvent is a signal sent on incoming Whisper message
|
||||
type WhisperMessageEvent struct {
|
||||
Payload string `json:"payload"`
|
||||
To string `json:"to"`
|
||||
From string `json:"from"`
|
||||
Sent int64 `json:"sent"`
|
||||
TTL int64 `json:"ttl"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// SendTransactionEvent is a signal sent on a send transaction request
|
||||
type SendTransactionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Args status.SendTxArgs `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
}
|
||||
|
||||
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
|
||||
type ReturnSendTransactionEvent struct {
|
||||
ID string `json:"id"`
|
||||
Args status.SendTxArgs `json:"args"`
|
||||
MessageID string `json:"message_id"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
ErrorCode string `json:"error_code"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// RawCompleteTransactionResult is a JSON returned from transaction complete function (used internally)
|
||||
type RawCompleteTransactionResult struct {
|
||||
Hash common.Hash
|
||||
Error error
|
||||
}
|
||||
|
||||
// CompleteTransactionsResult is list of results from CompleteTransactions() (used in exposed method)
|
||||
type CompleteTransactionsResult struct {
|
||||
Results map[string]CompleteTransactionResult `json:"results"`
|
||||
}
|
||||
|
||||
// RawDiscardTransactionResult is list of results from CompleteTransactions() (used internally)
|
||||
type RawDiscardTransactionResult struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// LocalStorageSetEvent is a signal sent whenever local storage Set method is called
|
||||
type LocalStorageSetEvent struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// RPCCall represents RPC call parameters
|
||||
type RPCCall struct {
|
||||
ID int64
|
||||
Method string
|
||||
Params []interface{}
|
||||
}
|
320
geth/utils.go
320
geth/utils.go
|
@ -1,320 +0,0 @@
|
|||
package geth
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
extern bool StatusServiceSignalEvent(const char *jsonEvent);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/geth/params"
|
||||
"github.com/status-im/status-go/static"
|
||||
)
|
||||
|
||||
var (
|
||||
muPrepareTestNode sync.Mutex
|
||||
|
||||
// RootDir is the main application directory
|
||||
RootDir string
|
||||
|
||||
// TestDataDir is data directory used for tests
|
||||
TestDataDir string
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// NodeNotificationHandler defines a handler able to process incoming node events.
|
||||
// Events are encoded as JSON strings.
|
||||
type NodeNotificationHandler func(jsonEvent string)
|
||||
|
||||
var notificationHandler NodeNotificationHandler = TriggerDefaultNodeNotificationHandler
|
||||
|
||||
// SetDefaultNodeNotificationHandler sets notification handler to invoke on SendSignal
|
||||
func SetDefaultNodeNotificationHandler(fn NodeNotificationHandler) {
|
||||
notificationHandler = fn
|
||||
}
|
||||
|
||||
// TriggerDefaultNodeNotificationHandler triggers default notification handler (helpful in tests)
|
||||
func TriggerDefaultNodeNotificationHandler(jsonEvent string) {
|
||||
log.Info("notification received (default notification handler)", "event", jsonEvent)
|
||||
}
|
||||
|
||||
// SendSignal sends application signal (JSON, normally) upwards to application (via default notification handler)
|
||||
func SendSignal(signal SignalEnvelope) {
|
||||
data, _ := json.Marshal(&signal)
|
||||
C.StatusServiceSignalEvent(C.CString(string(data)))
|
||||
}
|
||||
|
||||
//export NotifyNode
|
||||
func NotifyNode(jsonEvent *C.char) { // nolint: golint
|
||||
notificationHandler(C.GoString(jsonEvent))
|
||||
}
|
||||
|
||||
//export TriggerTestSignal
|
||||
func TriggerTestSignal() { // nolint: golint
|
||||
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
|
||||
}
|
||||
|
||||
// TestConfig contains shared (among different test packages) parameters
|
||||
type TestConfig struct {
|
||||
Node struct {
|
||||
SyncSeconds time.Duration
|
||||
HTTPPort int
|
||||
WSPort int
|
||||
}
|
||||
Account1 struct {
|
||||
Address string
|
||||
Password string
|
||||
}
|
||||
Account2 struct {
|
||||
Address string
|
||||
Password string
|
||||
}
|
||||
}
|
||||
|
||||
// LoadTestConfig loads test configuration values from disk
|
||||
func LoadTestConfig() (*TestConfig, error) {
|
||||
var testConfig TestConfig
|
||||
|
||||
configData := string(static.MustAsset("config/test-data.json"))
|
||||
if err := json.Unmarshal([]byte(configData), &testConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &testConfig, 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())
|
||||
}
|
||||
|
||||
// PrepareTestNode initializes node manager and start a test node (only once!)
|
||||
func PrepareTestNode() (err error) {
|
||||
muPrepareTestNode.Lock()
|
||||
defer muPrepareTestNode.Unlock()
|
||||
|
||||
manager := NodeManagerInstance()
|
||||
if manager.NodeInited() {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer HaltOnPanic()
|
||||
|
||||
testConfig, err := LoadTestConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncRequired := false
|
||||
if _, err = os.Stat(TestDataDir); os.IsNotExist(err) {
|
||||
syncRequired = true
|
||||
}
|
||||
|
||||
// prepare node directory
|
||||
if err = os.MkdirAll(filepath.Join(TestDataDir, "keystore"), os.ModePerm); err != nil {
|
||||
log.Warn("make node failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// import test accounts (with test ether on it)
|
||||
if err = ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account1.pk"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = ImportTestAccount(filepath.Join(TestDataDir, "keystore"), "test-account2.pk"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// start geth node and wait for it to initialize
|
||||
config, err := params.NewNodeConfig(filepath.Join(TestDataDir, "data"), params.RopstenNetworkID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.KeyStoreDir = filepath.Join(TestDataDir, "keystore")
|
||||
config.HTTPPort = testConfig.Node.HTTPPort // to avoid conflicts with running app, using different port in tests
|
||||
config.WSPort = testConfig.Node.WSPort // ditto
|
||||
config.LogEnabled = true
|
||||
|
||||
err = CreateAndRunNode(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
manager = NodeManagerInstance()
|
||||
if !manager.NodeInited() {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if service, err := manager.RPCClient(); err != nil || service == nil {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if service, err := manager.WhisperService(); err != nil || service == nil {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
if service, err := manager.LightEthereumService(); err != nil || service == nil {
|
||||
panic(ErrInvalidGethNode)
|
||||
}
|
||||
|
||||
if syncRequired {
|
||||
log.Warn("Sync is required", "duration", testConfig.Node.SyncSeconds)
|
||||
time.Sleep(testConfig.Node.SyncSeconds * time.Second) // LES syncs headers, so that we are up do date when it is done
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeTestCompleteTxHandler returns node notification handler to be used in test
|
||||
// basically notification handler completes a transaction (that is enqueued after
|
||||
// the handler has been installed)
|
||||
func MakeTestCompleteTxHandler(t *testing.T, txHash *common.Hash, completed chan struct{}) (handler func(jsonEvent string), err error) {
|
||||
testConfig, err := LoadTestConfig()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
handler = func(jsonEvent string) {
|
||||
var envelope SignalEnvelope
|
||||
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
|
||||
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
|
||||
return
|
||||
}
|
||||
if envelope.Type == EventTransactionQueued {
|
||||
event := envelope.Event.(map[string]interface{})
|
||||
|
||||
t.Logf("Transaction queued (will be completed shortly): {id: %s}\n", event["id"].(string))
|
||||
|
||||
if err := SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if *txHash, err = CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
|
||||
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Contract created: https://testnet.etherscan.io/tx/%s", txHash.Hex())
|
||||
close(completed) // so that timeout is aborted
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// FromAddress converts account address from string to common.Address.
|
||||
// The function is useful to format "From" field of send transaction struct.
|
||||
func FromAddress(accountAddress string) common.Address {
|
||||
from, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return common.Address{}
|
||||
}
|
||||
|
||||
return from.Address
|
||||
}
|
||||
|
||||
// ToAddress converts account address from string to *common.Address.
|
||||
// The function is useful to format "To" field of send transaction struct.
|
||||
func ToAddress(accountAddress string) *common.Address {
|
||||
to, err := ParseAccountString(accountAddress)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &to.Address
|
||||
}
|
||||
|
||||
// ParseAccountString parses hex encoded string and returns is as accounts.Account.
|
||||
func ParseAccountString(account string) (accounts.Account, error) {
|
||||
// valid address, convert to account
|
||||
if common.IsHexAddress(account) {
|
||||
return accounts.Account{Address: common.HexToAddress(account)}, nil
|
||||
}
|
||||
|
||||
return accounts.Account{}, ErrInvalidAccountAddressOrKey
|
||||
}
|
||||
|
||||
// AddressToDecryptedAccount tries to load and decrypt account with a given password
|
||||
func AddressToDecryptedAccount(address, password string) (accounts.Account, *keystore.Key, error) {
|
||||
nodeManager := NodeManagerInstance()
|
||||
keyStore, err := nodeManager.AccountKeyStore()
|
||||
if err != nil {
|
||||
return accounts.Account{}, nil, err
|
||||
}
|
||||
|
||||
account, err := ParseAccountString(address)
|
||||
if err != nil {
|
||||
return accounts.Account{}, nil, ErrAddressToAccountMappingFailure
|
||||
}
|
||||
|
||||
return keyStore.AccountDecryptedKey(account, password)
|
||||
}
|
||||
|
||||
// ImportTestAccount checks if test account exists in keystore, and if not
|
||||
// tries to import it (from static resources, see "static/keys" folder)
|
||||
func ImportTestAccount(keystoreDir, accountFile string) error {
|
||||
// make sure that keystore folder exists
|
||||
if _, err := os.Stat(keystoreDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(keystoreDir, os.ModePerm) // nolint: errcheck
|
||||
}
|
||||
|
||||
dst := filepath.Join(keystoreDir, accountFile)
|
||||
if _, err := os.Stat(dst); os.IsNotExist(err) {
|
||||
err = ioutil.WriteFile(dst, static.MustAsset("keys/"+accountFile), 0644)
|
||||
if err != nil {
|
||||
log.Warn("cannot copy test account PK", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package geth
|
|
@ -1,85 +0,0 @@
|
|||
package geth_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestWhisperFilterRace(t *testing.T) {
|
||||
err := geth.PrepareTestNode()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
whisperService, err := geth.NodeManagerInstance().WhisperService()
|
||||
if err != nil {
|
||||
t.Errorf("whisper service not running: %v", err)
|
||||
}
|
||||
|
||||
whisperAPI := whisper.NewPublicWhisperAPI(whisperService)
|
||||
|
||||
// account1
|
||||
_, accountKey1, err := geth.AddressToDecryptedAccount(testConfig.Account1.Address, testConfig.Account1.Password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
|
||||
|
||||
if _, err = whisperService.AddKeyPair(accountKey1.PrivateKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok, err := whisperAPI.HasKeyPair(accountKey1Hex); err != nil || !ok {
|
||||
t.Fatalf("identity not injected: %v", accountKey1Hex)
|
||||
}
|
||||
|
||||
// account2
|
||||
_, accountKey2, err := geth.AddressToDecryptedAccount(testConfig.Account2.Address, testConfig.Account2.Password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey))
|
||||
|
||||
if _, err = whisperService.AddKeyPair(accountKey2.PrivateKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok, err := whisperAPI.HasKeyPair(accountKey2Hex); err != nil || !ok {
|
||||
t.Fatalf("identity not injected: %v", accountKey2Hex)
|
||||
}
|
||||
|
||||
// race filter addition
|
||||
filterAdded := make(chan struct{})
|
||||
allFiltersAdded := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
counter := 10
|
||||
for range filterAdded {
|
||||
counter--
|
||||
if counter <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
close(allFiltersAdded)
|
||||
}()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
// nolint: errcheck
|
||||
whisperAPI.Subscribe(whisper.WhisperFilterArgs{
|
||||
Sig: accountKey1Hex,
|
||||
Key: accountKey2Hex,
|
||||
Topics: [][]byte{
|
||||
{0x4e, 0x03, 0x65, 0x7a}, {0x34, 0x60, 0x7c, 0x9b}, {0x21, 0x41, 0x7d, 0xf9},
|
||||
},
|
||||
})
|
||||
filterAdded <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
<-allFiltersAdded
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"Node": {
|
||||
"SyncSeconds": 90,
|
||||
"SyncSeconds": 10,
|
||||
"HTTPPort": 8645,
|
||||
"WSPort": 8646
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Package static embeds static (JS, HTML) resources right into the binaries
|
||||
package static
|
||||
|
||||
//go:generate go-bindata -pkg static -o bindata.go scripts/ bootcluster/ config/ keys/
|
||||
//go:generate go-bindata -pkg static -o bindata.go scripts/ bootcluster/ config/ keys/ testdata/...
|
||||
|
|
|
@ -3,6 +3,7 @@ package status
|
|||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
@ -106,13 +107,18 @@ func (q *TxQueue) Stop() {
|
|||
|
||||
// evictionLoop frees up queue to accommodate another transaction item
|
||||
func (q *TxQueue) evictionLoop() {
|
||||
evict := func() {
|
||||
if len(q.transactions) >= DefaultTxQueueCap { // eviction is required to accommodate another/last item
|
||||
q.Remove(<-q.evictableIDs)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-q.enqueueTicker:
|
||||
if len(q.transactions) >= (DefaultTxQueueCap - 1) { // eviction is required to accommodate another/last item
|
||||
q.Remove(<-q.evictableIDs)
|
||||
q.enqueueTicker <- struct{}{} // in case we pulled already removed item
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond): // do not wait for manual ticks, check queue regularly
|
||||
evict()
|
||||
case <-q.enqueueTicker: // when manually requested
|
||||
evict()
|
||||
case <-q.stopped:
|
||||
log.Info("StatusIM: transaction queue's eviction loop stopped")
|
||||
q.stoppedGroup.Done()
|
||||
|
@ -127,8 +133,9 @@ func (q *TxQueue) enqueueLoop() {
|
|||
for {
|
||||
select {
|
||||
case queuedTx := <-q.incomingPool:
|
||||
log.Info("StatusIM: transaction enqueued", "tx", queuedTx.ID)
|
||||
log.Info("StatusIM: transaction enqueued requested", "tx", queuedTx.ID)
|
||||
q.Enqueue(queuedTx)
|
||||
log.Info("StatusIM: transaction enqueued", "tx", queuedTx.ID)
|
||||
case <-q.stopped:
|
||||
log.Info("StatusIM: transaction queue's enqueue loop stopped")
|
||||
q.stoppedGroup.Done()
|
||||
|
|
|
@ -96,6 +96,7 @@ func VerifyProof(rootHash common.Hash, key []byte, proof []rlp.RawValue) (value
|
|||
sha.Reset()
|
||||
sha.Write(buf)
|
||||
if !bytes.Equal(sha.Sum(nil), wantHash) {
|
||||
log.Error("Invalid CHT Proof", "hash", common.ToHex(sha.Sum(nil)), "node", i)
|
||||
return nil, fmt.Errorf("bad proof node %d: hash mismatch", i)
|
||||
}
|
||||
n, err := decodeNode(wantHash, buf, 0)
|
||||
|
|
Loading…
Reference in New Issue