refactor: linters + recommendations applied, closes #146

This commit is contained in:
Victor Farazdagi 2017-05-03 17:24:48 +03:00 committed by Roman Volosovskyi
parent 311f2b80c5
commit 2f0c93fd3b
31 changed files with 1115 additions and 870 deletions

View File

@ -57,6 +57,57 @@ generate:
build/env.sh go generate ./static
rm ./static/scripts/web3.js
lint-deps:
go get -u github.com/alecthomas/gometalinter
gometalinter --install
lint-cur:
gometalinter --disable-all --enable=deadcode extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
lint:
@echo "Linter: go vet\n--------------------"
@gometalinter --disable-all --enable=vet extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: go vet --shadow\n--------------------"
@gometalinter --disable-all --enable=vetshadow extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: gofmt\n--------------------"
@gometalinter --disable-all --enable=gofmt extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: goimports\n--------------------"
@gometalinter --disable-all --enable=goimports extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: golint\n--------------------"
@gometalinter --disable-all --enable=golint extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: deadcode\n--------------------"
@gometalinter --disable-all --enable=deadcode extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: misspell\n--------------------"
@gometalinter --disable-all --enable=misspell extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: unparam\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=unparam extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: unused\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=unused extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: gocyclo\n--------------------"
@gometalinter --disable-all --enable=gocyclo extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: errcheck\n--------------------"
@gometalinter --disable-all --enable=errcheck extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: dupl\n--------------------"
@gometalinter --disable-all --enable=dupl extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: ineffassign\n--------------------"
@gometalinter --disable-all --enable=ineffassign extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: interfacer\n--------------------"
@gometalinter --disable-all --enable=interfacer extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: unconvert\n--------------------"
@gometalinter --disable-all --enable=unconvert extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: goconst\n--------------------"
@gometalinter --disable-all --enable=goconst extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: staticcheck\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=staticcheck extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: gas\n--------------------"
@gometalinter --disable-all --enable=gas extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: varcheck\n--------------------"
@gometalinter --disable-all --deadline 60s --enable=varcheck extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: structcheck\n--------------------"
@gometalinter --disable-all --enable=structcheck extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
@echo "Linter: gosimple\n--------------------"
@gometalinter --disable-all --deadline 45s --enable=gosimple extkeys cmd/... geth/... | grep -v -f ./static/config/linter_exclude_list.txt || echo "OK!"
test:
@build/env.sh echo "mode: set" > coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth

View File

@ -128,7 +128,7 @@ func CompleteTransaction(id, password *C.char) *C.char {
}
out := geth.CompleteTransactionResult{
Id: C.GoString(id),
ID: C.GoString(id),
Hash: txHash.Hex(),
Error: errString,
}
@ -143,15 +143,15 @@ func CompleteTransactions(ids, password *C.char) *C.char {
out.Results = make(map[string]geth.CompleteTransactionResult)
results := geth.CompleteTransactions(C.GoString(ids), C.GoString(password))
for txId, result := range results {
for txID, result := range results {
txResult := geth.CompleteTransactionResult{
Id: txId,
ID: txID,
Hash: result.Hash.Hex(),
}
if result.Error != nil {
txResult.Error = result.Error.Error()
}
out.Results[txId] = txResult
out.Results[txID] = txResult
}
outBytes, _ := json.Marshal(&out)
@ -169,7 +169,7 @@ func DiscardTransaction(id *C.char) *C.char {
}
out := geth.DiscardTransactionResult{
Id: C.GoString(id),
ID: C.GoString(id),
Error: errString,
}
outBytes, _ := json.Marshal(&out)
@ -183,14 +183,14 @@ func DiscardTransactions(ids *C.char) *C.char {
out.Results = make(map[string]geth.DiscardTransactionResult)
results := geth.DiscardTransactions(C.GoString(ids))
for txId, result := range results {
for txID, result := range results {
txResult := geth.DiscardTransactionResult{
Id: txId,
ID: txID,
}
if result.Error != nil {
txResult.Error = result.Error.Error()
}
out.Results[txId] = txResult
out.Results[txID] = txResult
}
outBytes, _ := json.Marshal(&out)
@ -198,8 +198,8 @@ func DiscardTransactions(ids *C.char) *C.char {
}
//export GenerateConfig
func GenerateConfig(datadir *C.char, networkId C.int) *C.char {
config, err := params.NewNodeConfig(C.GoString(datadir), uint64(networkId))
func GenerateConfig(datadir *C.char, networkID C.int) *C.char {
config, err := params.NewNodeConfig(C.GoString(datadir), uint64(networkID))
if err != nil {
return makeJSONErrorResponse(err)
}
@ -261,14 +261,14 @@ func InitJail(js *C.char) {
}
//export Parse
func Parse(chatId *C.char, js *C.char) *C.char {
res := jail.GetInstance().Parse(C.GoString(chatId), C.GoString(js))
func Parse(chatID *C.char, js *C.char) *C.char {
res := jail.GetInstance().Parse(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))
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))
return C.CString(res)
}

View File

@ -18,45 +18,64 @@ var (
)
var (
// NodeKeyFileFlag is a node key file to be used as node's private key
NodeKeyFileFlag = cli.StringFlag{
Name: "nodekey",
Usage: "P2P node key file (private key)",
}
// DataDirFlag defines data directory for the node
DataDirFlag = cli.StringFlag{
Name: "datadir",
Usage: "Data directory for the databases and keystore",
Value: params.DataDir,
}
NetworkIdFlag = cli.IntFlag{
// NetworkIDFlag defines network ID
NetworkIDFlag = cli.IntFlag{
Name: "networkid",
Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten)",
Value: params.TestNetworkId,
Value: params.TestNetworkID,
}
// LightEthEnabledFlag flags whether LES is enabled or not
LightEthEnabledFlag = cli.BoolFlag{
Name: "les",
Usage: "LES protocol enabled",
}
// WhisperEnabledFlag flags whether Whisper is enabled or not
WhisperEnabledFlag = cli.BoolFlag{
Name: "shh",
Usage: "SHH protocol enabled",
}
// SwarmEnabledFlag flags whether Swarm is enabled or not
SwarmEnabledFlag = cli.BoolFlag{
Name: "swarm",
Usage: "Swarm protocol enabled",
}
// HTTPEnabledFlag defines whether HTTP RPC endpoint should be opened or not
HTTPEnabledFlag = cli.BoolFlag{
Name: "http",
Usage: "HTTP RPC enpoint enabled",
}
// HTTPPortFlag defines HTTP RPC port to use (if HTTP RPC is enabled)
HTTPPortFlag = cli.IntFlag{
Name: "httpport",
Usage: "HTTP RPC server's listening port",
Value: params.HTTPPort,
}
// IPCEnabledFlag flags whether IPC is enabled or not
IPCEnabledFlag = cli.BoolFlag{
Name: "ipc",
Usage: "IPC RPC enpoint enabled",
}
// LogLevelFlag defines a log reporting level
LogLevelFlag = cli.StringFlag{
Name: "log",
Usage: `Log level, one of: ""ERROR", "WARNING", "INFO", "DEBUG", and "TRACE"`,
@ -76,7 +95,7 @@ func init() {
app.Flags = []cli.Flag{
NodeKeyFileFlag,
DataDirFlag,
NetworkIdFlag,
NetworkIDFlag,
LightEthEnabledFlag,
WhisperEnabledFlag,
SwarmEnabledFlag,
@ -121,7 +140,7 @@ func statusd(ctx *cli.Context) error {
// makeNodeConfig parses incoming CLI options and returns node configuration object
func makeNodeConfig(ctx *cli.Context) (*params.NodeConfig, error) {
nodeConfig, err := params.NewNodeConfig(ctx.GlobalString(DataDirFlag.Name), ctx.GlobalUint64(NetworkIdFlag.Name))
nodeConfig, err := params.NewNodeConfig(ctx.GlobalString(DataDirFlag.Name), ctx.GlobalUint64(NetworkIDFlag.Name))
if err != nil {
return nil, err
}

View File

@ -25,8 +25,11 @@ func version(ctx *cli.Context) error {
if gitCommit != "" {
fmt.Println("Git Commit:", gitCommit)
}
if buildStamp != "" {
fmt.Println("Build Stamp:", buildStamp)
}
fmt.Println("Network Id:", ctx.GlobalInt(NetworkIdFlag.Name))
fmt.Println("Network Id:", ctx.GlobalInt(NetworkIDFlag.Name))
fmt.Println("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))

View File

@ -20,6 +20,8 @@ import (
"github.com/status-im/status-go/geth/params"
)
const zeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000"
var testConfig *geth.TestConfig
func init() {
@ -28,6 +30,7 @@ func init() {
testConfig, _ = geth.LoadTestConfig()
}
// nolint: deadcode
func testExportedAPI(t *testing.T, done chan struct{}) {
<-startTestNode(t)
@ -108,7 +111,7 @@ func testGetDefaultConfig(t *testing.T) bool {
rawResponse := GenerateConfig(C.CString("/tmp/data-folder"), 1)
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &nodeConfig); err != nil {
t.Errorf("cannot decode reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -155,7 +158,7 @@ func testGetDefaultConfig(t *testing.T) bool {
nodeConfig = params.NodeConfig{}
rawResponse = GenerateConfig(C.CString("/tmp/data-folder"), 3)
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &nodeConfig); err != nil {
t.Errorf("cannot decode reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -208,7 +211,7 @@ func testResetChainData(t *testing.T) bool {
rawResponse := ResetChainData()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &resetChainDataResponse); err != nil {
t.Errorf("cannot decode ResetChainData reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode ResetChainData response (%s): %v", C.GoString(rawResponse), err)
return false
}
if resetChainDataResponse.Error != "" {
@ -224,7 +227,10 @@ func testResetChainData(t *testing.T) bool {
}
func testStopResumeNode(t *testing.T) bool {
geth.Logout() // to make sure that we start with empty account (which might get populated during previous tests)
// 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)
}
whisperService, err := geth.NodeManagerInstance().WhisperService()
if err != nil {
@ -248,8 +254,8 @@ func testStopResumeNode(t *testing.T) bool {
loginResponse := geth.JSONError{}
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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -262,12 +268,13 @@ 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{}
rawResponse = StopNode()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
t.Errorf("cannot decode StopNode reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
t.Errorf("cannot decode StopNode response (%s): %v", C.GoString(rawResponse), err)
return false
}
if response.Error != "" {
@ -278,12 +285,13 @@ func testStopResumeNode(t *testing.T) bool {
return true
}
// nolint: dupl
resumeNodeFn := func() bool {
response := geth.JSONError{}
rawResponse = ResumeNode()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
t.Errorf("cannot decode ResumeNode reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
t.Errorf("cannot decode ResumeNode response (%s): %v", C.GoString(rawResponse), err)
return false
}
if response.Error != "" {
@ -322,7 +330,7 @@ func testRestartNodeRPC(t *testing.T) bool {
rawResponse := StopNodeRPCServer()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &stopNodeRPCServerResponse); err != nil {
t.Errorf("cannot decode StopNodeRPCServer reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode StopNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
return false
}
if stopNodeRPCServerResponse.Error != "" {
@ -335,7 +343,7 @@ func testRestartNodeRPC(t *testing.T) bool {
rawResponse = StartNodeRPCServer()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &startNodeRPCServerResponse); err != nil {
t.Errorf("cannot decode StartNodeRPCServer reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode StartNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
return false
}
if startNodeRPCServerResponse.Error != "" {
@ -348,7 +356,7 @@ func testRestartNodeRPC(t *testing.T) bool {
rawResponse = StartNodeRPCServer()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &startNodeRPCServerResponse); err != nil {
t.Errorf("cannot decode StartNodeRPCServer reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode StartNodeRPCServer response (%s): %v", C.GoString(rawResponse), err)
return false
}
expectedError := "HTTP RPC already running on localhost:8645"
@ -361,7 +369,10 @@ func testRestartNodeRPC(t *testing.T) bool {
}
func testCreateChildAccount(t *testing.T) bool {
geth.Logout() // to make sure that we start with empty account (which might get populated during previous tests)
// 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 {
@ -373,8 +384,8 @@ func testCreateChildAccount(t *testing.T) bool {
createAccountResponse := geth.AccountInfo{}
rawResponse := CreateAccount(C.CString(testConfig.Account1.Password))
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createAccountResponse); err != nil {
t.Errorf("cannot decode CreateAccount reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &createAccountResponse); err != nil {
t.Errorf("cannot decode CreateAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -392,7 +403,7 @@ func testCreateChildAccount(t *testing.T) bool {
}
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
account, 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
@ -407,8 +418,8 @@ func testCreateChildAccount(t *testing.T) bool {
createSubAccountResponse := geth.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 reponse (%s): %v", C.GoString(rawResponse), err)
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
}
@ -428,11 +439,11 @@ func testCreateChildAccount(t *testing.T) bool {
rawResponse = CreateChildAccount(C.CString(""), C.CString("wrong password"))
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &createSubAccountResponse); err != nil {
t.Errorf("cannot decode CreateChildAccount reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
if createSubAccountResponse.Error != "cannot retreive a valid key for a given account: could not decrypt key with given passphrase" {
if createSubAccountResponse.Error != "cannot retrieve a valid key for a given account: could not decrypt key with given passphrase" {
t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", createSubAccountResponse.Error)
return false
}
@ -442,7 +453,7 @@ func testCreateChildAccount(t *testing.T) bool {
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 reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -456,7 +467,7 @@ func testCreateChildAccount(t *testing.T) bool {
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 reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -474,7 +485,7 @@ func testCreateChildAccount(t *testing.T) bool {
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 reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode CreateChildAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -508,8 +519,8 @@ func testRecoverAccount(t *testing.T) bool {
recoverAccountResponse := geth.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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -535,15 +546,15 @@ func testRecoverAccount(t *testing.T) bool {
}
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))
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
t.Errorf("cannot decode RecoverAccount reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -557,7 +568,7 @@ func testRecoverAccount(t *testing.T) bool {
}
// make sure that extended key exists and is imported ok too
account, 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
@ -570,8 +581,8 @@ func testRecoverAccount(t *testing.T) bool {
recoverAccountResponse = geth.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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -637,8 +648,8 @@ func testAccountSelect(t *testing.T) bool {
loginResponse := geth.JSONError{}
rawResponse := Login(C.CString(address1), C.CString("wrongPassword"))
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode RecoverAccount reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -650,8 +661,8 @@ func testAccountSelect(t *testing.T) bool {
loginResponse = geth.JSONError{}
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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -671,8 +682,8 @@ func testAccountSelect(t *testing.T) bool {
loginResponse = geth.JSONError{}
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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -725,7 +736,7 @@ func testAccountLogout(t *testing.T) bool {
rawResponse := Logout()
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &logoutResponse); err != nil {
t.Errorf("cannot decode RecoverAccount reponse (%s): %v", C.GoString(rawResponse), err)
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
return false
}
@ -756,7 +767,7 @@ func testCompleteTransaction(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 {
if err = geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
return false
}
@ -770,7 +781,7 @@ func testCompleteTransaction(t *testing.T) bool {
var txHash = ""
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope geth.SignalEnvelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
if err = json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
return
}
@ -781,12 +792,12 @@ func testCompleteTransaction(t *testing.T) bool {
completeTxResponse := geth.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 reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &completeTxResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
}
if completeTxResponse.Error != "" {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], completeTxResponse.Error)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], completeTxResponse.Error)
}
txHash = completeTxResponse.Hash
@ -847,12 +858,12 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
txIDs := make(chan string, testTxCount)
allTestTxCompleted := make(chan struct{}, 1)
// replace transaction notification handler
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txId 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)
@ -860,10 +871,10 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
}
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)
txID = event["id"].(string)
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txID)
txIds <- txId
txIDs <- txID
}
})
@ -886,58 +897,64 @@ func testCompleteMultipleQueuedTransactions(t *testing.T) bool {
}
// wait for transactions, and complete them in a single call
completeTxs := func(txIdStrings string) {
var parsedIds []string
json.Unmarshal([]byte(txIdStrings), &parsedIds)
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)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// complete
resultsString := CompleteTransactions(C.CString(string(updatedTxIdStrings)), C.CString(testConfig.Account1.Password))
resultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(testConfig.Account1.Password))
resultsStruct := geth.CompleteTransactionsResult{}
json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct)
if err := json.Unmarshal([]byte(C.GoString(resultsString)), &resultsStruct); err != nil {
t.Error(err)
return
}
results := resultsStruct.Results
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != "transaction hash not found" {
if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error != status.ErrQueuedTxIDNotFound.Error() {
t.Errorf("cannot complete txs: %v", results)
return
}
for txId, txResult := range results {
if txId != txResult.Id {
t.Errorf("tx id not set in result: expected id is %s", txId)
for txID, txResult := range results {
if txID != txResult.ID {
t.Errorf("tx id not set in result: expected id is %s", txID)
return
}
if txResult.Error != "" && txId != "invalid-tx-id" {
t.Errorf("invalid error for %s", txId)
if txResult.Error != "" && txID != "invalid-tx-id" {
t.Errorf("invalid error for %s", txID)
return
}
if txResult.Hash == "0x0000000000000000000000000000000000000000000000000000000000000000" && txId != "invalid-tx-id" {
t.Errorf("invalid hash (expected non empty hash): %s", txId)
if txResult.Hash == zeroHash && txID != "invalid-tx-id" {
t.Errorf("invalid hash (expected non empty hash): %s", txID)
return
}
if txResult.Hash != "0x0000000000000000000000000000000000000000000000000000000000000000" {
if txResult.Hash != zeroHash {
t.Logf("transaction complete: https://testnet.etherscan.io/tx/%s", txResult.Hash)
}
}
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txId := range parsedIds {
if backend.TransactionQueue().Has(status.QueuedTxId(txId)) {
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txId)
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
var txIDStrings []string
for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds)
txIDStrings = append(txIDStrings, <-txIDs)
}
txIdJSON, _ := json.Marshal(txIdStrings)
completeTxs(string(txIdJSON))
txIDJSON, _ := json.Marshal(txIDStrings)
completeTxs(string(txIDJSON))
allTestTxCompleted <- struct{}{}
}()
@ -975,7 +992,7 @@ 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 {
if err = geth.SelectAccount(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
t.Errorf("cannot select account: %v", testConfig.Account1.Address)
return false
}
@ -985,30 +1002,30 @@ func testDiscardTransaction(t *testing.T) bool {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
// replace transaction notification handler
var txId string
var txID string
txFailedEventCalled := false
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope geth.SignalEnvelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
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)
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)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
// discard
discardResponse := geth.DiscardTransactionResult{}
rawResponse := DiscardTransaction(C.CString(txId))
rawResponse := DiscardTransaction(C.CString(txID))
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
t.Errorf("cannot decode RecoverAccount reponse (%s): %v", C.GoString(rawResponse), err)
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &discardResponse); err != nil {
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
}
if discardResponse.Error != "" {
@ -1017,15 +1034,15 @@ func testDiscardTransaction(t *testing.T) bool {
}
// try completing discarded transaction
_, err = geth.CompleteTransaction(txId, testConfig.Account1.Password)
if err.Error() != "transaction hash not found" {
_, err = geth.CompleteTransaction(txID, testConfig.Account1.Password)
if err != status.ErrQueuedTxIDNotFound {
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)
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
}
@ -1102,13 +1119,13 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
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 txID string
var envelope geth.SignalEnvelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
@ -1116,15 +1133,15 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
}
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)
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)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
txIds <- txId
txIDs <- txID
}
if envelope.Type == geth.EventTransactionFailed {
@ -1170,64 +1187,73 @@ func testDiscardMultipleQueuedTransactions(t *testing.T) bool {
}
// wait for transactions, and discard immediately
discardTxs := func(txIdStrings string) {
var parsedIds []string
json.Unmarshal([]byte(txIdStrings), &parsedIds)
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)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// discard
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIdStrings)))
discardResultsString := DiscardTransactions(C.CString(string(updatedTxIDStrings)))
discardResultsStruct := geth.DiscardTransactionsResult{}
json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct)
if err := json.Unmarshal([]byte(C.GoString(discardResultsString)), &discardResultsStruct); err != nil {
t.Error(err)
return
}
discardResults := discardResultsStruct.Results
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != "transaction hash not found" {
if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error != status.ErrQueuedTxIDNotFound.Error() {
t.Errorf("cannot discard txs: %v", discardResults)
return
}
// try completing discarded transaction
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIdStrings)), C.CString(testConfig.Account1.Password))
completeResultsString := CompleteTransactions(C.CString(string(updatedTxIDStrings)), C.CString(testConfig.Account1.Password))
completeResultsStruct := geth.CompleteTransactionsResult{}
json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct)
if err := json.Unmarshal([]byte(C.GoString(completeResultsString)), &completeResultsStruct); err != nil {
t.Error(err)
return
}
completeResults := completeResultsStruct.Results
if len(completeResults) != (testTxCount + 1) {
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)")
}
for txId, txResult := range completeResults {
if txId != txResult.Id {
t.Errorf("tx id not set in result: expected id is %s", txId)
for txID, txResult := range completeResults {
if txID != txResult.ID {
t.Errorf("tx id not set in result: expected id is %s", txID)
return
}
if txResult.Error != "transaction hash not found" {
if txResult.Error != status.ErrQueuedTxIDNotFound.Error() {
t.Errorf("invalid error for %s", txResult.Hash)
return
}
if txResult.Hash != "0x0000000000000000000000000000000000000000000000000000000000000000" {
if txResult.Hash != zeroHash {
t.Errorf("invalid hash (expected zero): %s", txResult.Hash)
return
}
}
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txId := range parsedIds {
if backend.TransactionQueue().Has(status.QueuedTxId(txId)) {
t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txId)
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
var txIDStrings []string
for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds)
txIDStrings = append(txIDStrings, <-txIDs)
}
txIdJSON, _ := json.Marshal(txIdStrings)
discardTxs(string(txIdJSON))
txIDJSON, _ := json.Marshal(txIDStrings)
discardTxs(string(txIDJSON))
}()
// send multiple transactions
@ -1319,8 +1345,12 @@ func startTestNode(t *testing.T) <-chan struct{} {
}
// inject test accounts
geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account1.pk")
geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account2.pk")
if err := geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account1.pk"); err != nil {
panic(err)
}
if err := geth.ImportTestAccount(filepath.Join(geth.TestDataDir, "keystore"), "test-account2.pk"); err != nil {
panic(err)
}
waitForNodeStart := make(chan struct{}, 1)
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
@ -1356,7 +1386,7 @@ func startTestNode(t *testing.T) <-chan struct{} {
go func() {
configJSON := `{
"NetworkId": ` + strconv.Itoa(params.TestNetworkId) + `,
"NetworkId": ` + strconv.Itoa(params.TestNetworkID) + `,
"DataDir": "` + geth.TestDataDir + `",
"HTTPPort": ` + strconv.Itoa(testConfig.Node.HTTPPort) + `,
"WSPort": ` + strconv.Itoa(testConfig.Node.WSPort) + `,
@ -1364,11 +1394,13 @@ func startTestNode(t *testing.T) <-chan struct{} {
"LogLevel": "INFO"
}`
response := StartNode(C.CString(configJSON))
err := geth.JSONError{}
responseErr := geth.JSONError{}
json.Unmarshal([]byte(C.GoString(response)), &err)
if err.Error != "" {
panic("cannot start node: " + err.Error)
if err := json.Unmarshal([]byte(C.GoString(response)), &responseErr); err != nil {
panic(err)
}
if responseErr.Error != "" {
panic("cannot start node: " + responseErr.Error)
}
}()

View File

@ -11,53 +11,79 @@ import (
)
var (
// WhisperEchoModeFlag enables/disables Echo mode (arguments are printed for diagnostics)
WhisperEchoModeFlag = cli.BoolTFlag{
Name: "echo",
Usage: "Echo mode, prints some arguments for diagnostics (default: true)",
}
// WhisperBootstrapNodeFlag marks node as not actively listening for incoming connections
WhisperBootstrapNodeFlag = cli.BoolTFlag{
Name: "bootstrap",
Usage: "Don't actively connect to peers, wait for incoming connections (default: true)",
}
// WhisperNotificationServerNodeFlag enables/disables Push Notifications services
WhisperNotificationServerNodeFlag = cli.BoolFlag{
Name: "notify",
Usage: "Node is capable of sending Push Notifications",
}
// WhisperForwarderNodeFlag enables/disables message forwarding
// (when neither sends nor decrypts envelopes, just forwards them)
WhisperForwarderNodeFlag = cli.BoolFlag{
Name: "forward",
Usage: "Only forward messages, neither send nor decrypt messages",
}
// WhisperMailserverNodeFlag enables/disables Inboxing services
WhisperMailserverNodeFlag = cli.BoolFlag{
Name: "mailserver",
Usage: "Delivers expired messages on demand",
}
// WhisperIdentityFile is path to file containing private key of the node (for asymmetric encryption)
WhisperIdentityFile = cli.StringFlag{
Name: "identity",
Usage: "Protocol identity file (private key used for asymetric encryption)",
Usage: "Protocol identity file (private key used for asymmetric encryption)",
}
// WhisperPasswordFile is password used to do a symmetric encryption
WhisperPasswordFile = cli.StringFlag{
Name: "password",
Usage: "Password file (password is used for symmetric encryption)",
}
// WhisperPortFlag defines port on which Whisper protocol is listening
WhisperPortFlag = cli.IntFlag{
Name: "port",
Usage: "Whisper node's listening port",
Value: params.WhisperPort,
}
// WhisperPoWFlag is the minimum PoW required by the node
WhisperPoWFlag = cli.Float64Flag{
Name: "pow",
Usage: "PoW for messages to be added to queue, in float format",
Value: params.WhisperMinimumPoW,
}
// WhisperTTLFlag defines node's default TTL for envelopes
WhisperTTLFlag = cli.IntFlag{
Name: "ttl",
Usage: "Time to live for messages, in seconds",
Value: params.WhisperTTL,
}
// WhisperInjectTestAccounts if set, then test accounts will be imported
// into node's key store, and then will be injected as key pairs (identities)
// into the Whisper as well.
WhisperInjectTestAccounts = cli.BoolTFlag{
Name: "injectaccounts",
Usage: "Whether test account should be injected or not (default: true)",
}
// FirebaseAuthorizationKey path to file containing FCM password
FirebaseAuthorizationKey = cli.StringFlag{
Name: "firebaseauth",
Usage: "FCM Authorization Key used for sending Push Notifications",
@ -97,19 +123,27 @@ func wnode(ctx *cli.Context) error {
// import test accounts
if ctx.BoolT(WhisperInjectTestAccounts.Name) {
geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account1.pk")
geth.ImportTestAccount(filepath.Join(config.DataDir, "keystore"), "test-account2.pk")
if err = geth.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 {
return err
}
}
if err := geth.CreateAndRunNode(config); err != nil {
if err = geth.CreateAndRunNode(config); err != nil {
return err
}
// inject test accounts into Whisper
if ctx.BoolT(WhisperInjectTestAccounts.Name) {
testConfig, _ := geth.LoadTestConfig()
injectAccountIntoWhisper(testConfig.Account1.Address, testConfig.Account1.Password)
injectAccountIntoWhisper(testConfig.Account2.Address, testConfig.Account2.Password)
if err = injectAccountIntoWhisper(testConfig.Account1.Address, testConfig.Account1.Password); err != nil {
return err
}
if err = injectAccountIntoWhisper(testConfig.Account2.Address, testConfig.Account2.Password); err != nil {
return err
}
}
// wait till node has been stopped
@ -204,7 +238,7 @@ func injectAccountIntoWhisper(address, password string) error {
return geth.ErrAddressToAccountMappingFailure
}
account, accountKey, err := keyStore.AccountDecryptedKey(account, password)
_, accountKey, err := keyStore.AccountDecryptedKey(account, password)
if err != nil {
return fmt.Errorf("%s: %v", geth.ErrAccountToKeyMappingFailure.Error(), err)
}
@ -213,7 +247,9 @@ func injectAccountIntoWhisper(address, password string) error {
if err != nil {
return err
}
whisperService.AddKeyPair(accountKey.PrivateKey)
if _, err = whisperService.AddKeyPair(accountKey.PrivateKey); err != nil {
return err
}
return nil
}

View File

@ -42,6 +42,7 @@ import (
// TODO make sure we're doing this ^^^^ !!!!!!
const (
// HardenedKeyStart defines a starting point for hardened key.
// Each extended key has 2^31 normal child keys and 2^31 hardened child keys.
// Thus the range for normal child keys is [0, 2^31 - 1] and the range for hardened child keys is [2^31, 2^32 - 1].
HardenedKeyStart = 0x80000000 // 2^31
@ -58,31 +59,40 @@ const (
// public/private key data.
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
CoinTypeBTC = 0 // 0x80000000
CoinTypeTestNet = 1 // 0x80000001
CoinTypeETH = 60 // 0x8000003c
CoinTypeETC = 60 // 0x80000000
// CoinTypeBTC is BTC coin type
CoinTypeBTC = 0 // 0x80000000
// CoinTypeTestNet is test net coin type
CoinTypeTestNet = 1 // 0x80000001
// CoinTypeETH is ETH coin type
CoinTypeETH = 60 // 0x8000003c
// EmptyExtendedKeyString marker string for zero extended key
EmptyExtendedKeyString = "Zeroed extended key"
)
// errors
var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidSeed = errors.New("seed is invalid")
ErrInvalidSeedLen = fmt.Errorf("the recommended size of seed is %d-%d bits", MinSeedBytes, MaxSeedBytes)
ErrDerivingPrivateFromPublic = errors.New("cannot derive private key from public key")
ErrDerivingHardenedFromPublic = errors.New("cannot derive a hardened key from public key")
ErrBadChecksum = errors.New("bad extended key checksum")
ErrInvalidKeyLen = errors.New("serialized extended key length is invalid")
ErrDerivingChild = errors.New("error deriving child key")
ErrInvalidMasterKey = errors.New("invalid master key supplied")
PrivateKeyVersion, _ = hex.DecodeString("0488ADE4")
PublicKeyVersion, _ = hex.DecodeString("0488B21E")
)
type CoinType int
var (
// PrivateKeyVersion is version for private key
PrivateKeyVersion, _ = hex.DecodeString("0488ADE4")
// PublicKeyVersion is version for public key
PublicKeyVersion, _ = hex.DecodeString("0488B21E")
)
// ExtendedKey represents BIP44-compliant HD key
type ExtendedKey struct {
Version []byte // 4 bytes, mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private
Depth uint16 // 1 byte, depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
@ -133,10 +143,10 @@ func NewMaster(seed, salt []byte) (*ExtendedKey, error) {
// 2) Private extended key -> Non-hardened child private extended key
// 3) Public extended key -> Non-hardened child public extended key
// 4) Public extended key -> Hardened child public extended key (INVALID!)
func (parent *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
// A hardened child may not be created from a public extended key (Case #4).
isChildHardened := i >= HardenedKeyStart
if !parent.IsPrivate && isChildHardened {
if !k.IsPrivate && isChildHardened {
return nil, ErrDerivingHardenedFromPublic
}
@ -144,30 +154,30 @@ func (parent *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
seed := make([]byte, keyLen+4)
if isChildHardened {
// Case #1: 0x00 || ser256(parentKey) || ser32(i)
copy(seed[1:], parent.KeyData) // 0x00 || ser256(parentKey)
copy(seed[1:], k.KeyData) // 0x00 || ser256(parentKey)
} else {
// Case #2 and #3: serP(parentPubKey) || ser32(i)
copy(seed, parent.pubKeyBytes())
copy(seed, k.pubKeyBytes())
}
binary.BigEndian.PutUint32(seed[keyLen:], i)
secretKey, chainCode, err := splitHMAC(seed, parent.ChainCode)
secretKey, chainCode, err := splitHMAC(seed, k.ChainCode)
if err != nil {
return nil, err
}
child := &ExtendedKey{
ChainCode: chainCode,
Depth: parent.Depth + 1,
Depth: k.Depth + 1,
ChildNumber: i,
IsPrivate: parent.IsPrivate,
IsPrivate: k.IsPrivate,
// The fingerprint for the derived child is the first 4 bytes of parent's
FingerPrint: btcutil.Hash160(parent.pubKeyBytes())[:4],
FingerPrint: btcutil.Hash160(k.pubKeyBytes())[:4],
}
if parent.IsPrivate {
if k.IsPrivate {
// Case #1 or #2: childKey = parse256(IL) + parentKey
parentKeyBigInt := new(big.Int).SetBytes(parent.KeyData)
parentKeyBigInt := new(big.Int).SetBytes(k.KeyData)
keyBigInt := new(big.Int).SetBytes(secretKey)
keyBigInt.Add(keyBigInt, parentKeyBigInt)
keyBigInt.Mod(keyBigInt, btcec.S256().N)
@ -185,7 +195,7 @@ func (parent *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
// Convert the serialized compressed parent public key into X and Y coordinates
// so it can be added to the intermediate public key.
pubKey, err := btcec.ParsePubKey(parent.KeyData, btcec.S256())
pubKey, err := btcec.ParsePubKey(k.KeyData, btcec.S256())
if err != nil {
return nil, err
}
@ -201,17 +211,17 @@ func (parent *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
// BIP44Child returns Status CKD#i (where i is child index).
// BIP44 format is used: m / purpose' / coin_type' / account' / change / address_index
func (master *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) {
if !master.IsPrivate {
func (k *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) {
if !k.IsPrivate {
return nil, ErrInvalidMasterKey
}
if master.Depth != 0 {
if k.Depth != 0 {
return nil, ErrInvalidMasterKey
}
// m/44'/60'/0'/0/index
extKey, err := master.Derive([]uint32{
extKey, err := k.Derive([]uint32{
HardenedKeyStart + 44, // purpose
HardenedKeyStart + coinType, // cointype
HardenedKeyStart + 0, // account
@ -225,9 +235,10 @@ func (master *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error)
return extKey, nil
}
func (parent *ExtendedKey) Derive(path []uint32) (*ExtendedKey, error) {
// Derive returns a derived child key at a given path
func (k *ExtendedKey) Derive(path []uint32) (*ExtendedKey, error) {
var err error
extKey := parent
extKey := k
for _, i := range path {
extKey, err = extKey.Child(i)
if err != nil {
@ -238,6 +249,8 @@ func (parent *ExtendedKey) Derive(path []uint32) (*ExtendedKey, error) {
return extKey, nil
}
// Neuter returns a new extended public key from a give extended private key.
// If the input extended key is already public, it will be returned unaltered.
func (k *ExtendedKey) Neuter() (*ExtendedKey, error) {
// Already an extended public key.
if !k.IsPrivate {

View File

@ -164,240 +164,219 @@ tests:
}
}
func TestPrivateChildDerivation(t *testing.T) {
// The private extended keys for test vectors in [BIP32].
testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
tests := []struct {
name string
master string
path []uint32
wantPriv string
}{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPrivKey,
path: []uint32{},
wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPrivKey,
path: []uint32{0},
wantPriv: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPrivKey,
path: []uint32{0, 1},
wantPriv: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2},
wantPriv: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2},
wantPriv: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantPriv: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPrivKey,
path: []uint32{},
wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPrivKey,
path: []uint32{0},
wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647},
wantPriv: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1},
wantPriv: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantPriv: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPriv: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam",
},
// Custom tests to trigger specific conditions.
{
// Seed 000000000000000000000000000000da.
name: "Derived privkey with zero high byte m/0",
master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz",
path: []uint32{0},
wantPriv: "xprv9uC5JqtViMmgcAMUxcsBCBFA7oYCNs4bozPbyvLfddjHou4rMiGEHipz94xNaPb1e4f18TRoPXfiXx4C3cDAcADqxCSRSSWLvMBRWPctSN9",
},
}
tests:
for i, test := range tests {
extKey, err := extkeys.NewKeyFromString(test.master)
if err != nil {
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
continue
}
extKey, err = extKey.Derive(test.path)
if err != nil {
t.Errorf("cannot derive child: %v", err)
continue tests
}
privStr := extKey.String()
if privStr != test.wantPriv {
t.Errorf("Child #%d (%s): mismatched serialized private extended key -- got: %s, want: %s",
i, test.name, privStr, test.wantPriv)
continue
} else {
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
}
}
}
// TestPublicDerivation tests several vectors which derive public keys from
// other public keys works as intended.
func TestPublicDerivation(t *testing.T) {
// The public extended keys for test vectors in [BIP32].
testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
tests := []struct {
func TestChildDerivation(t *testing.T) {
type testCase struct {
name string
master string
path []uint32
wantPub string
}{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPubKey,
path: []uint32{},
wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPubKey,
path: []uint32{0},
wantPub: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPubKey,
path: []uint32{0, 1},
wantPub: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2},
wantPub: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2},
wantPub: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantPub: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPubKey,
path: []uint32{},
wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPubKey,
path: []uint32{0},
wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647},
wantPub: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1},
wantPub: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantPub: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPub: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK",
},
wantKey string
}
tests:
for i, test := range tests {
extKey, err := extkeys.NewKeyFromString(test.master)
if err != nil {
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
continue
// derive public keys from private keys
getPrivateChildDerivationTests := func() []testCase {
// The private extended keys for test vectors in [BIP32].
testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
return []testCase{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPrivKey,
path: []uint32{},
wantKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPrivKey,
path: []uint32{0},
wantKey: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPrivKey,
path: []uint32{0, 1},
wantKey: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2},
wantKey: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2},
wantKey: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantKey: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPrivKey,
path: []uint32{},
wantKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPrivKey,
path: []uint32{0},
wantKey: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647},
wantKey: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1},
wantKey: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantKey: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantKey: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam",
},
// Custom tests to trigger specific conditions.
{
// Seed 000000000000000000000000000000da.
name: "Derived privkey with zero high byte m/0",
master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz",
path: []uint32{0},
wantKey: "xprv9uC5JqtViMmgcAMUxcsBCBFA7oYCNs4bozPbyvLfddjHou4rMiGEHipz94xNaPb1e4f18TRoPXfiXx4C3cDAcADqxCSRSSWLvMBRWPctSN9",
},
}
extKey, err = extKey.Derive(test.path)
if err != nil {
t.Errorf("cannot derive child: %v", err)
continue tests
}
}
pubStr := extKey.String()
if pubStr != test.wantPub {
t.Errorf("Child #%d (%s): mismatched serialized public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub)
continue
} else {
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
// derive public keys from other public keys
getPublicChildDerivationTests := func() []testCase {
// The public extended keys for test vectors in [BIP32].
testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
return []testCase{
// Test vector 1
{
name: "test vector 1 chain m",
master: testVec1MasterPubKey,
path: []uint32{},
wantKey: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
},
{
name: "test vector 1 chain m/0",
master: testVec1MasterPubKey,
path: []uint32{0},
wantKey: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1",
},
{
name: "test vector 1 chain m/0/1",
master: testVec1MasterPubKey,
path: []uint32{0, 1},
wantKey: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr",
},
{
name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2},
wantKey: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv",
},
{
name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2},
wantKey: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp",
},
{
name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2, 1000000000},
wantKey: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9",
},
// Test vector 2
{
name: "test vector 2 chain m",
master: testVec2MasterPubKey,
path: []uint32{},
wantKey: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
},
{
name: "test vector 2 chain m/0",
master: testVec2MasterPubKey,
path: []uint32{0},
wantKey: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
},
{
name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647},
wantKey: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD",
},
{
name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1},
wantKey: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646},
wantKey: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta",
},
{
name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantKey: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK",
},
}
}
runTests := func(tests []testCase) {
for i, test := range tests {
extKey, err := extkeys.NewKeyFromString(test.master)
if err != nil {
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
continue
}
extKey, err = extKey.Derive(test.path)
if err != nil {
t.Errorf("cannot derive child: %v", err)
continue
}
gotKey := extKey.String()
if gotKey != test.wantKey {
t.Errorf("Child #%d (%s): mismatched serialized extended key -- got: %s, want: %s", i, test.name, gotKey, test.wantKey)
continue
} else {
t.Logf("test %d (%s): %s", i, test.name, extKey.String())
}
}
}
runTests(getPrivateChildDerivationTests())
runTests(getPublicChildDerivationTests())
}
func TestErrors(t *testing.T) {
@ -425,6 +404,10 @@ func TestErrors(t *testing.T) {
password := "badpassword"
extKey, err := extkeys.NewMaster(mnemonic.MnemonicSeed(phrase, password), []byte(extkeys.Salt))
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
pubKey, err := extKey.Neuter()
if err != nil {

View File

@ -7,10 +7,11 @@ import (
"encoding/binary"
"errors"
"fmt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"
"math/big"
"strings"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"
)
// Implementation of BIP39 https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
@ -24,6 +25,7 @@ import (
// https://github.com/trezor/python-mnemonic/blob/master/mnemonic/mnemonic.py
// https://github.com/bitpay/bitcore-mnemonic/blob/master/lib/mnemonic.js (used in eth-lightwallet.js)
// Salt is input salt to mnemonic key
const Salt = "status-im"
// available dictionaries
@ -39,6 +41,7 @@ const (
totalAvailableLanguages
)
// Languages is a list of supported languages for which mnemonic keys can be generated
var Languages = [...]string{
"English",
"Chinese (Simplified)",
@ -51,19 +54,26 @@ var Languages = [...]string{
}
var (
Last11BitsMask = big.NewInt(2047)
RightShift11BitsDivider = big.NewInt(2048)
BigOne = big.NewInt(1)
BigTwo = big.NewInt(2)
last11BitsMask = big.NewInt(2047)
rightShift11BitsDivider = big.NewInt(2048)
bigOne = big.NewInt(1)
bigTwo = big.NewInt(2)
)
// Language is language identifier
type Language int
// WordList is a list of input strings out of which mnemonic phrase is generated
type WordList [2048]string
// Mnemonic represents mnemonic generator inited with a given salt
type Mnemonic struct {
salt string
wordLists [totalAvailableLanguages]*WordList
}
// NewMnemonic returns new mnemonic generator
// nolint: dupl, misspell
func NewMnemonic(salt string) *Mnemonic {
if len(salt) == 0 {
salt = Salt
@ -85,6 +95,7 @@ func NewMnemonic(salt string) *Mnemonic {
return mnemonic
}
// AvailableLanguages returns list of languages available for mnemonic generation
func (m *Mnemonic) AvailableLanguages() []Language {
languages := make([]Language, totalAvailableLanguages)
for language := range m.wordLists {
@ -97,7 +108,8 @@ func (m *Mnemonic) AvailableLanguages() []Language {
//
//}
// To create a binary seed from the mnemonic, we use the PBKDF2 function with a mnemonic sentence (in UTF-8 NFKD)
// MnemonicSeed creates and returns a binary seed from the mnemonic.
// We use the PBKDF2 function with a mnemonic sentence (in UTF-8 NFKD)
// used as the password and the string SALT + passphrase (again in UTF-8 NFKD) used as the salt.
// The iteration count is set to 2048 and HMAC-SHA512 is used as the pseudo-random function.
// The length of the derived key is 512 bits (= 64 bytes).
@ -105,7 +117,7 @@ func (m *Mnemonic) MnemonicSeed(mnemonic string, password string) []byte {
return pbkdf2.Key(norm.NFKD.Bytes([]byte(mnemonic)), norm.NFKD.Bytes([]byte(m.salt+password)), 2048, 64, sha512.New)
}
// Returns a human readable seed for BIP32 Hierarchical Deterministic Wallets
// MnemonicPhrase returns a human readable seed for BIP32 Hierarchical Deterministic Wallets
func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
wordList, err := m.WordList(language)
if err != nil {
@ -149,15 +161,14 @@ func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
// TODO simplify?
for i := uint(0); i < checksumBitLength; i++ {
// Bitshift 1 left
entropyBigInt.Mul(entropyBigInt, BigTwo)
entropyBigInt.Mul(entropyBigInt, bigTwo)
// Set rightmost bit if leftmost checksum bit is set
if uint8(hash[0]&(1<<(7-i))) > 0 {
entropyBigInt.Or(entropyBigInt, BigOne)
if uint8(hash[0]&(1<<(7-i))) > 0 { // nolint: unconvert
entropyBigInt.Or(entropyBigInt, bigOne)
}
}
entropy = entropyBigInt.Bytes()
word := big.NewInt(0)
for i := sentenceLength - 1; i >= 0; i-- {
@ -165,8 +176,8 @@ func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
// each encoding a number from 0-2047, serving as an index into a wordlist.
// Get 11 right most bits and bitshift 11 to the right for next time
word.And(entropyBigInt, Last11BitsMask)
entropyBigInt.Div(entropyBigInt, RightShift11BitsDivider)
word.And(entropyBigInt, last11BitsMask)
entropyBigInt.Div(entropyBigInt, rightShift11BitsDivider)
// Get the bytes representing the 11 bits as a 2 byte slice
wordBytes := padByteSlice(word.Bytes(), 2)
@ -179,6 +190,7 @@ func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
return strings.Join(words, wordSeperator), nil
}
// ValidMnemonic validates mnemonic string
func (m *Mnemonic) ValidMnemonic(mnemonic string, language Language) bool {
wordList, err := m.WordList(language)
if err != nil {
@ -206,6 +218,7 @@ func (m *Mnemonic) ValidMnemonic(mnemonic string, language Language) bool {
return true
}
// WordList returns list of words for a given language
func (m *Mnemonic) WordList(language Language) (*WordList, error) {
if m.wordLists[language] == nil {
return nil, fmt.Errorf("language word list is missing (language id: %d)", language)

View File

@ -9,13 +9,16 @@ import (
"github.com/btcsuite/btcd/btcec"
)
// errors
var (
ErrInvalidSecretKey = errors.New("generated secret key cannot be used")
)
func splitHMAC(seed, salt []byte) (secretKey, chainCode []byte, err error) {
data := hmac.New(sha512.New, salt)
data.Write(seed)
if _, err = data.Write(seed); err != nil {
return
}
I := data.Sum(nil)
// Split I into two 32-byte sequences, IL and IR.

View File

@ -10,13 +10,12 @@ import (
"github.com/status-im/status-go/extkeys"
)
// errors
var (
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address")
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account")
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
ErrAddressToAccountMappingFailure = errors.New("cannot retrieve a valid account for a given address")
ErrAccountToKeyMappingFailure = errors.New("cannot retrieve a valid key for a given account")
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
ErrWhisperNoIdentityFound = errors.New("failed to locate identity previously injected into Whisper")
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")
@ -60,7 +59,7 @@ func CreateChildAccount(parentAddress, password string) (address, pubKey string,
}
if parentAddress == "" && nodeManager.SelectedAccount != nil { // derive from selected account by default
parentAddress = string(nodeManager.SelectedAccount.Address.Hex())
parentAddress = nodeManager.SelectedAccount.Address.Hex()
}
if parentAddress == "" {
@ -88,7 +87,9 @@ func CreateChildAccount(parentAddress, password string) (address, pubKey string,
if err != nil {
return "", "", err
}
keyStore.IncSubAccountIndex(account, password)
if err = keyStore.IncSubAccountIndex(account, password); err != nil {
return "", "", err
}
accountKey.SubAccountIndex++
// import derived key into account keystore
@ -206,13 +207,6 @@ func Logout() error {
return nil
}
// UnlockAccount unlocks an existing account for a certain duration and
// inject the account as a whisper identity if the account was created as
// a whisper enabled account
func UnlockAccount(address, password string, seconds int) error {
return ErrUnlockCalled
}
// 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) {

View File

@ -11,7 +11,7 @@ import (
func TestAccountsList(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
t.Fatal(err)
return
}
@ -20,7 +20,9 @@ func TestAccountsList(t *testing.T) {
t.Errorf("expected LES service: %v", err)
}
accounts := les.StatusBackend.AccountManager().Accounts()
geth.Logout()
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 {
@ -121,16 +123,17 @@ func TestAccountsList(t *testing.T) {
func TestAccountsCreateChildAccount(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
geth.Logout() // to make sure that we start with empty account (which might get populated during previous tests)
// 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.Error(err)
return
t.Fatal(err)
}
// create an account
@ -148,7 +151,7 @@ func TestAccountsCreateChildAccount(t *testing.T) {
}
// obtain decrypted key, and make sure that extended key (which will be used as root for sub-accounts) is present
account, 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
@ -174,7 +177,7 @@ func TestAccountsCreateChildAccount(t *testing.T) {
// try to create sub-account with wrong password
_, _, err = geth.CreateChildAccount("", "wrong password")
if !reflect.DeepEqual(err, errors.New("cannot retreive a valid key for a given account: could not decrypt key with given passphrase")) {
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
}
@ -210,8 +213,7 @@ func TestAccountsCreateChildAccount(t *testing.T) {
func TestAccountsRecoverAccount(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
keyStore, _ := geth.NodeManagerInstance().AccountKeyStore()
@ -261,7 +263,7 @@ func TestAccountsRecoverAccount(t *testing.T) {
}
// make sure that extended key exists and is imported ok too
account, 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
@ -304,8 +306,7 @@ func TestAccountSelect(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
// test to see if the account was injected in whisper
@ -370,8 +371,7 @@ func TestAccountsLogout(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
whisperService, err := geth.NodeManagerInstance().WhisperService()
@ -401,8 +401,7 @@ func TestAccountsLogout(t *testing.T) {
t.Error("identity not injected into whisper")
}
err = geth.Logout()
if err != nil {
if err = geth.Logout(); err != nil {
t.Errorf("cannot logout: %v", err)
}
@ -415,8 +414,7 @@ func TestAccountsLogout(t *testing.T) {
func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
// we need to make sure that selected account is injected as identity into Whisper
@ -483,8 +481,7 @@ func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
// stop node (and all of its sub-protocols)
if err := geth.NodeManagerInstance().StopNode(); err != nil {
t.Error(err)
return
t.Fatal(err)
}
// make sure that account is still selected
@ -499,8 +496,7 @@ func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
// resume node
if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
t.Error(err)
return
t.Fatal(err)
}
// re-check selected account (account2 MUST be selected)
@ -529,11 +525,12 @@ func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
err := geth.PrepareTestNode()
if err != nil {
t.Error(err)
return
t.Fatal(err)
}
geth.Logout()
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()
@ -562,8 +559,7 @@ func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
// stop node (and all of its sub-protocols)
if err := geth.NodeManagerInstance().StopNode(); err != nil {
t.Error(err)
return
t.Fatal(err)
}
// make sure that no account is selected
@ -574,8 +570,7 @@ func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
// resume node
if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
t.Error(err)
return
t.Fatal(err)
}
// make sure that no account is selected

View File

@ -6,14 +6,58 @@ import (
)
const (
EventLocalStorageSet = "local_storage.set"
// EventLocalStorageSet is triggered when set request is sent to local storage
EventLocalStorageSet = "local_storage.set"
// LocalStorageMaxDataLen is maximum length of data that you can store in local storage
LocalStorageMaxDataLen = 256
)
// registerHandlers augments and transforms a given jail cell's underlying VM,
// by adding and replacing method handlers.
func registerHandlers(jail *Jail, vm *otto.Otto, chatID string) (err error) {
jeth, err := vm.Get("jeth")
if err != nil {
return err
}
registerHandler := jeth.Object().Set
// register send handler
if err = registerHandler("send", makeSendHandler(jail, chatID)); err != nil {
return err
}
// register sendAsync handler
if err = registerHandler("sendAsync", makeSendHandler(jail, chatID)); err != nil {
return err
}
// register isConnected handler
if err = registerHandler("isConnected", makeJethIsConnectedHandler(jail)); err != nil {
return err
}
// define localStorage
if err = vm.Set("localStorage", struct{}{}); err != nil {
return
}
// register localStorage.set handler
localStorage, err := vm.Get("localStorage")
if err != nil {
return
}
if err = localStorage.Object().Set("set", makeLocalStorageSetHandler(chatID)); err != nil {
return
}
return nil
}
// makeSendHandler returns jeth.send() and jeth.sendAsync() handler
func makeSendHandler(jail *Jail, chatId string) func(call otto.FunctionCall) (response otto.Value) {
func makeSendHandler(jail *Jail, chatID string) func(call otto.FunctionCall) (response otto.Value) {
return func(call otto.FunctionCall) (response otto.Value) {
return jail.Send(chatId, call)
return jail.Send(chatID, call)
}
}
@ -30,7 +74,7 @@ func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (respon
return newErrorResponse(call, -32603, err.Error(), nil)
}
if netListeningResult != true {
if !netListeningResult {
return newErrorResponse(call, -32603, geth.ErrInvalidGethNode.Error(), nil)
}
@ -39,7 +83,7 @@ func makeJethIsConnectedHandler(jail *Jail) func(call otto.FunctionCall) (respon
}
// makeLocalStorageSetHandler returns localStorage.set() handler
func makeLocalStorageSetHandler(chatId string) func(call otto.FunctionCall) (response otto.Value) {
func makeLocalStorageSetHandler(chatID string) func(call otto.FunctionCall) (response otto.Value) {
return func(call otto.FunctionCall) otto.Value {
data := call.Argument(0).String()
if len(data) > LocalStorageMaxDataLen { // cap input string
@ -49,7 +93,7 @@ func makeLocalStorageSetHandler(chatId string) func(call otto.FunctionCall) (res
geth.SendSignal(geth.SignalEnvelope{
Type: EventLocalStorageSet,
Event: geth.LocalStorageSetEvent{
ChatId: chatId,
ChatID: chatID,
Data: data,
},
})

View File

@ -16,13 +16,17 @@ import (
)
const (
// JailedRuntimeRequestTimeout seconds before jailed request times out
JailedRuntimeRequestTimeout = time.Second * 60
)
// 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
@ -31,16 +35,18 @@ type Jail struct {
requestQueue *geth.JailedRequestQueue
}
// JailedRuntime represents single jail cell, which is JavaScript VM.
type JailedRuntime struct {
id string
vm *otto.Otto
sem *semaphore.Semaphore
}
var Web3_JS = static.MustAsset("scripts/web3.js")
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{
@ -51,6 +57,7 @@ func New() *Jail {
return jailInstance
}
// 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
@ -58,10 +65,12 @@ func Init(js string) *Jail {
return jailInstance
}
// GetInstance returns singleton jail environment instance
func GetInstance() *Jail {
return New() // singleton, we will always get the same reference
}
// NewJailedRuntime initializes and returns jail cell
func NewJailedRuntime(id string) *JailedRuntime {
return &JailedRuntime{
id: id,
@ -70,7 +79,10 @@ func NewJailedRuntime(id string) *JailedRuntime {
}
}
func (jail *Jail) Parse(chatId string, js string) string {
// 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())
}
@ -78,25 +90,23 @@ func (jail *Jail) Parse(chatId string, js string) string {
jail.Lock()
defer jail.Unlock()
jail.cells[chatId] = NewJailedRuntime(chatId)
vm := jail.cells[chatId].vm
jail.cells[chatID] = NewJailedRuntime(chatID)
vm := jail.cells[chatID].vm
initJjs := jail.statusJS + ";"
_, err := vm.Run(initJjs)
if _, err = vm.Run(initJjs); err != nil {
return printError(err.Error())
}
// jeth and its handlers
vm.Set("jeth", struct{}{})
jethObj, _ := vm.Get("jeth")
jethObj.Object().Set("send", makeSendHandler(jail, chatId))
jethObj.Object().Set("sendAsync", makeSendHandler(jail, chatId))
jethObj.Object().Set("isConnected", makeJethIsConnectedHandler(jail))
// init jeth and its handlers
if err = vm.Set("jeth", struct{}{}); err != nil {
return printError(err.Error())
}
if err = registerHandlers(jail, vm, chatID); err != nil {
return printError(err.Error())
}
// localStorage and its handlers
vm.Set("localStorage", struct{}{})
localStorage, _ := vm.Get("localStorage")
localStorage.Object().Set("set", makeLocalStorageSetHandler(chatId))
jjs := string(Web3_JS) + `
jjs := string(web3JS) + `
var Web3 = require('web3');
var web3 = new Web3(jeth);
var Bignumber = require("bignumber.js");
@ -104,24 +114,30 @@ func (jail *Jail) Parse(chatId string, js string) string {
return new Bignumber(val);
}
` + js + "; var catalog = JSON.stringify(_status_catalog);"
vm.Run(jjs)
if _, err = vm.Run(jjs); err != nil {
return printError(err.Error())
}
res, _ := vm.Get("catalog")
res, err := vm.Get("catalog")
if err != nil {
return printError(err.Error())
}
return printResult(res.String(), err)
}
func (jail *Jail) Call(chatId string, path string, args string) string {
// Call executes given JavaScript function w/i a jail cell context identified by the chatID
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]
cell, ok := jail.cells[chatID]
if !ok {
jail.RUnlock()
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatId))
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatID))
}
jail.RUnlock()
@ -131,7 +147,8 @@ func (jail *Jail) Call(chatId string, path string, args string) string {
return printResult(res.String(), err)
}
func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
// GetVM returns instance of Otto VM (which is persisted w/i jail cell) by chatID
func (jail *Jail) GetVM(chatID string) (*otto.Otto, error) {
if jail == nil {
return nil, ErrInvalidJail
}
@ -139,16 +156,17 @@ func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
jail.RLock()
defer jail.RUnlock()
cell, ok := jail.cells[chatId]
cell, ok := jail.cells[chatID]
if !ok {
return nil, fmt.Errorf("Cell[%s] doesn't exist.", chatId)
return nil, fmt.Errorf("cell[%s] doesn't exist", chatID)
}
return cell.vm, nil
}
// Send will serialize the first argument, send it to the node and returns the response.
func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Value) {
// 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)
@ -183,7 +201,7 @@ func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Val
resps, _ := call.Otto.Object("new Array()")
for _, req := range reqs {
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("id", req.Id)
resp.Set("id", req.ID)
var result json.RawMessage
// execute directly w/o RPC call to node
@ -191,7 +209,7 @@ func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Val
txHash, err := requestQueue.ProcessSendTransactionRequest(call.Otto, req)
resp.Set("result", txHash.Hex())
if err != nil {
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object()
resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
}
resps.Call("push", resp)
continue
@ -200,7 +218,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 := requestQueue.PreProcessRequest(call.Otto, req)
if err != nil {
return newErrorResponse(call, -32603, err.Error(), nil)
}
@ -220,9 +238,9 @@ func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Val
// raw message for some reason.
resp.Set("result", otto.NullValue())
} else {
resultVal, err := JSON.Call("parse", string(result))
if err != nil {
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object()
resultVal, callErr := JSON.Call("parse", string(result))
if callErr != nil {
resp = newErrorResponse(call, -32603, callErr.Error(), &req.ID).Object()
} else {
resp.Set("result", resultVal)
}
@ -233,12 +251,12 @@ func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Val
"message": err.Error(),
})
default:
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object()
resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
}
resps.Call("push", resp)
// do extra request post processing (setting back tx context)
requestQueue.PostProcessRequest(call.Otto, req, messageId)
requestQueue.PostProcessRequest(call.Otto, req, messageID)
}
// Return the responses either to the callback (if supplied)
@ -255,6 +273,9 @@ 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
@ -279,6 +300,9 @@ func (jail *Jail) RPCClient() (*rpc.Client, error) {
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
@ -312,7 +336,7 @@ func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface
func newResultResponse(call otto.FunctionCall, result interface{}) otto.Value {
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("result", result)
resp.Set("result", result) // nolint: errcheck
return resp.Value()
}

View File

@ -24,7 +24,7 @@ const (
whisperMessage4 = `test message 4 ("" -> "", anon broadcast)`
whisperMessage5 = `test message 5 ("" -> K1, encrypted anon broadcast)`
whisperMessage6 = `test message 6 (K2 -> K1, signed+encrypted, to us)`
chatID = "testChat"
testChatID = "testChat"
statusJSFilePath = "testdata/status.js"
txSendFolder = "testdata/tx-send/"
)
@ -54,17 +54,17 @@ func TestJailUnInited(t *testing.T) {
expectedError := errorWrapper(jail.ErrInvalidJail)
var jailInstance *jail.Jail
response := jailInstance.Parse(chatID, ``)
response := jailInstance.Parse(testChatID, ``)
if response != expectedError {
t.Errorf("error expected, but got: %v", response)
}
response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`)
response = jailInstance.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
if response != expectedError {
t.Errorf("error expected, but got: %v", response)
}
_, err := jailInstance.GetVM(chatID)
_, err := jailInstance.GetVM(testChatID)
if err != jail.ErrInvalidJail {
t.Errorf("error expected, but got: %v", err)
}
@ -84,14 +84,14 @@ func TestJailUnInited(t *testing.T) {
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
response = jailInstance.Parse(chatID, statusJS)
response = jailInstance.Parse(testChatID, statusJS)
expectedResponse := `{"result": {"commands":{},"responses":{}}}`
if response != expectedResponse {
t.Errorf("unexpected response received: %v", response)
}
// however, we still expect issue voiced if somebody tries to execute code with Call
response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`)
response = jailInstance.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
if response != errorWrapper(geth.ErrInvalidGethNode) {
t.Errorf("error expected, but got: %v", response)
}
@ -102,7 +102,7 @@ func TestJailUnInited(t *testing.T) {
t.Error(err)
return
}
response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`)
response = jailInstance.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse = `{"result": 144}`
if response != expectedResponse {
t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response)
@ -153,18 +153,18 @@ func TestJailFunctionCall(t *testing.T) {
_status_catalog.commands["testCommand"] = function (params) {
return params.val * params.val;
};`
jailInstance.Parse(chatID, statusJS)
jailInstance.Parse(testChatID, statusJS)
// call with wrong chat id
response := jailInstance.Call("chatIdNonExistent", "", "")
expectedError := `{"error":"Cell[chatIdNonExistent] doesn't exist."}`
response := jailInstance.Call("chatIDNonExistent", "", "")
expectedError := `{"error":"Cell[chatIDNonExistent] doesn't exist."}`
if response != expectedError {
t.Errorf("expected error is not returned: expected %s, got %s", expectedError, response)
return
}
// call extraFunc()
response = jailInstance.Call(chatID, `["commands", "testCommand"]`, `{"val": 12}`)
response = jailInstance.Call(testChatID, `["commands", "testCommand"]`, `{"val": 12}`)
expectedResponse := `{"result": 144}`
if response != expectedResponse {
t.Errorf("expected response is not returned: expected %s, got %s", expectedResponse, response)
@ -183,10 +183,10 @@ func TestJailRPCSend(t *testing.T) {
// load Status JS and add test command to it
statusJS := geth.LoadFromFile(statusJSFilePath)
jailInstance.Parse(chatID, statusJS)
jailInstance.Parse(testChatID, statusJS)
// obtain VM for a given chat (to send custom JS to jailed version of Send())
vm, err := jailInstance.GetVM(chatID)
vm, err := jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("cannot get VM: %v", err)
return
@ -276,7 +276,7 @@ func TestJailSendQueuedTransaction(t *testing.T) {
var txHash common.Hash
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
} else {
t.Logf("Transaction complete: https://testnet.etherscan.io/tx/%s", txHash.Hex())
}
@ -332,7 +332,7 @@ func TestJailSendQueuedTransaction(t *testing.T) {
{
`["commands", "getBalance"]`,
`{"address": "` + testConfig.Account1.Address + `"}`,
`{"result": {"context":{},"result":{"balance":42}}}`, // note emtpy (but present) context!
`{"result": {"context":{},"result":{"balance":42}}}`, // note empty (but present) context!
},
},
},
@ -350,7 +350,7 @@ func TestJailSendQueuedTransaction(t *testing.T) {
{
`["commands", "getBalance"]`,
`{"address": "` + testConfig.Account1.Address + `"}`,
`{"result": {"balance":42}}`, // note emtpy context!
`{"result": {"balance":42}}`, // note empty context!
},
},
},
@ -377,14 +377,14 @@ func TestJailSendQueuedTransaction(t *testing.T) {
for _, test := range tests {
jailInstance := jail.Init(geth.LoadFromFile(txSendFolder + test.file))
geth.PanicAfter(60*time.Second, txCompletedSuccessfully, test.name)
jailInstance.Parse(chatID, ``)
jailInstance.Parse(testChatID, ``)
requireMessageId = test.requireMessageId
for _, command := range test.commands {
go func(jail *jail.Jail, test testCase, command testCommand) {
t.Logf("->%s: %s", test.name, command.command)
response := jail.Call(chatID, command.command, command.params)
response := jail.Call(testChatID, command.command, command.params)
var txHash common.Hash
if command.command == `["commands", "send"]` {
txHash = <-txHashes
@ -432,16 +432,16 @@ func TestJailGetVM(t *testing.T) {
jailInstance := jail.Init("")
expectedError := `Cell[nonExistentChat] doesn't exist.`
expectedError := `cell[nonExistentChat] doesn't exist`
_, err = jailInstance.GetVM("nonExistentChat")
if err == nil || err.Error() != expectedError {
t.Error("expected error, but call succeeded")
}
// now let's create VM..
jailInstance.Parse(chatID, ``)
jailInstance.Parse(testChatID, ``)
// ..and see if VM becomes available
_, err = jailInstance.GetVM(chatID)
_, err = jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -455,10 +455,10 @@ func TestIsConnected(t *testing.T) {
}
jailInstance := jail.Init("")
jailInstance.Parse(chatID, "")
jailInstance.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
vm, err := jailInstance.GetVM(chatID)
vm, err := jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("cannot get VM: %v", err)
return
@ -500,10 +500,10 @@ func TestLocalStorageSet(t *testing.T) {
}
jailInstance := jail.Init("")
jailInstance.Parse(chatID, "")
jailInstance.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
vm, err := jailInstance.GetVM(chatID)
vm, err := jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("cannot get VM: %v", err)
return
@ -522,13 +522,13 @@ func TestLocalStorageSet(t *testing.T) {
}
if envelope.Type == jail.EventLocalStorageSet {
event := envelope.Event.(map[string]interface{})
chatId, ok := event["chat_id"].(string)
chatID, ok := event["chat_id"].(string)
if !ok {
t.Error("Chat id is required, but not found")
return
}
if chatId != chatID {
t.Errorf("incorrect chat id: expected %q, got: %q", chatID, chatId)
if chatID != testChatID {
t.Errorf("incorrect chat id: expected %q, got: %q", testChatID, chatID)
return
}
@ -592,10 +592,10 @@ func TestContractDeployment(t *testing.T) {
}
jailInstance := jail.Init("")
jailInstance.Parse(chatID, "")
jailInstance.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
vm, err := jailInstance.GetVM(chatID)
vm, err := jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("cannot get VM: %v", err)
return
@ -607,32 +607,11 @@ func TestContractDeployment(t *testing.T) {
// 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))
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 transation[%v]: %v", event["id"], err)
return
} else {
t.Logf("Contract created: https://testnet.etherscan.io/tx/%s", txHash.Hex())
}
close(completeQueuedTransaction) // so that timeout is aborted
}
})
handler, err := geth.MakeTestCompleteTxHandler(t, &txHash, completeQueuedTransaction)
if err != nil {
t.Fatal(err)
}
geth.SetDefaultNodeNotificationHandler(handler)
_, err = vm.Run(`
var responseValue = null;
@ -682,10 +661,10 @@ func TestGasEstimation(t *testing.T) {
}
jailInstance := jail.Init("")
jailInstance.Parse(chatID, "")
jailInstance.Parse(testChatID, "")
// obtain VM for a given chat (to send custom JS to jailed version of Send())
vm, err := jailInstance.GetVM(chatID)
vm, err := jailInstance.GetVM(testChatID)
if err != nil {
t.Errorf("cannot get VM: %v", err)
return
@ -697,32 +676,11 @@ func TestGasEstimation(t *testing.T) {
// 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 immediately): {id: %s}\n", event["id"].(string))
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 transation[%v]: %v", event["id"], err)
return
} else {
t.Logf("Contract created: https://testnet.etherscan.io/tx/%s", txHash.Hex())
}
close(completeQueuedTransaction) // so that timeout is aborted
}
})
handler, err := geth.MakeTestCompleteTxHandler(t, &txHash, completeQueuedTransaction)
if err != nil {
t.Fatal(err)
}
geth.SetDefaultNodeNotificationHandler(handler)
_, err = vm.Run(`
var responseValue = null;
@ -783,7 +741,9 @@ func TestJailWhisper(t *testing.T) {
}
accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
whisperService.AddKeyPair(accountKey1.PrivateKey)
if _, err := whisperService.AddKeyPair(accountKey1.PrivateKey); err != nil {
t.Fatalf("identity not injected: %v", accountKey1Hex)
}
if ok, err := whisperAPI.HasKeyPair(accountKey1Hex); err != nil || !ok {
t.Fatalf("identity not injected: %v", accountKey1Hex)
}
@ -795,7 +755,9 @@ func TestJailWhisper(t *testing.T) {
}
accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey))
whisperService.AddKeyPair(accountKey2.PrivateKey)
if _, err := whisperService.AddKeyPair(accountKey2.PrivateKey); err != nil {
t.Fatalf("identity not injected: %v", accountKey2Hex)
}
if ok, err := whisperAPI.HasKeyPair(accountKey2Hex); err != nil || !ok {
t.Fatalf("identity not injected: %v", accountKey2Hex)
}

View File

@ -31,15 +31,18 @@ import (
)
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")
ErrSshServiceRegistrationFailure = errors.New("failed to register the Whisper service")
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
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")
)
// Node represents running node (serves as a wrapper around P2P node)
@ -139,7 +142,7 @@ func MakeNode(config *params.NodeConfig) *Node {
// start Whisper service
if err := activateShhService(stack, config); err != nil {
Fatalf(fmt.Errorf("%v: %v", ErrSshServiceRegistrationFailure, err))
Fatalf(fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err))
}
return &Node{
@ -168,7 +171,7 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
ethConf := eth.DefaultConfig
ethConf.Genesis = genesis
ethConf.SyncMode = downloader.LightSync
ethConf.NetworkId = config.NetworkId
ethConf.NetworkId = config.NetworkID
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
ethConf.MaxPeers = config.MaxPeers
ethConf.DatabaseHandles = makeDatabaseHandles()
@ -213,11 +216,8 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) error {
return whisperService, nil
}
if err := stack.Register(serviceConstructor); err != nil {
return err
}
return nil
return stack.Register(serviceConstructor)
}
// makeIPCPath returns IPC-RPC filename
@ -297,6 +297,9 @@ 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)

View File

@ -44,6 +44,7 @@ type NodeServiceStack struct {
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")
@ -162,7 +163,7 @@ func (m *NodeManager) StartNode() {
defer signal.Stop(sigc)
<-sigc
log.Info("Got interrupt, shutting down...")
go m.node.geth.Stop()
go m.node.geth.Stop() // nolint: errcheck
for i := 3; i > 0; i-- {
<-sigc
if i > 1 {
@ -192,7 +193,9 @@ func (m *NodeManager) RestartNode() error {
return ErrInvalidGethNode
}
m.StopNode()
if err := m.StopNode(); err != nil {
return err
}
m.RunNode()
m.WaitNodeStarted()
@ -208,12 +211,7 @@ func (m *NodeManager) ResumeNode() error {
m.RunNode()
m.WaitNodeStarted()
// re-select the previously selected account
if err := ReSelectAccount(); err != nil {
return err
}
return nil
return ReSelectAccount()
}
// ResetChainData purges chain data (by removing data directory). Safe to apply on running P2P node.
@ -235,13 +233,10 @@ func (m *NodeManager) ResetChainData() error {
}
log.Info("chaindata removed", "dir", chainDataDir)
if err := m.ResumeNode(); err != nil {
return err
}
return nil
return m.ResumeNode()
}
// StartNodeRPCServer starts HTTP RPC server
func (m *NodeManager) StartNodeRPCServer() (bool, error) {
if m == nil || !m.NodeInited() {
return false, ErrInvalidGethNode
@ -258,7 +253,7 @@ func (m *NodeManager) StartNodeRPCServer() (bool, error) {
return m.api.StartRPC(&config.HTTPHost, &config.HTTPPort, &cors, &modules)
}
// StopNodeRPCServer stops HTTP RPC service attached to node
// StopNodeRPCServer stops HTTP RPC server attached to node
func (m *NodeManager) StopNodeRPCServer() (bool, error) {
if m == nil || !m.NodeInited() {
return false, ErrInvalidGethNode
@ -323,6 +318,7 @@ func (m *NodeManager) AccountKeyStore() (*keystore.KeyStore, error) {
}
// LightEthereumService exposes LES
// nolint: dupl
func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
@ -336,6 +332,7 @@ func (m *NodeManager) LightEthereumService() (*les.LightEthereum, error) {
}
// WhisperService exposes Whisper service
// nolint: dupl
func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
@ -349,6 +346,7 @@ func (m *NodeManager) WhisperService() (*whisper.Whisper, error) {
}
// RPCClient exposes Geth's RPC client
// nolint: dupl
func (m *NodeManager) RPCClient() (*rpc.Client, error) {
if m == nil || !m.NodeInited() {
return nil, ErrInvalidGethNode
@ -416,10 +414,11 @@ func (m *NodeManager) onNodeStarted() {
// PopulateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster
func (m *NodeManager) PopulateStaticPeers() {
for _, enode := range params.TestnetBootnodes {
m.AddPeer(enode)
m.AddPeer(enode) // nolint: errcheck
}
}
// Hex dumps address of a given extended key as hex string
func (k *SelectedExtKey) Hex() string {
if k == nil {
return "0x0"

View File

@ -28,9 +28,10 @@ func init() {
}
}
// errors
var (
ErrMissingDataDir = errors.New("missing required 'DataDir' parameter")
ErrMissingNetworkId = errors.New("missing required 'NetworkId' parameter")
ErrMissingNetworkID = errors.New("missing required 'NetworkID' parameter")
ErrEmptyPasswordFile = errors.New("password file cannot be empty")
ErrEmptyIdentityFile = errors.New("identity file cannot be empty")
ErrEmptyAuthorizationKeyFile = errors.New("authorization key file cannot be empty")
@ -49,6 +50,7 @@ type LightEthConfig struct {
DatabaseCache int
}
// FirebaseConfig holds FCM-related configuration
type FirebaseConfig struct {
// AuthorizationKeyFile file path that contains FCM authorization key
AuthorizationKeyFile string
@ -111,8 +113,8 @@ type NodeConfig struct {
// TestNet flag whether given configuration describes a test or mainnet
TestNet bool
// NetworkId sets network to use for selecting peers to connect to
NetworkId uint64
// NetworkID sets network to use for selecting peers to connect to
NetworkID uint64 `json:"NetworkId,"`
// DataDir is the file system folder the node should use for any data storage needs.
DataDir string
@ -192,9 +194,9 @@ type NodeConfig struct {
}
// NewNodeConfig creates new node configuration object
func NewNodeConfig(dataDir string, networkId uint64) (*NodeConfig, error) {
func NewNodeConfig(dataDir string, networkID uint64) (*NodeConfig, error) {
nodeConfig := &NodeConfig{
NetworkId: networkId,
NetworkID: networkID,
DataDir: dataDir,
Name: ClientIdentifier,
Version: Version,
@ -259,7 +261,7 @@ func (c *NodeConfig) populateDirs() error {
// populateChainConfig does necessary adjustments to config object (depending on network node will be runnin on)
func (c *NodeConfig) populateGenesis() error {
c.TestNet = false
if c.NetworkId == TestNetworkId {
if c.NetworkID == TestNetworkID {
c.TestNet = true
}
@ -307,8 +309,8 @@ func LoadNodeConfig(configJSON string) (*NodeConfig, error) {
return nil, ErrMissingDataDir
}
if nodeConfig.NetworkId <= 0 {
return nil, ErrMissingNetworkId
if nodeConfig.NetworkID <= 0 {
return nil, ErrMissingNetworkID
}
return nodeConfig, nil

View File

@ -56,8 +56,8 @@ var loadConfigTestCases = []struct {
"DataDir": "$TMPDIR"
}`,
func(t *testing.T, dataDir string, nodeConfig *params.NodeConfig, err error) {
if err != params.ErrMissingNetworkId {
t.Fatalf("expected error not thrown, expected: %v, thrown: %v", params.ErrMissingNetworkId, err)
if err != params.ErrMissingNetworkID {
t.Fatalf("expected error not thrown, expected: %v, thrown: %v", params.ErrMissingNetworkID, err)
}
},
},
@ -142,7 +142,7 @@ var loadConfigTestCases = []struct {
t.Fatalf("unexpected error: %v", err)
}
if nodeConfig.NetworkId != 3 {
if nodeConfig.NetworkID != 3 {
t.Fatal("wrong NetworkId")
}
@ -162,11 +162,11 @@ var loadConfigTestCases = []struct {
t.Fatal("wrong WSPort")
}
if nodeConfig.WSEnabled != false {
if nodeConfig.WSEnabled {
t.Fatal("wrong WSEnabled")
}
if nodeConfig.IPCEnabled != true {
if !nodeConfig.IPCEnabled {
t.Fatal("wrong IPCEnabled")
}
if nodeConfig.LightEthConfig.DatabaseCache != 64 {
@ -254,7 +254,7 @@ var loadConfigTestCases = []struct {
if chainConfig.DAOForkBlock.Cmp(gethparams.MainNetDAOForkBlock) != 0 {
t.Fatal("invalid chainConfig.DAOForkBlock")
}
if chainConfig.DAOForkSupport != true {
if !chainConfig.DAOForkSupport {
t.Fatal("invalid chainConfig.DAOForkSupport")
}
if chainConfig.EIP150Block.Cmp(gethparams.MainNetHomesteadGasRepriceBlock) != 0 {
@ -292,8 +292,8 @@ var loadConfigTestCases = []struct {
}
networkId := uint64(311)
if nodeConfig.NetworkId != networkId {
t.Fatalf("unexpected NetworkId, expected: %v, got: %v", networkId, nodeConfig.NetworkId)
if nodeConfig.NetworkID != networkId {
t.Fatalf("unexpected NetworkID, expected: %v, got: %v", networkId, nodeConfig.NetworkID)
}
},
},
@ -304,7 +304,7 @@ func TestLoadNodeConfig(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
defer os.RemoveAll(tmpDir) // nolint: errcheck
for _, testCase := range loadConfigTestCases {
t.Log("test: " + testCase.name)
@ -320,7 +320,7 @@ func TestConfigWriteRead(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
defer os.RemoveAll(tmpDir) // nolint: errcheck
nodeConfig, err := params.NewNodeConfig(tmpDir, networkId)
if err != nil {
@ -345,6 +345,6 @@ func TestConfigWriteRead(t *testing.T) {
}
}
configReadWrite(params.TestNetworkId, "testdata/config.testnet.json")
configReadWrite(params.MainNetworkId, "testdata/config.mainnet.json")
configReadWrite(params.TestNetworkID, "testdata/config.testnet.json")
configReadWrite(params.MainNetworkID, "testdata/config.mainnet.json")
}

View File

@ -69,9 +69,9 @@ const (
// FirebaseNotificationTriggerURL is URL where FCM notification requests are sent to
FirebaseNotificationTriggerURL = "https://fcm.googleapis.com/fcm/send"
// MainNetworkId is id of the main network
MainNetworkId = 1
// MainNetworkID is id of the main network
MainNetworkID = 1
// TestNetworkId is id of a test network
TestNetworkId = 3
// TestNetworkID is id of a test network
TestNetworkID = 3
)

View File

@ -18,7 +18,7 @@ func TestLogger(t *testing.T) {
}
//defer os.RemoveAll(tmpDir)
nodeConfig, err := params.NewNodeConfig(tmpDir, params.TestNetworkId)
nodeConfig, err := params.NewNodeConfig(tmpDir, params.TestNetworkID)
if err != nil {
t.Fatal("cannot create config object")
}
@ -74,7 +74,9 @@ func TestLogger(t *testing.T) {
validateLogText(`msg="logged INFO log level message"`) // debug level message is NOT logged
// stop logger and see if os.Stderr and gethlog continue functioning
nodeLogger.Stop()
if err = nodeLogger.Stop(); err != nil {
t.Fatal(err)
}
log.Info("logging message: this message happens after custom logger has been stopped")
}

View File

@ -5,10 +5,17 @@ import (
)
const (
VersionMajor = 0 // Major version component of the current release
VersionMinor = 9 // Minor version component of the current release
VersionPatch = 7 // Patch version component of the current release
VersionMeta = "unstable" // Version metadata to append to the version string
// VersionMajor is a major version component of the current release
VersionMajor = 0
// VersionMinor is a minor version component of the current release
VersionMinor = 9
// VersionPatch is a patch version component of the current release
VersionPatch = 7
// VersionMeta is metadata to append to the version string
VersionMeta = "unstable"
)
// Version exposes string representation of program version.

View File

@ -12,12 +12,24 @@ import (
)
const (
// EventTransactionQueued is triggered whan send transaction request is queued
EventTransactionQueued = "transaction.queued"
EventTransactionFailed = "transaction.failed"
SendTransactionRequest = "eth_sendTransaction"
MessageIdKey = "message_id"
// tx error codes
// 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"
@ -29,9 +41,9 @@ func onSendTransactionRequest(queuedTx status.QueuedTx) {
SendSignal(SignalEnvelope{
Type: EventTransactionQueued,
Event: SendTransactionEvent{
Id: string(queuedTx.Id),
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageId: messageIdFromContext(queuedTx.Context),
MessageID: messageIDFromContext(queuedTx.Context),
},
})
}
@ -50,9 +62,9 @@ func onSendTransactionReturn(queuedTx *status.QueuedTx, err error) {
SendSignal(SignalEnvelope{
Type: EventTransactionFailed,
Event: ReturnSendTransactionEvent{
Id: string(queuedTx.Id),
ID: string(queuedTx.ID),
Args: queuedTx.Args,
MessageId: messageIdFromContext(queuedTx.Context),
MessageID: messageIDFromContext(queuedTx.Context),
ErrorMessage: err.Error(),
ErrorCode: sendTransactionErrorCode(err),
},
@ -76,6 +88,7 @@ func sendTransactionErrorCode(err error) string {
}
}
// 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 {
@ -87,13 +100,14 @@ func CompleteTransaction(id, password string) (common.Hash, error) {
ctx := context.Background()
ctx = context.WithValue(ctx, status.SelectedAccountKey, NodeManagerInstance().SelectedAccount.Hex())
return backend.CompleteQueuedTransaction(ctx, status.QueuedTxId(id), password)
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)
parsedIDs, err := parseJSONArray(ids)
if err != nil {
results["none"] = RawCompleteTransactionResult{
Error: err,
@ -101,9 +115,9 @@ func CompleteTransactions(ids, password string) map[string]RawCompleteTransactio
return results
}
for _, txId := range parsedIds {
txHash, txErr := CompleteTransaction(txId, password)
results[txId] = RawCompleteTransactionResult{
for _, txID := range parsedIDs {
txHash, txErr := CompleteTransaction(txID, password)
results[txID] = RawCompleteTransactionResult{
Hash: txHash,
Error: txErr,
}
@ -112,6 +126,7 @@ func CompleteTransactions(ids, password string) map[string]RawCompleteTransactio
return results
}
// DiscardTransaction discards a given transaction from transaction queue
func DiscardTransaction(id string) error {
lightEthereum, err := NodeManagerInstance().LightEthereumService()
if err != nil {
@ -120,14 +135,15 @@ func DiscardTransaction(id string) error {
backend := lightEthereum.StatusBackend
return backend.DiscardQueuedTransaction(status.QueuedTxId(id))
return backend.DiscardQueuedTransaction(status.QueuedTxID(id))
}
// DiscardTransactions discards given multiple transactions from transaction queue
func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
var parsedIds []string
var parsedIDs []string
results := make(map[string]RawDiscardTransactionResult)
parsedIds, err := parseJSONArray(ids)
parsedIDs, err := parseJSONArray(ids)
if err != nil {
results["none"] = RawDiscardTransactionResult{
Error: err,
@ -135,10 +151,10 @@ func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
return results
}
for _, txId := range parsedIds {
err := DiscardTransaction(txId)
for _, txID := range parsedIDs {
err := DiscardTransaction(txID)
if err != nil {
results[txId] = RawDiscardTransactionResult{
results[txID] = RawDiscardTransactionResult{
Error: err,
}
}
@ -147,40 +163,50 @@ func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
return results
}
func messageIdFromContext(ctx context.Context) string {
func messageIDFromContext(ctx context.Context) string {
if ctx == nil {
return ""
}
if messageId, ok := ctx.Value(MessageIdKey).(string); ok {
return messageId
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())
messageID := currentMessageID(vm.Context())
return messageId, nil
return messageID, nil
}
func (q *JailedRequestQueue) PostProcessRequest(vm *otto.Otto, req RPCCall, messageId string) {
if len(messageId) > 0 {
vm.Call("addContext", nil, messageId, MessageIdKey, messageId)
// 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)
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()
@ -189,13 +215,13 @@ func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RP
}
backend := lightEthereum.StatusBackend
messageId, err := q.PreProcessRequest(vm, req)
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)
ctx = context.WithValue(ctx, MessageIDKey, messageID)
// this call blocks, up until Complete Transaction is called
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
@ -204,20 +230,20 @@ func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RP
}
// invoke post processing
q.PostProcessRequest(vm, req, messageId)
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 {
// 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")
messageID, err := statusObj.Object().Get("message_id")
if err != nil {
return ""
}
if messageId, err := messageId.ToString(); err == nil {
return messageId
if messageID, err := messageID.ToString(); err == nil {
return messageID
}
}
@ -287,6 +313,7 @@ func (r RPCCall) parseData() hexutil.Bytes {
return byteCode
}
// nolint: dupl
func (r RPCCall) parseValue() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
@ -307,6 +334,7 @@ func (r RPCCall) parseValue() *hexutil.Big {
return (*hexutil.Big)(parsedValue)
}
// nolint: dupl
func (r RPCCall) parseGas() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {
@ -326,6 +354,7 @@ func (r RPCCall) parseGas() *hexutil.Big {
return (*hexutil.Big)(parsedValue)
}
// nolint: dupl
func (r RPCCall) parseGasPrice() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{})
if !ok {

View File

@ -37,7 +37,9 @@ func TestQueuedContracts(t *testing.T) {
return
}
geth.Logout()
if err = geth.Logout(); err != nil {
t.Fatal(err)
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
@ -45,7 +47,7 @@ func TestQueuedContracts(t *testing.T) {
// replace transaction notification handler
var txHash = common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
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)
@ -57,7 +59,7 @@ func TestQueuedContracts(t *testing.T) {
// 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 transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
return
}
@ -67,7 +69,7 @@ func TestQueuedContracts(t *testing.T) {
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
t.Errorf("expected error on queued transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
return
}
@ -77,7 +79,7 @@ func TestQueuedContracts(t *testing.T) {
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
return
}
@ -143,7 +145,9 @@ func TestQueuedTransactions(t *testing.T) {
return
}
geth.Logout()
if err = geth.Logout(); err != nil {
t.Fatal(err)
}
// make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1)
@ -151,7 +155,7 @@ func TestQueuedTransactions(t *testing.T) {
// replace transaction notification handler
var txHash = common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
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)
@ -163,7 +167,7 @@ func TestQueuedTransactions(t *testing.T) {
// 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 transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
return
}
@ -173,7 +177,7 @@ func TestQueuedTransactions(t *testing.T) {
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender {
t.Errorf("expected error on queued transation[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
t.Errorf("expected error on queued transaction[%v] not thrown: expected %v, got %v", event["id"], status.ErrInvalidCompleteTxSender, err)
return
}
@ -183,7 +187,7 @@ func TestQueuedTransactions(t *testing.T) {
return
}
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil {
t.Errorf("cannot complete queued transation[%v]: %v", event["id"], err)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
return
}
@ -246,7 +250,7 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
// replace transaction notification handler
var txId string
var txID string
txFailedEventCalled := false
txHash := common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
@ -257,12 +261,12 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
}
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)
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 {
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
}
@ -274,7 +278,7 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
// 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 transation[%v]: %v", event["id"], err)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
return
}
@ -370,7 +374,7 @@ func TestDiscardQueuedTransactions(t *testing.T) {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
// replace transaction notification handler
var txId string
var txID string
txFailedEventCalled := false
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope geth.SignalEnvelope
@ -380,31 +384,31 @@ func TestDiscardQueuedTransactions(t *testing.T) {
}
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)
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)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
// discard
err := geth.DiscardTransaction(txId)
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)
_, 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)
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
}
@ -485,12 +489,12 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
txIDs := make(chan string, testTxCount)
allTestTxCompleted := make(chan struct{}, 1)
// replace transaction notification handler
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txId 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)
@ -498,10 +502,10 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
}
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)
txID = event["id"].(string)
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txID)
txIds <- txId
txIDs <- txID
}
})
@ -524,26 +528,29 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
}
// wait for transactions, and complete them in a single call
completeTxs := func(txIdStrings string) {
var parsedIds []string
json.Unmarshal([]byte(txIdStrings), &parsedIds)
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)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// complete
results := geth.CompleteTransactions(string(updatedTxIdStrings), testConfig.Account1.Password)
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)
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)
if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id" {
t.Errorf("invalid hash (expected non empty hash): %s", txID)
return
}
@ -553,21 +560,21 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
}
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)
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
var txIDStrings []string
for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds)
txIDStrings = append(txIDStrings, <-txIDs)
}
txIdJSON, _ := json.Marshal(txIdStrings)
completeTxs(string(txIdJSON))
txIDJSON, _ := json.Marshal(txIDStrings)
completeTxs(string(txIDJSON))
allTestTxCompleted <- struct{}{}
}()
@ -616,13 +623,13 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
// make sure you panic if transaction complete doesn't return
testTxCount := 3
txIds := make(chan string, testTxCount)
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 txID string
var envelope geth.SignalEnvelope
if err := json.Unmarshal([]byte(jsonEvent), &envelope); err != nil {
t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
@ -630,15 +637,15 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
}
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)
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)
if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txID)
return
}
txIds <- txId
txIDs <- txID
}
if envelope.Type == geth.EventTransactionFailed {
@ -684,22 +691,25 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
}
// wait for transactions, and discard immediately
discardTxs := func(txIdStrings string) {
var parsedIds []string
json.Unmarshal([]byte(txIdStrings), &parsedIds)
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)
parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// discard
discardResults := geth.DiscardTransactions(string(updatedTxIdStrings))
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)
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)")
}
@ -715,21 +725,21 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
}
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)
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
var txIDStrings []string
for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds)
txIDStrings = append(txIDStrings, <-txIDs)
}
txIdJSON, _ := json.Marshal(txIdStrings)
discardTxs(string(txIdJSON))
txIDJSON, _ := json.Marshal(txIDStrings)
discardTxs(string(txIDJSON))
}()
// send multiple transactions
@ -769,35 +779,15 @@ func TestNonExistentQueuedTransactions(t *testing.T) {
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 transation[%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
}
})
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 recieved")
t.Error("error expected and not received")
return
}
if err != status.ErrQueuedTxIdNotFound {
t.Errorf("unexpected error recieved: expected '%s', got: '%s'", status.ErrQueuedTxIdNotFound.Error(), err.Error())
if err != status.ErrQueuedTxIDNotFound {
t.Errorf("unexpected error received: expected '%s', got: '%s'", status.ErrQueuedTxIDNotFound.Error(), err.Error())
return
}
}
@ -841,7 +831,7 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
// 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 transation[%v]: %v", event["id"], err)
t.Errorf("cannot complete queued transaction[%v]: %v", event["id"], err)
return
}
@ -852,10 +842,10 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
txQueue := backend.TransactionQueue()
var i = 0
txIds := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxId{}
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
t.Logf("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.ID)
txIDs[i] = queuedTx.ID
i++
})
@ -865,7 +855,7 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
}
for i := 0; i < 10; i++ {
go backend.SendTransaction(nil, status.SendTxArgs{})
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
}
time.Sleep(3 * time.Second)
@ -877,7 +867,7 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
}
for i := 0; i < status.DefaultTxQueueCap+5; i++ { // stress test by hitting with lots of goroutines
go backend.SendTransaction(nil, status.SendTxArgs{})
go backend.SendTransaction(nil, status.SendTxArgs{}) // nolint: errcheck
}
time.Sleep(5 * time.Second)
@ -886,8 +876,8 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
return
}
for _, txId := range txIds {
txQueue.Remove(txId)
for _, txID := range txIDs {
txQueue.Remove(txID)
}
if txQueue.Count() != 0 {

View File

@ -5,11 +5,13 @@ import (
"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"`
@ -17,24 +19,23 @@ type AccountInfo struct {
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"`
}
type AddWhisperFilterResult struct {
Id int `json:"id"`
Error string `json:"error"`
}
// WhisperMessageEvent is a signal sent on incoming Whisper message
type WhisperMessageEvent struct {
Payload string `json:"payload"`
To string `json:"to"`
@ -44,55 +45,65 @@ type WhisperMessageEvent struct {
Hash string `json:"hash"`
}
// SendTransactionEvent is a signal sent on a send transaction request
type SendTransactionEvent struct {
Id string `json:"id"`
ID string `json:"id"`
Args status.SendTxArgs `json:"args"`
MessageId string `json:"message_id"`
MessageID string `json:"message_id"`
}
// ReturnSendTransactionEvent is a JSON returned whenever transaction send is returned
type ReturnSendTransactionEvent struct {
Id string `json:"id"`
ID string `json:"id"`
Args status.SendTxArgs `json:"args"`
MessageId string `json:"message_id"`
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"`
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"`
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"`
ChatID string `json:"chat_id"`
Data string `json:"data"`
}
// RPCCall represents RPC call parameters
type RPCCall struct {
Id int64
ID int64
Method string
Params []interface{}
}

View File

@ -15,6 +15,7 @@ import (
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts"
@ -27,8 +28,12 @@ import (
var (
muPrepareTestNode sync.Mutex
RootDir string
TestDataDir string
// RootDir is the main application directory
RootDir string
// TestDataDir is data directory used for tests
TestDataDir string
)
func init() {
@ -47,6 +52,8 @@ func init() {
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
@ -68,12 +75,12 @@ func SendSignal(signal SignalEnvelope) {
}
//export NotifyNode
func NotifyNode(jsonEvent *C.char) {
func NotifyNode(jsonEvent *C.char) { // nolint: golint
notificationHandler(C.GoString(jsonEvent))
}
//export TriggerTestSignal
func TriggerTestSignal() {
func TriggerTestSignal() { // nolint: golint
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
}
@ -106,27 +113,8 @@ func LoadTestConfig() (*TestConfig, error) {
return &testConfig, nil
}
func CopyFile(dst, src string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dst)
if err != nil {
return err
}
defer d.Close()
if _, err := io.Copy(d, s); err != nil {
return err
}
return nil
}
// LoadFromFile is usefull for loading test data, from testdata/filename into a variable
// 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 {
@ -140,6 +128,7 @@ func LoadFromFile(filename string) string {
return string(buf.Bytes())
}
// PrepareTestNode initializes node manager and start a test node (only once!)
func PrepareTestNode() (err error) {
muPrepareTestNode.Lock()
defer muPrepareTestNode.Unlock()
@ -157,26 +146,26 @@ func PrepareTestNode() (err error) {
}
syncRequired := false
if _, err := os.Stat(TestDataDir); os.IsNotExist(err) {
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 {
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 {
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 {
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.TestNetworkId)
config, err := params.NewNodeConfig(filepath.Join(TestDataDir, "data"), params.TestNetworkID)
if err != nil {
return err
}
@ -212,11 +201,42 @@ func PrepareTestNode() (err error) {
return nil
}
func RemoveTestNode() {
err := os.RemoveAll(TestDataDir)
// 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 {
log.Warn("could not clean up temporary datadir")
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
@ -231,6 +251,8 @@ func PanicAfter(waitSeconds time.Duration, abort chan struct{}, desc string) {
}()
}
// 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 {
@ -240,6 +262,8 @@ func FromAddress(accountAddress string) 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 {
@ -280,7 +304,7 @@ func AddressToDecryptedAccount(address, password string) (accounts.Account, *key
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)
os.MkdirAll(keystoreDir, os.ModePerm) // nolint: errcheck
}
dst := filepath.Join(keystoreDir, accountFile)

View File

@ -1,23 +1 @@
package geth
import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
)
func onWhisperMessage(message *whisper.Message) {
SendSignal(SignalEnvelope{
Type: "whisper",
Event: WhisperMessageEvent{
Payload: string(message.Payload),
From: common.ToHex(crypto.FromECDSAPub(message.Recover())),
To: common.ToHex(crypto.FromECDSAPub(message.To)),
Sent: message.Sent.Unix(),
TTL: int64(message.TTL / time.Second),
Hash: common.ToHex(message.Hash.Bytes()),
},
})
}

View File

@ -30,7 +30,9 @@ func TestWhisperFilterRace(t *testing.T) {
}
accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey))
whisperService.AddKeyPair(accountKey1.PrivateKey)
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)
}
@ -42,7 +44,9 @@ func TestWhisperFilterRace(t *testing.T) {
}
accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey))
whisperService.AddKeyPair(accountKey2.PrivateKey)
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)
}
@ -65,6 +69,7 @@ func TestWhisperFilterRace(t *testing.T) {
for i := 0; i < 10; i++ {
go func() {
// nolint: errcheck
whisperAPI.Subscribe(whisper.WhisperFilterArgs{
Sig: accountKey1Hex,
Key: accountKey2Hex,

View File

@ -0,0 +1,23 @@
comment on exported function CreateAccount should be of the form "CreateAccount ..." (golint)
comment on exported function CreateChildAccount should be of the form "CreateChildAccount ..." (golint)
comment on exported function RecoverAccount should be of the form "RecoverAccount ..." (golint)
comment on exported function Login should be of the form "Login ..." (golint)
comment on exported function Logout should be of the form "Logout ..." (golint)
comment on exported function CompleteTransaction should be of the form "CompleteTransaction ..." (golint)
comment on exported function CompleteTransactions should be of the form "CompleteTransactions ..." (golint)
comment on exported function DiscardTransaction should be of the form "DiscardTransaction ..." (golint)
comment on exported function DiscardTransactions should be of the form "DiscardTransactions ..." (golint)
comment on exported function GenerateConfig should be of the form "GenerateConfig ..." (golint)
comment on exported function StartNode should be of the form "StartNode ..." (golint)
comment on exported function StopNode should be of the form "StopNode ..." (golint)
comment on exported function ResumeNode should be of the form "ResumeNode ..." (golint)
comment on exported function ResetChainData should be of the form "ResetChainData ..." (golint)
comment on exported function StopNodeRPCServer should be of the form "StopNodeRPCServer ..." (golint)
comment on exported function StartNodeRPCServer should be of the form "StartNodeRPCServer ..." (golint)
comment on exported function InitJail should be of the form "InitJail ..." (golint)
comment on exported function Parse should be of the form "Parse ..." (golint)
comment on exported function Call should be of the form "Call ..." (golint)
comment on exported function PopulateStaticPeers should be of the form "PopulateStaticPeers ..." (golint)
comment on exported function AddPeer should be of the form "AddPeer ..." (golint)
comment on exported function NotifyNode should be of the form "NotifyNode ..." (golint)
comment on exported function TriggerTestSignal should be of the form "TriggerTestSignal ..." (golint)

View File

@ -99,7 +99,7 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
}
queuedTx := &status.QueuedTx{
Id: status.QueuedTxId(uuid.New()),
ID: status.QueuedTxID(uuid.New()),
Hash: common.Hash{},
Context: ctx,
Args: status.SendTxArgs(args),
@ -130,7 +130,7 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
}
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction
func (b *StatusBackend) CompleteQueuedTransaction(ctx context.Context, id status.QueuedTxId, passphrase string) (common.Hash, error) {
func (b *StatusBackend) CompleteQueuedTransaction(ctx context.Context, id status.QueuedTxID, passphrase string) (common.Hash, error) {
queuedTx, err := b.txQueue.Get(id)
if err != nil {
return common.Hash{}, err
@ -159,14 +159,14 @@ func (b *StatusBackend) CompleteQueuedTransaction(ctx context.Context, id status
}
// DiscardQueuedTransaction discards queued transaction forcing SendTransaction to return
func (b *StatusBackend) DiscardQueuedTransaction(id status.QueuedTxId) error {
func (b *StatusBackend) DiscardQueuedTransaction(id status.QueuedTxID) error {
queuedTx, err := b.txQueue.Get(id)
if err != nil {
return err
}
// remove from queue, before notifying SendTransaction
b.TransactionQueue().Remove(queuedTx.Id)
b.TransactionQueue().Remove(queuedTx.ID)
// allow SendTransaction to return
queuedTx.Err = status.ErrQueuedTxDiscarded

View File

@ -19,7 +19,7 @@ const (
)
var (
ErrQueuedTxIdNotFound = errors.New("transaction hash not found")
ErrQueuedTxIDNotFound = errors.New("transaction hash not found")
ErrQueuedTxTimedOut = errors.New("transaction sending timed out")
ErrQueuedTxDiscarded = errors.New("transaction has been discarded")
ErrInvalidCompleteTxSender = errors.New("transaction can only be completed by the same account which created it")
@ -27,9 +27,9 @@ var (
// TxQueue is capped container that holds pending transactions
type TxQueue struct {
transactions map[QueuedTxId]*QueuedTx
transactions map[QueuedTxID]*QueuedTx
mu sync.RWMutex // to guard transactions map
evictableIds chan QueuedTxId
evictableIDs chan QueuedTxID
enqueueTicker chan struct{}
incomingPool chan *QueuedTx
@ -46,7 +46,7 @@ type TxQueue struct {
// QueuedTx holds enough information to complete the queued transaction.
type QueuedTx struct {
Id QueuedTxId
ID QueuedTxID
Hash common.Hash
Context context.Context
Args SendTxArgs
@ -55,8 +55,8 @@ type QueuedTx struct {
Err error
}
// QueuedTxId queued transaction identifier
type QueuedTxId string
// QueuedTxID queued transaction identifier
type QueuedTxID string
// EnqueuedTxHandler is a function that receives queued/pending transactions, when they get queued
type EnqueuedTxHandler func(QueuedTx)
@ -79,8 +79,8 @@ type SendTxArgs struct {
func NewTransactionQueue() *TxQueue {
log.Info("StatusIM: initializing transaction queue")
return &TxQueue{
transactions: make(map[QueuedTxId]*QueuedTx),
evictableIds: make(chan QueuedTxId, DefaultTxQueueCap), // will be used to evict in FIFO
transactions: make(map[QueuedTxID]*QueuedTx),
evictableIDs: make(chan QueuedTxID, DefaultTxQueueCap), // will be used to evict in FIFO
enqueueTicker: make(chan struct{}),
incomingPool: make(chan *QueuedTx, DefaultTxSendQueueCap),
}
@ -110,7 +110,7 @@ func (q *TxQueue) evictionLoop() {
select {
case <-q.enqueueTicker:
if len(q.transactions) >= (DefaultTxQueueCap - 1) { // eviction is required to accommodate another/last item
q.Remove(<-q.evictableIds)
q.Remove(<-q.evictableIDs)
q.enqueueTicker <- struct{}{} // in case we pulled already removed item
}
case <-q.stopped:
@ -127,7 +127,7 @@ func (q *TxQueue) enqueueLoop() {
for {
select {
case queuedTx := <-q.incomingPool:
log.Info("StatusIM: transaction enqueued", "tx", queuedTx.Id)
log.Info("StatusIM: transaction enqueued", "tx", queuedTx.ID)
q.Enqueue(queuedTx)
case <-q.stopped:
log.Info("StatusIM: transaction queue's enqueue loop stopped")
@ -142,8 +142,8 @@ func (q *TxQueue) Reset() {
q.mu.Lock()
defer q.mu.Unlock()
q.transactions = make(map[QueuedTxId]*QueuedTx)
q.evictableIds = make(chan QueuedTxId, DefaultTxQueueCap)
q.transactions = make(map[QueuedTxID]*QueuedTx)
q.evictableIDs = make(chan QueuedTxID, DefaultTxQueueCap)
}
// EnqueueAsync enqueues incoming transaction in async manner, returns as soon as possible
@ -160,10 +160,10 @@ func (q *TxQueue) Enqueue(tx *QueuedTx) error {
}
q.enqueueTicker <- struct{}{} // notify eviction loop that we are trying to insert new item
q.evictableIds <- tx.Id // this will block when we hit DefaultTxQueueCap
q.evictableIDs <- tx.ID // this will block when we hit DefaultTxQueueCap
q.mu.Lock()
q.transactions[tx.Id] = tx
q.transactions[tx.ID] = tx
q.mu.Unlock()
// notify handler
@ -173,7 +173,7 @@ func (q *TxQueue) Enqueue(tx *QueuedTx) error {
}
// Get returns transaction by transaction identifier
func (q *TxQueue) Get(id QueuedTxId) (*QueuedTx, error) {
func (q *TxQueue) Get(id QueuedTxID) (*QueuedTx, error) {
q.mu.RLock()
defer q.mu.RUnlock()
@ -181,11 +181,11 @@ func (q *TxQueue) Get(id QueuedTxId) (*QueuedTx, error) {
return tx, nil
}
return nil, ErrQueuedTxIdNotFound
return nil, ErrQueuedTxIDNotFound
}
// Remove removes transaction by transaction identifier
func (q *TxQueue) Remove(id QueuedTxId) {
func (q *TxQueue) Remove(id QueuedTxID) {
q.mu.Lock()
defer q.mu.Unlock()
@ -201,7 +201,7 @@ func (q *TxQueue) Count() int {
}
// Has checks whether transaction with a given identifier exists in queue
func (q *TxQueue) Has(id QueuedTxId) bool {
func (q *TxQueue) Has(id QueuedTxID) bool {
q.mu.RLock()
defer q.mu.RUnlock()
@ -234,7 +234,7 @@ func (q *TxQueue) NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error) {
// on success, remove item from the queue and stop propagating
if err == nil {
q.Remove(queuedTx.Id)
q.Remove(queuedTx.ID)
return
}
@ -249,7 +249,7 @@ func (q *TxQueue) NotifyOnQueuedTxReturn(queuedTx *QueuedTx, err error) {
ErrInvalidCompleteTxSender: true, // completing tx create from another account
}
if !transientErrs[err] { // remove only on unrecoverable errors
q.Remove(queuedTx.Id)
q.Remove(queuedTx.ID)
}
// notify handler