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"
|
||||
|
||||
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..."
|
||||
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:"
|
||||
@ls -la $(GOBIN)/libstatus.*
|
||||
|
||||
|
@ -257,7 +259,6 @@ test-unit: UNIT_TEST_PACKAGES = $(shell go list ./... | \
|
|||
grep -v /vendor | \
|
||||
grep -v /t/e2e | \
|
||||
grep -v /t/benchmarks | \
|
||||
grep -v /lib | \
|
||||
grep -v /transactions/fake )
|
||||
test-unit: ##@tests Run unit and integration tests
|
||||
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 10m ./t/e2e/transactions/... -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: 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