2019-08-20 15:38:40 +00:00
|
|
|
package accounts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2019-08-08 07:31:24 +00:00
|
|
|
"encoding/json"
|
2019-08-20 15:38:40 +00:00
|
|
|
"errors"
|
|
|
|
|
2019-12-11 13:59:37 +00:00
|
|
|
"github.com/status-im/status-go/eth-node/types"
|
2019-12-27 09:58:25 +00:00
|
|
|
"github.com/status-im/status-go/params"
|
2019-08-20 15:38:40 +00:00
|
|
|
"github.com/status-im/status-go/sqlite"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
uniqueChatConstraint = "UNIQUE constraint failed: accounts.chat"
|
|
|
|
uniqueWalletConstraint = "UNIQUE constraint failed: accounts.wallet"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrWalletNotUnique returned if another account has `wallet` field set to true.
|
|
|
|
ErrWalletNotUnique = errors.New("another account is set to be default wallet. disable it before using new")
|
|
|
|
// ErrChatNotUnique returned if another account has `chat` field set to true.
|
|
|
|
ErrChatNotUnique = errors.New("another account is set to be default chat. disable it before using new")
|
2019-12-27 09:58:25 +00:00
|
|
|
// ErrInvalidConfig returned if config isn't allowed
|
|
|
|
ErrInvalidConfig = errors.New("configuration value not allowed")
|
2019-08-20 15:38:40 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Account struct {
|
2019-12-11 13:59:37 +00:00
|
|
|
Address types.Address `json:"address"`
|
2019-08-20 15:38:40 +00:00
|
|
|
Wallet bool `json:"wallet"`
|
|
|
|
Chat bool `json:"chat"`
|
2019-12-16 15:23:36 +00:00
|
|
|
Type string `json:"type,omitempty"`
|
|
|
|
Storage string `json:"storage,omitempty"`
|
|
|
|
Path string `json:"path,omitempty"`
|
2019-12-11 13:59:37 +00:00
|
|
|
PublicKey types.HexBytes `json:"public-key,omitempty"`
|
2019-08-20 15:38:40 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Color string `json:"color"`
|
|
|
|
}
|
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
type Settings struct {
|
|
|
|
// required
|
2020-07-22 07:41:40 +00:00
|
|
|
Address types.Address `json:"address"`
|
|
|
|
ChaosMode bool `json:"chaos-mode?,omitempty"`
|
|
|
|
Currency string `json:"currency,omitempty"`
|
|
|
|
CurrentNetwork string `json:"networks/current-network"`
|
|
|
|
CustomBootnodes *json.RawMessage `json:"custom-bootnodes,omitempty"`
|
|
|
|
CustomBootnodesEnabled *json.RawMessage `json:"custom-bootnodes-enabled?,omitempty"`
|
|
|
|
DappsAddress types.Address `json:"dapps-address"`
|
|
|
|
EIP1581Address types.Address `json:"eip1581-address"`
|
|
|
|
Fleet *string `json:"fleet,omitempty"`
|
|
|
|
HideHomeTooltip bool `json:"hide-home-tooltip?,omitempty"`
|
|
|
|
InstallationID string `json:"installation-id"`
|
|
|
|
KeyUID string `json:"key-uid"`
|
|
|
|
KeycardInstanceUID string `json:"keycard-instance-uid,omitempty"`
|
|
|
|
KeycardPAiredOn int64 `json:"keycard-paired-on,omitempty"`
|
|
|
|
KeycardPairing string `json:"keycard-pairing,omitempty"`
|
|
|
|
LastUpdated *int64 `json:"last-updated,omitempty"`
|
|
|
|
LatestDerivedPath uint `json:"latest-derived-path"`
|
|
|
|
LogLevel *string `json:"log-level,omitempty"`
|
|
|
|
Mnemonic *string `json:"mnemonic,omitempty"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Networks *json.RawMessage `json:"networks/networks"`
|
|
|
|
// NotificationsEnabled indicates whether local notifications should be enabled (android only)
|
|
|
|
NotificationsEnabled bool `json:"notifications-enabled?,omitempty"`
|
|
|
|
PhotoPath string `json:"photo-path"`
|
|
|
|
PinnedMailserver *json.RawMessage `json:"pinned-mailservers,omitempty"`
|
|
|
|
PreferredName *string `json:"preferred-name,omitempty"`
|
|
|
|
PreviewPrivacy bool `json:"preview-privacy?"`
|
|
|
|
PublicKey string `json:"public-key"`
|
|
|
|
// PushNotificationsServerEnabled indicates whether we should be running a push notification server
|
|
|
|
PushNotificationsServerEnabled bool `json:"push-notifications-server-enabled?,omitempty"`
|
|
|
|
// PushNotificationsFromContactsOnly indicates whether we should only receive push notifications from contacts
|
|
|
|
PushNotificationsFromContactsOnly bool `json:"push-notifications-from-contacts-only?,omitempty"`
|
|
|
|
RememberSyncingChoice bool `json:"remember-syncing-choice?,omitempty"`
|
|
|
|
// RemotePushNotificationsEnabled indicates whether we should be using remote notifications (ios only for now)
|
|
|
|
RemotePushNotificationsEnabled bool `json:"remote-push-notifications-enabled?,omitempty"`
|
|
|
|
SigningPhrase string `json:"signing-phrase"`
|
|
|
|
StickerPacksInstalled *json.RawMessage `json:"stickers/packs-installed,omitempty"`
|
|
|
|
StickerPacksPending *json.RawMessage `json:"stickers/packs-pending,omitempty"`
|
|
|
|
StickersRecentStickers *json.RawMessage `json:"stickers/recent-stickers,omitempty"`
|
|
|
|
SyncingOnMobileNetwork bool `json:"syncing-on-mobile-network?,omitempty"`
|
|
|
|
// SendPushNotifications indicates whether we should send push notifications for other clients
|
|
|
|
SendPushNotifications bool `json:"send-push-notifications?,omitempty"`
|
|
|
|
Appearance uint `json:"appearance"`
|
|
|
|
Usernames *json.RawMessage `json:"usernames,omitempty"`
|
|
|
|
WalletRootAddress types.Address `json:"wallet-root-address,omitempty"`
|
|
|
|
WalletSetUpPassed bool `json:"wallet-set-up-passed?,omitempty"`
|
|
|
|
WalletVisibleTokens *json.RawMessage `json:"wallet/visible-tokens,omitempty"`
|
|
|
|
WakuEnabled bool `json:"waku-enabled,omitempty"`
|
|
|
|
WakuBloomFilterMode bool `json:"waku-bloom-filter-mode,omitempty"`
|
2019-12-27 09:58:25 +00:00
|
|
|
}
|
|
|
|
|
2019-07-25 05:35:09 +00:00
|
|
|
func NewDB(db *sql.DB) *Database {
|
|
|
|
return &Database{db: db}
|
|
|
|
}
|
|
|
|
|
2019-08-20 15:38:40 +00:00
|
|
|
// Database sql wrapper for operations with browser objects.
|
|
|
|
type Database struct {
|
|
|
|
db *sql.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes database.
|
|
|
|
func (db Database) Close() error {
|
|
|
|
return db.db.Close()
|
|
|
|
}
|
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
func (db *Database) CreateSettings(s Settings, nodecfg params.NodeConfig) error {
|
|
|
|
_, err := db.db.Exec(`
|
|
|
|
INSERT INTO settings (
|
|
|
|
address,
|
|
|
|
currency,
|
|
|
|
current_network,
|
|
|
|
dapps_address,
|
|
|
|
eip1581_address,
|
|
|
|
installation_id,
|
|
|
|
key_uid,
|
|
|
|
keycard_instance_uid,
|
|
|
|
keycard_paired_on,
|
|
|
|
keycard_pairing,
|
|
|
|
latest_derived_path,
|
|
|
|
mnemonic,
|
|
|
|
name,
|
|
|
|
networks,
|
|
|
|
node_config,
|
|
|
|
photo_path,
|
|
|
|
preview_privacy,
|
|
|
|
public_key,
|
|
|
|
signing_phrase,
|
|
|
|
wallet_root_address,
|
2020-03-27 09:53:34 +00:00
|
|
|
waku_enabled,
|
2019-12-27 09:58:25 +00:00
|
|
|
synthetic_id
|
|
|
|
) VALUES (
|
|
|
|
?,?,?,?,?,?,?,?,?,?,
|
|
|
|
?,?,?,?,?,?,?,?,?,?,
|
2020-03-27 09:53:34 +00:00
|
|
|
?,
|
2019-12-27 09:58:25 +00:00
|
|
|
'id')`,
|
|
|
|
s.Address,
|
|
|
|
s.Currency,
|
|
|
|
s.CurrentNetwork,
|
|
|
|
s.DappsAddress,
|
|
|
|
s.EIP1581Address,
|
|
|
|
s.InstallationID,
|
|
|
|
s.KeyUID,
|
|
|
|
s.KeycardInstanceUID,
|
|
|
|
s.KeycardPAiredOn,
|
|
|
|
s.KeycardPairing,
|
|
|
|
s.LatestDerivedPath,
|
|
|
|
s.Mnemonic,
|
|
|
|
s.Name,
|
|
|
|
s.Networks,
|
|
|
|
&sqlite.JSONBlob{nodecfg},
|
|
|
|
s.PhotoPath,
|
|
|
|
s.PreviewPrivacy,
|
|
|
|
s.PublicKey,
|
|
|
|
s.SigningPhrase,
|
2020-03-27 09:53:34 +00:00
|
|
|
s.WalletRootAddress,
|
|
|
|
s.WakuEnabled,
|
|
|
|
)
|
2019-08-20 15:38:40 +00:00
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
return err
|
2019-08-20 15:38:40 +00:00
|
|
|
}
|
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
func (db *Database) SaveSetting(setting string, value interface{}) error {
|
|
|
|
var (
|
|
|
|
update *sql.Stmt
|
|
|
|
err error
|
|
|
|
)
|
2019-08-20 15:38:40 +00:00
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
switch setting {
|
|
|
|
case "chaos-mode?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET chaos_mode = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "currency":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET currency = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "custom-bootnodes":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET custom_bootnodes = ? WHERE synthetic_id = 'id'")
|
2020-01-02 17:01:38 +00:00
|
|
|
case "custom-bootnodes-enabled?":
|
2019-12-27 09:58:25 +00:00
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET custom_bootnodes_enabled = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "dapps-address":
|
2020-01-15 20:33:15 +00:00
|
|
|
str, ok := value.(string)
|
|
|
|
if ok {
|
|
|
|
value = types.HexToAddress(str)
|
|
|
|
} else {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET dapps_address = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "eip1581-address":
|
2020-01-15 20:33:15 +00:00
|
|
|
str, ok := value.(string)
|
|
|
|
if ok {
|
|
|
|
value = types.HexToAddress(str)
|
|
|
|
} else {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET eip1581_address = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "fleet":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET fleet = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "hide-home-tooltip?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET hide_home_tooltip = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "keycard-instance_uid":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET keycard_instance_uid = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "keycard-paired_on":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET keycard_paired_on = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "keycard-pairing":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET keycard_pairing = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "last-updated":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET last_updated = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "latest-derived-path":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET latest_derived_path = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "log-level":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET log_level = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "mnemonic":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET mnemonic = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "name":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET name = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "networks/current-network":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET current_network = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "networks/networks":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET networks = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "node-config":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET node_config = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "notifications-enabled?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET notifications_enabled = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "photo-path":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET photo_path = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "pinned-mailservers":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET pinned_mailservers = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "preferred-name":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET preferred_name = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "preview-privacy?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET preview_privacy = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "public-key":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET public_key = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "remember-syncing-choice?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET remember_syncing_choice = ? WHERE synthetic_id = 'id'")
|
2020-07-22 07:41:40 +00:00
|
|
|
case "remote-push-notifications-enabled?":
|
2020-07-15 12:25:01 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET remote_push_notifications_enabled = ? WHERE synthetic_id = 'id'")
|
2020-07-22 07:41:40 +00:00
|
|
|
case "push-notifications-server-enabled?":
|
2020-07-15 12:25:01 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2020-07-17 11:41:49 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET push_notifications_server_enabled = ? WHERE synthetic_id = 'id'")
|
2020-07-22 07:41:40 +00:00
|
|
|
case "push-notifications-from-contacts-only?":
|
2020-07-17 11:41:49 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET push_notifications_from_contacts_only = ? WHERE synthetic_id = 'id'")
|
2020-07-22 07:41:40 +00:00
|
|
|
case "send-push-notifications?":
|
2020-07-15 12:25:01 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET send_push_notifications = ? WHERE synthetic_id = 'id'")
|
2020-01-10 15:12:09 +00:00
|
|
|
case "stickers/packs-installed":
|
2019-12-27 09:58:25 +00:00
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET stickers_packs_installed = ? WHERE synthetic_id = 'id'")
|
2020-02-13 11:48:04 +00:00
|
|
|
case "stickers/packs-pending":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET stickers_packs_pending = ? WHERE synthetic_id = 'id'")
|
2020-01-10 15:12:09 +00:00
|
|
|
case "stickers/recent-stickers":
|
2019-12-27 09:58:25 +00:00
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET stickers_recent_stickers = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "syncing-on-mobile-network?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET syncing_on_mobile_network = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "usernames":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET usernames = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "wallet-set-up-passed?":
|
2020-01-02 17:01:38 +00:00
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
update, err = db.db.Prepare("UPDATE settings SET wallet_set_up_passed = ? WHERE synthetic_id = 'id'")
|
|
|
|
case "wallet/visible-tokens":
|
|
|
|
value = &sqlite.JSONBlob{value}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET wallet_visible_tokens = ? WHERE synthetic_id = 'id'")
|
2020-02-21 14:48:53 +00:00
|
|
|
case "waku-enabled":
|
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET waku_enabled = ? WHERE synthetic_id = 'id'")
|
2020-03-23 09:45:32 +00:00
|
|
|
case "appearance":
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET appearance = ? WHERE synthetic_id = 'id'")
|
2020-02-21 14:48:53 +00:00
|
|
|
case "waku-bloom-filter-mode":
|
|
|
|
_, ok := value.(bool)
|
|
|
|
if !ok {
|
|
|
|
return ErrInvalidConfig
|
|
|
|
}
|
|
|
|
update, err = db.db.Prepare("UPDATE settings SET waku_bloom_filter_mode = ? WHERE synthetic_id = 'id'")
|
|
|
|
|
2019-12-27 09:58:25 +00:00
|
|
|
default:
|
|
|
|
return ErrInvalidConfig
|
2019-08-08 07:31:24 +00:00
|
|
|
}
|
|
|
|
if err != nil {
|
2019-12-27 09:58:25 +00:00
|
|
|
return err
|
2019-08-08 07:31:24 +00:00
|
|
|
}
|
2019-12-27 09:58:25 +00:00
|
|
|
_, err = update.Exec(value)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *Database) GetNodeConfig(nodecfg interface{}) error {
|
|
|
|
return db.db.QueryRow("SELECT node_config FROM settings WHERE synthetic_id = 'id'").Scan(&sqlite.JSONBlob{nodecfg})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *Database) GetSettings() (Settings, error) {
|
|
|
|
var s Settings
|
2020-07-22 07:41:40 +00:00
|
|
|
err := db.db.QueryRow("SELECT address, chaos_mode, currency, current_network, custom_bootnodes, custom_bootnodes_enabled, dapps_address, eip1581_address, fleet, hide_home_tooltip, installation_id, key_uid, keycard_instance_uid, keycard_paired_on, keycard_pairing, last_updated, latest_derived_path, log_level, mnemonic, name, networks, notifications_enabled, push_notifications_server_enabled, push_notifications_from_contacts_only, remote_push_notifications_enabled, send_push_notifications, photo_path, pinned_mailservers, preferred_name, preview_privacy, public_key, remember_syncing_choice, signing_phrase, stickers_packs_installed, stickers_packs_pending, stickers_recent_stickers, syncing_on_mobile_network, usernames, appearance, wallet_root_address, wallet_set_up_passed, wallet_visible_tokens, waku_enabled, waku_bloom_filter_mode FROM settings WHERE synthetic_id = 'id'").Scan(
|
2019-12-27 09:58:25 +00:00
|
|
|
&s.Address,
|
|
|
|
&s.ChaosMode,
|
|
|
|
&s.Currency,
|
|
|
|
&s.CurrentNetwork,
|
|
|
|
&s.CustomBootnodes,
|
|
|
|
&s.CustomBootnodesEnabled,
|
|
|
|
&s.DappsAddress,
|
|
|
|
&s.EIP1581Address,
|
|
|
|
&s.Fleet,
|
|
|
|
&s.HideHomeTooltip,
|
|
|
|
&s.InstallationID,
|
|
|
|
&s.KeyUID,
|
|
|
|
&s.KeycardInstanceUID,
|
|
|
|
&s.KeycardPAiredOn,
|
|
|
|
&s.KeycardPairing,
|
|
|
|
&s.LastUpdated,
|
|
|
|
&s.LatestDerivedPath,
|
|
|
|
&s.LogLevel,
|
|
|
|
&s.Mnemonic,
|
|
|
|
&s.Name,
|
|
|
|
&s.Networks,
|
|
|
|
&s.NotificationsEnabled,
|
2020-07-22 07:41:40 +00:00
|
|
|
&s.PushNotificationsServerEnabled,
|
|
|
|
&s.PushNotificationsFromContactsOnly,
|
|
|
|
&s.RemotePushNotificationsEnabled,
|
|
|
|
&s.SendPushNotifications,
|
2019-12-27 09:58:25 +00:00
|
|
|
&s.PhotoPath,
|
|
|
|
&s.PinnedMailserver,
|
|
|
|
&s.PreferredName,
|
|
|
|
&s.PreviewPrivacy,
|
|
|
|
&s.PublicKey,
|
|
|
|
&s.RememberSyncingChoice,
|
|
|
|
&s.SigningPhrase,
|
|
|
|
&s.StickerPacksInstalled,
|
2020-02-13 11:48:04 +00:00
|
|
|
&s.StickerPacksPending,
|
2019-12-27 09:58:25 +00:00
|
|
|
&s.StickersRecentStickers,
|
|
|
|
&s.SyncingOnMobileNetwork,
|
|
|
|
&s.Usernames,
|
2020-03-23 09:45:32 +00:00
|
|
|
&s.Appearance,
|
2019-12-27 09:58:25 +00:00
|
|
|
&s.WalletRootAddress,
|
2020-01-02 17:01:38 +00:00
|
|
|
&s.WalletSetUpPassed,
|
2020-02-27 09:15:37 +00:00
|
|
|
&s.WalletVisibleTokens,
|
|
|
|
&s.WakuEnabled,
|
|
|
|
&s.WakuBloomFilterMode)
|
2019-12-27 09:58:25 +00:00
|
|
|
return s, err
|
2019-08-08 07:31:24 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 15:38:40 +00:00
|
|
|
func (db *Database) GetAccounts() ([]Account, error) {
|
2019-12-16 15:23:36 +00:00
|
|
|
rows, err := db.db.Query("SELECT address, wallet, chat, type, storage, pubkey, path, name, color FROM accounts ORDER BY created_at")
|
2019-08-20 15:38:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-14 10:51:32 +00:00
|
|
|
defer rows.Close()
|
2019-08-20 15:38:40 +00:00
|
|
|
accounts := []Account{}
|
|
|
|
pubkey := []byte{}
|
|
|
|
for rows.Next() {
|
|
|
|
acc := Account{}
|
|
|
|
err := rows.Scan(
|
|
|
|
&acc.Address, &acc.Wallet, &acc.Chat, &acc.Type, &acc.Storage,
|
|
|
|
&pubkey, &acc.Path, &acc.Name, &acc.Color)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if lth := len(pubkey); lth > 0 {
|
2019-12-11 13:59:37 +00:00
|
|
|
acc.PublicKey = make(types.HexBytes, lth)
|
2019-08-20 15:38:40 +00:00
|
|
|
copy(acc.PublicKey, pubkey)
|
|
|
|
}
|
|
|
|
accounts = append(accounts, acc)
|
|
|
|
}
|
|
|
|
return accounts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *Database) SaveAccounts(accounts []Account) (err error) {
|
|
|
|
var (
|
|
|
|
tx *sql.Tx
|
|
|
|
insert *sql.Stmt
|
|
|
|
update *sql.Stmt
|
|
|
|
)
|
|
|
|
tx, err = db.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err == nil {
|
|
|
|
err = tx.Commit()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_ = tx.Rollback()
|
|
|
|
}()
|
|
|
|
// NOTE(dshulyak) replace all record values using address (primary key)
|
|
|
|
// can't use `insert or replace` because of the additional constraints (wallet and chat)
|
2019-12-16 15:23:36 +00:00
|
|
|
insert, err = tx.Prepare("INSERT OR IGNORE INTO accounts (address, created_at, updated_at) VALUES (?, datetime('now'), datetime('now'))")
|
2019-08-20 15:38:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-16 15:23:36 +00:00
|
|
|
update, err = tx.Prepare("UPDATE accounts SET wallet = ?, chat = ?, type = ?, storage = ?, pubkey = ?, path = ?, name = ?, color = ?, updated_at = datetime('now') WHERE address = ?")
|
2019-08-20 15:38:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for i := range accounts {
|
|
|
|
acc := &accounts[i]
|
|
|
|
_, err = insert.Exec(acc.Address)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_, err = update.Exec(acc.Wallet, acc.Chat, acc.Type, acc.Storage, acc.PublicKey, acc.Path, acc.Name, acc.Color, acc.Address)
|
|
|
|
if err != nil {
|
|
|
|
switch err.Error() {
|
|
|
|
case uniqueChatConstraint:
|
|
|
|
err = ErrChatNotUnique
|
|
|
|
case uniqueWalletConstraint:
|
|
|
|
err = ErrWalletNotUnique
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-11 13:59:37 +00:00
|
|
|
func (db *Database) DeleteAccount(address types.Address) error {
|
2019-12-16 15:23:36 +00:00
|
|
|
_, err := db.db.Exec("DELETE FROM accounts WHERE address = ?", address)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-11 13:59:37 +00:00
|
|
|
func (db *Database) GetWalletAddress() (rst types.Address, err error) {
|
2019-08-20 15:38:40 +00:00
|
|
|
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
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
|
|
|
func (db *Database) GetWalletAddresses() (rst []types.Address, err error) {
|
|
|
|
rows, err := db.db.Query("SELECT address FROM accounts WHERE chat = 0 ORDER BY created_at")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
|
|
addr := types.Address{}
|
|
|
|
err = rows.Scan(&addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rst = append(rst, addr)
|
|
|
|
}
|
|
|
|
return rst, nil
|
|
|
|
}
|
|
|
|
|
2019-12-11 13:59:37 +00:00
|
|
|
func (db *Database) GetChatAddress() (rst types.Address, err error) {
|
2019-08-20 15:38:40 +00:00
|
|
|
err = db.db.QueryRow("SELECT address FROM accounts WHERE chat = 1").Scan(&rst)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-12-11 13:59:37 +00:00
|
|
|
func (db *Database) GetAddresses() (rst []types.Address, err error) {
|
2019-12-16 15:23:36 +00:00
|
|
|
rows, err := db.db.Query("SELECT address FROM accounts ORDER BY created_at")
|
2019-08-20 15:38:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-14 10:51:32 +00:00
|
|
|
defer rows.Close()
|
2019-08-20 15:38:40 +00:00
|
|
|
for rows.Next() {
|
2019-12-11 13:59:37 +00:00
|
|
|
addr := types.Address{}
|
2019-08-20 15:38:40 +00:00
|
|
|
err = rows.Scan(&addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rst = append(rst, addr)
|
|
|
|
}
|
|
|
|
return rst, nil
|
|
|
|
}
|
2019-08-29 08:06:22 +00:00
|
|
|
|
|
|
|
// AddressExists returns true if given address is stored in database.
|
2019-12-11 13:59:37 +00:00
|
|
|
func (db *Database) AddressExists(address types.Address) (exists bool, err error) {
|
2019-08-29 08:06:22 +00:00
|
|
|
err = db.db.QueryRow("SELECT EXISTS (SELECT 1 FROM accounts WHERE address = ?)", address).Scan(&exists)
|
|
|
|
return exists, err
|
|
|
|
}
|