645 lines
18 KiB
Go
645 lines
18 KiB
Go
package statusgo
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"unsafe"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/status-im/status-go/api"
|
|
"github.com/status-im/status-go/exportlogs"
|
|
"github.com/status-im/status-go/logutils"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/profiling"
|
|
"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"
|
|
validator "gopkg.in/go-playground/validator.v9"
|
|
)
|
|
|
|
var statusBackend = api.NewStatusBackend()
|
|
|
|
// All general log messages in this package should be routed through this logger.
|
|
var logger = log.New("package", "status-go/mobile")
|
|
|
|
// GenerateConfig for status node.
|
|
func GenerateConfig(datadir string, networkID int) string {
|
|
config, err := params.NewNodeConfig(datadir, uint64(networkID))
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
outBytes, err := json.Marshal(config)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return string(outBytes)
|
|
}
|
|
|
|
// StartNode starts the Ethereum Status node.
|
|
func StartNode(configJSON string) string {
|
|
config, err := params.NewConfigFromJSON(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 stops the Ethereum Status node.
|
|
func StopNode() string {
|
|
api.RunAsync(statusBackend.StopNode)
|
|
return makeJSONResponse(nil)
|
|
}
|
|
|
|
// ExtractGroupMembershipSignatures extract public keys from tuples of content/signature.
|
|
func ExtractGroupMembershipSignatures(signaturePairsStr string) string {
|
|
var signaturePairs [][2]string
|
|
|
|
if err := json.Unmarshal([]byte(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 string(data)
|
|
}
|
|
|
|
// SignGroupMembership signs a string containing group membership information.
|
|
func SignGroupMembership(content string) string {
|
|
signature, err := statusBackend.SignGroupMembership(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 string(data)
|
|
}
|
|
|
|
// ValidateNodeConfig validates config for the Status node.
|
|
func ValidateNodeConfig(configJSON string) string {
|
|
var resp APIDetailedResponse
|
|
|
|
_, err := params.NewConfigFromJSON(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 string(respJSON)
|
|
}
|
|
|
|
// ResetChainData removes chain data from data directory.
|
|
func ResetChainData() string {
|
|
api.RunAsync(statusBackend.ResetChainData)
|
|
return makeJSONResponse(nil)
|
|
}
|
|
|
|
// CallRPC calls public APIs via RPC.
|
|
func CallRPC(inputJSON string) string {
|
|
resp, err := statusBackend.CallRPC(inputJSON)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// CallPrivateRPC calls both public and private APIs via RPC.
|
|
func CallPrivateRPC(inputJSON string) string {
|
|
resp, err := statusBackend.CallPrivateRPC(inputJSON)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// CreateAccount is equivalent to creating an account from the command line,
|
|
// just modified to handle the function arg passing.
|
|
func CreateAccount(password string) string {
|
|
info, mnemonic, err := statusBackend.AccountManager().CreateAccount(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 string(outBytes)
|
|
}
|
|
|
|
// CreateChildAccount creates sub-account.
|
|
func CreateChildAccount(parentAddress, password string) string {
|
|
address, pubKey, err := statusBackend.AccountManager().CreateChildAccount(parentAddress, password)
|
|
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := AccountInfo{
|
|
Address: address,
|
|
PubKey: pubKey,
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
return string(outBytes)
|
|
}
|
|
|
|
// RecoverAccount re-creates master key using given details.
|
|
func RecoverAccount(password, mnemonic string) string {
|
|
info, err := statusBackend.AccountManager().RecoverAccount(password, 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: mnemonic,
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
return string(outBytes)
|
|
}
|
|
|
|
// StartOnboarding initialize the onboarding with n random accounts
|
|
func StartOnboarding(n, mnemonicPhraseLength int) string {
|
|
out := struct {
|
|
Accounts []OnboardingAccount `json:"accounts"`
|
|
Error string `json:"error"`
|
|
}{
|
|
Accounts: make([]OnboardingAccount, 0),
|
|
}
|
|
|
|
accounts, err := statusBackend.AccountManager().StartOnboarding(n, 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 string(outBytes)
|
|
}
|
|
|
|
//ImportOnboardingAccount re-creates and imports an account created during onboarding.
|
|
func ImportOnboardingAccount(id, password string) string {
|
|
info, mnemonic, err := statusBackend.AccountManager().ImportOnboardingAccount(id, 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 string(outBytes)
|
|
}
|
|
|
|
// RemoveOnboarding resets the current onboarding removing from memory all the generated keys.
|
|
func RemoveOnboarding() {
|
|
statusBackend.AccountManager().RemoveOnboarding()
|
|
}
|
|
|
|
// VerifyAccountPassword verifies account password.
|
|
func VerifyAccountPassword(keyStoreDir, address, password string) string {
|
|
_, err := statusBackend.AccountManager().VerifyAccountPassword(keyStoreDir, address, password)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// 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.
|
|
func Login(address, password string) string {
|
|
err := statusBackend.SelectAccount(address, address, password)
|
|
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.
|
|
func LoginWithKeycard(chatKeyData, encryptionKeyData string) string {
|
|
err := statusBackend.InjectChatAccount(chatKeyData, encryptionKeyData)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// Logout is equivalent to clearing whisper identities.
|
|
func Logout() string {
|
|
err := statusBackend.Logout()
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// SignMessage unmarshals rpc params {data, address, password} and
|
|
// passes them onto backend.SignMessage.
|
|
func SignMessage(rpcParams string) string {
|
|
var params personal.SignParams
|
|
err := json.Unmarshal([]byte(rpcParams), ¶ms)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
result, err := statusBackend.SignMessage(params)
|
|
return prepareJSONResponse(result.String(), err)
|
|
}
|
|
|
|
// SignTypedData unmarshall data into TypedData, validate it and signs with selected account,
|
|
// if password matches selected account.
|
|
//export SignTypedData
|
|
func SignTypedData(data, password string) string {
|
|
var typed typeddata.TypedData
|
|
err := json.Unmarshal([]byte(data), &typed)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
if err := typed.Validate(); err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
result, err := statusBackend.SignTypedData(typed, password)
|
|
return prepareJSONResponse(result.String(), err)
|
|
}
|
|
|
|
// HashTypedData unmarshalls data into TypedData, validates it and hashes it.
|
|
func HashTypedData(data string) string {
|
|
var typed typeddata.TypedData
|
|
err := json.Unmarshal([]byte(data), &typed)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
if err := typed.Validate(); err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
result, err := statusBackend.HashTypedData(typed)
|
|
return prepareJSONResponse(result.String(), err)
|
|
}
|
|
|
|
// Recover unmarshals rpc params {signDataString, signedData} and passes
|
|
// them onto backend.
|
|
func Recover(rpcParams string) string {
|
|
var params personal.RecoverParams
|
|
err := json.Unmarshal([]byte(rpcParams), ¶ms)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
addr, err := statusBackend.Recover(params)
|
|
return prepareJSONResponse(addr.String(), err)
|
|
}
|
|
|
|
// SendTransaction converts RPC args and calls backend.SendTransaction.
|
|
func SendTransaction(txArgsJSON, password string) string {
|
|
var params transactions.SendTxArgs
|
|
err := json.Unmarshal([]byte(txArgsJSON), ¶ms)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
hash, err := statusBackend.SendTransaction(params, password)
|
|
code := codeUnknown
|
|
if c, ok := errToCodeMap[err]; ok {
|
|
code = c
|
|
}
|
|
return prepareJSONResponseWithCode(hash.String(), err, code)
|
|
}
|
|
|
|
// SendTransactionWithSignature converts RPC args and calls backend.SendTransactionWithSignature
|
|
func SendTransactionWithSignature(txArgsJSON, sigString string) string {
|
|
var params transactions.SendTxArgs
|
|
err := json.Unmarshal([]byte(txArgsJSON), ¶ms)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
|
|
sig, err := hex.DecodeString(sigString)
|
|
if err != nil {
|
|
return prepareJSONResponseWithCode(nil, err, codeFailedParseParams)
|
|
}
|
|
|
|
hash, err := statusBackend.SendTransactionWithSignature(params, sig)
|
|
code := codeUnknown
|
|
if c, ok := errToCodeMap[err]; ok {
|
|
code = c
|
|
}
|
|
return prepareJSONResponseWithCode(hash.String(), err, code)
|
|
}
|
|
|
|
// HashTransaction validate the transaction and returns new txArgs and the transaction hash.
|
|
func HashTransaction(txArgsJSON string) string {
|
|
var params transactions.SendTxArgs
|
|
err := json.Unmarshal([]byte(txArgsJSON), ¶ms)
|
|
if err != nil {
|
|
return 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 common.Hash `json:"hash"`
|
|
}{
|
|
Transaction: newTxArgs,
|
|
Hash: hash,
|
|
}
|
|
|
|
return 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.
|
|
func HashMessage(message string) string {
|
|
hash, err := api.HashMessage(message)
|
|
code := codeUnknown
|
|
if c, ok := errToCodeMap[err]; ok {
|
|
code = c
|
|
}
|
|
return prepareJSONResponseWithCode(fmt.Sprintf("0x%x", hash), err, code)
|
|
}
|
|
|
|
// StartCPUProfile runs pprof for CPU.
|
|
func StartCPUProfile(dataDir string) string {
|
|
err := profiling.StartCPUProfile(dataDir)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// StopCPUProfiling stops pprof for cpu.
|
|
func StopCPUProfiling() string { //nolint: deadcode
|
|
err := profiling.StopCPUProfile()
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
//WriteHeapProfile starts pprof for heap
|
|
func WriteHeapProfile(dataDir string) string { //nolint: deadcode
|
|
err := profiling.WriteHeapFile(dataDir)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
func makeJSONResponse(err error) string {
|
|
errString := ""
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
errString = err.Error()
|
|
}
|
|
|
|
out := APIResponse{
|
|
Error: errString,
|
|
}
|
|
outBytes, _ := json.Marshal(out)
|
|
|
|
return string(outBytes)
|
|
}
|
|
|
|
// SendDataNotification sends push notifications by given tokens.
|
|
// dataPayloadJSON is a JSON string that looks like this:
|
|
// {
|
|
// "data": {
|
|
// "msg-v2": {
|
|
// "from": "0x2cea3bd5", // hash of sender (first 10 characters/4 bytes of sha3 hash)
|
|
// "to": "0xb1f89744", // hash of recipient (first 10 characters/4 bytes of sha3 hash)
|
|
// "id": "0x872653ad", // message ID hash (first 10 characters/4 bytes of sha3 hash)
|
|
// }
|
|
// }
|
|
// }
|
|
func SendDataNotification(dataPayloadJSON, tokensArray string) (result string) {
|
|
var (
|
|
err error
|
|
errString string
|
|
)
|
|
|
|
defer func() {
|
|
out := SendDataNotificationResult{
|
|
Status: err == nil,
|
|
Error: errString,
|
|
}
|
|
|
|
var resultBytes []byte
|
|
|
|
resultBytes, err = json.Marshal(out)
|
|
if err != nil {
|
|
logger.Error("failed to marshal SendDataNotification output", "error", err)
|
|
result = makeJSONResponse(err)
|
|
return
|
|
}
|
|
|
|
result = string(resultBytes)
|
|
}()
|
|
|
|
tokens, err := ParseJSONArray((tokensArray))
|
|
if err != nil {
|
|
errString = err.Error()
|
|
return ""
|
|
}
|
|
|
|
err = statusBackend.SendDataNotification(dataPayloadJSON, tokens...)
|
|
if err != nil {
|
|
errString = err.Error()
|
|
return ""
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// UpdateMailservers updates mail servers in status backend.
|
|
//export UpdateMailservers
|
|
func UpdateMailservers(data string) string {
|
|
var enodes []string
|
|
err := json.Unmarshal([]byte(data), &enodes)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
err = statusBackend.UpdateMailservers(enodes)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// GetNodesFromContract returns a list of nodes from a given contract
|
|
//export GetNodesFromContract
|
|
func GetNodesFromContract(rpcEndpoint string, contractAddress string) string {
|
|
nodes, err := statusBackend.GetNodesFromContract(
|
|
rpcEndpoint,
|
|
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 string(data)
|
|
}
|
|
|
|
// AddPeer adds an enode as a peer.
|
|
func AddPeer(enode string) string {
|
|
err := statusBackend.StatusNode().AddPeer(enode)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// ConnectionChange handles network state changes as reported
|
|
// by ReactNative (see https://facebook.github.io/react-native/docs/netinfo.html)
|
|
func ConnectionChange(typ string, expensive int) {
|
|
statusBackend.ConnectionChange(typ, expensive == 1)
|
|
}
|
|
|
|
// AppStateChange handles app state changes (background/foreground).
|
|
func AppStateChange(state string) {
|
|
statusBackend.AppStateChange(state)
|
|
}
|
|
|
|
// SetMobileSignalHandler setup geth callback to notify about new signal
|
|
// used for gomobile builds
|
|
func SetMobileSignalHandler(handler SignalHandler) {
|
|
signal.SetMobileSignalHandler(func(data []byte) {
|
|
if len(data) > 0 {
|
|
handler.HandleSignal(string(data))
|
|
}
|
|
})
|
|
}
|
|
|
|
// SetSignalEventCallback setup geth callback to notify about new signal
|
|
func SetSignalEventCallback(cb unsafe.Pointer) {
|
|
signal.SetSignalEventCallback(cb)
|
|
}
|
|
|
|
// ExportNodeLogs reads current node log and returns content to a caller.
|
|
//export ExportNodeLogs
|
|
func ExportNodeLogs() string {
|
|
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 string(data)
|
|
}
|
|
|
|
// ChaosModeUpdate sets the Chaos Mode on or off.
|
|
func ChaosModeUpdate(on bool) string {
|
|
node := statusBackend.StatusNode()
|
|
if node == nil {
|
|
return makeJSONResponse(errors.New("node is not running"))
|
|
}
|
|
|
|
err := node.ChaosModeCheckRPCClientsUpstreamURL(on)
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
// SignHash exposes vanilla ECDSA signing required for Swarm messages
|
|
func SignHash(hexEncodedHash string) string {
|
|
hexEncodedSignature, err := statusBackend.SignHash(hexEncodedHash)
|
|
if err != nil {
|
|
return makeJSONResponse(err)
|
|
}
|
|
|
|
return hexEncodedSignature
|
|
}
|