refactor: linters + recommendations applied, closes #146

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

View File

@ -57,6 +57,57 @@ generate:
build/env.sh go generate ./static build/env.sh go generate ./static
rm ./static/scripts/web3.js 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: test:
@build/env.sh echo "mode: set" > coverage-all.out @build/env.sh echo "mode: set" > coverage-all.out
build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth build/env.sh go test -coverprofile=coverage.out -covermode=set ./geth

View File

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

View File

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

View File

@ -25,8 +25,11 @@ func version(ctx *cli.Context) error {
if gitCommit != "" { if gitCommit != "" {
fmt.Println("Git Commit:", 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("Go Version:", runtime.Version())
fmt.Println("OS:", runtime.GOOS) fmt.Println("OS:", runtime.GOOS)
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))

View File

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

View File

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

View File

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

View File

@ -164,53 +164,57 @@ tests:
} }
} }
func TestPrivateChildDerivation(t *testing.T) { func TestChildDerivation(t *testing.T) {
type testCase struct {
name string
master string
path []uint32
wantKey string
}
// derive public keys from private keys
getPrivateChildDerivationTests := func() []testCase {
// The private extended keys for test vectors in [BIP32]. // The private extended keys for test vectors in [BIP32].
testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" testVec1MasterPrivKey := "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" testVec2MasterPrivKey := "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
tests := []struct { return []testCase{
name string
master string
path []uint32
wantPriv string
}{
// Test vector 1 // Test vector 1
{ {
name: "test vector 1 chain m", name: "test vector 1 chain m",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{}, path: []uint32{},
wantPriv: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", wantKey: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
}, },
{ {
name: "test vector 1 chain m/0", name: "test vector 1 chain m/0",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{0}, path: []uint32{0},
wantPriv: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R", wantKey: "xprv9uHRZZhbkedL37eZEnyrNsQPFZYRAvjy5rt6M1nbEkLSo378x1CQQLo2xxBvREwiK6kqf7GRNvsNEchwibzXaV6i5GcsgyjBeRguXhKsi4R",
}, },
{ {
name: "test vector 1 chain m/0/1", name: "test vector 1 chain m/0/1",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{0, 1}, path: []uint32{0, 1},
wantPriv: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G", wantKey: "xprv9ww7sMFLzJMzy7bV1qs7nGBxgKYrgcm3HcJvGb4yvNhT9vxXC7eX7WVULzCfxucFEn2TsVvJw25hH9d4mchywguGQCZvRgsiRaTY1HCqN8G",
}, },
{ {
name: "test vector 1 chain m/0/1/2", name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2}, path: []uint32{0, 1, 2},
wantPriv: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi", wantKey: "xprv9xrdP7iD2L1YZCgR9AecDgpDMZSTzP5KCfUykGXgjBxLgp1VFHsEeL3conzGAkbc1MigG1o8YqmfEA2jtkPdf4vwMaGJC2YSDbBTPAjfRUi",
}, },
{ {
name: "test vector 1 chain m/0/1/2/2", name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2}, path: []uint32{0, 1, 2, 2},
wantPriv: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh", wantKey: "xprvA2J8Hq4eiP7xCEBP7gzRJGJnd9CHTkEU6eTNMrZ6YR7H5boik8daFtDZxmJDfdMSKHwroCfAfsBKWWidRfBQjpegy6kzXSkQGGoMdWKz5Xh",
}, },
{ {
name: "test vector 1 chain m/0/1/2/2/1000000000", name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPrivKey, master: testVec1MasterPrivKey,
path: []uint32{0, 1, 2, 2, 1000000000}, path: []uint32{0, 1, 2, 2, 1000000000},
wantPriv: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz", wantKey: "xprvA3XhazxncJqJsQcG85Gg61qwPQKiobAnWjuPpjKhExprZjfse6nErRwTMwGe6uGWXPSykZSTiYb2TXAm7Qhwj8KgRd2XaD21Styu6h6AwFz",
}, },
// Test vector 2 // Test vector 2
@ -218,37 +222,37 @@ func TestPrivateChildDerivation(t *testing.T) {
name: "test vector 2 chain m", name: "test vector 2 chain m",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{}, path: []uint32{},
wantPriv: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", wantKey: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
}, },
{ {
name: "test vector 2 chain m/0", name: "test vector 2 chain m/0",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{0}, path: []uint32{0},
wantPriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", wantKey: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
}, },
{ {
name: "test vector 2 chain m/0/2147483647", name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647}, path: []uint32{0, 2147483647},
wantPriv: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6", wantKey: "xprv9wSp6B7cXJWXZRpDbxkFg3ry2fuSyUfvboJ5Yi6YNw7i1bXmq9QwQ7EwMpeG4cK2pnMqEx1cLYD7cSGSCtruGSXC6ZSVDHugMsZgbuY62m6",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1", name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1}, path: []uint32{0, 2147483647, 1},
wantPriv: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm", wantKey: "xprv9ysS5br6UbWCRCJcggvpUNMyhVWgD7NypY9gsVTMYmuRtZg8izyYC5Ey4T931WgWbfJwRDwfVFqV3b29gqHDbuEpGcbzf16pdomk54NXkSm",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1/2147483646", name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646}, path: []uint32{0, 2147483647, 1, 2147483646},
wantPriv: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp", wantKey: "xprvA2LfeWWwRCxh4iqigcDMnUf2E3nVUFkntc93nmUYBtb9rpSPYWa8MY3x9ZHSLZkg4G84UefrDruVK3FhMLSJsGtBx883iddHNuH1LNpRrEp",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1/2147483646/2", name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPrivKey, master: testVec2MasterPrivKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2}, path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPriv: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam", wantKey: "xprvA48ALo8BDjcRET68R5RsPzF3H7WeyYYtHcyUeLRGBPHXu6CJSGjwW7dWoeUWTEzT7LG3qk6Eg6x2ZoqD8gtyEFZecpAyvchksfLyg3Zbqam",
}, },
// Custom tests to trigger specific conditions. // Custom tests to trigger specific conditions.
@ -257,84 +261,55 @@ func TestPrivateChildDerivation(t *testing.T) {
name: "Derived privkey with zero high byte m/0", name: "Derived privkey with zero high byte m/0",
master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz", master: "xprv9s21ZrQH143K4FR6rNeqEK4EBhRgLjWLWhA3pw8iqgAKk82ypz58PXbrzU19opYcxw8JDJQF4id55PwTsN1Zv8Xt6SKvbr2KNU5y8jN8djz",
path: []uint32{0}, path: []uint32{0},
wantPriv: "xprv9uC5JqtViMmgcAMUxcsBCBFA7oYCNs4bozPbyvLfddjHou4rMiGEHipz94xNaPb1e4f18TRoPXfiXx4C3cDAcADqxCSRSSWLvMBRWPctSN9", wantKey: "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() // derive public keys from other public keys
if privStr != test.wantPriv { getPublicChildDerivationTests := func() []testCase {
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]. // The public extended keys for test vectors in [BIP32].
testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8" testVec1MasterPubKey := "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" testVec2MasterPubKey := "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
tests := []struct { return []testCase{
name string
master string
path []uint32
wantPub string
}{
// Test vector 1 // Test vector 1
{ {
name: "test vector 1 chain m", name: "test vector 1 chain m",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{}, path: []uint32{},
wantPub: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", wantKey: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
}, },
{ {
name: "test vector 1 chain m/0", name: "test vector 1 chain m/0",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{0}, path: []uint32{0},
wantPub: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1", wantKey: "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1",
}, },
{ {
name: "test vector 1 chain m/0/1", name: "test vector 1 chain m/0/1",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{0, 1}, path: []uint32{0, 1},
wantPub: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr", wantKey: "xpub6AvUGrnEpfvJBbfx7sQ89Q8hEMPM65UteqEX4yUbUiES2jHfjexmfJoxCGSwFMZiPBaKQT1RiKWrKfuDV4vpgVs4Xn8PpPTR2i79rwHd4Zr",
}, },
{ {
name: "test vector 1 chain m/0/1/2", name: "test vector 1 chain m/0/1/2",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{0, 1, 2}, path: []uint32{0, 1, 2},
wantPub: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv", wantKey: "xpub6BqyndF6rhZqmgktFCBcapkwubGxPqoAZtQaYewJHXVKZcLdnqBVC8N6f6FSHWUghjuTLeubWyQWfJdk2G3tGgvgj3qngo4vLTnnSjAZckv",
}, },
{ {
name: "test vector 1 chain m/0/1/2/2", name: "test vector 1 chain m/0/1/2/2",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2}, path: []uint32{0, 1, 2, 2},
wantPub: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp", wantKey: "xpub6FHUhLbYYkgFQiFrDiXRfQFXBB2msCxKTsNyAExi6keFxQ8sHfwpogY3p3s1ePSpUqLNYks5T6a3JqpCGszt4kxbyq7tUoFP5c8KWyiDtPp",
}, },
{ {
name: "test vector 1 chain m/0/1/2/2/1000000000", name: "test vector 1 chain m/0/1/2/2/1000000000",
master: testVec1MasterPubKey, master: testVec1MasterPubKey,
path: []uint32{0, 1, 2, 2, 1000000000}, path: []uint32{0, 1, 2, 2, 1000000000},
wantPub: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9", wantKey: "xpub6GX3zWVgSgPc5tgjE6ogT9nfwSADD3tdsxpzd7jJoJMqSY12Be6VQEFwDCp6wAQoZsH2iq5nNocHEaVDxBcobPrkZCjYW3QUmoDYzMFBDu9",
}, },
// Test vector 2 // Test vector 2
@ -342,57 +317,57 @@ func TestPublicDerivation(t *testing.T) {
name: "test vector 2 chain m", name: "test vector 2 chain m",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{}, path: []uint32{},
wantPub: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", wantKey: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
}, },
{ {
name: "test vector 2 chain m/0", name: "test vector 2 chain m/0",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{0}, path: []uint32{0},
wantPub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", wantKey: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
}, },
{ {
name: "test vector 2 chain m/0/2147483647", name: "test vector 2 chain m/0/2147483647",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{0, 2147483647}, path: []uint32{0, 2147483647},
wantPub: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD", wantKey: "xpub6ASAVgeWMg4pmutghzHG3BohahjwNwPmy2DgM6W9wGegtPrvNgjBwuZRD7hSDFhYfunq8vDgwG4ah1gVzZysgp3UsKz7VNjCnSUJJ5T4fdD",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1", name: "test vector 2 chain m/0/2147483647/1",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1}, path: []uint32{0, 2147483647, 1},
wantPub: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b", wantKey: "xpub6CrnV7NzJy4VdgP5niTpqWJiFXMAca6qBm5Hfsry77SQmN1HGYHnjsZSujoHzdxf7ZNK5UVrmDXFPiEW2ecwHGWMFGUxPC9ARipss9rXd4b",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1/2147483646", name: "test vector 2 chain m/0/2147483647/1/2147483646",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646}, path: []uint32{0, 2147483647, 1, 2147483646},
wantPub: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta", wantKey: "xpub6FL2423qFaWzHCvBndkN9cbkn5cysiUeFq4eb9t9kE88jcmY63tNuLNRzpHPdAM4dUpLhZ7aUm2cJ5zF7KYonf4jAPfRqTMTRBNkQL3Tfta",
}, },
{ {
name: "test vector 2 chain m/0/2147483647/1/2147483646/2", name: "test vector 2 chain m/0/2147483647/1/2147483646/2",
master: testVec2MasterPubKey, master: testVec2MasterPubKey,
path: []uint32{0, 2147483647, 1, 2147483646, 2}, path: []uint32{0, 2147483647, 1, 2147483646, 2},
wantPub: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK", wantKey: "xpub6H7WkJf547AiSwAbX6xsm8Bmq9M9P1Gjequ5SipsjipWmtXSyp4C3uwzewedGEgAMsDy4jEvNTWtxLyqqHY9C12gaBmgUdk2CGmwachwnWK",
}, },
} }
}
tests: runTests := func(tests []testCase) {
for i, test := range tests { for i, test := range tests {
extKey, err := extkeys.NewKeyFromString(test.master) extKey, err := extkeys.NewKeyFromString(test.master)
if err != nil { if err != nil {
t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err) t.Errorf("NewKeyFromString #%d (%s): unexpected error creating extended key: %v", i, test.name, err)
continue continue
} }
extKey, err = extKey.Derive(test.path) extKey, err = extKey.Derive(test.path)
if err != nil { if err != nil {
t.Errorf("cannot derive child: %v", err) t.Errorf("cannot derive child: %v", err)
continue tests continue
} }
pubStr := extKey.String() gotKey := extKey.String()
if pubStr != test.wantPub { if gotKey != test.wantKey {
t.Errorf("Child #%d (%s): mismatched serialized public extended key -- got: %s, want: %s", i, test.name, pubStr, test.wantPub) t.Errorf("Child #%d (%s): mismatched serialized extended key -- got: %s, want: %s", i, test.name, gotKey, test.wantKey)
continue continue
} else { } else {
t.Logf("test %d (%s): %s", i, test.name, extKey.String()) t.Logf("test %d (%s): %s", i, test.name, extKey.String())
@ -400,6 +375,10 @@ tests:
} }
} }
runTests(getPrivateChildDerivationTests())
runTests(getPublicChildDerivationTests())
}
func TestErrors(t *testing.T) { func TestErrors(t *testing.T) {
// Should get an error when seed has too few bytes. // Should get an error when seed has too few bytes.
_, err := extkeys.NewMaster(bytes.Repeat([]byte{0x00}, 15), []byte{0x00}) _, err := extkeys.NewMaster(bytes.Repeat([]byte{0x00}, 15), []byte{0x00})
@ -425,6 +404,10 @@ func TestErrors(t *testing.T) {
password := "badpassword" password := "badpassword"
extKey, err := extkeys.NewMaster(mnemonic.MnemonicSeed(phrase, password), []byte(extkeys.Salt)) 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() pubKey, err := extKey.Neuter()
if err != nil { if err != nil {

View File

@ -7,10 +7,11 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"
"math/big" "math/big"
"strings" "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 // 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/trezor/python-mnemonic/blob/master/mnemonic/mnemonic.py
// https://github.com/bitpay/bitcore-mnemonic/blob/master/lib/mnemonic.js (used in eth-lightwallet.js) // 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" const Salt = "status-im"
// available dictionaries // available dictionaries
@ -39,6 +41,7 @@ const (
totalAvailableLanguages totalAvailableLanguages
) )
// Languages is a list of supported languages for which mnemonic keys can be generated
var Languages = [...]string{ var Languages = [...]string{
"English", "English",
"Chinese (Simplified)", "Chinese (Simplified)",
@ -51,19 +54,26 @@ var Languages = [...]string{
} }
var ( var (
Last11BitsMask = big.NewInt(2047) last11BitsMask = big.NewInt(2047)
RightShift11BitsDivider = big.NewInt(2048) rightShift11BitsDivider = big.NewInt(2048)
BigOne = big.NewInt(1) bigOne = big.NewInt(1)
BigTwo = big.NewInt(2) bigTwo = big.NewInt(2)
) )
// Language is language identifier
type Language int type Language int
// WordList is a list of input strings out of which mnemonic phrase is generated
type WordList [2048]string type WordList [2048]string
// Mnemonic represents mnemonic generator inited with a given salt
type Mnemonic struct { type Mnemonic struct {
salt string salt string
wordLists [totalAvailableLanguages]*WordList wordLists [totalAvailableLanguages]*WordList
} }
// NewMnemonic returns new mnemonic generator
// nolint: dupl, misspell
func NewMnemonic(salt string) *Mnemonic { func NewMnemonic(salt string) *Mnemonic {
if len(salt) == 0 { if len(salt) == 0 {
salt = Salt salt = Salt
@ -85,6 +95,7 @@ func NewMnemonic(salt string) *Mnemonic {
return mnemonic return mnemonic
} }
// AvailableLanguages returns list of languages available for mnemonic generation
func (m *Mnemonic) AvailableLanguages() []Language { func (m *Mnemonic) AvailableLanguages() []Language {
languages := make([]Language, totalAvailableLanguages) languages := make([]Language, totalAvailableLanguages)
for language := range m.wordLists { 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. // 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 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). // 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) 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) { func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
wordList, err := m.WordList(language) wordList, err := m.WordList(language)
if err != nil { if err != nil {
@ -149,15 +161,14 @@ func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
// TODO simplify? // TODO simplify?
for i := uint(0); i < checksumBitLength; i++ { for i := uint(0); i < checksumBitLength; i++ {
// Bitshift 1 left // Bitshift 1 left
entropyBigInt.Mul(entropyBigInt, BigTwo) entropyBigInt.Mul(entropyBigInt, bigTwo)
// Set rightmost bit if leftmost checksum bit is set // Set rightmost bit if leftmost checksum bit is set
if uint8(hash[0]&(1<<(7-i))) > 0 { if uint8(hash[0]&(1<<(7-i))) > 0 { // nolint: unconvert
entropyBigInt.Or(entropyBigInt, BigOne) entropyBigInt.Or(entropyBigInt, bigOne)
} }
} }
entropy = entropyBigInt.Bytes()
word := big.NewInt(0) word := big.NewInt(0)
for i := sentenceLength - 1; i >= 0; i-- { 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. // 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 // Get 11 right most bits and bitshift 11 to the right for next time
word.And(entropyBigInt, Last11BitsMask) word.And(entropyBigInt, last11BitsMask)
entropyBigInt.Div(entropyBigInt, RightShift11BitsDivider) entropyBigInt.Div(entropyBigInt, rightShift11BitsDivider)
// Get the bytes representing the 11 bits as a 2 byte slice // Get the bytes representing the 11 bits as a 2 byte slice
wordBytes := padByteSlice(word.Bytes(), 2) wordBytes := padByteSlice(word.Bytes(), 2)
@ -179,6 +190,7 @@ func (m *Mnemonic) MnemonicPhrase(strength, language Language) (string, error) {
return strings.Join(words, wordSeperator), nil return strings.Join(words, wordSeperator), nil
} }
// ValidMnemonic validates mnemonic string
func (m *Mnemonic) ValidMnemonic(mnemonic string, language Language) bool { func (m *Mnemonic) ValidMnemonic(mnemonic string, language Language) bool {
wordList, err := m.WordList(language) wordList, err := m.WordList(language)
if err != nil { if err != nil {
@ -206,6 +218,7 @@ func (m *Mnemonic) ValidMnemonic(mnemonic string, language Language) bool {
return true return true
} }
// WordList returns list of words for a given language
func (m *Mnemonic) WordList(language Language) (*WordList, error) { func (m *Mnemonic) WordList(language Language) (*WordList, error) {
if m.wordLists[language] == nil { if m.wordLists[language] == nil {
return nil, fmt.Errorf("language word list is missing (language id: %d)", language) return nil, fmt.Errorf("language word list is missing (language id: %d)", language)

View File

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

View File

@ -10,13 +10,12 @@ import (
"github.com/status-im/status-go/extkeys" "github.com/status-im/status-go/extkeys"
) )
// errors
var ( var (
ErrAddressToAccountMappingFailure = errors.New("cannot retreive a valid account for a given address") ErrAddressToAccountMappingFailure = errors.New("cannot retrieve a valid account for a given address")
ErrAccountToKeyMappingFailure = errors.New("cannot retreive a valid key for a given account") ErrAccountToKeyMappingFailure = errors.New("cannot retrieve a valid key for a given account")
ErrUnlockCalled = errors.New("no need to unlock accounts, login instead")
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper") ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities") 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") ErrNoAccountSelected = errors.New("no account has been selected, please login")
ErrInvalidMasterKeyCreated = errors.New("can not create master extended key") ErrInvalidMasterKeyCreated = errors.New("can not create master extended key")
ErrInvalidAccountAddressOrKey = errors.New("cannot parse address or key to valid account address") 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 if parentAddress == "" && nodeManager.SelectedAccount != nil { // derive from selected account by default
parentAddress = string(nodeManager.SelectedAccount.Address.Hex()) parentAddress = nodeManager.SelectedAccount.Address.Hex()
} }
if parentAddress == "" { if parentAddress == "" {
@ -88,7 +87,9 @@ func CreateChildAccount(parentAddress, password string) (address, pubKey string,
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
keyStore.IncSubAccountIndex(account, password) if err = keyStore.IncSubAccountIndex(account, password); err != nil {
return "", "", err
}
accountKey.SubAccountIndex++ accountKey.SubAccountIndex++
// import derived key into account keystore // import derived key into account keystore
@ -206,13 +207,6 @@ func Logout() error {
return nil 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. // 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. // 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) { func importExtendedKey(extKey *extkeys.ExtendedKey, password string) (address, pubKey string, err error) {

View File

@ -11,7 +11,7 @@ import (
func TestAccountsList(t *testing.T) { func TestAccountsList(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return return
} }
@ -20,7 +20,9 @@ func TestAccountsList(t *testing.T) {
t.Errorf("expected LES service: %v", err) t.Errorf("expected LES service: %v", err)
} }
accounts := les.StatusBackend.AccountManager().Accounts() 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) // make sure that we start with empty accounts list (nobody has logged in yet)
if len(accounts) != 0 { if len(accounts) != 0 {
@ -121,16 +123,17 @@ func TestAccountsList(t *testing.T) {
func TestAccountsCreateChildAccount(t *testing.T) { func TestAccountsCreateChildAccount(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
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() keyStore, err := geth.NodeManagerInstance().AccountKeyStore()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
// create an account // 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 // 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 { if err != nil {
t.Errorf("can not obtain decrypted account key: %v", err) t.Errorf("can not obtain decrypted account key: %v", err)
return return
@ -174,7 +177,7 @@ func TestAccountsCreateChildAccount(t *testing.T) {
// try to create sub-account with wrong password // try to create sub-account with wrong password
_, _, err = geth.CreateChildAccount("", "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) t.Errorf("expected error is not returned (tried to create sub-account with wrong password): %v", err)
return return
} }
@ -210,8 +213,7 @@ func TestAccountsCreateChildAccount(t *testing.T) {
func TestAccountsRecoverAccount(t *testing.T) { func TestAccountsRecoverAccount(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
keyStore, _ := geth.NodeManagerInstance().AccountKeyStore() keyStore, _ := geth.NodeManagerInstance().AccountKeyStore()
@ -261,7 +263,7 @@ func TestAccountsRecoverAccount(t *testing.T) {
} }
// make sure that extended key exists and is imported ok too // 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 { if err != nil {
t.Errorf("can not obtain decrypted account key: %v", err) t.Errorf("can not obtain decrypted account key: %v", err)
return return
@ -304,8 +306,7 @@ func TestAccountSelect(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
// test to see if the account was injected in whisper // test to see if the account was injected in whisper
@ -370,8 +371,7 @@ func TestAccountsLogout(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
whisperService, err := geth.NodeManagerInstance().WhisperService() whisperService, err := geth.NodeManagerInstance().WhisperService()
@ -401,8 +401,7 @@ func TestAccountsLogout(t *testing.T) {
t.Error("identity not injected into whisper") t.Error("identity not injected into whisper")
} }
err = geth.Logout() if err = geth.Logout(); err != nil {
if err != nil {
t.Errorf("cannot logout: %v", err) t.Errorf("cannot logout: %v", err)
} }
@ -415,8 +414,7 @@ func TestAccountsLogout(t *testing.T) {
func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) { func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
// we need to make sure that selected account is injected as identity into Whisper // 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) // stop node (and all of its sub-protocols)
if err := geth.NodeManagerInstance().StopNode(); err != nil { if err := geth.NodeManagerInstance().StopNode(); err != nil {
t.Error(err) t.Fatal(err)
return
} }
// make sure that account is still selected // make sure that account is still selected
@ -499,8 +496,7 @@ func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
// resume node // resume node
if err := geth.NodeManagerInstance().ResumeNode(); err != nil { if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
t.Error(err) t.Fatal(err)
return
} }
// re-check selected account (account2 MUST be selected) // re-check selected account (account2 MUST be selected)
@ -529,11 +525,12 @@ func TestAccountsSelectedAccountOnNodeRestart(t *testing.T) {
func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) { func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
err := geth.PrepareTestNode() err := geth.PrepareTestNode()
if err != nil { if err != nil {
t.Error(err) t.Fatal(err)
return
} }
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 // we need to make sure that selected account is injected as identity into Whisper
whisperService, err := geth.NodeManagerInstance().WhisperService() whisperService, err := geth.NodeManagerInstance().WhisperService()
@ -562,8 +559,7 @@ func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
// stop node (and all of its sub-protocols) // stop node (and all of its sub-protocols)
if err := geth.NodeManagerInstance().StopNode(); err != nil { if err := geth.NodeManagerInstance().StopNode(); err != nil {
t.Error(err) t.Fatal(err)
return
} }
// make sure that no account is selected // make sure that no account is selected
@ -574,8 +570,7 @@ func TestAccountsNodeRestartWithNoSelectedAccount(t *testing.T) {
// resume node // resume node
if err := geth.NodeManagerInstance().ResumeNode(); err != nil { if err := geth.NodeManagerInstance().ResumeNode(); err != nil {
t.Error(err) t.Fatal(err)
return
} }
// make sure that no account is selected // make sure that no account is selected

View File

@ -6,14 +6,58 @@ import (
) )
const ( const (
// EventLocalStorageSet is triggered when set request is sent to local storage
EventLocalStorageSet = "local_storage.set" EventLocalStorageSet = "local_storage.set"
// LocalStorageMaxDataLen is maximum length of data that you can store in local storage
LocalStorageMaxDataLen = 256 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 // 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 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) return newErrorResponse(call, -32603, err.Error(), nil)
} }
if netListeningResult != true { if !netListeningResult {
return newErrorResponse(call, -32603, geth.ErrInvalidGethNode.Error(), nil) 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 // 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 { return func(call otto.FunctionCall) otto.Value {
data := call.Argument(0).String() data := call.Argument(0).String()
if len(data) > LocalStorageMaxDataLen { // cap input 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{ geth.SendSignal(geth.SignalEnvelope{
Type: EventLocalStorageSet, Type: EventLocalStorageSet,
Event: geth.LocalStorageSetEvent{ Event: geth.LocalStorageSetEvent{
ChatId: chatId, ChatID: chatID,
Data: data, Data: data,
}, },
}) })

View File

@ -16,13 +16,17 @@ import (
) )
const ( const (
// JailedRuntimeRequestTimeout seconds before jailed request times out
JailedRuntimeRequestTimeout = time.Second * 60 JailedRuntimeRequestTimeout = time.Second * 60
) )
// errors
var ( var (
ErrInvalidJail = errors.New("jail environment is not properly initialized") 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 { type Jail struct {
sync.RWMutex sync.RWMutex
client *rpc.Client // lazy inited on the first call client *rpc.Client // lazy inited on the first call
@ -31,16 +35,18 @@ type Jail struct {
requestQueue *geth.JailedRequestQueue requestQueue *geth.JailedRequestQueue
} }
// JailedRuntime represents single jail cell, which is JavaScript VM.
type JailedRuntime struct { type JailedRuntime struct {
id string id string
vm *otto.Otto vm *otto.Otto
sem *semaphore.Semaphore sem *semaphore.Semaphore
} }
var Web3_JS = static.MustAsset("scripts/web3.js") var web3JS = static.MustAsset("scripts/web3.js")
var jailInstance *Jail var jailInstance *Jail
var once sync.Once var once sync.Once
// New returns singleton jail environment
func New() *Jail { func New() *Jail {
once.Do(func() { once.Do(func() {
jailInstance = &Jail{ jailInstance = &Jail{
@ -51,6 +57,7 @@ func New() *Jail {
return jailInstance return jailInstance
} }
// Init allows to setup initial JavaScript to be loaded on each jail.Parse()
func Init(js string) *Jail { func Init(js string) *Jail {
jailInstance = New() // singleton, we will always get the same reference jailInstance = New() // singleton, we will always get the same reference
jailInstance.statusJS = js jailInstance.statusJS = js
@ -58,10 +65,12 @@ func Init(js string) *Jail {
return jailInstance return jailInstance
} }
// GetInstance returns singleton jail environment instance
func GetInstance() *Jail { func GetInstance() *Jail {
return New() // singleton, we will always get the same reference return New() // singleton, we will always get the same reference
} }
// NewJailedRuntime initializes and returns jail cell
func NewJailedRuntime(id string) *JailedRuntime { func NewJailedRuntime(id string) *JailedRuntime {
return &JailedRuntime{ return &JailedRuntime{
id: id, 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 { if jail == nil {
return printError(ErrInvalidJail.Error()) return printError(ErrInvalidJail.Error())
} }
@ -78,25 +90,23 @@ func (jail *Jail) Parse(chatId string, js string) string {
jail.Lock() jail.Lock()
defer jail.Unlock() defer jail.Unlock()
jail.cells[chatId] = NewJailedRuntime(chatId) jail.cells[chatID] = NewJailedRuntime(chatID)
vm := jail.cells[chatId].vm vm := jail.cells[chatID].vm
initJjs := jail.statusJS + ";" initJjs := jail.statusJS + ";"
_, err := vm.Run(initJjs) if _, err = vm.Run(initJjs); err != nil {
return printError(err.Error())
}
// jeth and its handlers // init jeth and its handlers
vm.Set("jeth", struct{}{}) if err = vm.Set("jeth", struct{}{}); err != nil {
jethObj, _ := vm.Get("jeth") return printError(err.Error())
jethObj.Object().Set("send", makeSendHandler(jail, chatId)) }
jethObj.Object().Set("sendAsync", makeSendHandler(jail, chatId)) if err = registerHandlers(jail, vm, chatID); err != nil {
jethObj.Object().Set("isConnected", makeJethIsConnectedHandler(jail)) return printError(err.Error())
}
// localStorage and its handlers jjs := string(web3JS) + `
vm.Set("localStorage", struct{}{})
localStorage, _ := vm.Get("localStorage")
localStorage.Object().Set("set", makeLocalStorageSetHandler(chatId))
jjs := string(Web3_JS) + `
var Web3 = require('web3'); var Web3 = require('web3');
var web3 = new Web3(jeth); var web3 = new Web3(jeth);
var Bignumber = require("bignumber.js"); var Bignumber = require("bignumber.js");
@ -104,24 +114,30 @@ func (jail *Jail) Parse(chatId string, js string) string {
return new Bignumber(val); return new Bignumber(val);
} }
` + js + "; var catalog = JSON.stringify(_status_catalog);" ` + 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) 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() _, err := jail.RPCClient()
if err != nil { if err != nil {
return printError(err.Error()) return printError(err.Error())
} }
jail.RLock() jail.RLock()
cell, ok := jail.cells[chatId] cell, ok := jail.cells[chatID]
if !ok { if !ok {
jail.RUnlock() jail.RUnlock()
return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatId)) return printError(fmt.Sprintf("Cell[%s] doesn't exist.", chatID))
} }
jail.RUnlock() jail.RUnlock()
@ -131,7 +147,8 @@ func (jail *Jail) Call(chatId string, path string, args string) string {
return printResult(res.String(), err) 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 { if jail == nil {
return nil, ErrInvalidJail return nil, ErrInvalidJail
} }
@ -139,16 +156,17 @@ func (jail *Jail) GetVM(chatId string) (*otto.Otto, error) {
jail.RLock() jail.RLock()
defer jail.RUnlock() defer jail.RUnlock()
cell, ok := jail.cells[chatId] cell, ok := jail.cells[chatID]
if !ok { 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 return cell.vm, nil
} }
// Send will serialize the first argument, send it to the node and returns the response. // 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() client, err := jail.RPCClient()
if err != nil { if err != nil {
return newErrorResponse(call, -32603, err.Error(), 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()") resps, _ := call.Otto.Object("new Array()")
for _, req := range reqs { for _, req := range reqs {
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`) resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("id", req.Id) resp.Set("id", req.ID)
var result json.RawMessage var result json.RawMessage
// execute directly w/o RPC call to node // 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) txHash, err := requestQueue.ProcessSendTransactionRequest(call.Otto, req)
resp.Set("result", txHash.Hex()) resp.Set("result", txHash.Hex())
if err != nil { 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) resps.Call("push", resp)
continue 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) // do extra request pre processing (persist message id)
// within function semaphore will be acquired and released, // within function semaphore will be acquired and released,
// so that no more than one client (per cell) can enter // 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 { if err != nil {
return newErrorResponse(call, -32603, err.Error(), 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. // raw message for some reason.
resp.Set("result", otto.NullValue()) resp.Set("result", otto.NullValue())
} else { } else {
resultVal, err := JSON.Call("parse", string(result)) resultVal, callErr := JSON.Call("parse", string(result))
if err != nil { if callErr != nil {
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object() resp = newErrorResponse(call, -32603, callErr.Error(), &req.ID).Object()
} else { } else {
resp.Set("result", resultVal) resp.Set("result", resultVal)
} }
@ -233,12 +251,12 @@ func (jail *Jail) Send(chatId string, call otto.FunctionCall) (response otto.Val
"message": err.Error(), "message": err.Error(),
}) })
default: default:
resp = newErrorResponse(call, -32603, err.Error(), &req.Id).Object() resp = newErrorResponse(call, -32603, err.Error(), &req.ID).Object()
} }
resps.Call("push", resp) resps.Call("push", resp)
// do extra request post processing (setting back tx context) // 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) // 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 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) { func (jail *Jail) RPCClient() (*rpc.Client, error) {
if jail == nil { if jail == nil {
return nil, ErrInvalidJail return nil, ErrInvalidJail
@ -279,6 +300,9 @@ func (jail *Jail) RPCClient() (*rpc.Client, error) {
return jail.client, nil 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) { func (jail *Jail) RequestQueue() (*geth.JailedRequestQueue, error) {
if jail == nil { if jail == nil {
return nil, ErrInvalidJail 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 { func newResultResponse(call otto.FunctionCall, result interface{}) otto.Value {
resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`) resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`)
resp.Set("result", result) resp.Set("result", result) // nolint: errcheck
return resp.Value() return resp.Value()
} }

View File

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

View File

@ -31,14 +31,17 @@ import (
) )
const ( const (
// EventNodeStarted is triggered when underlying node is fully started
EventNodeStarted = "node.started" EventNodeStarted = "node.started"
// EventNodeCrashed is triggered when node crashes
EventNodeCrashed = "node.crashed" EventNodeCrashed = "node.crashed"
) )
// node-related errors // node-related errors
var ( var (
ErrEthServiceRegistrationFailure = errors.New("failed to register the Ethereum service") ErrEthServiceRegistrationFailure = errors.New("failed to register the Ethereum service")
ErrSshServiceRegistrationFailure = errors.New("failed to register the Whisper service") ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service")
ErrLightEthRegistrationFailure = errors.New("failed to register the LES service") ErrLightEthRegistrationFailure = errors.New("failed to register the LES service")
) )
@ -139,7 +142,7 @@ func MakeNode(config *params.NodeConfig) *Node {
// start Whisper service // start Whisper service
if err := activateShhService(stack, config); err != nil { if err := activateShhService(stack, config); err != nil {
Fatalf(fmt.Errorf("%v: %v", ErrSshServiceRegistrationFailure, err)) Fatalf(fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err))
} }
return &Node{ return &Node{
@ -168,7 +171,7 @@ func activateEthService(stack *node.Node, config *params.NodeConfig) error {
ethConf := eth.DefaultConfig ethConf := eth.DefaultConfig
ethConf.Genesis = genesis ethConf.Genesis = genesis
ethConf.SyncMode = downloader.LightSync ethConf.SyncMode = downloader.LightSync
ethConf.NetworkId = config.NetworkId ethConf.NetworkId = config.NetworkID
ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache
ethConf.MaxPeers = config.MaxPeers ethConf.MaxPeers = config.MaxPeers
ethConf.DatabaseHandles = makeDatabaseHandles() ethConf.DatabaseHandles = makeDatabaseHandles()
@ -213,11 +216,8 @@ func activateShhService(stack *node.Node, config *params.NodeConfig) error {
return whisperService, nil return whisperService, nil
} }
if err := stack.Register(serviceConstructor); err != nil {
return err
}
return nil return stack.Register(serviceConstructor)
} }
// makeIPCPath returns IPC-RPC filename // makeIPCPath returns IPC-RPC filename
@ -297,6 +297,9 @@ func makeBootstrapNodesV5() []*discv5.Node {
return bootstapNodes 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{}) { func Fatalf(reason interface{}, args ...interface{}) {
// decide on output stream // decide on output stream
w := io.MultiWriter(os.Stdout, os.Stderr) w := io.MultiWriter(os.Stdout, os.Stderr)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,12 +12,24 @@ import (
) )
const ( const (
// EventTransactionQueued is triggered whan send transaction request is queued
EventTransactionQueued = "transaction.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" SendTransactionNoErrorCode = "0"
SendTransactionDefaultErrorCode = "1" SendTransactionDefaultErrorCode = "1"
SendTransactionPasswordErrorCode = "2" SendTransactionPasswordErrorCode = "2"
@ -29,9 +41,9 @@ func onSendTransactionRequest(queuedTx status.QueuedTx) {
SendSignal(SignalEnvelope{ SendSignal(SignalEnvelope{
Type: EventTransactionQueued, Type: EventTransactionQueued,
Event: SendTransactionEvent{ Event: SendTransactionEvent{
Id: string(queuedTx.Id), ID: string(queuedTx.ID),
Args: queuedTx.Args, Args: queuedTx.Args,
MessageId: messageIdFromContext(queuedTx.Context), MessageID: messageIDFromContext(queuedTx.Context),
}, },
}) })
} }
@ -50,9 +62,9 @@ func onSendTransactionReturn(queuedTx *status.QueuedTx, err error) {
SendSignal(SignalEnvelope{ SendSignal(SignalEnvelope{
Type: EventTransactionFailed, Type: EventTransactionFailed,
Event: ReturnSendTransactionEvent{ Event: ReturnSendTransactionEvent{
Id: string(queuedTx.Id), ID: string(queuedTx.ID),
Args: queuedTx.Args, Args: queuedTx.Args,
MessageId: messageIdFromContext(queuedTx.Context), MessageID: messageIDFromContext(queuedTx.Context),
ErrorMessage: err.Error(), ErrorMessage: err.Error(),
ErrorCode: sendTransactionErrorCode(err), 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) { func CompleteTransaction(id, password string) (common.Hash, error) {
lightEthereum, err := NodeManagerInstance().LightEthereumService() lightEthereum, err := NodeManagerInstance().LightEthereumService()
if err != nil { if err != nil {
@ -87,13 +100,14 @@ func CompleteTransaction(id, password string) (common.Hash, error) {
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, status.SelectedAccountKey, NodeManagerInstance().SelectedAccount.Hex()) 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 { func CompleteTransactions(ids, password string) map[string]RawCompleteTransactionResult {
results := make(map[string]RawCompleteTransactionResult) results := make(map[string]RawCompleteTransactionResult)
parsedIds, err := parseJSONArray(ids) parsedIDs, err := parseJSONArray(ids)
if err != nil { if err != nil {
results["none"] = RawCompleteTransactionResult{ results["none"] = RawCompleteTransactionResult{
Error: err, Error: err,
@ -101,9 +115,9 @@ func CompleteTransactions(ids, password string) map[string]RawCompleteTransactio
return results return results
} }
for _, txId := range parsedIds { for _, txID := range parsedIDs {
txHash, txErr := CompleteTransaction(txId, password) txHash, txErr := CompleteTransaction(txID, password)
results[txId] = RawCompleteTransactionResult{ results[txID] = RawCompleteTransactionResult{
Hash: txHash, Hash: txHash,
Error: txErr, Error: txErr,
} }
@ -112,6 +126,7 @@ func CompleteTransactions(ids, password string) map[string]RawCompleteTransactio
return results return results
} }
// DiscardTransaction discards a given transaction from transaction queue
func DiscardTransaction(id string) error { func DiscardTransaction(id string) error {
lightEthereum, err := NodeManagerInstance().LightEthereumService() lightEthereum, err := NodeManagerInstance().LightEthereumService()
if err != nil { if err != nil {
@ -120,14 +135,15 @@ func DiscardTransaction(id string) error {
backend := lightEthereum.StatusBackend 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 { func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
var parsedIds []string var parsedIDs []string
results := make(map[string]RawDiscardTransactionResult) results := make(map[string]RawDiscardTransactionResult)
parsedIds, err := parseJSONArray(ids) parsedIDs, err := parseJSONArray(ids)
if err != nil { if err != nil {
results["none"] = RawDiscardTransactionResult{ results["none"] = RawDiscardTransactionResult{
Error: err, Error: err,
@ -135,10 +151,10 @@ func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
return results return results
} }
for _, txId := range parsedIds { for _, txID := range parsedIDs {
err := DiscardTransaction(txId) err := DiscardTransaction(txID)
if err != nil { if err != nil {
results[txId] = RawDiscardTransactionResult{ results[txID] = RawDiscardTransactionResult{
Error: err, Error: err,
} }
} }
@ -147,40 +163,50 @@ func DiscardTransactions(ids string) map[string]RawDiscardTransactionResult {
return results return results
} }
func messageIdFromContext(ctx context.Context) string { func messageIDFromContext(ctx context.Context) string {
if ctx == nil { if ctx == nil {
return "" return ""
} }
if messageId, ok := ctx.Value(MessageIdKey).(string); ok { if messageID, ok := ctx.Value(MessageIDKey).(string); ok {
return messageId return messageID
} }
return "" 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{} type JailedRequestQueue struct{}
// NewJailedRequestsQueue returns new instance of request queue
func NewJailedRequestsQueue() *JailedRequestQueue { func NewJailedRequestsQueue() *JailedRequestQueue {
return &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) { 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) { // PostProcessRequest post-processes a given RPC call to a given Otto VM
if len(messageId) > 0 { func (q *JailedRequestQueue) PostProcessRequest(vm *otto.Otto, req RPCCall, messageID string) {
vm.Call("addContext", nil, messageId, MessageIdKey, messageId) if len(messageID) > 0 {
vm.Call("addContext", nil, messageID, MessageIDKey, messageID) // nolint: errcheck
} }
// set extra markers for queued transaction requests // set extra markers for queued transaction requests
if req.Method == SendTransactionRequest { 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) { func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RPCCall) (common.Hash, error) {
// obtain status backend from LES service // obtain status backend from LES service
lightEthereum, err := NodeManagerInstance().LightEthereumService() lightEthereum, err := NodeManagerInstance().LightEthereumService()
@ -189,13 +215,13 @@ func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RP
} }
backend := lightEthereum.StatusBackend backend := lightEthereum.StatusBackend
messageId, err := q.PreProcessRequest(vm, req) messageID, err := q.PreProcessRequest(vm, req)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
// onSendTransactionRequest() will use context to obtain and release ticket // onSendTransactionRequest() will use context to obtain and release ticket
ctx := context.Background() ctx := context.Background()
ctx = context.WithValue(ctx, MessageIdKey, messageId) ctx = context.WithValue(ctx, MessageIDKey, messageID)
// this call blocks, up until Complete Transaction is called // this call blocks, up until Complete Transaction is called
txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req)) txHash, err := backend.SendTransaction(ctx, sendTxArgsFromRPCCall(req))
@ -204,20 +230,20 @@ func (q *JailedRequestQueue) ProcessSendTransactionRequest(vm *otto.Otto, req RP
} }
// invoke post processing // invoke post processing
q.PostProcessRequest(vm, req, messageId) q.PostProcessRequest(vm, req, messageID)
return txHash, nil return txHash, nil
} }
// currentMessageId looks for `status.message_id` variable in current JS context // currentMessageID looks for `status.message_id` variable in current JS context
func currentMessageId(ctx otto.Context) string { func currentMessageID(ctx otto.Context) string {
if statusObj, ok := ctx.Symbols["status"]; ok { if statusObj, ok := ctx.Symbols["status"]; ok {
messageId, err := statusObj.Object().Get("message_id") messageID, err := statusObj.Object().Get("message_id")
if err != nil { if err != nil {
return "" return ""
} }
if messageId, err := messageId.ToString(); err == nil { if messageID, err := messageID.ToString(); err == nil {
return messageId return messageID
} }
} }
@ -287,6 +313,7 @@ func (r RPCCall) parseData() hexutil.Bytes {
return byteCode return byteCode
} }
// nolint: dupl
func (r RPCCall) parseValue() *hexutil.Big { func (r RPCCall) parseValue() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{}) params, ok := r.Params[0].(map[string]interface{})
if !ok { if !ok {
@ -307,6 +334,7 @@ func (r RPCCall) parseValue() *hexutil.Big {
return (*hexutil.Big)(parsedValue) return (*hexutil.Big)(parsedValue)
} }
// nolint: dupl
func (r RPCCall) parseGas() *hexutil.Big { func (r RPCCall) parseGas() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{}) params, ok := r.Params[0].(map[string]interface{})
if !ok { if !ok {
@ -326,6 +354,7 @@ func (r RPCCall) parseGas() *hexutil.Big {
return (*hexutil.Big)(parsedValue) return (*hexutil.Big)(parsedValue)
} }
// nolint: dupl
func (r RPCCall) parseGasPrice() *hexutil.Big { func (r RPCCall) parseGasPrice() *hexutil.Big {
params, ok := r.Params[0].(map[string]interface{}) params, ok := r.Params[0].(map[string]interface{})
if !ok { if !ok {

View File

@ -37,7 +37,9 @@ func TestQueuedContracts(t *testing.T) {
return return
} }
geth.Logout() if err = geth.Logout(); err != nil {
t.Fatal(err)
}
// make sure you panic if transaction complete doesn't return // make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1) completeQueuedTransaction := make(chan struct{}, 1)
@ -45,7 +47,7 @@ func TestQueuedContracts(t *testing.T) {
// replace transaction notification handler // replace transaction notification handler
var txHash = common.Hash{} var txHash = common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint :dupl
var envelope geth.SignalEnvelope 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) 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) // 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 { 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 return
} }
@ -67,7 +69,7 @@ func TestQueuedContracts(t *testing.T) {
return return
} }
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender { 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 return
} }
@ -77,7 +79,7 @@ func TestQueuedContracts(t *testing.T) {
return return
} }
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil { 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 return
} }
@ -143,7 +145,9 @@ func TestQueuedTransactions(t *testing.T) {
return return
} }
geth.Logout() if err = geth.Logout(); err != nil {
t.Fatal(err)
}
// make sure you panic if transaction complete doesn't return // make sure you panic if transaction complete doesn't return
completeQueuedTransaction := make(chan struct{}, 1) completeQueuedTransaction := make(chan struct{}, 1)
@ -151,7 +155,7 @@ func TestQueuedTransactions(t *testing.T) {
// replace transaction notification handler // replace transaction notification handler
var txHash = common.Hash{} var txHash = common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { // nolint: dupl
var envelope geth.SignalEnvelope 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) 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) // 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 { 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 return
} }
@ -173,7 +177,7 @@ func TestQueuedTransactions(t *testing.T) {
return return
} }
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != status.ErrInvalidCompleteTxSender { 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 return
} }
@ -183,7 +187,7 @@ func TestQueuedTransactions(t *testing.T) {
return return
} }
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil { 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 return
} }
@ -246,7 +250,7 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions") geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
// replace transaction notification handler // replace transaction notification handler
var txId string var txID string
txFailedEventCalled := false txFailedEventCalled := false
txHash := common.Hash{} txHash := common.Hash{}
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
@ -257,12 +261,12 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
} }
if envelope.Type == geth.EventTransactionQueued { if envelope.Type == geth.EventTransactionQueued {
event := envelope.Event.(map[string]interface{}) event := envelope.Event.(map[string]interface{})
txId = event["id"].(string) txID = event["id"].(string)
t.Logf("transaction queued (will be failed and completed on the second call): {id: %s}\n", txId) t.Logf("transaction queued (will be failed and completed on the second call): {id: %s}\n", txID)
// try with wrong password // try with wrong password
// make sure that tx is NOT removed from the queue (by re-trying with the correct 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) t.Errorf("expects wrong password error, but call succeeded (or got another error: %v)", err)
return return
} }
@ -274,7 +278,7 @@ func TestDoubleCompleteQueuedTransactions(t *testing.T) {
// now try to complete transaction, but with the correct password // now try to complete transaction, but with the correct password
if txHash, err = geth.CompleteTransaction(event["id"].(string), testConfig.Account1.Password); err != nil { 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 return
} }
@ -370,7 +374,7 @@ func TestDiscardQueuedTransactions(t *testing.T) {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions") geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestDiscardQueuedTransactions")
// replace transaction notification handler // replace transaction notification handler
var txId string var txID string
txFailedEventCalled := false txFailedEventCalled := false
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var envelope geth.SignalEnvelope var envelope geth.SignalEnvelope
@ -380,31 +384,31 @@ func TestDiscardQueuedTransactions(t *testing.T) {
} }
if envelope.Type == geth.EventTransactionQueued { if envelope.Type == geth.EventTransactionQueued {
event := envelope.Event.(map[string]interface{}) event := envelope.Event.(map[string]interface{})
txId = event["id"].(string) txID = event["id"].(string)
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txId) t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
if !backend.TransactionQueue().Has(status.QueuedTxId(txId)) { if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txId) t.Errorf("txqueue should still have test tx: %s", txID)
return return
} }
// discard // discard
err := geth.DiscardTransaction(txId) err := geth.DiscardTransaction(txID)
if err != nil { if err != nil {
t.Errorf("cannot discard tx: %v", err) t.Errorf("cannot discard tx: %v", err)
return return
} }
// try completing discarded transaction // 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" { if err.Error() != "transaction hash not found" {
t.Error("expects tx not found, but call to CompleteTransaction succeeded") t.Error("expects tx not found, but call to CompleteTransaction succeeded")
return return
} }
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
if backend.TransactionQueue().Has(status.QueuedTxId(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) t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
return return
} }
@ -485,12 +489,12 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
// make sure you panic if transaction complete doesn't return // make sure you panic if transaction complete doesn't return
testTxCount := 3 testTxCount := 3
txIds := make(chan string, testTxCount) txIDs := make(chan string, testTxCount)
allTestTxCompleted := make(chan struct{}, 1) allTestTxCompleted := make(chan struct{}, 1)
// replace transaction notification handler // replace transaction notification handler
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txId string var txID string
var envelope geth.SignalEnvelope 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) t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
@ -498,10 +502,10 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
} }
if envelope.Type == geth.EventTransactionQueued { if envelope.Type == geth.EventTransactionQueued {
event := envelope.Event.(map[string]interface{}) event := envelope.Event.(map[string]interface{})
txId = event["id"].(string) txID = event["id"].(string)
t.Logf("transaction queued (will be completed in a single call, once aggregated): {id: %s}\n", txId) 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 // wait for transactions, and complete them in a single call
completeTxs := func(txIdStrings string) { completeTxs := func(txIDStrings string) {
var parsedIds []string var parsedIDs []string
json.Unmarshal([]byte(txIdStrings), &parsedIds) if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
t.Error(err)
return
}
parsedIds = append(parsedIds, "invalid-tx-id") parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIdStrings, _ := json.Marshal(parsedIds) updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// complete // 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" { if len(results) != (testTxCount+1) || results["invalid-tx-id"].Error.Error() != "transaction hash not found" {
t.Errorf("cannot complete txs: %v", results) t.Errorf("cannot complete txs: %v", results)
return return
} }
for txId, txResult := range results { for txID, txResult := range results {
if txResult.Error != nil && txId != "invalid-tx-id" { if txResult.Error != nil && txID != "invalid-tx-id" {
t.Errorf("invalid error for %s", txId) t.Errorf("invalid error for %s", txID)
return return
} }
if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txId != "invalid-tx-id" { if txResult.Hash.Hex() == "0x0000000000000000000000000000000000000000000000000000000000000000" && txID != "invalid-tx-id" {
t.Errorf("invalid hash (expected non empty hash): %s", txId) t.Errorf("invalid hash (expected non empty hash): %s", txID)
return return
} }
@ -553,21 +560,21 @@ func TestCompleteMultipleQueuedTransactions(t *testing.T) {
} }
time.Sleep(1 * time.Second) // make sure that tx complete signal propagates time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txId := range parsedIds { for _, txID := range parsedIDs {
if backend.TransactionQueue().Has(status.QueuedTxId(txId)) { if backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txId) t.Errorf("txqueue should not have test tx at this point (it should be completed): %s", txID)
return return
} }
} }
} }
go func() { go func() {
var txIdStrings []string var txIDStrings []string
for i := 0; i < testTxCount; i++ { for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds) txIDStrings = append(txIDStrings, <-txIDs)
} }
txIdJSON, _ := json.Marshal(txIdStrings) txIDJSON, _ := json.Marshal(txIDStrings)
completeTxs(string(txIdJSON)) completeTxs(string(txIDJSON))
allTestTxCompleted <- struct{}{} allTestTxCompleted <- struct{}{}
}() }()
@ -616,13 +623,13 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
// make sure you panic if transaction complete doesn't return // make sure you panic if transaction complete doesn't return
testTxCount := 3 testTxCount := 3
txIds := make(chan string, testTxCount) txIDs := make(chan string, testTxCount)
allTestTxDiscarded := make(chan struct{}, 1) allTestTxDiscarded := make(chan struct{}, 1)
// replace transaction notification handler // replace transaction notification handler
txFailedEventCallCount := 0 txFailedEventCallCount := 0
geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) { geth.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
var txId string var txID string
var envelope geth.SignalEnvelope 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) t.Errorf("cannot unmarshal event's JSON: %s", jsonEvent)
@ -630,15 +637,15 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
} }
if envelope.Type == geth.EventTransactionQueued { if envelope.Type == geth.EventTransactionQueued {
event := envelope.Event.(map[string]interface{}) event := envelope.Event.(map[string]interface{})
txId = event["id"].(string) txID = event["id"].(string)
t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txId) t.Logf("transaction queued (will be discarded soon): {id: %s}\n", txID)
if !backend.TransactionQueue().Has(status.QueuedTxId(txId)) { if !backend.TransactionQueue().Has(status.QueuedTxID(txID)) {
t.Errorf("txqueue should still have test tx: %s", txId) t.Errorf("txqueue should still have test tx: %s", txID)
return return
} }
txIds <- txId txIDs <- txID
} }
if envelope.Type == geth.EventTransactionFailed { if envelope.Type == geth.EventTransactionFailed {
@ -684,22 +691,25 @@ func TestDiscardMultipleQueuedTransactions(t *testing.T) {
} }
// wait for transactions, and discard immediately // wait for transactions, and discard immediately
discardTxs := func(txIdStrings string) { discardTxs := func(txIDStrings string) {
var parsedIds []string var parsedIDs []string
json.Unmarshal([]byte(txIdStrings), &parsedIds) if err := json.Unmarshal([]byte(txIDStrings), &parsedIDs); err != nil {
t.Error(err)
return
}
parsedIds = append(parsedIds, "invalid-tx-id") parsedIDs = append(parsedIDs, "invalid-tx-id")
updatedTxIdStrings, _ := json.Marshal(parsedIds) updatedTxIDStrings, _ := json.Marshal(parsedIDs)
// discard // 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" { if len(discardResults) != 1 || discardResults["invalid-tx-id"].Error.Error() != "transaction hash not found" {
t.Errorf("cannot discard txs: %v", discardResults) t.Errorf("cannot discard txs: %v", discardResults)
return return
} }
// try completing discarded transaction // 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) { if len(completeResults) != (testTxCount + 1) {
t.Error("unexpected number of errors (call to CompleteTransaction should not succeed)") 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 time.Sleep(1 * time.Second) // make sure that tx complete signal propagates
for _, txId := range parsedIds { for _, txID := range parsedIDs {
if backend.TransactionQueue().Has(status.QueuedTxId(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) t.Errorf("txqueue should not have test tx at this point (it should be discarded): %s", txID)
return return
} }
} }
} }
go func() { go func() {
var txIdStrings []string var txIDStrings []string
for i := 0; i < testTxCount; i++ { for i := 0; i < testTxCount; i++ {
txIdStrings = append(txIdStrings, <-txIds) txIDStrings = append(txIDStrings, <-txIDs)
} }
txIdJSON, _ := json.Marshal(txIdStrings) txIDJSON, _ := json.Marshal(txIDStrings)
discardTxs(string(txIdJSON)) discardTxs(string(txIDJSON))
}() }()
// send multiple transactions // send multiple transactions
@ -769,35 +779,15 @@ func TestNonExistentQueuedTransactions(t *testing.T) {
geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions") geth.PanicAfter(20*time.Second, completeQueuedTransaction, "TestQueuedTransactions")
// replace transaction notification handler // replace transaction notification handler
var txHash = common.Hash{} geth.SetDefaultNodeNotificationHandler(func(string) {})
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
}
})
// try completing non-existing transaction // try completing non-existing transaction
if _, err = geth.CompleteTransaction("some-bad-transaction-id", testConfig.Account1.Password); err == nil { 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 return
} }
if err != status.ErrQueuedTxIdNotFound { if err != status.ErrQueuedTxIDNotFound {
t.Errorf("unexpected error recieved: expected '%s', got: '%s'", status.ErrQueuedTxIdNotFound.Error(), err.Error()) t.Errorf("unexpected error received: expected '%s', got: '%s'", status.ErrQueuedTxIDNotFound.Error(), err.Error())
return return
} }
} }
@ -841,7 +831,7 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
// next call is the very same one, but with the correct password // 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 { 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 return
} }
@ -852,10 +842,10 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
txQueue := backend.TransactionQueue() txQueue := backend.TransactionQueue()
var i = 0 var i = 0
txIds := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxId{} txIDs := [status.DefaultTxQueueCap + 5 + 10]status.QueuedTxID{}
backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) { backend.SetTransactionQueueHandler(func(queuedTx status.QueuedTx) {
t.Logf("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.Id) t.Logf("%d. Transaction queued (queue size: %d): {id: %v}\n", i, txQueue.Count(), queuedTx.ID)
txIds[i] = queuedTx.Id txIDs[i] = queuedTx.ID
i++ i++
}) })
@ -865,7 +855,7 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
} }
for i := 0; i < 10; i++ { 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) 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 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) time.Sleep(5 * time.Second)
@ -886,8 +876,8 @@ func TestEvictionOfQueuedTransactions(t *testing.T) {
return return
} }
for _, txId := range txIds { for _, txID := range txIDs {
txQueue.Remove(txId) txQueue.Remove(txID)
} }
if txQueue.Count() != 0 { if txQueue.Count() != 0 {

View File

@ -5,11 +5,13 @@ import (
"github.com/ethereum/go-ethereum/les/status" "github.com/ethereum/go-ethereum/les/status"
) )
// SignalEnvelope is a general signal sent upward from node to RN app
type SignalEnvelope struct { type SignalEnvelope struct {
Type string `json:"type"` Type string `json:"type"`
Event interface{} `json:"event"` Event interface{} `json:"event"`
} }
// AccountInfo represents account's info
type AccountInfo struct { type AccountInfo struct {
Address string `json:"address"` Address string `json:"address"`
PubKey string `json:"pubkey"` PubKey string `json:"pubkey"`
@ -17,24 +19,23 @@ type AccountInfo struct {
Error string `json:"error"` Error string `json:"error"`
} }
// JSONError is wrapper around errors, that are sent upwards
type JSONError struct { type JSONError struct {
Error string `json:"error"` Error string `json:"error"`
} }
// NodeCrashEvent is special kind of error, used to report node crashes
type NodeCrashEvent struct { type NodeCrashEvent struct {
Error string `json:"error"` Error string `json:"error"`
} }
// AddPeerResult is a JSON returned as a response to AddPeer() request
type AddPeerResult struct { type AddPeerResult struct {
Success bool `json:"success"` Success bool `json:"success"`
Error string `json:"error"` Error string `json:"error"`
} }
type AddWhisperFilterResult struct { // WhisperMessageEvent is a signal sent on incoming Whisper message
Id int `json:"id"`
Error string `json:"error"`
}
type WhisperMessageEvent struct { type WhisperMessageEvent struct {
Payload string `json:"payload"` Payload string `json:"payload"`
To string `json:"to"` To string `json:"to"`
@ -44,55 +45,65 @@ type WhisperMessageEvent struct {
Hash string `json:"hash"` Hash string `json:"hash"`
} }
// SendTransactionEvent is a signal sent on a send transaction request
type SendTransactionEvent struct { type SendTransactionEvent struct {
Id string `json:"id"` ID string `json:"id"`
Args status.SendTxArgs `json:"args"` 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 { type ReturnSendTransactionEvent struct {
Id string `json:"id"` ID string `json:"id"`
Args status.SendTxArgs `json:"args"` Args status.SendTxArgs `json:"args"`
MessageId string `json:"message_id"` MessageID string `json:"message_id"`
ErrorMessage string `json:"error_message"` ErrorMessage string `json:"error_message"`
ErrorCode string `json:"error_code"` ErrorCode string `json:"error_code"`
} }
// CompleteTransactionResult is a JSON returned from transaction complete function (used in exposed method)
type CompleteTransactionResult struct { type CompleteTransactionResult struct {
Id string `json:"id"` ID string `json:"id"`
Hash string `json:"hash"` Hash string `json:"hash"`
Error string `json:"error"` Error string `json:"error"`
} }
// RawCompleteTransactionResult is a JSON returned from transaction complete function (used internally)
type RawCompleteTransactionResult struct { type RawCompleteTransactionResult struct {
Hash common.Hash Hash common.Hash
Error error Error error
} }
// CompleteTransactionsResult is list of results from CompleteTransactions() (used in exposed method)
type CompleteTransactionsResult struct { type CompleteTransactionsResult struct {
Results map[string]CompleteTransactionResult `json:"results"` Results map[string]CompleteTransactionResult `json:"results"`
} }
// RawDiscardTransactionResult is list of results from CompleteTransactions() (used internally)
type RawDiscardTransactionResult struct { type RawDiscardTransactionResult struct {
Error error Error error
} }
// DiscardTransactionResult is a JSON returned from transaction discard function
type DiscardTransactionResult struct { type DiscardTransactionResult struct {
Id string `json:"id"` ID string `json:"id"`
Error string `json:"error"` Error string `json:"error"`
} }
// DiscardTransactionsResult is a list of results from DiscardTransactions()
type DiscardTransactionsResult struct { type DiscardTransactionsResult struct {
Results map[string]DiscardTransactionResult `json:"results"` Results map[string]DiscardTransactionResult `json:"results"`
} }
// LocalStorageSetEvent is a signal sent whenever local storage Set method is called
type LocalStorageSetEvent struct { type LocalStorageSetEvent struct {
ChatId string `json:"chat_id"` ChatID string `json:"chat_id"`
Data string `json:"data"` Data string `json:"data"`
} }
// RPCCall represents RPC call parameters
type RPCCall struct { type RPCCall struct {
Id int64 ID int64
Method string Method string
Params []interface{} Params []interface{}
} }

View File

@ -15,6 +15,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"testing"
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
@ -27,7 +28,11 @@ import (
var ( var (
muPrepareTestNode sync.Mutex muPrepareTestNode sync.Mutex
// RootDir is the main application directory
RootDir string RootDir string
// TestDataDir is data directory used for tests
TestDataDir string TestDataDir string
) )
@ -47,6 +52,8 @@ func init() {
TestDataDir = filepath.Join(RootDir, ".ethereumtest") 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) type NodeNotificationHandler func(jsonEvent string)
var notificationHandler NodeNotificationHandler = TriggerDefaultNodeNotificationHandler var notificationHandler NodeNotificationHandler = TriggerDefaultNodeNotificationHandler
@ -68,12 +75,12 @@ func SendSignal(signal SignalEnvelope) {
} }
//export NotifyNode //export NotifyNode
func NotifyNode(jsonEvent *C.char) { func NotifyNode(jsonEvent *C.char) { // nolint: golint
notificationHandler(C.GoString(jsonEvent)) notificationHandler(C.GoString(jsonEvent))
} }
//export TriggerTestSignal //export TriggerTestSignal
func TriggerTestSignal() { func TriggerTestSignal() { // nolint: golint
C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`)) C.StatusServiceSignalEvent(C.CString(`{"answer": 42}`))
} }
@ -106,27 +113,8 @@ func LoadTestConfig() (*TestConfig, error) {
return &testConfig, nil return &testConfig, nil
} }
func CopyFile(dst, src string) error { // LoadFromFile is useful for loading test data, from testdata/filename into a variable
s, err := os.Open(src) // nolint: errcheck
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
func LoadFromFile(filename string) string { func LoadFromFile(filename string) string {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
@ -140,6 +128,7 @@ func LoadFromFile(filename string) string {
return string(buf.Bytes()) return string(buf.Bytes())
} }
// PrepareTestNode initializes node manager and start a test node (only once!)
func PrepareTestNode() (err error) { func PrepareTestNode() (err error) {
muPrepareTestNode.Lock() muPrepareTestNode.Lock()
defer muPrepareTestNode.Unlock() defer muPrepareTestNode.Unlock()
@ -157,26 +146,26 @@ func PrepareTestNode() (err error) {
} }
syncRequired := false syncRequired := false
if _, err := os.Stat(TestDataDir); os.IsNotExist(err) { if _, err = os.Stat(TestDataDir); os.IsNotExist(err) {
syncRequired = true syncRequired = true
} }
// prepare node directory // 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) log.Warn("make node failed", "error", err)
return err return err
} }
// import test accounts (with test ether on it) // 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) 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) panic(err)
} }
// start geth node and wait for it to initialize // 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 { if err != nil {
return err return err
} }
@ -212,11 +201,42 @@ func PrepareTestNode() (err error) {
return nil return nil
} }
func RemoveTestNode() { // MakeTestCompleteTxHandler returns node notification handler to be used in test
err := os.RemoveAll(TestDataDir) // 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 { 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 // 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 { func FromAddress(accountAddress string) common.Address {
from, err := ParseAccountString(accountAddress) from, err := ParseAccountString(accountAddress)
if err != nil { if err != nil {
@ -240,6 +262,8 @@ func FromAddress(accountAddress string) common.Address {
return from.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 { func ToAddress(accountAddress string) *common.Address {
to, err := ParseAccountString(accountAddress) to, err := ParseAccountString(accountAddress)
if err != nil { if err != nil {
@ -280,7 +304,7 @@ func AddressToDecryptedAccount(address, password string) (accounts.Account, *key
func ImportTestAccount(keystoreDir, accountFile string) error { func ImportTestAccount(keystoreDir, accountFile string) error {
// make sure that keystore folder exists // make sure that keystore folder exists
if _, err := os.Stat(keystoreDir); os.IsNotExist(err) { 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) dst := filepath.Join(keystoreDir, accountFile)

View File

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

View File

@ -30,7 +30,9 @@ func TestWhisperFilterRace(t *testing.T) {
} }
accountKey1Hex := common.ToHex(crypto.FromECDSAPub(&accountKey1.PrivateKey.PublicKey)) 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 { if ok, err := whisperAPI.HasKeyPair(accountKey1Hex); err != nil || !ok {
t.Fatalf("identity not injected: %v", accountKey1Hex) t.Fatalf("identity not injected: %v", accountKey1Hex)
} }
@ -42,7 +44,9 @@ func TestWhisperFilterRace(t *testing.T) {
} }
accountKey2Hex := common.ToHex(crypto.FromECDSAPub(&accountKey2.PrivateKey.PublicKey)) 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 { if ok, err := whisperAPI.HasKeyPair(accountKey2Hex); err != nil || !ok {
t.Fatalf("identity not injected: %v", accountKey2Hex) t.Fatalf("identity not injected: %v", accountKey2Hex)
} }
@ -65,6 +69,7 @@ func TestWhisperFilterRace(t *testing.T) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
go func() { go func() {
// nolint: errcheck
whisperAPI.Subscribe(whisper.WhisperFilterArgs{ whisperAPI.Subscribe(whisper.WhisperFilterArgs{
Sig: accountKey1Hex, Sig: accountKey1Hex,
Key: accountKey2Hex, Key: accountKey2Hex,

View File

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

View File

@ -99,7 +99,7 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
} }
queuedTx := &status.QueuedTx{ queuedTx := &status.QueuedTx{
Id: status.QueuedTxId(uuid.New()), ID: status.QueuedTxID(uuid.New()),
Hash: common.Hash{}, Hash: common.Hash{},
Context: ctx, Context: ctx,
Args: status.SendTxArgs(args), Args: status.SendTxArgs(args),
@ -130,7 +130,7 @@ func (b *StatusBackend) SendTransaction(ctx context.Context, args status.SendTxA
} }
// CompleteQueuedTransaction wraps call to PublicTransactionPoolAPI.CompleteQueuedTransaction // 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) queuedTx, err := b.txQueue.Get(id)
if err != nil { if err != nil {
return common.Hash{}, err 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 // 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) queuedTx, err := b.txQueue.Get(id)
if err != nil { if err != nil {
return err return err
} }
// remove from queue, before notifying SendTransaction // remove from queue, before notifying SendTransaction
b.TransactionQueue().Remove(queuedTx.Id) b.TransactionQueue().Remove(queuedTx.ID)
// allow SendTransaction to return // allow SendTransaction to return
queuedTx.Err = status.ErrQueuedTxDiscarded queuedTx.Err = status.ErrQueuedTxDiscarded

View File

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