Generate bindings dynamically
Before we had two directories `lib/` and `mobile/` that generated respectively the bindings for desktop and ios/android. This needed to be kept in sync and there was a fair amount of code duplication, plus some missing methods on one or the other side. I have made a change so the whole `lib/` namespace is generated by parsing the `AST` of `mobile`, and bindings are generated before compiling.
This commit is contained in:
parent
0bffeab908
commit
b8d64cbb1e
7
Makefile
7
Makefile
|
@ -111,8 +111,10 @@ statusgo-ios: ##@cross-compile Build status-go for iOS
|
||||||
@echo "iOS framework cross compilation done in build/bin/Statusgo.framework"
|
@echo "iOS framework cross compilation done in build/bin/Statusgo.framework"
|
||||||
|
|
||||||
statusgo-library: ##@cross-compile Build status-go as static library for current platform
|
statusgo-library: ##@cross-compile Build status-go as static library for current platform
|
||||||
|
mkdir -p $(GOBIN)/statusgo-lib
|
||||||
|
go run cmd/library/* > $(GOBIN)/statusgo-lib/main.go
|
||||||
@echo "Building static library..."
|
@echo "Building static library..."
|
||||||
go build -buildmode=c-archive -o $(GOBIN)/libstatus.a $(BUILD_FLAGS) ./lib
|
go build -buildmode=c-archive -o $(GOBIN)/libstatus.a $(BUILD_FLAGS) $(GOBIN)/statusgo-lib
|
||||||
@echo "Static library built:"
|
@echo "Static library built:"
|
||||||
@ls -la $(GOBIN)/libstatus.*
|
@ls -la $(GOBIN)/libstatus.*
|
||||||
|
|
||||||
|
@ -257,7 +259,6 @@ test-unit: UNIT_TEST_PACKAGES = $(shell go list ./... | \
|
||||||
grep -v /vendor | \
|
grep -v /vendor | \
|
||||||
grep -v /t/e2e | \
|
grep -v /t/e2e | \
|
||||||
grep -v /t/benchmarks | \
|
grep -v /t/benchmarks | \
|
||||||
grep -v /lib | \
|
|
||||||
grep -v /transactions/fake )
|
grep -v /transactions/fake )
|
||||||
test-unit: ##@tests Run unit and integration tests
|
test-unit: ##@tests Run unit and integration tests
|
||||||
go test -v -failfast $(UNIT_TEST_PACKAGES) $(gotest_extraflags)
|
go test -v -failfast $(UNIT_TEST_PACKAGES) $(gotest_extraflags)
|
||||||
|
@ -276,8 +277,6 @@ test-e2e: ##@tests Run e2e tests
|
||||||
go test -timeout 20m ./t/e2e/whisper/... -network=$(networkid) $(gotest_extraflags)
|
go test -timeout 20m ./t/e2e/whisper/... -network=$(networkid) $(gotest_extraflags)
|
||||||
go test -timeout 10m ./t/e2e/transactions/... -network=$(networkid) $(gotest_extraflags)
|
go test -timeout 10m ./t/e2e/transactions/... -network=$(networkid) $(gotest_extraflags)
|
||||||
go test -timeout 10m ./t/e2e/services/... -network=$(networkid) $(gotest_extraflags)
|
go test -timeout 10m ./t/e2e/services/... -network=$(networkid) $(gotest_extraflags)
|
||||||
# e2e_test tag is required to include some files from ./lib without _test suffix
|
|
||||||
go test -timeout 40m -tags e2e_test ./lib -network=$(networkid) $(gotest_extraflags)
|
|
||||||
|
|
||||||
test-e2e-race: gotest_extraflags=-race
|
test-e2e-race: gotest_extraflags=-race
|
||||||
test-e2e-race: test-e2e ##@tests Run e2e tests with -race flag
|
test-e2e-race: test-e2e ##@tests Run e2e tests with -race flag
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
const prelude = `package main
|
||||||
|
// #include <stdlib.h>
|
||||||
|
import (
|
||||||
|
"C"
|
||||||
|
mobile "github.com/status-im/status-go/mobile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {}
|
||||||
|
`
|
||||||
|
|
||||||
|
const intType = "int"
|
||||||
|
const stringType = "string"
|
||||||
|
const unsafePointerType = "unsafe.Pointer"
|
||||||
|
const boolType = "bool"
|
|
@ -0,0 +1,137 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isCodeFile(info os.FileInfo) bool {
|
||||||
|
return !strings.HasSuffix(info.Name(), "test.go")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var output = prelude
|
||||||
|
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
|
||||||
|
// Parse the whole `mobile/` directory, excluding test files
|
||||||
|
parsedAST, err := parser.ParseDir(fset, "mobile/", isCodeFile, parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error parsing directory: %+v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range parsedAST {
|
||||||
|
for _, file := range a.Files {
|
||||||
|
// handle each file and append the output
|
||||||
|
output += handleFile(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFunction(name string, funcDecl *ast.FuncDecl) string {
|
||||||
|
params := funcDecl.Type.Params.List
|
||||||
|
results := funcDecl.Type.Results
|
||||||
|
|
||||||
|
// add export tag
|
||||||
|
output := fmt.Sprintf("// export %s\n", name)
|
||||||
|
// add initial func declaration
|
||||||
|
output += fmt.Sprintf("func %s (", name)
|
||||||
|
|
||||||
|
// iterate over parameters and correctly add the C type
|
||||||
|
paramCount := 0
|
||||||
|
for _, p := range params {
|
||||||
|
for _, paramIdentity := range p.Names {
|
||||||
|
if paramCount != 0 {
|
||||||
|
output += ", "
|
||||||
|
}
|
||||||
|
paramCount++
|
||||||
|
output += paramIdentity.Name
|
||||||
|
|
||||||
|
typeString := fmt.Sprint(paramIdentity.Obj.Decl.(*ast.Field).Type)
|
||||||
|
switch typeString {
|
||||||
|
case stringType:
|
||||||
|
output += " *C.char"
|
||||||
|
case intType, boolType:
|
||||||
|
output += " C.int"
|
||||||
|
case unsafePointerType:
|
||||||
|
output += " unsafe.Pointer"
|
||||||
|
default:
|
||||||
|
// ignore if the type is any different
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output += ")"
|
||||||
|
|
||||||
|
// check if it has a return value, convert to CString if so and return
|
||||||
|
if results != nil {
|
||||||
|
output += " *C.char {\nreturn C.CString("
|
||||||
|
} else {
|
||||||
|
output += " {\n"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the mobile equivalent function
|
||||||
|
output += fmt.Sprintf("mobile.%s(", name)
|
||||||
|
|
||||||
|
// iterate through the parameters, convert to go types and close
|
||||||
|
// the function call
|
||||||
|
paramCount = 0
|
||||||
|
for _, p := range params {
|
||||||
|
for _, paramIdentity := range p.Names {
|
||||||
|
if paramCount != 0 {
|
||||||
|
output += ", "
|
||||||
|
}
|
||||||
|
paramCount++
|
||||||
|
typeString := fmt.Sprint(paramIdentity.Obj.Decl.(*ast.Field).Type)
|
||||||
|
switch typeString {
|
||||||
|
case stringType:
|
||||||
|
output += fmt.Sprintf("C.GoString(%s)", paramIdentity.Name)
|
||||||
|
case intType:
|
||||||
|
output += fmt.Sprintf("int(%s)", paramIdentity.Name)
|
||||||
|
case unsafePointerType:
|
||||||
|
output += paramIdentity.Name
|
||||||
|
case boolType:
|
||||||
|
output += paramIdentity.Name
|
||||||
|
// convert int to bool
|
||||||
|
output += " == 1"
|
||||||
|
default:
|
||||||
|
// ignore otherwise
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close function call
|
||||||
|
output += ")"
|
||||||
|
|
||||||
|
// close conversion to CString
|
||||||
|
if results != nil {
|
||||||
|
output += ")\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// close function declaration
|
||||||
|
output += "}\n"
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFile(parsedAST *ast.File) string {
|
||||||
|
output := ""
|
||||||
|
for name, obj := range parsedAST.Scope.Objects {
|
||||||
|
// Ignore non-functions or non exported fields
|
||||||
|
if obj.Kind != ast.Fun || !unicode.IsUpper(rune(name[0])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
output += handleFunction(name, obj.Decl.(*ast.FuncDecl))
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
lib
|
|
||||||
===
|
|
||||||
|
|
||||||
**DEPRECATED. Please see [`./mobile`](./mobile/REDME.md) instead. Currently, the exported bindings are not in sync with `mobile`. They might be missing or working differently.**
|
|
||||||
|
|
||||||
This package provides CGO bindings so that it can be used to create a static library and link it with other projects in different languages, namely [status-react](https://github.com/status-im/status-react). Even though, we switched to [gomobile](./mobile/REDME.md), this package can still be useful for generating a static library to link with C/C++ program.
|
|
||||||
|
|
||||||
Bindings are exported and described in `library.go`.
|
|
651
lib/library.go
651
lib/library.go
|
@ -1,651 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// #include <stdlib.h>
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
validator "gopkg.in/go-playground/validator.v9"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/api"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/exportlogs"
|
|
||||||
"github.com/status-im/status-go/logutils"
|
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/profiling"
|
|
||||||
protocol "github.com/status-im/status-go/protocol"
|
|
||||||
"github.com/status-im/status-go/services/personal"
|
|
||||||
"github.com/status-im/status-go/services/typeddata"
|
|
||||||
"github.com/status-im/status-go/signal"
|
|
||||||
"github.com/status-im/status-go/transactions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OpenAccounts opens database and returns accounts list.
|
|
||||||
//export OpenAccounts
|
|
||||||
func OpenAccounts(datadir *C.char) *C.char {
|
|
||||||
statusBackend.UpdateRootDataDir(C.GoString(datadir))
|
|
||||||
err := statusBackend.OpenAccounts()
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
accs, err := statusBackend.GetAccounts()
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(accs)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractGroupMembershipSignatures extract public keys from tuples of content/signature
|
|
||||||
//export ExtractGroupMembershipSignatures
|
|
||||||
func ExtractGroupMembershipSignatures(signaturePairsStr *C.char) *C.char {
|
|
||||||
var signaturePairs [][2]string
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(signaturePairsStr)), &signaturePairs); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
identities, err := statusBackend.ExtractGroupMembershipSignatures(signaturePairs)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(struct {
|
|
||||||
Identities []string `json:"identities"`
|
|
||||||
}{Identities: identities})
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs a string containing group membership information
|
|
||||||
//export SignGroupMembership
|
|
||||||
func SignGroupMembership(content *C.char) *C.char {
|
|
||||||
signature, err := statusBackend.SignGroupMembership(C.GoString(content))
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(struct {
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
}{Signature: signature})
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
//ValidateNodeConfig validates config for status node
|
|
||||||
//export ValidateNodeConfig
|
|
||||||
func ValidateNodeConfig(configJSON *C.char) *C.char {
|
|
||||||
var resp APIDetailedResponse
|
|
||||||
|
|
||||||
_, err := params.NewConfigFromJSON(C.GoString(configJSON))
|
|
||||||
|
|
||||||
// Convert errors to APIDetailedResponse
|
|
||||||
switch err := err.(type) {
|
|
||||||
case validator.ValidationErrors:
|
|
||||||
resp = APIDetailedResponse{
|
|
||||||
Message: "validation: validation failed",
|
|
||||||
FieldErrors: make([]APIFieldError, len(err)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, ve := range err {
|
|
||||||
resp.FieldErrors[i] = APIFieldError{
|
|
||||||
Parameter: ve.Namespace(),
|
|
||||||
Errors: []APIError{
|
|
||||||
{
|
|
||||||
Message: fmt.Sprintf("field validation failed on the '%s' tag", ve.Tag()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case error:
|
|
||||||
resp = APIDetailedResponse{
|
|
||||||
Message: fmt.Sprintf("validation: %s", err.Error()),
|
|
||||||
}
|
|
||||||
case nil:
|
|
||||||
resp = APIDetailedResponse{
|
|
||||||
Status: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
respJSON, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(respJSON))
|
|
||||||
}
|
|
||||||
|
|
||||||
//ResetChainData remove chain data from data directory
|
|
||||||
//export ResetChainData
|
|
||||||
func ResetChainData() *C.char {
|
|
||||||
api.RunAsync(statusBackend.ResetChainData)
|
|
||||||
return makeJSONResponse(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//CallRPC calls public APIs via RPC
|
|
||||||
//export CallRPC
|
|
||||||
func CallRPC(inputJSON *C.char) *C.char {
|
|
||||||
outputJSON, err := statusBackend.CallRPC(C.GoString(inputJSON))
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
return C.CString(outputJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
//CallPrivateRPC calls both public and private APIs via RPC
|
|
||||||
//export CallPrivateRPC
|
|
||||||
func CallPrivateRPC(inputJSON *C.char) *C.char {
|
|
||||||
outputJSON, err := statusBackend.CallPrivateRPC(C.GoString(inputJSON))
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
return C.CString(outputJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
//CreateAccount is equivalent to creating an account from the command line,
|
|
||||||
// just modified to handle the function arg passing
|
|
||||||
//export CreateAccount
|
|
||||||
func CreateAccount(password *C.char) *C.char {
|
|
||||||
info, mnemonic, err := statusBackend.AccountManager().CreateAccount(C.GoString(password))
|
|
||||||
|
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := AccountInfo{
|
|
||||||
Address: info.WalletAddress,
|
|
||||||
PubKey: info.WalletPubKey,
|
|
||||||
WalletAddress: info.WalletAddress,
|
|
||||||
WalletPubKey: info.WalletPubKey,
|
|
||||||
ChatAddress: info.ChatAddress,
|
|
||||||
ChatPubKey: info.ChatPubKey,
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(out)
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
//RecoverAccount re-creates master key using given details
|
|
||||||
//export RecoverAccount
|
|
||||||
func RecoverAccount(password, mnemonic *C.char) *C.char {
|
|
||||||
info, err := statusBackend.AccountManager().RecoverAccount(C.GoString(password), C.GoString(mnemonic))
|
|
||||||
|
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := AccountInfo{
|
|
||||||
Address: info.WalletAddress,
|
|
||||||
PubKey: info.WalletPubKey,
|
|
||||||
WalletAddress: info.WalletAddress,
|
|
||||||
WalletPubKey: info.WalletPubKey,
|
|
||||||
ChatAddress: info.ChatAddress,
|
|
||||||
ChatPubKey: info.ChatPubKey,
|
|
||||||
Mnemonic: C.GoString(mnemonic),
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(out)
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartOnboarding initialize the onboarding with n random accounts
|
|
||||||
//export StartOnboarding
|
|
||||||
func StartOnboarding(n, mnemonicPhraseLength C.int) *C.char {
|
|
||||||
out := struct {
|
|
||||||
Accounts []OnboardingAccount `json:"accounts"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}{
|
|
||||||
Accounts: make([]OnboardingAccount, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts, err := statusBackend.AccountManager().StartOnboarding(int(n), int(mnemonicPhraseLength))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
out.Error = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
for _, account := range accounts {
|
|
||||||
out.Accounts = append(out.Accounts, OnboardingAccount{
|
|
||||||
ID: account.ID,
|
|
||||||
Address: account.Info.WalletAddress,
|
|
||||||
PubKey: account.Info.WalletPubKey,
|
|
||||||
WalletAddress: account.Info.WalletAddress,
|
|
||||||
WalletPubKey: account.Info.WalletPubKey,
|
|
||||||
ChatAddress: account.Info.ChatAddress,
|
|
||||||
ChatPubKey: account.Info.ChatPubKey,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outBytes, _ := json.Marshal(out)
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportOnboardingAccount re-creates and imports an account created during onboarding.
|
|
||||||
//export ImportOnboardingAccount
|
|
||||||
func ImportOnboardingAccount(id, password *C.char) *C.char {
|
|
||||||
info, mnemonic, err := statusBackend.AccountManager().ImportOnboardingAccount(C.GoString(id), C.GoString(password))
|
|
||||||
|
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := AccountInfo{
|
|
||||||
Address: info.WalletAddress,
|
|
||||||
PubKey: info.WalletPubKey,
|
|
||||||
WalletAddress: info.WalletAddress,
|
|
||||||
WalletPubKey: info.WalletPubKey,
|
|
||||||
ChatAddress: info.ChatAddress,
|
|
||||||
ChatPubKey: info.ChatPubKey,
|
|
||||||
Mnemonic: mnemonic,
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(out)
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveOnboarding resets the current onboarding removing from memory all the generated keys.
|
|
||||||
//export RemoveOnboarding
|
|
||||||
func RemoveOnboarding() {
|
|
||||||
statusBackend.AccountManager().RemoveOnboarding()
|
|
||||||
}
|
|
||||||
|
|
||||||
//VerifyAccountPassword verifies account password
|
|
||||||
//export VerifyAccountPassword
|
|
||||||
func VerifyAccountPassword(keyStoreDir, address, password *C.char) *C.char {
|
|
||||||
_, err := statusBackend.AccountManager().VerifyAccountPassword(C.GoString(keyStoreDir), C.GoString(address), C.GoString(password))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//StartNode - start Status node
|
|
||||||
//export StartNode
|
|
||||||
func StartNode(configJSON *C.char) *C.char {
|
|
||||||
config, err := params.NewConfigFromJSON(C.GoString(configJSON))
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := logutils.OverrideRootLogWithConfig(config, false); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
api.RunAsync(func() error { return statusBackend.StartNode(config) })
|
|
||||||
return makeJSONResponse(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//StopNode - stop status node
|
|
||||||
//export StopNode
|
|
||||||
func StopNode() *C.char {
|
|
||||||
api.RunAsync(statusBackend.StopNode)
|
|
||||||
return makeJSONResponse(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Login loads a key file (for a given address), tries to decrypt it using the password, to verify ownership
|
|
||||||
// if verified, purges all the previous identities from Whisper, and injects verified key as shh identity
|
|
||||||
//export Login
|
|
||||||
func Login(accountData, password *C.char) *C.char {
|
|
||||||
data, pass := C.GoString(accountData), C.GoString(password)
|
|
||||||
var account multiaccounts.Account
|
|
||||||
err := json.Unmarshal([]byte(data), &account)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
api.RunAsync(func() error { return statusBackend.StartNodeWithAccount(account, pass) })
|
|
||||||
return makeJSONResponse(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveAccountAndLogin saves account in status-go database..
|
|
||||||
//export SaveAccountAndLogin
|
|
||||||
func SaveAccountAndLogin(accountData, password, settingsJSON, configJSON, subaccountData *C.char) *C.char {
|
|
||||||
data, setJSON, confJSON, subData := C.GoString(accountData), C.GoString(settingsJSON), C.GoString(configJSON), C.GoString(subaccountData)
|
|
||||||
var account multiaccounts.Account
|
|
||||||
err := json.Unmarshal([]byte(data), &account)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
var settings accounts.Settings
|
|
||||||
err = json.Unmarshal([]byte(setJSON), &settings)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
var conf params.NodeConfig
|
|
||||||
err = json.Unmarshal([]byte(confJSON), &conf)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
var subaccs []accounts.Account
|
|
||||||
err = json.Unmarshal([]byte(subData), &subaccs)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
err = statusBackend.StartNodeWithAccountAndConfig(account, C.GoString(password), settings, &conf, subaccs)
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitKeystore initialize keystore before doing any operations with keys.
|
|
||||||
//export InitKeystore
|
|
||||||
func InitKeystore(keydir *C.char) *C.char {
|
|
||||||
err := statusBackend.AccountManager().InitKeystore(C.GoString(keydir))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginWithKeycard initializes an account with a chat key and encryption key used for PFS.
|
|
||||||
// It purges all the previous identities from Whisper, and injects the key as shh identity.
|
|
||||||
//export LoginWithKeycard
|
|
||||||
func LoginWithKeycard(chatKeyData, encryptionKeyData *C.char) *C.char {
|
|
||||||
err := statusBackend.InjectChatAccount(C.GoString(chatKeyData), C.GoString(encryptionKeyData))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Logout is equivalent to clearing whisper identities
|
|
||||||
//export Logout
|
|
||||||
func Logout() *C.char {
|
|
||||||
err := statusBackend.Logout()
|
|
||||||
if err != nil {
|
|
||||||
makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
return makeJSONResponse(statusBackend.StopNode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignMessage unmarshals rpc params {data, address, password} and passes
|
|
||||||
// them onto backend.SignMessage
|
|
||||||
//export SignMessage
|
|
||||||
func SignMessage(rpcParams *C.char) *C.char {
|
|
||||||
var params personal.SignParams
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
result, err := statusBackend.SignMessage(params)
|
|
||||||
return C.CString(prepareJSONResponse(result.String(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recover unmarshals rpc params {signDataString, signedData} and passes
|
|
||||||
// them onto backend.
|
|
||||||
//export Recover
|
|
||||||
func Recover(rpcParams *C.char) *C.char {
|
|
||||||
var params personal.RecoverParams
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(rpcParams)), ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
addr, err := statusBackend.Recover(params)
|
|
||||||
return C.CString(prepareJSONResponse(addr.String(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransaction converts RPC args and calls backend.SendTransaction
|
|
||||||
//export SendTransaction
|
|
||||||
func SendTransaction(txArgsJSON, password *C.char) *C.char {
|
|
||||||
var params transactions.SendTxArgs
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
hash, err := statusBackend.SendTransaction(params, C.GoString(password))
|
|
||||||
code := codeUnknown
|
|
||||||
if c, ok := errToCodeMap[err]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendTransactionWithSignature converts RPC args and calls backend.SendTransactionWithSignature
|
|
||||||
//export SendTransactionWithSignature
|
|
||||||
func SendTransactionWithSignature(txArgsJSON, sigString *C.char) *C.char {
|
|
||||||
var params transactions.SendTxArgs
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, err := hex.DecodeString(C.GoString(sigString))
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := statusBackend.SendTransactionWithSignature(params, sig)
|
|
||||||
code := codeUnknown
|
|
||||||
if c, ok := errToCodeMap[err]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
return C.CString(prepareJSONResponseWithCode(hash.String(), err, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashTransaction validate the transaction and returns new txArgs and the transaction hash.
|
|
||||||
//export HashTransaction
|
|
||||||
func HashTransaction(txArgsJSON *C.char) *C.char {
|
|
||||||
var params transactions.SendTxArgs
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(txArgsJSON)), ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
|
|
||||||
newTxArgs, hash, err := statusBackend.HashTransaction(params)
|
|
||||||
code := codeUnknown
|
|
||||||
if c, ok := errToCodeMap[err]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
|
|
||||||
result := struct {
|
|
||||||
Transaction transactions.SendTxArgs `json:"transaction"`
|
|
||||||
Hash types.Hash `json:"hash"`
|
|
||||||
}{
|
|
||||||
Transaction: newTxArgs,
|
|
||||||
Hash: hash,
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(prepareJSONResponseWithCode(result, err, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashMessage calculates the hash of a message to be safely signed by the keycard
|
|
||||||
// The hash is calulcated as
|
|
||||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
|
||||||
// This gives context to the signed message and prevents signing of transactions.
|
|
||||||
//export HashMessage
|
|
||||||
func HashMessage(message *C.char) *C.char {
|
|
||||||
hash, err := api.HashMessage(C.GoString(message))
|
|
||||||
code := codeUnknown
|
|
||||||
if c, ok := errToCodeMap[err]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
return C.CString(prepareJSONResponseWithCode(fmt.Sprintf("0x%x", hash), err, code))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignTypedData unmarshall data into TypedData, validate it and signs with selected account,
|
|
||||||
// if password matches selected account.
|
|
||||||
//export SignTypedData
|
|
||||||
func SignTypedData(data, address, password *C.char) *C.char {
|
|
||||||
var typed typeddata.TypedData
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(data)), &typed)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
if err := typed.Validate(); err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
result, err := statusBackend.SignTypedData(typed, C.GoString(address), C.GoString(password))
|
|
||||||
return C.CString(prepareJSONResponse(result.String(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashTypedData unmarshalls data into TypedData, validates it and hashes it.
|
|
||||||
//export HashTypedData
|
|
||||||
func HashTypedData(data *C.char) *C.char {
|
|
||||||
var typed typeddata.TypedData
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(data)), &typed)
|
|
||||||
if err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
if err := typed.Validate(); err != nil {
|
|
||||||
return C.CString(prepareJSONResponseWithCode(nil, err, codeFailedParseParams))
|
|
||||||
}
|
|
||||||
result, err := statusBackend.HashTypedData(typed)
|
|
||||||
return C.CString(prepareJSONResponse(result.String(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
//StartCPUProfile runs pprof for cpu
|
|
||||||
//export StartCPUProfile
|
|
||||||
func StartCPUProfile(dataDir *C.char) *C.char {
|
|
||||||
err := profiling.StartCPUProfile(C.GoString(dataDir))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//StopCPUProfiling stops pprof for cpu
|
|
||||||
//export StopCPUProfiling
|
|
||||||
func StopCPUProfiling() *C.char { //nolint: deadcode
|
|
||||||
err := profiling.StopCPUProfile()
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//WriteHeapProfile starts pprof for heap
|
|
||||||
//export WriteHeapProfile
|
|
||||||
func WriteHeapProfile(dataDir *C.char) *C.char { //nolint: deadcode
|
|
||||||
err := profiling.WriteHeapFile(C.GoString(dataDir))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeJSONResponse(err error) *C.char {
|
|
||||||
errString := ""
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "makeJSONResponse with an error: %v\n", err)
|
|
||||||
errString = err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := APIResponse{
|
|
||||||
Error: errString,
|
|
||||||
}
|
|
||||||
outBytes, _ := json.Marshal(out)
|
|
||||||
|
|
||||||
return C.CString(string(outBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddPeer adds an enode as a peer.
|
|
||||||
//export AddPeer
|
|
||||||
func AddPeer(enode *C.char) *C.char {
|
|
||||||
err := statusBackend.StatusNode().AddPeer(C.GoString(enode))
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectionChange handles network state changes as reported
|
|
||||||
// by ReactNative (see https://facebook.github.io/react-native/docs/netinfo.html)
|
|
||||||
//export ConnectionChange
|
|
||||||
func ConnectionChange(typ *C.char, expensive C.int) {
|
|
||||||
statusBackend.ConnectionChange(C.GoString(typ), expensive == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppStateChange handles app state changes (background/foreground).
|
|
||||||
//export AppStateChange
|
|
||||||
func AppStateChange(state *C.char) {
|
|
||||||
statusBackend.AppStateChange(C.GoString(state))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSignalEventCallback setup geth callback to notify about new signal
|
|
||||||
//export SetSignalEventCallback
|
|
||||||
func SetSignalEventCallback(cb unsafe.Pointer) {
|
|
||||||
signal.SetSignalEventCallback(cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportNodeLogs reads current node log and returns content to a caller.
|
|
||||||
//export ExportNodeLogs
|
|
||||||
func ExportNodeLogs() *C.char {
|
|
||||||
node := statusBackend.StatusNode()
|
|
||||||
if node == nil {
|
|
||||||
return makeJSONResponse(errors.New("node is not running"))
|
|
||||||
}
|
|
||||||
config := node.Config()
|
|
||||||
if config == nil {
|
|
||||||
return makeJSONResponse(errors.New("config and log file are not available"))
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(exportlogs.ExportFromBaseFile(config.LogFile))
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(fmt.Errorf("error marshalling to json: %v", err))
|
|
||||||
}
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChaosModeUpdate changes the URL of the upstream RPC client.
|
|
||||||
//export ChaosModeUpdate
|
|
||||||
func ChaosModeUpdate(on C.int) *C.char {
|
|
||||||
node := statusBackend.StatusNode()
|
|
||||||
if node == nil {
|
|
||||||
return makeJSONResponse(errors.New("node is not running"))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := node.ChaosModeCheckRPCClientsUpstreamURL(on == 1)
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNodesFromContract returns a list of nodes from a contract
|
|
||||||
//export GetNodesFromContract
|
|
||||||
func GetNodesFromContract(rpcEndpoint *C.char, contractAddress *C.char) *C.char {
|
|
||||||
nodes, err := statusBackend.GetNodesFromContract(
|
|
||||||
C.GoString(rpcEndpoint),
|
|
||||||
C.GoString(contractAddress),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(struct {
|
|
||||||
Nodes []string `json:"result"`
|
|
||||||
}{Nodes: nodes})
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHash exposes vanilla ECDSA signing required for Swarm messages
|
|
||||||
//export SignHash
|
|
||||||
func SignHash(hexEncodedHash *C.char) *C.char {
|
|
||||||
hexEncodedSignature, err := statusBackend.SignHash(
|
|
||||||
C.GoString(hexEncodedHash),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(hexEncodedSignature)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateAlias returns a 3 random name words given the pk string 0x prefixed
|
|
||||||
//export GenerateAlias
|
|
||||||
func GenerateAlias(pk string) *C.char {
|
|
||||||
// We ignore any error, empty string is considered an error
|
|
||||||
name, _ := protocol.GenerateAlias(pk)
|
|
||||||
return C.CString(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identicon returns the base64 identicon
|
|
||||||
//export Identicon
|
|
||||||
func Identicon(pk string) *C.char {
|
|
||||||
// We ignore any error, empty string is considered an error
|
|
||||||
identicon, _ := protocol.Identicon(pk)
|
|
||||||
return C.CString(identicon)
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
// +build e2e_test
|
|
||||||
|
|
||||||
// Tests in `./lib` package will run only when `e2e_test` build tag is provided.
|
|
||||||
// It's required to prevent some files from being included in the binary.
|
|
||||||
// Check out `lib/utils.go` for more details.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
Init()
|
|
||||||
|
|
||||||
testChainDir = filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()])
|
|
||||||
keystoreDir = filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()], "keystore")
|
|
||||||
|
|
||||||
nodeConfigJSON = `{
|
|
||||||
"NetworkId": ` + strconv.Itoa(GetNetworkID()) + `,
|
|
||||||
"DataDir": "` + testChainDir + `",
|
|
||||||
"KeyStoreDir": "` + filepath.Join(testChainDir, "keystore") + `",
|
|
||||||
"HTTPPort": ` + strconv.Itoa(TestConfig.Node.HTTPPort) + `,
|
|
||||||
"LogLevel": "INFO",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"APIModules": "web3,eth",
|
|
||||||
"LightEthConfig": {
|
|
||||||
"Enabled": true
|
|
||||||
},
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"DataDir": "` + path.Join(testChainDir, "wnode") + `",
|
|
||||||
"EnableNTPSync": false
|
|
||||||
},
|
|
||||||
"ShhextConfig": {
|
|
||||||
"BackupDisabledDataDir": "` + testChainDir + `"
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
// the actual test functions are in non-_test.go files (so that they can use cgo i.e. import "C")
|
|
||||||
// the only intent of these wrappers is for gotest can find what tests are exposed.
|
|
||||||
func TestExportedAPI(t *testing.T) {
|
|
||||||
testExportedAPI(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateNodeConfig(t *testing.T) {
|
|
||||||
noErrorsCallback := func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
assert.Empty(t, resp.FieldErrors)
|
|
||||||
assert.Empty(t, resp.Message)
|
|
||||||
require.True(t, resp.Status, "expected status equal true")
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
Name string
|
|
||||||
Config string
|
|
||||||
Callback func(*testing.T, APIDetailedResponse)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "response for valid config",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"KeyStoreDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"MailServerPassword": "status-offline-inbox"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: noErrorsCallback,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for invalid JSON string",
|
|
||||||
Config: `{"Network": }`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Contains(t, resp.Message, "validation: invalid character '}'")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config missing DataDir",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 3,
|
|
||||||
"KeyStoreDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": false
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Equal(t, 1, len(resp.FieldErrors))
|
|
||||||
require.Equal(t, resp.FieldErrors[0].Parameter, "NodeConfig.DataDir")
|
|
||||||
require.Contains(t, resp.Message, "validation: validation failed")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config missing BackupDisabledDataDir",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 3,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"KeyStoreDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": false
|
|
||||||
},
|
|
||||||
"ShhextConfig": {
|
|
||||||
"PFSEnabled": true
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Contains(t, resp.Message, "validation: field BackupDisabledDataDir is required if PFSEnabled is true")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config missing KeyStoreDir",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 3,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": false
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Equal(t, 1, len(resp.FieldErrors))
|
|
||||||
require.Equal(t, resp.FieldErrors[0].Parameter, "NodeConfig.KeyStoreDir")
|
|
||||||
require.Contains(t, resp.Message, "validation: validation failed")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config missing WhisperConfig.DataDir",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 3,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"KeyStoreDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Empty(t, resp.FieldErrors)
|
|
||||||
require.Contains(t, resp.Message, "WhisperConfig.DataDir must be specified when WhisperConfig.EnableMailServer is true")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config missing WhisperConfig.DataDir with WhisperConfig.EnableMailServer set to false",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 3,
|
|
||||||
"DataDir": "/tmp",
|
|
||||||
"KeyStoreDir": "/tmp",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": false
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Callback: noErrorsCallback,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "response for config with multiple errors",
|
|
||||||
Config: `{}`,
|
|
||||||
Callback: func(t *testing.T, resp APIDetailedResponse) {
|
|
||||||
required := map[string]string{
|
|
||||||
"NodeConfig.NetworkID": "required",
|
|
||||||
"NodeConfig.DataDir": "required",
|
|
||||||
"NodeConfig.KeyStoreDir": "required",
|
|
||||||
}
|
|
||||||
|
|
||||||
require.False(t, resp.Status)
|
|
||||||
require.Contains(t, resp.Message, "validation: validation failed")
|
|
||||||
require.Equal(t, 3, len(resp.FieldErrors))
|
|
||||||
|
|
||||||
for _, err := range resp.FieldErrors {
|
|
||||||
require.Contains(t, required, err.Parameter, err.Error())
|
|
||||||
require.Contains(t, err.Error(), required[err.Parameter])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
|
||||||
testValidateNodeConfig(t, tc.Config, tc.Callback)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,361 +0,0 @@
|
||||||
// +build e2e_test
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"C"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
mobile "github.com/status-im/status-go/mobile"
|
|
||||||
)
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/account/generator"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkMultiAccountErrorResponse(t *testing.T, respJSON *C.char, expectedError string) {
|
|
||||||
var e struct {
|
|
||||||
Error *string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(respJSON)), &e); err != nil {
|
|
||||||
t.Fatalf("error unmarshaling error response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Error == nil {
|
|
||||||
t.Fatalf("unexpected empty error. expected %s, got nil", expectedError)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *e.Error != expectedError {
|
|
||||||
t.Fatalf("unexpected error. expected %s, got %+v", expectedError, *e.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMultiAccountResponse(t *testing.T, respJSON *C.char, resp interface{}) {
|
|
||||||
var e struct {
|
|
||||||
Error *string `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
json.Unmarshal([]byte(C.GoString(respJSON)), &e)
|
|
||||||
if e.Error != nil {
|
|
||||||
t.Errorf("unexpected response error: %s", *e.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(respJSON)), resp); err != nil {
|
|
||||||
t.Fatalf("error unmarshaling response to expected struct: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountGenerateDeriveStoreLoadReset(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
params := C.CString(`{
|
|
||||||
"n": 2,
|
|
||||||
"mnemonicPhraseLength": 24,
|
|
||||||
"bip39Passphrase": ""
|
|
||||||
}`)
|
|
||||||
|
|
||||||
// generate 2 random accounts
|
|
||||||
rawResp := MultiAccountGenerate(params)
|
|
||||||
var generateResp []generator.GeneratedAccountInfo
|
|
||||||
// check there's no error in the response
|
|
||||||
checkMultiAccountResponse(t, rawResp, &generateResp)
|
|
||||||
if len(generateResp) != 2 {
|
|
||||||
t.Errorf("expected 2 accounts created, got %d", len(generateResp))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
bip44DerivationPath := "m/44'/60'/0'/0/0"
|
|
||||||
eip1581DerivationPath := "m/43'/60'/1581'/0'/0"
|
|
||||||
paths := []string{bip44DerivationPath, eip1581DerivationPath}
|
|
||||||
|
|
||||||
// derive 2 child accounts for each account without storing them
|
|
||||||
for i := 0; i < len(generateResp); i++ {
|
|
||||||
info := generateResp[i]
|
|
||||||
mnemonicLength := len(strings.Split(info.Mnemonic, " "))
|
|
||||||
|
|
||||||
if mnemonicLength != 24 {
|
|
||||||
t.Errorf("expected mnemonic to have 24 words, got %d", mnemonicLength)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := testMultiAccountDeriveAddresses(t, info.ID, paths, false); !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
password := "multi-account-test-password"
|
|
||||||
|
|
||||||
// store 2 derived child accounts from the first account.
|
|
||||||
// after that all the generated account should be remove from memory.
|
|
||||||
addresses, ok := testMultiAccountStoreDerived(t, generateResp[0].ID, password, paths)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedIDs := make([]string, 0)
|
|
||||||
|
|
||||||
// unlock and load all stored accounts.
|
|
||||||
for _, address := range addresses {
|
|
||||||
loadedID, ok := testMultiAccountLoadAccount(t, address, password)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
loadedIDs = append(loadedIDs, loadedID)
|
|
||||||
|
|
||||||
if _, ok := testMultiAccountDeriveAddresses(t, loadedID, paths, false); !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rawResp = MultiAccountReset()
|
|
||||||
// try again deriving addresses.
|
|
||||||
// it should fail because reset should remove all the accounts from memory.
|
|
||||||
for _, loadedID := range loadedIDs {
|
|
||||||
if _, ok := testMultiAccountDeriveAddresses(t, loadedID, paths, true); !ok {
|
|
||||||
t.Errorf("account is still in memory, expected Reset to remove all accounts")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountImportMnemonicAndDerive(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
mnemonicPhrase := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
||||||
bip39Passphrase := "TREZOR"
|
|
||||||
params := mobile.MultiAccountImportMnemonicParams{
|
|
||||||
MnemonicPhrase: mnemonicPhrase,
|
|
||||||
Bip39Passphrase: bip39Passphrase,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(¶ms)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountImportMnemonicParams")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// import mnemonic
|
|
||||||
rawResp := MultiAccountImportMnemonic(C.CString(string(paramsJSON)))
|
|
||||||
var importResp generator.IdentifiedAccountInfo
|
|
||||||
// check the response doesn't have errors
|
|
||||||
checkMultiAccountResponse(t, rawResp, &importResp)
|
|
||||||
|
|
||||||
bip44DerivationPath := "m/44'/60'/0'/0/0"
|
|
||||||
expectedBip44Address := "0x9c32F71D4DB8Fb9e1A58B0a80dF79935e7256FA6"
|
|
||||||
addresses, ok := testMultiAccountDeriveAddresses(t, importResp.ID, []string{bip44DerivationPath}, false)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if addresses[bip44DerivationPath] != expectedBip44Address {
|
|
||||||
t.Errorf("unexpected address; expected %s, got %s", expectedBip44Address, addresses[bip44DerivationPath])
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountDeriveAddresses(t *testing.T, accountID string, paths []string, expectAccountNotFoundError bool) (map[string]string, bool) { //nolint: gocyclo
|
|
||||||
params := mobile.MultiAccountDeriveAddressesParams{
|
|
||||||
AccountID: accountID,
|
|
||||||
Paths: paths,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(¶ms)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountDeriveAddressesParams")
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// derive addresses from account accountID
|
|
||||||
rawResp := MultiAccountDeriveAddresses(C.CString(string(paramsJSON)))
|
|
||||||
|
|
||||||
if expectAccountNotFoundError {
|
|
||||||
checkMultiAccountErrorResponse(t, rawResp, "account not found")
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
var deriveResp map[string]generator.AccountInfo
|
|
||||||
// check the response doesn't have errors
|
|
||||||
checkMultiAccountResponse(t, rawResp, &deriveResp)
|
|
||||||
if len(deriveResp) != len(paths) {
|
|
||||||
t.Errorf("expected %d derived accounts info, got %d", len(paths), len(deriveResp))
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses := make(map[string]string)
|
|
||||||
|
|
||||||
// check that we have an address for each derivation path we used.
|
|
||||||
for _, path := range paths {
|
|
||||||
info, ok := deriveResp[path]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("results doesn't contain account info for path %s", path)
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses[path] = info.Address
|
|
||||||
}
|
|
||||||
return addresses, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountStoreDerived(t *testing.T, accountID string, password string, paths []string) ([]string, bool) { //nolint: gocyclo
|
|
||||||
|
|
||||||
params := mobile.MultiAccountStoreDerivedAccountsParams{
|
|
||||||
MultiAccountDeriveAddressesParams: mobile.MultiAccountDeriveAddressesParams{
|
|
||||||
AccountID: accountID,
|
|
||||||
Paths: paths,
|
|
||||||
},
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountStoreDerivedParams")
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// store one child account for each derivation path.
|
|
||||||
rawResp := MultiAccountStoreDerivedAccounts(C.CString(string(paramsJSON)))
|
|
||||||
var storeResp map[string]generator.AccountInfo
|
|
||||||
|
|
||||||
// check that we don't have errors in the response
|
|
||||||
checkMultiAccountResponse(t, rawResp, &storeResp)
|
|
||||||
addresses := make([]string, 0)
|
|
||||||
for _, info := range storeResp {
|
|
||||||
addresses = append(addresses, info.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(addresses) != 2 {
|
|
||||||
t.Errorf("expected 2 addresses, got %d", len(addresses))
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// for each stored account, check that we can decrypt it with the password we used.
|
|
||||||
// FIXME pass it somehow
|
|
||||||
dir := keystoreDir
|
|
||||||
for _, address := range addresses {
|
|
||||||
_, err = statusBackend.AccountManager().VerifyAccountPassword(dir, address, password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to verify password on stored derived account")
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountGenerateAndDerive(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
paths := []string{"m/0", "m/1"}
|
|
||||||
params := mobile.MultiAccountGenerateAndDeriveAddressesParams{
|
|
||||||
MultiAccountGenerateParams: mobile.MultiAccountGenerateParams{
|
|
||||||
N: 2,
|
|
||||||
MnemonicPhraseLength: 12,
|
|
||||||
},
|
|
||||||
Paths: paths,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(¶ms)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountGenerateAndDeriveParams")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate 2 random accounts and derive 2 accounts from each one.
|
|
||||||
rawResp := MultiAccountGenerateAndDeriveAddresses(C.CString(string(paramsJSON)))
|
|
||||||
var generateResp []generator.GeneratedAndDerivedAccountInfo
|
|
||||||
// check there's no error in the response
|
|
||||||
checkMultiAccountResponse(t, rawResp, &generateResp)
|
|
||||||
if len(generateResp) != 2 {
|
|
||||||
t.Errorf("expected 2 accounts created, got %d", len(generateResp))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that for each account we have the 2 derived addresses
|
|
||||||
for _, info := range generateResp {
|
|
||||||
for _, path := range paths {
|
|
||||||
if _, ok := info.Derived[path]; !ok {
|
|
||||||
t.Errorf("results doesn't contain account info for path %s", path)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountImportStore(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
key, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed generating key")
|
|
||||||
}
|
|
||||||
|
|
||||||
hex := fmt.Sprintf("%#x", crypto.FromECDSA(key))
|
|
||||||
importParams := mobile.MultiAccountImportPrivateKeyParams{
|
|
||||||
PrivateKey: hex,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(&importParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountImportPrivateKeyParams")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// import raw private key
|
|
||||||
rawResp := MultiAccountImportPrivateKey(C.CString(string(paramsJSON)))
|
|
||||||
var importResp generator.IdentifiedAccountInfo
|
|
||||||
// check the response doesn't have errors
|
|
||||||
checkMultiAccountResponse(t, rawResp, &importResp)
|
|
||||||
|
|
||||||
// prepare StoreAccount params
|
|
||||||
password := "test-multiaccount-imported-key-password"
|
|
||||||
storeParams := mobile.MultiAccountStoreAccountParams{
|
|
||||||
AccountID: importResp.ID,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err = json.Marshal(storeParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountStoreParams")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the imported private key
|
|
||||||
rawResp = MultiAccountStoreAccount(C.CString(string(paramsJSON)))
|
|
||||||
var storeResp generator.AccountInfo
|
|
||||||
// check the response doesn't have errors
|
|
||||||
checkMultiAccountResponse(t, rawResp, &storeResp)
|
|
||||||
|
|
||||||
dir := keystoreDir
|
|
||||||
_, err = statusBackend.AccountManager().VerifyAccountPassword(dir, storeResp.Address, password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to verify password on stored derived account")
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMultiAccountLoadAccount(t *testing.T, address string, password string) (string, bool) { //nolint: gocyclo
|
|
||||||
t.Log("loading account")
|
|
||||||
params := mobile.MultiAccountLoadAccountParams{
|
|
||||||
Address: address,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
paramsJSON, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error encoding MultiAccountLoadAccountParams")
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the account in memory
|
|
||||||
rawResp := MultiAccountLoadAccount(C.CString(string(paramsJSON)))
|
|
||||||
var loadResp generator.IdentifiedAccountInfo
|
|
||||||
|
|
||||||
// check that we don't have errors in the response
|
|
||||||
checkMultiAccountResponse(t, rawResp, &loadResp)
|
|
||||||
|
|
||||||
return loadResp.ID, true
|
|
||||||
}
|
|
|
@ -1,709 +0,0 @@
|
||||||
// +build e2e_test
|
|
||||||
|
|
||||||
// This is a file with e2e tests for C bindings written in library.go.
|
|
||||||
// As a CGO file, it can't have `_test.go` suffix as it's not allowed by Go.
|
|
||||||
// At the same time, we don't want this file to be included in the binaries.
|
|
||||||
// This is why `e2e_test` tag was introduced. Without it, this file is excluded
|
|
||||||
// from the build. Providing this tag will include this file into the build
|
|
||||||
// and that's what is done while running e2e tests for `lib/` package.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"C"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/account"
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/keystore"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
||||||
"github.com/status-im/status-go/signal"
|
|
||||||
. "github.com/status-im/status-go/t/utils" //nolint: golint
|
|
||||||
"github.com/status-im/status-go/transactions"
|
|
||||||
)
|
|
||||||
import "github.com/status-im/status-go/params"
|
|
||||||
|
|
||||||
var (
|
|
||||||
testChainDir string
|
|
||||||
keystoreDir string
|
|
||||||
nodeConfigJSON string
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildAccountData(name, chatAddress string) *C.char {
|
|
||||||
return C.CString(fmt.Sprintf(`{
|
|
||||||
"name": "%s",
|
|
||||||
"key-uid": "%s"
|
|
||||||
}`, name, chatAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAccountSettings(name string) *C.char {
|
|
||||||
return C.CString(fmt.Sprintf(`{
|
|
||||||
"address": "0xdC540f3745Ff2964AFC1171a5A0DD726d1F6B472",
|
|
||||||
"current-network": "mainnet_rpc",
|
|
||||||
"dapps-address": "0xD1300f99fDF7346986CbC766903245087394ecd0",
|
|
||||||
"eip1581-address": "0xB1DDDE9235a541d1344550d969715CF43982de9f",
|
|
||||||
"installation-id": "d3efcff6-cffa-560e-a547-21d3858cbc51",
|
|
||||||
"key-uid": "0x4e8129f3edfc004875be17bf468a784098a9f69b53c095be1f52deff286935ab",
|
|
||||||
"last-derived-path": 0,
|
|
||||||
"name": "%s",
|
|
||||||
"networks/networks": {},
|
|
||||||
"photo-path": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==",
|
|
||||||
"preview-privacy": false,
|
|
||||||
"public-key": "0x04211fe0f69772ecf7eb0b5bfc7678672508a9fb01f2d699096f0d59ef7fe1a0cb1e648a80190db1c0f5f088872444d846f2956d0bd84069f3f9f69335af852ac0",
|
|
||||||
"signing-phrase": "yurt joey vibe",
|
|
||||||
"wallet-root-address": "0x3B591fd819F86D0A6a2EF2Bcb94f77807a7De1a6"
|
|
||||||
}`, name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildSubAccountData(chatAddress string) *C.char {
|
|
||||||
accs := []accounts.Account{
|
|
||||||
{
|
|
||||||
Wallet: true,
|
|
||||||
Chat: true,
|
|
||||||
Address: types.HexToAddress(chatAddress),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
data, _ := json.Marshal(accs)
|
|
||||||
return C.CString(string(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitSignal(feed *event.Feed, event string, timeout time.Duration) error {
|
|
||||||
events := make(chan signal.Envelope)
|
|
||||||
sub := feed.Subscribe(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
after := time.After(timeout)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case envelope := <-events:
|
|
||||||
if envelope.Type == event {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case <-after:
|
|
||||||
return fmt.Errorf("signal %v wasn't received in %v", event, timeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAccountAndLogin(t *testing.T, feed *event.Feed) account.Info {
|
|
||||||
account1, _, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Logf("account created: {address: %s, key: %s}", account1.WalletAddress, account1.WalletPubKey)
|
|
||||||
|
|
||||||
nodeConfig, _ := params.NewConfigFromJSON(nodeConfigJSON)
|
|
||||||
nodeConfig.KeyStoreDir = "keystore"
|
|
||||||
nodeConfig.DataDir = "/"
|
|
||||||
cnf, err := json.Marshal(nodeConfig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
signalErrC := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
signalErrC <- waitSignal(feed, signal.EventLoggedIn, 10*time.Second)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// SaveAccountAndLogin must be called only once when an account is created.
|
|
||||||
// If the account already exists, Login should be used.
|
|
||||||
rawResponse := SaveAccountAndLogin(
|
|
||||||
buildAccountData("test", account1.WalletAddress),
|
|
||||||
C.CString(TestConfig.Account1.Password),
|
|
||||||
buildAccountSettings("test"),
|
|
||||||
C.CString(string(cnf)),
|
|
||||||
buildSubAccountData(account1.WalletAddress),
|
|
||||||
)
|
|
||||||
var loginResponse APIResponse
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse))
|
|
||||||
require.Empty(t, loginResponse.Error)
|
|
||||||
require.NoError(t, <-signalErrC)
|
|
||||||
return account1
|
|
||||||
}
|
|
||||||
|
|
||||||
func loginUsingAccount(t *testing.T, feed *event.Feed, addr string) {
|
|
||||||
signalErrC := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
signalErrC <- waitSignal(feed, signal.EventLoggedIn, 5*time.Second)
|
|
||||||
}()
|
|
||||||
|
|
||||||
nodeConfig, _ := params.NewConfigFromJSON(nodeConfigJSON)
|
|
||||||
nodeConfig.KeyStoreDir = "keystore"
|
|
||||||
nodeConfig.DataDir = "/"
|
|
||||||
cnf, _ := json.Marshal(nodeConfig)
|
|
||||||
|
|
||||||
// SaveAccountAndLogin must be called only once when an account is created.
|
|
||||||
// If the account already exists, Login should be used.
|
|
||||||
rawResponse := SaveAccountAndLogin(
|
|
||||||
buildAccountData("test", addr),
|
|
||||||
C.CString(TestConfig.Account1.Password),
|
|
||||||
buildAccountSettings("test"),
|
|
||||||
C.CString(string(cnf)),
|
|
||||||
buildSubAccountData(addr),
|
|
||||||
)
|
|
||||||
var loginResponse APIResponse
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse))
|
|
||||||
require.Empty(t, loginResponse.Error)
|
|
||||||
require.NoError(t, <-signalErrC)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint: deadcode
|
|
||||||
func testExportedAPI(t *testing.T) {
|
|
||||||
// All of that is done because usage of cgo is not supported in tests.
|
|
||||||
// Probably, there should be a cleaner way, for example, test cgo bindings in e2e tests
|
|
||||||
// separately from other internal tests.
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fn func(t *testing.T, feed *event.Feed) bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"StopResumeNode",
|
|
||||||
testStopResumeNode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RPCInProc",
|
|
||||||
testCallRPC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RPCPrivateAPI",
|
|
||||||
testCallRPCWithPrivateAPI,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RPCPrivateClient",
|
|
||||||
testCallPrivateRPCWithPrivateAPI,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"VerifyAccountPassword",
|
|
||||||
testVerifyAccountPassword,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"RecoverAccount",
|
|
||||||
testRecoverAccount,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"LoginKeycard",
|
|
||||||
testLoginWithKeycard,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AccountLogout",
|
|
||||||
testAccountLogout,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SendTransactionWithLogin",
|
|
||||||
testSendTransactionWithLogin,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SendTransactionInvalidPassword",
|
|
||||||
testSendTransactionInvalidPassword,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SendTransactionFailed",
|
|
||||||
testFailedTransaction,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MultiAccount/Generate/Derive/StoreDerived/Load/Reset",
|
|
||||||
testMultiAccountGenerateDeriveStoreLoadReset,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MultiAccount/ImportMnemonic/Derive",
|
|
||||||
testMultiAccountImportMnemonicAndDerive,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MultiAccount/GenerateAndDerive",
|
|
||||||
testMultiAccountGenerateAndDerive,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"MultiAccount/Import/Store",
|
|
||||||
testMultiAccountImportStore,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
testDir := filepath.Join(TestDataDir, TestNetworkNames[GetNetworkID()])
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := os.MkdirAll(testDir, os.ModePerm)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testKeyDir := filepath.Join(testDir, "keystore")
|
|
||||||
require.NoError(t, ImportTestAccount(testKeyDir, GetAccount1PKFile()))
|
|
||||||
require.NoError(t, ImportTestAccount(testKeyDir, GetAccount2PKFile()))
|
|
||||||
|
|
||||||
// Inject test accounts.
|
|
||||||
response := InitKeystore(C.CString(testKeyDir))
|
|
||||||
if C.GoString(response) != `{"error":""}` {
|
|
||||||
t.Fatalf("failed to InitKeystore: %v", C.GoString(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the accounts database. It must be called
|
|
||||||
// after the test account got injected.
|
|
||||||
result := OpenAccounts(C.CString(testDir))
|
|
||||||
if C.GoString(response) != `{"error":""}` {
|
|
||||||
t.Fatalf("OpenAccounts() failed: %v", C.GoString(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a custom signals handler so that we can examine them here.
|
|
||||||
feed := &event.Feed{}
|
|
||||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
||||||
var envelope signal.Envelope
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(jsonEvent), &envelope))
|
|
||||||
feed.Send(envelope)
|
|
||||||
})
|
|
||||||
defer func() {
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
errCh <- waitSignal(feed, signal.EventNodeStopped, 5*time.Second)
|
|
||||||
}()
|
|
||||||
if n := statusBackend.StatusNode(); n == nil || !n.IsRunning() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logout()
|
|
||||||
require.NoError(t, <-errCh)
|
|
||||||
}()
|
|
||||||
require.True(t, tc.fn(t, feed))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testVerifyAccountPassword(t *testing.T, feed *event.Feed) bool {
|
|
||||||
tmpDir, err := ioutil.TempDir(os.TempDir(), "accounts")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpDir) // nolint: errcheck
|
|
||||||
|
|
||||||
if err = ImportTestAccount(tmpDir, GetAccount1PKFile()); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err = ImportTestAccount(tmpDir, GetAccount2PKFile()); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// rename account file (to see that file's internals reviewed, when locating account key)
|
|
||||||
accountFilePathOriginal := filepath.Join(tmpDir, GetAccount1PKFile())
|
|
||||||
accountFilePath := filepath.Join(tmpDir, "foo"+TestConfig.Account1.WalletAddress+"bar.pk")
|
|
||||||
if err := os.Rename(accountFilePathOriginal, accountFilePath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
response := APIResponse{}
|
|
||||||
rawResponse := VerifyAccountPassword(
|
|
||||||
C.CString(tmpDir),
|
|
||||||
C.CString(TestConfig.Account1.WalletAddress),
|
|
||||||
C.CString(TestConfig.Account1.Password))
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &response); err != nil {
|
|
||||||
t.Errorf("cannot decode response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if response.Error != "" {
|
|
||||||
t.Errorf("unexpected error: %s", response.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testStopResumeNode(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
account1 := createAccountAndLogin(t, feed)
|
|
||||||
whisperService, err := statusBackend.StatusNode().WhisperService()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, whisperService.HasKeyPair(account1.ChatPubKey), "whisper should have keypair")
|
|
||||||
|
|
||||||
response := APIResponse{}
|
|
||||||
rawResponse := StopNode()
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(C.GoString(rawResponse)), &response))
|
|
||||||
require.Empty(t, response.Error)
|
|
||||||
|
|
||||||
require.NoError(t, waitSignal(feed, signal.EventNodeStopped, 3*time.Second))
|
|
||||||
response = APIResponse{}
|
|
||||||
rawResponse = StartNode(C.CString(nodeConfigJSON))
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(C.GoString(rawResponse)), &response))
|
|
||||||
require.Empty(t, response.Error)
|
|
||||||
|
|
||||||
require.NoError(t, waitSignal(feed, signal.EventNodeReady, 5*time.Second))
|
|
||||||
|
|
||||||
// now, verify that we still have account logged in
|
|
||||||
whisperService, err = statusBackend.StatusNode().WhisperService()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, whisperService.HasKeyPair(account1.ChatPubKey))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCallRPC(t *testing.T, feed *event.Feed) bool {
|
|
||||||
createAccountAndLogin(t, feed)
|
|
||||||
expected := `{"jsonrpc":"2.0","id":64,"result":"0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"}`
|
|
||||||
rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`))
|
|
||||||
received := C.GoString(rawResponse)
|
|
||||||
if expected != received {
|
|
||||||
t.Errorf("unexpected response: expected: %v, got: %v", expected, received)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCallRPCWithPrivateAPI(t *testing.T, feed *event.Feed) bool {
|
|
||||||
createAccountAndLogin(t, feed)
|
|
||||||
expected := `{"jsonrpc":"2.0","id":64,"error":{"code":-32601,"message":"the method admin_nodeInfo does not exist/is not available"}}`
|
|
||||||
rawResponse := CallRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`))
|
|
||||||
require.Equal(t, expected, C.GoString(rawResponse))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCallPrivateRPCWithPrivateAPI(t *testing.T, feed *event.Feed) bool {
|
|
||||||
createAccountAndLogin(t, feed)
|
|
||||||
rawResponse := CallPrivateRPC(C.CString(`{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":64}`))
|
|
||||||
received := C.GoString(rawResponse)
|
|
||||||
if strings.Contains(received, "error") {
|
|
||||||
t.Errorf("unexpected response containing error: %v", received)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testRecoverAccount(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
keyStore := statusBackend.AccountManager().GetKeystore()
|
|
||||||
require.NotNil(t, keyStore)
|
|
||||||
// create an account
|
|
||||||
accountInfo, mnemonic, err := statusBackend.AccountManager().CreateAccount(TestConfig.Account1.Password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not create account: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t.Logf("Account created: {address: %s, key: %s, mnemonic:%s}", accountInfo.WalletAddress, accountInfo.WalletPubKey, mnemonic)
|
|
||||||
|
|
||||||
// try recovering using password + mnemonic
|
|
||||||
recoverAccountResponse := AccountInfo{}
|
|
||||||
rawResponse := RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
||||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if recoverAccountResponse.Error != "" {
|
|
||||||
t.Errorf("recover account failed: %v", recoverAccountResponse.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if recoverAccountResponse.Address != recoverAccountResponse.WalletAddress ||
|
|
||||||
recoverAccountResponse.PubKey != recoverAccountResponse.WalletPubKey {
|
|
||||||
t.Error("for backward compatibility pubkey/address should be equal to walletAddress/walletPubKey")
|
|
||||||
}
|
|
||||||
|
|
||||||
walletAddressCheck, walletPubKeyCheck := recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
||||||
chatAddressCheck, chatPubKeyCheck := recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey
|
|
||||||
|
|
||||||
if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck {
|
|
||||||
t.Error("recover wallet account details failed to pull the correct details")
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck {
|
|
||||||
t.Error("recover chat account details failed to pull the correct details")
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test recovering, but make sure that account/key file is removed i.e. simulate recovering on a new device
|
|
||||||
account, err := account.ParseAccountString(accountInfo.WalletAddress)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not get account from address: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
account, key, err := keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
extChild2String := key.ExtendedKey.String()
|
|
||||||
|
|
||||||
if err = keyStore.Delete(account, TestConfig.Account1.Password); err != nil {
|
|
||||||
t.Errorf("cannot remove account: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
recoverAccountResponse = AccountInfo{}
|
|
||||||
rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
||||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if recoverAccountResponse.Error != "" {
|
|
||||||
t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
walletAddressCheck, walletPubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
||||||
if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck {
|
|
||||||
t.Error("recover wallet account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
chatAddressCheck, chatPubKeyCheck = recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey
|
|
||||||
if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck {
|
|
||||||
t.Error("recover chat account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that extended key exists and is imported ok too
|
|
||||||
_, key, err = keyStore.AccountDecryptedKey(account, TestConfig.Account1.Password)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("can not obtain decrypted account key: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if extChild2String != key.ExtendedKey.String() {
|
|
||||||
t.Errorf("CKD#2 key mismatch, expected: %s, got: %s", extChild2String, key.ExtendedKey.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that calling import several times, just returns from cache (no error is expected)
|
|
||||||
recoverAccountResponse = AccountInfo{}
|
|
||||||
rawResponse = RecoverAccount(C.CString(TestConfig.Account1.Password), C.CString(mnemonic))
|
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &recoverAccountResponse); err != nil {
|
|
||||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if recoverAccountResponse.Error != "" {
|
|
||||||
t.Errorf("recover account failed (for non-cached account): %v", recoverAccountResponse.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
walletAddressCheck, walletPubKeyCheck = recoverAccountResponse.Address, recoverAccountResponse.PubKey
|
|
||||||
if accountInfo.WalletAddress != walletAddressCheck || accountInfo.WalletPubKey != walletPubKeyCheck {
|
|
||||||
t.Error("recover wallet account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
chatAddressCheck, chatPubKeyCheck = recoverAccountResponse.ChatAddress, recoverAccountResponse.ChatPubKey
|
|
||||||
if accountInfo.ChatAddress != chatAddressCheck || accountInfo.ChatPubKey != chatPubKeyCheck {
|
|
||||||
t.Error("recover chat account details failed to pull the correct details (for non-cached account)")
|
|
||||||
}
|
|
||||||
|
|
||||||
errC := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
errC <- waitSignal(feed, signal.EventLoggedIn, 5*time.Second)
|
|
||||||
}()
|
|
||||||
rawResponse = SaveAccountAndLogin(buildAccountData("test", walletAddressCheck), C.CString(TestConfig.Account1.Password), buildAccountSettings("test"), C.CString(nodeConfigJSON), buildSubAccountData(walletAddressCheck))
|
|
||||||
loginResponse := APIResponse{}
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse))
|
|
||||||
require.Empty(t, loginResponse.Error)
|
|
||||||
require.NoError(t, <-errC)
|
|
||||||
|
|
||||||
// time to login with recovered data
|
|
||||||
whisperService, err := statusBackend.StatusNode().WhisperService()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !whisperService.HasKeyPair(chatPubKeyCheck) {
|
|
||||||
t.Errorf("identity not injected into whisper: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLoginWithKeycard(t *testing.T, feed *event.Feed) bool { //nolint: gocyclo
|
|
||||||
createAccountAndLogin(t, feed)
|
|
||||||
chatPrivKey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error generating chat key")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
chatPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(chatPrivKey))
|
|
||||||
|
|
||||||
encryptionPrivKey, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error generating encryption key")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
encryptionPrivKeyHex := hex.EncodeToString(crypto.FromECDSA(encryptionPrivKey))
|
|
||||||
|
|
||||||
whisperService, err := statusBackend.StatusNode().WhisperService()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
chatPubKeyHex := types.EncodeHex(crypto.FromECDSAPub(&chatPrivKey.PublicKey))
|
|
||||||
if whisperService.HasKeyPair(chatPubKeyHex) {
|
|
||||||
t.Error("identity already present in whisper")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
loginResponse := APIResponse{}
|
|
||||||
rawResponse := LoginWithKeycard(C.CString(chatPrivKeyHex), C.CString(encryptionPrivKeyHex))
|
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(C.GoString(rawResponse)), &loginResponse); err != nil {
|
|
||||||
t.Errorf("cannot decode LoginWithKeycard response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if loginResponse.Error != "" {
|
|
||||||
t.Errorf("Test failed: could not login with keycard: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !whisperService.HasKeyPair(chatPubKeyHex) {
|
|
||||||
t.Error("identity not present in whisper after logging in with keycard")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAccountLogout(t *testing.T, feed *event.Feed) bool {
|
|
||||||
accountInfo := createAccountAndLogin(t, feed)
|
|
||||||
whisperService, err := statusBackend.StatusNode().WhisperService()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("whisper service not running: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !whisperService.HasKeyPair(accountInfo.ChatPubKey) {
|
|
||||||
t.Error("identity not injected into whisper")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
logoutResponse := APIResponse{}
|
|
||||||
rawResponse := Logout()
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(rawResponse)), &logoutResponse); err != nil {
|
|
||||||
t.Errorf("cannot decode RecoverAccount response (%s): %v", C.GoString(rawResponse), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if logoutResponse.Error != "" {
|
|
||||||
t.Errorf("cannot logout: %v", logoutResponse.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// now, logout and check if identity is removed indeed
|
|
||||||
if whisperService.HasKeyPair(accountInfo.ChatPubKey) {
|
|
||||||
t.Error("identity not cleared from whisper")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonrpcAnyResponse struct {
|
|
||||||
Result json.RawMessage `json:"result"`
|
|
||||||
jsonrpcErrorResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSendTransactionWithLogin(t *testing.T, feed *event.Feed) bool {
|
|
||||||
loginUsingAccount(t, feed, TestConfig.Account1.WalletAddress)
|
|
||||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
|
||||||
|
|
||||||
args, err := json.Marshal(transactions.SendTxArgs{
|
|
||||||
From: account.FromAddress(TestConfig.Account1.WalletAddress),
|
|
||||||
To: account.ToAddress(TestConfig.Account2.WalletAddress),
|
|
||||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to marshal errors: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password))
|
|
||||||
|
|
||||||
var result jsonrpcAnyResponse
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
|
||||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if result.Error.Message != "" {
|
|
||||||
t.Errorf("failed to send transaction: %v", result.Error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
hash := types.BytesToHash(result.Result)
|
|
||||||
if reflect.DeepEqual(hash, types.Hash{}) {
|
|
||||||
t.Errorf("response hash empty: %s", hash.Hex())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSendTransactionInvalidPassword(t *testing.T, feed *event.Feed) bool {
|
|
||||||
acc := createAccountAndLogin(t, feed)
|
|
||||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
|
||||||
|
|
||||||
args, err := json.Marshal(transactions.SendTxArgs{
|
|
||||||
From: types.HexToAddress(acc.WalletAddress),
|
|
||||||
To: account.ToAddress(TestConfig.Account2.WalletAddress),
|
|
||||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to marshal errors: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
rawResult := SendTransaction(C.CString(string(args)), C.CString("invalid password"))
|
|
||||||
|
|
||||||
var result jsonrpcAnyResponse
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
|
||||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if result.Error.Message != keystore.ErrDecrypt.Error() {
|
|
||||||
t.Errorf("invalid result: %q", result)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFailedTransaction(t *testing.T, feed *event.Feed) bool {
|
|
||||||
createAccountAndLogin(t, feed)
|
|
||||||
EnsureNodeSync(statusBackend.StatusNode().EnsureSync)
|
|
||||||
|
|
||||||
args, err := json.Marshal(transactions.SendTxArgs{
|
|
||||||
From: *account.ToAddress(TestConfig.Account1.WalletAddress),
|
|
||||||
To: account.ToAddress(TestConfig.Account2.WalletAddress),
|
|
||||||
Value: (*hexutil.Big)(big.NewInt(1000000000000)),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("failed to marshal errors: %v", err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
rawResult := SendTransaction(C.CString(string(args)), C.CString(TestConfig.Account1.Password))
|
|
||||||
|
|
||||||
var result jsonrpcAnyResponse
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(rawResult)), &result); err != nil {
|
|
||||||
t.Errorf("failed to unmarshal rawResult '%s': %v", C.GoString(rawResult), err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Error.Message != transactions.ErrAccountDoesntExist.Error() {
|
|
||||||
t.Errorf("expected error to be ErrAccountDoesntExist, got %s", result.Error.Message)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Result != nil {
|
|
||||||
t.Errorf("expected result to be nil")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint: deadcode
|
|
||||||
func testValidateNodeConfig(t *testing.T, config string, fn func(*testing.T, APIDetailedResponse)) {
|
|
||||||
result := ValidateNodeConfig(C.CString(config))
|
|
||||||
|
|
||||||
var resp APIDetailedResponse
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(C.GoString(result)), &resp)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fn(t, resp)
|
|
||||||
}
|
|
12
lib/main.go
12
lib/main.go
|
@ -1,12 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/status-im/status-go/api"
|
|
||||||
|
|
||||||
var statusBackend = api.NewGethStatusBackend()
|
|
||||||
|
|
||||||
// Technically this package supposed to be a lib for
|
|
||||||
// cross-compilation and usage with Android/iOS, but
|
|
||||||
// without main it produces cryptic errors.
|
|
||||||
// TODO(divan): investigate the cause of the errors
|
|
||||||
// and change this package to be a library if possible.
|
|
||||||
func main() {}
|
|
|
@ -1,192 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
// #include <stdlib.h>
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
mobile "github.com/status-im/status-go/mobile"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MultiAccountGenerate generates account in memory without storing them.
|
|
||||||
//export MultiAccountGenerate
|
|
||||||
func MultiAccountGenerate(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountGenerateParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().Generate(p.MnemonicPhraseLength, p.N, p.Bip39Passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountGenerateAndDeriveAddresses combines Generate and DeriveAddresses in one call.
|
|
||||||
//export MultiAccountGenerateAndDeriveAddresses
|
|
||||||
func MultiAccountGenerateAndDeriveAddresses(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountGenerateAndDeriveAddressesParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().GenerateAndDeriveAddresses(p.MnemonicPhraseLength, p.N, p.Bip39Passphrase, p.Paths)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountDeriveAddresses derive addresses from an account selected by ID, without storing them.
|
|
||||||
//export MultiAccountDeriveAddresses
|
|
||||||
func MultiAccountDeriveAddresses(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountDeriveAddressesParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().DeriveAddresses(p.AccountID, p.Paths)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountStoreDerivedAccounts derive accounts from the specified key and store them encrypted with the specified password.
|
|
||||||
//export MultiAccountStoreDerivedAccounts
|
|
||||||
func MultiAccountStoreDerivedAccounts(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountStoreDerivedAccountsParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().StoreDerivedAccounts(p.AccountID, p.Password, p.Paths)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountImportPrivateKey imports a raw private key without storing it.
|
|
||||||
//export MultiAccountImportPrivateKey
|
|
||||||
func MultiAccountImportPrivateKey(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountImportPrivateKeyParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().ImportPrivateKey(p.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountImportMnemonic imports an account derived from the mnemonic phrase and the Bip39Passphrase storing it.
|
|
||||||
//export MultiAccountImportMnemonic
|
|
||||||
func MultiAccountImportMnemonic(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountImportMnemonicParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().ImportMnemonic(p.MnemonicPhrase, p.Bip39Passphrase)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountStoreAccount stores the select account.
|
|
||||||
//export MultiAccountStoreAccount
|
|
||||||
func MultiAccountStoreAccount(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountStoreAccountParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().StoreAccount(p.AccountID, p.Password)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountLoadAccount loads in memory the account specified by address unlocking it with password.
|
|
||||||
//export MultiAccountLoadAccount
|
|
||||||
func MultiAccountLoadAccount(paramsJSON *C.char) *C.char {
|
|
||||||
var p mobile.MultiAccountLoadAccountParams
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(C.GoString(paramsJSON)), &p); err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := statusBackend.AccountManager().AccountsGenerator().LoadAccount(p.Address, p.Password)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := json.Marshal(resp)
|
|
||||||
if err != nil {
|
|
||||||
return makeJSONResponse(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return C.CString(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiAccountReset remove all the multi-account keys from memory.
|
|
||||||
//export MultiAccountReset
|
|
||||||
func MultiAccountReset() *C.char {
|
|
||||||
statusBackend.AccountManager().AccountsGenerator().Reset()
|
|
||||||
return makeJSONResponse(nil)
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/account"
|
|
||||||
"github.com/status-im/status-go/eth-node/keystore"
|
|
||||||
"github.com/status-im/status-go/transactions"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
codeUnknown int = iota
|
|
||||||
// special codes
|
|
||||||
codeFailedParseResponse
|
|
||||||
codeFailedParseParams
|
|
||||||
// account related codes
|
|
||||||
codeErrNoAccountSelected
|
|
||||||
codeErrInvalidTxSender
|
|
||||||
codeErrDecrypt
|
|
||||||
)
|
|
||||||
|
|
||||||
var errToCodeMap = map[error]int{
|
|
||||||
account.ErrNoAccountSelected: codeErrNoAccountSelected,
|
|
||||||
transactions.ErrInvalidTxSender: codeErrInvalidTxSender,
|
|
||||||
keystore.ErrDecrypt: codeErrDecrypt,
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonrpcSuccessfulResponse struct {
|
|
||||||
Result interface{} `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonrpcErrorResponse struct {
|
|
||||||
Error jsonError `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsonError struct {
|
|
||||||
Code int `json:"code,omitempty"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareJSONResponse(result interface{}, err error) string {
|
|
||||||
code := codeUnknown
|
|
||||||
if c, ok := errToCodeMap[err]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
|
|
||||||
return prepareJSONResponseWithCode(result, err, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareJSONResponseWithCode(result interface{}, err error, code int) string {
|
|
||||||
if err != nil {
|
|
||||||
errResponse := jsonrpcErrorResponse{
|
|
||||||
Error: jsonError{Code: code, Message: err.Error()},
|
|
||||||
}
|
|
||||||
response, _ := json.Marshal(&errResponse)
|
|
||||||
return string(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(jsonrpcSuccessfulResponse{result})
|
|
||||||
if err != nil {
|
|
||||||
return prepareJSONResponseWithCode(nil, err, codeFailedParseResponse)
|
|
||||||
}
|
|
||||||
return string(data)
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type nonJSON struct{}
|
|
||||||
|
|
||||||
func (*nonJSON) MarshalJSON() ([]byte, error) {
|
|
||||||
return nil, errors.New("invalid JSON")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrepareJSONResponseErrorWithResult(t *testing.T) {
|
|
||||||
data := prepareJSONResponse("0x123", nil)
|
|
||||||
require.Equal(t, `{"result":"0x123"}`, data)
|
|
||||||
|
|
||||||
data = prepareJSONResponse(&nonJSON{}, nil)
|
|
||||||
require.Contains(t, data, `{"error":{"code":1,"message":`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrepareJSONResponseErrorWithError(t *testing.T) {
|
|
||||||
data := prepareJSONResponse("0x123", errors.New("some error"))
|
|
||||||
require.Contains(t, data, `{"error":{"message":"some error"}}`)
|
|
||||||
}
|
|
86
lib/types.go
86
lib/types.go
|
@ -1,86 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIResponse generic response from API.
|
|
||||||
type APIResponse struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIDetailedResponse represents a generic response
|
|
||||||
// with possible errors.
|
|
||||||
type APIDetailedResponse struct {
|
|
||||||
Status bool `json:"status"`
|
|
||||||
Message string `json:"message,omitempty"`
|
|
||||||
FieldErrors []APIFieldError `json:"field_errors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error string representation of APIDetailedResponse.
|
|
||||||
func (r APIDetailedResponse) Error() string {
|
|
||||||
buf := bytes.NewBufferString("")
|
|
||||||
|
|
||||||
for _, err := range r.FieldErrors {
|
|
||||||
buf.WriteString(err.Error() + "\n") // nolint: gas
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIFieldError represents a set of errors
|
|
||||||
// related to a parameter.
|
|
||||||
type APIFieldError struct {
|
|
||||||
Parameter string `json:"parameter,omitempty"`
|
|
||||||
Errors []APIError `json:"errors"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error string representation of APIFieldError.
|
|
||||||
func (e APIFieldError) Error() string {
|
|
||||||
if len(e.Errors) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBufferString(fmt.Sprintf("Parameter: %s\n", e.Parameter))
|
|
||||||
|
|
||||||
for _, err := range e.Errors {
|
|
||||||
buf.WriteString(err.Error() + "\n") // nolint: gas
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSpace(buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIError represents a single error.
|
|
||||||
type APIError struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error string representation of APIError.
|
|
||||||
func (e APIError) Error() string {
|
|
||||||
return fmt.Sprintf("message=%s", e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountInfo represents account's info.
|
|
||||||
type AccountInfo struct {
|
|
||||||
Address string `json:"address"` // DEPRECATED
|
|
||||||
PubKey string `json:"pubkey"` // DEPRECATED
|
|
||||||
WalletAddress string `json:"walletAddress"`
|
|
||||||
WalletPubKey string `json:"walletPubKey"`
|
|
||||||
ChatAddress string `json:"chatAddress"`
|
|
||||||
ChatPubKey string `json:"chatPubKey"`
|
|
||||||
Mnemonic string `json:"mnemonic"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnboardingAccount represents accounts info generated for the onboarding.
|
|
||||||
type OnboardingAccount struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Address string `json:"address"` // DEPRECATED
|
|
||||||
PubKey string `json:"pubkey"` // DEPRECATED
|
|
||||||
WalletAddress string `json:"walletAddress"`
|
|
||||||
WalletPubKey string `json:"walletPubKey"`
|
|
||||||
ChatAddress string `json:"chatAddress"`
|
|
||||||
ChatPubKey string `json:"chatPubKey"`
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package statusgo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseJSONArray parses JSON array into Go array of string.
|
|
||||||
func ParseJSONArray(items string) ([]string, error) {
|
|
||||||
var parsedItems []string
|
|
||||||
err := json.Unmarshal([]byte(items), &parsedItems)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedItems, nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue