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:
Andrea Maria Piana 2020-06-02 15:34:21 +02:00
parent 0bffeab908
commit b8d64cbb1e
14 changed files with 156 additions and 2336 deletions

View File

@ -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

16
cmd/library/const.go Normal file
View File

@ -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"

137
cmd/library/main.go Normal file
View File

@ -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
}

View File

@ -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`.

View File

@ -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)), &params)
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)), &params)
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)), &params)
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)), &params)
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)), &params)
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)
}

View File

@ -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)
})
}
}

View File

@ -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(&params)
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(&params)
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(&params)
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
}

View File

@ -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)
}

View File

@ -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() {}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"}}`)
}

View File

@ -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"`
}

View File

@ -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
}