2017-05-16 12:09:52 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2017-09-04 12:56:58 +00:00
|
|
|
"context"
|
2018-03-22 12:31:12 +00:00
|
|
|
"errors"
|
2018-02-09 13:37:56 +00:00
|
|
|
"fmt"
|
2017-05-25 13:14:52 +00:00
|
|
|
"sync"
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
2018-03-20 18:35:28 +00:00
|
|
|
"github.com/ethereum/go-ethereum/log"
|
|
|
|
|
2017-09-26 13:44:26 +00:00
|
|
|
"github.com/status-im/status-go/geth/account"
|
2017-05-16 12:09:52 +00:00
|
|
|
"github.com/status-im/status-go/geth/common"
|
|
|
|
"github.com/status-im/status-go/geth/jail"
|
|
|
|
"github.com/status-im/status-go/geth/node"
|
2018-02-21 17:36:04 +00:00
|
|
|
"github.com/status-im/status-go/geth/notifications/push/fcm"
|
2017-05-16 12:09:52 +00:00
|
|
|
"github.com/status-im/status-go/geth/params"
|
2017-09-25 18:22:57 +00:00
|
|
|
"github.com/status-im/status-go/geth/signal"
|
2018-01-26 05:59:21 +00:00
|
|
|
"github.com/status-im/status-go/geth/transactions"
|
2017-10-10 15:30:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
//todo(jeka): should be removed
|
|
|
|
fcmServerKey = "AAAAxwa-r08:APA91bFtMIToDVKGAmVCm76iEXtA4dn9MPvLdYKIZqAlNpLJbd12EgdBI9DSDSXKdqvIAgLodepmRhGVaWvhxnXJzVpE6MoIRuKedDV3kfHSVBhWFqsyoLTwXY4xeufL9Sdzb581U-lx"
|
2017-05-16 12:09:52 +00:00
|
|
|
)
|
|
|
|
|
2018-03-22 12:31:12 +00:00
|
|
|
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")
|
|
|
|
)
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// StatusBackend implements Status.im service
|
|
|
|
type StatusBackend struct {
|
2018-02-09 13:37:56 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
nodeManager *node.NodeManager
|
2018-03-22 12:31:12 +00:00
|
|
|
accountManager *account.Manager
|
2018-01-22 09:31:24 +00:00
|
|
|
txQueueManager *transactions.Manager
|
2018-03-01 16:48:30 +00:00
|
|
|
jailManager jail.Manager
|
2018-03-23 08:55:05 +00:00
|
|
|
newNotification fcm.NotificationConstructor
|
2018-02-09 20:28:16 +00:00
|
|
|
connectionState ConnectionState
|
2018-03-20 18:35:28 +00:00
|
|
|
log log.Logger
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewStatusBackend create a new NewStatusBackend instance
|
|
|
|
func NewStatusBackend() *StatusBackend {
|
|
|
|
defer log.Info("Status backend initialized")
|
|
|
|
|
|
|
|
nodeManager := node.NewNodeManager()
|
2017-09-26 13:44:26 +00:00
|
|
|
accountManager := account.NewManager(nodeManager)
|
2018-01-26 05:59:21 +00:00
|
|
|
txQueueManager := transactions.NewManager(nodeManager, accountManager)
|
2017-10-06 16:52:26 +00:00
|
|
|
jailManager := jail.New(nodeManager)
|
2017-10-18 20:56:39 +00:00
|
|
|
notificationManager := fcm.NewNotification(fcmServerKey)
|
2017-08-15 10:27:12 +00:00
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
return &StatusBackend{
|
2017-10-18 20:17:40 +00:00
|
|
|
nodeManager: nodeManager,
|
|
|
|
accountManager: accountManager,
|
|
|
|
jailManager: jailManager,
|
|
|
|
txQueueManager: txQueueManager,
|
|
|
|
newNotification: notificationManager,
|
2018-03-20 18:35:28 +00:00
|
|
|
log: log.New("package", "status-go/geth/api.StatusBackend"),
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodeManager returns reference to node manager
|
2018-02-19 15:32:58 +00:00
|
|
|
func (b *StatusBackend) NodeManager() *node.NodeManager {
|
2018-02-14 16:32:36 +00:00
|
|
|
return b.nodeManager
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AccountManager returns reference to account manager
|
2018-03-22 12:31:12 +00:00
|
|
|
func (b *StatusBackend) AccountManager() *account.Manager {
|
2018-02-14 16:32:36 +00:00
|
|
|
return b.accountManager
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// JailManager returns reference to jail
|
2018-03-01 16:48:30 +00:00
|
|
|
func (b *StatusBackend) JailManager() jail.Manager {
|
2018-02-14 16:32:36 +00:00
|
|
|
return b.jailManager
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
2018-01-22 09:31:24 +00:00
|
|
|
// TxQueueManager returns reference to transactions manager
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) TxQueueManager() *transactions.Manager {
|
|
|
|
return b.txQueueManager
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// IsNodeRunning confirm that node is running
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) IsNodeRunning() bool {
|
|
|
|
return b.nodeManager.IsNodeRunning()
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StartNode start Status node, fails if node is already started
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) StartNode(config *params.NodeConfig) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
return b.startNode(config)
|
2018-02-09 13:37:56 +00:00
|
|
|
}
|
2017-05-25 13:14:52 +00:00
|
|
|
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) startNode(config *params.NodeConfig) (err error) {
|
2018-02-09 13:37:56 +00:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
err = fmt.Errorf("node crashed on start: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
2018-02-14 16:32:36 +00:00
|
|
|
err = b.nodeManager.StartNode(config)
|
2017-05-16 12:09:52 +00:00
|
|
|
if err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
switch err.(type) {
|
|
|
|
case node.RPCClientError:
|
2018-02-27 10:13:43 +00:00
|
|
|
err = fmt.Errorf("%v: %v", node.ErrRPCClient, err)
|
2018-02-09 13:37:56 +00:00
|
|
|
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
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
2018-02-09 13:37:56 +00:00
|
|
|
signal.Send(signal.Envelope{Type: signal.EventNodeStarted})
|
2018-01-18 16:55:17 +00:00
|
|
|
// tx queue manager should be started after node is started, it depends
|
|
|
|
// on rpc client being created
|
2018-02-14 16:32:36 +00:00
|
|
|
b.txQueueManager.Start()
|
|
|
|
if err := b.registerHandlers(); err != nil {
|
2018-03-20 18:35:28 +00:00
|
|
|
b.log.Error("Handler registration failed", "err", err)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
2018-03-22 12:31:12 +00:00
|
|
|
if err := b.ReSelectAccount(); err != nil {
|
2018-03-20 18:35:28 +00:00
|
|
|
b.log.Error("Reselect account failed", "err", err)
|
2017-10-20 09:06:22 +00:00
|
|
|
}
|
2018-03-20 18:35:28 +00:00
|
|
|
b.log.Info("Account reselected")
|
2018-02-09 13:37:56 +00:00
|
|
|
signal.Send(signal.Envelope{Type: signal.EventNodeReady})
|
|
|
|
return nil
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StopNode stop Status node. Stopped node cannot be resumed.
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) StopNode() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
return b.stopNode()
|
2018-02-09 13:37:56 +00:00
|
|
|
}
|
2017-05-16 12:09:52 +00:00
|
|
|
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) stopNode() error {
|
|
|
|
if !b.IsNodeRunning() {
|
2018-02-09 13:37:56 +00:00
|
|
|
return node.ErrNoRunningNode
|
2017-05-25 13:14:52 +00:00
|
|
|
}
|
2018-02-14 16:32:36 +00:00
|
|
|
b.txQueueManager.Stop()
|
|
|
|
b.jailManager.Stop()
|
|
|
|
defer signal.Send(signal.Envelope{Type: signal.EventNodeStopped})
|
|
|
|
return b.nodeManager.StopNode()
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
2017-05-25 13:14:52 +00:00
|
|
|
// RestartNode restart running Status node, fails if node is not running
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) RestartNode() error {
|
|
|
|
if !b.IsNodeRunning() {
|
2018-02-09 13:37:56 +00:00
|
|
|
return node.ErrNoRunningNode
|
2017-05-25 13:14:52 +00:00
|
|
|
}
|
2018-02-14 16:32:36 +00:00
|
|
|
config, err := b.nodeManager.NodeConfig()
|
2017-05-25 13:14:52 +00:00
|
|
|
if err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
return err
|
2017-05-25 13:14:52 +00:00
|
|
|
}
|
2018-02-09 13:37:56 +00:00
|
|
|
newcfg := *config
|
2018-02-14 16:32:36 +00:00
|
|
|
if err := b.stopNode(); err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-02-14 16:32:36 +00:00
|
|
|
return b.startNode(&newcfg)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
2017-05-25 13:14:52 +00:00
|
|
|
// ResetChainData remove chain data from data directory.
|
|
|
|
// Node is stopped, and new node is started, with clean data directory.
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) ResetChainData() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
config, err := b.nodeManager.NodeConfig()
|
2017-05-25 13:14:52 +00:00
|
|
|
if err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
return err
|
2017-05-25 13:14:52 +00:00
|
|
|
}
|
2018-02-09 13:37:56 +00:00
|
|
|
newcfg := *config
|
2018-02-14 16:32:36 +00:00
|
|
|
if err := b.stopNode(); err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-02-14 16:32:36 +00:00
|
|
|
// config is cleaned when node is stopped
|
|
|
|
if err := b.nodeManager.ResetChainData(&newcfg); err != nil {
|
2018-02-09 13:37:56 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
signal.Send(signal.Envelope{Type: signal.EventChainDataRemoved})
|
2018-02-14 16:32:36 +00:00
|
|
|
return b.startNode(&newcfg)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
2017-05-28 13:57:30 +00:00
|
|
|
// CallRPC executes RPC request on node's in-proc RPC server
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) CallRPC(inputJSON string) string {
|
|
|
|
client := b.nodeManager.RPCClient()
|
2017-09-14 20:14:31 +00:00
|
|
|
return client.CallRaw(inputJSON)
|
2017-05-28 13:57:30 +00:00
|
|
|
}
|
|
|
|
|
2017-09-04 12:56:58 +00:00
|
|
|
// SendTransaction creates a new transaction and waits until it's complete.
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) SendTransaction(ctx context.Context, args common.SendTxArgs) (hash gethcommon.Hash, err error) {
|
2017-09-04 12:56:58 +00:00
|
|
|
if ctx == nil {
|
|
|
|
ctx = context.Background()
|
|
|
|
}
|
2018-01-26 05:59:21 +00:00
|
|
|
tx := common.CreateTransaction(ctx, args)
|
2018-02-14 16:32:36 +00:00
|
|
|
if err = b.txQueueManager.QueueTransaction(tx); err != nil {
|
2018-01-05 20:58:17 +00:00
|
|
|
return hash, err
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
2018-02-14 16:32:36 +00:00
|
|
|
rst := b.txQueueManager.WaitForTransaction(tx)
|
2018-01-05 20:58:17 +00:00
|
|
|
if rst.Error != nil {
|
|
|
|
return hash, rst.Error
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
2018-01-05 20:58:17 +00:00
|
|
|
return rst.Hash, nil
|
2017-09-04 12:56:58 +00:00
|
|
|
}
|
|
|
|
|
2017-05-16 12:09:52 +00:00
|
|
|
// CompleteTransaction instructs backend to complete sending of a given transaction
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) CompleteTransaction(id common.QueuedTxID, password string) (gethcommon.Hash, error) {
|
|
|
|
return b.txQueueManager.CompleteTransaction(id, password)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CompleteTransactions instructs backend to complete sending of multiple transactions
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) CompleteTransactions(ids []common.QueuedTxID, password string) map[common.QueuedTxID]common.TransactionResult {
|
|
|
|
return b.txQueueManager.CompleteTransactions(ids, password)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DiscardTransaction discards a given transaction from transaction queue
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) DiscardTransaction(id common.QueuedTxID) error {
|
|
|
|
return b.txQueueManager.DiscardTransaction(id)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DiscardTransactions discards given multiple transactions from transaction queue
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) DiscardTransactions(ids []common.QueuedTxID) map[common.QueuedTxID]common.RawDiscardTransactionResult {
|
|
|
|
return b.txQueueManager.DiscardTransactions(ids)
|
2017-05-16 12:09:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// registerHandlers attaches Status callback handlers to running node
|
2018-02-14 16:32:36 +00:00
|
|
|
func (b *StatusBackend) registerHandlers() error {
|
|
|
|
rpcClient := b.NodeManager().RPCClient()
|
2017-10-26 13:25:38 +00:00
|
|
|
if rpcClient == nil {
|
|
|
|
return node.ErrRPCClient
|
|
|
|
}
|
2018-03-22 12:31:12 +00:00
|
|
|
rpcClient.RegisterHandler("eth_accounts", func(context.Context, ...interface{}) (interface{}, error) {
|
|
|
|
return b.AccountManager().Accounts()
|
|
|
|
})
|
2018-02-14 16:32:36 +00:00
|
|
|
rpcClient.RegisterHandler("eth_sendTransaction", b.txQueueManager.SendTransactionRPCHandler)
|
2017-05-16 12:09:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
2018-02-09 20:28:16 +00:00
|
|
|
|
|
|
|
// ConnectionChange handles network state changes logic.
|
2018-02-16 13:58:13 +00:00
|
|
|
func (b *StatusBackend) ConnectionChange(state ConnectionState) {
|
2018-03-20 18:35:28 +00:00
|
|
|
b.log.Info("Network state change", "old", b.connectionState, "new", state)
|
2018-02-16 13:58:13 +00:00
|
|
|
b.connectionState = state
|
2018-02-09 20:28:16 +00:00
|
|
|
|
|
|
|
// logic of handling state changes here
|
|
|
|
// restart node? force peers reconnect? etc
|
|
|
|
}
|
2018-03-14 15:46:21 +00:00
|
|
|
|
|
|
|
// AppStateChange handles app state changes (background/foreground).
|
|
|
|
func (b *StatusBackend) AppStateChange(state AppState) {
|
2018-03-20 18:35:28 +00:00
|
|
|
b.log.Info("App State changed.", "new-state", state)
|
2018-03-14 15:46:21 +00:00
|
|
|
|
|
|
|
// TODO: put node in low-power mode if the app is in background (or inactive)
|
|
|
|
// and normal mode if the app is in foreground.
|
|
|
|
}
|
2018-03-22 12:31:12 +00:00
|
|
|
|
|
|
|
// Logout clears whisper identities.
|
|
|
|
func (b *StatusBackend) Logout() error {
|
|
|
|
whisperService, err := b.nodeManager.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.nodeManager.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.nodeManager.WhisperService()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = whisperService.SelectKeyPair(acc.AccountKey.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return ErrWhisperIdentityInjectionFailure
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|