diff --git a/Makefile b/Makefile index 0c46d0624..b4c823033 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/cmd/statusd/library.go b/cmd/statusd/library.go index ea6c68a1b..76b007e35 100644 --- a/cmd/statusd/library.go +++ b/cmd/statusd/library.go @@ -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) } diff --git a/cmd/statusd/main.go b/cmd/statusd/main.go index 99520d351..8c58c378d 100644 --- a/cmd/statusd/main.go +++ b/cmd/statusd/main.go @@ -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 } diff --git a/cmd/statusd/misccmd.go b/cmd/statusd/misccmd.go index b01d4a151..2ca87275b 100644 --- a/cmd/statusd/misccmd.go +++ b/cmd/statusd/misccmd.go @@ -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")) diff --git a/cmd/statusd/utils.go b/cmd/statusd/utils.go index 8f733c335..f70d4b32e 100644 --- a/cmd/statusd/utils.go +++ b/cmd/statusd/utils.go @@ -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) } }() diff --git a/cmd/statusd/wnodecmd.go b/cmd/statusd/wnodecmd.go index 66dbe5524..8a8862148 100644 --- a/cmd/statusd/wnodecmd.go +++ b/cmd/statusd/wnodecmd.go @@ -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 } diff --git a/extkeys/hdkey.go b/extkeys/hdkey.go index 21edf2deb..f866431af 100644 --- a/extkeys/hdkey.go +++ b/extkeys/hdkey.go @@ -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 { diff --git a/extkeys/hdkey_test.go b/extkeys/hdkey_test.go index 913c04a55..72a6e24f8 100644 --- a/extkeys/hdkey_test.go +++ b/extkeys/hdkey_test.go @@ -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 { diff --git a/extkeys/mnemonic.go b/extkeys/mnemonic.go index d96cf9292..827917a2d 100644 --- a/extkeys/mnemonic.go +++ b/extkeys/mnemonic.go @@ -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) diff --git a/extkeys/utils.go b/extkeys/utils.go index 383c66c57..b9247e023 100644 --- a/extkeys/utils.go +++ b/extkeys/utils.go @@ -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. diff --git a/geth/accounts.go b/geth/accounts.go index c72b55524..1da85ee5d 100644 --- a/geth/accounts.go +++ b/geth/accounts.go @@ -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) { diff --git a/geth/accounts_test.go b/geth/accounts_test.go index 85cffb5fd..874069c08 100644 --- a/geth/accounts_test.go +++ b/geth/accounts_test.go @@ -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 diff --git a/geth/jail/handlers.go b/geth/jail/handlers.go index 137b20745..1019cac7a 100644 --- a/geth/jail/handlers.go +++ b/geth/jail/handlers.go @@ -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, }, }) diff --git a/geth/jail/jail.go b/geth/jail/jail.go index f7fd59ed1..c2fe0c8c8 100644 --- a/geth/jail/jail.go +++ b/geth/jail/jail.go @@ -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() } diff --git a/geth/jail/jail_test.go b/geth/jail/jail_test.go index d595f42ab..dd7ccb262 100644 --- a/geth/jail/jail_test.go +++ b/geth/jail/jail_test.go @@ -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) } diff --git a/geth/node.go b/geth/node.go index 8986d50c5..1eca0948b 100644 --- a/geth/node.go +++ b/geth/node.go @@ -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) diff --git a/geth/node_manager.go b/geth/node_manager.go index a4ee343c0..35131a192 100644 --- a/geth/node_manager.go +++ b/geth/node_manager.go @@ -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" diff --git a/geth/params/config.go b/geth/params/config.go index b492dc3ab..6496920d8 100644 --- a/geth/params/config.go +++ b/geth/params/config.go @@ -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 diff --git a/geth/params/config_test.go b/geth/params/config_test.go index 73938f4eb..3c18f5952 100644 --- a/geth/params/config_test.go +++ b/geth/params/config_test.go @@ -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") } diff --git a/geth/params/defaults.go b/geth/params/defaults.go index 4e05880ca..7057d8e74 100644 --- a/geth/params/defaults.go +++ b/geth/params/defaults.go @@ -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 ) diff --git a/geth/params/logger_test.go b/geth/params/logger_test.go index 69b7423fa..c46ecc0f8 100644 --- a/geth/params/logger_test.go +++ b/geth/params/logger_test.go @@ -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") } diff --git a/geth/params/version.go b/geth/params/version.go index bf0b963d9..07d13a34b 100644 --- a/geth/params/version.go +++ b/geth/params/version.go @@ -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. diff --git a/geth/txqueue.go b/geth/txqueue.go index 164cfba38..2c639e690 100644 --- a/geth/txqueue.go +++ b/geth/txqueue.go @@ -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 { diff --git a/geth/txqueue_test.go b/geth/txqueue_test.go index 9802d1fc5..c17b97ab3 100644 --- a/geth/txqueue_test.go +++ b/geth/txqueue_test.go @@ -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 { diff --git a/geth/types.go b/geth/types.go index 7be22ffc5..2d5b7aa3d 100644 --- a/geth/types.go +++ b/geth/types.go @@ -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{} } diff --git a/geth/utils.go b/geth/utils.go index f89255179..b644f0c92 100644 --- a/geth/utils.go +++ b/geth/utils.go @@ -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) diff --git a/geth/whisper.go b/geth/whisper.go index c00d28e7b..a6803c9e1 100644 --- a/geth/whisper.go +++ b/geth/whisper.go @@ -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()), - }, - }) -} diff --git a/geth/whisper_test.go b/geth/whisper_test.go index 7488d0983..4b8073e23 100644 --- a/geth/whisper_test.go +++ b/geth/whisper_test.go @@ -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, diff --git a/static/config/linter_exclude_list.txt b/static/config/linter_exclude_list.txt new file mode 100644 index 000000000..7bfc49b87 --- /dev/null +++ b/static/config/linter_exclude_list.txt @@ -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) diff --git a/vendor/github.com/ethereum/go-ethereum/internal/ethapi/status_backend.go b/vendor/github.com/ethereum/go-ethereum/internal/ethapi/status_backend.go index 85ab7c975..68d251700 100644 --- a/vendor/github.com/ethereum/go-ethereum/internal/ethapi/status_backend.go +++ b/vendor/github.com/ethereum/go-ethereum/internal/ethapi/status_backend.go @@ -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 diff --git a/vendor/github.com/ethereum/go-ethereum/les/status/txqueue.go b/vendor/github.com/ethereum/go-ethereum/les/status/txqueue.go index 71c590e3e..c53f0e372 100644 --- a/vendor/github.com/ethereum/go-ethereum/les/status/txqueue.go +++ b/vendor/github.com/ethereum/go-ethereum/les/status/txqueue.go @@ -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