feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions
+38
View File
@@ -0,0 +1,38 @@
Settings service
================
Settings service provides private API for storing all configuration for a selected account.
To enable:
1. Client must ensure that settings db is initialized in the api.Backend.
2. Add `settings` to APIModules in config.
API
---
### settings_saveConfig
#### Parameters
- `type`: `string` - configuratin type. if not unique error is raised.
- `conf`: `bytes` - raw json.
### settings_getConfig
#### Parameters
- `type`: string
#### Returns
- `conf` raw json
### settings_saveNodeConfig
Special case of the settings_saveConfig. In status-go we are using constant `node-config` as a type for node configuration.
Application depends on this value and will try to load it when node is started. This method is provided
in order to remove syncing mentioned constant between status-go and users.
#### Parameters
- `conf`: params.NodeConfig
+614
View File
@@ -0,0 +1,614 @@
package accounts
import (
"context"
"errors"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
walletsettings "github.com/status-im/status-go/multiaccounts/settings_wallet"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/services/accounts/accountsevent"
)
func NewAccountsAPI(manager *account.GethManager, config *params.NodeConfig, db *accounts.Database, feed *event.Feed, messenger **protocol.Messenger) *API {
return &API{manager, config, db, feed, messenger}
}
// API is class with methods available over RPC.
type API struct {
manager *account.GethManager
config *params.NodeConfig
db *accounts.Database
feed *event.Feed
messenger **protocol.Messenger
}
type DerivedAddress struct {
Address common.Address `json:"address"`
Path string `json:"path"`
HasActivity bool `json:"hasActivity"`
AlreadyCreated bool `json:"alreadyCreated"`
}
func (api *API) SaveAccount(ctx context.Context, account *accounts.Account) error {
log.Info("[AccountsAPI::SaveAccount]")
err := (*api.messenger).SaveOrUpdateAccount(account)
if err != nil {
return err
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeAdded,
Accounts: []common.Address{common.Address(account.Address)},
})
return nil
}
// Setting `Keypair` without `Accounts` will update keypair only, `Keycards` won't be saved/updated this way.
func (api *API) SaveKeypair(ctx context.Context, keypair *accounts.Keypair) error {
log.Info("[AccountsAPI::SaveKeypair]")
err := (*api.messenger).SaveOrUpdateKeypair(keypair)
if err != nil {
return err
}
commonAddresses := []common.Address{}
for _, acc := range keypair.Accounts {
commonAddresses = append(commonAddresses, common.Address(acc.Address))
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeAdded,
Accounts: commonAddresses,
})
return nil
}
func (api *API) HasPairedDevices(ctx context.Context) bool {
return (*api.messenger).HasPairedDevices()
}
// Setting `Keypair` without `Accounts` will update keypair only.
func (api *API) UpdateKeypairName(ctx context.Context, keyUID string, name string) error {
return (*api.messenger).UpdateKeypairName(keyUID, name)
}
func (api *API) MoveWalletAccount(ctx context.Context, fromPosition int64, toPosition int64) error {
return (*api.messenger).MoveWalletAccount(fromPosition, toPosition)
}
func (api *API) UpdateTokenPreferences(ctx context.Context, preferences []walletsettings.TokenPreferences) error {
return (*api.messenger).UpdateTokenPreferences(preferences)
}
func (api *API) GetTokenPreferences(ctx context.Context) ([]walletsettings.TokenPreferences, error) {
return (*api.messenger).GetTokenPreferences()
}
func (api *API) UpdateCollectiblePreferences(ctx context.Context, preferences []walletsettings.CollectiblePreferences) error {
return (*api.messenger).UpdateCollectiblePreferences(preferences)
}
func (api *API) GetCollectiblePreferences(ctx context.Context) ([]walletsettings.CollectiblePreferences, error) {
return (*api.messenger).GetCollectiblePreferences()
}
func (api *API) GetAccounts(ctx context.Context) ([]*accounts.Account, error) {
return api.db.GetActiveAccounts()
}
func (api *API) GetWatchOnlyAccounts(ctx context.Context) ([]*accounts.Account, error) {
return api.db.GetActiveWatchOnlyAccounts()
}
func (api *API) GetKeypairs(ctx context.Context) ([]*accounts.Keypair, error) {
return api.db.GetActiveKeypairs()
}
func (api *API) GetAccountByAddress(ctx context.Context, address types.Address) (*accounts.Account, error) {
return api.db.GetAccountByAddress(address)
}
func (api *API) GetKeypairByKeyUID(ctx context.Context, keyUID string) (*accounts.Keypair, error) {
return api.db.GetKeypairByKeyUID(keyUID)
}
func (api *API) DeleteAccount(ctx context.Context, address types.Address) error {
err := (*api.messenger).DeleteAccount(address)
if err != nil {
return err
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeRemoved,
Accounts: []common.Address{common.Address(address)},
})
return nil
}
func (api *API) DeleteKeypair(ctx context.Context, keyUID string) error {
keypair, err := api.db.GetKeypairByKeyUID(keyUID)
if err != nil {
return err
}
err = (*api.messenger).DeleteKeypair(keyUID)
if err != nil {
return err
}
var addresses []common.Address
for _, acc := range keypair.Accounts {
if acc.Chat {
continue
}
addresses = append(addresses, common.Address(acc.Address))
}
api.feed.Send(accountsevent.Event{
Type: accountsevent.EventTypeRemoved,
Accounts: addresses,
})
return nil
}
func (api *API) AddKeypair(ctx context.Context, password string, keypair *accounts.Keypair) error {
if len(keypair.KeyUID) == 0 {
return errors.New("`KeyUID` field of a keypair must be set")
}
if len(keypair.Name) == 0 {
return errors.New("`Name` field of a keypair must be set")
}
if len(keypair.Type) == 0 {
return errors.New("`Type` field of a keypair must be set")
}
if keypair.Type != accounts.KeypairTypeKey {
if len(keypair.DerivedFrom) == 0 {
return errors.New("`DerivedFrom` field of a keypair must be set")
}
}
for _, acc := range keypair.Accounts {
if acc.KeyUID != keypair.KeyUID {
return errors.New("all accounts of a keypair must have the same `KeyUID` as keypair key uid")
}
err := api.checkAccountValidity(acc)
if err != nil {
return err
}
}
err := api.SaveKeypair(ctx, keypair)
if err != nil {
return err
}
if len(password) > 0 {
for _, acc := range keypair.Accounts {
if acc.Type == accounts.AccountTypeGenerated || acc.Type == accounts.AccountTypeSeed {
err = api.createKeystoreFileForAccount(keypair.DerivedFrom, password, acc)
if err != nil {
return err
}
}
}
}
return nil
}
func (api *API) checkAccountValidity(account *accounts.Account) error {
if len(account.Address) == 0 {
return errors.New("`Address` field of an account must be set")
}
if len(account.Type) == 0 {
return errors.New("`Type` field of an account must be set")
}
if account.Wallet || account.Chat {
return errors.New("default wallet and chat account cannot be added this way")
}
if len(account.Name) == 0 {
return errors.New("`Name` field of an account must be set")
}
if len(account.Emoji) == 0 {
return errors.New("`Emoji` field of an account must be set")
}
if len(account.ColorID) == 0 {
return errors.New("`ColorID` field of an account must be set")
}
if account.Type != accounts.AccountTypeWatch {
if len(account.KeyUID) == 0 {
return errors.New("`KeyUID` field of an account must be set")
}
if len(account.PublicKey) == 0 {
return errors.New("`PublicKey` field of an account must be set")
}
if account.Type != accounts.AccountTypeKey {
if len(account.Path) == 0 {
return errors.New("`Path` field of an account must be set")
}
}
}
addressExists, err := api.db.AddressExists(account.Address)
if err != nil {
return err
}
if addressExists {
return errors.New("account already exists")
}
return nil
}
func (api *API) createKeystoreFileForAccount(masterAddress string, password string, account *accounts.Account) error {
if account.Type != accounts.AccountTypeGenerated && account.Type != accounts.AccountTypeSeed {
return errors.New("cannot create keystore file if account is not of `generated` or `seed` type")
}
if masterAddress == "" {
return errors.New("cannot create keystore file if master address is empty")
}
if password == "" {
return errors.New("cannot create keystore file if password is empty")
}
info, err := api.manager.AccountsGenerator().LoadAccount(masterAddress, password)
if err != nil {
return err
}
_, err = api.manager.AccountsGenerator().StoreDerivedAccounts(info.ID, password, []string{account.Path})
return err
}
func (api *API) AddAccount(ctx context.Context, password string, account *accounts.Account) error {
err := api.checkAccountValidity(account)
if err != nil {
return err
}
if account.Type != accounts.AccountTypeWatch {
kp, err := api.db.GetKeypairByKeyUID(account.KeyUID)
if err != nil {
if err == accounts.ErrDbKeypairNotFound {
return errors.New("cannot add an account for an unknown keypair")
}
return err
}
// we need to create local keystore file only if password is provided and the account is being added is of
// "generated" or "seed" type.
if (account.Type == accounts.AccountTypeGenerated || account.Type == accounts.AccountTypeSeed) && len(password) > 0 {
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, account)
if err != nil {
return err
}
}
}
if account.Type == accounts.AccountTypeGenerated {
account.AddressWasNotShown = true
}
return api.SaveAccount(ctx, account)
}
// Imports a new private key and creates local keystore file.
func (api *API) ImportPrivateKey(ctx context.Context, privateKey string, password string) error {
info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey)
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(info.KeyUID)
if err != nil && err != accounts.ErrDbKeypairNotFound {
return err
}
if kp != nil {
return errors.New("provided private key was already imported")
}
_, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
return err
}
// Creates all keystore files for a keypair and mark it in db as fully operable.
func (api *API) MakePrivateKeyKeypairFullyOperable(ctx context.Context, privateKey string, password string) error {
info, err := api.manager.AccountsGenerator().ImportPrivateKey(privateKey)
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(info.KeyUID)
if err != nil {
return err
}
if kp == nil {
return errors.New("keypair for the provided private key is not known")
}
_, err = api.manager.AccountsGenerator().StoreAccount(info.ID, password)
if err != nil {
return err
}
return (*api.messenger).MarkKeypairFullyOperable(info.KeyUID)
}
func (api *API) MakePartiallyOperableAccoutsFullyOperable(ctx context.Context, password string) (addresses []types.Address, err error) {
profileKeypair, err := api.db.GetProfileKeypair()
if err != nil {
return
}
if !profileKeypair.MigratedToKeycard() && !api.VerifyPassword(password) {
err = errors.New("wrong password provided")
return
}
keypairs, err := api.db.GetActiveKeypairs()
if err != nil {
return
}
for _, kp := range keypairs {
for _, acc := range kp.Accounts {
if acc.Operable != accounts.AccountPartiallyOperable {
continue
}
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, acc)
if err != nil {
return
}
err = api.db.MarkAccountFullyOperable(acc.Address)
if err != nil {
return
}
addresses = append(addresses, acc.Address)
}
}
return
}
// Imports a new mnemonic and creates local keystore file.
func (api *API) ImportMnemonic(ctx context.Context, mnemonic string, password string) error {
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
if err != nil && err != accounts.ErrDbKeypairNotFound {
return err
}
if kp != nil {
return errors.New("provided mnemonic was already imported, to add new account use `AddAccount` endpoint")
}
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
return err
}
// Creates all keystore files for a keypair and mark it in db as fully operable.
func (api *API) MakeSeedPhraseKeypairFullyOperable(ctx context.Context, mnemonic string, password string) error {
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
if err != nil {
return err
}
if kp == nil {
return errors.New("keypair for the provided seed phrase is not known")
}
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
if err != nil {
return err
}
var paths []string
for _, acc := range kp.Accounts {
paths = append(paths, acc.Path)
}
_, err = api.manager.AccountsGenerator().StoreDerivedAccounts(generatedAccountInfo.ID, password, paths)
if err != nil {
return err
}
return (*api.messenger).MarkKeypairFullyOperable(generatedAccountInfo.KeyUID)
}
// Creates a random new mnemonic.
func (api *API) GetRandomMnemonic(ctx context.Context) (string, error) {
return account.GetRandomMnemonic()
}
func (api *API) VerifyKeystoreFileForAccount(address types.Address, password string) bool {
_, err := api.manager.VerifyAccountPassword(api.config.KeyStoreDir, address.Hex(), password)
return err == nil
}
func (api *API) VerifyPassword(password string) bool {
address, err := api.db.GetChatAddress()
if err != nil {
return false
}
return api.VerifyKeystoreFileForAccount(address, password)
}
func (api *API) MigrateNonProfileKeycardKeypairToApp(ctx context.Context, mnemonic string, password string) error {
mnemonicNoExtraSpaces := strings.Join(strings.Fields(mnemonic), " ")
generatedAccountInfo, err := api.manager.AccountsGenerator().ImportMnemonic(mnemonicNoExtraSpaces, "")
if err != nil {
return err
}
kp, err := api.db.GetKeypairByKeyUID(generatedAccountInfo.KeyUID)
if err != nil {
return err
}
if kp.Type == accounts.KeypairTypeProfile {
return errors.New("cannot migrate profile keypair")
}
if !kp.MigratedToKeycard() {
return errors.New("keypair being migrated is not a keycard keypair")
}
profileKeypair, err := api.db.GetProfileKeypair()
if err != nil {
return err
}
if !profileKeypair.MigratedToKeycard() && !api.VerifyPassword(password) {
return errors.New("wrong password provided")
}
_, err = api.manager.AccountsGenerator().StoreAccount(generatedAccountInfo.ID, password)
if err != nil {
return err
}
for _, acc := range kp.Accounts {
err = api.createKeystoreFileForAccount(kp.DerivedFrom, password, acc)
if err != nil {
return err
}
}
// this will emit SyncKeypair message
return (*api.messenger).DeleteAllKeycardsWithKeyUID(ctx, generatedAccountInfo.KeyUID)
}
// If keypair is migrated from keycard to app, then `accountsComingFromKeycard` should be set to true, otherwise false.
// If keycard is new `Position` will be determined and set by the backend and `KeycardLocked` will be set to false.
// If keycard is already added, `Position` and `KeycardLocked` will be unchanged.
func (api *API) SaveOrUpdateKeycard(ctx context.Context, keycard *accounts.Keycard, accountsComingFromKeycard bool) error {
if len(keycard.AccountsAddresses) == 0 {
return errors.New("cannot migrate a keypair without accounts")
}
kpDb, err := api.db.GetKeypairByKeyUID(keycard.KeyUID)
if err != nil {
if err == accounts.ErrDbKeypairNotFound {
return errors.New("cannot migrate an unknown keypair")
}
return err
}
err = (*api.messenger).SaveOrUpdateKeycard(ctx, keycard)
if err != nil {
return err
}
if !accountsComingFromKeycard {
// Once we migrate a keypair, corresponding keystore files need to be deleted
// if the keypair being migrated is not already migrated (in case user is creating a copy of an existing Keycard)
// and if keypair operability is different from non operable (otherwise there are not keystore files to be deleted).
if !kpDb.MigratedToKeycard() && kpDb.Operability() != accounts.AccountNonOperable {
for _, acc := range kpDb.Accounts {
if acc.Operable != accounts.AccountFullyOperable {
continue
}
err = api.manager.DeleteAccount(acc.Address)
if err != nil {
return err
}
}
err = api.manager.DeleteAccount(types.Address(common.HexToAddress(kpDb.DerivedFrom)))
if err != nil {
return err
}
}
err = (*api.messenger).MarkKeypairFullyOperable(keycard.KeyUID)
if err != nil {
return err
}
}
return nil
}
func (api *API) GetAllKnownKeycards(ctx context.Context) ([]*accounts.Keycard, error) {
return api.db.GetAllKnownKeycards()
}
func (api *API) GetKeycardsWithSameKeyUID(ctx context.Context, keyUID string) ([]*accounts.Keycard, error) {
return api.db.GetKeycardsWithSameKeyUID(keyUID)
}
func (api *API) GetKeycardByKeycardUID(ctx context.Context, keycardUID string) (*accounts.Keycard, error) {
return api.db.GetKeycardByKeycardUID(keycardUID)
}
func (api *API) SetKeycardName(ctx context.Context, keycardUID string, kpName string) error {
return (*api.messenger).SetKeycardName(ctx, keycardUID, kpName)
}
func (api *API) KeycardLocked(ctx context.Context, keycardUID string) error {
return (*api.messenger).KeycardLocked(ctx, keycardUID)
}
func (api *API) KeycardUnlocked(ctx context.Context, keycardUID string) error {
return (*api.messenger).KeycardUnlocked(ctx, keycardUID)
}
func (api *API) DeleteKeycardAccounts(ctx context.Context, keycardUID string, accountAddresses []types.Address) error {
return (*api.messenger).DeleteKeycardAccounts(ctx, keycardUID, accountAddresses)
}
func (api *API) DeleteKeycard(ctx context.Context, keycardUID string) error {
return (*api.messenger).DeleteKeycard(ctx, keycardUID)
}
func (api *API) DeleteAllKeycardsWithKeyUID(ctx context.Context, keyUID string) error {
return (*api.messenger).DeleteAllKeycardsWithKeyUID(ctx, keyUID)
}
func (api *API) UpdateKeycardUID(ctx context.Context, oldKeycardUID string, newKeycardUID string) error {
return (*api.messenger).UpdateKeycardUID(ctx, oldKeycardUID, newKeycardUID)
}
func (api *API) AddressWasShown(address types.Address) error {
return api.db.AddressWasShown(address)
}
@@ -0,0 +1,19 @@
package accountsevent
import "github.com/ethereum/go-ethereum/common"
// EventType type for event types.
type EventType string
// Event is a type for accounts events.
type Event struct {
Type EventType `json:"type"`
Accounts []common.Address `json:"accounts"`
}
const (
// EventTypeAdded is emitted when a new account is added.
EventTypeAdded EventType = "added"
// EventTypeRemoved is emitted when an account is removed.
EventTypeRemoved EventType = "removed"
)
@@ -0,0 +1,85 @@
package accountsevent
import (
"context"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/services/wallet/async"
)
type AccountsChangeCb func(changedAddresses []common.Address, eventType EventType, currentAddresses []common.Address)
// Watcher executes a given callback whenever an account gets added/removed
type Watcher struct {
accountsDB *accounts.Database
accountFeed *event.Feed
group *async.Group
callback AccountsChangeCb
}
func NewWatcher(accountsDB *accounts.Database, accountFeed *event.Feed, callback AccountsChangeCb) *Watcher {
return &Watcher{
accountsDB: accountsDB,
accountFeed: accountFeed,
callback: callback,
}
}
func (w *Watcher) Start() {
if w.group != nil {
return
}
w.group = async.NewGroup(context.Background())
w.group.Add(func(ctx context.Context) error {
return watch(ctx, w.accountsDB, w.accountFeed, w.callback)
})
}
func (w *Watcher) Stop() {
if w.group != nil {
w.group.Stop()
w.group.Wait()
w.group = nil
}
}
func onAccountsChange(accountsDB *accounts.Database, callback AccountsChangeCb, changedAddresses []common.Address, eventType EventType) {
currentEthAddresses, err := accountsDB.GetWalletAddresses()
if err != nil {
log.Error("failed getting wallet addresses", "error", err)
return
}
currentAddresses := make([]common.Address, 0, len(currentEthAddresses))
for _, ethAddress := range currentEthAddresses {
currentAddresses = append(currentAddresses, common.Address(ethAddress))
}
if callback != nil {
callback(changedAddresses, eventType, currentAddresses)
}
}
func watch(ctx context.Context, accountsDB *accounts.Database, accountFeed *event.Feed, callback AccountsChangeCb) error {
ch := make(chan Event, 1)
sub := accountFeed.Subscribe(ch)
defer sub.Unsubscribe()
for {
select {
case <-ctx.Done():
return nil
case err := <-sub.Err():
if err != nil {
log.Error("accounts watcher subscription failed", "error", err)
}
case ev := <-ch:
onAccountsChange(accountsDB, callback, ev.Accounts, ev.Type)
}
}
}
@@ -0,0 +1,93 @@
package accounts
import (
"errors"
"github.com/status-im/status-go/timesource"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts"
)
var (
// ErrUpdatingWrongAccount raised if caller tries to update any other account except one used for login.
ErrUpdatingWrongAccount = errors.New("failed to update wrong account. Please login with that account first")
)
func NewMultiAccountsAPI(db *multiaccounts.Database, mediaServer *server.MediaServer) *MultiAccountsAPI {
return &MultiAccountsAPI{db: db, mediaServer: mediaServer}
}
// MultiAccountsAPI is class with methods available over RPC.
type MultiAccountsAPI struct {
db *multiaccounts.Database
mediaServer *server.MediaServer
}
func (api *MultiAccountsAPI) UpdateAccount(account multiaccounts.Account) error {
oldAcc, err := api.db.GetAccount(account.KeyUID)
if err != nil {
return err
}
if oldAcc == nil {
return errors.New("UpdateAccount but account not found")
}
if oldAcc.CustomizationColor != account.CustomizationColor {
updatedAt := timesource.GetCurrentTimeInMillis()
account.CustomizationColorClock = updatedAt
}
return api.db.UpdateAccount(account)
}
//
// Profile Images
//
// GetIdentityImages returns an array of json marshalled IdentityImages assigned to the user's identity
func (api *MultiAccountsAPI) GetIdentityImages(keyUID string) ([]*images.IdentityImage, error) {
return api.db.GetIdentityImages(keyUID)
}
// GetIdentityImage returns a json object representing the image with the given name
func (api *MultiAccountsAPI) GetIdentityImage(keyUID, name string) (*images.IdentityImage, error) {
return api.db.GetIdentityImage(keyUID, name)
}
// StoreIdentityImage takes the filepath of an image, crops it as per the rect coords and finally resizes the image.
// The resulting image(s) will be stored in the DB along with other user account information.
// aX and aY represent the pixel coordinates of the upper left corner of the image's cropping area
// bX and bY represent the pixel coordinates of the lower right corner of the image's cropping area
func (api *MultiAccountsAPI) StoreIdentityImage(keyUID, filepath string, aX, aY, bX, bY int) ([]images.IdentityImage, error) {
iis, err := images.GenerateIdentityImages(filepath, aX, aY, bX, bY)
if err != nil {
return nil, err
}
err = api.db.StoreIdentityImages(keyUID, iis, true)
if err != nil {
return nil, err
}
return iis, err
}
func (api *MultiAccountsAPI) StoreIdentityImageFromURL(keyUID, url string) ([]images.IdentityImage, error) {
iis, err := images.GenerateIdentityImagesFromURL(url)
if err != nil {
return nil, err
}
err = api.db.StoreIdentityImages(keyUID, iis, true)
if err != nil {
return nil, err
}
return iis, err
}
// DeleteIdentityImage deletes an IdentityImage from the db with the given name
func (api *MultiAccountsAPI) DeleteIdentityImage(keyUID string) error {
return api.db.DeleteIdentityImage(keyUID)
}
+1
View File
@@ -0,0 +1 @@
{"ClusterConfig":{"Enabled":true,"Fleet":"eth.beta","BootNodes":["enode://7427dfe38bd4cf7c58bb96417806fab25782ec3e6046a8053370022cbaa281536e8d64ecd1b02e1f8f72768e295d06258ba43d88304db068e6f2417ae8bcb9a6@104.154.88.123:443","enode://e8a7c03b58911e98bbd66accb2a55d57683f35b23bf9dfca89e5e244eb5cc3f25018b4112db507faca34fb69ffb44b362f79eda97a669a8df29c72e654416784@47.91.224.35:443","enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@206.189.243.57:443","enode://43947863cfa5aad1178f482ac35a8ebb9116cded1c23f7f9af1a47badfc1ee7f0dd9ec0543417cc347225a6e47e46c6873f647559e43434596c54e17a4d3a1e4@47.52.74.140:443"],"TrustedMailServers":["enode://744098ab6d3308af5cd03920aea60c46d16b2cd3d33bf367cbaf1d01c2fcd066ff8878576d0967897cd7dbb0e63f873cc0b4f7e4b0f1d7222e6b3451a78d9bda@47.89.20.15:443","enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@206.189.243.168:443","enode://7de99e4cb1b3523bd26ca212369540646607c721ad4f3e5c821ed9148150ce6ce2e72631723002210fac1fd52dfa8bbdf3555e05379af79515e1179da37cc3db@35.188.19.210:443","enode://da61e9eff86a56633b635f887d8b91e0ff5236bbc05b8169834292e92afb92929dcf6efdbf373a37903da8fe0384d5a0a8247e83f1ce211aa429200b6d28c548@47.91.156.93:443","enode://74957e361ab290e6af45a124536bc9adee39fbd2f995a77ace6ed7d05d9a1c7c98b78b2df5f8071c439b9c0afe4a69893ede4ad633473f96bc195ddf33f6ce00@47.52.255.195:443","enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@206.189.243.162:443"],"StaticNodes":["enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@206.189.243.177:443","enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@206.189.243.172:443"],"RendezvousNodes":["/ip4/206.189.243.57/tcp/30703/ethv4/16Uiu2HAmLqTXuY4Sb6G28HNooaFUXUKzpzKXCcgyJxgaEE2i5vnf","/ip4/174.138.105.243/tcp/30703/ethv4/16Uiu2HAmRHPzF3rQg55PgYPcQkyvPVH9n2hWsYPhUJBZ6kVjJgdV"]},"DataDir":"/ethereum/mainnet_rpc_dev","LogLevel":"INFO","Rendezvous":true,"WhisperConfig":{"Enabled":true,"LightClient":true,"MinimumPoW":0.001,"EnableNTPSync":true},"LogEnabled":true,"BrowsersConfig":{"Enabled":true},"RequireTopics":{"whisper":{"Min":2,"Max":2}},"UpstreamConfig":{"Enabled":true,"URL":"https://mainnet.infura.io/v3/f315575765b14720b32382a61a89341a"},"ListenAddr":":30304","PermissionsConfig":{"Enabled":true},"NetworkId":1,"Name":"StatusIM","NoDiscovery":false,"ShhextConfig":{"BackupDisabledDataDir":"/data/user/0/im.status.ethereum.debug/files/../no_backup","InstallationID":"cf40e6c9-262f-5a76-9621-7b6fe0a91cd2","MaxMessageDeliveryAttempts":6,"MailServerConfirmations":true,"DataSyncEnabled":false,"DisableGenericDiscoveryTopic":false,"SendV1Messages":false,"PFSEnabled":true},"WalletConfig":{"Enabled":true},"StatusAccountsConfig":{"Enabled":true}}
+97
View File
@@ -0,0 +1,97 @@
package accounts
import (
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
)
// NewService initializes service instance.
func NewService(db *accounts.Database, mdb *multiaccounts.Database, manager *account.GethManager, config *params.NodeConfig, feed *event.Feed, mediaServer *server.MediaServer) *Service {
return &Service{db, mdb, manager, config, feed, nil, mediaServer}
}
// Service is a browsers service.
type Service struct {
db *accounts.Database
mdb *multiaccounts.Database
manager *account.GethManager
config *params.NodeConfig
feed *event.Feed
messenger *protocol.Messenger
mediaServer *server.MediaServer
}
func (s *Service) Init(messenger *protocol.Messenger) {
s.messenger = messenger
}
// Start a service.
func (s *Service) Start() error {
return s.manager.InitKeystore(s.config.KeyStoreDir)
}
// Stop a service.
func (s *Service) Stop() error {
return nil
}
// APIs returns list of available RPC APIs.
func (s *Service) APIs() []rpc.API {
return []rpc.API{
{
Namespace: "settings",
Version: "0.1.0",
Service: NewSettingsAPI(&s.messenger, s.db, s.config),
},
{
Namespace: "accounts",
Version: "0.1.0",
Service: s.AccountsAPI(),
},
{
Namespace: "multiaccounts",
Version: "0.1.0",
Service: NewMultiAccountsAPI(s.mdb, s.mediaServer),
},
}
}
func (s *Service) AccountsAPI() *API {
return NewAccountsAPI(s.manager, s.config, s.db, s.feed, &s.messenger)
}
// Protocols returns list of p2p protocols.
func (s *Service) Protocols() []p2p.Protocol {
return nil
}
func (s *Service) GetKeypairByKeyUID(keyUID string) (*accounts.Keypair, error) {
return s.db.GetKeypairByKeyUID(keyUID)
}
func (s *Service) GetSettings() (settings.Settings, error) {
return s.db.GetSettings()
}
func (s *Service) GetMessenger() *protocol.Messenger {
return s.messenger
}
func (s *Service) VerifyPassword(password string) bool {
address, err := s.db.GetChatAddress()
if err != nil {
return false
}
_, err = s.manager.VerifyAccountPassword(s.config.KeyStoreDir, address.Hex(), password)
return err == nil
}
+193
View File
@@ -0,0 +1,193 @@
package accounts
import (
"context"
"errors"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/nodecfg"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/protocol/identity"
)
func NewSettingsAPI(messenger **protocol.Messenger, db *accounts.Database, config *params.NodeConfig) *SettingsAPI {
return &SettingsAPI{messenger, db, config}
}
// SettingsAPI is class with methods available over RPC.
type SettingsAPI struct {
messenger **protocol.Messenger
db *accounts.Database
config *params.NodeConfig
}
func (api *SettingsAPI) SaveSetting(ctx context.Context, typ string, val interface{}) error {
// NOTE(Ferossgp): v0.62.0 Backward compatibility, skip this for older clients instead of returning error
if typ == "waku-enabled" {
return nil
}
err := api.db.SaveSetting(typ, val)
if err != nil {
return err
}
return nil
}
func (api *SettingsAPI) GetSettings(ctx context.Context) (settings.Settings, error) {
return api.db.GetSettings()
}
// NodeConfig returns the currently used node configuration
func (api *SettingsAPI) NodeConfig(ctx context.Context) (*params.NodeConfig, error) {
return api.config, nil
}
// Saves the nodeconfig in the database. The node must be restarted for the changes to be applied
func (api *SettingsAPI) SaveNodeConfig(ctx context.Context, n *params.NodeConfig) error {
return nodecfg.SaveNodeConfig(api.db.DB(), n)
}
// Notifications Settings
func (api *SettingsAPI) NotificationsGetAllowNotifications() (bool, error) {
return api.db.GetAllowNotifications()
}
func (api *SettingsAPI) NotificationsSetAllowNotifications(value bool) error {
return api.db.SetAllowNotifications(value)
}
func (api *SettingsAPI) NotificationsGetOneToOneChats() (string, error) {
return api.db.GetOneToOneChats()
}
func (api *SettingsAPI) NotificationsSetOneToOneChats(value string) error {
return api.db.SetOneToOneChats(value)
}
func (api *SettingsAPI) NotificationsGetGroupChats() (string, error) {
return api.db.GetGroupChats()
}
func (api *SettingsAPI) NotificationsSetGroupChats(value string) error {
return api.db.SetGroupChats(value)
}
func (api *SettingsAPI) NotificationsGetPersonalMentions() (string, error) {
return api.db.GetPersonalMentions()
}
func (api *SettingsAPI) NotificationsSetPersonalMentions(value string) error {
return api.db.SetPersonalMentions(value)
}
func (api *SettingsAPI) NotificationsGetGlobalMentions() (string, error) {
return api.db.GetGlobalMentions()
}
func (api *SettingsAPI) NotificationsSetGlobalMentions(value string) error {
return api.db.SetGlobalMentions(value)
}
func (api *SettingsAPI) NotificationsGetAllMessages() (string, error) {
return api.db.GetAllMessages()
}
func (api *SettingsAPI) NotificationsSetAllMessages(value string) error {
return api.db.SetAllMessages(value)
}
func (api *SettingsAPI) NotificationsGetContactRequests() (string, error) {
return api.db.GetContactRequests()
}
func (api *SettingsAPI) NotificationsSetContactRequests(value string) error {
return api.db.SetContactRequests(value)
}
func (api *SettingsAPI) NotificationsGetIdentityVerificationRequests() (string, error) {
return api.db.GetIdentityVerificationRequests()
}
func (api *SettingsAPI) NotificationsSetIdentityVerificationRequests(value string) error {
return api.db.SetIdentityVerificationRequests(value)
}
func (api *SettingsAPI) NotificationsGetSoundEnabled() (bool, error) {
return api.db.GetSoundEnabled()
}
func (api *SettingsAPI) NotificationsSetSoundEnabled(value bool) error {
return api.db.SetSoundEnabled(value)
}
func (api *SettingsAPI) NotificationsGetVolume() (int, error) {
return api.db.GetVolume()
}
func (api *SettingsAPI) NotificationsSetVolume(value int) error {
return api.db.SetVolume(value)
}
func (api *SettingsAPI) NotificationsGetMessagePreview() (int, error) {
return api.db.GetMessagePreview()
}
func (api *SettingsAPI) NotificationsSetMessagePreview(value int) error {
return api.db.SetMessagePreview(value)
}
// Notifications Settings - Exemption settings
func (api *SettingsAPI) NotificationsGetExMuteAllMessages(id string) (bool, error) {
return api.db.GetExMuteAllMessages(id)
}
func (api *SettingsAPI) NotificationsGetExPersonalMentions(id string) (string, error) {
return api.db.GetExPersonalMentions(id)
}
func (api *SettingsAPI) NotificationsGetExGlobalMentions(id string) (string, error) {
return api.db.GetExGlobalMentions(id)
}
func (api *SettingsAPI) NotificationsGetExOtherMessages(id string) (string, error) {
return api.db.GetExOtherMessages(id)
}
func (api *SettingsAPI) NotificationsSetExemptions(id string, muteAllMessages bool, personalMentions string,
globalMentions string, otherMessages string) error {
return api.db.SetExemptions(id, muteAllMessages, personalMentions, globalMentions, otherMessages)
}
func (api *SettingsAPI) DeleteExemptions(id string) error {
return api.db.DeleteExemptions(id)
}
// Deprecated: Use api.go/SetBio instead
func (api *SettingsAPI) SetBio(bio string) error {
return (*api.messenger).SetBio(bio)
}
func (api *SettingsAPI) GetSocialLinks() (identity.SocialLinks, error) {
return api.db.GetSocialLinks()
}
func (api *SettingsAPI) AddOrReplaceSocialLinks(links identity.SocialLinks) error {
for _, link := range links {
if len(link.Text) == 0 {
return errors.New("`Text` field of a social link must be set")
}
if len(link.URL) == 0 {
return errors.New("`URL` field of a social link must be set")
}
}
return (*api.messenger).AddOrReplaceSocialLinks(links)
}
func (api *SettingsAPI) MnemonicWasShown() error {
return api.db.MnemonicWasShown()
}
@@ -0,0 +1,17 @@
package settingsevent
import "github.com/status-im/status-go/multiaccounts/settings"
// EventType type for event types.
type EventType string
// Event is a type for accounts events.
type Event struct {
Type EventType `json:"type"`
Setting settings.SettingField `json:"setting"`
Value interface{} `json:"value"`
}
const (
EventTypeChanged EventType = "changed"
)
@@ -0,0 +1,72 @@
package settingsevent
import (
"context"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/services/wallet/async"
)
type SettingChangeCb func(setting settings.SettingField, value interface{})
// Watcher executes a given callback whenever an account gets added/removed
type Watcher struct {
feed *event.Feed
group *async.Group
callback SettingChangeCb
}
func NewWatcher(feed *event.Feed, callback SettingChangeCb) *Watcher {
return &Watcher{
feed: feed,
callback: callback,
}
}
func (w *Watcher) Start() {
if w.group != nil {
return
}
w.group = async.NewGroup(context.Background())
w.group.Add(func(ctx context.Context) error {
return watch(ctx, w.feed, w.callback)
})
}
func (w *Watcher) Stop() {
if w.group != nil {
w.group.Stop()
w.group.Wait()
w.group = nil
}
}
func onSettingChanged(callback SettingChangeCb, setting settings.SettingField, value interface{}) {
if callback != nil {
callback(setting, value)
}
}
func watch(ctx context.Context, feed *event.Feed, callback SettingChangeCb) error {
ch := make(chan Event, 1)
sub := feed.Subscribe(ch)
defer sub.Unsubscribe()
for {
select {
case <-ctx.Done():
return nil
case err := <-sub.Err():
if err != nil {
log.Error("settings watcher subscription failed", "error", err)
}
case ev := <-ch:
if ev.Type == EventTypeChanged {
onSettingChanged(callback, ev.Setting, ev.Value)
}
}
}
}