2019-11-23 17:57:05 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-06-26 11:14:27 +00:00
|
|
|
"crypto/ecdsa"
|
2019-11-23 17:57:05 +00:00
|
|
|
"database/sql"
|
2023-09-16 01:20:23 +00:00
|
|
|
"encoding/json"
|
2019-11-23 17:57:05 +00:00
|
|
|
"fmt"
|
|
|
|
"math/big"
|
2020-07-13 10:45:36 +00:00
|
|
|
"os"
|
2019-11-23 17:57:05 +00:00
|
|
|
"path/filepath"
|
2021-08-13 07:24:33 +00:00
|
|
|
"strings"
|
2019-11-23 17:57:05 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2024-06-05 13:03:34 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
"github.com/pkg/errors"
|
2023-09-16 01:20:23 +00:00
|
|
|
|
2021-12-21 14:27:18 +00:00
|
|
|
"github.com/imdario/mergo"
|
|
|
|
|
2024-05-26 08:31:13 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
|
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
|
|
"github.com/ethereum/go-ethereum/log"
|
2022-10-25 14:25:08 +00:00
|
|
|
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
2020-01-02 09:10:19 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/account"
|
2023-03-21 17:02:04 +00:00
|
|
|
"github.com/status-im/status-go/account/generator"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/appdatabase"
|
2024-07-03 11:51:14 +00:00
|
|
|
"github.com/status-im/status-go/centralizedmetrics"
|
|
|
|
centralizedmetricscommon "github.com/status-im/status-go/centralizedmetrics/common"
|
2023-07-12 22:29:38 +00:00
|
|
|
"github.com/status-im/status-go/common/dbsetup"
|
2021-05-14 10:55:42 +00:00
|
|
|
"github.com/status-im/status-go/connection"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2023-07-12 22:29:38 +00:00
|
|
|
"github.com/status-im/status-go/images"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/logutils"
|
|
|
|
"github.com/status-im/status-go/multiaccounts"
|
|
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
2023-06-21 14:09:55 +00:00
|
|
|
multiacccommon "github.com/status-im/status-go/multiaccounts/common"
|
2022-03-23 18:47:00 +00:00
|
|
|
"github.com/status-im/status-go/multiaccounts/settings"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/node"
|
2022-01-12 20:04:43 +00:00
|
|
|
"github.com/status-im/status-go/nodecfg"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/params"
|
2023-01-06 12:21:14 +00:00
|
|
|
"github.com/status-im/status-go/protocol"
|
2023-06-21 14:09:55 +00:00
|
|
|
identityutils "github.com/status-im/status-go/protocol/identity"
|
2023-05-18 06:27:16 +00:00
|
|
|
"github.com/status-im/status-go/protocol/identity/colorhash"
|
2023-03-16 14:49:25 +00:00
|
|
|
"github.com/status-im/status-go/protocol/requests"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/rpc"
|
2023-07-12 22:29:38 +00:00
|
|
|
"github.com/status-im/status-go/server/pairing/statecontrol"
|
2024-06-26 11:14:27 +00:00
|
|
|
"github.com/status-im/status-go/services/ens"
|
2022-02-10 15:15:27 +00:00
|
|
|
"github.com/status-im/status-go/services/ext"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/services/personal"
|
|
|
|
"github.com/status-im/status-go/services/typeddata"
|
2024-06-26 11:14:27 +00:00
|
|
|
"github.com/status-im/status-go/services/wallet"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/signal"
|
2024-06-26 11:14:27 +00:00
|
|
|
"github.com/status-im/status-go/sqlite"
|
2019-11-23 17:57:05 +00:00
|
|
|
"github.com/status-im/status-go/transactions"
|
2023-07-12 22:29:38 +00:00
|
|
|
"github.com/status-im/status-go/walletdatabase"
|
2019-11-23 17:57:05 +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")
|
2020-02-17 14:38:59 +00:00
|
|
|
// ErrWakuIdentityInjectionFailure injecting whisper identities has failed.
|
|
|
|
ErrWakuIdentityInjectionFailure = errors.New("failed to inject identity into waku")
|
2019-11-23 17:57:05 +00:00
|
|
|
// ErrUnsupportedRPCMethod is for methods not supported by the RPC interface
|
|
|
|
ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface")
|
|
|
|
// ErrRPCClientUnavailable is returned if an RPC client can't be retrieved.
|
|
|
|
// This is a normal situation when a node is stopped.
|
|
|
|
ErrRPCClientUnavailable = errors.New("JSON-RPC client is unavailable")
|
2021-04-29 19:22:10 +00:00
|
|
|
// ErrDBNotAvailable is returned if a method is called before the DB is available for usage
|
|
|
|
ErrDBNotAvailable = errors.New("DB is unavailable")
|
2022-02-27 14:46:17 +00:00
|
|
|
// ErrConfigNotAvailable is returned if a method is called before the nodeconfig is set
|
|
|
|
ErrConfigNotAvailable = errors.New("NodeConfig is not available")
|
2019-11-23 17:57:05 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var _ StatusBackend = (*GethStatusBackend)(nil)
|
|
|
|
|
|
|
|
// GethStatusBackend implements the Status.im service over go-ethereum
|
|
|
|
type GethStatusBackend struct {
|
|
|
|
mu sync.Mutex
|
|
|
|
// rootDataDir is the same for all networks.
|
2022-02-27 14:46:17 +00:00
|
|
|
rootDataDir string
|
|
|
|
appDB *sql.DB
|
2023-08-11 11:25:14 +00:00
|
|
|
walletDB *sql.DB
|
2022-02-27 14:46:17 +00:00
|
|
|
config *params.NodeConfig
|
|
|
|
|
2023-07-12 22:29:38 +00:00
|
|
|
statusNode *node.StatusNode
|
|
|
|
personalAPI *personal.PublicAPI
|
|
|
|
multiaccountsDB *multiaccounts.Database
|
|
|
|
account *multiaccounts.Account
|
|
|
|
accountManager *account.GethManager
|
|
|
|
transactor *transactions.Transactor
|
|
|
|
connectionState connection.State
|
|
|
|
appState appState
|
|
|
|
selectedAccountKeyID string
|
|
|
|
log log.Logger
|
|
|
|
allowAllRPC bool // used only for tests, disables api method restrictions
|
|
|
|
LocalPairingStateManager *statecontrol.ProcessStateManager
|
2024-07-03 11:51:14 +00:00
|
|
|
centralizedMetrics *centralizedmetrics.MetricService
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewGethStatusBackend create a new GethStatusBackend instance
|
|
|
|
func NewGethStatusBackend() *GethStatusBackend {
|
2022-08-11 14:27:25 +00:00
|
|
|
defer log.Info("Status backend initialized", "backend", "geth", "version", params.Version, "commit", params.GitCommit, "IpfsGatewayURL", params.IpfsGatewayURL)
|
2019-11-23 17:57:05 +00:00
|
|
|
|
2021-07-07 06:11:09 +00:00
|
|
|
backend := &GethStatusBackend{}
|
|
|
|
backend.initialize()
|
|
|
|
return backend
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) initialize() {
|
2019-11-27 12:22:23 +00:00
|
|
|
accountManager := account.NewGethManager()
|
2019-11-23 17:57:05 +00:00
|
|
|
transactor := transactions.NewTransactor()
|
|
|
|
personalAPI := personal.NewAPI()
|
2021-12-21 15:44:37 +00:00
|
|
|
statusNode := node.New(transactor)
|
2020-10-28 07:56:14 +00:00
|
|
|
|
2021-07-07 06:11:09 +00:00
|
|
|
b.statusNode = statusNode
|
|
|
|
b.accountManager = accountManager
|
|
|
|
b.transactor = transactor
|
|
|
|
b.personalAPI = personalAPI
|
|
|
|
b.statusNode.SetMultiaccountsDB(b.multiaccountsDB)
|
|
|
|
b.log = log.New("package", "status-go/api.GethStatusBackend")
|
2023-07-12 22:29:38 +00:00
|
|
|
b.LocalPairingStateManager = new(statecontrol.ProcessStateManager)
|
|
|
|
b.LocalPairingStateManager.SetPairing(false)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StatusNode returns reference to node manager
|
|
|
|
func (b *GethStatusBackend) StatusNode() *node.StatusNode {
|
|
|
|
return b.statusNode
|
|
|
|
}
|
|
|
|
|
|
|
|
// AccountManager returns reference to account manager
|
2019-12-19 18:27:27 +00:00
|
|
|
func (b *GethStatusBackend) AccountManager() *account.GethManager {
|
2019-11-23 17:57:05 +00:00
|
|
|
return b.accountManager
|
|
|
|
}
|
|
|
|
|
|
|
|
// Transactor returns reference to a status transactor
|
|
|
|
func (b *GethStatusBackend) Transactor() *transactions.Transactor {
|
|
|
|
return b.transactor
|
|
|
|
}
|
|
|
|
|
2020-02-17 14:38:59 +00:00
|
|
|
// SelectedAccountKeyID returns a Whisper key ID of the selected chat key pair.
|
|
|
|
func (b *GethStatusBackend) SelectedAccountKeyID() string {
|
|
|
|
return b.selectedAccountKeyID
|
2019-12-18 22:24:38 +00:00
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// IsNodeRunning confirm that node is running
|
|
|
|
func (b *GethStatusBackend) IsNodeRunning() bool {
|
|
|
|
return b.statusNode.IsRunning()
|
|
|
|
}
|
|
|
|
|
|
|
|
// StartNode start Status node, fails if node is already started
|
|
|
|
func (b *GethStatusBackend) StartNode(config *params.NodeConfig) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if err := b.startNode(config); err != nil {
|
|
|
|
signal.SendNodeCrashed(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) UpdateRootDataDir(datadir string) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
b.rootDataDir = datadir
|
|
|
|
}
|
|
|
|
|
2022-08-31 12:47:16 +00:00
|
|
|
func (b *GethStatusBackend) GetMultiaccountDB() *multiaccounts.Database {
|
|
|
|
return b.multiaccountsDB
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) OpenAccounts() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.multiaccountsDB != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
db, err := multiaccounts.InitializeDB(filepath.Join(b.rootDataDir, "accounts.sql"))
|
|
|
|
if err != nil {
|
2020-10-13 12:32:47 +00:00
|
|
|
b.log.Error("failed to initialize accounts db", "err", err)
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.multiaccountsDB = db
|
2024-07-03 11:51:14 +00:00
|
|
|
|
|
|
|
b.centralizedMetrics = centralizedmetrics.NewDefaultMetricService(b.multiaccountsDB.DB())
|
|
|
|
err = b.centralizedMetrics.EnsureStarted()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-07-07 06:11:09 +00:00
|
|
|
// Probably we should iron out a bit better how to create/dispose of the status-service
|
2021-06-30 11:40:54 +00:00
|
|
|
b.statusNode.SetMultiaccountsDB(db)
|
2023-05-18 06:27:16 +00:00
|
|
|
|
|
|
|
err = b.statusNode.StartMediaServerWithoutDB()
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to start media server without app db", "err", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-03 11:51:14 +00:00
|
|
|
func (b *GethStatusBackend) CentralizedMetricsInfo() (*centralizedmetrics.MetricsInfo, error) {
|
|
|
|
if b.centralizedMetrics == nil {
|
|
|
|
return nil, errors.New("centralized metrics not initialized")
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.centralizedMetrics.Info()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) ToggleCentralizedMetrics(isEnabled bool) error {
|
|
|
|
if b.centralizedMetrics == nil {
|
|
|
|
return errors.New("centralized metrics nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.centralizedMetrics.ToggleEnabled(isEnabled)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) AddCentralizedMetric(metric centralizedmetricscommon.Metric) error {
|
|
|
|
if b.centralizedMetrics == nil {
|
|
|
|
return errors.New("centralized metrics nil")
|
|
|
|
}
|
|
|
|
return b.centralizedMetrics.AddMetric(metric)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) GetAccounts() ([]multiaccounts.Account, error) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.multiaccountsDB == nil {
|
|
|
|
return nil, errors.New("accounts db wasn't initialized")
|
|
|
|
}
|
|
|
|
return b.multiaccountsDB.GetAccounts()
|
|
|
|
}
|
|
|
|
|
2023-06-01 12:57:05 +00:00
|
|
|
func (b *GethStatusBackend) getAccountByKeyUID(keyUID string) (*multiaccounts.Account, error) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.multiaccountsDB == nil {
|
|
|
|
return nil, errors.New("accounts db wasn't initialized")
|
|
|
|
}
|
|
|
|
as, err := b.multiaccountsDB.GetAccounts()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, acc := range as {
|
|
|
|
if acc.KeyUID == keyUID {
|
|
|
|
return &acc, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("account with keyUID %s not found", keyUID)
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) SaveAccount(account multiaccounts.Account) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.multiaccountsDB == nil {
|
|
|
|
return errors.New("accounts db wasn't initialized")
|
|
|
|
}
|
|
|
|
return b.multiaccountsDB.SaveAccount(account)
|
|
|
|
}
|
|
|
|
|
2022-07-06 16:12:49 +00:00
|
|
|
func (b *GethStatusBackend) DeleteMultiaccount(keyUID string, keyStoreDir string) error {
|
2020-07-13 10:45:36 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.multiaccountsDB == nil {
|
|
|
|
return errors.New("accounts db wasn't initialized")
|
|
|
|
}
|
|
|
|
|
|
|
|
err := b.multiaccountsDB.DeleteAccount(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
appDbPath, err := b.getAppDBPath(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
walletDbPath, err := b.getWalletDBPath(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-07-13 10:45:36 +00:00
|
|
|
dbFiles := []string{
|
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", keyUID)),
|
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql-shm", keyUID)),
|
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql-wal", keyUID)),
|
2020-09-25 07:31:55 +00:00
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", keyUID)),
|
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db-shm", keyUID)),
|
|
|
|
filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db-wal", keyUID)),
|
2023-08-10 11:41:04 +00:00
|
|
|
appDbPath,
|
2024-01-18 18:54:54 +00:00
|
|
|
appDbPath + "-shm",
|
|
|
|
appDbPath + "-wal",
|
2023-08-22 14:36:51 +00:00
|
|
|
walletDbPath,
|
2024-01-18 18:54:54 +00:00
|
|
|
walletDbPath + "-shm",
|
|
|
|
walletDbPath + "-wal",
|
2020-07-13 10:45:36 +00:00
|
|
|
}
|
|
|
|
for _, path := range dbFiles {
|
|
|
|
if _, err := os.Stat(path); err == nil {
|
|
|
|
err = os.Remove(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-28 12:32:45 +00:00
|
|
|
if b.account != nil && b.account.KeyUID == keyUID {
|
|
|
|
// reset active account
|
|
|
|
b.account = nil
|
|
|
|
}
|
|
|
|
|
2020-07-13 10:45:36 +00:00
|
|
|
return os.RemoveAll(keyStoreDir)
|
|
|
|
}
|
|
|
|
|
2021-08-13 07:24:33 +00:00
|
|
|
func (b *GethStatusBackend) DeleteImportedKey(address, password, keyStoreDir string) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
|
|
err := filepath.Walk(keyStoreDir, func(path string, fileInfo os.FileInfo, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if strings.Contains(fileInfo.Name(), address) {
|
|
|
|
_, err := b.accountManager.VerifyAccountPassword(keyStoreDir, "0x"+address, password)
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to verify account", "account", address, "error", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return os.Remove(path)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-06-13 15:20:21 +00:00
|
|
|
func (b *GethStatusBackend) runDBFileMigrations(account multiaccounts.Account, password string) (string, error) {
|
|
|
|
// Migrate file path to fix issue https://github.com/status-im/status-go/issues/2027
|
|
|
|
unsupportedPath := filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", account.KeyUID))
|
|
|
|
v3Path := filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", account.KeyUID))
|
2023-08-22 14:36:51 +00:00
|
|
|
v4Path, err := b.getAppDBPath(account.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2023-06-13 15:20:21 +00:00
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
_, err = os.Stat(unsupportedPath)
|
2023-06-13 15:20:21 +00:00
|
|
|
if err == nil {
|
|
|
|
err := os.Rename(unsupportedPath, v3Path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// rename journals as well, but ignore errors
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Rename(unsupportedPath+"-shm", v3Path+"-shm")
|
2023-06-13 15:20:21 +00:00
|
|
|
_ = os.Rename(unsupportedPath+"-wal", v3Path+"-wal")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = os.Stat(v3Path); err == nil {
|
2023-07-05 15:56:34 +00:00
|
|
|
if err := appdatabase.MigrateV3ToV4(v3Path, v4Path, password, account.KDFIterations, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished); err != nil {
|
2023-06-13 15:20:21 +00:00
|
|
|
_ = os.Remove(v4Path)
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Remove(v4Path + "-shm")
|
2023-06-13 15:20:21 +00:00
|
|
|
_ = os.Remove(v4Path + "-wal")
|
|
|
|
return "", errors.New("Failed to migrate v3 db to v4: " + err.Error())
|
|
|
|
}
|
|
|
|
_ = os.Remove(v3Path)
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Remove(v3Path + "-shm")
|
2023-06-13 15:20:21 +00:00
|
|
|
_ = os.Remove(v3Path + "-wal")
|
|
|
|
}
|
|
|
|
|
|
|
|
return v4Path, nil
|
|
|
|
}
|
|
|
|
|
2023-08-11 11:25:14 +00:00
|
|
|
func (b *GethStatusBackend) ensureDBsOpened(account multiaccounts.Account, password string) (err error) {
|
|
|
|
// After wallet DB initial migration, the tables moved to wallet DB are removed from appDB
|
|
|
|
// so better migrate wallet DB first to avoid removal if wallet DB migration fails
|
|
|
|
if err = b.ensureWalletDBOpened(account, password); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = b.ensureAppDBOpened(account, password); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) ensureAppDBOpened(account multiaccounts.Account, password string) (err error) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.appDB != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if len(b.rootDataDir) == 0 {
|
|
|
|
return errors.New("root datadir wasn't provided")
|
|
|
|
}
|
2020-09-25 07:31:55 +00:00
|
|
|
|
2023-06-13 15:20:21 +00:00
|
|
|
dbFilePath, err := b.runDBFileMigrations(account, password)
|
|
|
|
if err != nil {
|
|
|
|
return errors.New("Failed to migrate db file: " + err.Error())
|
2020-09-25 07:31:55 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 11:59:54 +00:00
|
|
|
appdatabase.CurrentAppDBKeyUID = account.KeyUID
|
2023-06-13 15:20:21 +00:00
|
|
|
b.appDB, err = appdatabase.InitializeDB(dbFilePath, password, account.KDFIterations)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
2023-08-22 14:36:51 +00:00
|
|
|
b.log.Error("failed to initialize db", "err", err.Error())
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-06-30 11:40:54 +00:00
|
|
|
b.statusNode.SetAppDB(b.appDB)
|
2019-11-23 17:57:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
func fileExists(path string) bool {
|
|
|
|
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) walletDBExists(keyUID string) bool {
|
|
|
|
path, err := b.getWalletDBPath(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileExists(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) appDBExists(keyUID string) bool {
|
|
|
|
path, err := b.getAppDBPath(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return fileExists(path)
|
|
|
|
}
|
|
|
|
|
2023-08-11 11:25:14 +00:00
|
|
|
func (b *GethStatusBackend) ensureWalletDBOpened(account multiaccounts.Account, password string) (err error) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.walletDB != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2023-08-22 14:36:51 +00:00
|
|
|
|
|
|
|
dbWalletPath, err := b.getWalletDBPath(account.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-08-11 11:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
b.walletDB, err = walletdatabase.InitializeDB(dbWalletPath, password, account.KDFIterations)
|
|
|
|
if err != nil {
|
2023-08-22 14:36:51 +00:00
|
|
|
b.log.Error("failed to initialize wallet db", "err", err.Error())
|
2023-08-11 11:25:14 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.statusNode.SetWalletDB(b.walletDB)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-27 15:23:07 +00:00
|
|
|
func (b *GethStatusBackend) setupLogSettings() error {
|
|
|
|
logSettings := logutils.LogSettings{
|
|
|
|
Enabled: b.config.LogEnabled,
|
|
|
|
MobileSystem: b.config.LogMobileSystem,
|
|
|
|
Level: b.config.LogLevel,
|
|
|
|
File: b.config.LogFile,
|
|
|
|
MaxSize: b.config.LogMaxSize,
|
|
|
|
MaxBackups: b.config.LogMaxBackups,
|
|
|
|
CompressRotated: b.config.LogCompressRotated,
|
|
|
|
}
|
|
|
|
if err := logutils.OverrideRootLogWithConfig(logSettings, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
// Deprecated: Use StartNodeWithAccount instead.
|
|
|
|
func (b *GethStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string, nodecfg *params.NodeConfig) error {
|
2022-09-27 20:27:20 +00:00
|
|
|
if acc.KDFIterations == 0 {
|
|
|
|
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(acc.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
acc.KDFIterations = kdfIterations
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
chatKey, err := ethcrypto.HexToECDSA(keyHex)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
err = b.startNodeWithAccount(acc, password, nodecfg, chatKey)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
// Stop node for clean up
|
|
|
|
_ = b.StopNode()
|
|
|
|
}
|
2023-07-05 15:56:34 +00:00
|
|
|
// get logged in
|
2024-06-26 11:14:27 +00:00
|
|
|
if b.LocalPairingStateManager.IsPairing() {
|
|
|
|
return nil
|
2023-06-01 05:33:57 +00:00
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
return b.LoggedIn(acc.KeyUID, err)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
func (b *GethStatusBackend) OverwriteNodeConfigValues(conf *params.NodeConfig, n *params.NodeConfig) (*params.NodeConfig, error) {
|
2022-05-24 10:57:45 +00:00
|
|
|
if err := mergo.Merge(conf, n, mergo.WithOverride); err != nil {
|
2021-12-22 16:08:09 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-16 07:58:36 +00:00
|
|
|
conf.Networks = n.Networks
|
|
|
|
|
2022-01-12 20:04:43 +00:00
|
|
|
if err := b.saveNodeConfig(conf); err != nil {
|
2021-12-22 16:08:09 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
return conf, nil
|
2021-12-22 16:08:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-01 12:57:05 +00:00
|
|
|
func (b *GethStatusBackend) updateAccountColorHashAndColorID(keyUID string, accountsDB *accounts.Database) (*multiaccounts.Account, error) {
|
|
|
|
multiAccount, err := b.getAccountByKeyUID(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-06-21 13:24:53 +00:00
|
|
|
if multiAccount.ColorHash == nil {
|
2023-06-01 12:57:05 +00:00
|
|
|
keypair, err := accountsDB.GetKeypairByKeyUID(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
publicKey := keypair.GetChatPublicKey()
|
|
|
|
if publicKey == nil {
|
|
|
|
return nil, errors.New("chat public key not found")
|
|
|
|
}
|
|
|
|
if err = enrichMultiAccountByPublicKey(multiAccount, publicKey); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = b.multiaccountsDB.UpdateAccount(*multiAccount); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return multiAccount, nil
|
|
|
|
}
|
|
|
|
|
2023-05-24 15:02:53 +00:00
|
|
|
func (b *GethStatusBackend) overrideNetworks(conf *params.NodeConfig, request *requests.Login) {
|
|
|
|
conf.Networks = setRPCs(defaultNetworks, &request.WalletSecretsConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) LoginAccount(request *requests.Login) error {
|
|
|
|
err := b.loginAccount(request)
|
|
|
|
if err != nil {
|
|
|
|
// Stop node for clean up
|
|
|
|
_ = b.StopNode()
|
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
if b.LocalPairingStateManager.IsPairing() {
|
|
|
|
return nil
|
|
|
|
}
|
2023-07-05 15:56:34 +00:00
|
|
|
return b.LoggedIn(request.KeyUID, err)
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) loginAccount(request *requests.Login) error {
|
|
|
|
if err := request.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
if request.Mnemonic != "" {
|
|
|
|
info, err := b.generateAccountInfo(request.Mnemonic)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to generate account info")
|
|
|
|
}
|
|
|
|
|
|
|
|
derivedAddresses, err := b.getDerivedAddresses(info.ID)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to get derived addresses")
|
|
|
|
}
|
|
|
|
|
|
|
|
request.Password = derivedAddresses[pathEncryption].PublicKey
|
|
|
|
request.KeycardWhisperPrivateKey = derivedAddresses[pathDefaultChat].PrivateKey
|
|
|
|
}
|
2023-05-24 15:02:53 +00:00
|
|
|
|
|
|
|
acc := multiaccounts.Account{
|
|
|
|
KeyUID: request.KeyUID,
|
|
|
|
KDFIterations: request.KdfIterations,
|
|
|
|
}
|
|
|
|
|
|
|
|
if acc.KDFIterations == 0 {
|
2023-10-02 09:28:42 +00:00
|
|
|
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
err := b.ensureDBsOpened(acc, request.Password)
|
2023-05-24 15:02:53 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to open database")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
2023-09-16 01:20:23 +00:00
|
|
|
defaultCfg := ¶ms.NodeConfig{
|
|
|
|
// why we need this? relate PR: https://github.com/status-im/status-go/pull/4014
|
2024-04-26 14:11:49 +00:00
|
|
|
KeycardPairingDataFile: DefaultKeycardPairingDataFile,
|
2023-09-16 01:20:23 +00:00
|
|
|
}
|
2023-09-27 17:26:10 +00:00
|
|
|
|
2024-02-02 13:55:46 +00:00
|
|
|
defaultCfg.WalletConfig = buildWalletConfig(&request.WalletSecretsConfig)
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
err = b.UpdateNodeConfigFleet(acc, request.Password, defaultCfg)
|
2023-09-27 17:26:10 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to update node config fleet")
|
2023-09-27 17:26:10 +00:00
|
|
|
}
|
|
|
|
|
2023-09-16 01:20:23 +00:00
|
|
|
err = b.loadNodeConfig(defaultCfg)
|
2023-05-24 15:02:53 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to load node config")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
2024-03-28 15:01:44 +00:00
|
|
|
if request.RuntimeLogLevel != "" {
|
|
|
|
b.config.LogLevel = request.RuntimeLogLevel
|
|
|
|
}
|
|
|
|
|
2023-06-28 12:07:33 +00:00
|
|
|
if b.config.WakuV2Config.Enabled && request.WakuV2Nameserver != "" {
|
|
|
|
b.config.WakuV2Config.Nameserver = request.WakuV2Nameserver
|
|
|
|
}
|
|
|
|
|
2024-06-18 13:36:53 +00:00
|
|
|
b.config.ShhextConfig.BandwidthStatsEnabled = request.BandwidthStatsEnabled
|
|
|
|
|
2023-05-24 15:02:53 +00:00
|
|
|
b.overrideNetworks(b.config, request)
|
|
|
|
|
2024-06-26 14:33:22 +00:00
|
|
|
if request.APIConfig != nil {
|
|
|
|
overrideApiConfig(b.config, request.APIConfig)
|
|
|
|
}
|
|
|
|
|
2023-05-24 15:02:53 +00:00
|
|
|
err = b.setupLogSettings()
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to setup log settings")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
accountsDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to create accounts db")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
2023-06-01 12:57:05 +00:00
|
|
|
|
|
|
|
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to update account color hash and color id")
|
2023-06-01 12:57:05 +00:00
|
|
|
}
|
|
|
|
b.account = multiAccount
|
|
|
|
|
2023-05-24 15:02:53 +00:00
|
|
|
chatAddr, err := accountsDB.GetChatAddress()
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to get chat address")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
walletAddr, err := accountsDB.GetWalletAddress()
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to get wallet address")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
watchAddrs, err := accountsDB.GetWalletAddresses()
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to get wallet addresses")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
login := account.LoginParams{
|
2024-06-26 11:14:27 +00:00
|
|
|
Password: request.Password,
|
2023-05-24 15:02:53 +00:00
|
|
|
ChatAddress: chatAddr,
|
|
|
|
WatchAddresses: watchAddrs,
|
|
|
|
MainAccount: walletAddr,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = b.StartNode(b.config)
|
|
|
|
if err != nil {
|
|
|
|
b.log.Info("failed to start node")
|
2024-06-26 11:14:27 +00:00
|
|
|
return errors.Wrap(err, "failed to start node")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
if chatKey := request.ChatPrivateKey(); chatKey == nil {
|
|
|
|
err = b.SelectAccount(login)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to select account")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// In case of keycard, we don't have a keystore, instead we have private key loaded from the keycard
|
|
|
|
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to set chat account")
|
|
|
|
}
|
|
|
|
_, err = b.accountManager.SelectedChatAccount()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to get selected chat account")
|
|
|
|
}
|
|
|
|
|
|
|
|
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
|
|
|
|
err = b.injectAccountsIntoServices()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to inject accounts into services")
|
|
|
|
}
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
|
2023-05-24 15:02:53 +00:00
|
|
|
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
b.log.Error("failed to update account")
|
|
|
|
return errors.Wrap(err, "failed to update account")
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2024-02-22 19:32:24 +00:00
|
|
|
}
|
|
|
|
|
2024-02-27 16:15:11 +00:00
|
|
|
// UpdateNodeConfigFleet loads the fleet from the settings and updates the node configuration
|
2024-02-22 19:32:24 +00:00
|
|
|
// If the fleet in settings is empty, or not supported anymore, it will be overridden with the default fleet.
|
|
|
|
// In that case settings fleet value remain the same, only runtime node configuration is updated.
|
2024-02-27 19:00:20 +00:00
|
|
|
func (b *GethStatusBackend) UpdateNodeConfigFleet(acc multiaccounts.Account, password string, config *params.NodeConfig) error {
|
2024-02-27 16:15:11 +00:00
|
|
|
if config == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-27 19:00:20 +00:00
|
|
|
err := b.ensureDBsOpened(acc, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-02-22 19:32:24 +00:00
|
|
|
accountSettings, err := b.GetSettings()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fleet := accountSettings.GetFleet()
|
2023-05-24 15:02:53 +00:00
|
|
|
|
2024-02-22 19:32:24 +00:00
|
|
|
if !params.IsFleetSupported(fleet) {
|
|
|
|
b.log.Warn("fleet is not supported, overriding with default value",
|
|
|
|
"fleet", fleet,
|
|
|
|
"defaultFleet", DefaultFleet)
|
|
|
|
fleet = DefaultFleet
|
|
|
|
}
|
|
|
|
|
|
|
|
err = SetFleet(fleet, config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2023-05-24 15:02:53 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
// Deprecated: Use loginAccount instead
|
|
|
|
func (b *GethStatusBackend) startNodeWithAccount(acc multiaccounts.Account, password string, inputNodeCfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
|
2023-08-11 11:25:14 +00:00
|
|
|
err := b.ensureDBsOpened(acc, password)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-27 14:46:17 +00:00
|
|
|
|
|
|
|
err = b.loadNodeConfig(inputNodeCfg)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-08-18 12:44:10 +00:00
|
|
|
|
2022-02-27 15:23:07 +00:00
|
|
|
err = b.setupLogSettings()
|
|
|
|
if err != nil {
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-02-27 15:23:07 +00:00
|
|
|
|
2022-03-23 18:47:00 +00:00
|
|
|
accountsDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-01 12:57:05 +00:00
|
|
|
|
2023-06-21 13:24:53 +00:00
|
|
|
if acc.ColorHash == nil {
|
2023-06-01 12:57:05 +00:00
|
|
|
multiAccount, err := b.updateAccountColorHashAndColorID(acc.KeyUID, accountsDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
acc = *multiAccount
|
|
|
|
}
|
|
|
|
|
|
|
|
b.account = &acc
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
chatAddr, err := accountsDB.GetChatAddress()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
walletAddr, err := accountsDB.GetWalletAddress()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
status-im/status-react#9203 Faster tx fetching with less request
*** How it worked before this PR on multiaccount creation:
- On multiacc creation we scanned chain for eth and erc20 transfers. For
each address of a new empty multiaccount this scan required
1. two `eth_getBalance` requests to find out that there is no any
balance change between zero and the last block, for eth transfers
2. and `chain-size/100000` (currently ~100) `eth_getLogs` requests,
for erc20 transfers
- For some reason we scanned an address of the chat account as well, and
also accounts were not deduplicated. So even for an empty multiacc we
scanned chain twice for each chat and main wallet addresses, in result
app had to execute about 400 requests.
- As mentioned above, `eth_getBalance` requests were used to check if
there were any eth transfers, and that caused empty history in case
if user already used all available eth (so that both zero and latest
blocks show 0 eth for an address). There might have been transactions
but we wouldn't fetch/show them.
- There was no upper limit for the number of rpc requests during the
scan, so it could require indefinite number of requests; the scanning
algorithm was written so that we persisted the whole history of
transactions or tried to scan form the beginning again in case of
failure, giving up only after 10 minutes of failures. In result
addresses with sufficient number of transactions would never be fully
scanned and during these 10 minutes app could use gigabytes of
internet data.
- Failures were caused by `eth_getBlockByNumber`/`eth_getBlockByHash`
requests. These requests return significantly bigger responses than
`eth_getBalance`/`eth_transactionsCount` and it is likely that
execution of thousands of them in parallel caused failures for
accounts with hundreds of transactions. Even for an account with 12k
we could successfully determine blocks with transaction in a few
minutes using `eth_getBalance` requests, but `eth_getBlock...`
couldn't be processed for this acc.
- There was no caching for for `eth_getBalance` requests, and this
caused in average 3-4 times more such requests than is needed.
*** How it works now on multiaccount creation:
- On multiacc creation we scan chain for last ~30 eth transactions and
then check erc20 in the range where these eth transactions were found.
For an empty address in multiacc this means:
1. two `eth_getBalance` transactions to determine that there was no
balance change between zero and the last block; two
`eth_transactionsCount` requests to determine there are no outgoing
transactions for this address; total 4 requests for eth transfers
2. 20 `eth_getLogs` for erc20 transfers. This number can be lowered,
but that's not a big deal
- Deduplication of addresses is added and also we don't scan chat
account, so a new multiacc requires ~25 (we also request latest block
number and probably execute a few other calls) request to determine
that multiacc is empty (comparing to ~400 before)
- In case if address contains transactions we:
1. determine the range which contains 20-25 outgoing eth/erc20
transactions. This usually requires up to 10 `eth_transactionCount`
requests
2. then we scan chain for eth transfers using `eth_getBalance` and
`eth_transactionCount` (for double checking zero balances)
3. we make sure that we do not scan db for more than 30 blocks with
transfers. That's important for accounts with mostly incoming
transactions, because the range found on the first step might
contain any number of incoming transfers, but only 20-25 outgoing
transactions
4. when we found ~30 blocks in a given range, we update initial
range `from` block using the oldest found block
5. and now we scan db for erc20transfers using `eth_getLogs`
`oldest-found-eth-block`-`latest-block`, we make not more than 20 calls
6. when all blocks which contain incoming/outgoing transfers for a
given address are found, we save these blocks to db and mark that
transfers from these blocks are still to be fetched
7. Then we select latest ~30 (the number can be adjusted) blocks from
these which were found and fetch transfers, this requires 3-4
requests per transfer.
8. we persist scanned range so that we know were to start next time
9. we dispatch an event which tells client that transactions are found
10. client fetches latest 20 transfers
- when user presses "fetch more" button we check if app's db contains next
20 transfers, if not we scan chain again and return transfers after
small fixes
2019-12-18 11:01:46 +00:00
|
|
|
watchAddrs, err := accountsDB.GetWalletAddresses()
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
login := account.LoginParams{
|
|
|
|
Password: password,
|
|
|
|
ChatAddress: chatAddr,
|
|
|
|
WatchAddresses: watchAddrs,
|
|
|
|
MainAccount: walletAddr,
|
|
|
|
}
|
2020-10-28 07:56:14 +00:00
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
err = b.StartNode(b.config)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
2023-03-16 14:49:25 +00:00
|
|
|
b.log.Info("failed to start node")
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-10-28 07:56:14 +00:00
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
if chatKey == nil {
|
|
|
|
// Load account from keystore
|
|
|
|
err = b.SelectAccount(login)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// In case of keycard, we don't have keystore, but we directly have the private key
|
|
|
|
if err := b.accountManager.SetChatAccount(chatKey); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = b.accountManager.SelectedChatAccount()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
|
|
|
|
err = b.injectAccountsIntoServices()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
|
2019-12-05 08:00:57 +00:00
|
|
|
err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix())
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
2023-03-16 14:49:25 +00:00
|
|
|
b.log.Info("failed to update account")
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-10-28 07:56:14 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-11-10 15:12:54 +00:00
|
|
|
func (b *GethStatusBackend) accountsDB() (*accounts.Database, error) {
|
|
|
|
return accounts.NewDB(b.appDB)
|
|
|
|
}
|
|
|
|
|
2023-07-05 15:56:34 +00:00
|
|
|
func (b *GethStatusBackend) GetSettings() (*settings.Settings, error) {
|
2023-11-10 15:12:54 +00:00
|
|
|
accountsDB, err := b.accountsDB()
|
2023-07-05 15:56:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-11-10 15:12:54 +00:00
|
|
|
settings, err := accountsDB.GetSettings()
|
2023-07-05 15:56:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &settings, nil
|
|
|
|
}
|
|
|
|
|
2023-09-16 01:20:23 +00:00
|
|
|
func (b *GethStatusBackend) GetEnsUsernames() ([]*ens.UsernameDetail, error) {
|
|
|
|
db := ens.NewEnsDatabase(b.appDB)
|
|
|
|
removed := false
|
|
|
|
return db.GetEnsUsernames(&removed)
|
|
|
|
}
|
|
|
|
|
2020-06-22 12:03:28 +00:00
|
|
|
func (b *GethStatusBackend) MigrateKeyStoreDir(acc multiaccounts.Account, password, oldDir, newDir string) error {
|
2023-08-11 11:25:14 +00:00
|
|
|
err := b.ensureDBsOpened(acc, password)
|
2020-06-22 12:03:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-23 18:47:00 +00:00
|
|
|
accountDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-25 15:17:17 +00:00
|
|
|
accounts, err := accountDB.GetActiveAccounts()
|
2020-06-22 12:03:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
settings, err := accountDB.GetSettings()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
addresses := []string{settings.EIP1581Address.Hex(), settings.WalletRootAddress.Hex()}
|
|
|
|
for _, account := range accounts {
|
|
|
|
addresses = append(addresses, account.Address.Hex())
|
|
|
|
}
|
|
|
|
err = b.accountManager.MigrateKeyStoreDir(oldDir, newDir, addresses)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-16 20:51:41 +00:00
|
|
|
func (b *GethStatusBackend) Login(keyUID, password string) error {
|
2024-06-26 11:14:27 +00:00
|
|
|
return b.startNodeWithAccount(multiaccounts.Account{KeyUID: keyUID}, password, nil, nil)
|
2023-03-16 20:51:41 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string, nodecfg *params.NodeConfig, chatKey *ecdsa.PrivateKey) error {
|
|
|
|
err := b.startNodeWithAccount(acc, password, nodecfg, chatKey)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
// Stop node for clean up
|
|
|
|
_ = b.StopNode()
|
|
|
|
}
|
2023-07-05 15:56:34 +00:00
|
|
|
// get logged in
|
2023-07-12 22:29:38 +00:00
|
|
|
if !b.LocalPairingStateManager.IsPairing() {
|
2023-07-05 15:56:34 +00:00
|
|
|
return b.LoggedIn(acc.KeyUID, err)
|
2023-06-01 05:33:57 +00:00
|
|
|
}
|
2023-07-11 19:36:51 +00:00
|
|
|
return err
|
2023-07-05 15:56:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) LoggedIn(keyUID string, err error) error {
|
|
|
|
if err != nil {
|
2023-09-16 01:20:23 +00:00
|
|
|
signal.SendLoggedIn(nil, nil, nil, err)
|
2023-07-11 19:13:16 +00:00
|
|
|
return err
|
2023-07-05 15:56:34 +00:00
|
|
|
}
|
|
|
|
settings, err := b.GetSettings()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
account, err := b.getAccountByKeyUID(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-16 01:20:23 +00:00
|
|
|
ensUsernames, err := b.GetEnsUsernames()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var ensUsernamesJSON json.RawMessage
|
|
|
|
if ensUsernames != nil {
|
|
|
|
ensUsernamesJSON, err = json.Marshal(ensUsernames)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
signal.SendLoggedIn(account, settings, ensUsernamesJSON, nil)
|
2023-07-05 15:56:34 +00:00
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2021-01-07 11:15:02 +00:00
|
|
|
func (b *GethStatusBackend) ExportUnencryptedDatabase(acc multiaccounts.Account, password, directory string) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.appDB != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if len(b.rootDataDir) == 0 {
|
|
|
|
return errors.New("root datadir wasn't provided")
|
|
|
|
}
|
|
|
|
|
2023-06-13 15:20:21 +00:00
|
|
|
dbPath, err := b.runDBFileMigrations(acc, password)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-01-07 11:15:02 +00:00
|
|
|
}
|
|
|
|
|
2023-10-02 09:28:42 +00:00
|
|
|
err = sqlite.DecryptDB(dbPath, directory, password, acc.KDFIterations)
|
2021-01-07 11:15:02 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to initialize db", "err", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) ImportUnencryptedDatabase(acc multiaccounts.Account, password, databasePath string) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
if b.appDB != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
path, err := b.getAppDBPath(acc.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-07 11:15:02 +00:00
|
|
|
|
2023-10-02 09:28:42 +00:00
|
|
|
err = sqlite.EncryptDB(databasePath, path, password, acc.KDFIterations, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
|
2021-01-07 11:15:02 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to initialize db", "err", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-14 10:12:23 +00:00
|
|
|
func (b *GethStatusBackend) reEncryptKeyStoreDir(currentPassword string, newPassword string) error {
|
2021-06-23 09:21:21 +00:00
|
|
|
config := b.StatusNode().Config()
|
2023-02-11 11:14:20 +00:00
|
|
|
keyDir := ""
|
|
|
|
if config == nil {
|
|
|
|
keyDir = b.accountManager.Keydir
|
|
|
|
} else {
|
|
|
|
keyDir = config.KeyStoreDir
|
|
|
|
}
|
2021-06-23 09:21:21 +00:00
|
|
|
|
2023-02-11 11:14:20 +00:00
|
|
|
if keyDir != "" {
|
2023-06-14 10:12:23 +00:00
|
|
|
err := b.accountManager.ReEncryptKeyStoreDir(keyDir, currentPassword, newPassword)
|
2022-09-26 06:50:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("ReEncryptKeyStoreDir error: %v", err)
|
|
|
|
}
|
2021-06-23 09:21:21 +00:00
|
|
|
}
|
2023-06-14 10:12:23 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) ChangeDatabasePassword(keyUID string, password string, newPassword string) error {
|
|
|
|
account, err := b.multiaccountsDB.GetAccount(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
internalDbPath, err := dbsetup.GetDBFilename(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to get database file name, %w", err)
|
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
appDBPath, err := b.getAppDBPath(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
isCurrentAccount := appDBPath == internalDbPath
|
2023-08-17 15:18:56 +00:00
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
restartNode := func() {
|
|
|
|
if isCurrentAccount {
|
|
|
|
if err != nil {
|
2023-08-17 15:18:56 +00:00
|
|
|
// TODO https://github.com/status-im/status-go/issues/3906
|
|
|
|
// Fix restarting node, as it always fails but the error is ignored
|
|
|
|
// because UI calls Logout and Quit afterwards. It should not be UI-dependent
|
|
|
|
// and should be handled gracefully here if it makes sense to run dummy node after
|
|
|
|
// logout
|
2024-06-26 11:14:27 +00:00
|
|
|
_ = b.startNodeWithAccount(*account, password, nil, nil)
|
2023-08-10 11:41:04 +00:00
|
|
|
} else {
|
2024-06-26 11:14:27 +00:00
|
|
|
_ = b.startNodeWithAccount(*account, newPassword, nil, nil)
|
2023-08-10 11:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
defer restartNode()
|
|
|
|
|
|
|
|
logout := func() {
|
|
|
|
if isCurrentAccount {
|
|
|
|
_ = b.Logout()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
noLogout := func() {}
|
|
|
|
|
2023-08-17 15:18:56 +00:00
|
|
|
// First change app DB password, because it also reencrypts the keystore,
|
|
|
|
// otherwise if we call changeWalletDbPassword first and logout, we will fail
|
|
|
|
// to reencrypt the keystore
|
|
|
|
err = b.changeAppDBPassword(account, logout, password, newPassword)
|
2023-06-14 10:12:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-23 09:21:21 +00:00
|
|
|
|
2023-08-17 15:18:56 +00:00
|
|
|
// Already logged out but pass a param to decouple the logic for testing
|
|
|
|
err = b.changeWalletDBPassword(account, noLogout, password, newPassword)
|
2023-08-10 11:41:04 +00:00
|
|
|
if err != nil {
|
2023-08-17 15:18:56 +00:00
|
|
|
// Revert the password to original
|
|
|
|
err2 := b.changeAppDBPassword(account, noLogout, newPassword, password)
|
|
|
|
if err2 != nil {
|
|
|
|
log.Error("failed to revert app db password", "err", err2)
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-14 10:12:23 +00:00
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
func (b *GethStatusBackend) changeAppDBPassword(account *multiaccounts.Account, logout func(), password string, newPassword string) error {
|
|
|
|
tmpDbPath, cleanup, err := b.createTempDBFile("v4.db")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer cleanup()
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
dbPath, err := b.getAppDBPath(account.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-06-14 10:12:23 +00:00
|
|
|
// Exporting database to a temporary file with a new password
|
2023-10-02 09:28:42 +00:00
|
|
|
err = sqlite.ExportDB(dbPath, password, account.KDFIterations, tmpDbPath, newPassword, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
|
2022-09-27 20:27:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-06-14 10:12:23 +00:00
|
|
|
|
|
|
|
err = b.reEncryptKeyStoreDir(password, newPassword)
|
2021-06-23 09:21:21 +00:00
|
|
|
if err != nil {
|
2023-06-14 10:12:23 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replacing the old database with the new one requires closing all connections to the database
|
|
|
|
// This is done by stopping the node and restarting it with the new DB
|
2023-08-10 11:41:04 +00:00
|
|
|
logout()
|
2023-06-14 10:12:23 +00:00
|
|
|
|
|
|
|
// Replacing the old database files with the new ones, ignoring the wal and shm errors
|
2023-08-10 11:41:04 +00:00
|
|
|
replaceCleanup, err := replaceDBFile(dbPath, tmpDbPath)
|
|
|
|
if replaceCleanup != nil {
|
|
|
|
defer replaceCleanup()
|
|
|
|
}
|
|
|
|
|
2023-06-14 10:12:23 +00:00
|
|
|
if err != nil {
|
|
|
|
// Restore the old account
|
|
|
|
_ = b.reEncryptKeyStoreDir(newPassword, password)
|
2021-06-23 09:21:21 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-06-14 10:12:23 +00:00
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) changeWalletDBPassword(account *multiaccounts.Account, logout func(), password string, newPassword string) error {
|
|
|
|
tmpDbPath, cleanup, err := b.createTempDBFile("wallet.db")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer cleanup()
|
2023-06-14 10:12:23 +00:00
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
dbPath, err := b.getWalletDBPath(account.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
// Exporting database to a temporary file with a new password
|
2023-10-02 09:28:42 +00:00
|
|
|
err = sqlite.ExportDB(dbPath, password, account.KDFIterations, tmpDbPath, newPassword, signal.SendReEncryptionStarted, signal.SendReEncryptionFinished)
|
2023-08-10 11:41:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2023-06-14 10:12:23 +00:00
|
|
|
}
|
2023-08-10 11:41:04 +00:00
|
|
|
|
|
|
|
// Replacing the old database with the new one requires closing all connections to the database
|
|
|
|
// This is done by stopping the node and restarting it with the new DB
|
|
|
|
logout()
|
|
|
|
|
|
|
|
// Replacing the old database files with the new ones, ignoring the wal and shm errors
|
|
|
|
replaceCleanup, err := replaceDBFile(dbPath, tmpDbPath)
|
|
|
|
if replaceCleanup != nil {
|
|
|
|
defer replaceCleanup()
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) createTempDBFile(pattern string) (tmpDbPath string, cleanup func(), err error) {
|
|
|
|
if len(b.rootDataDir) == 0 {
|
|
|
|
err = errors.New("root datadir wasn't provided")
|
|
|
|
return
|
|
|
|
}
|
2023-09-13 00:21:13 +00:00
|
|
|
rootDataDir := b.rootDataDir
|
|
|
|
//On iOS, the rootDataDir value does not contain a trailing slash.
|
|
|
|
//This is causing an incorrectly formatted temporary file path to be generated, leading to an "operation not permitted" error.
|
|
|
|
//e.g. value of rootDataDir is `/var/mobile/.../12906D5A-E831-49E9-BBE7-5FFE8E805D8A/Library`,
|
|
|
|
//the file path generated is something like `/var/mobile/.../12906D5A-E831-49E9-BBE7-5FFE8E805D8A/123-v4.db`
|
|
|
|
//which removed `Library` from the path.
|
|
|
|
if !strings.HasSuffix(rootDataDir, "/") {
|
|
|
|
rootDataDir += "/"
|
|
|
|
}
|
|
|
|
file, err := os.CreateTemp(filepath.Dir(rootDataDir), "*-"+pattern)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-01-19 14:58:36 +00:00
|
|
|
err = file.Close()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
tmpDbPath = file.Name()
|
|
|
|
cleanup = func() {
|
|
|
|
filePath := file.Name()
|
|
|
|
_ = os.Remove(filePath)
|
|
|
|
_ = os.Remove(filePath + "-wal")
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Remove(filePath + "-shm")
|
2023-08-10 11:41:04 +00:00
|
|
|
_ = os.Remove(filePath + "-journal")
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func replaceDBFile(dbPath string, newDBPath string) (cleanup func(), err error) {
|
|
|
|
err = os.Rename(newDBPath, dbPath)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup = func() {
|
|
|
|
_ = os.Remove(dbPath + "-wal")
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Remove(dbPath + "-shm")
|
2023-08-10 11:41:04 +00:00
|
|
|
_ = os.Rename(newDBPath+"-wal", dbPath+"-wal")
|
2024-01-18 18:54:54 +00:00
|
|
|
_ = os.Rename(newDBPath+"-shm", dbPath+"-shm")
|
2023-08-10 11:41:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
2021-06-23 09:21:21 +00:00
|
|
|
}
|
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
func (b *GethStatusBackend) ConvertToKeycardAccount(account multiaccounts.Account, s settings.Settings, keycardUID string, password string, newPassword string) error {
|
2023-08-02 07:39:58 +00:00
|
|
|
messenger := b.Messenger()
|
|
|
|
if messenger == nil {
|
|
|
|
return errors.New("cannot resolve messenger instance")
|
|
|
|
}
|
|
|
|
|
2023-01-25 10:25:07 +00:00
|
|
|
err := b.multiaccountsDB.UpdateAccountKeycardPairing(account.KeyUID, account.KeycardPairing)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-11 11:25:14 +00:00
|
|
|
err = b.ensureDBsOpened(account, password)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-23 18:47:00 +00:00
|
|
|
accountDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-16 10:48:00 +00:00
|
|
|
|
|
|
|
keypair, err := accountDB.GetKeypairByKeyUID(account.KeyUID)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
2023-05-16 10:48:00 +00:00
|
|
|
if err == accounts.ErrDbKeypairNotFound {
|
|
|
|
return errors.New("cannot convert an unknown keypair")
|
|
|
|
}
|
2021-07-20 11:48:10 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
err = accountDB.SaveSettingField(settings.KeycardInstanceUID, s.KeycardInstanceUID)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
err = accountDB.SaveSettingField(settings.KeycardPairedOn, s.KeycardPairedOn)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
err = accountDB.SaveSettingField(settings.KeycardPairing, s.KeycardPairing)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
err = accountDB.SaveSettingField(settings.Mnemonic, nil)
|
2022-11-08 13:30:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-02 07:39:58 +00:00
|
|
|
err = accountDB.SaveSettingField(settings.ProfileMigrationNeeded, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
// This check is added due to mobile app cause it doesn't support a Keycard features as desktop app.
|
|
|
|
// We should remove the following line once mobile and desktop app align.
|
|
|
|
if len(keycardUID) > 0 {
|
|
|
|
displayName, err := accountDB.DisplayName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-05 12:41:58 +00:00
|
|
|
position, err := accountDB.GetPositionForNextNewKeycard()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
kc := accounts.Keycard{
|
2023-07-05 12:41:58 +00:00
|
|
|
KeycardUID: keycardUID,
|
|
|
|
KeycardName: displayName,
|
|
|
|
KeycardLocked: false,
|
|
|
|
KeyUID: account.KeyUID,
|
|
|
|
Position: position,
|
2023-05-05 07:43:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
for _, acc := range keypair.Accounts {
|
2023-05-09 18:48:56 +00:00
|
|
|
kc.AccountsAddresses = append(kc.AccountsAddresses, acc.Address)
|
2023-05-05 07:43:05 +00:00
|
|
|
}
|
2023-08-02 07:39:58 +00:00
|
|
|
err = messenger.SaveOrUpdateKeycard(context.Background(), &kc)
|
2023-05-05 07:43:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
masterAddress, err := accountDB.GetMasterAddress()
|
2023-01-25 17:46:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
eip1581Address, err := accountDB.GetEIP1581Address()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
walletRootAddress, err := accountDB.GetWalletRootAddress()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
err = b.closeDBs()
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-08 13:30:33 +00:00
|
|
|
err = b.ChangeDatabasePassword(account.KeyUID, password, newPassword)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-09 18:48:56 +00:00
|
|
|
// We need to delete all accounts for the Keycard which is being added
|
2023-05-16 10:48:00 +00:00
|
|
|
for _, acc := range keypair.Accounts {
|
2023-05-05 07:43:05 +00:00
|
|
|
err = b.accountManager.DeleteAccount(acc.Address)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-11-08 13:30:33 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-02 10:49:23 +00:00
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
err = b.accountManager.DeleteAccount(masterAddress)
|
2023-02-02 10:49:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
err = b.accountManager.DeleteAccount(eip1581Address)
|
2023-02-02 10:49:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-05 07:43:05 +00:00
|
|
|
err = b.accountManager.DeleteAccount(walletRootAddress)
|
2023-02-02 10:49:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-08 13:30:33 +00:00
|
|
|
|
|
|
|
return nil
|
2021-07-20 11:48:10 +00:00
|
|
|
}
|
|
|
|
|
2023-10-10 16:12:38 +00:00
|
|
|
func (b *GethStatusBackend) RestoreAccountAndLogin(request *requests.RestoreAccount) (*multiaccounts.Account, error) {
|
2023-03-16 14:49:25 +00:00
|
|
|
|
|
|
|
if err := request.Validate(); err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
response, err := b.generateOrImportAccount(request.Mnemonic, 0, request.FetchBackup, &request.CreateAccount)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
err = b.StartNodeWithAccountAndInitialConfig(
|
|
|
|
*response.account,
|
|
|
|
request.Password,
|
|
|
|
*response.settings,
|
|
|
|
response.nodeConfig,
|
|
|
|
response.subAccounts,
|
|
|
|
response.chatPrivateKey,
|
|
|
|
)
|
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("start node", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return response.account, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) RestoreKeycardAccountAndLogin(request *requests.RestoreAccount) (*multiaccounts.Account, error) {
|
|
|
|
if err := request.Validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, request.Keycard.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
derivedAddresses := map[string]generator.AccountInfo{
|
|
|
|
pathDefaultChat: {
|
|
|
|
Address: request.Keycard.WhisperAddress,
|
|
|
|
PublicKey: request.Keycard.WhisperPublicKey,
|
|
|
|
PrivateKey: request.Keycard.WhisperPrivateKey,
|
|
|
|
},
|
|
|
|
pathWalletRoot: {
|
|
|
|
Address: request.Keycard.WalletRootAddress,
|
|
|
|
},
|
|
|
|
pathDefaultWallet: {
|
|
|
|
Address: request.Keycard.WalletAddress,
|
|
|
|
PublicKey: request.Keycard.WalletPublicKey,
|
|
|
|
},
|
|
|
|
pathEIP1581: {
|
|
|
|
Address: request.Keycard.Eip1581Address,
|
|
|
|
},
|
|
|
|
pathEncryption: {
|
|
|
|
PublicKey: request.Keycard.EncryptionPublicKey,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
input := &prepareAccountInput{
|
|
|
|
customizationColorClock: 0,
|
|
|
|
accountID: "", // empty for keycard
|
|
|
|
keyUID: request.Keycard.KeyUID,
|
|
|
|
address: request.Keycard.Address,
|
|
|
|
mnemonic: "",
|
|
|
|
restoringAccount: true,
|
|
|
|
derivedAddresses: derivedAddresses,
|
|
|
|
fetchBackup: request.FetchBackup, // WARNING: Ensure this value is correct
|
|
|
|
keyStoreDir: keyStoreDir,
|
|
|
|
}
|
|
|
|
|
|
|
|
response, err := b.prepareNodeAccount(&request.CreateAccount, input)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = b.StartNodeWithAccountAndInitialConfig(
|
|
|
|
*response.account,
|
|
|
|
request.Password,
|
|
|
|
*response.settings,
|
|
|
|
response.nodeConfig,
|
|
|
|
response.subAccounts,
|
|
|
|
response.chatPrivateKey, //request.WhisperPrivateKey,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("start node", err)
|
|
|
|
return nil, errors.Wrap(err, "failed to start node")
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.account, nil
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-03-28 08:12:37 +00:00
|
|
|
func (b *GethStatusBackend) GetKeyUIDByMnemonic(mnemonic string) (string, error) {
|
|
|
|
accountGenerator := b.accountManager.AccountsGenerator()
|
|
|
|
|
|
|
|
info, err := accountGenerator.ImportMnemonic(mnemonic, "")
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return info.KeyUID, nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
type prepareAccountInput struct {
|
|
|
|
customizationColorClock uint64
|
|
|
|
accountID string
|
|
|
|
keyUID string
|
|
|
|
address string
|
|
|
|
mnemonic string
|
|
|
|
restoringAccount bool
|
|
|
|
derivedAddresses map[string]generator.AccountInfo
|
|
|
|
fetchBackup bool
|
|
|
|
keyStoreDir string
|
|
|
|
opts []params.Option
|
|
|
|
}
|
|
|
|
|
|
|
|
type accountBundle struct {
|
|
|
|
account *multiaccounts.Account
|
|
|
|
settings *settings.Settings
|
|
|
|
nodeConfig *params.NodeConfig
|
|
|
|
subAccounts []*accounts.Account
|
|
|
|
chatPrivateKey *ecdsa.PrivateKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) generateOrImportAccount(mnemonic string, customizationColorClock uint64, fetchBackup bool, request *requests.CreateAccount, opts ...params.Option) (*accountBundle, error) {
|
2024-06-05 00:00:51 +00:00
|
|
|
info, err := b.generateAccountInfo(mnemonic)
|
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, err
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2024-06-05 13:03:34 +00:00
|
|
|
keyStoreDir, err := b.initKeyStoreDirWithAccount(request.RootDataDir, info.KeyUID)
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, err
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
derivedAddresses, err := b.getDerivedAddresses(info.ID)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
input := &prepareAccountInput{
|
|
|
|
customizationColorClock: customizationColorClock,
|
|
|
|
accountID: info.ID,
|
|
|
|
keyUID: info.KeyUID,
|
|
|
|
address: info.Address,
|
|
|
|
mnemonic: info.Mnemonic,
|
|
|
|
restoringAccount: mnemonic != "",
|
|
|
|
derivedAddresses: derivedAddresses,
|
|
|
|
fetchBackup: fetchBackup,
|
|
|
|
keyStoreDir: keyStoreDir,
|
|
|
|
opts: opts,
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.prepareNodeAccount(request, input)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) prepareNodeAccount(request *requests.CreateAccount, input *prepareAccountInput) (*accountBundle, error) {
|
|
|
|
var err error
|
|
|
|
response := &accountBundle{}
|
|
|
|
|
|
|
|
if request.KeycardInstanceUID != "" {
|
|
|
|
request.Password = input.derivedAddresses[pathEncryption].PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: I intentionally left this condition separately and not an `else` branch. Technically it's an `else`,
|
|
|
|
// but the statements inside are not the opposite statement of the first statement. It's just kinda like this:
|
|
|
|
// - replace password when we're using keycard
|
|
|
|
// - store account when we're not using keycard
|
|
|
|
if request.KeycardInstanceUID == "" {
|
|
|
|
err = b.storeAccount(input.accountID, request.Password, paths)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
response.account, err = b.buildAccount(request, input)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to build account")
|
|
|
|
}
|
|
|
|
|
|
|
|
response.settings, err = b.prepareSettings(request, input)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to prepare settings")
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
response.nodeConfig, err = b.prepareConfig(request, input, response.settings.InstallationID)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to prepare node config")
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
response.subAccounts, err = b.prepareSubAccounts(request, input)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to prepare sub accounts")
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
response, err = b.prepareForKeycard(request, input, response)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to prepare for keycard")
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return response, nil
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-05 13:03:34 +00:00
|
|
|
func (b *GethStatusBackend) initKeyStoreDirWithAccount(rootDataDir, keyUID string) (string, error) {
|
|
|
|
b.UpdateRootDataDir(rootDataDir)
|
2024-06-05 00:00:51 +00:00
|
|
|
keystoreDir := keystoreRelativePath
|
|
|
|
userKeyStoreDir := filepath.Join(keystoreDir, keyUID)
|
|
|
|
// Initialize keystore dir with account
|
|
|
|
return userKeyStoreDir, b.accountManager.InitKeystore(filepath.Join(b.rootDataDir, userKeyStoreDir))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) generateAccountInfo(mnemonic string) (*generator.GeneratedAccountInfo, error) {
|
2023-03-21 17:02:04 +00:00
|
|
|
accountGenerator := b.accountManager.AccountsGenerator()
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2023-03-21 17:02:04 +00:00
|
|
|
var info generator.GeneratedAccountInfo
|
2024-06-05 00:00:51 +00:00
|
|
|
var err error
|
2023-03-21 17:02:04 +00:00
|
|
|
if mnemonic == "" {
|
|
|
|
// generate 1(n) account with default mnemonic length and no passphrase
|
|
|
|
generatedAccountInfos, err := accountGenerator.Generate(defaultMnemonicLength, 1, "")
|
|
|
|
info = generatedAccountInfos[0]
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2023-03-21 17:02:04 +00:00
|
|
|
if err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2023-03-21 17:02:04 +00:00
|
|
|
info, err = accountGenerator.ImportMnemonic(mnemonic, "")
|
|
|
|
if err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
return &info, nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) storeAccount(id string, password string, paths []string) error {
|
|
|
|
accountGenerator := b.accountManager.AccountsGenerator()
|
|
|
|
|
|
|
|
_, err := accountGenerator.StoreAccount(id, password)
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
_, err = accountGenerator.StoreDerivedAccounts(id, password, paths)
|
2023-10-10 16:12:38 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return err
|
2023-03-29 11:36:13 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) buildAccount(request *requests.CreateAccount, input *prepareAccountInput) (*multiaccounts.Account, error) {
|
|
|
|
err := b.OpenAccounts()
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
acc := &multiaccounts.Account{
|
|
|
|
KeyUID: input.keyUID,
|
2023-07-18 13:35:06 +00:00
|
|
|
Name: request.DisplayName,
|
|
|
|
CustomizationColor: multiacccommon.CustomizationColor(request.CustomizationColor),
|
2024-06-26 11:14:27 +00:00
|
|
|
CustomizationColorClock: input.customizationColorClock,
|
2024-03-28 15:01:44 +00:00
|
|
|
KDFIterations: request.KdfIterations,
|
2024-04-18 07:34:00 +00:00
|
|
|
Timestamp: time.Now().Unix(),
|
2024-03-28 15:01:44 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
if acc.KDFIterations == 0 {
|
|
|
|
acc.KDFIterations = dbsetup.ReducedKDFIterationsNumber
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
2024-03-28 15:01:44 +00:00
|
|
|
|
2023-06-30 01:18:42 +00:00
|
|
|
if request.ImagePath != "" {
|
2024-03-28 15:01:44 +00:00
|
|
|
imageCropRectangle := request.ImageCropRectangle
|
|
|
|
if imageCropRectangle == nil {
|
|
|
|
// Default crop rectangle used by mobile
|
|
|
|
imageCropRectangle = &requests.ImageCropRectangle{
|
|
|
|
Ax: 0,
|
|
|
|
Ay: 0,
|
|
|
|
Bx: 1000,
|
|
|
|
By: 1000,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
iis, err := images.GenerateIdentityImages(request.ImagePath,
|
|
|
|
imageCropRectangle.Ax, imageCropRectangle.Ay, imageCropRectangle.Bx, imageCropRectangle.By)
|
|
|
|
|
2023-06-30 01:18:42 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, err
|
2023-06-30 01:18:42 +00:00
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
acc.Images = iis
|
2023-06-30 01:18:42 +00:00
|
|
|
}
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return acc, nil
|
2024-06-05 00:00:51 +00:00
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) prepareSettings(request *requests.CreateAccount, input *prepareAccountInput) (*settings.Settings, error) {
|
|
|
|
settings, err := defaultSettings(input.keyUID, input.address, input.derivedAddresses)
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
2023-06-27 07:54:49 +00:00
|
|
|
|
|
|
|
settings.DeviceName = request.DeviceName
|
2023-03-16 20:51:41 +00:00
|
|
|
settings.DisplayName = request.DisplayName
|
2023-03-27 07:33:03 +00:00
|
|
|
settings.PreviewPrivacy = request.PreviewPrivacy
|
2023-03-28 14:47:58 +00:00
|
|
|
settings.CurrentNetwork = request.CurrentNetwork
|
2024-03-26 11:38:16 +00:00
|
|
|
settings.TestNetworksEnabled = request.TestNetworksEnabled
|
2024-06-26 11:14:27 +00:00
|
|
|
if !input.restoringAccount {
|
|
|
|
settings.Mnemonic = &input.mnemonic
|
2023-10-18 10:02:35 +00:00
|
|
|
settings.OmitTransfersHistoryScan = true
|
2024-01-29 13:39:58 +00:00
|
|
|
// TODO(rasom): uncomment it as soon as address will be properly
|
|
|
|
// marked as shown on mobile client
|
|
|
|
//settings.MnemonicWasNotShown = true
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2024-07-11 09:57:56 +00:00
|
|
|
if request.WakuV2Fleet != "" {
|
|
|
|
settings.Fleet = &request.WakuV2Fleet
|
|
|
|
}
|
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
return settings, nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) prepareConfig(request *requests.CreateAccount, input *prepareAccountInput, installationID string) (*params.NodeConfig, error) {
|
|
|
|
nodeConfig, err := defaultNodeConfig(installationID, request, input.opts...)
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
nodeConfig.ProcessBackedupMessages = input.fetchBackup
|
2023-03-16 14:49:25 +00:00
|
|
|
|
2023-05-18 14:00:55 +00:00
|
|
|
// when we set nodeConfig.KeyStoreDir, value of nodeConfig.KeyStoreDir should not contain the rootDataDir
|
|
|
|
// loadNodeConfig will add rootDataDir to nodeConfig.KeyStoreDir
|
2024-06-26 11:14:27 +00:00
|
|
|
nodeConfig.KeyStoreDir = input.keyStoreDir
|
2023-03-29 11:36:13 +00:00
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
return nodeConfig, nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) prepareSubAccounts(request *requests.CreateAccount, input *prepareAccountInput) ([]*accounts.Account, error) {
|
|
|
|
walletDerivedAccount := input.derivedAddresses[pathDefaultWallet]
|
2023-03-16 14:49:25 +00:00
|
|
|
walletAccount := &accounts.Account{
|
2024-06-26 11:14:27 +00:00
|
|
|
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
|
|
|
|
KeyUID: input.keyUID,
|
|
|
|
Address: types.HexToAddress(walletDerivedAccount.Address),
|
|
|
|
ColorID: multiacccommon.CustomizationColor(request.CustomizationColor),
|
|
|
|
Emoji: request.Emoji,
|
|
|
|
Wallet: true,
|
|
|
|
Path: pathDefaultWallet,
|
|
|
|
Name: walletAccountDefaultName,
|
|
|
|
AddressWasNotShown: !input.restoringAccount,
|
|
|
|
}
|
|
|
|
|
|
|
|
chatDerivedAccount := input.derivedAddresses[pathDefaultChat]
|
2023-03-16 14:49:25 +00:00
|
|
|
chatAccount := &accounts.Account{
|
|
|
|
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
|
2024-06-26 11:14:27 +00:00
|
|
|
KeyUID: input.keyUID,
|
2023-03-16 14:49:25 +00:00
|
|
|
Address: types.HexToAddress(chatDerivedAccount.Address),
|
2023-03-16 20:51:41 +00:00
|
|
|
Name: request.DisplayName,
|
2023-03-16 14:49:25 +00:00
|
|
|
Chat: true,
|
|
|
|
Path: pathDefaultChat,
|
|
|
|
}
|
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
return []*accounts.Account{walletAccount, chatAccount}, nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
func (b *GethStatusBackend) prepareForKeycard(request *requests.CreateAccount, input *prepareAccountInput, response *accountBundle) (*accountBundle, error) {
|
|
|
|
if request.KeycardInstanceUID == "" {
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
kp := wallet.NewKeycardPairings()
|
|
|
|
kp.SetKeycardPairingsFile(response.nodeConfig.KeycardPairingDataFile)
|
|
|
|
pairings, err := kp.GetPairings()
|
2023-03-16 14:49:25 +00:00
|
|
|
if err != nil {
|
2024-06-26 11:14:27 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to get keycard pairings")
|
|
|
|
}
|
|
|
|
|
|
|
|
keycard, ok := pairings[request.KeycardInstanceUID]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("keycard not found in pairings file")
|
|
|
|
}
|
|
|
|
|
|
|
|
response.settings.KeycardInstanceUID = request.KeycardInstanceUID
|
|
|
|
response.settings.KeycardPairedOn = time.Now().Unix()
|
|
|
|
response.settings.KeycardPairing = keycard.Key
|
|
|
|
response.account.KeycardPairing = keycard.Key
|
|
|
|
|
|
|
|
privateKeyHex := strings.TrimPrefix(input.derivedAddresses[pathDefaultChat].PrivateKey, "0x")
|
|
|
|
response.chatPrivateKey, err = crypto.HexToECDSA(privateKeyHex)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to parse chat private key hex")
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
2023-03-16 20:51:41 +00:00
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) getDerivedAddresses(id string) (map[string]generator.AccountInfo, error) {
|
|
|
|
accountGenerator := b.accountManager.AccountsGenerator()
|
|
|
|
return accountGenerator.DeriveAddresses(id, paths)
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2024-03-22 10:55:09 +00:00
|
|
|
// CreateAccountAndLogin creates a new account and logs in with it.
|
|
|
|
// NOTE: requests.CreateAccount is used for public, params.Option maybe used for internal usage.
|
|
|
|
func (b *GethStatusBackend) CreateAccountAndLogin(request *requests.CreateAccount, opts ...params.Option) (*multiaccounts.Account, error) {
|
2024-03-28 15:01:44 +00:00
|
|
|
validation := &requests.CreateAccountValidation{
|
|
|
|
AllowEmptyDisplayName: false,
|
|
|
|
}
|
|
|
|
if err := request.Validate(validation); err != nil {
|
2023-10-10 16:12:38 +00:00
|
|
|
return nil, err
|
2023-03-21 17:02:04 +00:00
|
|
|
}
|
2024-06-05 00:00:51 +00:00
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
response, err := b.generateOrImportAccount("", 1, false, request, opts...)
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
err = b.StartNodeWithAccountAndInitialConfig(
|
|
|
|
*response.account,
|
|
|
|
request.Password,
|
|
|
|
*response.settings,
|
|
|
|
response.nodeConfig,
|
|
|
|
response.subAccounts,
|
|
|
|
response.chatPrivateKey,
|
|
|
|
)
|
|
|
|
|
2024-06-05 00:00:51 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("start node", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
return response.account, nil
|
2023-03-16 14:49:25 +00:00
|
|
|
}
|
|
|
|
|
2023-01-25 10:25:07 +00:00
|
|
|
func (b *GethStatusBackend) ConvertToRegularAccount(mnemonic string, currPassword string, newPassword string) error {
|
2023-08-02 07:39:58 +00:00
|
|
|
messenger := b.Messenger()
|
|
|
|
if messenger == nil {
|
|
|
|
return errors.New("cannot resolve messenger instance")
|
|
|
|
}
|
|
|
|
|
2023-01-25 10:25:07 +00:00
|
|
|
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
|
|
|
|
accountInfo, err := b.accountManager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(accountInfo.KeyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
err = b.ensureDBsOpened(multiaccounts.Account{KeyUID: accountInfo.KeyUID, KDFIterations: kdfIterations}, currPassword)
|
2023-01-25 10:25:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-07-25 15:17:17 +00:00
|
|
|
knownAccounts, err := db.GetActiveAccounts()
|
2023-01-25 10:25:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We add these two paths, cause others will be added via `StoreAccount` function call
|
|
|
|
const pathWalletRoot = "m/44'/60'/0'/0"
|
|
|
|
const pathEIP1581 = "m/43'/60'/1581'"
|
|
|
|
var paths []string
|
|
|
|
paths = append(paths, pathWalletRoot, pathEIP1581)
|
|
|
|
for _, acc := range knownAccounts {
|
|
|
|
if accountInfo.KeyUID == acc.KeyUID {
|
|
|
|
paths = append(paths, acc.Path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = b.accountManager.AccountsGenerator().StoreAccount(accountInfo.ID, currPassword)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = b.accountManager.AccountsGenerator().StoreDerivedAccounts(accountInfo.ID, currPassword, paths)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = b.multiaccountsDB.UpdateAccountKeycardPairing(accountInfo.KeyUID, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-02 07:39:58 +00:00
|
|
|
err = messenger.DeleteAllKeycardsWithKeyUID(context.Background(), accountInfo.KeyUID)
|
2023-01-25 10:25:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = db.SaveSettingField(settings.KeycardInstanceUID, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = db.SaveSettingField(settings.KeycardPairedOn, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = db.SaveSettingField(settings.KeycardPairing, "")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-02 07:39:58 +00:00
|
|
|
err = db.SaveSettingField(settings.ProfileMigrationNeeded, false)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
err = b.closeDBs()
|
2023-01-25 10:25:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.ChangeDatabasePassword(accountInfo.KeyUID, currPassword, newPassword)
|
|
|
|
}
|
|
|
|
|
2021-07-20 11:48:10 +00:00
|
|
|
func (b *GethStatusBackend) VerifyDatabasePassword(keyUID string, password string) error {
|
2022-09-27 20:27:20 +00:00
|
|
|
kdfIterations, err := b.multiaccountsDB.GetAccountKDFIterationsNumber(keyUID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
if !b.appDBExists(keyUID) || !b.walletDBExists(keyUID) {
|
|
|
|
return errors.New("One or more databases not created")
|
|
|
|
}
|
|
|
|
|
2023-08-11 11:25:14 +00:00
|
|
|
err = b.ensureDBsOpened(multiaccounts.Account{KeyUID: keyUID, KDFIterations: kdfIterations}, password)
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
err = b.closeDBs()
|
2021-07-20 11:48:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-01 12:57:05 +00:00
|
|
|
func enrichMultiAccountBySubAccounts(account *multiaccounts.Account, subaccs []*accounts.Account) error {
|
2023-05-18 06:27:16 +00:00
|
|
|
if account.ColorHash != nil && account.ColorID != 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, acc := range subaccs {
|
|
|
|
subaccs[i].KeyUID = account.KeyUID
|
|
|
|
if acc.Chat {
|
2023-06-01 12:57:05 +00:00
|
|
|
pk := string(acc.PublicKey.Bytes())
|
|
|
|
colorHash, err := colorhash.GenerateFor(pk)
|
2023-05-18 06:27:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
account.ColorHash = colorHash
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
colorID, err := identityutils.ToColorID(pk)
|
2023-05-18 06:27:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
account.ColorID = colorID
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-06-01 12:57:05 +00:00
|
|
|
func enrichMultiAccountByPublicKey(account *multiaccounts.Account, publicKey types.HexBytes) error {
|
|
|
|
pk := string(publicKey.Bytes())
|
|
|
|
colorHash, err := colorhash.GenerateFor(pk)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
account.ColorHash = colorHash
|
|
|
|
|
2023-06-21 14:09:55 +00:00
|
|
|
colorID, err := identityutils.ToColorID(pk)
|
2023-06-01 12:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
account.ColorID = colorID
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-26 11:14:27 +00:00
|
|
|
// Deprecated: Use CreateAccountAndLogin instead
|
|
|
|
func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(
|
|
|
|
account multiaccounts.Account,
|
|
|
|
password string,
|
|
|
|
settings settings.Settings,
|
|
|
|
nodecfg *params.NodeConfig,
|
|
|
|
subaccs []*accounts.Account,
|
|
|
|
keyHex string,
|
|
|
|
) error {
|
2023-06-01 12:57:05 +00:00
|
|
|
err := enrichMultiAccountBySubAccounts(&account, subaccs)
|
2019-12-27 09:58:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-18 06:27:16 +00:00
|
|
|
err = b.SaveAccount(account)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-11 11:25:14 +00:00
|
|
|
err = b.ensureDBsOpened(account, password)
|
2019-12-27 09:58:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = b.saveAccountsAndSettings(settings, nodecfg, subaccs)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-20 10:08:57 +00:00
|
|
|
return b.StartNodeWithKey(account, password, keyHex, nodecfg)
|
2019-12-27 09:58:25 +00:00
|
|
|
}
|
|
|
|
|
2021-12-21 14:27:18 +00:00
|
|
|
// StartNodeWithAccountAndInitialConfig is used after account and config was generated.
|
2019-11-23 17:57:05 +00:00
|
|
|
// In current setup account name and config is generated on the client side. Once/if it will be generated on
|
|
|
|
// status-go side this flow can be simplified.
|
2024-06-26 11:14:27 +00:00
|
|
|
// TODO: Consider passing accountBundle here directly
|
2021-12-21 14:27:18 +00:00
|
|
|
func (b *GethStatusBackend) StartNodeWithAccountAndInitialConfig(
|
2019-12-27 09:58:25 +00:00
|
|
|
account multiaccounts.Account,
|
|
|
|
password string,
|
2022-03-23 18:47:00 +00:00
|
|
|
settings settings.Settings,
|
2019-12-27 09:58:25 +00:00
|
|
|
nodecfg *params.NodeConfig,
|
2022-05-18 10:42:51 +00:00
|
|
|
subaccs []*accounts.Account,
|
2024-06-26 11:14:27 +00:00
|
|
|
chatKey *ecdsa.PrivateKey,
|
2019-12-27 09:58:25 +00:00
|
|
|
) error {
|
2023-06-01 12:57:05 +00:00
|
|
|
err := enrichMultiAccountBySubAccounts(&account, subaccs)
|
2023-05-18 06:27:16 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = b.SaveAccount(account)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-11 11:25:14 +00:00
|
|
|
err = b.ensureDBsOpened(account, password)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
err = b.saveAccountsAndSettings(settings, nodecfg, subaccs)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-06-26 11:14:27 +00:00
|
|
|
return b.StartNodeWithAccount(account, password, nodecfg, chatKey)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
// TODO: change in `saveAccountsAndSettings` function param `subaccs []*accounts.Account` parameter to `profileKeypair *accounts.Keypair` parameter
|
2022-05-18 10:42:51 +00:00
|
|
|
func (b *GethStatusBackend) saveAccountsAndSettings(settings settings.Settings, nodecfg *params.NodeConfig, subaccs []*accounts.Account) error {
|
2019-11-23 17:57:05 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-03-23 18:47:00 +00:00
|
|
|
accdb, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = accdb.CreateSettings(settings, *nodecfg)
|
2019-12-27 09:58:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-11-30 09:41:35 +00:00
|
|
|
|
|
|
|
// In case of setting up new account either way (creating new, importing seed phrase, keycard account...) we should not
|
|
|
|
// back up any data after login, as it was the case before, that's the reason why we're setting last backup time to the time
|
|
|
|
// when an account was created.
|
|
|
|
now := time.Now().Unix()
|
|
|
|
err = accdb.SetLastBackup(uint64(now))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-05-16 10:48:00 +00:00
|
|
|
keypair := &accounts.Keypair{
|
|
|
|
KeyUID: settings.KeyUID,
|
|
|
|
Name: settings.DisplayName,
|
|
|
|
Type: accounts.KeypairTypeProfile,
|
|
|
|
DerivedFrom: settings.Address.String(),
|
|
|
|
LastUsedDerivationIndex: 0,
|
|
|
|
}
|
|
|
|
|
2023-07-27 13:20:40 +00:00
|
|
|
// When creating a new account, the chat account should have position -1, cause it doesn't participate
|
|
|
|
// in the wallet view and default wallet account should be at position 0.
|
2023-05-16 10:48:00 +00:00
|
|
|
for _, acc := range subaccs {
|
2023-07-27 13:20:40 +00:00
|
|
|
if acc.Chat {
|
|
|
|
acc.Position = -1
|
|
|
|
}
|
|
|
|
if acc.Wallet {
|
|
|
|
acc.Position = 0
|
|
|
|
}
|
2023-05-16 10:48:00 +00:00
|
|
|
acc.Operable = accounts.AccountFullyOperable
|
|
|
|
keypair.Accounts = append(keypair.Accounts, acc)
|
|
|
|
}
|
|
|
|
|
|
|
|
return accdb.SaveOrUpdateKeypair(keypair)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
func (b *GethStatusBackend) loadNodeConfig(inputNodeCfg *params.NodeConfig) error {
|
2019-11-23 17:57:05 +00:00
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-02-27 14:46:17 +00:00
|
|
|
|
|
|
|
conf, err := nodecfg.GetNodeConfigFromDB(b.appDB)
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
2022-02-27 14:46:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if inputNodeCfg != nil {
|
2022-10-14 08:50:36 +00:00
|
|
|
// If an installationID is provided, we override it
|
|
|
|
if conf != nil && conf.ShhextConfig.InstallationID != "" {
|
|
|
|
inputNodeCfg.ShhextConfig.InstallationID = conf.ShhextConfig.InstallationID
|
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
conf, err = b.OverwriteNodeConfigValues(conf, inputNodeCfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
2020-03-30 11:42:21 +00:00
|
|
|
|
2021-06-16 20:19:45 +00:00
|
|
|
// Start WakuV1 if WakuV2 is not enabled
|
|
|
|
conf.WakuConfig.Enabled = !conf.WakuV2Config.Enabled
|
2019-11-23 17:57:05 +00:00
|
|
|
// NodeConfig.Version should be taken from params.Version
|
|
|
|
// which is set at the compile time.
|
|
|
|
// What's cached is usually outdated so we overwrite it here.
|
|
|
|
conf.Version = params.Version
|
2022-05-09 13:07:57 +00:00
|
|
|
conf.RootDataDir = b.rootDataDir
|
2020-02-03 09:20:04 +00:00
|
|
|
conf.DataDir = filepath.Join(b.rootDataDir, conf.DataDir)
|
2024-06-05 13:03:34 +00:00
|
|
|
conf.KeyStoreDir = filepath.Join(b.rootDataDir, conf.KeyStoreDir)
|
|
|
|
|
|
|
|
if _, err = os.Stat(conf.RootDataDir); os.IsNotExist(err) {
|
|
|
|
if err := os.MkdirAll(conf.RootDataDir, os.ModePerm); err != nil {
|
|
|
|
b.log.Warn("failed to create data directory", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-03 09:20:04 +00:00
|
|
|
if len(conf.LogDir) == 0 {
|
|
|
|
conf.LogFile = filepath.Join(b.rootDataDir, conf.LogFile)
|
|
|
|
} else {
|
|
|
|
conf.LogFile = filepath.Join(conf.LogDir, conf.LogFile)
|
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
b.config = conf
|
|
|
|
|
2024-02-02 21:08:48 +00:00
|
|
|
if inputNodeCfg != nil && inputNodeCfg.RuntimeLogLevel != "" {
|
|
|
|
b.config.LogLevel = inputNodeCfg.RuntimeLogLevel
|
|
|
|
}
|
|
|
|
|
2022-02-27 14:46:17 +00:00
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2022-01-12 20:04:43 +00:00
|
|
|
func (b *GethStatusBackend) saveNodeConfig(n *params.NodeConfig) error {
|
|
|
|
err := nodecfg.SaveNodeConfig(b.appDB, n)
|
2021-12-21 14:27:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-29 19:22:10 +00:00
|
|
|
func (b *GethStatusBackend) GetNodeConfig() (*params.NodeConfig, error) {
|
2022-05-03 14:47:56 +00:00
|
|
|
return nodecfg.GetNodeConfigFromDB(b.appDB)
|
2021-04-29 19:22:10 +00:00
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) {
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
err = fmt.Errorf("node crashed on start: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-12-01 14:37:48 +00:00
|
|
|
b.log.Info("status-go version details", "version", params.Version, "commit", params.GitCommit)
|
2020-08-20 09:05:39 +00:00
|
|
|
b.log.Debug("starting node with config", "config", config)
|
2020-02-26 19:35:47 +00:00
|
|
|
// Update config with some defaults.
|
2020-08-20 09:05:39 +00:00
|
|
|
if err := config.UpdateWithDefaults(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-02-27 15:04:49 +00:00
|
|
|
// Updating node config
|
|
|
|
b.config = config
|
|
|
|
|
2020-08-20 09:05:39 +00:00
|
|
|
b.log.Debug("updated config with defaults", "config", config)
|
2020-02-26 19:35:47 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// Start by validating configuration
|
|
|
|
if err := config.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-02-08 13:56:25 +00:00
|
|
|
if b.accountManager.GetManager() == nil {
|
|
|
|
err = b.accountManager.InitKeystore(config.KeyStoreDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
manager := b.accountManager.GetManager()
|
|
|
|
if manager == nil {
|
|
|
|
return errors.New("ethereum accounts.Manager is nil")
|
|
|
|
}
|
2023-02-08 13:56:25 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
if err = b.statusNode.StartWithOptions(config, node.StartOptions{
|
|
|
|
// The peers discovery protocols are started manually after
|
|
|
|
// `node.ready` signal is sent.
|
|
|
|
// It was discussed in https://github.com/status-im/status-go/pull/1333.
|
|
|
|
StartDiscovery: false,
|
|
|
|
AccountsManager: manager,
|
|
|
|
}); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-03-10 12:53:40 +00:00
|
|
|
b.accountManager.SetRPCClient(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
|
2019-11-23 17:57:05 +00:00
|
|
|
signal.SendNodeStarted()
|
|
|
|
|
|
|
|
b.transactor.SetNetworkID(config.NetworkID)
|
|
|
|
b.transactor.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
|
2021-07-12 09:44:26 +00:00
|
|
|
b.personalAPI.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout)
|
2019-11-23 17:57:05 +00:00
|
|
|
|
|
|
|
if err = b.registerHandlers(); err != nil {
|
|
|
|
b.log.Error("Handler registration failed", "err", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b.log.Info("Handlers registered")
|
|
|
|
|
2019-12-18 22:24:38 +00:00
|
|
|
// Handle a case when a node is stopped and resumed.
|
|
|
|
// If there is no account selected, an error is returned.
|
|
|
|
if _, err := b.accountManager.SelectedChatAccount(); err == nil {
|
2020-11-24 13:13:46 +00:00
|
|
|
if err := b.injectAccountsIntoServices(); err != nil {
|
2019-12-18 22:24:38 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if err != account.ErrNoAccountSelected {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-12 12:45:32 +00:00
|
|
|
if b.statusNode.WalletService() != nil {
|
|
|
|
b.statusNode.WalletService().KeycardPairings().SetKeycardPairingsFile(config.KeycardPairingDataFile)
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
signal.SendNodeReady()
|
|
|
|
|
|
|
|
if err := b.statusNode.StartDiscovery(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StopNode stop Status node. Stopped node cannot be resumed.
|
|
|
|
func (b *GethStatusBackend) StopNode() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
return b.stopNode()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) stopNode() error {
|
2021-07-07 06:11:09 +00:00
|
|
|
if b.statusNode == nil || !b.IsNodeRunning() {
|
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
2023-07-12 22:29:38 +00:00
|
|
|
if !b.LocalPairingStateManager.IsPairing() {
|
2023-06-01 05:33:57 +00:00
|
|
|
defer signal.SendNodeStopped()
|
|
|
|
}
|
2021-07-07 06:11:09 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
return b.statusNode.Stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
// RestartNode restart running Status node, fails if node is not running
|
|
|
|
func (b *GethStatusBackend) RestartNode() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
|
|
if !b.IsNodeRunning() {
|
|
|
|
return node.ErrNoRunningNode
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := b.stopNode(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-02-27 14:46:17 +00:00
|
|
|
|
|
|
|
return b.startNode(b.config)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ResetChainData remove chain data from data directory.
|
|
|
|
// Node is stopped, and new node is started, with clean data directory.
|
|
|
|
func (b *GethStatusBackend) ResetChainData() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2022-02-27 14:46:17 +00:00
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
if err := b.stopNode(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// config is cleaned when node is stopped
|
2022-02-27 14:46:17 +00:00
|
|
|
if err := b.statusNode.ResetChainData(b.config); err != nil {
|
2019-11-23 17:57:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
signal.SendChainDataRemoved()
|
2022-02-27 14:46:17 +00:00
|
|
|
return b.startNode(b.config)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// CallRPC executes public RPC requests on node's in-proc RPC server.
|
|
|
|
func (b *GethStatusBackend) CallRPC(inputJSON string) (string, error) {
|
|
|
|
client := b.statusNode.RPCClient()
|
|
|
|
if client == nil {
|
|
|
|
return "", ErrRPCClientUnavailable
|
|
|
|
}
|
|
|
|
return client.CallRaw(inputJSON), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server.
|
|
|
|
func (b *GethStatusBackend) CallPrivateRPC(inputJSON string) (string, error) {
|
2021-07-09 13:19:33 +00:00
|
|
|
client := b.statusNode.RPCClient()
|
2019-11-23 17:57:05 +00:00
|
|
|
if client == nil {
|
|
|
|
return "", ErrRPCClientUnavailable
|
|
|
|
}
|
|
|
|
return client.CallRaw(inputJSON), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendTransaction creates a new transaction and waits until it's complete.
|
|
|
|
func (b *GethStatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) {
|
|
|
|
verifiedAccount, err := b.getVerifiedWalletAccount(sendArgs.From.String(), password)
|
|
|
|
if err != nil {
|
|
|
|
return hash, err
|
|
|
|
}
|
|
|
|
|
2024-03-12 09:15:30 +00:00
|
|
|
return b.transactor.SendTransaction(sendArgs, verifiedAccount)
|
2022-06-09 13:09:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) SendTransactionWithChainID(chainID uint64, sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) {
|
|
|
|
verifiedAccount, err := b.getVerifiedWalletAccount(sendArgs.From.String(), password)
|
|
|
|
if err != nil {
|
|
|
|
return hash, err
|
|
|
|
}
|
|
|
|
|
2024-03-12 09:15:30 +00:00
|
|
|
return b.transactor.SendTransactionWithChainID(chainID, sendArgs, verifiedAccount)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) {
|
2024-05-26 08:31:13 +00:00
|
|
|
txWithSignature, err := b.transactor.BuildTransactionWithSignature(b.transactor.NetworkID(), sendArgs, sig)
|
|
|
|
if err != nil {
|
|
|
|
return hash, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.transactor.SendTransactionWithSignature(common.Address(sendArgs.From), sendArgs.Symbol, sendArgs.MultiTransactionID, txWithSignature)
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// HashTransaction validate the transaction and returns new sendArgs and the transaction hash.
|
|
|
|
func (b *GethStatusBackend) HashTransaction(sendArgs transactions.SendTxArgs) (transactions.SendTxArgs, types.Hash, error) {
|
|
|
|
return b.transactor.HashTransaction(sendArgs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignMessage checks the pwd vs the selected account and passes on the signParams
|
|
|
|
// to personalAPI for message signature
|
|
|
|
func (b *GethStatusBackend) SignMessage(rpcParams personal.SignParams) (types.HexBytes, error) {
|
|
|
|
verifiedAccount, err := b.getVerifiedWalletAccount(rpcParams.Address, rpcParams.Password)
|
|
|
|
if err != nil {
|
|
|
|
return types.HexBytes{}, err
|
|
|
|
}
|
|
|
|
return b.personalAPI.Sign(rpcParams, verifiedAccount)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Recover calls the personalAPI to return address associated with the private
|
|
|
|
// key that was used to calculate the signature in the message
|
|
|
|
func (b *GethStatusBackend) Recover(rpcParams personal.RecoverParams) (types.Address, error) {
|
|
|
|
return b.personalAPI.Recover(rpcParams)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignTypedData accepts data and password. Gets verified account and signs typed data.
|
|
|
|
func (b *GethStatusBackend) SignTypedData(typed typeddata.TypedData, address string, password string) (types.HexBytes, error) {
|
|
|
|
account, err := b.getVerifiedWalletAccount(address, password)
|
|
|
|
if err != nil {
|
|
|
|
return types.HexBytes{}, err
|
|
|
|
}
|
|
|
|
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
|
|
|
|
sig, err := typeddata.Sign(typed, account.AccountKey.PrivateKey, chain)
|
|
|
|
if err != nil {
|
|
|
|
return types.HexBytes{}, err
|
|
|
|
}
|
|
|
|
return types.HexBytes(sig), err
|
|
|
|
}
|
|
|
|
|
2020-10-29 07:46:02 +00:00
|
|
|
// SignTypedDataV4 accepts data and password. Gets verified account and signs typed data.
|
|
|
|
func (b *GethStatusBackend) SignTypedDataV4(typed signercore.TypedData, address string, password string) (types.HexBytes, error) {
|
|
|
|
account, err := b.getVerifiedWalletAccount(address, password)
|
|
|
|
if err != nil {
|
|
|
|
return types.HexBytes{}, err
|
|
|
|
}
|
|
|
|
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
|
|
|
|
sig, err := typeddata.SignTypedDataV4(typed, account.AccountKey.PrivateKey, chain)
|
|
|
|
if err != nil {
|
|
|
|
return types.HexBytes{}, err
|
|
|
|
}
|
|
|
|
return types.HexBytes(sig), err
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// HashTypedData generates the hash of TypedData.
|
|
|
|
func (b *GethStatusBackend) HashTypedData(typed typeddata.TypedData) (types.Hash, error) {
|
|
|
|
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
|
|
|
|
hash, err := typeddata.ValidateAndHash(typed, chain)
|
|
|
|
if err != nil {
|
|
|
|
return types.Hash{}, err
|
|
|
|
}
|
|
|
|
return types.Hash(hash), err
|
|
|
|
}
|
|
|
|
|
2020-10-29 07:46:02 +00:00
|
|
|
// HashTypedDataV4 generates the hash of TypedData.
|
|
|
|
func (b *GethStatusBackend) HashTypedDataV4(typed signercore.TypedData) (types.Hash, error) {
|
|
|
|
chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID)
|
|
|
|
hash, err := typeddata.HashTypedDataV4(typed, chain)
|
|
|
|
if err != nil {
|
|
|
|
return types.Hash{}, err
|
|
|
|
}
|
|
|
|
return types.Hash(hash), err
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) {
|
|
|
|
config := b.StatusNode().Config()
|
2022-03-23 18:47:00 +00:00
|
|
|
db, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to create new *Database instance", "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-12-11 13:59:37 +00:00
|
|
|
exists, err := db.AddressExists(types.HexToAddress(address))
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to query db for a given address", "address", address, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !exists {
|
|
|
|
b.log.Error("failed to get a selected account", "err", transactions.ErrInvalidTxSender)
|
|
|
|
return nil, transactions.ErrAccountDoesntExist
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := b.accountManager.VerifyAccountPassword(config.KeyStoreDir, address, password)
|
2022-07-06 16:12:49 +00:00
|
|
|
if _, ok := err.(*account.ErrCannotLocateKeyFile); ok {
|
|
|
|
key, err = b.generatePartialAccountKey(db, address, password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to verify account", "account", address, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &account.SelectedExtKey{
|
2019-12-19 18:27:27 +00:00
|
|
|
Address: key.Address,
|
2019-11-23 17:57:05 +00:00
|
|
|
AccountKey: key,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2022-07-06 16:12:49 +00:00
|
|
|
func (b *GethStatusBackend) generatePartialAccountKey(db *accounts.Database, address string, password string) (*types.Key, error) {
|
|
|
|
dbPath, err := db.GetPath(types.HexToAddress(address))
|
|
|
|
path := "m/" + dbPath[strings.LastIndex(dbPath, "/")+1:]
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to get path for given account address", "account", address, "error", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rootAddress, err := db.GetWalletRootAddress()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
info, err := b.accountManager.AccountsGenerator().LoadAccount(rootAddress.Hex(), password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
masterID := info.ID
|
|
|
|
|
|
|
|
accInfosMap, err := b.accountManager.AccountsGenerator().StoreDerivedAccounts(masterID, password, []string{path})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, key, err := b.accountManager.AddressToDecryptedAccount(accInfosMap[path].Address, password)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return key, nil
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// registerHandlers attaches Status callback handlers to running node
|
|
|
|
func (b *GethStatusBackend) registerHandlers() error {
|
|
|
|
var clients []*rpc.Client
|
|
|
|
|
|
|
|
if c := b.StatusNode().RPCClient(); c != nil {
|
|
|
|
clients = append(clients, c)
|
|
|
|
} else {
|
|
|
|
return errors.New("RPC client unavailable")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, client := range clients {
|
|
|
|
client.RegisterHandler(
|
|
|
|
params.AccountsMethodName,
|
2021-09-22 17:49:20 +00:00
|
|
|
func(context.Context, uint64, ...interface{}) (interface{}, error) {
|
2019-11-23 17:57:05 +00:00
|
|
|
return b.accountManager.Accounts()
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if b.allowAllRPC {
|
|
|
|
// this should only happen in unit-tests, this variable is not available outside this package
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
client.RegisterHandler(params.SendTransactionMethodName, unsupportedMethodHandler)
|
|
|
|
client.RegisterHandler(params.PersonalSignMethodName, unsupportedMethodHandler)
|
|
|
|
client.RegisterHandler(params.PersonalRecoverMethodName, unsupportedMethodHandler)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-09-22 17:49:20 +00:00
|
|
|
func unsupportedMethodHandler(ctx context.Context, chainID uint64, rpcParams ...interface{}) (interface{}, error) {
|
2019-11-23 17:57:05 +00:00
|
|
|
return nil, ErrUnsupportedRPCMethod
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnectionChange handles network state changes logic.
|
|
|
|
func (b *GethStatusBackend) ConnectionChange(typ string, expensive bool) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
2021-05-14 10:55:42 +00:00
|
|
|
state := connection.State{
|
2022-03-28 10:10:40 +00:00
|
|
|
Type: connection.NewType(typ),
|
2019-11-23 17:57:05 +00:00
|
|
|
Expensive: expensive,
|
|
|
|
}
|
2021-05-14 10:55:42 +00:00
|
|
|
if typ == connection.None {
|
2019-11-23 17:57:05 +00:00
|
|
|
state.Offline = true
|
|
|
|
}
|
|
|
|
|
|
|
|
b.log.Info("Network state change", "old", b.connectionState, "new", state)
|
|
|
|
|
|
|
|
b.connectionState = state
|
2021-06-30 11:40:54 +00:00
|
|
|
b.statusNode.ConnectionChanged(state)
|
2019-11-23 17:57:05 +00:00
|
|
|
|
|
|
|
// logic of handling state changes here
|
|
|
|
// restart node? force peers reconnect? etc
|
|
|
|
}
|
|
|
|
|
|
|
|
// AppStateChange handles app state changes (background/foreground).
|
|
|
|
// state values: see https://facebook.github.io/react-native/docs/appstate.html
|
|
|
|
func (b *GethStatusBackend) AppStateChange(state string) {
|
2023-02-15 08:14:00 +00:00
|
|
|
var messenger *protocol.Messenger
|
2019-11-23 17:57:05 +00:00
|
|
|
s, err := parseAppState(state)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("AppStateChange failed, ignoring", "error", err)
|
2023-02-15 08:14:00 +00:00
|
|
|
return
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
b.appState = s
|
|
|
|
|
2023-02-15 08:14:00 +00:00
|
|
|
if b.statusNode == nil {
|
|
|
|
log.Warn("statusNode nil, not reporting app state change")
|
|
|
|
return
|
|
|
|
}
|
2022-02-10 17:19:34 +00:00
|
|
|
|
2023-02-15 08:14:00 +00:00
|
|
|
if b.statusNode.WakuExtService() != nil {
|
|
|
|
messenger = b.statusNode.WakuExtService().Messenger()
|
|
|
|
}
|
2022-02-10 17:19:34 +00:00
|
|
|
|
2023-02-15 08:14:00 +00:00
|
|
|
if b.statusNode.WakuV2ExtService() != nil {
|
|
|
|
messenger = b.statusNode.WakuV2ExtService().Messenger()
|
|
|
|
}
|
|
|
|
|
|
|
|
if messenger == nil {
|
|
|
|
log.Warn("messenger nil, not reporting app state change")
|
|
|
|
return
|
|
|
|
}
|
2022-02-10 17:19:34 +00:00
|
|
|
|
2023-02-15 08:14:00 +00:00
|
|
|
if s == appStateForeground {
|
|
|
|
messenger.ToForeground()
|
|
|
|
} else {
|
|
|
|
messenger.ToBackground()
|
2022-02-10 17:19:34 +00:00
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +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.
|
|
|
|
}
|
|
|
|
|
2020-10-28 07:56:14 +00:00
|
|
|
func (b *GethStatusBackend) StopLocalNotifications() error {
|
2021-06-30 11:40:54 +00:00
|
|
|
if b.statusNode == nil {
|
2020-10-28 07:56:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-06-30 11:40:54 +00:00
|
|
|
return b.statusNode.StopLocalNotifications()
|
2020-10-28 07:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *GethStatusBackend) StartLocalNotifications() error {
|
2021-06-30 11:40:54 +00:00
|
|
|
if b.statusNode == nil {
|
2020-10-28 07:56:14 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-06-30 11:40:54 +00:00
|
|
|
return b.statusNode.StartLocalNotifications()
|
2020-10-28 07:56:14 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// Logout clears whisper identities.
|
|
|
|
func (b *GethStatusBackend) Logout() error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
2024-01-30 11:45:08 +00:00
|
|
|
b.log.Debug("logging out")
|
2019-11-23 17:57:05 +00:00
|
|
|
err := b.cleanupServices()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-10 11:41:04 +00:00
|
|
|
err = b.closeDBs()
|
2019-11-23 17:57:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b.AccountManager().Logout()
|
2023-02-28 12:32:45 +00:00
|
|
|
b.account = nil
|
2019-11-23 17:57:05 +00:00
|
|
|
|
2021-07-07 06:11:09 +00:00
|
|
|
if b.statusNode != nil {
|
|
|
|
if err := b.statusNode.Stop(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.statusNode = nil
|
|
|
|
}
|
2024-01-30 11:45:08 +00:00
|
|
|
|
|
|
|
if !b.LocalPairingStateManager.IsPairing() {
|
|
|
|
signal.SendNodeStopped()
|
|
|
|
}
|
|
|
|
|
2021-07-07 06:11:09 +00:00
|
|
|
// re-initialize the node, at some point we should better manage the lifecycle
|
|
|
|
b.initialize()
|
2023-05-18 11:22:20 +00:00
|
|
|
|
|
|
|
err = b.statusNode.StartMediaServerWithoutDB()
|
|
|
|
if err != nil {
|
|
|
|
b.log.Error("failed to start media server without app db", "err", err)
|
|
|
|
return err
|
|
|
|
}
|
2019-11-23 17:57:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// cleanupServices stops parts of services that doesn't managed by a node and removes injected data from services.
|
|
|
|
func (b *GethStatusBackend) cleanupServices() error {
|
2021-06-30 11:40:54 +00:00
|
|
|
b.selectedAccountKeyID = ""
|
|
|
|
if b.statusNode == nil {
|
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
2021-06-30 11:40:54 +00:00
|
|
|
return b.statusNode.Cleanup()
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
func (b *GethStatusBackend) closeDBs() error {
|
|
|
|
err := b.closeWalletDB()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return b.closeAppDB()
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
func (b *GethStatusBackend) closeAppDB() error {
|
|
|
|
if b.appDB != nil {
|
|
|
|
err := b.appDB.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.appDB = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-10 11:41:04 +00:00
|
|
|
func (b *GethStatusBackend) closeWalletDB() error {
|
|
|
|
if b.walletDB != nil {
|
|
|
|
err := b.walletDB.Close()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.walletDB = nil
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// SelectAccount selects current wallet and chat accounts, by verifying that each address has corresponding account which can be decrypted
|
|
|
|
// using provided password. Once verification is done, the decrypted chat key is injected into Whisper (as a single identity,
|
|
|
|
// all previous identities are removed).
|
|
|
|
func (b *GethStatusBackend) SelectAccount(loginParams account.LoginParams) error {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
|
|
|
b.AccountManager().RemoveOnboarding()
|
|
|
|
|
|
|
|
err := b.accountManager.SelectAccount(loginParams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-12-10 13:38:58 +00:00
|
|
|
if loginParams.MultiAccount != nil {
|
|
|
|
b.account = loginParams.MultiAccount
|
|
|
|
}
|
|
|
|
|
2020-11-24 13:13:46 +00:00
|
|
|
if err := b.injectAccountsIntoServices(); err != nil {
|
2019-12-18 22:24:38 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
2020-12-09 14:03:43 +00:00
|
|
|
func (b *GethStatusBackend) GetActiveAccount() (*multiaccounts.Account, error) {
|
2020-12-09 14:45:59 +00:00
|
|
|
if b.account == nil {
|
2020-12-09 14:36:40 +00:00
|
|
|
return nil, errors.New("master key account is nil in the GethStatusBackend")
|
2020-12-09 14:45:59 +00:00
|
|
|
}
|
2020-12-09 14:03:43 +00:00
|
|
|
|
|
|
|
return b.account, nil
|
|
|
|
}
|
|
|
|
|
2023-12-01 11:30:42 +00:00
|
|
|
func (b *GethStatusBackend) LocalPairingStarted() error {
|
|
|
|
if b.account == nil {
|
|
|
|
return errors.New("master key account is nil in the GethStatusBackend")
|
|
|
|
}
|
|
|
|
|
|
|
|
accountDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return accountDB.MnemonicWasShown()
|
|
|
|
}
|
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
func (b *GethStatusBackend) injectAccountsIntoWakuService(w types.WakuKeyManager, st *ext.Service) error {
|
2019-11-23 17:57:05 +00:00
|
|
|
chatAccount, err := b.accountManager.SelectedChatAccount()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-18 22:24:38 +00:00
|
|
|
identity := chatAccount.AccountKey.PrivateKey
|
2019-11-23 17:57:05 +00:00
|
|
|
|
2020-12-09 14:03:43 +00:00
|
|
|
acc, err := b.GetActiveAccount()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
if err := w.DeleteKeyPairs(); err != nil { // err is not possible; method return value is incorrect
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.selectedAccountKeyID, err = w.AddKeyPair(identity)
|
|
|
|
if err != nil {
|
|
|
|
return ErrWakuIdentityInjectionFailure
|
|
|
|
}
|
2020-02-17 14:38:59 +00:00
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
if st != nil {
|
2023-10-30 14:51:57 +00:00
|
|
|
if err := st.InitProtocol(b.statusNode.GethNode().Config().Name, identity, b.appDB, b.walletDB, b.statusNode.HTTPServer(), b.multiaccountsDB, acc, b.accountManager, b.statusNode.RPCClient(), b.statusNode.WalletService(), b.statusNode.CommunityTokensService(), b.statusNode.WakuV2Service(), logutils.ZapLogger()); err != nil {
|
2020-02-17 14:38:59 +00:00
|
|
|
return err
|
|
|
|
}
|
2022-02-10 15:15:27 +00:00
|
|
|
// Set initial connection state
|
|
|
|
st.ConnectionChanged(b.connectionState)
|
2019-11-23 17:57:05 +00:00
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
messenger := st.Messenger()
|
|
|
|
// Init public status api
|
|
|
|
b.statusNode.StatusPublicService().Init(messenger)
|
2022-07-06 16:12:49 +00:00
|
|
|
b.statusNode.AccountService().Init(messenger)
|
2022-02-10 15:15:27 +00:00
|
|
|
// Init chat service
|
2022-03-23 18:47:00 +00:00
|
|
|
accDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
b.statusNode.ChatService(accDB).Init(messenger)
|
2023-04-26 15:37:18 +00:00
|
|
|
b.statusNode.EnsService().Init(messenger.SyncEnsNamesWithDispatchMessage)
|
2024-03-14 08:39:06 +00:00
|
|
|
b.statusNode.CommunityTokensService().Init(messenger)
|
2022-02-10 15:15:27 +00:00
|
|
|
}
|
2021-08-05 13:27:47 +00:00
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-05-14 10:55:42 +00:00
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
func (b *GethStatusBackend) injectAccountsIntoServices() error {
|
|
|
|
if b.statusNode.WakuService() != nil {
|
|
|
|
return b.injectAccountsIntoWakuService(b.statusNode.WakuService(), func() *ext.Service {
|
|
|
|
if b.statusNode.WakuExtService() == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return b.statusNode.WakuExtService().Service
|
|
|
|
}())
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
2021-06-16 20:19:45 +00:00
|
|
|
|
2022-02-10 15:15:27 +00:00
|
|
|
if b.statusNode.WakuV2Service() != nil {
|
|
|
|
return b.injectAccountsIntoWakuService(b.statusNode.WakuV2Service(), func() *ext.Service {
|
|
|
|
if b.statusNode.WakuV2ExtService() == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return b.statusNode.WakuV2ExtService().Service
|
|
|
|
}())
|
2021-06-16 20:19:45 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 22:24:38 +00:00
|
|
|
return nil
|
2019-11-23 17:57:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractGroupMembershipSignatures extract signatures from tuples of content/signature
|
|
|
|
func (b *GethStatusBackend) ExtractGroupMembershipSignatures(signaturePairs [][2]string) ([]string, error) {
|
|
|
|
return crypto.ExtractSignatures(signaturePairs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignGroupMembership signs a piece of data containing membership information
|
|
|
|
func (b *GethStatusBackend) SignGroupMembership(content string) (string, error) {
|
|
|
|
selectedChatAccount, err := b.accountManager.SelectedChatAccount()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return crypto.SignStringAsHex(content, selectedChatAccount.AccountKey.PrivateKey)
|
|
|
|
}
|
|
|
|
|
2023-01-06 12:21:14 +00:00
|
|
|
func (b *GethStatusBackend) Messenger() *protocol.Messenger {
|
|
|
|
node := b.StatusNode()
|
|
|
|
if node != nil {
|
2023-01-11 11:30:54 +00:00
|
|
|
accountService := node.AccountService()
|
|
|
|
if accountService != nil {
|
|
|
|
return accountService.GetMessenger()
|
2023-01-06 12:21:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-23 17:57:05 +00:00
|
|
|
// SignHash exposes vanilla ECDSA signing for signing a message for Swarm
|
|
|
|
func (b *GethStatusBackend) SignHash(hexEncodedHash string) (string, error) {
|
|
|
|
hash, err := hexutil.Decode(hexEncodedHash)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("SignHash: could not unmarshal the input: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
chatAccount, err := b.accountManager.SelectedChatAccount()
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("SignHash: could not select account: %v", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
signature, err := ethcrypto.Sign(hash, chatAccount.AccountKey.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("SignHash: could not sign the hash: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
hexEncodedSignature := types.EncodeHex(signature)
|
|
|
|
return hexEncodedSignature, nil
|
|
|
|
}
|
2022-03-17 18:06:02 +00:00
|
|
|
|
|
|
|
func (b *GethStatusBackend) SwitchFleet(fleet string, conf *params.NodeConfig) error {
|
|
|
|
if b.appDB == nil {
|
|
|
|
return ErrDBNotAvailable
|
|
|
|
}
|
|
|
|
|
2022-03-23 18:47:00 +00:00
|
|
|
accountDB, err := accounts.NewDB(b.appDB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = accountDB.SaveSetting("fleet", fleet)
|
2022-03-17 18:06:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = nodecfg.SaveNodeConfig(b.appDB, conf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-01 05:33:57 +00:00
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
func (b *GethStatusBackend) getAppDBPath(keyUID string) (string, error) {
|
|
|
|
if len(b.rootDataDir) == 0 {
|
|
|
|
return "", errors.New("root datadir wasn't provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(b.rootDataDir, fmt.Sprintf("%s-v4.db", keyUID)), nil
|
2023-08-10 11:41:04 +00:00
|
|
|
}
|
|
|
|
|
2023-08-22 14:36:51 +00:00
|
|
|
func (b *GethStatusBackend) getWalletDBPath(keyUID string) (string, error) {
|
|
|
|
if len(b.rootDataDir) == 0 {
|
|
|
|
return "", errors.New("root datadir wasn't provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(b.rootDataDir, fmt.Sprintf("%s-wallet.db", keyUID)), nil
|
2023-08-10 11:41:04 +00:00
|
|
|
}
|