mirror of
https://github.com/status-im/status-go.git
synced 2025-02-23 04:08:27 +00:00
We need to be able to sign more than just transactions to make DApps work properly. This change separates signing requests from the transactions and make it more general to prepare to intoduce different types of signing requests. This change is designed to preserve status APIs, so it is backward-comparible with the current API bindings.
364 lines
11 KiB
Go
364 lines
11 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
"github.com/status-im/status-go/geth/account"
|
|
"github.com/status-im/status-go/geth/jail"
|
|
"github.com/status-im/status-go/geth/node"
|
|
"github.com/status-im/status-go/geth/notifications/push/fcm"
|
|
"github.com/status-im/status-go/geth/params"
|
|
"github.com/status-im/status-go/geth/signal"
|
|
"github.com/status-im/status-go/geth/transactions"
|
|
"github.com/status-im/status-go/sign"
|
|
)
|
|
|
|
const (
|
|
//todo(jeka): should be removed
|
|
fcmServerKey = "AAAAxwa-r08:APA91bFtMIToDVKGAmVCm76iEXtA4dn9MPvLdYKIZqAlNpLJbd12EgdBI9DSDSXKdqvIAgLodepmRhGVaWvhxnXJzVpE6MoIRuKedDV3kfHSVBhWFqsyoLTwXY4xeufL9Sdzb581U-lx"
|
|
)
|
|
|
|
var (
|
|
// ErrWhisperClearIdentitiesFailure clearing whisper identities has failed.
|
|
ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities")
|
|
// ErrWhisperIdentityInjectionFailure injecting whisper identities has failed.
|
|
ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper")
|
|
)
|
|
|
|
// StatusBackend implements Status.im service
|
|
type StatusBackend struct {
|
|
mu sync.Mutex
|
|
statusNode *node.StatusNode
|
|
pendingSignRequests *sign.PendingRequests
|
|
accountManager *account.Manager
|
|
transactor *transactions.Transactor
|
|
jailManager jail.Manager
|
|
newNotification fcm.NotificationConstructor
|
|
connectionState ConnectionState
|
|
log log.Logger
|
|
}
|
|
|
|
// NewStatusBackend create a new NewStatusBackend instance
|
|
func NewStatusBackend() *StatusBackend {
|
|
defer log.Info("Status backend initialized")
|
|
|
|
statusNode := node.New()
|
|
pendingSignRequests := sign.NewPendingRequests()
|
|
accountManager := account.NewManager(statusNode)
|
|
transactor := transactions.NewTransactor(pendingSignRequests)
|
|
jailManager := jail.New(statusNode)
|
|
notificationManager := fcm.NewNotification(fcmServerKey)
|
|
|
|
return &StatusBackend{
|
|
pendingSignRequests: pendingSignRequests,
|
|
statusNode: statusNode,
|
|
accountManager: accountManager,
|
|
jailManager: jailManager,
|
|
transactor: transactor,
|
|
newNotification: notificationManager,
|
|
log: log.New("package", "status-go/geth/api.StatusBackend"),
|
|
}
|
|
}
|
|
|
|
// StatusNode returns reference to node manager
|
|
func (b *StatusBackend) StatusNode() *node.StatusNode {
|
|
return b.statusNode
|
|
}
|
|
|
|
// AccountManager returns reference to account manager
|
|
func (b *StatusBackend) AccountManager() *account.Manager {
|
|
return b.accountManager
|
|
}
|
|
|
|
// JailManager returns reference to jail
|
|
func (b *StatusBackend) JailManager() jail.Manager {
|
|
return b.jailManager
|
|
}
|
|
|
|
// Transactor returns reference to a status transactor
|
|
func (b *StatusBackend) Transactor() *transactions.Transactor {
|
|
return b.transactor
|
|
}
|
|
|
|
// PendingSignRequests returns reference to a list of current sign requests
|
|
func (b *StatusBackend) PendingSignRequests() *sign.PendingRequests {
|
|
return b.pendingSignRequests
|
|
}
|
|
|
|
// IsNodeRunning confirm that node is running
|
|
func (b *StatusBackend) IsNodeRunning() bool {
|
|
return b.statusNode.IsRunning()
|
|
}
|
|
|
|
// StartNode start Status node, fails if node is already started
|
|
func (b *StatusBackend) StartNode(config *params.NodeConfig) error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.startNode(config)
|
|
}
|
|
|
|
func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = fmt.Errorf("node crashed on start: %v", err)
|
|
}
|
|
}()
|
|
err = b.statusNode.Start(config)
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case node.RPCClientError:
|
|
err = fmt.Errorf("%v: %v", node.ErrRPCClient, err)
|
|
case node.EthNodeError:
|
|
err = fmt.Errorf("%v: %v", node.ErrNodeStartFailure, err)
|
|
}
|
|
signal.Send(signal.Envelope{
|
|
Type: signal.EventNodeCrashed,
|
|
Event: signal.NodeCrashEvent{
|
|
Error: err,
|
|
},
|
|
})
|
|
return err
|
|
}
|
|
signal.Send(signal.Envelope{Type: signal.EventNodeStarted})
|
|
|
|
b.transactor.SetNetworkID(config.NetworkID)
|
|
b.transactor.SetRPCClient(b.statusNode.RPCClient())
|
|
if err := b.registerHandlers(); err != nil {
|
|
b.log.Error("Handler registration failed", "err", err)
|
|
}
|
|
if err := b.ReSelectAccount(); err != nil {
|
|
b.log.Error("Reselect account failed", "err", err)
|
|
}
|
|
b.log.Info("Account reselected")
|
|
signal.Send(signal.Envelope{Type: signal.EventNodeReady})
|
|
return nil
|
|
}
|
|
|
|
// StopNode stop Status node. Stopped node cannot be resumed.
|
|
func (b *StatusBackend) StopNode() error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
return b.stopNode()
|
|
}
|
|
|
|
func (b *StatusBackend) stopNode() error {
|
|
if !b.IsNodeRunning() {
|
|
return node.ErrNoRunningNode
|
|
}
|
|
b.jailManager.Stop()
|
|
defer signal.Send(signal.Envelope{Type: signal.EventNodeStopped})
|
|
return b.statusNode.Stop()
|
|
}
|
|
|
|
// RestartNode restart running Status node, fails if node is not running
|
|
func (b *StatusBackend) RestartNode() error {
|
|
if !b.IsNodeRunning() {
|
|
return node.ErrNoRunningNode
|
|
}
|
|
config, err := b.statusNode.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newcfg := *config
|
|
if err := b.stopNode(); err != nil {
|
|
return err
|
|
}
|
|
return b.startNode(&newcfg)
|
|
}
|
|
|
|
// ResetChainData remove chain data from data directory.
|
|
// Node is stopped, and new node is started, with clean data directory.
|
|
func (b *StatusBackend) ResetChainData() error {
|
|
b.mu.Lock()
|
|
defer b.mu.Unlock()
|
|
config, err := b.statusNode.Config()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newcfg := *config
|
|
if err := b.stopNode(); err != nil {
|
|
return err
|
|
}
|
|
// config is cleaned when node is stopped
|
|
if err := b.statusNode.ResetChainData(&newcfg); err != nil {
|
|
return err
|
|
}
|
|
signal.Send(signal.Envelope{Type: signal.EventChainDataRemoved})
|
|
return b.startNode(&newcfg)
|
|
}
|
|
|
|
// CallRPC executes RPC request on node's in-proc RPC server
|
|
func (b *StatusBackend) CallRPC(inputJSON string) string {
|
|
client := b.statusNode.RPCClient()
|
|
return client.CallRaw(inputJSON)
|
|
}
|
|
|
|
// SendTransaction creates a new transaction and waits until it's complete.
|
|
func (b *StatusBackend) SendTransaction(ctx context.Context, args transactions.SendTxArgs) (hash gethcommon.Hash, err error) {
|
|
return b.transactor.SendTransaction(ctx, args)
|
|
}
|
|
|
|
func (b *StatusBackend) getVerifiedAccount(password string) (*account.SelectedExtKey, error) {
|
|
selectedAccount, err := b.accountManager.SelectedAccount()
|
|
if err != nil {
|
|
b.log.Error("failed to get a selected account", "err", err)
|
|
return nil, err
|
|
}
|
|
config, err := b.StatusNode().Config()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = b.accountManager.VerifyAccountPassword(config.KeyStoreDir, selectedAccount.Address.String(), password)
|
|
if err != nil {
|
|
b.log.Error("failed to verify account", "account", selectedAccount.Address.String(), "error", err)
|
|
return nil, err
|
|
}
|
|
return selectedAccount, nil
|
|
}
|
|
|
|
// CompleteTransaction instructs backend to complete sending of a given transaction
|
|
func (b *StatusBackend) CompleteTransaction(id string, password string) (hash gethcommon.Hash, err error) {
|
|
return b.pendingSignRequests.Approve(id, password, b.getVerifiedAccount)
|
|
}
|
|
|
|
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
|
func (b *StatusBackend) CompleteTransactions(ids []string, password string) map[string]sign.Result {
|
|
results := make(map[string]sign.Result)
|
|
for _, txID := range ids {
|
|
txHash, txErr := b.CompleteTransaction(txID, password)
|
|
results[txID] = sign.Result{
|
|
Hash: txHash,
|
|
Error: txErr,
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
// DiscardTransaction discards a given transaction from transaction queue
|
|
func (b *StatusBackend) DiscardTransaction(id string) error {
|
|
return b.pendingSignRequests.Discard(id)
|
|
}
|
|
|
|
// DiscardTransactions discards given multiple transactions from transaction queue
|
|
func (b *StatusBackend) DiscardTransactions(ids []string) map[string]error {
|
|
results := make(map[string]error)
|
|
for _, txID := range ids {
|
|
err := b.DiscardTransaction(txID)
|
|
if err != nil {
|
|
results[txID] = err
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
// registerHandlers attaches Status callback handlers to running node
|
|
func (b *StatusBackend) registerHandlers() error {
|
|
rpcClient := b.StatusNode().RPCClient()
|
|
if rpcClient == nil {
|
|
return node.ErrRPCClient
|
|
}
|
|
|
|
rpcClient.RegisterHandler(params.AccountsMethodName, func(context.Context, ...interface{}) (interface{}, error) {
|
|
return b.AccountManager().Accounts()
|
|
})
|
|
|
|
rpcClient.RegisterHandler(params.SendTransactionMethodName, func(ctx context.Context, rpcParams ...interface{}) (interface{}, error) {
|
|
txArgs, err := transactions.RPCCalltoSendTxArgs(rpcParams...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hash, err := b.SendTransaction(ctx, txArgs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hash.Hex(), err
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
//
|
|
|
|
// ConnectionChange handles network state changes logic.
|
|
func (b *StatusBackend) ConnectionChange(state ConnectionState) {
|
|
b.log.Info("Network state change", "old", b.connectionState, "new", state)
|
|
b.connectionState = state
|
|
|
|
// logic of handling state changes here
|
|
// restart node? force peers reconnect? etc
|
|
}
|
|
|
|
// AppStateChange handles app state changes (background/foreground).
|
|
func (b *StatusBackend) AppStateChange(state AppState) {
|
|
b.log.Info("App State changed.", "new-state", state)
|
|
|
|
// TODO: put node in low-power mode if the app is in background (or inactive)
|
|
// and normal mode if the app is in foreground.
|
|
}
|
|
|
|
// Logout clears whisper identities.
|
|
func (b *StatusBackend) Logout() error {
|
|
whisperService, err := b.statusNode.WhisperService()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = whisperService.DeleteKeyPairs()
|
|
if err != nil {
|
|
return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err)
|
|
}
|
|
|
|
return b.AccountManager().Logout()
|
|
}
|
|
|
|
// ReSelectAccount selects previously selected account, often, after node restart.
|
|
func (b *StatusBackend) ReSelectAccount() error {
|
|
selectedAccount, err := b.AccountManager().SelectedAccount()
|
|
if selectedAccount == nil || err == account.ErrNoAccountSelected {
|
|
return nil
|
|
}
|
|
whisperService, err := b.statusNode.WhisperService()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := whisperService.SelectKeyPair(selectedAccount.AccountKey.PrivateKey); err != nil {
|
|
return ErrWhisperIdentityInjectionFailure
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SelectAccount selects current account, by verifying that address has corresponding account which can be decrypted
|
|
// using provided password. Once verification is done, decrypted key is injected into Whisper (as a single identity,
|
|
// all previous identities are removed).
|
|
func (b *StatusBackend) SelectAccount(address, password string) error {
|
|
err := b.accountManager.SelectAccount(address, password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acc, err := b.accountManager.SelectedAccount()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
whisperService, err := b.statusNode.WhisperService()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = whisperService.SelectKeyPair(acc.AccountKey.PrivateKey)
|
|
if err != nil {
|
|
return ErrWhisperIdentityInjectionFailure
|
|
}
|
|
|
|
return nil
|
|
}
|