38
vendor/github.com/status-im/status-go/services/accounts/README.md
generated
vendored
Normal file
38
vendor/github.com/status-im/status-go/services/accounts/README.md
generated
vendored
Normal 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
vendor/github.com/status-im/status-go/services/accounts/accounts.go
generated
vendored
Normal file
614
vendor/github.com/status-im/status-go/services/accounts/accounts.go
generated
vendored
Normal 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)
|
||||
}
|
||||
19
vendor/github.com/status-im/status-go/services/accounts/accountsevent/events.go
generated
vendored
Normal file
19
vendor/github.com/status-im/status-go/services/accounts/accountsevent/events.go
generated
vendored
Normal file
@@ -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"
|
||||
)
|
||||
85
vendor/github.com/status-im/status-go/services/accounts/accountsevent/watcher.go
generated
vendored
Normal file
85
vendor/github.com/status-im/status-go/services/accounts/accountsevent/watcher.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
93
vendor/github.com/status-im/status-go/services/accounts/multiaccounts.go
generated
vendored
Normal file
93
vendor/github.com/status-im/status-go/services/accounts/multiaccounts.go
generated
vendored
Normal file
@@ -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
vendor/github.com/status-im/status-go/services/accounts/out.json
generated
vendored
Normal file
1
vendor/github.com/status-im/status-go/services/accounts/out.json
generated
vendored
Normal 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
vendor/github.com/status-im/status-go/services/accounts/service.go
generated
vendored
Normal file
97
vendor/github.com/status-im/status-go/services/accounts/service.go
generated
vendored
Normal 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
vendor/github.com/status-im/status-go/services/accounts/settings.go
generated
vendored
Normal file
193
vendor/github.com/status-im/status-go/services/accounts/settings.go
generated
vendored
Normal 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()
|
||||
}
|
||||
17
vendor/github.com/status-im/status-go/services/accounts/settingsevent/events.go
generated
vendored
Normal file
17
vendor/github.com/status-im/status-go/services/accounts/settingsevent/events.go
generated
vendored
Normal file
@@ -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"
|
||||
)
|
||||
72
vendor/github.com/status-im/status-go/services/accounts/settingsevent/watcher.go
generated
vendored
Normal file
72
vendor/github.com/status-im/status-go/services/accounts/settingsevent/watcher.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/status-im/status-go/services/appmetrics/api.go
generated
vendored
Normal file
34
vendor/github.com/status-im/status-go/services/appmetrics/api.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package appmetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/appmetrics"
|
||||
)
|
||||
|
||||
func NewAPI(db *appmetrics.Database) *API {
|
||||
return &API{db: db, sessionID: uuid.NewRandom().String()}
|
||||
}
|
||||
|
||||
type API struct {
|
||||
db *appmetrics.Database
|
||||
sessionID string
|
||||
}
|
||||
|
||||
func (api *API) ValidateAppMetrics(ctx context.Context, appMetrics []appmetrics.AppMetric) error {
|
||||
log.Debug("[AppMetricsAPI::ValidateAppMetrics]")
|
||||
return api.db.ValidateAppMetrics(appMetrics)
|
||||
}
|
||||
|
||||
func (api *API) SaveAppMetrics(ctx context.Context, appMetrics []appmetrics.AppMetric) error {
|
||||
log.Debug("[AppMetricsAPI::SaveAppMetrics]")
|
||||
return api.db.SaveAppMetrics(appMetrics, api.sessionID)
|
||||
}
|
||||
|
||||
func (api *API) GetAppMetrics(ctx context.Context, limit int, offset int) (appmetrics.Page, error) {
|
||||
log.Debug("[AppMetricsAPI::GetAppMetrics]")
|
||||
return api.db.GetAppMetrics(limit, offset)
|
||||
}
|
||||
39
vendor/github.com/status-im/status-go/services/appmetrics/service.go
generated
vendored
Normal file
39
vendor/github.com/status-im/status-go/services/appmetrics/service.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package appmetrics
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/appmetrics"
|
||||
)
|
||||
|
||||
func NewService(db *appmetrics.Database) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *appmetrics.Database
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "appmetrics",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s.db),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
48
vendor/github.com/status-im/status-go/services/browsers/README.md
generated
vendored
Normal file
48
vendor/github.com/status-im/status-go/services/browsers/README.md
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
Browsers Service
|
||||
================
|
||||
|
||||
Browser service provides read/write API for browser object.
|
||||
|
||||
To enable include browsers config part and add `browsers` to APIModules:
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"BrowsersConfig": {
|
||||
"Enabled": true,
|
||||
},
|
||||
APIModules: "browsers"
|
||||
}
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Enabling service will expose three additional methods:
|
||||
|
||||
#### browsers_addBrowser
|
||||
|
||||
Stores browser in the database.
|
||||
All fields are specified below:
|
||||
|
||||
```json
|
||||
{
|
||||
"browser-id": "1",
|
||||
"name": "first",
|
||||
"timestamp": 10,
|
||||
"dapp?": true,
|
||||
"history-index": 1,
|
||||
"history": [
|
||||
"one",
|
||||
"two"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### browsers_getBrowsers
|
||||
|
||||
Reads all browsers, returns in the format specified above. List is sorted by timestamp.
|
||||
|
||||
#### browsers_deleteBrowser
|
||||
|
||||
Delete browser from database. Accepts browser `id`.
|
||||
51
vendor/github.com/status-im/status-go/services/browsers/api.go
generated
vendored
Normal file
51
vendor/github.com/status-im/status-go/services/browsers/api.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
func NewAPI(db *Database) *API {
|
||||
return &API{db: db}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
func (api *API) GetBookmarks(ctx context.Context) ([]*Bookmark, error) {
|
||||
log.Debug("call to get bookmarks")
|
||||
rst, err := api.db.GetBookmarks()
|
||||
log.Debug("result from database for bookmarks", "len", len(rst))
|
||||
return rst, err
|
||||
}
|
||||
|
||||
func (api *API) StoreBookmark(ctx context.Context, bookmark Bookmark) (Bookmark, error) {
|
||||
log.Debug("call to create a bookmark")
|
||||
bookmarkResult, err := api.db.StoreBookmark(bookmark)
|
||||
log.Debug("result from database for creating a bookmark", "err", err)
|
||||
return bookmarkResult, err
|
||||
}
|
||||
|
||||
func (api *API) UpdateBookmark(ctx context.Context, originalURL string, bookmark Bookmark) error {
|
||||
log.Debug("call to update a bookmark")
|
||||
err := api.db.UpdateBookmark(originalURL, bookmark)
|
||||
log.Debug("result from database for updating a bookmark", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (api *API) DeleteBookmark(ctx context.Context, url string) error {
|
||||
log.Debug("call to remove a bookmark")
|
||||
err := api.db.DeleteBookmark(url)
|
||||
log.Debug("result from database for remove a bookmark", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (api *API) RemoveBookmark(ctx context.Context, url string) error {
|
||||
log.Debug("call to remove a bookmark logically")
|
||||
err := api.db.RemoveBookmark(url)
|
||||
log.Debug("result from database for remove a bookmark logically", "err", err)
|
||||
return err
|
||||
}
|
||||
200
vendor/github.com/status-im/status-go/services/browsers/database.go
generated
vendored
Normal file
200
vendor/github.com/status-im/status-go/services/browsers/database.go
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/mat/besticon/besticon"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func NewDB(db *sql.DB) *Database {
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
type BookmarksType string
|
||||
|
||||
type Bookmark struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
ImageURL string `json:"imageUrl"`
|
||||
Removed bool `json:"removed"`
|
||||
Clock uint64 `json:"-"` //used to sync
|
||||
DeletedAt uint64 `json:"deletedAt,omitempty"`
|
||||
}
|
||||
type Browser struct {
|
||||
ID string `json:"browser-id"`
|
||||
Name string `json:"name"`
|
||||
Timestamp uint64 `json:"timestamp"`
|
||||
Dapp bool `json:"dapp?"`
|
||||
HistoryIndex int `json:"history-index"`
|
||||
History []string `json:"history,omitempty"`
|
||||
}
|
||||
|
||||
func (db *Database) GetBookmarks() ([]*Bookmark, error) {
|
||||
rows, err := db.db.Query(`SELECT url, name, image_url, removed, deleted_at FROM bookmarks`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var rst []*Bookmark
|
||||
for rows.Next() {
|
||||
bookmark := &Bookmark{}
|
||||
err := rows.Scan(&bookmark.URL, &bookmark.Name, &bookmark.ImageURL, &bookmark.Removed, &bookmark.DeletedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rst = append(rst, bookmark)
|
||||
}
|
||||
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db *Database) StoreBookmark(bookmark Bookmark) (Bookmark, error) {
|
||||
insert, err := db.db.Prepare("INSERT OR REPLACE INTO bookmarks (url, name, image_url, removed, clock, deleted_at) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
|
||||
if err != nil {
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
// Get the right icon
|
||||
finder := besticon.IconFinder{}
|
||||
icons, iconError := finder.FetchIcons(bookmark.URL)
|
||||
|
||||
if iconError == nil && len(icons) > 0 {
|
||||
icon := finder.IconInSizeRange(besticon.SizeRange{Min: 48, Perfect: 48, Max: 100})
|
||||
if icon != nil {
|
||||
bookmark.ImageURL = icon.URL
|
||||
} else {
|
||||
bookmark.ImageURL = icons[0].URL
|
||||
}
|
||||
} else {
|
||||
log.Error("error getting the bookmark icon", "iconError", iconError)
|
||||
}
|
||||
|
||||
_, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt)
|
||||
return bookmark, err
|
||||
}
|
||||
|
||||
func (db *Database) StoreBookmarkWithoutFetchIcon(bookmark *Bookmark, tx *sql.Tx) (err error) {
|
||||
if tx == nil {
|
||||
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
}
|
||||
|
||||
insert, err := tx.Prepare("INSERT OR REPLACE INTO bookmarks (url, name, image_url, removed, clock, deleted_at) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer insert.Close()
|
||||
|
||||
_, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) StoreSyncBookmarks(bookmarks []*Bookmark) ([]*Bookmark, error) {
|
||||
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
var storedBookmarks []*Bookmark
|
||||
for _, bookmark := range bookmarks {
|
||||
shouldSync, err := db.shouldSyncBookmark(bookmark, tx)
|
||||
if err != nil {
|
||||
return storedBookmarks, err
|
||||
}
|
||||
if shouldSync {
|
||||
err := db.StoreBookmarkWithoutFetchIcon(bookmark, tx)
|
||||
if err != nil {
|
||||
return storedBookmarks, err
|
||||
}
|
||||
storedBookmarks = append(storedBookmarks, bookmark)
|
||||
}
|
||||
}
|
||||
return storedBookmarks, nil
|
||||
}
|
||||
|
||||
func (db *Database) shouldSyncBookmark(bookmark *Bookmark, tx *sql.Tx) (shouldSync bool, err error) {
|
||||
if tx == nil {
|
||||
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
}
|
||||
qr := tx.QueryRow(`SELECT 1 FROM bookmarks WHERE url = ? AND clock > ?`, bookmark.URL, bookmark.Clock)
|
||||
var result int
|
||||
err = qr.Scan(&result)
|
||||
switch err {
|
||||
case sql.ErrNoRows:
|
||||
// Query does not match, therefore synced_at value is not older than the new clock value or id was not found
|
||||
return true, nil
|
||||
case nil:
|
||||
// Error is nil, therefore query matched and synced_at is older than the new clock
|
||||
return false, nil
|
||||
default:
|
||||
// Error is not nil and is not sql.ErrNoRows, therefore pass out the error
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) UpdateBookmark(originalURL string, bookmark Bookmark) error {
|
||||
insert, err := db.db.Prepare("UPDATE bookmarks SET url = ?, name = ?, image_url = ?, removed = ?, clock = ?, deleted_at = ? WHERE url = ?")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = insert.Exec(bookmark.URL, bookmark.Name, bookmark.ImageURL, bookmark.Removed, bookmark.Clock, bookmark.DeletedAt, originalURL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) DeleteBookmark(url string) error {
|
||||
_, err := db.db.Exec(`DELETE FROM bookmarks WHERE url = ?`, url)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) RemoveBookmark(url string) error {
|
||||
_, err := db.db.Exec(`UPDATE bookmarks SET removed = 1 WHERE url = ?`, url)
|
||||
return err
|
||||
}
|
||||
42
vendor/github.com/status-im/status-go/services/browsers/service.go
generated
vendored
Normal file
42
vendor/github.com/status-im/status-go/services/browsers/service.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package browsers
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// NewService initializes service instance.
|
||||
func NewService(db *Database) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
// Service is a browsers service.
|
||||
type Service struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
// Start a service.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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: "browsers",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s.db),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
558
vendor/github.com/status-im/status-go/services/chat/api.go
generated
vendored
Normal file
558
vendor/github.com/status-im/status-go/services/chat/api.go
generated
vendored
Normal file
@@ -0,0 +1,558 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/common/shard"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/requests"
|
||||
v1protocol "github.com/status-im/status-go/protocol/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrChatNotFound = errors.New("can't find chat")
|
||||
ErrCommunityNotFound = errors.New("can't find community")
|
||||
ErrCommunitiesNotSupported = errors.New("communities are not supported")
|
||||
ErrChatTypeNotSupported = errors.New("chat type not supported")
|
||||
)
|
||||
|
||||
type ChannelGroupType string
|
||||
|
||||
const Personal ChannelGroupType = "personal"
|
||||
const Community ChannelGroupType = "community"
|
||||
|
||||
type PinnedMessages struct {
|
||||
Cursor string
|
||||
PinnedMessages []*common.PinnedMessage
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
// Community Role
|
||||
Role protobuf.CommunityMember_Roles `json:"role,omitempty"`
|
||||
// Joined indicates if the member has joined the group chat
|
||||
Joined bool `json:"joined"`
|
||||
}
|
||||
|
||||
type Chat struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Color string `json:"color"`
|
||||
Emoji string `json:"emoji"`
|
||||
Active bool `json:"active"`
|
||||
ChatType protocol.ChatType `json:"chatType"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
LastClockValue uint64 `json:"lastClockValue"`
|
||||
DeletedAtClockValue uint64 `json:"deletedAtClockValue"`
|
||||
ReadMessagesAtClockValue uint64 `json:"readMessagesAtClockValue"`
|
||||
UnviewedMessagesCount uint `json:"unviewedMessagesCount"`
|
||||
UnviewedMentionsCount uint `json:"unviewedMentionsCount"`
|
||||
LastMessage *common.Message `json:"lastMessage"`
|
||||
Members map[string]Member `json:"members,omitempty"`
|
||||
MembershipUpdates []v1protocol.MembershipUpdateEvent `json:"membershipUpdateEvents"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Identicon string `json:"identicon"`
|
||||
Muted bool `json:"muted"`
|
||||
InvitationAdmin string `json:"invitationAdmin,omitempty"`
|
||||
ReceivedInvitationAdmin string `json:"receivedInvitationAdmin,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
CommunityID string `json:"communityId"`
|
||||
CategoryID string `json:"categoryId"`
|
||||
Position int32 `json:"position,omitempty"`
|
||||
Permissions *protobuf.CommunityPermissions `json:"permissions,omitempty"`
|
||||
Joined int64 `json:"joined,omitempty"`
|
||||
SyncedTo uint32 `json:"syncedTo,omitempty"`
|
||||
SyncedFrom uint32 `json:"syncedFrom,omitempty"`
|
||||
FirstMessageTimestamp uint32 `json:"firstMessageTimestamp,omitempty"`
|
||||
Highlight bool `json:"highlight,omitempty"`
|
||||
PinnedMessages *PinnedMessages `json:"pinnedMessages,omitempty"`
|
||||
CanPost bool `json:"canPost"`
|
||||
Base64Image string `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
type ChannelGroup struct {
|
||||
Type ChannelGroupType `json:"channelGroupType"`
|
||||
Name string `json:"name"`
|
||||
Images map[string]images.IdentityImage `json:"images"`
|
||||
Color string `json:"color"`
|
||||
Chats map[string]*Chat `json:"chats"`
|
||||
Categories map[string]communities.CommunityCategory `json:"categories"`
|
||||
EnsName string `json:"ensName"`
|
||||
MemberRole protobuf.CommunityMember_Roles `json:"memberRole"`
|
||||
Verified bool `json:"verified"`
|
||||
Description string `json:"description"`
|
||||
IntroMessage string `json:"introMessage"`
|
||||
OutroMessage string `json:"outroMessage"`
|
||||
Tags []communities.CommunityTag `json:"tags"`
|
||||
Permissions *protobuf.CommunityPermissions `json:"permissions"`
|
||||
Members map[string]*protobuf.CommunityMember `json:"members"`
|
||||
CanManageUsers bool `json:"canManageUsers"`
|
||||
Muted bool `json:"muted"`
|
||||
BanList []string `json:"banList"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
CommunityTokensMetadata []*protobuf.CommunityTokenMetadata `json:"communityTokensMetadata"`
|
||||
UnviewedMessagesCount int `json:"unviewedMessagesCount"`
|
||||
UnviewedMentionsCount int `json:"unviewedMentionsCount"`
|
||||
CheckChannelPermissionResponses map[string]*communities.CheckChannelPermissionsResponse `json:"checkChannelPermissionResponses"`
|
||||
PubsubTopic string `json:"pubsubTopic"`
|
||||
PubsubTopicKey string `json:"pubsubTopicKey"`
|
||||
Shard *shard.Shard `json:"shard"`
|
||||
}
|
||||
|
||||
func NewAPI(service *Service) *API {
|
||||
return &API{
|
||||
s: service,
|
||||
log: log.New("package", "status-go/services/chat.API"),
|
||||
}
|
||||
}
|
||||
|
||||
type API struct {
|
||||
s *Service
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func unique(communities []*communities.Community) (result []*communities.Community) {
|
||||
inResult := make(map[string]bool)
|
||||
for _, community := range communities {
|
||||
if _, ok := inResult[community.IDString()]; !ok {
|
||||
inResult[community.IDString()] = true
|
||||
result = append(result, community)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (api *API) getChannelGroups(ctx context.Context, channelGroupID string) (map[string]ChannelGroup, error) {
|
||||
joinedCommunities, err := api.s.messenger.JoinedCommunities()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spectatedCommunities, err := api.s.messenger.SpectatedCommunities()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
|
||||
result := make(map[string]ChannelGroup)
|
||||
|
||||
// Get chats from cache to get unviewed messages counts
|
||||
channels := api.s.messenger.Chats()
|
||||
totalUnviewedMessageCount := 0
|
||||
totalUnviewedMentionsCount := 0
|
||||
|
||||
if channelGroupID == "" || channelGroupID == pubKey {
|
||||
chats := make(map[string]*Chat)
|
||||
for _, chat := range channels {
|
||||
if !chat.IsActivePersonalChat() {
|
||||
continue
|
||||
}
|
||||
if !chat.Muted || chat.UnviewedMentionsCount > 0 {
|
||||
totalUnviewedMessageCount += int(chat.UnviewedMessagesCount)
|
||||
}
|
||||
totalUnviewedMentionsCount += int(chat.UnviewedMentionsCount)
|
||||
|
||||
c, err := api.toAPIChat(chat, nil, pubKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chats[chat.ID] = c
|
||||
}
|
||||
|
||||
result[pubKey] = ChannelGroup{
|
||||
Type: Personal,
|
||||
Name: "",
|
||||
Images: make(map[string]images.IdentityImage),
|
||||
Color: "",
|
||||
Chats: chats,
|
||||
Categories: make(map[string]communities.CommunityCategory),
|
||||
EnsName: "", // Not implemented yet in communities
|
||||
MemberRole: protobuf.CommunityMember_ROLE_OWNER,
|
||||
Verified: true,
|
||||
Description: "",
|
||||
IntroMessage: "",
|
||||
OutroMessage: "",
|
||||
Tags: []communities.CommunityTag{},
|
||||
Permissions: &protobuf.CommunityPermissions{},
|
||||
Muted: false,
|
||||
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
|
||||
UnviewedMessagesCount: totalUnviewedMessageCount,
|
||||
UnviewedMentionsCount: totalUnviewedMentionsCount,
|
||||
CheckChannelPermissionResponses: make(map[string]*communities.CheckChannelPermissionsResponse),
|
||||
}
|
||||
}
|
||||
|
||||
if channelGroupID == pubKey {
|
||||
// They asked for the personal channel group only, so we return now
|
||||
return result, nil
|
||||
}
|
||||
|
||||
for _, community := range unique(append(joinedCommunities, spectatedCommunities...)) {
|
||||
if channelGroupID != "" && channelGroupID != community.IDString() {
|
||||
continue
|
||||
}
|
||||
totalUnviewedMessageCount = 0
|
||||
totalUnviewedMentionsCount = 0
|
||||
|
||||
for _, chat := range channels {
|
||||
if chat.CommunityID != community.IDString() || !chat.Active {
|
||||
continue
|
||||
}
|
||||
if !chat.Muted || chat.UnviewedMentionsCount > 0 {
|
||||
totalUnviewedMessageCount += int(chat.UnviewedMessagesCount)
|
||||
}
|
||||
totalUnviewedMentionsCount += int(chat.UnviewedMentionsCount)
|
||||
}
|
||||
|
||||
chGrp := ChannelGroup{
|
||||
Type: Community,
|
||||
Name: community.Name(),
|
||||
Color: community.Color(),
|
||||
Images: make(map[string]images.IdentityImage),
|
||||
Chats: make(map[string]*Chat),
|
||||
Categories: make(map[string]communities.CommunityCategory),
|
||||
MemberRole: community.MemberRole(community.MemberIdentity()),
|
||||
Verified: community.Verified(),
|
||||
Description: community.DescriptionText(),
|
||||
IntroMessage: community.IntroMessage(),
|
||||
OutroMessage: community.OutroMessage(),
|
||||
Tags: community.Tags(),
|
||||
Permissions: community.Description().Permissions,
|
||||
Members: community.Description().Members,
|
||||
CanManageUsers: community.CanManageUsers(community.MemberIdentity()),
|
||||
Muted: community.Muted(),
|
||||
BanList: community.Description().BanList,
|
||||
Encrypted: community.Encrypted(),
|
||||
CommunityTokensMetadata: community.Description().CommunityTokensMetadata,
|
||||
UnviewedMessagesCount: totalUnviewedMessageCount,
|
||||
UnviewedMentionsCount: totalUnviewedMentionsCount,
|
||||
CheckChannelPermissionResponses: make(map[string]*communities.CheckChannelPermissionsResponse),
|
||||
PubsubTopic: community.PubsubTopic(),
|
||||
PubsubTopicKey: community.PubsubTopicKey(),
|
||||
Shard: community.Shard(),
|
||||
}
|
||||
|
||||
for t, i := range community.Images() {
|
||||
chGrp.Images[t] = images.IdentityImage{Name: t, Payload: i.Payload}
|
||||
}
|
||||
|
||||
for _, cat := range community.Categories() {
|
||||
chGrp.Categories[cat.CategoryId] = communities.CommunityCategory{
|
||||
ID: cat.CategoryId,
|
||||
Name: cat.Name,
|
||||
Position: int(cat.Position),
|
||||
}
|
||||
}
|
||||
|
||||
for _, chat := range channels {
|
||||
if chat.CommunityID == community.IDString() && chat.Active {
|
||||
_, exists := community.Chats()[chat.CommunityChatID()]
|
||||
if !exists {
|
||||
api.log.Warn("Chat not found in the community", "chat.ID", chat.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
c, err := api.toAPIChat(chat, community, pubKey, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chGrp.Chats[c.ID] = c
|
||||
}
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.GetCommunityCheckChannelPermissionResponses(community.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chGrp.CheckChannelPermissionResponses = response.Channels
|
||||
|
||||
result[community.IDString()] = chGrp
|
||||
|
||||
if channelGroupID == community.IDString() {
|
||||
// We asked for this particular community, so we return now
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (api *API) GetChannelGroups(ctx context.Context) (map[string]ChannelGroup, error) {
|
||||
return api.getChannelGroups(ctx, "")
|
||||
}
|
||||
|
||||
func (api *API) GetChannelGroupByID(ctx context.Context, channelGroupID string) (map[string]ChannelGroup, error) {
|
||||
return api.getChannelGroups(ctx, channelGroupID)
|
||||
}
|
||||
|
||||
func (api *API) GetChat(ctx context.Context, communityID types.HexBytes, chatID string) (*Chat, error) {
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
messengerChat, community, err := api.getChatAndCommunity(pubKey, communityID, chatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if messengerChat == nil {
|
||||
return nil, ErrChatNotFound
|
||||
}
|
||||
|
||||
result, err := api.toAPIChat(messengerChat, community, pubKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (api *API) GetMembers(ctx context.Context, communityID types.HexBytes, chatID string) (map[string]Member, error) {
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
messengerChat, community, err := api.getChatAndCommunity(pubKey, communityID, chatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getChatMembers(messengerChat, community, pubKey)
|
||||
}
|
||||
|
||||
func (api *API) JoinChat(ctx context.Context, communityID types.HexBytes, chatID string) (*Chat, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.CreatePublicChat(&requests.CreatePublicChat{ID: chatID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
|
||||
return api.toAPIChat(response.Chats()[0], nil, pubKey, false)
|
||||
}
|
||||
|
||||
func (api *API) toAPIChat(protocolChat *protocol.Chat, community *communities.Community, pubKey string, skipPinnedMessages bool) (*Chat, error) {
|
||||
chat := &Chat{
|
||||
ID: strings.TrimPrefix(protocolChat.ID, protocolChat.CommunityID),
|
||||
Name: protocolChat.Name,
|
||||
Description: protocolChat.Description,
|
||||
Color: protocolChat.Color,
|
||||
Emoji: protocolChat.Emoji,
|
||||
Active: protocolChat.Active,
|
||||
ChatType: protocolChat.ChatType,
|
||||
Timestamp: protocolChat.Timestamp,
|
||||
LastClockValue: protocolChat.LastClockValue,
|
||||
DeletedAtClockValue: protocolChat.DeletedAtClockValue,
|
||||
ReadMessagesAtClockValue: protocolChat.ReadMessagesAtClockValue,
|
||||
UnviewedMessagesCount: protocolChat.UnviewedMessagesCount,
|
||||
UnviewedMentionsCount: protocolChat.UnviewedMentionsCount,
|
||||
LastMessage: protocolChat.LastMessage,
|
||||
MembershipUpdates: protocolChat.MembershipUpdates,
|
||||
Alias: protocolChat.Alias,
|
||||
Identicon: protocolChat.Identicon,
|
||||
Muted: protocolChat.Muted,
|
||||
InvitationAdmin: protocolChat.InvitationAdmin,
|
||||
ReceivedInvitationAdmin: protocolChat.ReceivedInvitationAdmin,
|
||||
Profile: protocolChat.Profile,
|
||||
CommunityID: protocolChat.CommunityID,
|
||||
CategoryID: protocolChat.CategoryID,
|
||||
Joined: protocolChat.Joined,
|
||||
SyncedTo: protocolChat.SyncedTo,
|
||||
SyncedFrom: protocolChat.SyncedFrom,
|
||||
FirstMessageTimestamp: protocolChat.FirstMessageTimestamp,
|
||||
Highlight: protocolChat.Highlight,
|
||||
Base64Image: protocolChat.Base64Image,
|
||||
}
|
||||
|
||||
if protocolChat.OneToOne() {
|
||||
chat.Name = "" // Emptying since it contains non useful data
|
||||
}
|
||||
|
||||
if !skipPinnedMessages {
|
||||
pinnedMessages, cursor, err := api.s.messenger.PinnedMessageByChatID(protocolChat.ID, "", -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(pinnedMessages) != 0 {
|
||||
chat.PinnedMessages = &PinnedMessages{
|
||||
Cursor: cursor,
|
||||
PinnedMessages: pinnedMessages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := chat.populateCommunityFields(community)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chatMembers, err := getChatMembers(protocolChat, community, pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chat.Members = chatMembers
|
||||
|
||||
return chat, nil
|
||||
}
|
||||
|
||||
func getChatMembers(sourceChat *protocol.Chat, community *communities.Community, userPubKey string) (map[string]Member, error) {
|
||||
result := make(map[string]Member)
|
||||
if sourceChat != nil {
|
||||
if sourceChat.ChatType == protocol.ChatTypePrivateGroupChat && len(sourceChat.Members) > 0 {
|
||||
for _, m := range sourceChat.Members {
|
||||
result[m.ID] = Member{
|
||||
Role: func() protobuf.CommunityMember_Roles {
|
||||
if m.Admin {
|
||||
return protobuf.CommunityMember_ROLE_OWNER
|
||||
}
|
||||
return protobuf.CommunityMember_ROLE_NONE
|
||||
}(),
|
||||
Joined: true,
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if sourceChat.ChatType == protocol.ChatTypeOneToOne {
|
||||
result[sourceChat.ID] = Member{
|
||||
Joined: true,
|
||||
}
|
||||
result[userPubKey] = Member{
|
||||
Joined: true,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
if community != nil {
|
||||
channel, exists := community.Chats()[sourceChat.CommunityChatID()]
|
||||
if !exists {
|
||||
// Skip unknown community chats. They might be channels that were deleted. We shouldn't get here
|
||||
return result, nil
|
||||
}
|
||||
for member := range channel.Members {
|
||||
pubKey, err := common.HexToPubkey(member)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[member] = Member{
|
||||
Role: community.MemberRole(pubKey),
|
||||
Joined: community.Joined(),
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (api *API) getCommunityByID(id string) (*communities.Community, error) {
|
||||
communityID, err := hexutil.Decode(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
community, err := api.s.messenger.GetCommunityByID(communityID)
|
||||
if community == nil && err == nil {
|
||||
return nil, ErrCommunityNotFound
|
||||
}
|
||||
|
||||
return community, err
|
||||
}
|
||||
|
||||
func (chat *Chat) populateCommunityFields(community *communities.Community) error {
|
||||
if community == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
commChat, exists := community.Chats()[chat.ID]
|
||||
if !exists {
|
||||
// Skip unknown community chats. They might be channels that were deleted
|
||||
return nil
|
||||
}
|
||||
|
||||
canPost, err := community.CanMemberIdentityPost(chat.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chat.CategoryID = commChat.CategoryId
|
||||
chat.Position = commChat.Position
|
||||
chat.Permissions = commChat.Permissions
|
||||
chat.Emoji = commChat.Identity.Emoji
|
||||
chat.Name = commChat.Identity.DisplayName
|
||||
chat.Description = commChat.Identity.Description
|
||||
chat.CanPost = canPost
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) getChatAndCommunity(pubKey string, communityID types.HexBytes, chatID string) (*protocol.Chat, *communities.Community, error) {
|
||||
fullChatID := chatID
|
||||
|
||||
if string(communityID.Bytes()) == pubKey { // Obtaining chats from personal
|
||||
communityID = []byte{}
|
||||
}
|
||||
|
||||
if len(communityID) != 0 {
|
||||
id := string(communityID.Bytes())
|
||||
|
||||
if chatID == "" {
|
||||
community, err := api.getCommunityByID(id)
|
||||
return nil, community, err
|
||||
}
|
||||
|
||||
fullChatID = id + chatID
|
||||
}
|
||||
|
||||
messengerChat := api.s.messenger.Chat(fullChatID)
|
||||
if messengerChat == nil {
|
||||
return nil, nil, ErrChatNotFound
|
||||
}
|
||||
|
||||
var community *communities.Community
|
||||
if messengerChat.CommunityID != "" {
|
||||
var err error
|
||||
community, err = api.getCommunityByID(messengerChat.CommunityID)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return messengerChat, community, nil
|
||||
}
|
||||
|
||||
func (api *API) EditChat(ctx context.Context, communityID types.HexBytes, chatID string, name string, color string, image images.CroppedImage) (*Chat, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
chatToEdit := api.s.messenger.Chat(chatID)
|
||||
if chatToEdit == nil {
|
||||
return nil, ErrChatNotFound
|
||||
}
|
||||
|
||||
if chatToEdit.ChatType != protocol.ChatTypePrivateGroupChat {
|
||||
return nil, ErrChatTypeNotSupported
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.EditGroupChat(ctx, chatID, name, color, image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
return api.toAPIChat(response.Chats()[0], nil, pubKey, false)
|
||||
}
|
||||
236
vendor/github.com/status-im/status-go/services/chat/api_group_chats.go
generated
vendored
Normal file
236
vendor/github.com/status-im/status-go/services/chat/api_group_chats.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/requests"
|
||||
)
|
||||
|
||||
type GroupChatResponse struct {
|
||||
Chat *Chat `json:"chat"`
|
||||
Messages []*common.Message `json:"messages"`
|
||||
}
|
||||
|
||||
type GroupChatResponseWithInvitations struct {
|
||||
Chat *Chat `json:"chat"`
|
||||
Messages []*common.Message `json:"messages"`
|
||||
Invitations []*protocol.GroupChatInvitation `json:"invitations"`
|
||||
}
|
||||
|
||||
type CreateOneToOneChatResponse struct {
|
||||
Chat *Chat `json:"chat,omitempty"`
|
||||
Contact *protocol.Contact `json:"contact,omitempty"`
|
||||
}
|
||||
|
||||
type StartGroupChatResponse struct {
|
||||
Chat *Chat `json:"chat,omitempty"`
|
||||
Contacts []*protocol.Contact `json:"contacts"`
|
||||
Messages []*common.Message `json:"messages,omitempty"`
|
||||
}
|
||||
|
||||
func (api *API) CreateOneToOneChat(ctx context.Context, communityID types.HexBytes, ID types.HexBytes, ensName string) (*CreateOneToOneChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
response, err := api.s.messenger.CreateOneToOneChat(&requests.CreateOneToOneChat{ID: ID, ENSName: ensName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chat, err := api.toAPIChat(response.Chats()[0], nil, pubKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contact *protocol.Contact
|
||||
if ensName != "" {
|
||||
contact = response.Contacts[0]
|
||||
}
|
||||
|
||||
return &CreateOneToOneChatResponse{
|
||||
Chat: chat,
|
||||
Contact: contact,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) CreateGroupChat(ctx context.Context, communityID types.HexBytes, name string, members []string) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.CreateGroupChatWithMembers(ctx, name, members)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) CreateGroupChatFromInvitation(communityID types.HexBytes, name string, chatID string, adminPK string) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.CreateGroupChatFromInvitation(name, chatID, adminPK)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) LeaveChat(ctx context.Context, communityID types.HexBytes, chatID string, remove bool) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.LeaveGroupChat(ctx, chatID, remove)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) AddMembers(ctx context.Context, communityID types.HexBytes, chatID string, members []string) (*GroupChatResponseWithInvitations, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponseWithInvitations(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.AddMembersToGroupChat(ctx, chatID, members)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) RemoveMember(ctx context.Context, communityID types.HexBytes, chatID string, member string) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.RemoveMembersFromGroupChat(ctx, chatID, []string{member})
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) MakeAdmin(ctx context.Context, communityID types.HexBytes, chatID string, member string) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.AddAdminsToGroupChat(ctx, chatID, []string{member})
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) RenameChat(ctx context.Context, communityID types.HexBytes, chatID string, name string) (*GroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponse(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.ChangeGroupChatName(ctx, chatID, name)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) SendGroupChatInvitationRequest(ctx context.Context, communityID types.HexBytes, chatID string, adminPK string, message string) (*GroupChatResponseWithInvitations, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
return api.execAndGetGroupChatResponseWithInvitations(func() (*protocol.MessengerResponse, error) {
|
||||
return api.s.messenger.SendGroupChatInvitationRequest(ctx, chatID, adminPK, message)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) GetGroupChatInvitations() ([]*protocol.GroupChatInvitation, error) {
|
||||
return api.s.messenger.GetGroupChatInvitations()
|
||||
}
|
||||
|
||||
func (api *API) SendGroupChatInvitationRejection(ctx context.Context, invitationRequestID string) ([]*protocol.GroupChatInvitation, error) {
|
||||
response, err := api.s.messenger.SendGroupChatInvitationRejection(ctx, invitationRequestID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Invitations, nil
|
||||
}
|
||||
|
||||
func (api *API) StartGroupChat(ctx context.Context, communityID types.HexBytes, name string, members []string) (*StartGroupChatResponse, error) {
|
||||
if len(communityID) != 0 {
|
||||
return nil, ErrCommunitiesNotSupported
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
|
||||
var response *protocol.MessengerResponse
|
||||
var err error
|
||||
if len(members) == 1 {
|
||||
memberPk, err := common.HexToPubkey(members[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err = api.s.messenger.CreateOneToOneChat(&requests.CreateOneToOneChat{
|
||||
ID: types.HexBytes(crypto.FromECDSAPub(memberPk)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
response, err = api.s.messenger.CreateGroupChatWithMembers(ctx, name, members)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
chat, err := api.toAPIChat(response.Chats()[0], nil, pubKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &StartGroupChatResponse{
|
||||
Chat: chat,
|
||||
Contacts: response.Contacts,
|
||||
Messages: response.Messages(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) toGroupChatResponse(pubKey string, response *protocol.MessengerResponse) (*GroupChatResponse, error) {
|
||||
chat, err := api.toAPIChat(response.Chats()[0], nil, pubKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GroupChatResponse{
|
||||
Chat: chat,
|
||||
Messages: response.Messages(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) toGroupChatResponseWithInvitations(pubKey string, response *protocol.MessengerResponse) (*GroupChatResponseWithInvitations, error) {
|
||||
g, err := api.toGroupChatResponse(pubKey, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GroupChatResponseWithInvitations{
|
||||
Chat: g.Chat,
|
||||
Messages: g.Messages,
|
||||
Invitations: response.Invitations,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) execAndGetGroupChatResponse(fn func() (*protocol.MessengerResponse, error)) (*GroupChatResponse, error) {
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
response, err := fn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return api.toGroupChatResponse(pubKey, response)
|
||||
}
|
||||
|
||||
func (api *API) execAndGetGroupChatResponseWithInvitations(fn func() (*protocol.MessengerResponse, error)) (*GroupChatResponseWithInvitations, error) {
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
|
||||
response, err := fn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.toGroupChatResponseWithInvitations(pubKey, response)
|
||||
}
|
||||
161
vendor/github.com/status-im/status-go/services/chat/api_send_messages.go
generated
vendored
Normal file
161
vendor/github.com/status-im/status-go/services/chat/api_send_messages.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/forPelevin/gomoji"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type SendMessageResponse struct {
|
||||
Chat *Chat `json:"chat"`
|
||||
Messages []*common.Message `json:"messages"`
|
||||
}
|
||||
|
||||
func (api *API) SendSticker(ctx context.Context, communityID types.HexBytes, chatID string, packID int32, hash string, responseTo string) (*SendMessageResponse, error) {
|
||||
ensName, _ := api.s.accountsDB.GetPreferredUsername()
|
||||
|
||||
msg := &common.Message{
|
||||
CommunityID: string(communityID.Bytes()),
|
||||
ChatMessage: &protobuf.ChatMessage{
|
||||
ChatId: chatID,
|
||||
ContentType: protobuf.ChatMessage_STICKER,
|
||||
Text: "Update to latest version to see a nice sticker here!",
|
||||
Payload: &protobuf.ChatMessage_Sticker{
|
||||
Sticker: &protobuf.StickerMessage{
|
||||
Hash: hash,
|
||||
Pack: packID,
|
||||
},
|
||||
},
|
||||
ResponseTo: responseTo,
|
||||
EnsName: ensName,
|
||||
},
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.SendChatMessage(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.toSendMessageResponse(response)
|
||||
|
||||
}
|
||||
|
||||
func (api *API) toSendMessageResponse(response *protocol.MessengerResponse) (*SendMessageResponse, error) {
|
||||
protocolChat := response.Chats()[0]
|
||||
|
||||
community, err := api.s.messenger.GetCommunityByID(types.HexBytes(protocolChat.CommunityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubKey := types.EncodeHex(crypto.FromECDSAPub(api.s.messenger.IdentityPublicKey()))
|
||||
chat, err := api.toAPIChat(protocolChat, community, pubKey, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SendMessageResponse{
|
||||
Chat: chat,
|
||||
Messages: response.Messages(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isTextOrEmoji(text string) protobuf.ChatMessage_ContentType {
|
||||
contentType := protobuf.ChatMessage_TEXT_PLAIN
|
||||
if gomoji.RemoveEmojis(text) == "" && len(gomoji.FindAll(text)) != 0 {
|
||||
contentType = protobuf.ChatMessage_EMOJI
|
||||
}
|
||||
|
||||
return contentType
|
||||
}
|
||||
|
||||
func (api *API) SendMessage(ctx context.Context, communityID types.HexBytes, chatID string, text string, responseTo string) (*SendMessageResponse, error) {
|
||||
ensName, _ := api.s.accountsDB.GetPreferredUsername()
|
||||
|
||||
msg := &common.Message{
|
||||
CommunityID: string(communityID.Bytes()),
|
||||
ChatMessage: &protobuf.ChatMessage{
|
||||
ChatId: chatID,
|
||||
ContentType: isTextOrEmoji(text),
|
||||
Text: text,
|
||||
ResponseTo: responseTo,
|
||||
EnsName: ensName,
|
||||
},
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.SendChatMessage(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.toSendMessageResponse(response)
|
||||
}
|
||||
|
||||
func (api *API) SendImages(ctx context.Context, communityID types.HexBytes, chatID string, imagePaths []string, text string, responseTo string) (*SendMessageResponse, error) {
|
||||
ensName, _ := api.s.accountsDB.GetPreferredUsername()
|
||||
|
||||
var messages []*common.Message
|
||||
|
||||
for _, imagePath := range imagePaths {
|
||||
messages = append(messages, &common.Message{
|
||||
CommunityID: string(communityID.Bytes()),
|
||||
ChatMessage: &protobuf.ChatMessage{
|
||||
ChatId: chatID,
|
||||
ContentType: protobuf.ChatMessage_IMAGE,
|
||||
Text: "Update to latest version to see a nice image here!",
|
||||
ResponseTo: responseTo,
|
||||
EnsName: ensName,
|
||||
},
|
||||
ImagePath: imagePath,
|
||||
})
|
||||
}
|
||||
|
||||
if text != "" {
|
||||
messages = append(messages, &common.Message{
|
||||
CommunityID: string(communityID.Bytes()),
|
||||
ChatMessage: &protobuf.ChatMessage{
|
||||
ChatId: chatID,
|
||||
ContentType: isTextOrEmoji(text),
|
||||
Text: text,
|
||||
ResponseTo: responseTo,
|
||||
EnsName: ensName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.SendChatMessages(ctx, messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.toSendMessageResponse(response)
|
||||
|
||||
}
|
||||
|
||||
func (api *API) SendAudio(ctx context.Context, communityID types.HexBytes, chatID string, audioPath string, responseTo string) (*SendMessageResponse, error) {
|
||||
ensName, _ := api.s.accountsDB.GetPreferredUsername()
|
||||
|
||||
msg := &common.Message{
|
||||
CommunityID: string(communityID.Bytes()),
|
||||
ChatMessage: &protobuf.ChatMessage{
|
||||
ChatId: chatID,
|
||||
Text: "Update to latest version to listen to an audio message here!",
|
||||
ContentType: protobuf.ChatMessage_AUDIO,
|
||||
ResponseTo: responseTo,
|
||||
EnsName: ensName,
|
||||
},
|
||||
AudioPath: audioPath,
|
||||
}
|
||||
|
||||
response, err := api.s.messenger.SendChatMessage(ctx, msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.toSendMessageResponse(response)
|
||||
}
|
||||
46
vendor/github.com/status-im/status-go/services/chat/service.go
generated
vendored
Normal file
46
vendor/github.com/status-im/status-go/services/chat/service.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package chat
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
)
|
||||
|
||||
func NewService(accountsDB *accounts.Database) *Service {
|
||||
return &Service{
|
||||
accountsDB: accountsDB,
|
||||
}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
messenger *protocol.Messenger
|
||||
accountsDB *accounts.Database
|
||||
}
|
||||
|
||||
func (s *Service) Init(messenger *protocol.Messenger) {
|
||||
s.messenger = messenger
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) APIs() []gethrpc.API {
|
||||
return []gethrpc.API{
|
||||
{
|
||||
Namespace: "chat",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
914
vendor/github.com/status-im/status-go/services/communitytokens/api.go
generated
vendored
Normal file
914
vendor/github.com/status-im/status-go/services/communitytokens/api.go
generated
vendored
Normal file
@@ -0,0 +1,914 @@
|
||||
package communitytokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/mastertoken"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
|
||||
communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/services/utils"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
wcommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
func NewAPI(s *Service) *API {
|
||||
return &API{
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
type API struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
type DeploymentDetails struct {
|
||||
ContractAddress string `json:"contractAddress"`
|
||||
TransactionHash string `json:"transactionHash"`
|
||||
}
|
||||
|
||||
const maxSupply = 999999999
|
||||
|
||||
type DeploymentParameters struct {
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Supply *bigint.BigInt `json:"supply"`
|
||||
InfiniteSupply bool `json:"infiniteSupply"`
|
||||
Transferable bool `json:"transferable"`
|
||||
RemoteSelfDestruct bool `json:"remoteSelfDestruct"`
|
||||
TokenURI string `json:"tokenUri"`
|
||||
OwnerTokenAddress string `json:"ownerTokenAddress"`
|
||||
MasterTokenAddress string `json:"masterTokenAddress"`
|
||||
}
|
||||
|
||||
func (d *DeploymentParameters) GetSupply() *big.Int {
|
||||
if d.InfiniteSupply {
|
||||
return d.GetInfiniteSupply()
|
||||
}
|
||||
return d.Supply.Int
|
||||
}
|
||||
|
||||
// infinite supply for ERC721 is 2^256-1
|
||||
func (d *DeploymentParameters) GetInfiniteSupply() *big.Int {
|
||||
return GetInfiniteSupply()
|
||||
}
|
||||
|
||||
func GetInfiniteSupply() *big.Int {
|
||||
max := new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil)
|
||||
max.Sub(max, big.NewInt(1))
|
||||
return max
|
||||
}
|
||||
|
||||
func (d *DeploymentParameters) Validate(isAsset bool) error {
|
||||
if len(d.Name) <= 0 {
|
||||
return errors.New("empty collectible name")
|
||||
}
|
||||
if len(d.Symbol) <= 0 {
|
||||
return errors.New("empty collectible symbol")
|
||||
}
|
||||
var maxForType = big.NewInt(maxSupply)
|
||||
if isAsset {
|
||||
assetMultiplier, _ := big.NewInt(0).SetString("1000000000000000000", 10)
|
||||
maxForType = maxForType.Mul(maxForType, assetMultiplier)
|
||||
}
|
||||
if !d.InfiniteSupply && (d.Supply.Cmp(big.NewInt(0)) < 0 || d.Supply.Cmp(maxForType) > 0) {
|
||||
return fmt.Errorf("wrong supply value: %v", d.Supply)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) DeployCollectibles(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
|
||||
err := deploymentParameters.Validate(false)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
address, tx, _, err := collectibles.DeployCollectibles(transactOpts, ethClient, deploymentParameters.Name,
|
||||
deploymentParameters.Symbol, deploymentParameters.GetSupply(),
|
||||
deploymentParameters.RemoteSelfDestruct, deploymentParameters.Transferable,
|
||||
deploymentParameters.TokenURI, common.HexToAddress(deploymentParameters.OwnerTokenAddress),
|
||||
common.HexToAddress(deploymentParameters.MasterTokenAddress))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.DeployCommunityToken,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
|
||||
}
|
||||
|
||||
func decodeSignature(sig []byte) (r [32]byte, s [32]byte, v uint8, err error) {
|
||||
if len(sig) != crypto.SignatureLength {
|
||||
return [32]byte{}, [32]byte{}, 0, fmt.Errorf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength)
|
||||
}
|
||||
copy(r[:], sig[:32])
|
||||
copy(s[:], sig[32:64])
|
||||
v = sig[64] + 27
|
||||
return r, s, v, nil
|
||||
}
|
||||
|
||||
func prepareDeploymentSignatureStruct(signature string, communityID string, addressFrom common.Address) (communitytokendeployer.CommunityTokenDeployerDeploymentSignature, error) {
|
||||
r, s, v, err := decodeSignature(common.FromHex(signature))
|
||||
if err != nil {
|
||||
return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err
|
||||
}
|
||||
communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return communitytokendeployer.CommunityTokenDeployerDeploymentSignature{}, err
|
||||
}
|
||||
communitySignature := communitytokendeployer.CommunityTokenDeployerDeploymentSignature{
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
Deployer: addressFrom,
|
||||
Signer: communityEthAddress,
|
||||
}
|
||||
return communitySignature, nil
|
||||
}
|
||||
|
||||
func (api *API) DeployOwnerToken(ctx context.Context, chainID uint64,
|
||||
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
|
||||
signature string, communityID string, signerPubKey string,
|
||||
txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
err := ownerTokenParameters.Validate(false)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
if len(signerPubKey) <= 0 {
|
||||
return DeploymentDetails{}, fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
|
||||
err = masterTokenParameters.Validate(false)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: ownerTokenParameters.Name,
|
||||
Symbol: ownerTokenParameters.Symbol,
|
||||
BaseURI: ownerTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: masterTokenParameters.Name,
|
||||
Symbol: masterTokenParameters.Symbol,
|
||||
BaseURI: masterTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.Address(txArgs.From))
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
log.Debug("Signature:", communitySignature)
|
||||
|
||||
tx, err := deployerContractInst.Deploy(transactOpts, ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
|
||||
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.DeployOwnerToken,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
return DeploymentDetails{"", tx.Hash().Hex()}, nil
|
||||
}
|
||||
|
||||
func (api *API) GetMasterTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logMasterTokenCreatedSig := []byte("DeployMasterToken(address)")
|
||||
logMasterTokenCreatedSigHash := crypto.Keccak256Hash(logMasterTokenCreatedSig)
|
||||
|
||||
for _, vLog := range receipt.Logs {
|
||||
if vLog.Topics[0].Hex() == logMasterTokenCreatedSigHash.Hex() {
|
||||
event, err := deployerContractInst.ParseDeployMasterToken(*vLog)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return event.Arg0.Hex(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("can't find master token address in transaction: %v", txHash)
|
||||
}
|
||||
|
||||
func (api *API) GetOwnerTokenContractAddressFromHash(ctx context.Context, chainID uint64, txHash string) (string, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
receipt, err := ethClient.TransactionReceipt(ctx, common.HexToHash(txHash))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deployerContractInst, err := api.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
logOwnerTokenCreatedSig := []byte("DeployOwnerToken(address)")
|
||||
logOwnerTokenCreatedSigHash := crypto.Keccak256Hash(logOwnerTokenCreatedSig)
|
||||
|
||||
for _, vLog := range receipt.Logs {
|
||||
if vLog.Topics[0].Hex() == logOwnerTokenCreatedSigHash.Hex() {
|
||||
event, err := deployerContractInst.ParseDeployOwnerToken(*vLog)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return event.Arg0.Hex(), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("can't find owner token address in transaction: %v", txHash)
|
||||
}
|
||||
|
||||
func (api *API) DeployAssets(ctx context.Context, chainID uint64, deploymentParameters DeploymentParameters, txArgs transactions.SendTxArgs, password string) (DeploymentDetails, error) {
|
||||
|
||||
err := deploymentParameters.Validate(true)
|
||||
if err != nil {
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
const decimals = 18
|
||||
address, tx, _, err := assets.DeployAssets(transactOpts, ethClient, deploymentParameters.Name,
|
||||
deploymentParameters.Symbol, decimals, deploymentParameters.GetSupply(),
|
||||
deploymentParameters.TokenURI,
|
||||
common.HexToAddress(deploymentParameters.OwnerTokenAddress),
|
||||
common.HexToAddress(deploymentParameters.MasterTokenAddress))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.DeployCommunityToken,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return DeploymentDetails{}, err
|
||||
}
|
||||
|
||||
return DeploymentDetails{address.Hex(), tx.Hash().Hex()}, nil
|
||||
}
|
||||
|
||||
// Returns gas units + 10%
|
||||
func (api *API) DeployCollectiblesEstimate(ctx context.Context) (uint64, error) {
|
||||
// TODO investigate why the code below does not return correct values
|
||||
/*ethClient, err := api.s.manager.rpcClient.EthClient(420)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := collectiblesABI.Pack("", "name", "SYMBOL", big.NewInt(20), true, false, "tokenUriwhcih is very long asdkfjlsdkjflk",
|
||||
common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"), common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
callMsg := ethereum.CallMsg{
|
||||
From: common.HexToAddress("0x77b48394c650520012795a1a25696d7eb542d110"),
|
||||
To: nil,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil*/
|
||||
|
||||
// TODO compute fee dynamically
|
||||
// the code above returns too low fees, need to investigate
|
||||
gasAmount := uint64(2500000)
|
||||
return gasAmount + uint64(float32(gasAmount)*0.1), nil
|
||||
}
|
||||
|
||||
// Returns gas units + 10%
|
||||
func (api *API) DeployAssetsEstimate(ctx context.Context) (uint64, error) {
|
||||
// TODO compute fee dynamically
|
||||
gasAmount := uint64(1500000)
|
||||
return gasAmount + uint64(float32(gasAmount)*0.1), nil
|
||||
}
|
||||
|
||||
func (api *API) DeployOwnerTokenEstimate(ctx context.Context, chainID uint64, fromAddress string,
|
||||
ownerTokenParameters DeploymentParameters, masterTokenParameters DeploymentParameters,
|
||||
signature string, communityID string, signerPubKey string) (uint64, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deployerAddress, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deployerABI, err := abi.JSON(strings.NewReader(communitytokendeployer.CommunityTokenDeployerABI))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ownerTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: ownerTokenParameters.Name,
|
||||
Symbol: ownerTokenParameters.Symbol,
|
||||
BaseURI: ownerTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
masterTokenConfig := communitytokendeployer.CommunityTokenDeployerTokenConfig{
|
||||
Name: masterTokenParameters.Name,
|
||||
Symbol: masterTokenParameters.Symbol,
|
||||
BaseURI: masterTokenParameters.TokenURI,
|
||||
}
|
||||
|
||||
communitySignature, err := prepareDeploymentSignatureStruct(signature, communityID, common.HexToAddress(fromAddress))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := deployerABI.Pack("deploy", ownerTokenConfig, masterTokenConfig, communitySignature, common.FromHex(signerPubKey))
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
toAddr := deployerAddress
|
||||
fromAddr := common.HexToAddress(fromAddress)
|
||||
|
||||
callMsg := ethereum.CallMsg{
|
||||
From: fromAddr,
|
||||
To: &toAddr,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil
|
||||
}
|
||||
|
||||
func (api *API) NewMasterTokenInstance(chainID uint64, contractAddress string) (*mastertoken.MasterToken, error) {
|
||||
backend, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mastertoken.NewMasterToken(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (api *API) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) {
|
||||
return api.s.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (api *API) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
|
||||
return api.s.manager.NewCommunityTokenDeployerInstance(chainID)
|
||||
}
|
||||
|
||||
func (api *API) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) {
|
||||
return api.s.NewCommunityOwnerTokenRegistryInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (api *API) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
return api.s.manager.NewCollectiblesInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (api *API) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
||||
return api.s.manager.NewAssetsInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
// if we want to mint 2 tokens to addresses ["a", "b"] we need to mint
|
||||
// twice to every address - we need to send to smart contract table ["a", "a", "b", "b"]
|
||||
func (api *API) multiplyWalletAddresses(amount *bigint.BigInt, contractAddresses []string) []string {
|
||||
var totalAddresses []string
|
||||
for i := big.NewInt(1); i.Cmp(amount.Int) <= 0; {
|
||||
totalAddresses = append(totalAddresses, contractAddresses...)
|
||||
i.Add(i, big.NewInt(1))
|
||||
}
|
||||
return totalAddresses
|
||||
}
|
||||
|
||||
func (api *API) PrepareMintCollectiblesData(walletAddresses []string, amount *bigint.BigInt) []common.Address {
|
||||
totalAddresses := api.multiplyWalletAddresses(amount, walletAddresses)
|
||||
var usersAddresses = []common.Address{}
|
||||
for _, k := range totalAddresses {
|
||||
usersAddresses = append(usersAddresses, common.HexToAddress(k))
|
||||
}
|
||||
return usersAddresses
|
||||
}
|
||||
|
||||
// Universal minting function for every type of token.
|
||||
func (api *API) MintTokens(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, walletAddresses []string, amount *bigint.BigInt) (string, error) {
|
||||
|
||||
err := api.ValidateWalletsAndAmounts(walletAddresses, amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
contractInst, err := NewTokenInstance(api, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tx, err := contractInst.Mint(transactOpts, walletAddresses, amount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.AirdropCommunityToken,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
func (api *API) EstimateMintTokens(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
|
||||
tokenType, err := api.s.db.GetTokenType(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch tokenType {
|
||||
case protobuf.CommunityTokenType_ERC721:
|
||||
return api.EstimateMintCollectibles(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
|
||||
case protobuf.CommunityTokenType_ERC20:
|
||||
return api.EstimateMintAssets(ctx, chainID, contractAddress, fromAddress, walletAddresses, amount)
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown token type: %v", tokenType)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) EstimateMintCollectibles(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
|
||||
err := api.ValidateWalletsAndAmounts(walletAddresses, amount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
usersAddresses := api.PrepareMintCollectiblesData(walletAddresses, amount)
|
||||
return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses)
|
||||
}
|
||||
|
||||
func (api *API) PrepareMintAssetsData(walletAddresses []string, amount *bigint.BigInt) ([]common.Address, []*big.Int) {
|
||||
var usersAddresses = []common.Address{}
|
||||
var amountsList = []*big.Int{}
|
||||
for _, k := range walletAddresses {
|
||||
usersAddresses = append(usersAddresses, common.HexToAddress(k))
|
||||
amountsList = append(amountsList, amount.Int)
|
||||
}
|
||||
return usersAddresses, amountsList
|
||||
}
|
||||
|
||||
// Estimate MintAssets cost.
|
||||
func (api *API) EstimateMintAssets(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, walletAddresses []string, amount *bigint.BigInt) (uint64, error) {
|
||||
err := api.ValidateWalletsAndAmounts(walletAddresses, amount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
usersAddresses, amountsList := api.PrepareMintAssetsData(walletAddresses, amount)
|
||||
return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "mintTo", usersAddresses, amountsList)
|
||||
}
|
||||
|
||||
// This is only ERC721 function
|
||||
func (api *API) RemoteDestructedAmount(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// total supply = airdropped only (w/o burnt)
|
||||
totalSupply, err := contractInst.TotalSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// minted = all created tokens (airdropped and remotely destructed)
|
||||
mintedCount, err := contractInst.MintedCount(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res = new(big.Int)
|
||||
res.Sub(mintedCount, totalSupply)
|
||||
|
||||
return &bigint.BigInt{Int: res}, nil
|
||||
}
|
||||
|
||||
// This is only ERC721 function
|
||||
func (api *API) RemoteBurn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, tokenIds []*bigint.BigInt) (string, error) {
|
||||
err := api.validateTokens(tokenIds)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
var tempTokenIds []*big.Int
|
||||
for _, v := range tokenIds {
|
||||
tempTokenIds = append(tempTokenIds, v.Int)
|
||||
}
|
||||
|
||||
contractInst, err := NewTokenInstance(api, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tx, err := contractInst.RemoteBurn(transactOpts, tempTokenIds)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.RemoteDestructCollectible,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
// This is only ERC721 function
|
||||
func (api *API) EstimateRemoteBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, tokenIds []*bigint.BigInt) (uint64, error) {
|
||||
err := api.validateTokens(tokenIds)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var tempTokenIds []*big.Int
|
||||
for _, v := range tokenIds {
|
||||
tempTokenIds = append(tempTokenIds, v.Int)
|
||||
}
|
||||
|
||||
return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "remoteBurn", tempTokenIds)
|
||||
}
|
||||
|
||||
func (api *API) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
return api.s.manager.GetCollectiblesContractInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (api *API) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
||||
return api.s.manager.GetAssetContractInstance(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (api *API) RemainingSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
|
||||
tokenType, err := api.s.db.GetTokenType(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch tokenType {
|
||||
case protobuf.CommunityTokenType_ERC721:
|
||||
return api.remainingCollectiblesSupply(ctx, chainID, contractAddress)
|
||||
case protobuf.CommunityTokenType_ERC20:
|
||||
return api.remainingAssetsSupply(ctx, chainID, contractAddress)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown token type: %v", tokenType)
|
||||
}
|
||||
}
|
||||
|
||||
// RemainingSupply = MaxSupply - MintedCount
|
||||
func (api *API) remainingCollectiblesSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maxSupply, err := contractInst.MaxSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mintedCount, err := contractInst.MintedCount(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res = new(big.Int)
|
||||
res.Sub(maxSupply, mintedCount)
|
||||
return &bigint.BigInt{Int: res}, nil
|
||||
}
|
||||
|
||||
// RemainingSupply = MaxSupply - TotalSupply
|
||||
func (api *API) remainingAssetsSupply(ctx context.Context, chainID uint64, contractAddress string) (*bigint.BigInt, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewAssetsInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maxSupply, err := contractInst.MaxSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalSupply, err := contractInst.TotalSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res = new(big.Int)
|
||||
res.Sub(maxSupply, totalSupply)
|
||||
return &bigint.BigInt{Int: res}, nil
|
||||
}
|
||||
|
||||
func (api *API) maxSupplyCollectibles(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contractInst.MaxSupply(callOpts)
|
||||
}
|
||||
|
||||
func (api *API) maxSupplyAssets(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewAssetsInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contractInst.MaxSupply(callOpts)
|
||||
}
|
||||
|
||||
func (api *API) maxSupply(ctx context.Context, chainID uint64, contractAddress string) (*big.Int, error) {
|
||||
tokenType, err := api.s.db.GetTokenType(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch tokenType {
|
||||
case protobuf.CommunityTokenType_ERC721:
|
||||
return api.maxSupplyCollectibles(ctx, chainID, contractAddress)
|
||||
case protobuf.CommunityTokenType_ERC20:
|
||||
return api.maxSupplyAssets(ctx, chainID, contractAddress)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown token type: %v", tokenType)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) prepareNewMaxSupply(ctx context.Context, chainID uint64, contractAddress string, burnAmount *bigint.BigInt) (*big.Int, error) {
|
||||
maxSupply, err := api.maxSupply(ctx, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var newMaxSupply = new(big.Int)
|
||||
newMaxSupply.Sub(maxSupply, burnAmount.Int)
|
||||
return newMaxSupply, nil
|
||||
}
|
||||
|
||||
func (api *API) Burn(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, burnAmount *bigint.BigInt) (string, error) {
|
||||
err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.s.accountsManager, api.s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
newMaxSupply, err := api.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
contractInst, err := NewTokenInstance(api, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tx, err := contractInst.SetMaxSupply(transactOpts, newMaxSupply)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.BurnCommunityToken,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
func (api *API) EstimateBurn(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, burnAmount *bigint.BigInt) (uint64, error) {
|
||||
err := api.validateBurnAmount(ctx, burnAmount, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
newMaxSupply, err := api.prepareNewMaxSupply(ctx, chainID, contractAddress, burnAmount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return api.estimateMethod(ctx, chainID, contractAddress, fromAddress, "setMaxSupply", newMaxSupply)
|
||||
}
|
||||
|
||||
func (api *API) ValidateWalletsAndAmounts(walletAddresses []string, amount *bigint.BigInt) error {
|
||||
if len(walletAddresses) == 0 {
|
||||
return errors.New("wallet addresses list is empty")
|
||||
}
|
||||
if amount.Cmp(big.NewInt(0)) <= 0 {
|
||||
return errors.New("amount is <= 0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) validateTokens(tokenIds []*bigint.BigInt) error {
|
||||
if len(tokenIds) == 0 {
|
||||
return errors.New("token list is empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) validateBurnAmount(ctx context.Context, burnAmount *bigint.BigInt, chainID uint64, contractAddress string) error {
|
||||
if burnAmount.Cmp(big.NewInt(0)) <= 0 {
|
||||
return errors.New("burnAmount is less than 0")
|
||||
}
|
||||
remainingSupply, err := api.RemainingSupply(ctx, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if burnAmount.Cmp(remainingSupply.Int) > 1 {
|
||||
return errors.New("burnAmount is bigger than remaining amount")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) estimateMethodForTokenInstance(ctx context.Context, contractInstance TokenInstance, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
|
||||
ethClient, err := api.s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := contractInstance.PackMethod(ctx, methodName, args...)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
toAddr := common.HexToAddress(contractAddress)
|
||||
fromAddr := common.HexToAddress(fromAddress)
|
||||
|
||||
callMsg := ethereum.CallMsg{
|
||||
From: fromAddr,
|
||||
To: &toAddr,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + uint64(float32(estimate)*0.1), nil
|
||||
}
|
||||
|
||||
func (api *API) estimateMethod(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, methodName string, args ...interface{}) (uint64, error) {
|
||||
contractInst, err := NewTokenInstance(api, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return api.estimateMethodForTokenInstance(ctx, contractInst, chainID, contractAddress, fromAddress, methodName, args...)
|
||||
}
|
||||
|
||||
// Gets signer public key from smart contract with a given chainId and address
|
||||
func (api *API) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
|
||||
return api.s.GetSignerPubKey(ctx, chainID, contractAddress)
|
||||
}
|
||||
|
||||
// Gets signer public key directly from deployer contract
|
||||
func (api *API) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
return api.s.SafeGetSignerPubKey(ctx, chainID, communityID)
|
||||
}
|
||||
|
||||
// Gets owner token contract address from deployer contract
|
||||
func (api *API) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
return api.s.SafeGetOwnerTokenAddress(ctx, chainID, communityID)
|
||||
}
|
||||
|
||||
func (api *API) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) {
|
||||
return api.s.SetSignerPubKey(ctx, chainID, contractAddress, txArgs, password, newSignerPubKey)
|
||||
}
|
||||
|
||||
func (api *API) EstimateSetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, fromAddress string, newSignerPubKey string) (uint64, error) {
|
||||
if len(newSignerPubKey) <= 0 {
|
||||
return 0, fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ownerTokenInstance := &OwnerTokenInstance{instance: contractInst}
|
||||
|
||||
return api.estimateMethodForTokenInstance(ctx, ownerTokenInstance, chainID, contractAddress, fromAddress, "setSignerPublicKey", common.FromHex(newSignerPubKey))
|
||||
}
|
||||
|
||||
func (api *API) OwnerTokenOwnerAddress(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ownerAddress, err := contractInst.OwnerOf(callOpts, big.NewInt(0))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ownerAddress.Hex(), nil
|
||||
}
|
||||
8
vendor/github.com/status-im/status-go/services/communitytokens/asset_contract_data.go
generated
vendored
Normal file
8
vendor/github.com/status-im/status-go/services/communitytokens/asset_contract_data.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package communitytokens
|
||||
|
||||
import "github.com/status-im/status-go/services/wallet/bigint"
|
||||
|
||||
type AssetContractData struct {
|
||||
TotalSupply *bigint.BigInt
|
||||
InfiniteSupply bool
|
||||
}
|
||||
10
vendor/github.com/status-im/status-go/services/communitytokens/collectible_contract_data.go
generated
vendored
Normal file
10
vendor/github.com/status-im/status-go/services/communitytokens/collectible_contract_data.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
package communitytokens
|
||||
|
||||
import "github.com/status-im/status-go/services/wallet/bigint"
|
||||
|
||||
type CollectibleContractData struct {
|
||||
TotalSupply *bigint.BigInt
|
||||
Transferable bool
|
||||
RemoteBurnable bool
|
||||
InfiniteSupply bool
|
||||
}
|
||||
66
vendor/github.com/status-im/status-go/services/communitytokens/database.go
generated
vendored
Normal file
66
vendor/github.com/status-im/status-go/services/communitytokens/database.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package communitytokens
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/protocol/communities/token"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewCommunityTokensDatabase(db *sql.DB) *Database {
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
func (db *Database) GetTokenType(chainID uint64, contractAddress string) (protobuf.CommunityTokenType, error) {
|
||||
var result = protobuf.CommunityTokenType_UNKNOWN_TOKEN_TYPE
|
||||
rows, err := db.db.Query(`SELECT type FROM community_tokens WHERE chain_id=? AND address=? LIMIT 1`, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
err := rows.Scan(&result)
|
||||
return result, err
|
||||
}
|
||||
return result, fmt.Errorf("can't find token: chainId %v, contractAddress %v", chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (db *Database) GetTokenPrivilegesLevel(chainID uint64, contractAddress string) (token.PrivilegesLevel, error) {
|
||||
var result = token.CommunityLevel
|
||||
rows, err := db.db.Query(`SELECT privileges_level FROM community_tokens WHERE chain_id=? AND address=? LIMIT 1`, chainID, contractAddress)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
err := rows.Scan(&result)
|
||||
return result, err
|
||||
}
|
||||
return result, fmt.Errorf("can't find privileges level: chainId %v, contractAddress %v", chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (db *Database) GetCommunityERC20Metadata() ([]*token.CommunityToken, error) {
|
||||
rows, err := db.db.Query(`SELECT community_id, address, name, symbol, chain_id FROM community_tokens WHERE type = ?`, protobuf.CommunityTokenType_ERC20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []*token.CommunityToken
|
||||
for rows.Next() {
|
||||
token := token.CommunityToken{}
|
||||
err := rows.Scan(&token.CommunityID, &token.Address, &token.Name, &token.Symbol, &token.ChainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, &token)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
208
vendor/github.com/status-im/status-go/services/communitytokens/manager.go
generated
vendored
Normal file
208
vendor/github.com/status-im/status-go/services/communitytokens/manager.go
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
package communitytokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
communitytokendeployer "github.com/status-im/status-go/contracts/community-tokens/deployer"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
rpcClient *rpc.Client
|
||||
}
|
||||
|
||||
func NewManager(rpcClient *rpc.Client) *Manager {
|
||||
return &Manager{
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) NewCollectiblesInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
backend, err := m.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return collectibles.NewCollectibles(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (m *Manager) NewCommunityTokenDeployerInstance(chainID uint64) (*communitytokendeployer.CommunityTokenDeployer, error) {
|
||||
backend, err := m.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return communitytokendeployer.NewCommunityTokenDeployer(deployerAddr, backend)
|
||||
}
|
||||
|
||||
func (m *Manager) GetCollectiblesContractInstance(chainID uint64, contractAddress string) (*collectibles.Collectibles, error) {
|
||||
contractInst, err := m.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contractInst, nil
|
||||
}
|
||||
|
||||
func (m *Manager) NewAssetsInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
||||
backend, err := m.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return assets.NewAssets(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (m *Manager) GetAssetContractInstance(chainID uint64, contractAddress string) (*assets.Assets, error) {
|
||||
contractInst, err := m.NewAssetsInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return contractInst, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) {
|
||||
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
|
||||
|
||||
contract, err := m.GetCollectiblesContractInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalSupply, err := contract.MaxSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transferable, err := contract.Transferable(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteBurnable, err := contract.RemoteBurnable(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CollectibleContractData{
|
||||
TotalSupply: &bigint.BigInt{Int: totalSupply},
|
||||
Transferable: transferable,
|
||||
RemoteBurnable: remoteBurnable,
|
||||
InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) {
|
||||
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
|
||||
contract, err := m.GetAssetContractInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
totalSupply, err := contract.MaxSupply(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AssetContractData{
|
||||
TotalSupply: &bigint.BigInt{Int: totalSupply},
|
||||
InfiniteSupply: GetInfiniteSupply().Cmp(totalSupply) == 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convert33BytesPubKeyToEthAddress(pubKey string) (common.Address, error) {
|
||||
decoded, err := types.DecodeHex(pubKey)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
communityPubKey, err := crypto.DecompressPubkey(decoded)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return common.Address(crypto.PubkeyToAddress(*communityPubKey)), nil
|
||||
}
|
||||
|
||||
// Simpler version of hashing typed structured data alternative to typedStructuredDataHash. Keeping this for reference.
|
||||
func customTypedStructuredDataHash(domainSeparator []byte, signatureTypedHash []byte, signer string, deployer string) types.Hash {
|
||||
// every field should be 32 bytes, eth address is 20 bytes so padding should be added
|
||||
emptyOffset := [12]byte{}
|
||||
hashedEncoded := crypto.Keccak256Hash(signatureTypedHash, emptyOffset[:], common.HexToAddress(signer).Bytes(),
|
||||
emptyOffset[:], common.HexToAddress(deployer).Bytes())
|
||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, hashedEncoded.Bytes()))
|
||||
return crypto.Keccak256Hash(rawData)
|
||||
}
|
||||
|
||||
// Returns a typed structured hash according to https://eips.ethereum.org/EIPS/eip-712
|
||||
// Domain separator from smart contract is used.
|
||||
func typedStructuredDataHash(domainSeparator []byte, signer string, addressFrom string, deployerContractAddress string, chainID uint64) (types.Hash, error) {
|
||||
myTypedData := apitypes.TypedData{
|
||||
Types: apitypes.Types{
|
||||
"Deploy": []apitypes.Type{
|
||||
{Name: "signer", Type: "address"},
|
||||
{Name: "deployer", Type: "address"},
|
||||
},
|
||||
"EIP712Domain": []apitypes.Type{
|
||||
{Name: "name", Type: "string"},
|
||||
{Name: "version", Type: "string"},
|
||||
{Name: "chainId", Type: "uint256"},
|
||||
{Name: "verifyingContract", Type: "address"},
|
||||
},
|
||||
},
|
||||
PrimaryType: "Deploy",
|
||||
// Domain field should be here to keep correct structure but
|
||||
// domainSeparator from smart contract is used.
|
||||
Domain: apitypes.TypedDataDomain{
|
||||
Name: "CommunityTokenDeployer", // name from Deployer smart contract
|
||||
Version: "1", // version from Deployer smart contract
|
||||
ChainId: math.NewHexOrDecimal256(int64(chainID)),
|
||||
VerifyingContract: deployerContractAddress,
|
||||
},
|
||||
Message: apitypes.TypedDataMessage{
|
||||
"signer": signer,
|
||||
"deployer": addressFrom,
|
||||
},
|
||||
}
|
||||
|
||||
typedDataHash, err := myTypedData.HashStruct(myTypedData.PrimaryType, myTypedData.Message)
|
||||
if err != nil {
|
||||
return types.Hash{}, err
|
||||
}
|
||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", domainSeparator, string(typedDataHash)))
|
||||
return crypto.Keccak256Hash(rawData), nil
|
||||
}
|
||||
|
||||
// Creates
|
||||
func (m *Manager) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
callOpts := &bind.CallOpts{Pending: false}
|
||||
communityEthAddr, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deployerAddr, err := communitytokendeployer.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deployerContractInst, err := m.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainSeparator, err := deployerContractInst.DOMAINSEPARATOR(callOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
structedHash, err := typedStructuredDataHash(domainSeparator[:], communityEthAddr.Hex(), addressFrom, deployerAddr.Hex(), chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return structedHash.Bytes(), nil
|
||||
}
|
||||
188
vendor/github.com/status-im/status-go/services/communitytokens/service.go
generated
vendored
Normal file
188
vendor/github.com/status-im/status-go/services/communitytokens/service.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
package communitytokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
|
||||
communityownertokenregistry "github.com/status-im/status-go/contracts/community-tokens/registry"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/utils"
|
||||
wcommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
type ServiceInterface interface {
|
||||
GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error)
|
||||
SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error)
|
||||
GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error)
|
||||
SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error)
|
||||
DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error)
|
||||
}
|
||||
|
||||
// Collectibles service
|
||||
type Service struct {
|
||||
manager *Manager
|
||||
accountsManager *account.GethManager
|
||||
pendingTracker *transactions.PendingTxTracker
|
||||
config *params.NodeConfig
|
||||
db *Database
|
||||
}
|
||||
|
||||
// Returns a new Collectibles Service.
|
||||
func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB) *Service {
|
||||
return &Service{
|
||||
manager: &Manager{rpcClient: rpcClient},
|
||||
accountsManager: accountsManager,
|
||||
pendingTracker: pendingTracker,
|
||||
config: config,
|
||||
db: NewCommunityTokensDatabase(appDb),
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []ethRpc.API {
|
||||
return []ethRpc.API{
|
||||
{
|
||||
Namespace: "communitytokens",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) NewCommunityOwnerTokenRegistryInstance(chainID uint64, contractAddress string) (*communityownertokenregistry.CommunityOwnerTokenRegistry, error) {
|
||||
backend, err := s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return communityownertokenregistry.NewCommunityOwnerTokenRegistry(common.HexToAddress(contractAddress), backend)
|
||||
}
|
||||
|
||||
func (s *Service) NewOwnerTokenInstance(chainID uint64, contractAddress string) (*ownertoken.OwnerToken, error) {
|
||||
|
||||
backend, err := s.manager.rpcClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ownertoken.NewOwnerToken(common.HexToAddress(contractAddress), backend)
|
||||
|
||||
}
|
||||
|
||||
func (s *Service) GetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string) (string, error) {
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signerPubKey, err := contractInst.SignerPublicKey(callOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return types.ToHex(signerPubKey), nil
|
||||
}
|
||||
|
||||
func (s *Service) SafeGetSignerPubKey(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
// 1. Get Owner Token contract address from deployer contract - SafeGetOwnerTokenAddress()
|
||||
ownerTokenAddr, err := s.SafeGetOwnerTokenAddress(ctx, chainID, communityID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 2. Get Signer from owner token contract - GetSignerPubKey()
|
||||
return s.GetSignerPubKey(ctx, chainID, ownerTokenAddr)
|
||||
}
|
||||
|
||||
func (s *Service) SafeGetOwnerTokenAddress(ctx context.Context, chainID uint64, communityID string) (string, error) {
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
deployerContractInst, err := s.manager.NewCommunityTokenDeployerInstance(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
registryAddr, err := deployerContractInst.DeploymentRegistry(callOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
registryContractInst, err := s.NewCommunityOwnerTokenRegistryInstance(chainID, registryAddr.Hex())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
communityEthAddress, err := convert33BytesPubKeyToEthAddress(communityID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ownerTokenAddress, err := registryContractInst.GetEntry(callOpts, communityEthAddress)
|
||||
|
||||
return ownerTokenAddress.Hex(), err
|
||||
}
|
||||
|
||||
func (s *Service) GetCollectibleContractData(chainID uint64, contractAddress string) (*CollectibleContractData, error) {
|
||||
return s.manager.GetCollectibleContractData(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (s *Service) GetAssetContractData(chainID uint64, contractAddress string) (*AssetContractData, error) {
|
||||
return s.manager.GetAssetContractData(chainID, contractAddress)
|
||||
}
|
||||
|
||||
func (s *Service) DeploymentSignatureDigest(chainID uint64, addressFrom string, communityID string) ([]byte, error) {
|
||||
return s.manager.DeploymentSignatureDigest(chainID, addressFrom, communityID)
|
||||
}
|
||||
|
||||
func (s *Service) SetSignerPubKey(ctx context.Context, chainID uint64, contractAddress string, txArgs transactions.SendTxArgs, password string, newSignerPubKey string) (string, error) {
|
||||
|
||||
if len(newSignerPubKey) <= 0 {
|
||||
return "", fmt.Errorf("signerPubKey is empty")
|
||||
}
|
||||
|
||||
transactOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, s.accountsManager, s.config.KeyStoreDir, txArgs.From, password))
|
||||
|
||||
contractInst, err := s.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tx, err := contractInst.SetSignerPublicKey(transactOpts, common.FromHex(newSignerPubKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = s.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.SetSignerPublicKey,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
179
vendor/github.com/status-im/status-go/services/communitytokens/token_instances.go
generated
vendored
Normal file
179
vendor/github.com/status-im/status-go/services/communitytokens/token_instances.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
package communitytokens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/assets"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/mastertoken"
|
||||
"github.com/status-im/status-go/contracts/community-tokens/ownertoken"
|
||||
"github.com/status-im/status-go/protocol/communities/token"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
type TokenInstance interface {
|
||||
RemoteBurn(*bind.TransactOpts, []*big.Int) (*types.Transaction, error)
|
||||
Mint(*bind.TransactOpts, []string, *bigint.BigInt) (*types.Transaction, error)
|
||||
SetMaxSupply(*bind.TransactOpts, *big.Int) (*types.Transaction, error)
|
||||
PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
// Owner Token
|
||||
type OwnerTokenInstance struct {
|
||||
TokenInstance
|
||||
instance *ownertoken.OwnerToken
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("remote destruction for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("minting for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("setting max supply for owner token not implemented")
|
||||
}
|
||||
|
||||
func (t OwnerTokenInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) {
|
||||
ownerTokenABI, err := abi.JSON(strings.NewReader(ownertoken.OwnerTokenABI))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return ownerTokenABI.Pack(methodName, args...)
|
||||
}
|
||||
|
||||
// Master Token
|
||||
type MasterTokenInstance struct {
|
||||
TokenInstance
|
||||
instance *mastertoken.MasterToken
|
||||
api *API
|
||||
}
|
||||
|
||||
func (t MasterTokenInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return t.instance.RemoteBurn(transactOpts, tokenIds)
|
||||
}
|
||||
|
||||
func (t MasterTokenInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) {
|
||||
usersAddresses := t.api.PrepareMintCollectiblesData(walletAddresses, amount)
|
||||
return t.instance.MintTo(transactOpts, usersAddresses)
|
||||
}
|
||||
|
||||
func (t MasterTokenInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) {
|
||||
return t.instance.SetMaxSupply(transactOpts, maxSupply)
|
||||
}
|
||||
|
||||
func (t MasterTokenInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) {
|
||||
masterTokenABI, err := abi.JSON(strings.NewReader(mastertoken.MasterTokenABI))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return masterTokenABI.Pack(methodName, args...)
|
||||
}
|
||||
|
||||
// Collectible
|
||||
type CollectibleInstance struct {
|
||||
TokenInstance
|
||||
instance *collectibles.Collectibles
|
||||
api *API
|
||||
}
|
||||
|
||||
func (t CollectibleInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return t.instance.RemoteBurn(transactOpts, tokenIds)
|
||||
}
|
||||
|
||||
func (t CollectibleInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) {
|
||||
usersAddresses := t.api.PrepareMintCollectiblesData(walletAddresses, amount)
|
||||
return t.instance.MintTo(transactOpts, usersAddresses)
|
||||
}
|
||||
|
||||
func (t CollectibleInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) {
|
||||
return t.instance.SetMaxSupply(transactOpts, maxSupply)
|
||||
}
|
||||
|
||||
func (t CollectibleInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) {
|
||||
collectiblesABI, err := abi.JSON(strings.NewReader(collectibles.CollectiblesABI))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return collectiblesABI.Pack(methodName, args...)
|
||||
}
|
||||
|
||||
// Asset
|
||||
type AssetInstance struct {
|
||||
TokenInstance
|
||||
instance *assets.Assets
|
||||
api *API
|
||||
}
|
||||
|
||||
func (t AssetInstance) RemoteBurn(transactOpts *bind.TransactOpts, tokenIds []*big.Int) (*types.Transaction, error) {
|
||||
return nil, fmt.Errorf("remote destruction for assets not implemented")
|
||||
}
|
||||
|
||||
// The amount should be in smallest denomination of the asset (like wei) with decimal = 18, eg.
|
||||
// if we want to mint 2.34 of the token, then amount should be 234{16 zeros}.
|
||||
func (t AssetInstance) Mint(transactOpts *bind.TransactOpts, walletAddresses []string, amount *bigint.BigInt) (*types.Transaction, error) {
|
||||
usersAddresses, amountsList := t.api.PrepareMintAssetsData(walletAddresses, amount)
|
||||
return t.instance.MintTo(transactOpts, usersAddresses, amountsList)
|
||||
}
|
||||
|
||||
func (t AssetInstance) SetMaxSupply(transactOpts *bind.TransactOpts, maxSupply *big.Int) (*types.Transaction, error) {
|
||||
return t.instance.SetMaxSupply(transactOpts, maxSupply)
|
||||
}
|
||||
|
||||
func (t AssetInstance) PackMethod(ctx context.Context, methodName string, args ...interface{}) ([]byte, error) {
|
||||
assetsABI, err := abi.JSON(strings.NewReader(assets.AssetsABI))
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return assetsABI.Pack(methodName, args...)
|
||||
}
|
||||
|
||||
// creator
|
||||
|
||||
func NewTokenInstance(api *API, chainID uint64, contractAddress string) (TokenInstance, error) {
|
||||
tokenType, err := api.s.db.GetTokenType(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privLevel, err := api.s.db.GetTokenPrivilegesLevel(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case privLevel == token.OwnerLevel:
|
||||
contractInst, err := api.NewOwnerTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OwnerTokenInstance{instance: contractInst}, nil
|
||||
case privLevel == token.MasterLevel:
|
||||
contractInst, err := api.NewMasterTokenInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &MasterTokenInstance{instance: contractInst}, nil
|
||||
case tokenType == protobuf.CommunityTokenType_ERC721:
|
||||
contractInst, err := api.NewCollectiblesInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CollectibleInstance{instance: contractInst}, nil
|
||||
case tokenType == protobuf.CommunityTokenType_ERC20:
|
||||
contractInst, err := api.NewAssetsInstance(chainID, contractAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AssetInstance{instance: contractInst}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown type of contract: chain=%v, address=%v", chainID, contractAddress)
|
||||
}
|
||||
770
vendor/github.com/status-im/status-go/services/ens/api.go
generated
vendored
Normal file
770
vendor/github.com/status-im/status-go/services/ens/api.go
generated
vendored
Normal file
@@ -0,0 +1,770 @@
|
||||
package ens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/multiformats/go-multibase"
|
||||
"github.com/multiformats/go-multihash"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wealdtech/go-ens/v3"
|
||||
"github.com/wealdtech/go-multicodec"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/contracts"
|
||||
"github.com/status-im/status-go/contracts/registrar"
|
||||
"github.com/status-im/status-go/contracts/resolver"
|
||||
"github.com/status-im/status-go/contracts/snt"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/services/utils"
|
||||
wcommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
const StatusDomain = "stateofus.eth"
|
||||
|
||||
func NewAPI(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB, timeSource func() time.Time, syncUserDetailFunc *syncUsernameDetail) *API {
|
||||
return &API{
|
||||
contractMaker: &contracts.ContractMaker{
|
||||
RPCClient: rpcClient,
|
||||
},
|
||||
accountsManager: accountsManager,
|
||||
pendingTracker: pendingTracker,
|
||||
config: config,
|
||||
addrPerChain: make(map[uint64]common.Address),
|
||||
db: NewEnsDatabase(appDb),
|
||||
|
||||
quit: make(chan struct{}),
|
||||
timeSource: timeSource,
|
||||
syncUserDetailFunc: syncUserDetailFunc,
|
||||
}
|
||||
}
|
||||
|
||||
type URI struct {
|
||||
Scheme string
|
||||
Host string
|
||||
Path string
|
||||
}
|
||||
|
||||
// use this to avoid using messenger directly to avoid circular dependency (protocol->ens->protocol)
|
||||
type syncUsernameDetail func(context.Context, *UsernameDetail) error
|
||||
|
||||
type API struct {
|
||||
contractMaker *contracts.ContractMaker
|
||||
accountsManager *account.GethManager
|
||||
pendingTracker *transactions.PendingTxTracker
|
||||
config *params.NodeConfig
|
||||
|
||||
addrPerChain map[uint64]common.Address
|
||||
addrPerChainMutex sync.Mutex
|
||||
|
||||
quitOnce sync.Once
|
||||
quit chan struct{}
|
||||
|
||||
db *Database
|
||||
syncUserDetailFunc *syncUsernameDetail
|
||||
|
||||
timeSource func() time.Time
|
||||
}
|
||||
|
||||
func (api *API) Stop() {
|
||||
api.quitOnce.Do(func() {
|
||||
close(api.quit)
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) unixTime() uint64 {
|
||||
return uint64(api.timeSource().Unix())
|
||||
}
|
||||
|
||||
func (api *API) GetEnsUsernames(ctx context.Context) ([]*UsernameDetail, error) {
|
||||
removed := false
|
||||
return api.db.GetEnsUsernames(&removed)
|
||||
}
|
||||
|
||||
func (api *API) Add(ctx context.Context, chainID uint64, username string) error {
|
||||
ud := &UsernameDetail{Username: username, ChainID: chainID, Clock: api.unixTime()}
|
||||
err := api.db.AddEnsUsername(ud)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return (*api.syncUserDetailFunc)(ctx, ud)
|
||||
}
|
||||
|
||||
func (api *API) Remove(ctx context.Context, chainID uint64, username string) error {
|
||||
ud := &UsernameDetail{Username: username, ChainID: chainID, Clock: api.unixTime()}
|
||||
affected, err := api.db.RemoveEnsUsername(ud)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected {
|
||||
return (*api.syncUserDetailFunc)(ctx, ud)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) GetRegistrarAddress(ctx context.Context, chainID uint64) (common.Address, error) {
|
||||
return api.usernameRegistrarAddr(ctx, chainID)
|
||||
}
|
||||
|
||||
func (api *API) Resolver(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registry, err := api.contractMaker.NewRegistry(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
resolver, err := registry.Resolver(callOpts, nameHash(username))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resolver, nil
|
||||
}
|
||||
|
||||
func (api *API) GetName(ctx context.Context, chainID uint64, address common.Address) (string, error) {
|
||||
backend, err := api.contractMaker.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ens.ReverseResolve(backend, address)
|
||||
}
|
||||
|
||||
func (api *API) OwnerOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registry, err := api.contractMaker.NewRegistry(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
owner, err := registry.Owner(callOpts, nameHash(username))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &owner, nil
|
||||
}
|
||||
|
||||
func (api *API) ContentHash(ctx context.Context, chainID uint64, username string) ([]byte, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
contentHash, err := resolver.Contenthash(callOpts, nameHash(username))
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return contentHash, nil
|
||||
}
|
||||
|
||||
func (api *API) PublicKeyOf(ctx context.Context, chainID uint64, username string) (string, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
pubKey, err := resolver.Pubkey(callOpts, nameHash(username))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "0x04" + hex.EncodeToString(pubKey.X[:]) + hex.EncodeToString(pubKey.Y[:]), nil
|
||||
}
|
||||
|
||||
func (api *API) AddressOf(ctx context.Context, chainID uint64, username string) (*common.Address, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
addr, err := resolver.Addr(callOpts, nameHash(username))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &addr, nil
|
||||
}
|
||||
|
||||
func (api *API) usernameRegistrarAddr(ctx context.Context, chainID uint64) (common.Address, error) {
|
||||
log.Info("obtaining username registrar address")
|
||||
api.addrPerChainMutex.Lock()
|
||||
defer api.addrPerChainMutex.Unlock()
|
||||
addr, ok := api.addrPerChain[chainID]
|
||||
if ok {
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
registryAddr, err := api.OwnerOf(ctx, chainID, StatusDomain)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
api.addrPerChain[chainID] = *registryAddr
|
||||
|
||||
go func() {
|
||||
registry, err := api.contractMaker.NewRegistry(chainID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logs := make(chan *resolver.ENSRegistryWithFallbackNewOwner)
|
||||
|
||||
sub, err := registry.WatchNewOwner(&bind.WatchOpts{}, logs, nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-api.quit:
|
||||
log.Info("quitting ens contract subscription")
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
if err != nil {
|
||||
log.Error("ens contract subscription error: " + err.Error())
|
||||
}
|
||||
return
|
||||
case vLog := <-logs:
|
||||
api.addrPerChainMutex.Lock()
|
||||
api.addrPerChain[chainID] = vLog.Owner
|
||||
api.addrPerChainMutex.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return *registryAddr, nil
|
||||
}
|
||||
|
||||
func (api *API) ExpireAt(ctx context.Context, chainID uint64, username string) (string, error) {
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
expTime, err := registrar.GetExpirationTime(callOpts, usernameToLabel(username))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", expTime), nil
|
||||
}
|
||||
|
||||
func (api *API) Price(ctx context.Context, chainID uint64) (string, error) {
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: ctx, Pending: false}
|
||||
price, err := registrar.GetPrice(callOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", price), nil
|
||||
}
|
||||
|
||||
func (api *API) Release(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string) (string, error) {
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registrar, err := api.contractMaker.NewUsernameRegistrar(chainID, registryAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
|
||||
tx, err := registrar.Release(txOpts, usernameToLabel(username))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.ReleaseENS,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.Remove(ctx, chainID, fullDomainName(username))
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Releasing ENS username: transaction successful, but removing failed")
|
||||
}
|
||||
|
||||
return tx.Hash().String(), nil
|
||||
}
|
||||
|
||||
func (api *API) ReleasePrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (ethereum.CallMsg, error) {
|
||||
registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
data, err := registrarABI.Pack("release", usernameToLabel(username))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
sntAddress, err := snt.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
return ethereum.CallMsg{
|
||||
From: common.Address(txArgs.From),
|
||||
To: &sntAddress,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) ReleasePrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (interface{}, error) {
|
||||
callMsg, err := api.ReleasePrepareTxCallMsg(ctx, chainID, txArgs, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toCallArg(callMsg), nil
|
||||
}
|
||||
|
||||
func (api *API) ReleaseEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string) (uint64, error) {
|
||||
registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
data, err := registrarABI.Pack("release", usernameToLabel(username))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
estimate, err := ethClient.EstimateGas(ctx, ethereum.CallMsg{
|
||||
From: common.Address(txArgs.From),
|
||||
To: ®istryAddr,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + 1000, nil
|
||||
}
|
||||
|
||||
func (api *API) Register(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
|
||||
snt, err := api.contractMaker.NewSNT(chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
priceHex, err := api.Price(ctx, chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
price := new(big.Int)
|
||||
price.SetString(priceHex, 16)
|
||||
|
||||
registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
x, y := extractCoordinates(pubkey)
|
||||
extraData, err := registrarABI.Pack("register", usernameToLabel(username), common.Address(txArgs.From), x, y)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
|
||||
tx, err := snt.ApproveAndCall(
|
||||
txOpts,
|
||||
registryAddr,
|
||||
price,
|
||||
extraData,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.RegisterENS,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.Add(ctx, chainID, fullDomainName(username))
|
||||
if err != nil {
|
||||
log.Warn("Registering ENS username: transaction successful, but adding failed")
|
||||
}
|
||||
|
||||
return tx.Hash().String(), nil
|
||||
}
|
||||
|
||||
func (api *API) RegisterPrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (ethereum.CallMsg, error) {
|
||||
priceHex, err := api.Price(ctx, chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
price := new(big.Int)
|
||||
price.SetString(priceHex, 16)
|
||||
|
||||
registrarABI, err := abi.JSON(strings.NewReader(registrar.UsernameRegistrarABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
x, y := extractCoordinates(pubkey)
|
||||
extraData, err := registrarABI.Pack("register", usernameToLabel(username), common.Address(txArgs.From), x, y)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
registryAddr, err := api.usernameRegistrarAddr(ctx, chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
data, err := sntABI.Pack("approveAndCall", registryAddr, price, extraData)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
sntAddress, err := snt.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
return ethereum.CallMsg{
|
||||
From: common.Address(txArgs.From),
|
||||
To: &sntAddress,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) RegisterPrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (interface{}, error) {
|
||||
callMsg, err := api.RegisterPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toCallArg(callMsg), nil
|
||||
}
|
||||
|
||||
func (api *API) RegisterEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (uint64, error) {
|
||||
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
callMsg, err := api.RegisterPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + 1000, nil
|
||||
}
|
||||
|
||||
func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resolver, err := api.contractMaker.NewPublicResolver(chainID, resolverAddress)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
x, y := extractCoordinates(pubkey)
|
||||
txOpts := txArgs.ToTransactOpts(utils.GetSigner(chainID, api.accountsManager, api.config.KeyStoreDir, txArgs.From, password))
|
||||
tx, err := resolver.SetPubkey(txOpts, nameHash(username), x, y)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.pendingTracker.TrackPendingTransaction(
|
||||
wcommon.ChainID(chainID),
|
||||
tx.Hash(),
|
||||
common.Address(txArgs.From),
|
||||
transactions.SetPubKey,
|
||||
transactions.AutoDelete,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("TrackPendingTransaction error", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = api.Add(ctx, chainID, fullDomainName(username))
|
||||
|
||||
if err != nil {
|
||||
log.Warn("Registering ENS username: transaction successful, but adding failed")
|
||||
}
|
||||
|
||||
return tx.Hash().String(), nil
|
||||
}
|
||||
|
||||
func (api *API) SetPubKeyPrepareTxCallMsg(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (ethereum.CallMsg, error) {
|
||||
err := validateENSUsername(username)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
x, y := extractCoordinates(pubkey)
|
||||
|
||||
resolverABI, err := abi.JSON(strings.NewReader(resolver.PublicResolverABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
data, err := resolverABI.Pack("setPubkey", nameHash(username), x, y)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
resolverAddress, err := api.Resolver(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
return ethereum.CallMsg{
|
||||
From: common.Address(txArgs.From),
|
||||
To: resolverAddress,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) SetPubKeyPrepareTx(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (interface{}, error) {
|
||||
callMsg, err := api.SetPubKeyPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toCallArg(callMsg), nil
|
||||
}
|
||||
|
||||
func (api *API) SetPubKeyEstimate(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, username string, pubkey string) (uint64, error) {
|
||||
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
callMsg, err := api.SetPubKeyPrepareTxCallMsg(ctx, chainID, txArgs, username, pubkey)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
estimate, err := ethClient.EstimateGas(ctx, callMsg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return estimate + 1000, nil
|
||||
}
|
||||
|
||||
func (api *API) ResourceURL(ctx context.Context, chainID uint64, username string) (*URI, error) {
|
||||
scheme := "https"
|
||||
contentHash, err := api.ContentHash(ctx, chainID, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(contentHash) == 0 {
|
||||
return &URI{}, nil
|
||||
}
|
||||
|
||||
data, codec, err := multicodec.RemoveCodec(contentHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codecName, err := multicodec.Name(codec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch codecName {
|
||||
case "ipfs-ns":
|
||||
thisCID, err := cid.Parse(data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse CID")
|
||||
}
|
||||
str, err := thisCID.StringOfBase(multibase.Base32)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to obtain base36 representation")
|
||||
}
|
||||
|
||||
parsedURL, _ := url.Parse(params.IpfsGatewayURL)
|
||||
// Remove scheme from the url
|
||||
host := parsedURL.Hostname() + parsedURL.Path + str
|
||||
return &URI{scheme, host, ""}, nil
|
||||
case "ipns-ns":
|
||||
id, offset := binary.Uvarint(data)
|
||||
if id == 0 {
|
||||
return nil, fmt.Errorf("unknown CID")
|
||||
}
|
||||
|
||||
data, _, err := multicodec.RemoveCodec(data[offset:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedMHash, err := multihash.Decode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &URI{scheme, string(decodedMHash.Digest), ""}, nil
|
||||
case "swarm-ns":
|
||||
id, offset := binary.Uvarint(data)
|
||||
if id == 0 {
|
||||
return nil, fmt.Errorf("unknown CID")
|
||||
}
|
||||
data, _, err := multicodec.RemoveCodec(data[offset:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedMHash, err := multihash.Decode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := "/bzz:/" + hex.EncodeToString(decodedMHash.Digest) + "/"
|
||||
return &URI{scheme, "swarm-gateways.net", path}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown codec name %s", codecName)
|
||||
}
|
||||
}
|
||||
|
||||
func toCallArg(msg ethereum.CallMsg) interface{} {
|
||||
arg := map[string]interface{}{
|
||||
"from": msg.From,
|
||||
"to": msg.To,
|
||||
}
|
||||
if len(msg.Data) > 0 {
|
||||
arg["data"] = hexutil.Bytes(msg.Data)
|
||||
}
|
||||
if msg.Value != nil {
|
||||
arg["value"] = (*hexutil.Big)(msg.Value)
|
||||
}
|
||||
if msg.Gas != 0 {
|
||||
arg["gas"] = hexutil.Uint64(msg.Gas)
|
||||
}
|
||||
if msg.GasPrice != nil {
|
||||
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func fullDomainName(username string) string {
|
||||
return username + "." + StatusDomain
|
||||
}
|
||||
81
vendor/github.com/status-im/status-go/services/ens/database.go
generated
vendored
Normal file
81
vendor/github.com/status-im/status-go/services/ens/database.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package ens
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
type UsernameDetail struct {
|
||||
Username string `json:"username"`
|
||||
ChainID uint64 `json:"chainId"`
|
||||
Clock uint64 `json:"clock"`
|
||||
Removed bool `json:"removed"`
|
||||
}
|
||||
|
||||
func NewEnsDatabase(db *sql.DB) *Database {
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
func (db *Database) GetEnsUsernames(removed *bool) (result []*UsernameDetail, err error) {
|
||||
|
||||
var sqlQuery = `SELECT username, chain_id, clock, removed
|
||||
FROM ens_usernames`
|
||||
|
||||
var rows *sql.Rows
|
||||
if removed == nil {
|
||||
rows, err = db.db.Query(sqlQuery)
|
||||
} else {
|
||||
sqlQuery += " WHERE removed = ?"
|
||||
rows, err = db.db.Query(sqlQuery, removed)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var ensUsername UsernameDetail
|
||||
err = rows.Scan(&ensUsername.Username, &ensUsername.ChainID, &ensUsername.Clock, &ensUsername.Removed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, &ensUsername)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (db *Database) AddEnsUsername(details *UsernameDetail) error {
|
||||
const sqlQuery = `INSERT OR REPLACE INTO ens_usernames(username, chain_id, clock, removed)
|
||||
VALUES (?, ?, ?, ?)`
|
||||
_, err := db.db.Exec(sqlQuery, details.Username, details.ChainID, details.Clock, details.Removed)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) RemoveEnsUsername(details *UsernameDetail) (bool, error) {
|
||||
const sqlQuery = `UPDATE ens_usernames SET removed = 1, clock = ?
|
||||
WHERE username = (?) AND chain_id = ?`
|
||||
result, err := db.db.Exec(sqlQuery, details.Clock, details.Username, details.ChainID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
n, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (db *Database) SaveOrUpdateEnsUsername(details *UsernameDetail) error {
|
||||
const sqlQuery = `INSERT OR REPLACE INTO ens_usernames (username, chain_id, clock, removed)
|
||||
SELECT ?, ?, ?, ?
|
||||
WHERE NOT EXISTS (SELECT 1 FROM ens_usernames WHERE username = ? AND chain_id = ? AND clock >= ?);`
|
||||
|
||||
_, err := db.db.Exec(sqlQuery, details.Username, details.ChainID, details.Clock, details.Removed, details.Username, details.ChainID, details.Clock)
|
||||
return err
|
||||
}
|
||||
72
vendor/github.com/status-im/status-go/services/ens/service.go
generated
vendored
Normal file
72
vendor/github.com/status-im/status-go/services/ens/service.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package ens
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
// NewService initializes service instance.
|
||||
func NewService(rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, config *params.NodeConfig, appDb *sql.DB, timeSource func() time.Time) *Service {
|
||||
service := &Service{
|
||||
rpcClient,
|
||||
accountsManager,
|
||||
pendingTracker,
|
||||
config,
|
||||
nil,
|
||||
nil,
|
||||
}
|
||||
service.api = NewAPI(rpcClient, accountsManager, pendingTracker, config, appDb, timeSource, &service.syncUserDetailFunc)
|
||||
return service
|
||||
}
|
||||
|
||||
// Service is a browsers service.
|
||||
type Service struct {
|
||||
rpcClient *rpc.Client
|
||||
accountsManager *account.GethManager
|
||||
pendingTracker *transactions.PendingTxTracker
|
||||
config *params.NodeConfig
|
||||
api *API
|
||||
syncUserDetailFunc syncUsernameDetail
|
||||
}
|
||||
|
||||
func (s *Service) Init(syncUserDetailFunc syncUsernameDetail) {
|
||||
s.syncUserDetailFunc = syncUserDetailFunc
|
||||
}
|
||||
|
||||
// Start a service.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a service.
|
||||
func (s *Service) Stop() error {
|
||||
s.api.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) API() *API {
|
||||
return s.api
|
||||
}
|
||||
|
||||
// APIs returns list of available RPC APIs.
|
||||
func (s *Service) APIs() []ethRpc.API {
|
||||
return []ethRpc.API{
|
||||
{
|
||||
Namespace: "ens",
|
||||
Version: "0.1.0",
|
||||
Service: s.api,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
54
vendor/github.com/status-im/status-go/services/ens/strings.go
generated
vendored
Normal file
54
vendor/github.com/status-im/status-go/services/ens/strings.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package ens
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func nameHash(name string) common.Hash {
|
||||
node := common.Hash{}
|
||||
|
||||
if len(name) > 0 {
|
||||
labels := strings.Split(name, ".")
|
||||
|
||||
for i := len(labels) - 1; i >= 0; i-- {
|
||||
labelSha := crypto.Keccak256Hash([]byte(labels[i]))
|
||||
node = crypto.Keccak256Hash(node.Bytes(), labelSha.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func validateENSUsername(username string) error {
|
||||
if !strings.HasSuffix(username, ".eth") {
|
||||
return fmt.Errorf("username must end with .eth")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func usernameToLabel(username string) [32]byte {
|
||||
usernameHashed := crypto.Keccak256([]byte(username))
|
||||
var label [32]byte
|
||||
copy(label[:], usernameHashed)
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
func extractCoordinates(pubkey string) ([32]byte, [32]byte) {
|
||||
x, _ := hex.DecodeString(pubkey[4:68])
|
||||
y, _ := hex.DecodeString(pubkey[68:132])
|
||||
|
||||
var xByte [32]byte
|
||||
copy(xByte[:], x)
|
||||
|
||||
var yByte [32]byte
|
||||
copy(yByte[:], y)
|
||||
|
||||
return xByte, yByte
|
||||
}
|
||||
82
vendor/github.com/status-im/status-go/services/ext/README.md
generated
vendored
Normal file
82
vendor/github.com/status-im/status-go/services/ext/README.md
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
Whisper API Extension
|
||||
=====================
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
|
||||
#### shhext_getNewFilterMessages
|
||||
|
||||
Accepts the same input as [`shh_getFilterMessages`](https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_getFilterChanges).
|
||||
|
||||
##### Returns
|
||||
|
||||
Returns a list of whisper messages matching the specified filter. Filters out
|
||||
the messages already confirmed received by [`shhext_confirmMessagesProcessed`](#shhextconfirmmessagesprocessed)
|
||||
|
||||
Deduplication is made using the whisper envelope content and topic only, so the
|
||||
same content received in different whisper envelopes will be deduplicated.
|
||||
|
||||
|
||||
#### shhext_confirmMessagesProcessed
|
||||
|
||||
Confirms whisper messages received and processed on the client side. These
|
||||
messages won't appear anymore when [`shhext_getNewFilterMessages`](#shhextgetnewfiltermessages)
|
||||
is called.
|
||||
|
||||
##### Parameters
|
||||
|
||||
Gets a list of whisper envelopes.
|
||||
|
||||
|
||||
#### shhext_post
|
||||
|
||||
Accepts same input as [`shh_post`](https://github.com/ethereum/wiki/wiki/JSON-RPC#shh_post).
|
||||
|
||||
##### Returns
|
||||
|
||||
`DATA`, 32 Bytes - the envelope hash
|
||||
|
||||
#### shhext_requestMessages
|
||||
|
||||
Sends a request for historic messages to a mail server.
|
||||
|
||||
##### Parameters
|
||||
|
||||
1. `Object` - The message request object:
|
||||
|
||||
- `mailServerPeer`:`URL` - Mail servers' enode addess
|
||||
- `from`:`QUANTITY` - (optional) Lower bound of time range as unix timestamp, default is 24 hours back from now
|
||||
- `to`:`QUANTITY`- (optional) Upper bound of time range as unix timestamp, default is now
|
||||
- `topic`:`DATA`, 4 Bytes - Regular whisper topic
|
||||
- `symKeyID`:`DATA`- ID of a symmetric key to authenticate to mail server, derived from mail server password
|
||||
|
||||
##### Returns
|
||||
|
||||
`Boolean` - returns `true` if the request was send, otherwise `false`.
|
||||
|
||||
Signals
|
||||
-------
|
||||
|
||||
Sends sent signal once per envelope.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "envelope.sent",
|
||||
"event": {
|
||||
"hash": "0xea0b93079ed32588628f1cabbbb5ed9e4d50b7571064c2962c3853972db67790"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sends expired signal if envelope dropped from whisper local queue before it was
|
||||
sent to any peer on the network.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "envelope.expired",
|
||||
"event": {
|
||||
"hash": "0x754f4c12dccb14886f791abfeb77ffb86330d03d5a4ba6f37a8c21281988b69e"
|
||||
}
|
||||
}
|
||||
```
|
||||
1808
vendor/github.com/status-im/status-go/services/ext/api.go
generated
vendored
Normal file
1808
vendor/github.com/status-im/status-go/services/ext/api.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
55
vendor/github.com/status-im/status-go/services/ext/context.go
generated
vendored
Normal file
55
vendor/github.com/status-im/status-go/services/ext/context.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/db"
|
||||
)
|
||||
|
||||
// ContextKey is a type used for keys in ext Context.
|
||||
type ContextKey struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewContextKey returns new ContextKey instance.
|
||||
func NewContextKey(name string) ContextKey {
|
||||
return ContextKey{Name: name}
|
||||
}
|
||||
|
||||
var (
|
||||
historyDBKey = NewContextKey("history_db")
|
||||
requestRegistryKey = NewContextKey("request_registry")
|
||||
timeKey = NewContextKey("time")
|
||||
)
|
||||
|
||||
// NewContext creates Context with all required fields.
|
||||
func NewContext(ctx context.Context, source TimeSource, registry *RequestsRegistry, storage db.Storage) Context {
|
||||
ctx = context.WithValue(ctx, historyDBKey, db.NewHistoryStore(storage))
|
||||
ctx = context.WithValue(ctx, timeKey, source)
|
||||
ctx = context.WithValue(ctx, requestRegistryKey, registry)
|
||||
return Context{ctx}
|
||||
}
|
||||
|
||||
// TimeSource is a type used for current time.
|
||||
type TimeSource func() time.Time
|
||||
|
||||
// Context provides access to request-scoped values.
|
||||
type Context struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
// HistoryStore returns db.HistoryStore instance associated with this request.
|
||||
func (c Context) HistoryStore() db.HistoryStore {
|
||||
return c.Value(historyDBKey).(db.HistoryStore)
|
||||
}
|
||||
|
||||
// Time returns current time using time function associated with this request.
|
||||
func (c Context) Time() time.Time {
|
||||
return c.Value(timeKey).(TimeSource)()
|
||||
}
|
||||
|
||||
// RequestRegistry returns RequestRegistry that tracks each request life-span.
|
||||
func (c Context) RequestRegistry() *RequestsRegistry {
|
||||
return c.Value(requestRegistryKey).(*RequestsRegistry)
|
||||
}
|
||||
48
vendor/github.com/status-im/status-go/services/ext/handler_mock.go
generated
vendored
Normal file
48
vendor/github.com/status-im/status-go/services/ext/handler_mock.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
type failureMessage struct {
|
||||
IDs [][]byte
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewHandlerMock(buf int) HandlerMock {
|
||||
return HandlerMock{
|
||||
confirmations: make(chan [][]byte, buf),
|
||||
expirations: make(chan failureMessage, buf),
|
||||
requestsCompleted: make(chan types.Hash, buf),
|
||||
requestsExpired: make(chan types.Hash, buf),
|
||||
requestsFailed: make(chan types.Hash, buf),
|
||||
}
|
||||
}
|
||||
|
||||
type HandlerMock struct {
|
||||
confirmations chan [][]byte
|
||||
expirations chan failureMessage
|
||||
requestsCompleted chan types.Hash
|
||||
requestsExpired chan types.Hash
|
||||
requestsFailed chan types.Hash
|
||||
}
|
||||
|
||||
func (t HandlerMock) EnvelopeSent(ids [][]byte) {
|
||||
t.confirmations <- ids
|
||||
}
|
||||
|
||||
func (t HandlerMock) EnvelopeExpired(ids [][]byte, err error) {
|
||||
t.expirations <- failureMessage{IDs: ids, Error: err}
|
||||
}
|
||||
|
||||
func (t HandlerMock) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
||||
if err == nil {
|
||||
t.requestsCompleted <- requestID
|
||||
} else {
|
||||
t.requestsFailed <- requestID
|
||||
}
|
||||
}
|
||||
|
||||
func (t HandlerMock) MailServerRequestExpired(hash types.Hash) {
|
||||
t.requestsExpired <- hash
|
||||
}
|
||||
136
vendor/github.com/status-im/status-go/services/ext/mailrequests.go
generated
vendored
Normal file
136
vendor/github.com/status-im/status-go/services/ext/mailrequests.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/services/ext/mailservers"
|
||||
)
|
||||
|
||||
// EnvelopeState in local tracker
|
||||
type EnvelopeState int
|
||||
|
||||
const (
|
||||
// NotRegistered returned if asked hash wasn't registered in the tracker.
|
||||
NotRegistered EnvelopeState = -1
|
||||
// MailServerRequestSent is set when p2p request is sent to the mailserver
|
||||
MailServerRequestSent
|
||||
)
|
||||
|
||||
// MailRequestMonitor is responsible for monitoring history request to mailservers.
|
||||
type MailRequestMonitor struct {
|
||||
eventSub mailservers.EnvelopeEventSubscriber
|
||||
handler EnvelopeEventsHandler
|
||||
|
||||
mu sync.Mutex
|
||||
cache map[types.Hash]EnvelopeState
|
||||
|
||||
requestsRegistry *RequestsRegistry
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func NewMailRequestMonitor(eventSub mailservers.EnvelopeEventSubscriber, h EnvelopeEventsHandler, reg *RequestsRegistry) *MailRequestMonitor {
|
||||
return &MailRequestMonitor{
|
||||
eventSub: eventSub,
|
||||
handler: h,
|
||||
cache: make(map[types.Hash]EnvelopeState),
|
||||
requestsRegistry: reg,
|
||||
}
|
||||
}
|
||||
|
||||
// Start processing events.
|
||||
func (m *MailRequestMonitor) Start() {
|
||||
m.quit = make(chan struct{})
|
||||
m.wg.Add(1)
|
||||
go func() {
|
||||
m.handleEnvelopeEvents()
|
||||
m.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop process events.
|
||||
func (m *MailRequestMonitor) Stop() {
|
||||
close(m.quit)
|
||||
m.wg.Wait()
|
||||
}
|
||||
|
||||
func (m *MailRequestMonitor) GetState(hash types.Hash) EnvelopeState {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
state, exist := m.cache[hash]
|
||||
if !exist {
|
||||
return NotRegistered
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// handleEnvelopeEvents processes whisper envelope events
|
||||
func (m *MailRequestMonitor) handleEnvelopeEvents() {
|
||||
events := make(chan types.EnvelopeEvent, 100) // must be buffered to prevent blocking whisper
|
||||
sub := m.eventSub.SubscribeEnvelopeEvents(events)
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case <-m.quit:
|
||||
return
|
||||
case event := <-events:
|
||||
m.handleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleEvent based on type of the event either triggers
|
||||
// confirmation handler or removes hash from MailRequestMonitor
|
||||
func (m *MailRequestMonitor) handleEvent(event types.EnvelopeEvent) {
|
||||
handlers := map[types.EventType]func(types.EnvelopeEvent){
|
||||
types.EventMailServerRequestSent: m.handleRequestSent,
|
||||
types.EventMailServerRequestCompleted: m.handleEventMailServerRequestCompleted,
|
||||
types.EventMailServerRequestExpired: m.handleEventMailServerRequestExpired,
|
||||
}
|
||||
|
||||
if handler, ok := handlers[event.Event]; ok {
|
||||
handler(event)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MailRequestMonitor) handleRequestSent(event types.EnvelopeEvent) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.cache[event.Hash] = MailServerRequestSent
|
||||
}
|
||||
|
||||
func (m *MailRequestMonitor) handleEventMailServerRequestCompleted(event types.EnvelopeEvent) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.requestsRegistry.Unregister(event.Hash)
|
||||
state, ok := m.cache[event.Hash]
|
||||
if !ok || state != MailServerRequestSent {
|
||||
return
|
||||
}
|
||||
log.Debug("mailserver response received", "hash", event.Hash)
|
||||
delete(m.cache, event.Hash)
|
||||
if m.handler != nil {
|
||||
if resp, ok := event.Data.(*types.MailServerResponse); ok {
|
||||
m.handler.MailServerRequestCompleted(event.Hash, resp.LastEnvelopeHash, resp.Cursor, resp.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MailRequestMonitor) handleEventMailServerRequestExpired(event types.EnvelopeEvent) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.requestsRegistry.Unregister(event.Hash)
|
||||
state, ok := m.cache[event.Hash]
|
||||
if !ok || state != MailServerRequestSent {
|
||||
return
|
||||
}
|
||||
log.Debug("mailserver response expired", "hash", event.Hash)
|
||||
delete(m.cache, event.Hash)
|
||||
if m.handler != nil {
|
||||
m.handler.MailServerRequestExpired(event.Hash)
|
||||
}
|
||||
}
|
||||
145
vendor/github.com/status-im/status-go/services/ext/mailservers/cache.go
generated
vendored
Normal file
145
vendor/github.com/status-im/status-go/services/ext/mailservers/cache.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
||||
"github.com/status-im/status-go/db"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
// NewPeerRecord returns instance of the peer record.
|
||||
func NewPeerRecord(node *enode.Node) PeerRecord {
|
||||
return PeerRecord{node: node}
|
||||
}
|
||||
|
||||
// PeerRecord is set data associated with each peer that is stored on disk.
|
||||
// PeerRecord stored with a enode as a key in leveldb, and body marshalled as json.
|
||||
type PeerRecord struct {
|
||||
node *enode.Node
|
||||
|
||||
// last time it was used.
|
||||
LastUsed time.Time
|
||||
}
|
||||
|
||||
// Encode encodes PeerRecords to bytes.
|
||||
func (r PeerRecord) Encode() ([]byte, error) {
|
||||
return json.Marshal(r)
|
||||
}
|
||||
|
||||
// ID returns enode identity of the node.
|
||||
func (r PeerRecord) ID() enode.ID {
|
||||
return r.node.ID()
|
||||
}
|
||||
|
||||
// Node returs pointer to original object.
|
||||
// enode.Node doensn't allow modification on the object.
|
||||
func (r PeerRecord) Node() *enode.Node {
|
||||
return r.node
|
||||
}
|
||||
|
||||
// EncodeKey returns bytes that will should be used as a key in persistent storage.
|
||||
func (r PeerRecord) EncodeKey() ([]byte, error) {
|
||||
return r.Node().MarshalText()
|
||||
}
|
||||
|
||||
// NewCache returns pointer to a Cache instance.
|
||||
func NewCache(db *leveldb.DB) *Cache {
|
||||
return &Cache{db: db}
|
||||
}
|
||||
|
||||
// Cache is wrapper for operations on disk with leveldb.
|
||||
type Cache struct {
|
||||
db *leveldb.DB
|
||||
}
|
||||
|
||||
// Replace deletes old and adds new records in the persistent cache.
|
||||
func (c *Cache) Replace(nodes []*enode.Node) error {
|
||||
batch := new(leveldb.Batch)
|
||||
iter := createPeersIterator(c.db)
|
||||
defer iter.Release()
|
||||
newNodes := nodesToMap(nodes)
|
||||
for iter.Next() {
|
||||
record, err := unmarshalKeyValue(keyWithoutPrefix(iter.Key()), iter.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, exist := newNodes[types.EnodeID(record.ID())]; exist {
|
||||
delete(newNodes, types.EnodeID(record.ID()))
|
||||
} else {
|
||||
batch.Delete(iter.Key())
|
||||
}
|
||||
}
|
||||
for _, n := range newNodes {
|
||||
enodeKey, err := n.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// we put nil as default value doesn't have any state associated with them.
|
||||
batch.Put(db.Key(db.MailserversCache, enodeKey), nil)
|
||||
}
|
||||
return c.db.Write(batch, nil)
|
||||
}
|
||||
|
||||
// LoadAll loads all records from persistent database.
|
||||
func (c *Cache) LoadAll() (rst []PeerRecord, err error) {
|
||||
iter := createPeersIterator(c.db)
|
||||
for iter.Next() {
|
||||
record, err := unmarshalKeyValue(keyWithoutPrefix(iter.Key()), iter.Value())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rst = append(rst, record)
|
||||
}
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
// UpdateRecord updates single record.
|
||||
func (c *Cache) UpdateRecord(record PeerRecord) error {
|
||||
enodeKey, err := record.EncodeKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := record.Encode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.db.Put(db.Key(db.MailserversCache, enodeKey), value, nil)
|
||||
}
|
||||
|
||||
func unmarshalKeyValue(key, value []byte) (record PeerRecord, err error) {
|
||||
enodeKey := key
|
||||
node := new(enode.Node)
|
||||
err = node.UnmarshalText(enodeKey)
|
||||
if err != nil {
|
||||
return record, err
|
||||
}
|
||||
record = PeerRecord{node: node}
|
||||
if len(value) != 0 {
|
||||
err = json.Unmarshal(value, &record)
|
||||
}
|
||||
return record, err
|
||||
}
|
||||
|
||||
func nodesToMap(nodes []*enode.Node) map[types.EnodeID]*enode.Node {
|
||||
rst := map[types.EnodeID]*enode.Node{}
|
||||
for _, n := range nodes {
|
||||
rst[types.EnodeID(n.ID())] = n
|
||||
}
|
||||
return rst
|
||||
}
|
||||
|
||||
func createPeersIterator(level *leveldb.DB) iterator.Iterator {
|
||||
return level.NewIterator(util.BytesPrefix([]byte{byte(db.MailserversCache)}), nil)
|
||||
}
|
||||
|
||||
// keyWithoutPrefix removes first byte from key.
|
||||
func keyWithoutPrefix(key []byte) []byte {
|
||||
return key[1:]
|
||||
}
|
||||
271
vendor/github.com/status-im/status-go/services/ext/mailservers/connmanager.go
generated
vendored
Normal file
271
vendor/github.com/status-im/status-go/services/ext/mailservers/connmanager.go
generated
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
const (
|
||||
peerEventsBuffer = 10 // sufficient buffer to avoid blocking a p2p feed.
|
||||
whisperEventsBuffer = 20 // sufficient buffer to avod blocking a eventSub envelopes feed.
|
||||
)
|
||||
|
||||
// PeerAdderRemover is an interface for adding or removing peers.
|
||||
type PeerAdderRemover interface {
|
||||
AddPeer(node *enode.Node)
|
||||
RemovePeer(node *enode.Node)
|
||||
}
|
||||
|
||||
// PeerEventsSubscriber interface to subscribe for p2p.PeerEvent's.
|
||||
type PeerEventsSubscriber interface {
|
||||
SubscribeEvents(chan *p2p.PeerEvent) event.Subscription
|
||||
}
|
||||
|
||||
// EnvelopeEventSubscriber interface to subscribe for types.EnvelopeEvent's.
|
||||
type EnvelopeEventSubscriber interface {
|
||||
SubscribeEnvelopeEvents(chan<- types.EnvelopeEvent) types.Subscription
|
||||
}
|
||||
|
||||
type p2pServer interface {
|
||||
PeerAdderRemover
|
||||
PeerEventsSubscriber
|
||||
}
|
||||
|
||||
// NewConnectionManager creates an instance of ConnectionManager.
|
||||
func NewConnectionManager(server p2pServer, eventSub EnvelopeEventSubscriber, target, maxFailures int, timeout time.Duration) *ConnectionManager {
|
||||
return &ConnectionManager{
|
||||
server: server,
|
||||
eventSub: eventSub,
|
||||
connectedTarget: target,
|
||||
maxFailures: maxFailures,
|
||||
notifications: make(chan []*enode.Node),
|
||||
timeoutWaitAdded: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionManager manages keeps target of peers connected.
|
||||
type ConnectionManager struct {
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
|
||||
server p2pServer
|
||||
eventSub EnvelopeEventSubscriber
|
||||
|
||||
notifications chan []*enode.Node
|
||||
connectedTarget int
|
||||
timeoutWaitAdded time.Duration
|
||||
maxFailures int
|
||||
}
|
||||
|
||||
// Notify sends a non-blocking notification about new nodes.
|
||||
func (ps *ConnectionManager) Notify(nodes []*enode.Node) {
|
||||
ps.wg.Add(1)
|
||||
go func() {
|
||||
select {
|
||||
case ps.notifications <- nodes:
|
||||
case <-ps.quit:
|
||||
}
|
||||
ps.wg.Done()
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// Start subscribes to a p2p server and handles new peers and state updates for those peers.
|
||||
func (ps *ConnectionManager) Start() {
|
||||
ps.quit = make(chan struct{})
|
||||
ps.wg.Add(1)
|
||||
go func() {
|
||||
state := newInternalState(ps.server, ps.connectedTarget, ps.timeoutWaitAdded)
|
||||
events := make(chan *p2p.PeerEvent, peerEventsBuffer)
|
||||
sub := ps.server.SubscribeEvents(events)
|
||||
whisperEvents := make(chan types.EnvelopeEvent, whisperEventsBuffer)
|
||||
whisperSub := ps.eventSub.SubscribeEnvelopeEvents(whisperEvents)
|
||||
requests := map[types.Hash]struct{}{}
|
||||
failuresPerServer := map[types.EnodeID]int{}
|
||||
|
||||
defer sub.Unsubscribe()
|
||||
defer whisperSub.Unsubscribe()
|
||||
defer ps.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ps.quit:
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
log.Error("retry after error subscribing to p2p events", "error", err)
|
||||
return
|
||||
case err := <-whisperSub.Err():
|
||||
log.Error("retry after error suscribing to eventSub events", "error", err)
|
||||
return
|
||||
case newNodes := <-ps.notifications:
|
||||
state.processReplacement(newNodes, events)
|
||||
case ev := <-events:
|
||||
processPeerEvent(state, ev)
|
||||
case ev := <-whisperEvents:
|
||||
// TODO treat failed requests the same way as expired
|
||||
switch ev.Event {
|
||||
case types.EventMailServerRequestSent:
|
||||
requests[ev.Hash] = struct{}{}
|
||||
case types.EventMailServerRequestCompleted:
|
||||
// reset failures count on first success
|
||||
failuresPerServer[ev.Peer] = 0
|
||||
delete(requests, ev.Hash)
|
||||
case types.EventMailServerRequestExpired:
|
||||
_, exist := requests[ev.Hash]
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
failuresPerServer[ev.Peer]++
|
||||
log.Debug("request to a mail server expired, disconnect a peer", "address", ev.Peer)
|
||||
if failuresPerServer[ev.Peer] >= ps.maxFailures {
|
||||
state.nodeDisconnected(ev.Peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop gracefully closes all background goroutines and waits until they finish.
|
||||
func (ps *ConnectionManager) Stop() {
|
||||
if ps.quit == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ps.quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
close(ps.quit)
|
||||
ps.wg.Wait()
|
||||
ps.quit = nil
|
||||
}
|
||||
|
||||
func (state *internalState) processReplacement(newNodes []*enode.Node, events <-chan *p2p.PeerEvent) {
|
||||
replacement := map[types.EnodeID]*enode.Node{}
|
||||
for _, n := range newNodes {
|
||||
replacement[types.EnodeID(n.ID())] = n
|
||||
}
|
||||
state.replaceNodes(replacement)
|
||||
if state.ReachedTarget() {
|
||||
log.Debug("already connected with required target", "target", state.target)
|
||||
return
|
||||
}
|
||||
if state.timeout != 0 {
|
||||
log.Debug("waiting defined timeout to establish connections",
|
||||
"timeout", state.timeout, "target", state.target)
|
||||
timer := time.NewTimer(state.timeout)
|
||||
waitForConnections(state, timer.C, events)
|
||||
timer.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func newInternalState(srv PeerAdderRemover, target int, timeout time.Duration) *internalState {
|
||||
return &internalState{
|
||||
options: options{target: target, timeout: timeout},
|
||||
srv: srv,
|
||||
connected: map[types.EnodeID]struct{}{},
|
||||
currentNodes: map[types.EnodeID]*enode.Node{},
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
target int
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
type internalState struct {
|
||||
options
|
||||
srv PeerAdderRemover
|
||||
|
||||
connected map[types.EnodeID]struct{}
|
||||
currentNodes map[types.EnodeID]*enode.Node
|
||||
}
|
||||
|
||||
func (state *internalState) ReachedTarget() bool {
|
||||
return len(state.connected) >= state.target
|
||||
}
|
||||
|
||||
func (state *internalState) replaceNodes(new map[types.EnodeID]*enode.Node) {
|
||||
for nid, n := range state.currentNodes {
|
||||
if _, exist := new[nid]; !exist {
|
||||
delete(state.connected, nid)
|
||||
state.srv.RemovePeer(n)
|
||||
}
|
||||
}
|
||||
if !state.ReachedTarget() {
|
||||
for _, n := range new {
|
||||
state.srv.AddPeer(n)
|
||||
}
|
||||
}
|
||||
state.currentNodes = new
|
||||
}
|
||||
|
||||
func (state *internalState) nodeAdded(peer types.EnodeID) {
|
||||
n, exist := state.currentNodes[peer]
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if state.ReachedTarget() {
|
||||
state.srv.RemovePeer(n)
|
||||
} else {
|
||||
state.connected[types.EnodeID(n.ID())] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (state *internalState) nodeDisconnected(peer types.EnodeID) {
|
||||
n, exist := state.currentNodes[peer] // unrelated event
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
_, exist = state.connected[peer] // check if already disconnected
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
if len(state.currentNodes) == 1 { // keep node connected if we don't have another choice
|
||||
return
|
||||
}
|
||||
state.srv.RemovePeer(n) // remove peer permanently, otherwise p2p.Server will try to reconnect
|
||||
delete(state.connected, peer)
|
||||
if !state.ReachedTarget() { // try to connect with any other selected (but not connected) node
|
||||
for nid, n := range state.currentNodes {
|
||||
_, exist := state.connected[nid]
|
||||
if exist || peer == nid {
|
||||
continue
|
||||
}
|
||||
state.srv.AddPeer(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processPeerEvent(state *internalState, ev *p2p.PeerEvent) {
|
||||
switch ev.Type {
|
||||
case p2p.PeerEventTypeAdd:
|
||||
log.Debug("connected to a mailserver", "address", ev.Peer)
|
||||
state.nodeAdded(types.EnodeID(ev.Peer))
|
||||
case p2p.PeerEventTypeDrop:
|
||||
log.Debug("mailserver disconnected", "address", ev.Peer)
|
||||
state.nodeDisconnected(types.EnodeID(ev.Peer))
|
||||
}
|
||||
}
|
||||
|
||||
func waitForConnections(state *internalState, timeout <-chan time.Time, events <-chan *p2p.PeerEvent) {
|
||||
for {
|
||||
select {
|
||||
case ev := <-events:
|
||||
processPeerEvent(state, ev)
|
||||
if state.ReachedTarget() {
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
85
vendor/github.com/status-im/status-go/services/ext/mailservers/connmonitor.go
generated
vendored
Normal file
85
vendor/github.com/status-im/status-go/services/ext/mailservers/connmonitor.go
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
// NewLastUsedConnectionMonitor returns pointer to the instance of LastUsedConnectionMonitor.
|
||||
func NewLastUsedConnectionMonitor(ps *PeerStore, cache *Cache, eventSub EnvelopeEventSubscriber) *LastUsedConnectionMonitor {
|
||||
return &LastUsedConnectionMonitor{
|
||||
ps: ps,
|
||||
cache: cache,
|
||||
eventSub: eventSub,
|
||||
}
|
||||
}
|
||||
|
||||
// LastUsedConnectionMonitor watches relevant events and reflects it in cache.
|
||||
type LastUsedConnectionMonitor struct {
|
||||
ps *PeerStore
|
||||
cache *Cache
|
||||
|
||||
eventSub EnvelopeEventSubscriber
|
||||
|
||||
quit chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// Start spins a separate goroutine to watch connections.
|
||||
func (mon *LastUsedConnectionMonitor) Start() {
|
||||
mon.quit = make(chan struct{})
|
||||
mon.wg.Add(1)
|
||||
go func() {
|
||||
events := make(chan types.EnvelopeEvent, whisperEventsBuffer)
|
||||
sub := mon.eventSub.SubscribeEnvelopeEvents(events)
|
||||
defer sub.Unsubscribe()
|
||||
defer mon.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-mon.quit:
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
log.Error("retry after error suscribing to eventSub events", "error", err)
|
||||
return
|
||||
case ev := <-events:
|
||||
node := mon.ps.Get(ev.Peer)
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
if ev.Event == types.EventMailServerRequestCompleted {
|
||||
err := mon.updateRecord(ev.Peer)
|
||||
if err != nil {
|
||||
log.Error("unable to update storage", "peer", ev.Peer, "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (mon *LastUsedConnectionMonitor) updateRecord(nodeID types.EnodeID) error {
|
||||
node := mon.ps.Get(nodeID)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return mon.cache.UpdateRecord(PeerRecord{node: node, LastUsed: time.Now()})
|
||||
}
|
||||
|
||||
// Stop closes channel to signal a quit and waits until all goroutines are stoppped.
|
||||
func (mon *LastUsedConnectionMonitor) Stop() {
|
||||
if mon.quit == nil {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-mon.quit:
|
||||
return
|
||||
default:
|
||||
}
|
||||
close(mon.quit)
|
||||
mon.wg.Wait()
|
||||
mon.quit = nil
|
||||
}
|
||||
63
vendor/github.com/status-im/status-go/services/ext/mailservers/peerstore.go
generated
vendored
Normal file
63
vendor/github.com/status-im/status-go/services/ext/mailservers/peerstore.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoConnected returned when mail servers are not connected.
|
||||
ErrNoConnected = errors.New("no connected mail servers")
|
||||
)
|
||||
|
||||
// PeersProvider is an interface for requesting list of peers.
|
||||
type PeersProvider interface {
|
||||
Peers() []*p2p.Peer
|
||||
}
|
||||
|
||||
// NewPeerStore returns an instance of PeerStore.
|
||||
func NewPeerStore(cache *Cache) *PeerStore {
|
||||
return &PeerStore{
|
||||
nodes: map[types.EnodeID]*enode.Node{},
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// PeerStore stores list of selected mail servers and keeps N of them connected.
|
||||
type PeerStore struct {
|
||||
mu sync.RWMutex
|
||||
nodes map[types.EnodeID]*enode.Node
|
||||
|
||||
cache *Cache
|
||||
}
|
||||
|
||||
// Exist confirms that peers was added to a store.
|
||||
func (ps *PeerStore) Exist(nodeID types.EnodeID) bool {
|
||||
ps.mu.RLock()
|
||||
defer ps.mu.RUnlock()
|
||||
_, exist := ps.nodes[nodeID]
|
||||
return exist
|
||||
}
|
||||
|
||||
// Get returns instance of the node with requested ID or nil if ID is not found.
|
||||
func (ps *PeerStore) Get(nodeID types.EnodeID) *enode.Node {
|
||||
ps.mu.RLock()
|
||||
defer ps.mu.RUnlock()
|
||||
return ps.nodes[nodeID]
|
||||
}
|
||||
|
||||
// Update updates peers locally.
|
||||
func (ps *PeerStore) Update(nodes []*enode.Node) error {
|
||||
ps.mu.Lock()
|
||||
ps.nodes = map[types.EnodeID]*enode.Node{}
|
||||
for _, n := range nodes {
|
||||
ps.nodes[types.EnodeID(n.ID())] = n
|
||||
}
|
||||
ps.mu.Unlock()
|
||||
return ps.cache.Replace(nodes)
|
||||
}
|
||||
54
vendor/github.com/status-im/status-go/services/ext/mailservers/utils.go
generated
vendored
Normal file
54
vendor/github.com/status-im/status-go/services/ext/mailservers/utils.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
// GetFirstConnected returns first connected peer that is also added to a peer store.
|
||||
// Raises ErrNoConnected if no peers are added to a peer store.
|
||||
func GetFirstConnected(provider PeersProvider, store *PeerStore) (*enode.Node, error) {
|
||||
peers := provider.Peers()
|
||||
for _, p := range peers {
|
||||
if store.Exist(types.EnodeID(p.ID())) {
|
||||
return p.Node(), nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNoConnected
|
||||
}
|
||||
|
||||
// NodesNotifee interface to be notified when new nodes are received.
|
||||
type NodesNotifee interface {
|
||||
Notify([]*enode.Node)
|
||||
}
|
||||
|
||||
// EnsureUsedRecordsAddedFirst checks if any nodes were marked as connected before app went offline.
|
||||
func EnsureUsedRecordsAddedFirst(ps *PeerStore, conn NodesNotifee) error {
|
||||
records, err := ps.cache.LoadAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil
|
||||
}
|
||||
sort.Slice(records, func(i, j int) bool {
|
||||
return records[i].LastUsed.After(records[j].LastUsed)
|
||||
})
|
||||
all := recordsToNodes(records)
|
||||
if !records[0].LastUsed.IsZero() {
|
||||
conn.Notify(all[:1])
|
||||
}
|
||||
conn.Notify(all)
|
||||
return nil
|
||||
}
|
||||
|
||||
func recordsToNodes(records []PeerRecord) []*enode.Node {
|
||||
nodes := make([]*enode.Node, len(records))
|
||||
for i := range records {
|
||||
nodes[i] = records[i].Node()
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
45
vendor/github.com/status-im/status-go/services/ext/node_mock.go
generated
vendored
Normal file
45
vendor/github.com/status-im/status-go/services/ext/node_mock.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
enstypes "github.com/status-im/status-go/eth-node/types/ens"
|
||||
)
|
||||
|
||||
type TestNodeWrapper struct {
|
||||
whisper types.Whisper
|
||||
waku types.Waku
|
||||
}
|
||||
|
||||
func NewTestNodeWrapper(whisper types.Whisper, waku types.Waku) *TestNodeWrapper {
|
||||
return &TestNodeWrapper{whisper: whisper, waku: waku}
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) NewENSVerifier(_ *zap.Logger) enstypes.ENSVerifier {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) GetWhisper(_ interface{}) (types.Whisper, error) {
|
||||
return w.whisper, nil
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) GetWaku(_ interface{}) (types.Waku, error) {
|
||||
return w.waku, nil
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) GetWakuV2(_ interface{}) (types.Waku, error) {
|
||||
return w.waku, nil
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) PeersCount() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) AddPeer(url string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (w *TestNodeWrapper) RemovePeer(url string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
98
vendor/github.com/status-im/status-go/services/ext/requests.go
generated
vendored
Normal file
98
vendor/github.com/status-im/status-go/services/ext/requests.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultRequestsDelay will be used in RequestsRegistry if no other was provided.
|
||||
DefaultRequestsDelay = 3 * time.Second
|
||||
)
|
||||
|
||||
type requestMeta struct {
|
||||
timestamp time.Time
|
||||
lastUID types.Hash
|
||||
}
|
||||
|
||||
// NewRequestsRegistry creates instance of the RequestsRegistry and returns pointer to it.
|
||||
func NewRequestsRegistry(delay time.Duration) *RequestsRegistry {
|
||||
r := &RequestsRegistry{
|
||||
delay: delay,
|
||||
}
|
||||
r.Clear()
|
||||
return r
|
||||
}
|
||||
|
||||
// RequestsRegistry keeps map for all requests with timestamp when they were made.
|
||||
type RequestsRegistry struct {
|
||||
mu sync.Mutex
|
||||
delay time.Duration
|
||||
uidToTopics map[types.Hash]types.Hash
|
||||
byTopicsHash map[types.Hash]requestMeta
|
||||
}
|
||||
|
||||
// Register request with given topics. If request with same topics was made in less then configured delay then error
|
||||
// will be returned.
|
||||
func (r *RequestsRegistry) Register(uid types.Hash, topics []types.TopicType) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
topicsHash := topicsToHash(topics)
|
||||
if meta, exist := r.byTopicsHash[topicsHash]; exist {
|
||||
if time.Since(meta.timestamp) < r.delay {
|
||||
return fmt.Errorf("another request with the same topics was sent less than %s ago. Please wait for a bit longer, or set `force` to true in request parameters", r.delay)
|
||||
}
|
||||
}
|
||||
newMeta := requestMeta{
|
||||
timestamp: time.Now(),
|
||||
lastUID: uid,
|
||||
}
|
||||
r.uidToTopics[uid] = topicsHash
|
||||
r.byTopicsHash[topicsHash] = newMeta
|
||||
return nil
|
||||
}
|
||||
|
||||
// Has returns true if given uid is stored in registry.
|
||||
func (r *RequestsRegistry) Has(uid types.Hash) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
_, exist := r.uidToTopics[uid]
|
||||
return exist
|
||||
}
|
||||
|
||||
// Unregister removes request with given UID from registry.
|
||||
func (r *RequestsRegistry) Unregister(uid types.Hash) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
topicsHash, exist := r.uidToTopics[uid]
|
||||
if !exist {
|
||||
return
|
||||
}
|
||||
delete(r.uidToTopics, uid)
|
||||
meta := r.byTopicsHash[topicsHash]
|
||||
// remove topicsHash only if we are trying to unregister last request with this topic.
|
||||
if meta.lastUID == uid {
|
||||
delete(r.byTopicsHash, topicsHash)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear recreates all structures used for caching requests.
|
||||
func (r *RequestsRegistry) Clear() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.uidToTopics = map[types.Hash]types.Hash{}
|
||||
r.byTopicsHash = map[types.Hash]requestMeta{}
|
||||
}
|
||||
|
||||
// topicsToHash returns non-cryptographic hash of the topics.
|
||||
func topicsToHash(topics []types.TopicType) types.Hash {
|
||||
hash := fnv.New32()
|
||||
for i := range topics {
|
||||
_, _ = hash.Write(topics[i][:]) // never returns error per documentation
|
||||
}
|
||||
return types.BytesToHash(hash.Sum(nil))
|
||||
}
|
||||
67
vendor/github.com/status-im/status-go/services/ext/rpc.go
generated
vendored
Normal file
67
vendor/github.com/status-im/status-go/services/ext/rpc.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// TODO: These types should be defined using protobuf, but protoc can only emit []byte instead of types.HexBytes,
|
||||
// which causes issues when marshaling to JSON on the react side. Let's do that once the chat protocol is moved to the go repo.
|
||||
|
||||
package ext
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
)
|
||||
|
||||
// SendPublicMessageRPC represents the RPC payload for the SendPublicMessage RPC method
|
||||
type SendPublicMessageRPC struct {
|
||||
Sig string // TODO: remove
|
||||
Chat string
|
||||
Payload types.HexBytes
|
||||
}
|
||||
|
||||
// TODO: implement with accordance to https://github.com/status-im/status-go/protocol/issues/28.
|
||||
func (m SendPublicMessageRPC) ID() string { return m.Chat }
|
||||
|
||||
func (m SendPublicMessageRPC) PublicName() string { return m.Chat }
|
||||
|
||||
func (m SendPublicMessageRPC) PublicKey() *ecdsa.PublicKey { return nil }
|
||||
|
||||
// SendDirectMessageRPC represents the RPC payload for the SendDirectMessage RPC method
|
||||
type SendDirectMessageRPC struct {
|
||||
Sig string // TODO: remove
|
||||
Chat string
|
||||
Payload types.HexBytes
|
||||
PubKey types.HexBytes
|
||||
DH bool // TODO: make sure to remove safely
|
||||
}
|
||||
|
||||
// TODO: implement with accordance to https://github.com/status-im/status-go/protocol/issues/28.
|
||||
func (m SendDirectMessageRPC) ID() string { return "" }
|
||||
|
||||
func (m SendDirectMessageRPC) PublicName() string { return "" }
|
||||
|
||||
func (m SendDirectMessageRPC) PublicKey() *ecdsa.PublicKey {
|
||||
publicKey, _ := crypto.UnmarshalPubkey(m.PubKey)
|
||||
return publicKey
|
||||
}
|
||||
|
||||
type JoinRPC struct {
|
||||
Chat string
|
||||
PubKey types.HexBytes
|
||||
Payload types.HexBytes
|
||||
}
|
||||
|
||||
func (m JoinRPC) ID() string { return m.Chat }
|
||||
|
||||
func (m JoinRPC) PublicName() string {
|
||||
if len(m.PubKey) > 0 {
|
||||
return ""
|
||||
}
|
||||
return m.Chat
|
||||
}
|
||||
|
||||
func (m JoinRPC) PublicKey() *ecdsa.PublicKey {
|
||||
if len(m.PubKey) > 0 {
|
||||
return nil
|
||||
}
|
||||
publicKey, _ := crypto.UnmarshalPubkey(m.PubKey)
|
||||
return publicKey
|
||||
}
|
||||
828
vendor/github.com/status-im/status-go/services/ext/service.go
generated
vendored
Normal file
828
vendor/github.com/status-im/status-go/services/ext/service.go
generated
vendored
Normal file
@@ -0,0 +1,828 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"go.uber.org/zap"
|
||||
|
||||
commongethtypes "github.com/ethereum/go-ethereum/common"
|
||||
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/api/multiformat"
|
||||
"github.com/status-im/status-go/connection"
|
||||
"github.com/status-im/status-go/db"
|
||||
coretypes "github.com/status-im/status-go/eth-node/core/types"
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/images"
|
||||
"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"
|
||||
"github.com/status-im/status-go/protocol/anonmetrics"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/common/shard"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/communities/token"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/pushnotificationclient"
|
||||
"github.com/status-im/status-go/protocol/pushnotificationserver"
|
||||
"github.com/status-im/status-go/protocol/transport"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/server"
|
||||
"github.com/status-im/status-go/services/browsers"
|
||||
"github.com/status-im/status-go/services/communitytokens"
|
||||
"github.com/status-im/status-go/services/ext/mailservers"
|
||||
mailserversDB "github.com/status-im/status-go/services/mailservers"
|
||||
"github.com/status-im/status-go/services/wallet"
|
||||
w_common "github.com/status-im/status-go/services/wallet/common"
|
||||
"github.com/status-im/status-go/services/wallet/thirdparty"
|
||||
"github.com/status-im/status-go/wakuv2"
|
||||
)
|
||||
|
||||
const infinityString = "∞"
|
||||
const providerID = "community"
|
||||
|
||||
// EnvelopeEventsHandler used for two different event types.
|
||||
type EnvelopeEventsHandler interface {
|
||||
EnvelopeSent([][]byte)
|
||||
EnvelopeExpired([][]byte, error)
|
||||
MailServerRequestCompleted(types.Hash, types.Hash, []byte, error)
|
||||
MailServerRequestExpired(types.Hash)
|
||||
}
|
||||
|
||||
// Service is a service that provides some additional API to whisper-based protocols like Whisper or Waku.
|
||||
type Service struct {
|
||||
messenger *protocol.Messenger
|
||||
identity *ecdsa.PrivateKey
|
||||
cancelMessenger chan struct{}
|
||||
storage db.TransactionalStorage
|
||||
n types.Node
|
||||
rpcClient *rpc.Client
|
||||
config params.NodeConfig
|
||||
mailMonitor *MailRequestMonitor
|
||||
server *p2p.Server
|
||||
peerStore *mailservers.PeerStore
|
||||
accountsDB *accounts.Database
|
||||
multiAccountsDB *multiaccounts.Database
|
||||
account *multiaccounts.Account
|
||||
}
|
||||
|
||||
// Make sure that Service implements node.Service interface.
|
||||
var _ node.Lifecycle = (*Service)(nil)
|
||||
|
||||
func New(
|
||||
config params.NodeConfig,
|
||||
n types.Node,
|
||||
rpcClient *rpc.Client,
|
||||
ldb *leveldb.DB,
|
||||
mailMonitor *MailRequestMonitor,
|
||||
eventSub mailservers.EnvelopeEventSubscriber,
|
||||
) *Service {
|
||||
cache := mailservers.NewCache(ldb)
|
||||
peerStore := mailservers.NewPeerStore(cache)
|
||||
return &Service{
|
||||
storage: db.NewLevelDBStorage(ldb),
|
||||
n: n,
|
||||
rpcClient: rpcClient,
|
||||
config: config,
|
||||
mailMonitor: mailMonitor,
|
||||
peerStore: peerStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) NodeID() *ecdsa.PrivateKey {
|
||||
if s.server == nil {
|
||||
return nil
|
||||
}
|
||||
return s.server.PrivateKey
|
||||
}
|
||||
|
||||
func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
|
||||
if len(rawURL) == 0 {
|
||||
return mailservers.GetFirstConnected(s.server, s.peerStore)
|
||||
}
|
||||
return enode.ParseV4(rawURL)
|
||||
}
|
||||
|
||||
func (s *Service) InitProtocol(nodeName string, identity *ecdsa.PrivateKey, appDb, walletDb *sql.DB, httpServer *server.MediaServer, multiAccountDb *multiaccounts.Database, acc *multiaccounts.Account, accountManager *account.GethManager, rpcClient *rpc.Client, walletService *wallet.Service, communityTokensService *communitytokens.Service, wakuService *wakuv2.Waku, logger *zap.Logger) error {
|
||||
var err error
|
||||
if !s.config.ShhextConfig.PFSEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If Messenger has been already set up, we need to shut it down
|
||||
// before we init it again. Otherwise, it will lead to goroutines leakage
|
||||
// due to not stopped filters.
|
||||
if s.messenger != nil {
|
||||
if err := s.messenger.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.identity = identity
|
||||
|
||||
dataDir := filepath.Clean(s.config.ShhextConfig.BackupDisabledDataDir)
|
||||
|
||||
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envelopesMonitorConfig := &transport.EnvelopesMonitorConfig{
|
||||
MaxAttempts: s.config.ShhextConfig.MaxMessageDeliveryAttempts,
|
||||
AwaitOnlyMailServerConfirmations: s.config.ShhextConfig.MailServerConfirmations,
|
||||
IsMailserver: func(peer types.EnodeID) bool {
|
||||
return s.peerStore.Exist(peer)
|
||||
},
|
||||
EnvelopeEventsHandler: EnvelopeSignalHandler{},
|
||||
Logger: logger,
|
||||
}
|
||||
s.accountsDB, err = accounts.NewDB(appDb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.multiAccountsDB = multiAccountDb
|
||||
s.account = acc
|
||||
|
||||
options, err := buildMessengerOptions(s.config, identity, appDb, walletDb, httpServer, s.rpcClient, s.multiAccountsDB, acc, envelopesMonitorConfig, s.accountsDB, walletService, communityTokensService, wakuService, logger, &MessengerSignalsHandler{}, accountManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
messenger, err := protocol.NewMessenger(
|
||||
nodeName,
|
||||
identity,
|
||||
s.n,
|
||||
s.config.ShhextConfig.InstallationID,
|
||||
s.peerStore,
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.messenger = messenger
|
||||
s.messenger.SetP2PServer(s.server)
|
||||
if s.config.ProcessBackedupMessages {
|
||||
s.messenger.EnableBackedupMessagesProcessing()
|
||||
}
|
||||
return messenger.Init()
|
||||
}
|
||||
|
||||
func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) {
|
||||
// Start a loop that retrieves all messages and propagates them to status-mobile.
|
||||
s.cancelMessenger = make(chan struct{})
|
||||
response, err := s.messenger.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.messenger.StartRetrieveMessagesLoop(time.Second, s.cancelMessenger)
|
||||
go s.verifyTransactionLoop(30*time.Second, s.cancelMessenger)
|
||||
|
||||
if s.config.ShhextConfig.BandwidthStatsEnabled {
|
||||
go s.retrieveStats(5*time.Second, s.cancelMessenger)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *Service) retrieveStats(tick time.Duration, cancel <-chan struct{}) {
|
||||
ticker := time.NewTicker(tick)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
response := s.messenger.GetStats()
|
||||
PublisherSignalHandler{}.Stats(response)
|
||||
case <-cancel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type verifyTransactionClient struct {
|
||||
chainID *big.Int
|
||||
url string
|
||||
}
|
||||
|
||||
func (c *verifyTransactionClient) TransactionByHash(ctx context.Context, hash types.Hash) (coretypes.Message, coretypes.TransactionStatus, error) {
|
||||
signer := gethtypes.NewLondonSigner(c.chainID)
|
||||
client, err := ethclient.Dial(c.url)
|
||||
if err != nil {
|
||||
return coretypes.Message{}, coretypes.TransactionStatusPending, err
|
||||
}
|
||||
|
||||
transaction, pending, err := client.TransactionByHash(ctx, commongethtypes.BytesToHash(hash.Bytes()))
|
||||
if err != nil {
|
||||
return coretypes.Message{}, coretypes.TransactionStatusPending, err
|
||||
}
|
||||
|
||||
message, err := transaction.AsMessage(signer, nil)
|
||||
if err != nil {
|
||||
return coretypes.Message{}, coretypes.TransactionStatusPending, err
|
||||
}
|
||||
from := types.BytesToAddress(message.From().Bytes())
|
||||
to := types.BytesToAddress(message.To().Bytes())
|
||||
|
||||
if pending {
|
||||
return coretypes.NewMessage(
|
||||
from,
|
||||
&to,
|
||||
message.Nonce(),
|
||||
message.Value(),
|
||||
message.Gas(),
|
||||
message.GasPrice(),
|
||||
message.Data(),
|
||||
message.CheckNonce(),
|
||||
), coretypes.TransactionStatusPending, nil
|
||||
}
|
||||
|
||||
receipt, err := client.TransactionReceipt(ctx, commongethtypes.BytesToHash(hash.Bytes()))
|
||||
if err != nil {
|
||||
return coretypes.Message{}, coretypes.TransactionStatusPending, err
|
||||
}
|
||||
|
||||
coremessage := coretypes.NewMessage(
|
||||
from,
|
||||
&to,
|
||||
message.Nonce(),
|
||||
message.Value(),
|
||||
message.Gas(),
|
||||
message.GasPrice(),
|
||||
message.Data(),
|
||||
message.CheckNonce(),
|
||||
)
|
||||
|
||||
// Token transfer, check the logs
|
||||
if len(coremessage.Data()) != 0 {
|
||||
if w_common.IsTokenTransfer(receipt.Logs) {
|
||||
return coremessage, coretypes.TransactionStatus(receipt.Status), nil
|
||||
}
|
||||
return coremessage, coretypes.TransactionStatusFailed, nil
|
||||
}
|
||||
|
||||
return coremessage, coretypes.TransactionStatus(receipt.Status), nil
|
||||
}
|
||||
|
||||
func (s *Service) verifyTransactionLoop(tick time.Duration, cancel <-chan struct{}) {
|
||||
if s.config.ShhextConfig.VerifyTransactionURL == "" {
|
||||
log.Warn("not starting transaction loop")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(tick)
|
||||
defer ticker.Stop()
|
||||
|
||||
ctx, cancelVerifyTransaction := context.WithCancel(context.Background())
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
accounts, err := s.accountsDB.GetActiveAccounts()
|
||||
if err != nil {
|
||||
log.Error("failed to retrieve accounts", "err", err)
|
||||
}
|
||||
var wallets []types.Address
|
||||
for _, account := range accounts {
|
||||
if account.IsWalletNonWatchOnlyAccount() {
|
||||
wallets = append(wallets, types.BytesToAddress(account.Address.Bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.messenger.ValidateTransactions(ctx, wallets)
|
||||
if err != nil {
|
||||
log.Error("failed to validate transactions", "err", err)
|
||||
continue
|
||||
}
|
||||
s.messenger.PublishMessengerResponse(response)
|
||||
|
||||
case <-cancel:
|
||||
cancelVerifyTransaction()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) EnableInstallation(installationID string) error {
|
||||
return s.messenger.EnableInstallation(installationID)
|
||||
}
|
||||
|
||||
// DisableInstallation disables an installation for multi-device sync.
|
||||
func (s *Service) DisableInstallation(installationID string) error {
|
||||
return s.messenger.DisableInstallation(installationID)
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []gethrpc.API {
|
||||
panic("this is abstract service, use shhext or wakuext implementation")
|
||||
}
|
||||
|
||||
func (s *Service) SetP2PServer(server *p2p.Server) {
|
||||
s.server = server
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
log.Info("Stopping shhext service")
|
||||
if s.cancelMessenger != nil {
|
||||
select {
|
||||
case <-s.cancelMessenger:
|
||||
// channel already closed
|
||||
default:
|
||||
close(s.cancelMessenger)
|
||||
s.cancelMessenger = nil
|
||||
}
|
||||
}
|
||||
|
||||
if s.messenger != nil {
|
||||
if err := s.messenger.Shutdown(); err != nil {
|
||||
log.Error("failed to stop messenger", "err", err)
|
||||
return err
|
||||
}
|
||||
s.messenger = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildMessengerOptions(
|
||||
config params.NodeConfig,
|
||||
identity *ecdsa.PrivateKey,
|
||||
appDb *sql.DB,
|
||||
walletDb *sql.DB,
|
||||
httpServer *server.MediaServer,
|
||||
rpcClient *rpc.Client,
|
||||
multiAccounts *multiaccounts.Database,
|
||||
account *multiaccounts.Account,
|
||||
envelopesMonitorConfig *transport.EnvelopesMonitorConfig,
|
||||
accountsDB *accounts.Database,
|
||||
walletService *wallet.Service,
|
||||
communityTokensService *communitytokens.Service,
|
||||
wakuService *wakuv2.Waku,
|
||||
logger *zap.Logger,
|
||||
messengerSignalsHandler protocol.MessengerSignalsHandler,
|
||||
accountManager account.Manager,
|
||||
) ([]protocol.Option, error) {
|
||||
options := []protocol.Option{
|
||||
protocol.WithCustomLogger(logger),
|
||||
protocol.WithPushNotifications(),
|
||||
protocol.WithDatabase(appDb),
|
||||
protocol.WithWalletDatabase(walletDb),
|
||||
protocol.WithMultiAccounts(multiAccounts),
|
||||
protocol.WithMailserversDatabase(mailserversDB.NewDB(appDb)),
|
||||
protocol.WithAccount(account),
|
||||
protocol.WithBrowserDatabase(browsers.NewDB(appDb)),
|
||||
protocol.WithEnvelopesMonitorConfig(envelopesMonitorConfig),
|
||||
protocol.WithSignalsHandler(messengerSignalsHandler),
|
||||
protocol.WithENSVerificationConfig(config.ShhextConfig.VerifyENSURL, config.ShhextConfig.VerifyENSContractAddress),
|
||||
protocol.WithClusterConfig(config.ClusterConfig),
|
||||
protocol.WithTorrentConfig(&config.TorrentConfig),
|
||||
protocol.WithHTTPServer(httpServer),
|
||||
protocol.WithRPCClient(rpcClient),
|
||||
protocol.WithMessageCSV(config.OutputMessageCSVEnabled),
|
||||
protocol.WithWalletConfig(&config.WalletConfig),
|
||||
protocol.WithWalletService(walletService),
|
||||
protocol.WithCommunityTokensService(communityTokensService),
|
||||
protocol.WithWakuService(wakuService),
|
||||
protocol.WithAccountManager(accountManager),
|
||||
}
|
||||
|
||||
if config.ShhextConfig.DataSyncEnabled {
|
||||
options = append(options, protocol.WithDatasync())
|
||||
}
|
||||
|
||||
settings, err := accountsDB.GetSettings()
|
||||
if err != sql.ErrNoRows && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate anon metrics client config
|
||||
if settings.AnonMetricsShouldSend {
|
||||
keyBytes, err := hex.DecodeString(config.ShhextConfig.AnonMetricsSendID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := crypto.UnmarshalPubkey(keyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amcc := &anonmetrics.ClientConfig{
|
||||
ShouldSend: true,
|
||||
SendAddress: key,
|
||||
}
|
||||
options = append(options, protocol.WithAnonMetricsClientConfig(amcc))
|
||||
}
|
||||
|
||||
// Generate anon metrics server config
|
||||
if config.ShhextConfig.AnonMetricsServerEnabled {
|
||||
if len(config.ShhextConfig.AnonMetricsServerPostgresURI) == 0 {
|
||||
return nil, errors.New("AnonMetricsServerPostgresURI must be set")
|
||||
}
|
||||
|
||||
amsc := &anonmetrics.ServerConfig{
|
||||
Enabled: true,
|
||||
PostgresURI: config.ShhextConfig.AnonMetricsServerPostgresURI,
|
||||
}
|
||||
options = append(options, protocol.WithAnonMetricsServerConfig(amsc))
|
||||
}
|
||||
|
||||
if settings.TelemetryServerURL != "" {
|
||||
options = append(options, protocol.WithTelemetry(settings.TelemetryServerURL))
|
||||
}
|
||||
|
||||
if settings.PushNotificationsServerEnabled {
|
||||
config := &pushnotificationserver.Config{
|
||||
Enabled: true,
|
||||
Logger: logger,
|
||||
}
|
||||
options = append(options, protocol.WithPushNotificationServerConfig(config))
|
||||
}
|
||||
|
||||
var pushNotifServKey []*ecdsa.PublicKey
|
||||
for _, d := range config.ShhextConfig.DefaultPushNotificationsServers {
|
||||
pushNotifServKey = append(pushNotifServKey, d.PublicKey)
|
||||
}
|
||||
|
||||
options = append(options, protocol.WithPushNotificationClientConfig(&pushnotificationclient.Config{
|
||||
DefaultServers: pushNotifServKey,
|
||||
BlockMentions: settings.PushNotificationsBlockMentions,
|
||||
SendEnabled: settings.SendPushNotifications,
|
||||
AllowFromContactsOnly: settings.PushNotificationsFromContactsOnly,
|
||||
RemoteNotificationsEnabled: settings.RemotePushNotificationsEnabled,
|
||||
}))
|
||||
|
||||
if config.ShhextConfig.VerifyTransactionURL != "" {
|
||||
client := &verifyTransactionClient{
|
||||
url: config.ShhextConfig.VerifyTransactionURL,
|
||||
chainID: big.NewInt(config.ShhextConfig.VerifyTransactionChainID),
|
||||
}
|
||||
options = append(options, protocol.WithVerifyTransactionClient(client))
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (s *Service) ConnectionChanged(state connection.State) {
|
||||
if s.messenger != nil {
|
||||
s.messenger.ConnectionChanged(state)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Messenger() *protocol.Messenger {
|
||||
return s.messenger
|
||||
}
|
||||
|
||||
func tokenURIToCommunityID(tokenURI string) string {
|
||||
tmpStr := strings.Split(tokenURI, "/")
|
||||
|
||||
// Community NFTs have a tokenURI of the form "compressedCommunityID/tokenID"
|
||||
if len(tmpStr) != 2 {
|
||||
return ""
|
||||
}
|
||||
compressedCommunityID := tmpStr[0]
|
||||
|
||||
hexCommunityID, err := multiformat.DeserializeCompressedKey(compressedCommunityID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
pubKey, err := common.HexToPubkey(hexCommunityID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
communityID := types.EncodeHex(crypto.CompressPubkey(pubKey))
|
||||
|
||||
return communityID
|
||||
}
|
||||
|
||||
func (s *Service) GetCommunityID(tokenURI string) string {
|
||||
if tokenURI != "" {
|
||||
return tokenURIToCommunityID(tokenURI)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Service) FillCollectibleMetadata(collectible *thirdparty.FullCollectibleData) error {
|
||||
if s.messenger == nil {
|
||||
return fmt.Errorf("messenger not ready")
|
||||
}
|
||||
|
||||
if collectible == nil {
|
||||
return fmt.Errorf("empty collectible")
|
||||
}
|
||||
|
||||
id := collectible.CollectibleData.ID
|
||||
communityID := collectible.CollectibleData.CommunityID
|
||||
|
||||
if communityID == "" {
|
||||
return fmt.Errorf("invalid communityID")
|
||||
}
|
||||
|
||||
// FetchCommunityInfo should have been previously called once to ensure
|
||||
// that the latest version of the CommunityDescription is available in the DB
|
||||
community, err := s.fetchCommunity(communityID, false)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if community == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
tokenMetadata, err := s.fetchCommunityCollectibleMetadata(community, id.ContractID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tokenMetadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
communityToken, err := s.fetchCommunityToken(communityID, id.ContractID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permission := fetchCommunityCollectiblePermission(community, id)
|
||||
|
||||
privilegesLevel := token.CommunityLevel
|
||||
if permission != nil {
|
||||
privilegesLevel = permissionTypeToPrivilegesLevel(permission.GetType())
|
||||
}
|
||||
|
||||
imagePayload, _ := images.GetPayloadFromURI(tokenMetadata.GetImage())
|
||||
|
||||
collectible.CollectibleData.ContractType = w_common.ContractTypeERC721
|
||||
collectible.CollectibleData.Provider = providerID
|
||||
collectible.CollectibleData.Name = tokenMetadata.GetName()
|
||||
collectible.CollectibleData.Description = tokenMetadata.GetDescription()
|
||||
collectible.CollectibleData.ImagePayload = imagePayload
|
||||
collectible.CollectibleData.Traits = getCollectibleCommunityTraits(communityToken)
|
||||
|
||||
if collectible.CollectionData == nil {
|
||||
collectible.CollectionData = &thirdparty.CollectionData{
|
||||
ID: id.ContractID,
|
||||
CommunityID: communityID,
|
||||
}
|
||||
}
|
||||
collectible.CollectionData.ContractType = w_common.ContractTypeERC721
|
||||
collectible.CollectionData.Provider = providerID
|
||||
collectible.CollectionData.Name = tokenMetadata.GetName()
|
||||
collectible.CollectionData.ImagePayload = imagePayload
|
||||
|
||||
collectible.CommunityInfo = communityToInfo(community)
|
||||
|
||||
collectible.CollectibleCommunityInfo = &thirdparty.CollectibleCommunityInfo{
|
||||
PrivilegesLevel: privilegesLevel,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func permissionTypeToPrivilegesLevel(permissionType protobuf.CommunityTokenPermission_Type) token.PrivilegesLevel {
|
||||
switch permissionType {
|
||||
case protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER:
|
||||
return token.OwnerLevel
|
||||
case protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER:
|
||||
return token.MasterLevel
|
||||
default:
|
||||
return token.CommunityLevel
|
||||
}
|
||||
}
|
||||
|
||||
func communityToInfo(community *communities.Community) *thirdparty.CommunityInfo {
|
||||
if community == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &thirdparty.CommunityInfo{
|
||||
CommunityName: community.Name(),
|
||||
CommunityColor: community.Color(),
|
||||
CommunityImagePayload: fetchCommunityImage(community),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) FetchCommunityInfo(communityID string) (*thirdparty.CommunityInfo, error) {
|
||||
community, err := s.fetchCommunity(communityID, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return communityToInfo(community), nil
|
||||
}
|
||||
|
||||
func (s *Service) fetchCommunity(communityID string, fetchLatest bool) (*communities.Community, error) {
|
||||
if s.messenger == nil {
|
||||
return nil, fmt.Errorf("messenger not ready")
|
||||
}
|
||||
|
||||
// Try to fetch metadata from Messenger communities
|
||||
|
||||
// TODO: we need the shard information in the collectible to be able to retrieve info for
|
||||
// communities that have specific shards
|
||||
|
||||
if fetchLatest {
|
||||
// Try to fetch the latest version of the Community
|
||||
var shard *shard.Shard = nil // TODO: build this with info from token
|
||||
// NOTE: The community returned by this function will be nil if
|
||||
// the version we have in the DB is the latest available.
|
||||
_, err := s.messenger.FetchCommunity(&protocol.FetchCommunityRequest{
|
||||
CommunityKey: communityID,
|
||||
Shard: shard,
|
||||
TryDatabase: false,
|
||||
WaitForResponse: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the latest successfully fetched version of the Community
|
||||
community, err := s.messenger.FindCommunityInfoFromDB(communityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return community, nil
|
||||
}
|
||||
|
||||
func (s *Service) fetchCommunityToken(communityID string, contractID thirdparty.ContractID) (*token.CommunityToken, error) {
|
||||
if s.messenger == nil {
|
||||
return nil, fmt.Errorf("messenger not ready")
|
||||
}
|
||||
|
||||
return s.messenger.GetCommunityToken(communityID, int(contractID.ChainID), contractID.Address.String())
|
||||
}
|
||||
|
||||
func (s *Service) fetchCommunityCollectibleMetadata(community *communities.Community, contractID thirdparty.ContractID) (*protobuf.CommunityTokenMetadata, error) {
|
||||
tokensMetadata := community.CommunityTokensMetadata()
|
||||
|
||||
for _, tokenMetadata := range tokensMetadata {
|
||||
contractAddresses := tokenMetadata.GetContractAddresses()
|
||||
if contractAddresses[uint64(contractID.ChainID)] == contractID.Address.Hex() {
|
||||
return tokenMetadata, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func tokenCriterionContainsCollectible(tokenCriterion *protobuf.TokenCriteria, id thirdparty.CollectibleUniqueID) bool {
|
||||
// Check if token type matches
|
||||
if tokenCriterion.Type != protobuf.CommunityTokenType_ERC721 {
|
||||
return false
|
||||
}
|
||||
|
||||
for chainID, contractAddressStr := range tokenCriterion.ContractAddresses {
|
||||
if chainID != uint64(id.ContractID.ChainID) {
|
||||
continue
|
||||
}
|
||||
|
||||
contractAddress := commongethtypes.HexToAddress(contractAddressStr)
|
||||
if contractAddress != id.ContractID.Address {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(tokenCriterion.TokenIds) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, tokenID := range tokenCriterion.TokenIds {
|
||||
tokenIDBigInt := new(big.Int).SetUint64(tokenID)
|
||||
if id.TokenID.Cmp(tokenIDBigInt) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func permissionContainsCollectible(permission *communities.CommunityTokenPermission, id thirdparty.CollectibleUniqueID) bool {
|
||||
// See if any token criterion contains the collectible we're looking for
|
||||
for _, tokenCriterion := range permission.TokenCriteria {
|
||||
if tokenCriterionContainsCollectible(tokenCriterion, id) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fetchCommunityCollectiblePermission(community *communities.Community, id thirdparty.CollectibleUniqueID) *communities.CommunityTokenPermission {
|
||||
// Permnission types of interest
|
||||
permissionTypes := []protobuf.CommunityTokenPermission_Type{
|
||||
protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER,
|
||||
protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER,
|
||||
}
|
||||
|
||||
for _, permissionType := range permissionTypes {
|
||||
permissions := community.TokenPermissionsByType(permissionType)
|
||||
// See if any community permission matches the type we're looking for
|
||||
for _, permission := range permissions {
|
||||
if permissionContainsCollectible(permission, id) {
|
||||
return permission
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchCommunityImage(community *communities.Community) []byte {
|
||||
imageTypes := []string{
|
||||
images.LargeDimName,
|
||||
images.SmallDimName,
|
||||
}
|
||||
|
||||
communityImages := community.Images()
|
||||
|
||||
for _, imageType := range imageTypes {
|
||||
if pbImage, ok := communityImages[imageType]; ok {
|
||||
return pbImage.Payload
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func boolToString(value bool) string {
|
||||
if value {
|
||||
return "Yes"
|
||||
}
|
||||
return "No"
|
||||
}
|
||||
|
||||
func getCollectibleCommunityTraits(token *token.CommunityToken) []thirdparty.CollectibleTrait {
|
||||
if token == nil {
|
||||
return make([]thirdparty.CollectibleTrait, 0)
|
||||
}
|
||||
|
||||
totalStr := infinityString
|
||||
availableStr := infinityString
|
||||
if !token.InfiniteSupply {
|
||||
totalStr = token.Supply.String()
|
||||
// TODO: calculate available supply. See services/communitytokens/api.go
|
||||
availableStr = totalStr
|
||||
}
|
||||
|
||||
transferableStr := boolToString(token.Transferable)
|
||||
|
||||
destructibleStr := boolToString(token.RemoteSelfDestruct)
|
||||
|
||||
return []thirdparty.CollectibleTrait{
|
||||
{
|
||||
TraitType: "Symbol",
|
||||
Value: token.Symbol,
|
||||
},
|
||||
{
|
||||
TraitType: "Total",
|
||||
Value: totalStr,
|
||||
},
|
||||
{
|
||||
TraitType: "Available",
|
||||
Value: availableStr,
|
||||
},
|
||||
{
|
||||
TraitType: "Transferable",
|
||||
Value: transferableStr,
|
||||
},
|
||||
{
|
||||
TraitType: "Destructible",
|
||||
Value: destructibleStr,
|
||||
},
|
||||
}
|
||||
}
|
||||
186
vendor/github.com/status-im/status-go/services/ext/signal.go
generated
vendored
Normal file
186
vendor/github.com/status-im/status-go/services/ext/signal.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/protocol/communities"
|
||||
"github.com/status-im/status-go/protocol/discord"
|
||||
"github.com/status-im/status-go/protocol/wakusync"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
// EnvelopeSignalHandler sends signals when envelope is sent or expired.
|
||||
type EnvelopeSignalHandler struct{}
|
||||
|
||||
// EnvelopeSent triggered when envelope delivered atleast to 1 peer.
|
||||
func (h EnvelopeSignalHandler) EnvelopeSent(identifiers [][]byte) {
|
||||
signal.SendEnvelopeSent(identifiers)
|
||||
}
|
||||
|
||||
// EnvelopeExpired triggered when envelope is expired but wasn't delivered to any peer.
|
||||
func (h EnvelopeSignalHandler) EnvelopeExpired(identifiers [][]byte, err error) {
|
||||
signal.SendEnvelopeExpired(identifiers, err)
|
||||
}
|
||||
|
||||
// MailServerRequestCompleted triggered when the mailserver sends a message to notify that the request has been completed
|
||||
func (h EnvelopeSignalHandler) MailServerRequestCompleted(requestID types.Hash, lastEnvelopeHash types.Hash, cursor []byte, err error) {
|
||||
signal.SendMailServerRequestCompleted(requestID, lastEnvelopeHash, cursor, err)
|
||||
}
|
||||
|
||||
// MailServerRequestExpired triggered when the mailserver request expires
|
||||
func (h EnvelopeSignalHandler) MailServerRequestExpired(hash types.Hash) {
|
||||
signal.SendMailServerRequestExpired(hash)
|
||||
}
|
||||
|
||||
// PublisherSignalHandler sends signals on protocol events
|
||||
type PublisherSignalHandler struct{}
|
||||
|
||||
func (h PublisherSignalHandler) DecryptMessageFailed(pubKey string) {
|
||||
signal.SendDecryptMessageFailed(pubKey)
|
||||
}
|
||||
|
||||
func (h PublisherSignalHandler) BundleAdded(identity string, installationID string) {
|
||||
signal.SendBundleAdded(identity, installationID)
|
||||
}
|
||||
|
||||
func (h PublisherSignalHandler) NewMessages(response *protocol.MessengerResponse) {
|
||||
signal.SendNewMessages(response)
|
||||
}
|
||||
|
||||
func (h PublisherSignalHandler) Stats(stats types.StatsSummary) {
|
||||
signal.SendStats(stats)
|
||||
}
|
||||
|
||||
// MessengerSignalHandler sends signals on messenger events
|
||||
type MessengerSignalsHandler struct{}
|
||||
|
||||
// MessageDelivered passes information that message was delivered
|
||||
func (m MessengerSignalsHandler) MessageDelivered(chatID string, messageID string) {
|
||||
signal.SendMessageDelivered(chatID, messageID)
|
||||
}
|
||||
|
||||
// BackupPerformed passes information that a backup was performed
|
||||
func (m MessengerSignalsHandler) BackupPerformed(lastBackup uint64) {
|
||||
signal.SendBackupPerformed(lastBackup)
|
||||
}
|
||||
|
||||
// MessageDelivered passes info about community that was requested before
|
||||
func (m MessengerSignalsHandler) CommunityInfoFound(community *communities.Community) {
|
||||
signal.SendCommunityInfoFound(community)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) MessengerResponse(response *protocol.MessengerResponse) {
|
||||
PublisherSignalHandler{}.NewMessages(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryRequestStarted(numBatches int) {
|
||||
signal.SendHistoricMessagesRequestStarted(numBatches)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryRequestCompleted() {
|
||||
signal.SendHistoricMessagesRequestCompleted()
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchivesProtocolEnabled() {
|
||||
signal.SendHistoryArchivesProtocolEnabled()
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchivesProtocolDisabled() {
|
||||
signal.SendHistoryArchivesProtocolDisabled()
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) CreatingHistoryArchives(communityID string) {
|
||||
signal.SendCreatingHistoryArchives(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) NoHistoryArchivesCreated(communityID string, from int, to int) {
|
||||
signal.SendNoHistoryArchivesCreated(communityID, from, to)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchivesCreated(communityID string, from int, to int) {
|
||||
signal.SendHistoryArchivesCreated(communityID, from, to)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchivesSeeding(communityID string) {
|
||||
signal.SendHistoryArchivesSeeding(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchivesUnseeded(communityID string) {
|
||||
signal.SendHistoryArchivesUnseeded(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) HistoryArchiveDownloaded(communityID string, from int, to int) {
|
||||
signal.SendHistoryArchiveDownloaded(communityID, from, to)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DownloadingHistoryArchivesStarted(communityID string) {
|
||||
signal.SendDownloadingHistoryArchivesStarted(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) ImportingHistoryArchiveMessages(communityID string) {
|
||||
signal.SendImportingHistoryArchiveMessages(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DownloadingHistoryArchivesFinished(communityID string) {
|
||||
signal.SendDownloadingHistoryArchivesFinished(communityID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) StatusUpdatesTimedOut(statusUpdates *[]protocol.UserStatus) {
|
||||
signal.SendStatusUpdatesTimedOut(statusUpdates)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordCategoriesAndChannelsExtracted(categories []*discord.Category, channels []*discord.Channel, oldestMessageTimestamp int64, errors map[string]*discord.ImportError) {
|
||||
signal.SendDiscordCategoriesAndChannelsExtracted(categories, channels, oldestMessageTimestamp, errors)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordCommunityImportProgress(importProgress *discord.ImportProgress) {
|
||||
signal.SendDiscordCommunityImportProgress(importProgress)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordChannelImportProgress(importProgress *discord.ImportProgress) {
|
||||
signal.SendDiscordChannelImportProgress(importProgress)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordCommunityImportFinished(id string) {
|
||||
signal.SendDiscordCommunityImportFinished(id)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordChannelImportFinished(communityID string, channelID string) {
|
||||
signal.SendDiscordChannelImportFinished(communityID, channelID)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordCommunityImportCancelled(id string) {
|
||||
signal.SendDiscordCommunityImportCancelled(id)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordCommunityImportCleanedUp(id string) {
|
||||
signal.SendDiscordCommunityImportCleanedUp(id)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) DiscordChannelImportCancelled(id string) {
|
||||
signal.SendDiscordChannelImportCancelled(id)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) {
|
||||
signal.SendWakuFetchingBackupProgress(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendWakuBackedUpProfile(response *wakusync.WakuBackedUpDataResponse) {
|
||||
signal.SendWakuBackedUpProfile(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendWakuBackedUpSettings(response *wakusync.WakuBackedUpDataResponse) {
|
||||
signal.SendWakuBackedUpSettings(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendWakuBackedUpKeypair(response *wakusync.WakuBackedUpDataResponse) {
|
||||
signal.SendWakuBackedUpKeypair(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendWakuBackedUpWatchOnlyAccount(response *wakusync.WakuBackedUpDataResponse) {
|
||||
signal.SendWakuBackedUpWatchOnlyAccount(response)
|
||||
}
|
||||
|
||||
func (m *MessengerSignalsHandler) SendCuratedCommunitiesUpdate(response *communities.KnownCommunitiesResponse) {
|
||||
signal.SendCuratedCommunitiesUpdate(response)
|
||||
}
|
||||
162
vendor/github.com/status-im/status-go/services/gif/gif.go
generated
vendored
Normal file
162
vendor/github.com/status-im/status-go/services/gif/gif.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package gif
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
)
|
||||
|
||||
type Gif struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
TinyURL string `json:"tinyUrl"`
|
||||
Height int `json:"height"`
|
||||
IsFavorite bool `json:"isFavorite"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Items []Gif `json:"items"`
|
||||
}
|
||||
|
||||
var tenorAPIKey = ""
|
||||
var defaultParams = "&media_filter=minimal&limit=50&key="
|
||||
|
||||
const maxRetry = 3
|
||||
const baseURL = "https://g.tenor.com/v1/"
|
||||
|
||||
func NewGifAPI(db *accounts.Database) *API {
|
||||
return &API{db}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
db *accounts.Database
|
||||
}
|
||||
|
||||
func (api *API) SetTenorAPIKey(key string) (err error) {
|
||||
log.Info("[GifAPI::SetTenorAPIKey]")
|
||||
err = api.db.SaveSettingField(settings.GifAPIKey, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenorAPIKey = key
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) GetContentWithRetry(path string) (value string, err error) {
|
||||
var currentRetry = 0
|
||||
var response *http.Response
|
||||
for currentRetry < maxRetry {
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
ResponseHeaderTimeout: time.Second * 1,
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Timeout: 1 * time.Second,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
response, err = client.Get(baseURL + path + defaultParams + tenorAPIKey)
|
||||
|
||||
if err != nil {
|
||||
log.Error("can't get content from path %s with %s", path, err.Error())
|
||||
currentRetry++
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
return "", fmt.Errorf("Could not reach Tenor API")
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("Status error: %v", response.StatusCode)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Read body: %v", err)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (api *API) FetchGifs(path string) (value string, err error) {
|
||||
log.Info("[GifAPI::fetchGifs]")
|
||||
return api.GetContentWithRetry(path)
|
||||
}
|
||||
|
||||
func (api *API) UpdateRecentGifs(updatedGifs json.RawMessage) (err error) {
|
||||
log.Info("[GifAPI::updateRecentGifs]")
|
||||
recentGifsContainer := Container{}
|
||||
err = json.Unmarshal(updatedGifs, &recentGifsContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = api.db.SaveSettingField(settings.GifRecents, recentGifsContainer.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) UpdateFavoriteGifs(updatedGifs json.RawMessage) (err error) {
|
||||
log.Info("[GifAPI::updateFavoriteGifs]", updatedGifs)
|
||||
favsGifsContainer := Container{}
|
||||
err = json.Unmarshal(updatedGifs, &favsGifsContainer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = api.db.SaveSettingField(settings.GifFavourites, favsGifsContainer.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) GetRecentGifs() (recentGifs []Gif, err error) {
|
||||
log.Info("[GifAPI::getRecentGifs]")
|
||||
gifs, err := api.db.GifRecents()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recentGifs = make([]Gif, 0)
|
||||
savedRecentGifs := []Gif{}
|
||||
if len(gifs) > 0 {
|
||||
err = json.Unmarshal(gifs, &savedRecentGifs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recentGifs = savedRecentGifs
|
||||
}
|
||||
return recentGifs, nil
|
||||
}
|
||||
|
||||
func (api *API) GetFavoriteGifs() (favoriteGifs []Gif, err error) {
|
||||
log.Info("[GifAPI::getFavoriteGifs]")
|
||||
gifs, err := api.db.GifFavorites()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
favoriteGifs = make([]Gif, 0)
|
||||
savedFavGifs := []Gif{}
|
||||
if len(gifs) > 0 {
|
||||
err = json.Unmarshal(gifs, &savedFavGifs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
favoriteGifs = savedFavGifs
|
||||
}
|
||||
return favoriteGifs, nil
|
||||
}
|
||||
45
vendor/github.com/status-im/status-go/services/gif/service.go
generated
vendored
Normal file
45
vendor/github.com/status-im/status-go/services/gif/service.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package gif
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
)
|
||||
|
||||
// Service represents out own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
accountsDB *accounts.Database
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func NewService(db *accounts.Database) *Service {
|
||||
return &Service{accountsDB: db}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "gif",
|
||||
Version: "0.1.0",
|
||||
Service: NewGifAPI(s.accountsDB),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
31
vendor/github.com/status-im/status-go/services/local-notifications/api.go
generated
vendored
Normal file
31
vendor/github.com/status-im/status-go/services/local-notifications/api.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package localnotifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
func NewAPI(s *Service) *API {
|
||||
return &API{s}
|
||||
}
|
||||
|
||||
type API struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
func (api *API) NotificationPreferences(ctx context.Context) ([]NotificationPreference, error) {
|
||||
return api.s.db.GetPreferences()
|
||||
}
|
||||
|
||||
func (api *API) SwitchWalletNotifications(ctx context.Context, preference bool) error {
|
||||
log.Debug("Switch Transaction Notification")
|
||||
err := api.s.db.ChangeWalletPreference(preference)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
api.s.WatchingEnabled = preference
|
||||
|
||||
return nil
|
||||
}
|
||||
233
vendor/github.com/status-im/status-go/services/local-notifications/core.go
generated
vendored
Normal file
233
vendor/github.com/status-im/status-go/services/local-notifications/core.go
generated
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
package localnotifications
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
"github.com/status-im/status-go/signal"
|
||||
)
|
||||
|
||||
type PushCategory string
|
||||
|
||||
type NotificationType string
|
||||
|
||||
type NotificationBody interface {
|
||||
json.Marshaler
|
||||
}
|
||||
|
||||
type Notification struct {
|
||||
ID common.Hash
|
||||
Platform float32
|
||||
Body NotificationBody
|
||||
BodyType NotificationType
|
||||
Title string
|
||||
Message string
|
||||
Category PushCategory
|
||||
Deeplink string
|
||||
Image string
|
||||
IsScheduled bool
|
||||
ScheduledTime string
|
||||
IsConversation bool
|
||||
IsGroupConversation bool
|
||||
ConversationID string
|
||||
Timestamp uint64
|
||||
Author NotificationAuthor
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
type NotificationAuthor struct {
|
||||
ID string `json:"id"`
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// notificationAlias is an interim struct used for json un/marshalling
|
||||
type notificationAlias struct {
|
||||
ID common.Hash `json:"id"`
|
||||
Platform float32 `json:"platform,omitempty"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
BodyType NotificationType `json:"bodyType"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Category PushCategory `json:"category,omitempty"`
|
||||
Deeplink string `json:"deepLink,omitempty"`
|
||||
Image string `json:"imageUrl,omitempty"`
|
||||
IsScheduled bool `json:"isScheduled,omitempty"`
|
||||
ScheduledTime string `json:"scheduleTime,omitempty"`
|
||||
IsConversation bool `json:"isConversation,omitempty"`
|
||||
IsGroupConversation bool `json:"isGroupConversation,omitempty"`
|
||||
ConversationID string `json:"conversationId,omitempty"`
|
||||
Timestamp uint64 `json:"timestamp,omitempty"`
|
||||
Author NotificationAuthor `json:"notificationAuthor,omitempty"`
|
||||
Deleted bool `json:"deleted,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEvent - structure used to pass messages from chat to bus
|
||||
type MessageEvent struct{}
|
||||
|
||||
// CustomEvent - structure used to pass custom user set messages to bus
|
||||
type CustomEvent struct{}
|
||||
|
||||
type transmitter struct {
|
||||
publisher *event.Feed
|
||||
|
||||
wg sync.WaitGroup
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// Service keeps the state of message bus
|
||||
type Service struct {
|
||||
started bool
|
||||
WatchingEnabled bool
|
||||
chainID uint64
|
||||
transmitter *transmitter
|
||||
walletTransmitter *transmitter
|
||||
db *Database
|
||||
walletDB *transfer.Database
|
||||
accountsDB *accounts.Database
|
||||
}
|
||||
|
||||
func NewService(appDB *sql.DB, walletDB *transfer.Database, chainID uint64) (*Service, error) {
|
||||
db := NewDB(appDB, chainID)
|
||||
accountsDB, err := accounts.NewDB(appDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trans := &transmitter{}
|
||||
walletTrans := &transmitter{}
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
chainID: chainID,
|
||||
walletDB: walletDB,
|
||||
accountsDB: accountsDB,
|
||||
transmitter: trans,
|
||||
walletTransmitter: walletTrans,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *Notification) MarshalJSON() ([]byte, error) {
|
||||
|
||||
var body json.RawMessage
|
||||
if n.Body != nil {
|
||||
encodedBody, err := n.Body.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = encodedBody
|
||||
}
|
||||
|
||||
alias := notificationAlias{
|
||||
ID: n.ID,
|
||||
Platform: n.Platform,
|
||||
Body: body,
|
||||
BodyType: n.BodyType,
|
||||
Category: n.Category,
|
||||
Title: n.Title,
|
||||
Message: n.Message,
|
||||
Deeplink: n.Deeplink,
|
||||
Image: n.Image,
|
||||
IsScheduled: n.IsScheduled,
|
||||
ScheduledTime: n.ScheduledTime,
|
||||
IsConversation: n.IsConversation,
|
||||
IsGroupConversation: n.IsGroupConversation,
|
||||
ConversationID: n.ConversationID,
|
||||
Timestamp: n.Timestamp,
|
||||
Author: n.Author,
|
||||
Deleted: n.Deleted,
|
||||
}
|
||||
|
||||
return json.Marshal(alias)
|
||||
}
|
||||
|
||||
func PushMessages(ns []*Notification) {
|
||||
for _, n := range ns {
|
||||
pushMessage(n)
|
||||
}
|
||||
}
|
||||
|
||||
func pushMessage(notification *Notification) {
|
||||
log.Debug("Pushing a new push notification")
|
||||
signal.SendLocalNotifications(notification)
|
||||
}
|
||||
|
||||
// Start Worker which processes all incoming messages
|
||||
func (s *Service) Start() error {
|
||||
s.started = true
|
||||
|
||||
s.transmitter.quit = make(chan struct{})
|
||||
s.transmitter.publisher = &event.Feed{}
|
||||
|
||||
events := make(chan TransactionEvent, 10)
|
||||
sub := s.transmitter.publisher.Subscribe(events)
|
||||
|
||||
s.transmitter.wg.Add(1)
|
||||
go func() {
|
||||
defer s.transmitter.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-s.transmitter.quit:
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
if err != nil {
|
||||
log.Error("Local notifications transmitter failed with", "error", err)
|
||||
}
|
||||
return
|
||||
case event := <-events:
|
||||
s.transactionsHandler(event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Successful start")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop worker
|
||||
func (s *Service) Stop() error {
|
||||
s.started = false
|
||||
|
||||
if s.transmitter.quit != nil {
|
||||
close(s.transmitter.quit)
|
||||
s.transmitter.wg.Wait()
|
||||
s.transmitter.quit = nil
|
||||
}
|
||||
|
||||
if s.walletTransmitter.quit != nil {
|
||||
close(s.walletTransmitter.quit)
|
||||
s.walletTransmitter.wg.Wait()
|
||||
s.walletTransmitter.quit = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// APIs returns list of available RPC APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "localnotifications",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) IsStarted() bool {
|
||||
return s.started
|
||||
}
|
||||
53
vendor/github.com/status-im/status-go/services/local-notifications/database.go
generated
vendored
Normal file
53
vendor/github.com/status-im/status-go/services/local-notifications/database.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package localnotifications
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
network uint64
|
||||
}
|
||||
|
||||
type NotificationPreference struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Service string `json:"service"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
}
|
||||
|
||||
func NewDB(db *sql.DB, network uint64) *Database {
|
||||
return &Database{db: db, network: network}
|
||||
}
|
||||
|
||||
func (db *Database) GetPreferences() (rst []NotificationPreference, err error) {
|
||||
rows, err := db.db.Query("SELECT service, event, identifier, enabled FROM local_notifications_preferences")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
pref := NotificationPreference{}
|
||||
err = rows.Scan(&pref.Service, &pref.Event, &pref.Identifier, &pref.Enabled)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rst = append(rst, pref)
|
||||
}
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetWalletPreference() (rst NotificationPreference, err error) {
|
||||
pref := db.db.QueryRow("SELECT service, event, identifier, enabled FROM local_notifications_preferences WHERE service = 'wallet' AND event = 'transaction' AND identifier = 'all'")
|
||||
|
||||
err = pref.Scan(&rst.Service, &rst.Event, &rst.Identifier, &rst.Enabled)
|
||||
if err == sql.ErrNoRows {
|
||||
return rst, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *Database) ChangeWalletPreference(preference bool) error {
|
||||
_, err := db.db.Exec("INSERT OR REPLACE INTO local_notifications_preferences (service, event, identifier, enabled) VALUES ('wallet', 'transaction', 'all', ?)", preference)
|
||||
return err
|
||||
}
|
||||
230
vendor/github.com/status-im/status-go/services/local-notifications/transaction.go
generated
vendored
Normal file
230
vendor/github.com/status-im/status-go/services/local-notifications/transaction.go
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
package localnotifications
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/services/wallet/transfer"
|
||||
"github.com/status-im/status-go/services/wallet/walletevent"
|
||||
)
|
||||
|
||||
type transactionState string
|
||||
|
||||
const (
|
||||
walletDeeplinkPrefix = "status-app://wallet/"
|
||||
|
||||
failed transactionState = "failed"
|
||||
inbound transactionState = "inbound"
|
||||
outbound transactionState = "outbound"
|
||||
)
|
||||
|
||||
// TransactionEvent - structure used to pass messages from wallet to bus
|
||||
type TransactionEvent struct {
|
||||
Type string `json:"type"`
|
||||
BlockNumber *big.Int `json:"block-number"`
|
||||
Accounts []common.Address `json:"accounts"`
|
||||
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
|
||||
}
|
||||
|
||||
type transactionBody struct {
|
||||
State transactionState `json:"state"`
|
||||
From common.Address `json:"from"`
|
||||
To common.Address `json:"to"`
|
||||
FromAccount *accounts.Account `json:"fromAccount,omitempty"`
|
||||
ToAccount *accounts.Account `json:"toAccount,omitempty"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
ERC20 bool `json:"erc20"`
|
||||
Contract common.Address `json:"contract"`
|
||||
Network uint64 `json:"network"`
|
||||
}
|
||||
|
||||
func (t transactionBody) MarshalJSON() ([]byte, error) {
|
||||
type Alias transactionBody
|
||||
item := struct{ *Alias }{Alias: (*Alias)(&t)}
|
||||
return json.Marshal(item)
|
||||
}
|
||||
|
||||
func (s *Service) buildTransactionNotification(rawTransfer transfer.Transfer) *Notification {
|
||||
log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer)
|
||||
|
||||
var deeplink string
|
||||
var state transactionState
|
||||
transfer := transfer.CastToTransferView(rawTransfer)
|
||||
|
||||
switch {
|
||||
case transfer.TxStatus == hexutil.Uint64(0):
|
||||
state = failed
|
||||
case transfer.Address == transfer.To:
|
||||
state = inbound
|
||||
default:
|
||||
state = outbound
|
||||
}
|
||||
|
||||
from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select From account by address", "error", err)
|
||||
}
|
||||
|
||||
to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To))
|
||||
|
||||
if err != nil {
|
||||
log.Debug("Could not select To account by address", "error", err)
|
||||
}
|
||||
|
||||
if from != nil {
|
||||
deeplink = walletDeeplinkPrefix + from.Address.String()
|
||||
} else if to != nil {
|
||||
deeplink = walletDeeplinkPrefix + to.Address.String()
|
||||
}
|
||||
|
||||
body := transactionBody{
|
||||
State: state,
|
||||
From: transfer.From,
|
||||
To: transfer.Address,
|
||||
FromAccount: from,
|
||||
ToAccount: to,
|
||||
Value: transfer.Value,
|
||||
ERC20: string(transfer.Type) == "erc20",
|
||||
Contract: transfer.Contract,
|
||||
Network: transfer.NetworkID,
|
||||
}
|
||||
|
||||
return &Notification{
|
||||
BodyType: TypeTransaction,
|
||||
ID: transfer.ID,
|
||||
Body: body,
|
||||
Deeplink: deeplink,
|
||||
Category: CategoryTransaction,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) transactionsHandler(payload TransactionEvent) {
|
||||
log.Info("Handled a new transaction", "info", payload)
|
||||
|
||||
limit := 20
|
||||
if payload.BlockNumber != nil {
|
||||
for _, address := range payload.Accounts {
|
||||
if payload.BlockNumber.Cmp(payload.MaxKnownBlocks[address]) >= 0 {
|
||||
log.Info("Handled transfer for address", "info", address)
|
||||
transfers, err := s.walletDB.GetTransfersByAddressAndBlock(s.chainID, address, payload.BlockNumber, int64(limit))
|
||||
if err != nil {
|
||||
log.Error("Could not fetch transfers", "error", err)
|
||||
}
|
||||
|
||||
for _, transaction := range transfers {
|
||||
n := s.buildTransactionNotification(transaction)
|
||||
pushMessage(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeWallet - Subscribes to wallet signals
|
||||
func (s *Service) SubscribeWallet(publisher *event.Feed) error {
|
||||
s.walletTransmitter.publisher = publisher
|
||||
|
||||
preference, err := s.db.GetWalletPreference()
|
||||
|
||||
if err != nil {
|
||||
log.Error("Failed to get wallet preference", "error", err)
|
||||
s.WatchingEnabled = false
|
||||
} else {
|
||||
s.WatchingEnabled = preference.Enabled
|
||||
}
|
||||
|
||||
s.StartWalletWatcher()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// StartWalletWatcher - Forward wallet events to notifications
|
||||
func (s *Service) StartWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
// already running, nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
if s.walletTransmitter.publisher == nil {
|
||||
log.Error("wallet publisher was not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
s.walletTransmitter.quit = make(chan struct{})
|
||||
events := make(chan walletevent.Event, 10)
|
||||
sub := s.walletTransmitter.publisher.Subscribe(events)
|
||||
|
||||
s.walletTransmitter.wg.Add(1)
|
||||
|
||||
maxKnownBlocks := map[common.Address]*big.Int{}
|
||||
go func() {
|
||||
defer s.walletTransmitter.wg.Done()
|
||||
historyReady := false
|
||||
for {
|
||||
select {
|
||||
case <-s.walletTransmitter.quit:
|
||||
sub.Unsubscribe()
|
||||
return
|
||||
case err := <-sub.Err():
|
||||
// technically event.Feed cannot send an error to subscription.Err channel.
|
||||
// the only time we will get an event is when that channel is closed.
|
||||
if err != nil {
|
||||
log.Error("wallet signals transmitter failed with", "error", err)
|
||||
}
|
||||
return
|
||||
case event := <-events:
|
||||
if event.Type == transfer.EventNewTransfers && historyReady && event.BlockNumber != nil {
|
||||
newBlocks := false
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
newBlocks = true
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
} else if event.BlockNumber.Cmp(maxKnownBlocks[address]) == 1 {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
newBlocks = true
|
||||
}
|
||||
}
|
||||
if newBlocks && s.WatchingEnabled {
|
||||
s.transmitter.publisher.Send(TransactionEvent{
|
||||
Type: string(event.Type),
|
||||
BlockNumber: event.BlockNumber,
|
||||
Accounts: event.Accounts,
|
||||
MaxKnownBlocks: maxKnownBlocks,
|
||||
})
|
||||
}
|
||||
} else if event.Type == transfer.EventRecentHistoryReady {
|
||||
historyReady = true
|
||||
if event.BlockNumber != nil {
|
||||
for _, address := range event.Accounts {
|
||||
if _, ok := maxKnownBlocks[address]; !ok {
|
||||
maxKnownBlocks[address] = event.BlockNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopWalletWatcher - stops watching for new wallet events
|
||||
func (s *Service) StopWalletWatcher() {
|
||||
if s.walletTransmitter.quit != nil {
|
||||
close(s.walletTransmitter.quit)
|
||||
s.walletTransmitter.wg.Wait()
|
||||
s.walletTransmitter.quit = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsWatchingWallet - check if local-notifications are subscribed to wallet updates
|
||||
func (s *Service) IsWatchingWallet() bool {
|
||||
return s.walletTransmitter.quit != nil
|
||||
}
|
||||
11
vendor/github.com/status-im/status-go/services/local-notifications/types.go
generated
vendored
Normal file
11
vendor/github.com/status-im/status-go/services/local-notifications/types.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package localnotifications
|
||||
|
||||
const (
|
||||
CategoryTransaction PushCategory = "transaction"
|
||||
CategoryMessage PushCategory = "newMessage"
|
||||
CategoryGroupInvite PushCategory = "groupInvite"
|
||||
CategoryCommunityRequestToJoin = "communityRequestToJoin"
|
||||
|
||||
TypeTransaction NotificationType = "transaction"
|
||||
TypeMessage NotificationType = "message"
|
||||
)
|
||||
118
vendor/github.com/status-im/status-go/services/mailservers/README.md
generated
vendored
Normal file
118
vendor/github.com/status-im/status-go/services/mailservers/README.md
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
Mailservers Service
|
||||
================
|
||||
|
||||
Mailservers service provides read/write API for `Mailserver` object
|
||||
which stores details about user's mailservers.
|
||||
|
||||
To enable this service, include `mailservers` in APIModules:
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"MailserversConfig": {
|
||||
"Enabled": true
|
||||
},
|
||||
"APIModules": "mailservers"
|
||||
}
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Enabling service will expose three additional methods:
|
||||
|
||||
#### mailservers_addMailserver
|
||||
|
||||
Stores `Mailserver` in the database.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"name": "my mailserver",
|
||||
"address": "enode://...",
|
||||
"password": "some-pass",
|
||||
"fleet": "prod"
|
||||
}
|
||||
```
|
||||
|
||||
#### mailservers_getMailservers
|
||||
|
||||
Reads all saved mailservers.
|
||||
|
||||
#### mailservers_deleteMailserver
|
||||
|
||||
Deletes a mailserver specified by an ID.
|
||||
|
||||
## Mailserver requests gap service
|
||||
|
||||
Mailserver request gaps service provides read/write API for `MailserverRequestGap` object
|
||||
which stores details about the gaps between mailserver requests.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
The service exposes four methods
|
||||
|
||||
#### mailserverrequestgaps_addMailserverRequestGaps
|
||||
|
||||
Stores `MailserverRequestGap` in the database.
|
||||
All fields are specified below:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"chatId": "chat-id",
|
||||
"from": 1,
|
||||
"to": 2
|
||||
}
|
||||
```
|
||||
|
||||
#### mailservers_getMailserverRequestGaps
|
||||
|
||||
Reads all saved mailserver request gaps by chatID.
|
||||
|
||||
#### mailservers_deleteMailserverRequestGaps
|
||||
|
||||
Deletes all MailserverRequestGaps specified by IDs.
|
||||
|
||||
#### mailservers_deleteMailserverRequestGapsByChatID
|
||||
|
||||
Deletes all MailserverRequestGaps specified by chatID.
|
||||
|
||||
#### mailservers_addMailserverTopic
|
||||
|
||||
Stores `MailserverTopic` in the database.
|
||||
```json
|
||||
{
|
||||
"topic": "topic-as-string",
|
||||
"chat-ids": ["a", "list", "of", "chatIDs"],
|
||||
"last-request": 1
|
||||
}
|
||||
```
|
||||
|
||||
#### mailservers_getMailserverTopics
|
||||
|
||||
Reads all saved mailserver topics.
|
||||
|
||||
#### mailservers_deleteMailserverTopic
|
||||
|
||||
Deletes a mailserver topic using `topic` as an identifier.
|
||||
|
||||
#### mailservers_addChatRequestRange
|
||||
|
||||
Stores `ChatRequestRange` in the database.
|
||||
```json
|
||||
{
|
||||
"chat-id": "chat-id-001",
|
||||
"lowest-request-from": 1567693421154,
|
||||
"highest-request-to": 1567693576779
|
||||
}
|
||||
```
|
||||
|
||||
#### mailservers_getChatRequestRanges
|
||||
|
||||
Reads all saved chat request ranges.
|
||||
|
||||
#### mailservers_deleteChatRequestRange
|
||||
|
||||
Deletes a chat request range by `chat-id`.
|
||||
72
vendor/github.com/status-im/status-go/services/mailservers/api.go
generated
vendored
Normal file
72
vendor/github.com/status-im/status-go/services/mailservers/api.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package mailservers
|
||||
|
||||
import "context"
|
||||
|
||||
func NewAPI(db *Database) *API {
|
||||
return &API{db}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
func (a *API) AddMailserver(ctx context.Context, m Mailserver) error {
|
||||
return a.db.Add(m)
|
||||
}
|
||||
|
||||
func (a *API) GetMailservers(ctx context.Context) ([]Mailserver, error) {
|
||||
return a.db.Mailservers()
|
||||
}
|
||||
|
||||
func (a *API) DeleteMailserver(ctx context.Context, id string) error {
|
||||
return a.db.Delete(id)
|
||||
}
|
||||
|
||||
func (a *API) AddMailserverRequestGaps(ctx context.Context, gaps []MailserverRequestGap) error {
|
||||
return a.db.AddGaps(gaps)
|
||||
}
|
||||
|
||||
func (a *API) GetMailserverRequestGaps(ctx context.Context, chatID string) ([]MailserverRequestGap, error) {
|
||||
return a.db.RequestGaps(chatID)
|
||||
}
|
||||
|
||||
func (a *API) DeleteMailserverRequestGaps(ctx context.Context, ids []string) error {
|
||||
return a.db.DeleteGaps(ids)
|
||||
}
|
||||
|
||||
func (a *API) DeleteMailserverRequestGapsByChatID(ctx context.Context, chatID string) error {
|
||||
return a.db.DeleteGapsByChatID(chatID)
|
||||
}
|
||||
|
||||
func (a *API) AddMailserverTopic(ctx context.Context, topic MailserverTopic) error {
|
||||
return a.db.AddTopic(topic)
|
||||
}
|
||||
|
||||
func (a *API) AddMailserverTopics(ctx context.Context, topics []MailserverTopic) error {
|
||||
return a.db.AddTopics(topics)
|
||||
}
|
||||
|
||||
func (a *API) GetMailserverTopics(ctx context.Context) ([]MailserverTopic, error) {
|
||||
return a.db.Topics()
|
||||
}
|
||||
|
||||
func (a *API) DeleteMailserverTopic(ctx context.Context, pubsubTopic string, topic string) error {
|
||||
return a.db.DeleteTopic(pubsubTopic, topic)
|
||||
}
|
||||
|
||||
func (a *API) AddChatRequestRange(ctx context.Context, req ChatRequestRange) error {
|
||||
return a.db.AddChatRequestRange(req)
|
||||
}
|
||||
|
||||
func (a *API) AddChatRequestRanges(ctx context.Context, reqs []ChatRequestRange) error {
|
||||
return a.db.AddChatRequestRanges(reqs)
|
||||
}
|
||||
|
||||
func (a *API) GetChatRequestRanges(ctx context.Context) ([]ChatRequestRange, error) {
|
||||
return a.db.ChatRequestRanges()
|
||||
}
|
||||
|
||||
func (a *API) DeleteChatRequestRange(ctx context.Context, chatID string) error {
|
||||
return a.db.DeleteChatRequestRange(chatID)
|
||||
}
|
||||
501
vendor/github.com/status-im/status-go/services/mailservers/database.go
generated
vendored
Normal file
501
vendor/github.com/status-im/status-go/services/mailservers/database.go
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
|
||||
"github.com/status-im/status-go/protocol/transport"
|
||||
)
|
||||
|
||||
type Mailserver struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Custom bool `json:"custom"`
|
||||
Address string `json:"address"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Fleet string `json:"fleet"`
|
||||
Version uint `json:"version"`
|
||||
FailedRequests uint `json:"-"`
|
||||
}
|
||||
|
||||
func (m Mailserver) Enode() (*enode.Node, error) {
|
||||
return enode.ParseV4(m.Address)
|
||||
}
|
||||
|
||||
func (m Mailserver) IDBytes() ([]byte, error) {
|
||||
if m.Version == 2 {
|
||||
id, err := peer.Decode(m.UniqueID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(id.Pretty()), err
|
||||
}
|
||||
|
||||
node, err := enode.ParseV4(m.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node.ID().Bytes(), nil
|
||||
}
|
||||
|
||||
func (m Mailserver) PeerID() (peer.ID, error) {
|
||||
if m.Version != 2 {
|
||||
return "", errors.New("not available")
|
||||
}
|
||||
|
||||
pID, err := peer.Decode(m.UniqueID())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return pID, nil
|
||||
}
|
||||
|
||||
func (m Mailserver) UniqueID() string {
|
||||
if m.Version == 2 {
|
||||
s := strings.Split(m.Address, "/")
|
||||
return s[len(s)-1]
|
||||
}
|
||||
return m.Address
|
||||
}
|
||||
|
||||
func (m Mailserver) nullablePassword() (val sql.NullString) {
|
||||
if m.Password != "" {
|
||||
val.String = m.Password
|
||||
val.Valid = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type MailserverRequestGap struct {
|
||||
ID string `json:"id"`
|
||||
ChatID string `json:"chatId"`
|
||||
From uint64 `json:"from"`
|
||||
To uint64 `json:"to"`
|
||||
}
|
||||
|
||||
type MailserverTopic struct {
|
||||
PubsubTopic string `json:"pubsubTopic"`
|
||||
ContentTopic string `json:"topic"`
|
||||
Discovery bool `json:"discovery?"`
|
||||
Negotiated bool `json:"negotiated?"`
|
||||
ChatIDs []string `json:"chat-ids"`
|
||||
LastRequest int `json:"last-request"` // default is 1
|
||||
}
|
||||
|
||||
type ChatRequestRange struct {
|
||||
ChatID string `json:"chat-id"`
|
||||
LowestRequestFrom int `json:"lowest-request-from"`
|
||||
HighestRequestTo int `json:"highest-request-to"`
|
||||
}
|
||||
|
||||
// sqlStringSlice helps to serialize a slice of strings into a single column using JSON serialization.
|
||||
type sqlStringSlice []string
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ss *sqlStringSlice) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*ss = nil
|
||||
return nil
|
||||
}
|
||||
src, ok := value.([]byte)
|
||||
if !ok {
|
||||
return errors.New("invalid value type, expected byte slice")
|
||||
}
|
||||
return json.Unmarshal(src, ss)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ss sqlStringSlice) Value() (driver.Value, error) {
|
||||
return json.Marshal(ss)
|
||||
}
|
||||
|
||||
// Database sql wrapper for operations with mailserver objects.
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDB(db *sql.DB) *Database {
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
func (d *Database) Add(mailserver Mailserver) error {
|
||||
_, err := d.db.Exec(`INSERT OR REPLACE INTO mailservers(
|
||||
id,
|
||||
name,
|
||||
address,
|
||||
password,
|
||||
fleet
|
||||
) VALUES (?, ?, ?, ?, ?)`,
|
||||
mailserver.ID,
|
||||
mailserver.Name,
|
||||
mailserver.Address,
|
||||
mailserver.nullablePassword(),
|
||||
mailserver.Fleet,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) Mailservers() ([]Mailserver, error) {
|
||||
var result []Mailserver
|
||||
|
||||
rows, err := d.db.Query(`SELECT id, name, address, password, fleet FROM mailservers`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
m Mailserver
|
||||
password sql.NullString
|
||||
)
|
||||
if err := rows.Scan(
|
||||
&m.ID,
|
||||
&m.Name,
|
||||
&m.Address,
|
||||
&password,
|
||||
&m.Fleet,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Custom = true
|
||||
if password.Valid {
|
||||
m.Password = password.String
|
||||
}
|
||||
result = append(result, m)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *Database) Delete(id string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM mailservers WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) AddGaps(gaps []MailserverRequestGap) error {
|
||||
tx, err := d.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
for _, gap := range gaps {
|
||||
|
||||
_, err := tx.Exec(`INSERT OR REPLACE INTO mailserver_request_gaps(
|
||||
id,
|
||||
chat_id,
|
||||
gap_from,
|
||||
gap_to
|
||||
) VALUES (?, ?, ?, ?)`,
|
||||
gap.ID,
|
||||
gap.ChatID,
|
||||
gap.From,
|
||||
gap.To,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Database) RequestGaps(chatID string) ([]MailserverRequestGap, error) {
|
||||
var result []MailserverRequestGap
|
||||
|
||||
rows, err := d.db.Query(`SELECT id, chat_id, gap_from, gap_to FROM mailserver_request_gaps WHERE chat_id = ?`, chatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var m MailserverRequestGap
|
||||
if err := rows.Scan(
|
||||
&m.ID,
|
||||
&m.ChatID,
|
||||
&m.From,
|
||||
&m.To,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, m)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteGaps(ids []string) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
inVector := strings.Repeat("?, ", len(ids)-1) + "?"
|
||||
query := fmt.Sprintf(`DELETE FROM mailserver_request_gaps WHERE id IN (%s)`, inVector) // nolint: gosec
|
||||
idsArgs := make([]interface{}, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
idsArgs = append(idsArgs, id)
|
||||
}
|
||||
|
||||
_, err := d.db.Exec(query, idsArgs...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) DeleteGapsByChatID(chatID string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM mailserver_request_gaps WHERE chat_id = ?`, chatID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) AddTopic(topic MailserverTopic) error {
|
||||
|
||||
chatIDs := sqlStringSlice(topic.ChatIDs)
|
||||
_, err := d.db.Exec(`INSERT OR REPLACE INTO mailserver_topics(
|
||||
pubsub_topic,
|
||||
topic,
|
||||
chat_ids,
|
||||
last_request,
|
||||
discovery,
|
||||
negotiated
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
topic.PubsubTopic,
|
||||
topic.ContentTopic,
|
||||
chatIDs,
|
||||
topic.LastRequest,
|
||||
topic.Discovery,
|
||||
topic.Negotiated,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) AddTopics(topics []MailserverTopic) (err error) {
|
||||
var tx *sql.Tx
|
||||
tx, err = d.db.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
for _, topic := range topics {
|
||||
chatIDs := sqlStringSlice(topic.ChatIDs)
|
||||
_, err = tx.Exec(`INSERT OR REPLACE INTO mailserver_topics(
|
||||
pubsub_topic,
|
||||
topic,
|
||||
chat_ids,
|
||||
last_request,
|
||||
discovery,
|
||||
negotiated
|
||||
) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
topic.PubsubTopic,
|
||||
topic.ContentTopic,
|
||||
chatIDs,
|
||||
topic.LastRequest,
|
||||
topic.Discovery,
|
||||
topic.Negotiated,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Database) Topics() ([]MailserverTopic, error) {
|
||||
var result []MailserverTopic
|
||||
|
||||
rows, err := d.db.Query(`SELECT pubsub_topic, topic, chat_ids, last_request,discovery,negotiated FROM mailserver_topics`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var (
|
||||
t MailserverTopic
|
||||
chatIDs sqlStringSlice
|
||||
)
|
||||
if err := rows.Scan(
|
||||
&t.PubsubTopic,
|
||||
&t.ContentTopic,
|
||||
&chatIDs,
|
||||
&t.LastRequest,
|
||||
&t.Discovery,
|
||||
&t.Negotiated,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ChatIDs = chatIDs
|
||||
result = append(result, t)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *Database) ResetLastRequest(pubsubTopic, contentTopic string) error {
|
||||
_, err := d.db.Exec("UPDATE mailserver_topics SET last_request = 0 WHERE pubsub_topic = ? AND topic = ?", pubsubTopic, contentTopic)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) DeleteTopic(pubsubTopic, contentTopic string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM mailserver_topics WHERE pubsub_topic = ? AND topic = ?`, pubsubTopic, contentTopic)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetTopics deletes all topics excepts the one set, or upsert those if
|
||||
// missing
|
||||
func (d *Database) SetTopics(filters []*transport.Filter) (err error) {
|
||||
var tx *sql.Tx
|
||||
tx, err = d.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
contentTopicsPerPubsubTopic := make(map[string]map[string]struct{})
|
||||
for _, filter := range filters {
|
||||
contentTopics, ok := contentTopicsPerPubsubTopic[filter.PubsubTopic]
|
||||
if !ok {
|
||||
contentTopics = make(map[string]struct{})
|
||||
}
|
||||
contentTopics[filter.ContentTopic.String()] = struct{}{}
|
||||
contentTopicsPerPubsubTopic[filter.PubsubTopic] = contentTopics
|
||||
}
|
||||
|
||||
for pubsubTopic, contentTopics := range contentTopicsPerPubsubTopic {
|
||||
topicsArgs := make([]interface{}, 0, len(contentTopics)+1)
|
||||
topicsArgs = append(topicsArgs, pubsubTopic)
|
||||
for ct := range contentTopics {
|
||||
topicsArgs = append(topicsArgs, ct)
|
||||
}
|
||||
|
||||
inVector := strings.Repeat("?, ", len(contentTopics)-1) + "?"
|
||||
|
||||
// Delete topics
|
||||
query := "DELETE FROM mailserver_topics WHERE pubsub_topic = ? AND topic NOT IN (" + inVector + ")" // nolint: gosec
|
||||
_, err = tx.Exec(query, topicsArgs...)
|
||||
}
|
||||
|
||||
// Default to now - 1.day
|
||||
lastRequest := (time.Now().Add(-24 * time.Hour)).Unix()
|
||||
// Insert if not existing
|
||||
for _, filter := range filters {
|
||||
// fetch
|
||||
var topic string
|
||||
err = tx.QueryRow(`SELECT topic FROM mailserver_topics WHERE topic = ? AND pubsub_topic = ?`, filter.ContentTopic.String(), filter.PubsubTopic).Scan(&topic)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return
|
||||
} else if err == sql.ErrNoRows {
|
||||
// we insert the topic
|
||||
_, err = tx.Exec(`INSERT INTO mailserver_topics(topic,pubsub_topic,last_request,discovery,negotiated) VALUES (?,?,?,?,?)`, filter.ContentTopic.String(), filter.PubsubTopic, lastRequest, filter.Discovery, filter.Negotiated)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Database) AddChatRequestRange(req ChatRequestRange) error {
|
||||
_, err := d.db.Exec(`INSERT OR REPLACE INTO mailserver_chat_request_ranges(
|
||||
chat_id,
|
||||
lowest_request_from,
|
||||
highest_request_to
|
||||
) VALUES (?, ?, ?)`,
|
||||
req.ChatID,
|
||||
req.LowestRequestFrom,
|
||||
req.HighestRequestTo,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) AddChatRequestRanges(reqs []ChatRequestRange) (err error) {
|
||||
var tx *sql.Tx
|
||||
tx, err = d.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
for _, req := range reqs {
|
||||
|
||||
_, err = tx.Exec(`INSERT OR REPLACE INTO mailserver_chat_request_ranges(
|
||||
chat_id,
|
||||
lowest_request_from,
|
||||
highest_request_to
|
||||
) VALUES (?, ?, ?)`,
|
||||
req.ChatID,
|
||||
req.LowestRequestFrom,
|
||||
req.HighestRequestTo,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Database) ChatRequestRanges() ([]ChatRequestRange, error) {
|
||||
var result []ChatRequestRange
|
||||
|
||||
rows, err := d.db.Query(`SELECT chat_id, lowest_request_from, highest_request_to FROM mailserver_chat_request_ranges`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var req ChatRequestRange
|
||||
if err := rows.Scan(
|
||||
&req.ChatID,
|
||||
&req.LowestRequestFrom,
|
||||
&req.HighestRequestTo,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, req)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteChatRequestRange(chatID string) error {
|
||||
_, err := d.db.Exec(`DELETE FROM mailserver_chat_request_ranges WHERE chat_id = ?`, chatID)
|
||||
return err
|
||||
}
|
||||
235
vendor/github.com/status-im/status-go/services/mailservers/fleet.go
generated
vendored
Normal file
235
vendor/github.com/status-im/status-go/services/mailservers/fleet.go
generated
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
package mailservers
|
||||
|
||||
import "github.com/status-im/status-go/params"
|
||||
|
||||
func DefaultMailserversByFleet(fleet string) []Mailserver {
|
||||
var items []Mailserver
|
||||
for _, ms := range DefaultMailservers() {
|
||||
if ms.Fleet == fleet {
|
||||
items = append(items, ms)
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func DefaultMailservers() []Mailserver {
|
||||
|
||||
return []Mailserver{
|
||||
Mailserver{
|
||||
ID: "mail-01.ac-cn-hongkong-c.eth.prod",
|
||||
Address: "enode://606ae04a71e5db868a722c77a21c8244ae38f1bd6e81687cc6cfe88a3063fa1c245692232f64f45bd5408fed5133eab8ed78049332b04f9c110eac7f71c1b429@47.75.247.214:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.do-ams3.eth.prod",
|
||||
Address: "enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@178.128.142.54:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.gc-us-central1-a.eth.prod",
|
||||
Address: "enode://ee2b53b0ace9692167a410514bca3024695dbf0e1a68e1dff9716da620efb195f04a4b9e873fb9b74ac84de801106c465b8e2b6c4f0d93b8749d1578bfcaf03e@104.197.238.144:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-02.ac-cn-hongkong-c.eth.prod",
|
||||
Address: "enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-02.do-ams3.eth.prod",
|
||||
Address: "enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@178.128.142.26:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-02.gc-us-central1-a.eth.prod",
|
||||
Address: "enode://30211cbd81c25f07b03a0196d56e6ce4604bb13db773ff1c0ea2253547fafd6c06eae6ad3533e2ba39d59564cfbdbb5e2ce7c137a5ebb85e99dcfc7a75f99f55@23.236.58.92:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-03.ac-cn-hongkong-c.eth.prod",
|
||||
Address: "enode://e85f1d4209f2f99da801af18db8716e584a28ad0bdc47fbdcd8f26af74dbd97fc279144680553ec7cd9092afe683ddea1e0f9fc571ebcb4b1d857c03a088853d@47.244.129.82:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-03.do-ams3.eth.prod",
|
||||
Address: "enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@178.128.142.94:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-03.gc-us-central1-a.eth.prod",
|
||||
Address: "enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443",
|
||||
Fleet: params.FleetProd,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.ac-cn-hongkong-c.eth.staging",
|
||||
Address: "enode://b74859176c9751d314aeeffc26ec9f866a412752e7ddec91b19018a18e7cca8d637cfe2cedcb972f8eb64d816fbd5b4e89c7e8c7fd7df8a1329fa43db80b0bfe@47.52.90.156:443",
|
||||
Fleet: params.FleetStaging,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.do-ams3.eth.staging",
|
||||
Address: "enode://69f72baa7f1722d111a8c9c68c39a31430e9d567695f6108f31ccb6cd8f0adff4991e7fdca8fa770e75bc8a511a87d24690cbc80e008175f40c157d6f6788d48@206.189.240.16:443",
|
||||
Fleet: params.FleetStaging,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.gc-us-central1-a.eth.staging",
|
||||
Address: "enode://e4fc10c1f65c8aed83ac26bc1bfb21a45cc1a8550a58077c8d2de2a0e0cd18e40fd40f7e6f7d02dc6cd06982b014ce88d6e468725ffe2c138e958788d0002a7f@35.239.193.41:443",
|
||||
Fleet: params.FleetStaging,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.ac-cn-hongkong-c.eth.test",
|
||||
Address: "enode://619dbb5dda12e85bf0eb5db40fb3de625609043242737c0e975f7dfd659d85dc6d9a84f9461a728c5ab68c072fed38ca6a53917ca24b8e93cc27bdef3a1e79ac@47.52.188.196:443",
|
||||
Fleet: params.FleetTest,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.do-ams3.eth.test",
|
||||
Address: "enode://e4865fe6c2a9c1a563a6447990d8e9ce672644ae3e08277ce38ec1f1b690eef6320c07a5d60c3b629f5d4494f93d6b86a745a0bf64ab295bbf6579017adc6ed8@206.189.243.161:443",
|
||||
Fleet: params.FleetTest,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "mail-01.gc-us-central1-a.eth.test",
|
||||
Address: "enode://707e57453acd3e488c44b9d0e17975371e2f8fb67525eae5baca9b9c8e06c86cde7c794a6c2e36203bf9f56cae8b0e50f3b33c4c2b694a7baeea1754464ce4e3@35.192.229.172:443",
|
||||
Fleet: params.FleetTest,
|
||||
Version: 1,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.ac-cn-hongkong-c.wakuv2.prod",
|
||||
Address: "/ip4/8.210.222.231/tcp/30303/p2p/16Uiu2HAm4v86W3bmT1BiH6oSPzcsSr24iDQpSN5Qa992BCjjwgrD",
|
||||
Fleet: params.FleetWakuV2Prod,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.do-ams3.wakuv2.prod",
|
||||
Address: "/ip4/188.166.135.145/tcp/30303/p2p/16Uiu2HAmL5okWopX7NqZWBUKVqW8iUxCEmd5GMHLVPwCgzYzQv3e",
|
||||
Fleet: params.FleetWakuV2Prod,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.gc-us-central1-a.wakuv2.prod",
|
||||
Address: "/ip4/34.121.100.108/tcp/30303/p2p/16Uiu2HAmVkKntsECaYfefR1V2yCR79CegLATuTPE6B9TxgxBiiiA",
|
||||
Fleet: params.FleetWakuV2Prod,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.ac-cn-hongkong-c.wakuv2.test",
|
||||
Address: "/ip4/47.242.210.73/tcp/30303/p2p/16Uiu2HAkvWiyFsgRhuJEb9JfjYxEkoHLgnUQmr1N5mKWnYjxYRVm",
|
||||
Fleet: params.FleetWakuV2Test,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.do-ams3.wakuv2.test",
|
||||
Address: "/ip4/134.209.139.210/tcp/30303/p2p/16Uiu2HAmPLe7Mzm8TsYUubgCAW1aJoeFScxrLj8ppHFivPo97bUZ",
|
||||
Fleet: params.FleetWakuV2Test,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.gc-us-central1-a.wakuv2.test",
|
||||
Address: "/ip4/104.154.239.128/tcp/30303/p2p/16Uiu2HAmJb2e28qLXxT5kZxVUUoJt72EMzNGXB47Rxx5hw3q4YjS",
|
||||
Fleet: params.FleetWakuV2Test,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.ac-cn-hongkong-c.status.prod",
|
||||
Address: "/dns4/node-01.ac-cn-hongkong-c.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAkvEZgh3KLwhLwXg95e5ojM8XykJ4Kxi2T7hk22rnA7pJC",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.do-ams3.status.prod",
|
||||
Address: "/dns4/node-01.do-ams3.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAm6HZZr7aToTvEBPpiys4UxajCTU97zj5v7RNR2gbniy1D",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.gc-us-central1-a.status.prod",
|
||||
Address: "/dns4/node-01.gc-us-central1-a.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAkwBp8T6G77kQXSNMnxgaMky1JeyML5yqoTHRM8dbeCBNb",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-02.ac-cn-hongkong-c.status.prod",
|
||||
Address: "/dns4/node-02.ac-cn-hongkong-c.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmFy8BrJhCEmCYrUfBdSNkrPw6VHExtv4rRp1DSBnCPgx8",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-02.do-ams3.status.prod",
|
||||
Address: "/dns4/node-02.do-ams3.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmSve7tR5YZugpskMv2dmJAsMUKmfWYEKRXNUxRaTCnsXV",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-02.gc-us-central1-a.status.prod",
|
||||
Address: "/dns4/node-02.gc-us-central1-a.status.prod.statusim.net/tcp/30303/p2p/16Uiu2HAmDQugwDHM3YeUp86iGjrUvbdw3JPRgikC7YoGBsT2ymMg",
|
||||
Fleet: params.FleetStatusProd,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.ac-cn-hongkong-c.status.test",
|
||||
Address: "/dns4/node-01.ac-cn-hongkong-c.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAm2BjXxCp1sYFJQKpLLbPbwd5juxbsYofu3TsS3auvT9Yi",
|
||||
Fleet: params.FleetStatusTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.do-ams3.status.test",
|
||||
Address: "/dns4/node-01.do-ams3.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAkukebeXjTQ9QDBeNDWuGfbaSg79wkkhK4vPocLgR6QFDf",
|
||||
Fleet: params.FleetStatusTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "node-01.gc-us-central1-a.status.test",
|
||||
Address: "/dns4/node-01.gc-us-central1-a.status.test.statusim.net/tcp/30303/p2p/16Uiu2HAmGDX3iAFox93PupVYaHa88kULGqMpJ7AEHGwj3jbMtt76",
|
||||
Fleet: params.FleetStatusTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-01.do-ams3.shards.test",
|
||||
Address: "/dns4/store-01.do-ams3.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAmAUdrQ3uwzuE4Gy4D56hX6uLKEeerJAnhKEHZ3DxF1EfT",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-02.do-ams3.shards.test",
|
||||
Address: "/dns4/store-02.do-ams3.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAm9aDJPkhGxc2SFcEACTFdZ91Q5TJjp76qZEhq9iF59x7R",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-01.gc-us-central1-a.shards.test",
|
||||
Address: "/dns4/store-01.gc-us-central1-a.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAmMELCo218hncCtTvC2Dwbej3rbyHQcR8erXNnKGei7WPZ",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-02.gc-us-central1-a.shards.test",
|
||||
Address: "/dns4/store-02.gc-us-central1-a.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAmJnVR7ZzFaYvciPVafUXuYGLHPzSUigqAmeNw9nJUVGeM",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-01.ac-cn-hongkong-c.shards.test",
|
||||
Address: "/dns4/store-01.ac-cn-hongkong-c.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAm2M7xs7cLPc3jamawkEqbr7cUJX11uvY7LxQ6WFUdUKUT",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
Mailserver{
|
||||
ID: "store-02.ac-cn-hongkong-c.shards.test",
|
||||
Address: "/dns4/store-02.ac-cn-hongkong-c.shards.test.statusim.net/tcp/30303/p2p/16Uiu2HAm9CQhsuwPR54q27kNj9iaQVfyRzTGKrhFmr94oD8ujU6P",
|
||||
Fleet: params.FleetShardsTest,
|
||||
Version: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
36
vendor/github.com/status-im/status-go/services/mailservers/service.go
generated
vendored
Normal file
36
vendor/github.com/status-im/status-go/services/mailservers/service.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
func NewService(db *Database) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "mailservers",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s.db),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
152
vendor/github.com/status-im/status-go/services/mailservers/tcp_ping.go
generated
vendored
Normal file
152
vendor/github.com/status-im/status-go/services/mailservers/tcp_ping.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
package mailservers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
multiaddr "github.com/multiformats/go-multiaddr"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/status-im/status-go/rtt"
|
||||
)
|
||||
|
||||
type PingQuery struct {
|
||||
Addresses []string `json:"addresses"`
|
||||
TimeoutMs int `json:"timeoutMs"`
|
||||
}
|
||||
|
||||
type PingResult struct {
|
||||
Address string `json:"address"`
|
||||
RTTMs *int `json:"rttMs"`
|
||||
Err *string `json:"error"`
|
||||
}
|
||||
|
||||
type parseFn func(string) (string, error)
|
||||
|
||||
func (pr *PingResult) Update(rttMs int, err error) {
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
pr.Err = &errStr
|
||||
}
|
||||
if rttMs >= 0 {
|
||||
pr.RTTMs = &rttMs
|
||||
} else {
|
||||
pr.RTTMs = nil
|
||||
}
|
||||
}
|
||||
|
||||
func EnodeToAddr(node *enode.Node) (string, error) {
|
||||
var ip4 enr.IPv4
|
||||
err := node.Load(&ip4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var tcp enr.TCP
|
||||
err = node.Load(&tcp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", net.IP(ip4).String(), tcp), nil
|
||||
}
|
||||
|
||||
func EnodeStringToAddr(enodeAddr string) (string, error) {
|
||||
node, err := enode.ParseV4(enodeAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return EnodeToAddr(node)
|
||||
}
|
||||
|
||||
func parse(addresses []string, fn parseFn) (map[string]*PingResult, []string) {
|
||||
results := make(map[string]*PingResult, len(addresses))
|
||||
var toPing []string
|
||||
|
||||
for i := range addresses {
|
||||
addr, err := fn(addresses[i])
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
results[addresses[i]] = &PingResult{Address: addresses[i], Err: &errStr}
|
||||
continue
|
||||
}
|
||||
results[addr] = &PingResult{Address: addresses[i]}
|
||||
toPing = append(toPing, addr)
|
||||
}
|
||||
return results, toPing
|
||||
}
|
||||
|
||||
func mapValues(m map[string]*PingResult) []*PingResult {
|
||||
rval := make([]*PingResult, 0, len(m))
|
||||
for _, value := range m {
|
||||
rval = append(rval, value)
|
||||
}
|
||||
return rval
|
||||
}
|
||||
|
||||
func DoPing(ctx context.Context, addresses []string, timeoutMs int, p parseFn) ([]*PingResult, error) {
|
||||
timeout := time.Duration(timeoutMs) * time.Millisecond
|
||||
|
||||
resultsMap, toPing := parse(addresses, p)
|
||||
|
||||
// run the checks concurrently
|
||||
results, err := rtt.CheckHosts(toPing, timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set ping results
|
||||
for i := range results {
|
||||
r := results[i]
|
||||
pr := resultsMap[r.Addr]
|
||||
if pr == nil {
|
||||
continue
|
||||
}
|
||||
pr.Update(r.RTTMs, r.Err)
|
||||
}
|
||||
|
||||
return mapValues(resultsMap), nil
|
||||
}
|
||||
|
||||
func (a *API) Ping(ctx context.Context, pq PingQuery) ([]*PingResult, error) {
|
||||
return DoPing(ctx, pq.Addresses, pq.TimeoutMs, EnodeStringToAddr)
|
||||
}
|
||||
|
||||
func MultiAddressToAddress(multiAddr string) (string, error) {
|
||||
|
||||
ma, err := multiaddr.NewMultiaddr(multiAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
dns4, err := ma.ValueForProtocol(multiaddr.P_DNS4)
|
||||
if err != nil && err != multiaddr.ErrProtocolNotFound {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ip4 := ""
|
||||
if dns4 != "" {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
ip4, err = net.DefaultResolver.LookupCNAME(ctx, dns4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
ip4, err = ma.ValueForProtocol(multiaddr.P_IP4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
tcp, err := ma.ValueForProtocol(multiaddr.P_TCP)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", ip4, tcp), nil
|
||||
}
|
||||
|
||||
func (a *API) MultiAddressPing(ctx context.Context, pq PingQuery) ([]*PingResult, error) {
|
||||
return DoPing(ctx, pq.Addresses, pq.TimeoutMs, MultiAddressToAddress)
|
||||
}
|
||||
48
vendor/github.com/status-im/status-go/services/peer/api.go
generated
vendored
Normal file
48
vendor/github.com/status-im/status-go/services/peer/api.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidTopic error returned when the requested topic is not valid.
|
||||
ErrInvalidTopic = errors.New("topic not valid")
|
||||
|
||||
// ErrInvalidRange error returned when max-min range is not valid.
|
||||
ErrInvalidRange = errors.New("invalid range, Min should be lower or equal to Max")
|
||||
|
||||
// ErrDiscovererNotProvided error when discoverer is not being provided.
|
||||
ErrDiscovererNotProvided = errors.New("discoverer not provided")
|
||||
)
|
||||
|
||||
// PublicAPI represents a set of APIs from the `web3.peer` namespace.
|
||||
type PublicAPI struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the peer API.
|
||||
func NewAPI(s *Service) *PublicAPI {
|
||||
return &PublicAPI{s: s}
|
||||
}
|
||||
|
||||
// DiscoverRequest json request for peer_discover.
|
||||
type DiscoverRequest struct {
|
||||
Topic string `json:"topic"`
|
||||
Max int `json:"max"`
|
||||
Min int `json:"min"`
|
||||
}
|
||||
|
||||
// Discover is an implementation of `peer_discover` or `web3.peer.discover` API.
|
||||
func (api *PublicAPI) Discover(context context.Context, req DiscoverRequest) (err error) {
|
||||
if api.s.d == nil {
|
||||
return ErrDiscovererNotProvided
|
||||
}
|
||||
if len(req.Topic) == 0 {
|
||||
return ErrInvalidTopic
|
||||
}
|
||||
if req.Max < req.Min {
|
||||
return ErrInvalidRange
|
||||
}
|
||||
return api.s.d.Discover(req.Topic, req.Max, req.Min)
|
||||
}
|
||||
48
vendor/github.com/status-im/status-go/services/peer/discoverer_mock.go
generated
vendored
Normal file
48
vendor/github.com/status-im/status-go/services/peer/discoverer_mock.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: services/peer/service.go
|
||||
|
||||
// Package peer is a generated GoMock package.
|
||||
package peer
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDiscoverer is a mock of Discoverer interface
|
||||
type MockDiscoverer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDiscovererMockRecorder
|
||||
}
|
||||
|
||||
// MockDiscovererMockRecorder is the mock recorder for MockDiscoverer
|
||||
type MockDiscovererMockRecorder struct {
|
||||
mock *MockDiscoverer
|
||||
}
|
||||
|
||||
// NewMockDiscoverer creates a new mock instance
|
||||
func NewMockDiscoverer(ctrl *gomock.Controller) *MockDiscoverer {
|
||||
mock := &MockDiscoverer{ctrl: ctrl}
|
||||
mock.recorder = &MockDiscovererMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockDiscoverer) EXPECT() *MockDiscovererMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Discover mocks base method
|
||||
func (m *MockDiscoverer) Discover(topic string, max, min int) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Discover", topic, max, min)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Discover indicates an expected call of Discover
|
||||
func (mr *MockDiscovererMockRecorder) Discover(topic, max, min interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockDiscoverer)(nil).Discover), topic, max, min)
|
||||
}
|
||||
59
vendor/github.com/status-im/status-go/services/peer/service.go
generated
vendored
Normal file
59
vendor/github.com/status-im/status-go/services/peer/service.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Lifecycle interface.
|
||||
var _ node.Lifecycle = (*Service)(nil)
|
||||
|
||||
// Discoverer manages peer discovery.
|
||||
type Discoverer interface {
|
||||
Discover(topic string, max, min int) error
|
||||
}
|
||||
|
||||
// Service it manages all endpoints for peer operations.
|
||||
type Service struct {
|
||||
d Discoverer
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "peer",
|
||||
Version: "1.0",
|
||||
Service: NewAPI(s),
|
||||
Public: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetDiscoverer sets discoverer for the API calls.
|
||||
func (s *Service) SetDiscoverer(d Discoverer) {
|
||||
s.d = d
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
38
vendor/github.com/status-im/status-go/services/permissions/README.md
generated
vendored
Normal file
38
vendor/github.com/status-im/status-go/services/permissions/README.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
Dapps permissions service
|
||||
=========================
|
||||
|
||||
To enable:
|
||||
|
||||
```json
|
||||
{
|
||||
"PermissionsConfig": {
|
||||
"Enabled": true,
|
||||
},
|
||||
APIModules: "permissions"
|
||||
}
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
#### permissions_addDappPermissions
|
||||
|
||||
Stores provided permissions for dapp. On update replaces previous version of the object.
|
||||
|
||||
```json
|
||||
{
|
||||
"dapp": "first",
|
||||
"permissions": [
|
||||
"r",
|
||||
"x"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### permissions_getDappPermissions
|
||||
|
||||
Returns all permissions for dapps. Order is not deterministic.
|
||||
|
||||
#### permissions_deleteDappPermissions
|
||||
|
||||
Delete dapp by a name.
|
||||
30
vendor/github.com/status-im/status-go/services/permissions/api.go
generated
vendored
Normal file
30
vendor/github.com/status-im/status-go/services/permissions/api.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package permissions
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func NewAPI(db *Database) *API {
|
||||
return &API{db}
|
||||
}
|
||||
|
||||
// API is class with methods available over RPC.
|
||||
type API struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
func (api *API) AddDappPermissions(ctx context.Context, perms DappPermissions) error {
|
||||
return api.db.AddPermissions(perms)
|
||||
}
|
||||
|
||||
func (api *API) GetDappPermissions(ctx context.Context) ([]DappPermissions, error) {
|
||||
return api.db.GetPermissions()
|
||||
}
|
||||
|
||||
func (api *API) DeleteDappPermissions(ctx context.Context, name string) error {
|
||||
return api.db.DeletePermission(name, "")
|
||||
}
|
||||
|
||||
func (api *API) DeleteDappPermissionsByNameAndAddress(ctx context.Context, name string, address string) error {
|
||||
return api.db.DeletePermission(name, address)
|
||||
}
|
||||
168
vendor/github.com/status-im/status-go/services/permissions/database.go
generated
vendored
Normal file
168
vendor/github.com/status-im/status-go/services/permissions/database.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
package permissions
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
func NewDB(db *sql.DB) *Database {
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
type DappPermissions struct {
|
||||
ID int
|
||||
Name string `json:"dapp"`
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
|
||||
func (db *Database) AddPermissions(perms DappPermissions) (err error) {
|
||||
tx, err := db.db.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
dRows, err := tx.Query("SELECT id FROM dapps where name = ? AND address = ?", perms.Name, perms.Address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dRows.Close()
|
||||
|
||||
var id int64
|
||||
if dRows.Next() {
|
||||
err = dRows.Scan(&id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dInsert, err := tx.Prepare("INSERT INTO dapps(name, address) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := dInsert.Exec(perms.Name, perms.Address)
|
||||
dInsert.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err = res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pDelete, err := tx.Prepare("DELETE FROM permissions WHERE dapp_id = ?")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer pDelete.Close()
|
||||
_, err = pDelete.Exec(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(perms.Permissions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pInsert, err := tx.Prepare("INSERT INTO permissions(dapp_id, permission) VALUES(?, ?)")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer pInsert.Close()
|
||||
for _, perm := range perms.Permissions {
|
||||
_, err = pInsert.Exec(id, perm)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *Database) GetPermissions() (rst []DappPermissions, err error) {
|
||||
tx, err := db.db.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
// FULL and RIGHT joins are not supported
|
||||
dRows, err := tx.Query("SELECT id, name, address FROM dapps")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dRows.Close()
|
||||
dapps := map[int]*DappPermissions{}
|
||||
for dRows.Next() {
|
||||
perms := DappPermissions{}
|
||||
err = dRows.Scan(&perms.ID, &perms.Name, &perms.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dapps[perms.ID] = &perms
|
||||
}
|
||||
|
||||
pRows, err := tx.Query("SELECT dapp_id, permission from permissions")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer pRows.Close()
|
||||
var (
|
||||
id int
|
||||
permission string
|
||||
)
|
||||
for pRows.Next() {
|
||||
err = pRows.Scan(&id, &permission)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dapps[id].Permissions = append(dapps[id].Permissions, permission)
|
||||
}
|
||||
rst = make([]DappPermissions, 0, len(dapps))
|
||||
for key := range dapps {
|
||||
rst = append(rst, *dapps[key])
|
||||
}
|
||||
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeletePermission(name string, address string) error {
|
||||
_, err := db.db.Exec("DELETE FROM dapps WHERE name = ? AND address = ?", name, address)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *Database) HasPermission(dappName string, address string, permission string) (bool, error) {
|
||||
var id int64
|
||||
err := db.db.QueryRow("SELECT id FROM dapps where name = ? AND address = ?", dappName, address).Scan(&id)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var count uint64
|
||||
err = db.db.QueryRow(
|
||||
`SELECT COUNT(1) FROM permissions WHERE dapp_id = ? AND permission = ?`,
|
||||
id, permission,
|
||||
).Scan(&count)
|
||||
return count > 0, err
|
||||
}
|
||||
41
vendor/github.com/status-im/status-go/services/permissions/service.go
generated
vendored
Normal file
41
vendor/github.com/status-im/status-go/services/permissions/service.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package permissions
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// NewService initializes service instance.
|
||||
func NewService(db *Database) *Service {
|
||||
return &Service{db: db}
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *Database
|
||||
}
|
||||
|
||||
// Start a service.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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: "permissions",
|
||||
Version: "0.1.0",
|
||||
Service: NewAPI(s.db),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/status-im/status-go/services/personal/README.md
generated
vendored
Normal file
7
vendor/github.com/status-im/status-go/services/personal/README.md
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# personal
|
||||
|
||||
This package contains Status integraton with `personal_*` RPC APIs more
|
||||
information on these APIs can be found on the Ethereum Wiki:
|
||||
https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal
|
||||
|
||||
In `web3.js` these methods are located in `web3.personal` namespace.
|
||||
91
vendor/github.com/status-im/status-go/services/personal/api.go
generated
vendored
Normal file
91
vendor/github.com/status-im/status-go/services/personal/api.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package personal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidPersonalSignAccount is returned when the account passed to
|
||||
// personal_sign isn't equal to the currently selected account.
|
||||
ErrInvalidPersonalSignAccount = errors.New("invalid account as only the selected one can generate a signature")
|
||||
)
|
||||
|
||||
// SignParams required to sign messages
|
||||
type SignParams struct {
|
||||
Data interface{} `json:"data"`
|
||||
Address string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// RecoverParams are for calling `personal_ecRecover`
|
||||
type RecoverParams struct {
|
||||
Message string `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
// PublicAPI represents a set of APIs from the `web3.personal` namespace.
|
||||
type PublicAPI struct {
|
||||
rpcClient *rpc.Client
|
||||
rpcTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the personal API.
|
||||
func NewAPI() *PublicAPI {
|
||||
return &PublicAPI{
|
||||
rpcTimeout: 300 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// SetRPC sets RPC params (client and timeout) for the API calls.
|
||||
func (api *PublicAPI) SetRPC(rpcClient *rpc.Client, timeout time.Duration) {
|
||||
api.rpcClient = rpcClient
|
||||
api.rpcTimeout = timeout
|
||||
}
|
||||
|
||||
// Recover is an implementation of `personal_ecRecover` or `web3.personal.ecRecover` API
|
||||
func (api *PublicAPI) Recover(rpcParams RecoverParams) (addr types.Address, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), api.rpcTimeout)
|
||||
defer cancel()
|
||||
var gethAddr common.Address
|
||||
err = api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&gethAddr,
|
||||
api.rpcClient.UpstreamChainID,
|
||||
params.PersonalRecoverMethodName,
|
||||
rpcParams.Message, rpcParams.Signature)
|
||||
addr = types.Address(gethAddr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sign is an implementation of `personal_sign` or `web3.personal.sign` API
|
||||
func (api *PublicAPI) Sign(rpcParams SignParams, verifiedAccount *account.SelectedExtKey) (result types.HexBytes, err error) {
|
||||
if !strings.EqualFold(rpcParams.Address, verifiedAccount.Address.Hex()) {
|
||||
err = ErrInvalidPersonalSignAccount
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), api.rpcTimeout)
|
||||
defer cancel()
|
||||
var gethResult hexutil.Bytes
|
||||
err = api.rpcClient.CallContextIgnoringLocalHandlers(
|
||||
ctx,
|
||||
&gethResult,
|
||||
api.rpcClient.UpstreamChainID,
|
||||
params.PersonalSignMethodName,
|
||||
rpcParams.Data, rpcParams.Address, rpcParams.Password)
|
||||
result = types.HexBytes(gethResult)
|
||||
|
||||
return
|
||||
}
|
||||
51
vendor/github.com/status-im/status-go/services/personal/service.go
generated
vendored
Normal file
51
vendor/github.com/status-im/status-go/services/personal/service.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package personal
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/ethapi"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Service interface.
|
||||
var _ node.Lifecycle = (*Service)(nil)
|
||||
|
||||
// Service represents out own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
am *accounts.Manager
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New(am *accounts.Manager) *Service {
|
||||
return &Service{am}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "personal",
|
||||
Version: "1.0",
|
||||
Service: ethapi.NewLimitedPersonalAPI(s.am),
|
||||
Public: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
257
vendor/github.com/status-im/status-go/services/rpcfilters/api.go
generated
vendored
Normal file
257
vendor/github.com/status-im/status-go/services/rpcfilters/api.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
getrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFilterLivenessPeriod = 5 * time.Minute
|
||||
defaultLogsPeriod = 3 * time.Second
|
||||
defaultLogsQueryTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
errFilterNotFound = errors.New("filter not found")
|
||||
)
|
||||
|
||||
type filter interface {
|
||||
add(interface{}) error
|
||||
pop() interface{}
|
||||
stop()
|
||||
deadline() *time.Timer
|
||||
}
|
||||
|
||||
type ChainEvent interface {
|
||||
Start() error
|
||||
Stop()
|
||||
Subscribe() (id int, ch interface{})
|
||||
Unsubscribe(id int)
|
||||
}
|
||||
|
||||
// PublicAPI represents filter API that is exported to `eth` namespace
|
||||
type PublicAPI struct {
|
||||
filtersMu sync.Mutex
|
||||
filters map[getrpc.ID]filter
|
||||
|
||||
// filterLivenessLoop defines how often timeout loop is executed
|
||||
filterLivenessLoop time.Duration
|
||||
// filter liveness increased by this period when changes are requested
|
||||
filterLivenessPeriod time.Duration
|
||||
|
||||
client func() ContextCaller
|
||||
chainID func() uint64
|
||||
|
||||
latestBlockChangedEvent *latestBlockChangedEvent
|
||||
transactionSentToUpstreamEvent *transactionSentToUpstreamEvent
|
||||
}
|
||||
|
||||
// NewPublicAPI returns a reference to the PublicAPI object
|
||||
func NewPublicAPI(s *Service) *PublicAPI {
|
||||
api := &PublicAPI{
|
||||
filters: make(map[getrpc.ID]filter),
|
||||
latestBlockChangedEvent: s.latestBlockChangedEvent,
|
||||
transactionSentToUpstreamEvent: s.transactionSentToUpstreamEvent,
|
||||
|
||||
client: func() ContextCaller { return s.rpc.RPCClient() },
|
||||
chainID: func() uint64 { return s.rpc.RPCClient().UpstreamChainID },
|
||||
filterLivenessLoop: defaultFilterLivenessPeriod,
|
||||
filterLivenessPeriod: defaultFilterLivenessPeriod + 10*time.Second,
|
||||
}
|
||||
go api.timeoutLoop(s.quit)
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *PublicAPI) timeoutLoop(quit chan struct{}) {
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
case <-time.After(api.filterLivenessLoop):
|
||||
api.filtersMu.Lock()
|
||||
for id, f := range api.filters {
|
||||
deadline := f.deadline()
|
||||
if deadline == nil {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-deadline.C:
|
||||
delete(api.filters, id)
|
||||
f.stop()
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
api.filtersMu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *PublicAPI) NewFilter(crit filters.FilterCriteria) (getrpc.ID, error) {
|
||||
id := getrpc.ID(uuid.New())
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
f := &logsFilter{
|
||||
id: id,
|
||||
crit: ethereum.FilterQuery(crit),
|
||||
originalCrit: ethereum.FilterQuery(crit),
|
||||
done: make(chan struct{}),
|
||||
timer: time.NewTimer(api.filterLivenessPeriod),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
logsCache: newCache(defaultCacheSize),
|
||||
}
|
||||
api.filtersMu.Lock()
|
||||
api.filters[id] = f
|
||||
api.filtersMu.Unlock()
|
||||
go pollLogs(api.client(), api.chainID(), f, defaultLogsQueryTimeout, defaultLogsPeriod)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// NewBlockFilter is an implemenation of `eth_newBlockFilter` API
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter
|
||||
func (api *PublicAPI) NewBlockFilter() getrpc.ID {
|
||||
api.filtersMu.Lock()
|
||||
defer api.filtersMu.Unlock()
|
||||
|
||||
f := newHashFilter()
|
||||
id := getrpc.ID(uuid.New())
|
||||
|
||||
api.filters[id] = f
|
||||
|
||||
go func() {
|
||||
id, si := api.latestBlockChangedEvent.Subscribe()
|
||||
s, ok := si.(chan common.Hash)
|
||||
if !ok {
|
||||
panic("latestBlockChangedEvent returned wrong type")
|
||||
}
|
||||
|
||||
defer api.latestBlockChangedEvent.Unsubscribe(id)
|
||||
|
||||
for {
|
||||
select {
|
||||
case hash := <-s:
|
||||
if err := f.add(hash); err != nil {
|
||||
log.Error("error adding value to filter", "hash", hash, "error", err)
|
||||
}
|
||||
case <-f.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// NewPendingTransactionFilter is an implementation of `eth_newPendingTransactionFilter` API
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter
|
||||
func (api *PublicAPI) NewPendingTransactionFilter() getrpc.ID {
|
||||
api.filtersMu.Lock()
|
||||
defer api.filtersMu.Unlock()
|
||||
|
||||
f := newHashFilter()
|
||||
id := getrpc.ID(uuid.New())
|
||||
|
||||
api.filters[id] = f
|
||||
|
||||
go func() {
|
||||
id, si := api.transactionSentToUpstreamEvent.Subscribe()
|
||||
s, ok := si.(chan *PendingTxInfo)
|
||||
if !ok {
|
||||
panic("transactionSentToUpstreamEvent returned wrong type")
|
||||
}
|
||||
defer api.transactionSentToUpstreamEvent.Unsubscribe(id)
|
||||
|
||||
for {
|
||||
select {
|
||||
case hash := <-s:
|
||||
if err := f.add(hash); err != nil {
|
||||
log.Error("error adding value to filter", "hash", hash, "error", err)
|
||||
}
|
||||
case <-f.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return id
|
||||
|
||||
}
|
||||
|
||||
// UninstallFilter is an implemenation of `eth_uninstallFilter` API
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter
|
||||
func (api *PublicAPI) UninstallFilter(id getrpc.ID) bool {
|
||||
api.filtersMu.Lock()
|
||||
f, found := api.filters[id]
|
||||
if found {
|
||||
delete(api.filters, id)
|
||||
}
|
||||
api.filtersMu.Unlock()
|
||||
|
||||
if found {
|
||||
f.stop()
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
// GetFilterLogs returns the logs for the filter with the given id.
|
||||
// If the filter could not be found an empty array of logs is returned.
|
||||
//
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs
|
||||
func (api *PublicAPI) GetFilterLogs(ctx context.Context, id getrpc.ID) ([]types.Log, error) {
|
||||
api.filtersMu.Lock()
|
||||
f, exist := api.filters[id]
|
||||
api.filtersMu.Unlock()
|
||||
if !exist {
|
||||
return []types.Log{}, errFilterNotFound
|
||||
}
|
||||
logs, ok := f.(*logsFilter)
|
||||
if !ok {
|
||||
return []types.Log{}, fmt.Errorf("filter with ID %v is not of logs type", id)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, defaultLogsQueryTimeout)
|
||||
defer cancel()
|
||||
rst, err := getLogs(ctx, api.client(), api.chainID(), logs.originalCrit)
|
||||
return rst, err
|
||||
}
|
||||
|
||||
// GetFilterChanges returns the hashes for the filter with the given id since
|
||||
// last time it was called. This can be used for polling.
|
||||
//
|
||||
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges
|
||||
func (api *PublicAPI) GetFilterChanges(id getrpc.ID) (interface{}, error) {
|
||||
api.filtersMu.Lock()
|
||||
defer api.filtersMu.Unlock()
|
||||
|
||||
if f, found := api.filters[id]; found {
|
||||
deadline := f.deadline()
|
||||
if deadline != nil {
|
||||
if !deadline.Stop() {
|
||||
// timer expired but filter is not yet removed in timeout loop
|
||||
// receive timer value and reset timer
|
||||
// see https://golang.org/pkg/time/#Timer.Reset
|
||||
<-deadline.C
|
||||
}
|
||||
deadline.Reset(api.filterLivenessPeriod)
|
||||
}
|
||||
rst := f.pop()
|
||||
if rst == nil {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
return rst, nil
|
||||
}
|
||||
return []interface{}{}, errFilterNotFound
|
||||
}
|
||||
56
vendor/github.com/status-im/status-go/services/rpcfilters/hash_filter.go
generated
vendored
Normal file
56
vendor/github.com/status-im/status-go/services/rpcfilters/hash_filter.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type hashFilter struct {
|
||||
hashes []common.Hash
|
||||
mu sync.Mutex
|
||||
done chan struct{}
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// add adds a hash to the hashFilter
|
||||
func (f *hashFilter) add(data interface{}) error {
|
||||
hash, ok := data.(common.Hash)
|
||||
if !ok {
|
||||
return errors.New("provided data is not a common.Hash")
|
||||
}
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.hashes = append(f.hashes, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// pop returns all the hashes stored in the hashFilter and clears the hashFilter contents
|
||||
func (f *hashFilter) pop() interface{} {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
hashes := f.hashes
|
||||
f.hashes = nil
|
||||
return hashes
|
||||
}
|
||||
|
||||
func (f *hashFilter) stop() {
|
||||
select {
|
||||
case <-f.done:
|
||||
return
|
||||
default:
|
||||
close(f.done)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *hashFilter) deadline() *time.Timer {
|
||||
return f.timer
|
||||
}
|
||||
|
||||
func newHashFilter() *hashFilter {
|
||||
return &hashFilter{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
164
vendor/github.com/status-im/status-go/services/rpcfilters/latest_block_changed_event.go
generated
vendored
Normal file
164
vendor/github.com/status-im/status-go/services/rpcfilters/latest_block_changed_event.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTickerPeriod = 3 * time.Second
|
||||
defaultReportHistorySize = 20
|
||||
)
|
||||
|
||||
// ringArray represents a thread-safe capped collection of hashes.
|
||||
type ringArray struct {
|
||||
mu sync.Mutex
|
||||
maxCount int
|
||||
currentIndex int
|
||||
blocks []common.Hash
|
||||
}
|
||||
|
||||
func newRingArray(maxCount int) *ringArray {
|
||||
return &ringArray{
|
||||
maxCount: maxCount,
|
||||
blocks: make([]common.Hash, maxCount),
|
||||
}
|
||||
}
|
||||
|
||||
// TryAddUnique adds a hash to the array if the array doesn't have it.
|
||||
// Returns true if the element was added.
|
||||
func (r *ringArray) TryAddUnique(hash common.Hash) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.has(hash) {
|
||||
return false
|
||||
}
|
||||
|
||||
r.blocks[r.currentIndex] = hash
|
||||
r.currentIndex++
|
||||
if r.currentIndex >= len(r.blocks) {
|
||||
r.currentIndex = 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// has returns `true` if the hash is in the array.
|
||||
// It has linear complexity but on short arrays it isn't worth optimizing.
|
||||
func (r *ringArray) has(hash common.Hash) bool {
|
||||
for _, h := range r.blocks {
|
||||
if h == hash {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// latestBlockChangedEvent represents an event that one can subscribe to
|
||||
type latestBlockChangedEvent struct {
|
||||
sxMu sync.Mutex
|
||||
sx map[int]chan common.Hash
|
||||
|
||||
reportedBlocks *ringArray
|
||||
|
||||
provider latestBlockProvider
|
||||
quit chan struct{}
|
||||
tickerPeriod time.Duration
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) Start() error {
|
||||
if e.quit != nil {
|
||||
return errors.New("latest block changed event is already started")
|
||||
}
|
||||
|
||||
e.quit = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(e.tickerPeriod)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if e.numberOfSubscriptions() == 0 {
|
||||
continue
|
||||
}
|
||||
latestBlock, err := e.provider.GetLatestBlock()
|
||||
if err != nil {
|
||||
log.Error("error while receiving latest block", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
e.processLatestBlock(latestBlock)
|
||||
case <-e.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) numberOfSubscriptions() int {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
return len(e.sx)
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) processLatestBlock(latestBlock blockInfo) {
|
||||
// if we received the hash we already received before, don't add it
|
||||
if !e.reportedBlocks.TryAddUnique(latestBlock.Hash) {
|
||||
return
|
||||
}
|
||||
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
for _, channel := range e.sx {
|
||||
channel <- latestBlock.Hash
|
||||
}
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) Stop() {
|
||||
if e.quit == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-e.quit:
|
||||
e.quit = nil
|
||||
return
|
||||
default:
|
||||
close(e.quit)
|
||||
}
|
||||
|
||||
e.quit = nil
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) Subscribe() (int, interface{}) {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
channel := make(chan common.Hash)
|
||||
id := len(e.sx)
|
||||
e.sx[id] = channel
|
||||
return id, channel
|
||||
}
|
||||
|
||||
func (e *latestBlockChangedEvent) Unsubscribe(id int) {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
delete(e.sx, id)
|
||||
}
|
||||
|
||||
func newLatestBlockChangedEvent(provider latestBlockProvider) *latestBlockChangedEvent {
|
||||
return &latestBlockChangedEvent{
|
||||
sx: make(map[int]chan common.Hash),
|
||||
provider: provider,
|
||||
reportedBlocks: newRingArray(defaultReportHistorySize),
|
||||
tickerPeriod: defaultTickerPeriod,
|
||||
}
|
||||
}
|
||||
58
vendor/github.com/status-im/status-go/services/rpcfilters/latest_block_provider.go
generated
vendored
Normal file
58
vendor/github.com/status-im/status-go/services/rpcfilters/latest_block_provider.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
type rpcProvider interface {
|
||||
RPCClient() *rpc.Client
|
||||
}
|
||||
|
||||
// blockInfo contains the hash and the number of the latest block
|
||||
type blockInfo struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
NumberBytes hexutil.Bytes `json:"number"`
|
||||
}
|
||||
|
||||
// Number returns a big.Int representation of the encoded block number.
|
||||
func (i blockInfo) Number() *big.Int {
|
||||
number := big.NewInt(0)
|
||||
number.SetBytes(i.NumberBytes)
|
||||
return number
|
||||
}
|
||||
|
||||
// latestBlockProvider provides the latest block info from the blockchain
|
||||
type latestBlockProvider interface {
|
||||
GetLatestBlock() (blockInfo, error)
|
||||
}
|
||||
|
||||
// latestBlockProviderRPC is an implementation of latestBlockProvider interface
|
||||
// that requests a block using an RPC client provided
|
||||
type latestBlockProviderRPC struct {
|
||||
rpc rpcProvider
|
||||
}
|
||||
|
||||
// GetLatestBlock returns the block info
|
||||
func (p *latestBlockProviderRPC) GetLatestBlock() (blockInfo, error) {
|
||||
rpcClient := p.rpc.RPCClient()
|
||||
|
||||
if rpcClient == nil {
|
||||
return blockInfo{}, errors.New("no active RPC client: is the node running?")
|
||||
}
|
||||
|
||||
var result blockInfo
|
||||
|
||||
err := rpcClient.Call(&result, rpcClient.UpstreamChainID, "eth_getBlockByNumber", "latest", false)
|
||||
|
||||
if err != nil {
|
||||
return blockInfo{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
70
vendor/github.com/status-im/status-go/services/rpcfilters/latest_logs.go
generated
vendored
Normal file
70
vendor/github.com/status-im/status-go/services/rpcfilters/latest_logs.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
getRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// ContextCaller provides CallContext method as ethereums rpc.Client.
|
||||
type ContextCaller interface {
|
||||
CallContext(ctx context.Context, result interface{}, chainID uint64, method string, args ...interface{}) error
|
||||
}
|
||||
|
||||
func pollLogs(client ContextCaller, chainID uint64, f *logsFilter, timeout, period time.Duration) {
|
||||
query := func() {
|
||||
ctx, cancel := context.WithTimeout(f.ctx, timeout)
|
||||
defer cancel()
|
||||
logs, err := getLogs(ctx, client, chainID, f.criteria())
|
||||
if err != nil {
|
||||
log.Error("Error fetch logs", "criteria", f.crit, "error", err)
|
||||
return
|
||||
}
|
||||
if err := f.add(logs); err != nil {
|
||||
log.Error("Error adding logs", "logs", logs, "error", err)
|
||||
}
|
||||
}
|
||||
query()
|
||||
latest := time.NewTicker(period)
|
||||
defer latest.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-latest.C:
|
||||
query()
|
||||
case <-f.done:
|
||||
log.Debug("Filter was stopped", "ID", f.id, "crit", f.crit)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func getLogs(ctx context.Context, client ContextCaller, chainID uint64, crit ethereum.FilterQuery) (rst []types.Log, err error) {
|
||||
return rst, client.CallContext(ctx, &rst, chainID, "eth_getLogs", toFilterArg(crit))
|
||||
}
|
||||
|
||||
func toFilterArg(q ethereum.FilterQuery) interface{} {
|
||||
arg := map[string]interface{}{
|
||||
"fromBlock": toBlockNumArg(q.FromBlock),
|
||||
"toBlock": toBlockNumArg(q.ToBlock),
|
||||
"address": q.Addresses,
|
||||
"topics": q.Topics,
|
||||
}
|
||||
if q.FromBlock == nil {
|
||||
arg["fromBlock"] = "0x0"
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func toBlockNumArg(number *big.Int) string {
|
||||
if number == nil || number.Int64() == getRpc.LatestBlockNumber.Int64() {
|
||||
return "latest"
|
||||
} else if number.Int64() == getRpc.PendingBlockNumber.Int64() {
|
||||
return "pending"
|
||||
}
|
||||
return hexutil.EncodeBig(number)
|
||||
}
|
||||
148
vendor/github.com/status-im/status-go/services/rpcfilters/logs_cache.go
generated
vendored
Normal file
148
vendor/github.com/status-im/status-go/services/rpcfilters/logs_cache.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCacheSize = 20
|
||||
)
|
||||
|
||||
type cacheRecord struct {
|
||||
block uint64
|
||||
hash common.Hash
|
||||
logs []types.Log
|
||||
}
|
||||
|
||||
func newCache(size int) *cache {
|
||||
return &cache{
|
||||
records: make([]cacheRecord, 0, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
mu sync.RWMutex
|
||||
size int // length of the records
|
||||
records []cacheRecord
|
||||
}
|
||||
|
||||
// add inserts logs into cache and returns added and replaced logs.
|
||||
// replaced logs with will be returned with Removed=true.
|
||||
func (c *cache) add(logs []types.Log) (added, replaced []types.Log, err error) {
|
||||
if len(logs) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
aggregated := aggregateLogs(logs, c.size) // size doesn't change
|
||||
if len(aggregated) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if err := checkLogsAreInOrder(aggregated); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
// find common block. e.g. [3,4] and [1,2,3,4] = 3
|
||||
last := 0
|
||||
if len(c.records) > 0 {
|
||||
last = len(c.records) - 1
|
||||
for aggregated[0].block < c.records[last].block && last > 0 {
|
||||
last--
|
||||
}
|
||||
}
|
||||
c.records, added, replaced = merge(last, c.records, aggregated)
|
||||
if lth := len(c.records); lth > c.size {
|
||||
copy(c.records, c.records[lth-c.size:])
|
||||
}
|
||||
return added, replaced, nil
|
||||
}
|
||||
|
||||
func (c *cache) earliestBlockNum() uint64 {
|
||||
if len(c.records) == 0 {
|
||||
return 0
|
||||
}
|
||||
return c.records[0].block
|
||||
}
|
||||
|
||||
func checkLogsAreInOrder(records []cacheRecord) error {
|
||||
for prev, i := 0, 1; i < len(records); i++ {
|
||||
if records[prev].block == records[i].block-1 {
|
||||
prev = i
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"logs must be delivered straight in order. gaps between blocks '%d' and '%d'",
|
||||
records[prev].block, records[i].block,
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// merge merges received records into old slice starting at provided position, example:
|
||||
// [1, 2, 3]
|
||||
//
|
||||
// [2, 3, 4]
|
||||
//
|
||||
// [1, 2, 3, 4]
|
||||
// if hash doesn't match previously received hash - such block was removed due to reorg
|
||||
// logs that were a part of that block will be returned with Removed set to true
|
||||
func merge(last int, old, received []cacheRecord) ([]cacheRecord, []types.Log, []types.Log) {
|
||||
var (
|
||||
added, replaced []types.Log
|
||||
block uint64
|
||||
hash common.Hash
|
||||
)
|
||||
for i := range received {
|
||||
record := received[i]
|
||||
if last < len(old) {
|
||||
block = old[last].block
|
||||
hash = old[last].hash
|
||||
}
|
||||
if record.block > block {
|
||||
// simply add new records
|
||||
added = append(added, record.logs...)
|
||||
old = append(old, record)
|
||||
} else if record.hash != hash && record.block == block {
|
||||
// record hash is not equal to previous record hash at the same height
|
||||
// replace record in hash and add logs as replaced
|
||||
replaced = append(replaced, old[last].logs...)
|
||||
added = append(added, record.logs...)
|
||||
old[last] = record
|
||||
}
|
||||
last++
|
||||
}
|
||||
return old, added, replaced
|
||||
}
|
||||
|
||||
// aggregateLogs creates at most requested amount of cacheRecords from provided logs.
|
||||
// cacheRecords will be sorted in ascending order, starting from lowest block to highest.
|
||||
func aggregateLogs(logs []types.Log, limit int) []cacheRecord {
|
||||
// sort in reverse order, so that iteration will start from latest blocks
|
||||
sort.Slice(logs, func(i, j int) bool {
|
||||
return logs[i].BlockNumber > logs[j].BlockNumber
|
||||
})
|
||||
rst := make([]cacheRecord, limit)
|
||||
pos, start := len(rst)-1, 0
|
||||
var hash common.Hash
|
||||
for i := range logs {
|
||||
log := logs[i]
|
||||
if (hash != common.Hash{}) && hash != log.BlockHash {
|
||||
rst[pos].logs = logs[start:i]
|
||||
start = i
|
||||
if pos-1 < 0 {
|
||||
break
|
||||
}
|
||||
pos--
|
||||
}
|
||||
rst[pos].logs = logs[start:]
|
||||
rst[pos].block = log.BlockNumber
|
||||
rst[pos].hash = log.BlockHash
|
||||
hash = log.BlockHash
|
||||
}
|
||||
return rst[pos:]
|
||||
}
|
||||
161
vendor/github.com/status-im/status-go/services/rpcfilters/logs_filter.go
generated
vendored
Normal file
161
vendor/github.com/status-im/status-go/services/rpcfilters/logs_filter.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type logsFilter struct {
|
||||
mu sync.RWMutex
|
||||
logs []types.Log
|
||||
crit ethereum.FilterQuery // will be modified and different from original
|
||||
|
||||
originalCrit ethereum.FilterQuery // not modified version of the criteria
|
||||
|
||||
logsCache *cache
|
||||
|
||||
id rpc.ID
|
||||
timer *time.Timer
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (f *logsFilter) criteria() ethereum.FilterQuery {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
return f.crit
|
||||
}
|
||||
|
||||
func (f *logsFilter) add(data interface{}) error {
|
||||
logs, ok := data.([]types.Log)
|
||||
if !ok {
|
||||
return fmt.Errorf("can't cast %v to types.Log", data)
|
||||
}
|
||||
filtered := filterLogs(logs, f.crit)
|
||||
if len(filtered) > 0 {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
added, replaced, err := f.logsCache.add(filtered)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, log := range replaced {
|
||||
log.Removed = true
|
||||
f.logs = append(f.logs, log)
|
||||
}
|
||||
if len(added) > 0 {
|
||||
f.logs = append(f.logs, added...)
|
||||
}
|
||||
// if there was no replaced logs - keep polling only latest logs
|
||||
if len(replaced) == 0 {
|
||||
adjustFromBlock(&f.crit)
|
||||
} else {
|
||||
// otherwise poll earliest known block in cache
|
||||
earliest := f.logsCache.earliestBlockNum()
|
||||
if earliest != 0 {
|
||||
f.crit.FromBlock = new(big.Int).SetUint64(earliest)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *logsFilter) pop() interface{} {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
rst := f.logs
|
||||
f.logs = nil
|
||||
return rst
|
||||
}
|
||||
|
||||
func (f *logsFilter) stop() {
|
||||
select {
|
||||
case <-f.done:
|
||||
return
|
||||
default:
|
||||
close(f.done)
|
||||
if f.cancel != nil {
|
||||
f.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *logsFilter) deadline() *time.Timer {
|
||||
return f.timer
|
||||
}
|
||||
|
||||
// adjustFromBlock adjusts crit.FromBlock to latest to avoid querying same logs.
|
||||
func adjustFromBlock(crit *ethereum.FilterQuery) {
|
||||
latest := big.NewInt(rpc.LatestBlockNumber.Int64())
|
||||
// don't adjust if filter is not interested in newer blocks
|
||||
if crit.ToBlock != nil && crit.ToBlock.Cmp(latest) == 1 {
|
||||
return
|
||||
}
|
||||
// don't adjust if from block is already pending
|
||||
if crit.FromBlock != nil && crit.FromBlock.Cmp(latest) == -1 {
|
||||
return
|
||||
}
|
||||
crit.FromBlock = latest
|
||||
}
|
||||
|
||||
func includes(addresses []common.Address, a common.Address) bool {
|
||||
for _, addr := range addresses {
|
||||
if addr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// filterLogs creates a slice of logs matching the given criteria.
|
||||
func filterLogs(logs []types.Log, crit ethereum.FilterQuery) (
|
||||
ret []types.Log) {
|
||||
for _, log := range logs {
|
||||
if matchLog(log, crit) {
|
||||
ret = append(ret, log)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func matchLog(log types.Log, crit ethereum.FilterQuery) bool {
|
||||
if crit.FromBlock != nil && crit.FromBlock.Int64() >= 0 && crit.FromBlock.Uint64() > log.BlockNumber {
|
||||
return false
|
||||
}
|
||||
if crit.ToBlock != nil && crit.ToBlock.Int64() >= 0 && crit.ToBlock.Uint64() < log.BlockNumber {
|
||||
return false
|
||||
}
|
||||
if len(crit.Addresses) > 0 && !includes(crit.Addresses, log.Address) {
|
||||
return false
|
||||
}
|
||||
if len(crit.Topics) > len(log.Topics) {
|
||||
return false
|
||||
}
|
||||
return matchTopics(log, crit.Topics)
|
||||
}
|
||||
|
||||
func matchTopics(log types.Log, topics [][]common.Hash) bool {
|
||||
for i, sub := range topics {
|
||||
match := len(sub) == 0 // empty rule set == wildcard
|
||||
for _, topic := range sub {
|
||||
if log.Topics[i] == topic {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
75
vendor/github.com/status-im/status-go/services/rpcfilters/service.go
generated
vendored
Normal file
75
vendor/github.com/status-im/status-go/services/rpcfilters/service.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Lifecycle interface.
|
||||
var _ node.Lifecycle = (*Service)(nil)
|
||||
|
||||
// Service represents out own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
latestBlockChangedEvent *latestBlockChangedEvent
|
||||
transactionSentToUpstreamEvent *transactionSentToUpstreamEvent
|
||||
rpc rpcProvider
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New(rpc rpcProvider) *Service {
|
||||
provider := &latestBlockProviderRPC{rpc}
|
||||
latestBlockChangedEvent := newLatestBlockChangedEvent(provider)
|
||||
transactionSentToUpstreamEvent := newTransactionSentToUpstreamEvent()
|
||||
return &Service{
|
||||
latestBlockChangedEvent: latestBlockChangedEvent,
|
||||
transactionSentToUpstreamEvent: transactionSentToUpstreamEvent,
|
||||
|
||||
rpc: rpc,
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAPI(s),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
func (s *Service) Start() error {
|
||||
s.quit = make(chan struct{})
|
||||
err := s.transactionSentToUpstreamEvent.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.latestBlockChangedEvent.Start()
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
close(s.quit)
|
||||
s.transactionSentToUpstreamEvent.Stop()
|
||||
s.latestBlockChangedEvent.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) TransactionSentToUpstreamEvent() ChainEvent {
|
||||
return s.transactionSentToUpstreamEvent
|
||||
}
|
||||
|
||||
func (s *Service) TriggerTransactionSentToUpstreamEvent(txInfo *PendingTxInfo) {
|
||||
s.transactionSentToUpstreamEvent.Trigger(txInfo)
|
||||
}
|
||||
112
vendor/github.com/status-im/status-go/services/rpcfilters/transaction_sent_to_upstream_event.go
generated
vendored
Normal file
112
vendor/github.com/status-im/status-go/services/rpcfilters/transaction_sent_to_upstream_event.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
package rpcfilters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type PendingTxInfo struct {
|
||||
Hash common.Hash
|
||||
Type string
|
||||
From common.Address
|
||||
ChainID uint64
|
||||
}
|
||||
|
||||
// transactionSentToUpstreamEvent represents an event that one can subscribe to
|
||||
type transactionSentToUpstreamEvent struct {
|
||||
sxMu sync.Mutex
|
||||
sx map[int]chan *PendingTxInfo
|
||||
listener chan *PendingTxInfo
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func newTransactionSentToUpstreamEvent() *transactionSentToUpstreamEvent {
|
||||
return &transactionSentToUpstreamEvent{
|
||||
sx: make(map[int]chan *PendingTxInfo),
|
||||
listener: make(chan *PendingTxInfo),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) Start() error {
|
||||
if e.quit != nil {
|
||||
return errors.New("latest transaction sent to upstream event is already started")
|
||||
}
|
||||
|
||||
e.quit = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case transactionInfo := <-e.listener:
|
||||
if e.numberOfSubscriptions() == 0 {
|
||||
continue
|
||||
}
|
||||
e.processTransactionSentToUpstream(transactionInfo)
|
||||
case <-e.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) numberOfSubscriptions() int {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
return len(e.sx)
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) processTransactionSentToUpstream(transactionInfo *PendingTxInfo) {
|
||||
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
for id, channel := range e.sx {
|
||||
select {
|
||||
case channel <- transactionInfo:
|
||||
default:
|
||||
log.Error("dropping messages %s for subscriotion %d because the channel is full", transactionInfo, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) Stop() {
|
||||
if e.quit == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-e.quit:
|
||||
return
|
||||
default:
|
||||
close(e.quit)
|
||||
}
|
||||
|
||||
e.quit = nil
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) Subscribe() (int, interface{}) {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
channel := make(chan *PendingTxInfo, 512)
|
||||
id := len(e.sx)
|
||||
e.sx[id] = channel
|
||||
return id, channel
|
||||
}
|
||||
|
||||
func (e *transactionSentToUpstreamEvent) Unsubscribe(id int) {
|
||||
e.sxMu.Lock()
|
||||
defer e.sxMu.Unlock()
|
||||
|
||||
delete(e.sx, id)
|
||||
}
|
||||
|
||||
// Trigger gets called in order to trigger the event
|
||||
func (e *transactionSentToUpstreamEvent) Trigger(transactionInfo *PendingTxInfo) {
|
||||
e.listener <- transactionInfo
|
||||
}
|
||||
34
vendor/github.com/status-im/status-go/services/rpcstats/api.go
generated
vendored
Normal file
34
vendor/github.com/status-im/status-go/services/rpcstats/api.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package rpcstats
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// PublicAPI represents a set of APIs from the namespace.
|
||||
type PublicAPI struct {
|
||||
s *Service
|
||||
}
|
||||
|
||||
// NewAPI creates an instance of the API.
|
||||
func NewAPI(s *Service) *PublicAPI {
|
||||
return &PublicAPI{s: s}
|
||||
}
|
||||
|
||||
// Reset resets RPC usage stats
|
||||
func (api *PublicAPI) Reset(context context.Context) {
|
||||
resetStats()
|
||||
}
|
||||
|
||||
type RPCStats struct {
|
||||
Total uint `json:"total"`
|
||||
CounterPerMethod map[string]uint `json:"methods"`
|
||||
}
|
||||
|
||||
// GetStats retrun RPC usage stats
|
||||
func (api *PublicAPI) GetStats(context context.Context) (RPCStats, error) {
|
||||
total, perMethod := getStats()
|
||||
return RPCStats{
|
||||
Total: total,
|
||||
CounterPerMethod: perMethod,
|
||||
}, nil
|
||||
}
|
||||
44
vendor/github.com/status-im/status-go/services/rpcstats/service.go
generated
vendored
Normal file
44
vendor/github.com/status-im/status-go/services/rpcstats/service.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
package rpcstats
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Service represents our own implementation of status status operations.
|
||||
type Service struct{}
|
||||
|
||||
// New returns a new Service.
|
||||
func New() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "rpcstats",
|
||||
Version: "1.0",
|
||||
Service: NewAPI(s),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Start() error {
|
||||
resetStats()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
// It does nothing in this case but is required by `node.Service` interface.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
48
vendor/github.com/status-im/status-go/services/rpcstats/stats.go
generated
vendored
Normal file
48
vendor/github.com/status-im/status-go/services/rpcstats/stats.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package rpcstats
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type RPCUsageStats struct {
|
||||
total uint
|
||||
counterPerMethod map[string]uint
|
||||
rw sync.RWMutex
|
||||
}
|
||||
|
||||
var stats *RPCUsageStats
|
||||
|
||||
func getInstance() *RPCUsageStats {
|
||||
if stats == nil {
|
||||
stats = &RPCUsageStats{
|
||||
total: 0,
|
||||
counterPerMethod: map[string]uint{},
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func getStats() (uint, map[string]uint) {
|
||||
stats := getInstance()
|
||||
stats.rw.RLock()
|
||||
defer stats.rw.RUnlock()
|
||||
return stats.total, stats.counterPerMethod
|
||||
}
|
||||
|
||||
func resetStats() {
|
||||
stats := getInstance()
|
||||
stats.rw.Lock()
|
||||
defer stats.rw.Unlock()
|
||||
|
||||
stats.total = 0
|
||||
stats.counterPerMethod = map[string]uint{}
|
||||
}
|
||||
|
||||
func CountCall(method string) {
|
||||
stats := getInstance()
|
||||
stats.rw.Lock()
|
||||
defer stats.rw.Unlock()
|
||||
|
||||
stats.total++
|
||||
stats.counterPerMethod[method]++
|
||||
}
|
||||
89
vendor/github.com/status-im/status-go/services/status/service.go
generated
vendored
Normal file
89
vendor/github.com/status-im/status-go/services/status/service.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/protocol"
|
||||
"github.com/status-im/status-go/protocol/common/shard"
|
||||
)
|
||||
|
||||
// Make sure that Service implements node.Lifecycle interface.
|
||||
var _ node.Lifecycle = (*Service)(nil)
|
||||
var ErrNotInitialized = errors.New("status public api not initialized")
|
||||
|
||||
// Service represents out own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
messenger *protocol.Messenger
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (s *Service) Init(messenger *protocol.Messenger) {
|
||||
s.messenger = messenger
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: "status",
|
||||
Version: "1.0",
|
||||
Service: NewPublicAPI(s),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewPublicAPI returns a reference to the PublicAPI object
|
||||
func NewPublicAPI(s *Service) *PublicAPI {
|
||||
api := &PublicAPI{
|
||||
service: s,
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type PublicAPI struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func (p *PublicAPI) CommunityInfo(communityID types.HexBytes, shard *shard.Shard) (json.RawMessage, error) {
|
||||
if p.service.messenger == nil {
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
community, err := p.service.messenger.FetchCommunity(&protocol.FetchCommunityRequest{
|
||||
CommunityKey: communityID.String(),
|
||||
Shard: shard,
|
||||
TryDatabase: true,
|
||||
WaitForResponse: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return community.MarshalPublicAPIJSON()
|
||||
}
|
||||
481
vendor/github.com/status-im/status-go/services/stickers/api.go
generated
vendored
Normal file
481
vendor/github.com/status-im/status-go/services/stickers/api.go
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/zenthangplus/goccm"
|
||||
"olympos.io/encoding/edn"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/contracts"
|
||||
"github.com/status-im/status-go/contracts/stickers"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/ipfs"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/server"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
const maxConcurrentRequests = 3
|
||||
const requestTimeout = time.Duration(5) * time.Second
|
||||
|
||||
// ConnectionType constants
|
||||
type stickerStatus int
|
||||
|
||||
const (
|
||||
statusAvailable stickerStatus = iota
|
||||
statusInstalled
|
||||
statusPending
|
||||
statusPurchased
|
||||
)
|
||||
|
||||
type API struct {
|
||||
contractMaker *contracts.ContractMaker
|
||||
accountsManager *account.GethManager
|
||||
accountsDB *accounts.Database
|
||||
pendingTracker *transactions.PendingTxTracker
|
||||
|
||||
keyStoreDir string
|
||||
downloader *ipfs.Downloader
|
||||
httpServer *server.MediaServer
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
type Sticker struct {
|
||||
PackID *bigint.BigInt `json:"packID,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
}
|
||||
|
||||
type StickerPack struct {
|
||||
ID *bigint.BigInt `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Author string `json:"author"`
|
||||
Owner common.Address `json:"owner,omitempty"`
|
||||
Price *bigint.BigInt `json:"price"`
|
||||
Preview string `json:"preview"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Stickers []Sticker `json:"stickers"`
|
||||
|
||||
Status stickerStatus `json:"status"`
|
||||
}
|
||||
|
||||
type StickerPackCollection map[uint]StickerPack
|
||||
|
||||
type ednSticker struct {
|
||||
Hash string
|
||||
}
|
||||
|
||||
type ednStickerPack struct {
|
||||
Name string
|
||||
Author string
|
||||
Thumbnail string
|
||||
Preview string
|
||||
Stickers []ednSticker
|
||||
}
|
||||
type ednStickerPackInfo struct {
|
||||
Meta ednStickerPack
|
||||
}
|
||||
|
||||
func NewAPI(ctx context.Context, acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, pendingTracker *transactions.PendingTxTracker, keyStoreDir string, downloader *ipfs.Downloader, httpServer *server.MediaServer) *API {
|
||||
result := &API{
|
||||
contractMaker: &contracts.ContractMaker{
|
||||
RPCClient: rpcClient,
|
||||
},
|
||||
accountsManager: accountsManager,
|
||||
accountsDB: acc,
|
||||
pendingTracker: pendingTracker,
|
||||
keyStoreDir: keyStoreDir,
|
||||
downloader: downloader,
|
||||
ctx: ctx,
|
||||
httpServer: httpServer,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (api *API) Market(chainID uint64) ([]StickerPack, error) {
|
||||
// TODO: eventually this should be changed to include pagination
|
||||
accs, err := api.accountsDB.GetActiveAccounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allStickerPacks, err := api.getContractPacks(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
purchasedPacks := make(map[uint]struct{})
|
||||
|
||||
purchasedPackChan := make(chan *big.Int)
|
||||
errChan := make(chan error)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
go api.getAccountsPurchasedPack(chainID, accs, purchasedPackChan, errChan, doneChan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case packID := <-purchasedPackChan:
|
||||
if packID != nil {
|
||||
purchasedPacks[uint(packID.Uint64())] = struct{}{}
|
||||
}
|
||||
|
||||
case <-doneChan:
|
||||
var result []StickerPack
|
||||
for _, pack := range allStickerPacks {
|
||||
packID := uint(pack.ID.Uint64())
|
||||
_, isPurchased := purchasedPacks[packID]
|
||||
if isPurchased {
|
||||
pack.Status = statusPurchased
|
||||
} else {
|
||||
pack.Status = statusAvailable
|
||||
}
|
||||
result = append(result, pack)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) execTokenPackID(chainID uint64, tokenIDs []*big.Int, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
|
||||
defer close(doneChan)
|
||||
defer close(errChan)
|
||||
defer close(resultChan)
|
||||
|
||||
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
if len(tokenIDs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
|
||||
|
||||
c := goccm.New(maxConcurrentRequests)
|
||||
for _, tokenID := range tokenIDs {
|
||||
c.Wait()
|
||||
go func(tokenID *big.Int) {
|
||||
defer c.Done()
|
||||
packID, err := stickerPack.TokenPackId(callOpts, tokenID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
resultChan <- packID
|
||||
}(tokenID)
|
||||
}
|
||||
c.WaitAllDone()
|
||||
}
|
||||
|
||||
func (api *API) getTokenPackIDs(chainID uint64, tokenIDs []*big.Int) ([]*big.Int, error) {
|
||||
tokenPackIDChan := make(chan *big.Int)
|
||||
errChan := make(chan error)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
|
||||
go api.execTokenPackID(chainID, tokenIDs, tokenPackIDChan, errChan, doneChan)
|
||||
|
||||
var tokenPackIDs []*big.Int
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return tokenPackIDs, nil
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case t := <-tokenPackIDChan:
|
||||
if t != nil {
|
||||
tokenPackIDs = append(tokenPackIDs, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) getPurchasedPackIDs(chainID uint64, account types.Address) ([]*big.Int, error) {
|
||||
// TODO: this should be replaced in the future by something like TheGraph to reduce the number of requests to infura
|
||||
|
||||
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
|
||||
|
||||
balance, err := stickerPack.BalanceOf(callOpts, common.Address(account))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenIDs, err := api.getTokenOwnerOfIndex(chainID, account, balance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return api.getTokenPackIDs(chainID, tokenIDs)
|
||||
}
|
||||
|
||||
func (api *API) fetchStickerPacks(chainID uint64, resultChan chan<- *StickerPack, errChan chan<- error, doneChan chan<- struct{}) {
|
||||
defer close(doneChan)
|
||||
defer close(errChan)
|
||||
defer close(resultChan)
|
||||
|
||||
installedPacks, err := api.Installed()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
pendingPacks, err := api.pendingStickerPacks()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
stickerType, err := api.contractMaker.NewStickerType(chainID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
|
||||
|
||||
numPacks, err := stickerType.PackCount(callOpts)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
if numPacks.Uint64() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c := goccm.New(maxConcurrentRequests)
|
||||
for i := uint64(0); i < numPacks.Uint64(); i++ {
|
||||
c.Wait()
|
||||
go func(i uint64) {
|
||||
defer c.Done()
|
||||
|
||||
packID := new(big.Int).SetUint64(i)
|
||||
|
||||
_, exists := installedPacks[uint(i)]
|
||||
if exists {
|
||||
return // We already have the sticker pack data, no need to query it
|
||||
}
|
||||
|
||||
_, exists = pendingPacks[uint(i)]
|
||||
if exists {
|
||||
return // We already have the sticker pack data, no need to query it
|
||||
}
|
||||
|
||||
stickerPack, err := api.fetchPackData(stickerType, packID, true)
|
||||
if err != nil {
|
||||
log.Warn("Could not retrieve stickerpack data", "packID", packID, "error", err)
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
resultChan <- stickerPack
|
||||
}(i)
|
||||
}
|
||||
|
||||
c.WaitAllDone()
|
||||
}
|
||||
|
||||
func (api *API) fetchPackData(stickerType *stickers.StickerType, packID *big.Int, translateHashes bool) (*StickerPack, error) {
|
||||
|
||||
timeoutContext, timeoutCancel := context.WithTimeout(api.ctx, requestTimeout)
|
||||
defer timeoutCancel()
|
||||
|
||||
callOpts := &bind.CallOpts{Context: timeoutContext, Pending: false}
|
||||
packData, err := stickerType.GetPackData(callOpts, packID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stickerPack := &StickerPack{
|
||||
ID: &bigint.BigInt{Int: packID},
|
||||
Owner: packData.Owner,
|
||||
Price: &bigint.BigInt{Int: packData.Price},
|
||||
}
|
||||
|
||||
err = api.downloadPackData(stickerPack, packData.Contenthash, translateHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stickerPack, nil
|
||||
}
|
||||
|
||||
func (api *API) downloadPackData(stickerPack *StickerPack, contentHash []byte, translateHashes bool) error {
|
||||
fileContent, err := api.downloader.Get(hexutil.Encode(contentHash)[2:], true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return api.populateStickerPackAttributes(stickerPack, fileContent, translateHashes)
|
||||
}
|
||||
|
||||
func (api *API) hashToURL(hash string) string {
|
||||
return api.httpServer.MakeStickerURL(hash)
|
||||
}
|
||||
|
||||
func (api *API) populateStickerPackAttributes(stickerPack *StickerPack, ednSource []byte, translateHashes bool) error {
|
||||
var stickerpackIPFSInfo ednStickerPackInfo
|
||||
err := edn.Unmarshal(ednSource, &stickerpackIPFSInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stickerPack.Author = stickerpackIPFSInfo.Meta.Author
|
||||
stickerPack.Name = stickerpackIPFSInfo.Meta.Name
|
||||
|
||||
if translateHashes {
|
||||
stickerPack.Preview = api.hashToURL(stickerpackIPFSInfo.Meta.Preview)
|
||||
stickerPack.Thumbnail = api.hashToURL(stickerpackIPFSInfo.Meta.Thumbnail)
|
||||
} else {
|
||||
stickerPack.Preview = stickerpackIPFSInfo.Meta.Preview
|
||||
stickerPack.Thumbnail = stickerpackIPFSInfo.Meta.Thumbnail
|
||||
}
|
||||
|
||||
for _, s := range stickerpackIPFSInfo.Meta.Stickers {
|
||||
url := ""
|
||||
if translateHashes {
|
||||
url = api.hashToURL(s.Hash)
|
||||
}
|
||||
|
||||
stickerPack.Stickers = append(stickerPack.Stickers, Sticker{
|
||||
PackID: stickerPack.ID,
|
||||
URL: url,
|
||||
Hash: s.Hash,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) getContractPacks(chainID uint64) ([]StickerPack, error) {
|
||||
stickerPackChan := make(chan *StickerPack)
|
||||
errChan := make(chan error)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
|
||||
go api.fetchStickerPacks(chainID, stickerPackChan, errChan, doneChan)
|
||||
|
||||
var packs []StickerPack
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return packs, nil
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case pack := <-stickerPackChan:
|
||||
if pack != nil {
|
||||
packs = append(packs, *pack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) getAccountsPurchasedPack(chainID uint64, accs []*accounts.Account, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
|
||||
defer close(doneChan)
|
||||
defer close(errChan)
|
||||
defer close(resultChan)
|
||||
|
||||
if len(accs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c := goccm.New(maxConcurrentRequests)
|
||||
for _, account := range accs {
|
||||
c.Wait()
|
||||
go func(acc *accounts.Account) {
|
||||
defer c.Done()
|
||||
packs, err := api.getPurchasedPackIDs(chainID, acc.Address)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range packs {
|
||||
resultChan <- p
|
||||
}
|
||||
}(account)
|
||||
}
|
||||
c.WaitAllDone()
|
||||
}
|
||||
|
||||
func (api *API) execTokenOwnerOfIndex(chainID uint64, account types.Address, balance *big.Int, resultChan chan<- *big.Int, errChan chan<- error, doneChan chan<- struct{}) {
|
||||
defer close(doneChan)
|
||||
defer close(errChan)
|
||||
defer close(resultChan)
|
||||
|
||||
stickerPack, err := api.contractMaker.NewStickerPack(chainID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
if balance.Int64() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
|
||||
|
||||
c := goccm.New(maxConcurrentRequests)
|
||||
for i := uint64(0); i < balance.Uint64(); i++ {
|
||||
c.Wait()
|
||||
go func(i uint64) {
|
||||
defer c.Done()
|
||||
tokenID, err := stickerPack.TokenOfOwnerByIndex(callOpts, common.Address(account), new(big.Int).SetUint64(i))
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
resultChan <- tokenID
|
||||
}(i)
|
||||
}
|
||||
c.WaitAllDone()
|
||||
}
|
||||
|
||||
func (api *API) getTokenOwnerOfIndex(chainID uint64, account types.Address, balance *big.Int) ([]*big.Int, error) {
|
||||
tokenIDChan := make(chan *big.Int)
|
||||
errChan := make(chan error)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
|
||||
go api.execTokenOwnerOfIndex(chainID, account, balance, tokenIDChan, errChan, doneChan)
|
||||
|
||||
var tokenIDs []*big.Int
|
||||
for {
|
||||
select {
|
||||
case <-doneChan:
|
||||
return tokenIDs, nil
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case tokenID := <-tokenIDChan:
|
||||
if tokenID != nil {
|
||||
tokenIDs = append(tokenIDs, tokenID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
vendor/github.com/status-im/status-go/services/stickers/install.go
generated
vendored
Normal file
128
vendor/github.com/status-im/status-go/services/stickers/install.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
func (api *API) Install(chainID uint64, packID *bigint.BigInt) error {
|
||||
installedPacks, err := api.installedStickerPacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := installedPacks[uint(packID.Uint64())]; exists {
|
||||
return errors.New("sticker pack is already installed")
|
||||
}
|
||||
|
||||
// TODO: this does not validate if the pack is purchased. Should it?
|
||||
|
||||
stickerType, err := api.contractMaker.NewStickerType(chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stickerPack, err := api.fetchPackData(stickerType, packID.Int, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
installedPacks[uint(packID.Uint64())] = *stickerPack
|
||||
|
||||
err = api.accountsDB.SaveSettingField(settings.StickersPacksInstalled, installedPacks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) installedStickerPacks() (StickerPackCollection, error) {
|
||||
stickerPacks := make(StickerPackCollection)
|
||||
|
||||
installedStickersJSON, err := api.accountsDB.GetInstalledStickerPacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if installedStickersJSON == nil {
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(*installedStickersJSON, &stickerPacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
func (api *API) Installed() (StickerPackCollection, error) {
|
||||
stickerPacks, err := api.installedStickerPacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for packID, stickerPack := range stickerPacks {
|
||||
stickerPack.Status = statusInstalled
|
||||
stickerPack.Preview = api.hashToURL(stickerPack.Preview)
|
||||
stickerPack.Thumbnail = api.hashToURL(stickerPack.Thumbnail)
|
||||
for i, sticker := range stickerPack.Stickers {
|
||||
sticker.URL = api.hashToURL(sticker.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stickerPack.Stickers[i] = sticker
|
||||
}
|
||||
stickerPacks[packID] = stickerPack
|
||||
}
|
||||
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
func (api *API) Uninstall(packID *bigint.BigInt) error {
|
||||
installedPacks, err := api.installedStickerPacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := installedPacks[uint(packID.Uint64())]; !exists {
|
||||
return errors.New("sticker pack is not installed")
|
||||
}
|
||||
|
||||
delete(installedPacks, uint(packID.Uint64()))
|
||||
|
||||
err = api.accountsDB.SaveSettingField(settings.StickersPacksInstalled, installedPacks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Removing uninstalled pack from recent stickers
|
||||
|
||||
recentStickers, err := api.recentStickers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
idx := -1
|
||||
for i, r := range recentStickers {
|
||||
if r.PackID.Cmp(packID.Int) == 0 {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if idx > -1 {
|
||||
var newRecentStickers []Sticker
|
||||
newRecentStickers = append(newRecentStickers, recentStickers[:idx]...)
|
||||
if idx != len(recentStickers)-1 {
|
||||
newRecentStickers = append(newRecentStickers, recentStickers[idx+1:]...)
|
||||
}
|
||||
return api.accountsDB.SaveSettingField(settings.StickersRecentStickers, newRecentStickers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
133
vendor/github.com/status-im/status-go/services/stickers/pending.go
generated
vendored
Normal file
133
vendor/github.com/status-im/status-go/services/stickers/pending.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
func (api *API) AddPending(chainID uint64, packID *bigint.BigInt) error {
|
||||
pendingPacks, err := api.pendingStickerPacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := pendingPacks[uint(packID.Uint64())]; exists {
|
||||
return errors.New("sticker pack is already pending")
|
||||
}
|
||||
|
||||
stickerType, err := api.contractMaker.NewStickerType(chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stickerPack, err := api.fetchPackData(stickerType, packID.Int, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pendingPacks[uint(packID.Uint64())] = *stickerPack
|
||||
|
||||
return api.accountsDB.SaveSettingField(settings.StickersPacksPending, pendingPacks)
|
||||
}
|
||||
|
||||
func (api *API) pendingStickerPacks() (StickerPackCollection, error) {
|
||||
stickerPacks := make(StickerPackCollection)
|
||||
|
||||
pendingStickersJSON, err := api.accountsDB.GetPendingStickerPacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pendingStickersJSON == nil {
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(*pendingStickersJSON, &stickerPacks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
func (api *API) Pending() (StickerPackCollection, error) {
|
||||
stickerPacks, err := api.pendingStickerPacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for packID, stickerPack := range stickerPacks {
|
||||
stickerPack.Status = statusPending
|
||||
stickerPack.Preview = api.hashToURL(stickerPack.Preview)
|
||||
stickerPack.Thumbnail = api.hashToURL(stickerPack.Thumbnail)
|
||||
for i, sticker := range stickerPack.Stickers {
|
||||
sticker.URL = api.hashToURL(sticker.Hash)
|
||||
stickerPack.Stickers[i] = sticker
|
||||
}
|
||||
stickerPacks[packID] = stickerPack
|
||||
}
|
||||
|
||||
return stickerPacks, nil
|
||||
}
|
||||
|
||||
func (api *API) ProcessPending(chainID uint64) (pendingChanged StickerPackCollection, err error) {
|
||||
pendingStickerPacks, err := api.pendingStickerPacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accs, err := api.accountsDB.GetActiveAccounts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
purchasedPacks := make(map[uint]struct{})
|
||||
purchasedPackChan := make(chan *big.Int)
|
||||
errChan := make(chan error)
|
||||
doneChan := make(chan struct{}, 1)
|
||||
go api.getAccountsPurchasedPack(chainID, accs, purchasedPackChan, errChan, doneChan)
|
||||
for {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case packID := <-purchasedPackChan:
|
||||
if packID != nil {
|
||||
purchasedPacks[uint(packID.Uint64())] = struct{}{}
|
||||
}
|
||||
case <-doneChan:
|
||||
result := make(StickerPackCollection)
|
||||
for _, stickerPack := range pendingStickerPacks {
|
||||
packID := uint(stickerPack.ID.Uint64())
|
||||
if _, exists := purchasedPacks[packID]; !exists {
|
||||
continue
|
||||
}
|
||||
delete(pendingStickerPacks, packID)
|
||||
stickerPack.Status = statusPurchased
|
||||
result[packID] = stickerPack
|
||||
}
|
||||
err = api.accountsDB.SaveSettingField(settings.StickersPacksPending, pendingStickerPacks)
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) RemovePending(packID *bigint.BigInt) error {
|
||||
pendingPacks, err := api.pendingStickerPacks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := pendingPacks[uint(packID.Uint64())]; !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(pendingPacks, uint(packID.Uint64()))
|
||||
|
||||
return api.accountsDB.SaveSettingField(settings.StickersPacksPending, pendingPacks)
|
||||
}
|
||||
102
vendor/github.com/status-im/status-go/services/stickers/recent.go
generated
vendored
Normal file
102
vendor/github.com/status-im/status-go/services/stickers/recent.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
const maxNumberRecentStickers = 24
|
||||
|
||||
func (api *API) recentStickers() ([]Sticker, error) {
|
||||
installedStickersPacksJSON, err := api.accountsDB.GetInstalledStickerPacks()
|
||||
|
||||
if err != nil || installedStickersPacksJSON == nil {
|
||||
return []Sticker{}, nil
|
||||
}
|
||||
|
||||
recentStickersJSON, err := api.accountsDB.GetRecentStickers()
|
||||
|
||||
if err != nil || recentStickersJSON == nil {
|
||||
return []Sticker{}, nil
|
||||
}
|
||||
|
||||
recentStickersList := make([]Sticker, 0)
|
||||
if err := json.Unmarshal(*recentStickersJSON, &recentStickersList); err != nil {
|
||||
return []Sticker{}, err
|
||||
}
|
||||
|
||||
var installedStickersPacks map[string]StickerPack
|
||||
if err := json.Unmarshal(*installedStickersPacksJSON, &installedStickersPacks); err != nil {
|
||||
return []Sticker{}, err
|
||||
}
|
||||
|
||||
recentStickersListInExistingPacks := make([]Sticker, 0)
|
||||
existingPackIDs := make(map[string]bool)
|
||||
|
||||
for k := range installedStickersPacks {
|
||||
existingPackIDs[k] = true
|
||||
}
|
||||
|
||||
for _, s := range recentStickersList {
|
||||
packIDStr := s.PackID.String()
|
||||
if _, exists := existingPackIDs[packIDStr]; exists {
|
||||
recentStickersListInExistingPacks = append(recentStickersListInExistingPacks, s)
|
||||
}
|
||||
}
|
||||
|
||||
return recentStickersListInExistingPacks, nil
|
||||
}
|
||||
|
||||
func (api *API) ClearRecent() error {
|
||||
var recentStickersList []Sticker
|
||||
return api.accountsDB.SaveSettingField(settings.StickersRecentStickers, recentStickersList)
|
||||
}
|
||||
|
||||
func (api *API) Recent() ([]Sticker, error) {
|
||||
recentStickersList, err := api.recentStickers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, sticker := range recentStickersList {
|
||||
sticker.URL = api.hashToURL(sticker.Hash)
|
||||
recentStickersList[i] = sticker
|
||||
}
|
||||
|
||||
return recentStickersList, nil
|
||||
}
|
||||
|
||||
func (api *API) AddRecent(packID *bigint.BigInt, hash string) error {
|
||||
sticker := Sticker{
|
||||
PackID: packID,
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
recentStickersList, err := api.recentStickers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove duplicated
|
||||
idx := -1
|
||||
for i, currSticker := range recentStickersList {
|
||||
if currSticker.PackID.Cmp(sticker.PackID.Int) == 0 && currSticker.Hash == sticker.Hash {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
if idx > -1 {
|
||||
recentStickersList = append(recentStickersList[:idx], recentStickersList[idx+1:]...)
|
||||
}
|
||||
|
||||
sticker.URL = ""
|
||||
|
||||
if len(recentStickersList) >= maxNumberRecentStickers {
|
||||
recentStickersList = append([]Sticker{sticker}, recentStickersList[:maxNumberRecentStickers-1]...)
|
||||
} else {
|
||||
recentStickersList = append([]Sticker{sticker}, recentStickersList...)
|
||||
}
|
||||
|
||||
return api.accountsDB.SaveSettingField(settings.StickersRecentStickers, recentStickersList)
|
||||
}
|
||||
76
vendor/github.com/status-im/status-go/services/stickers/service.go
generated
vendored
Normal file
76
vendor/github.com/status-im/status-go/services/stickers/service.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
ethRpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/account"
|
||||
"github.com/status-im/status-go/ipfs"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/params"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
"github.com/status-im/status-go/server"
|
||||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
// NewService initializes service instance.
|
||||
func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *account.GethManager, config *params.NodeConfig, downloader *ipfs.Downloader, httpServer *server.MediaServer, pendingTracker *transactions.PendingTxTracker) *Service {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &Service{
|
||||
accountsDB: acc,
|
||||
rpcClient: rpcClient,
|
||||
accountsManager: accountsManager,
|
||||
keyStoreDir: config.KeyStoreDir,
|
||||
downloader: downloader,
|
||||
httpServer: httpServer,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
api: NewAPI(ctx, acc, rpcClient, accountsManager, pendingTracker, config.KeyStoreDir, downloader, httpServer),
|
||||
}
|
||||
}
|
||||
|
||||
// Service is a browsers service.
|
||||
type Service struct {
|
||||
accountsDB *accounts.Database
|
||||
rpcClient *rpc.Client
|
||||
accountsManager *account.GethManager
|
||||
downloader *ipfs.Downloader
|
||||
keyStoreDir string
|
||||
httpServer *server.MediaServer
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
api *API
|
||||
}
|
||||
|
||||
// Start a service.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a service.
|
||||
func (s *Service) Stop() error {
|
||||
s.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) API() *API {
|
||||
return s.api
|
||||
}
|
||||
|
||||
// APIs returns list of available RPC APIs.
|
||||
func (s *Service) APIs() []ethRpc.API {
|
||||
return []ethRpc.API{
|
||||
{
|
||||
Namespace: "stickers",
|
||||
Version: "0.1.0",
|
||||
Service: s.api,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns list of p2p protocols.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
115
vendor/github.com/status-im/status-go/services/stickers/transactions.go
generated
vendored
Normal file
115
vendor/github.com/status-im/status-go/services/stickers/transactions.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
package stickers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/status-im/status-go/contracts/snt"
|
||||
"github.com/status-im/status-go/contracts/stickers"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/services/wallet/bigint"
|
||||
)
|
||||
|
||||
func (api *API) BuyPrepareTxCallMsg(chainID uint64, from types.Address, packID *bigint.BigInt) (ethereum.CallMsg, error) {
|
||||
callOpts := &bind.CallOpts{Context: api.ctx, Pending: false}
|
||||
|
||||
stickerType, err := api.contractMaker.NewStickerType(chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
packInfo, err := stickerType.GetPackData(callOpts, packID.Int)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
stickerMarketABI, err := abi.JSON(strings.NewReader(stickers.StickerMarketABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
extraData, err := stickerMarketABI.Pack("buyToken", packID.Int, from, packInfo.Price)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
sntABI, err := abi.JSON(strings.NewReader(snt.SNTABI))
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
stickerMarketAddress, err := stickers.StickerMarketContractAddress(chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
data, err := sntABI.Pack("approveAndCall", stickerMarketAddress, packInfo.Price, extraData)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
sntAddress, err := snt.ContractAddress(chainID)
|
||||
if err != nil {
|
||||
return ethereum.CallMsg{}, err
|
||||
}
|
||||
|
||||
return ethereum.CallMsg{
|
||||
From: common.Address(from),
|
||||
To: &sntAddress,
|
||||
Value: big.NewInt(0),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) BuyPrepareTx(ctx context.Context, chainID uint64, from types.Address, packID *bigint.BigInt) (interface{}, error) {
|
||||
callMsg, err := api.BuyPrepareTxCallMsg(chainID, from, packID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toCallArg(callMsg), nil
|
||||
}
|
||||
|
||||
func (api *API) BuyEstimate(ctx context.Context, chainID uint64, from types.Address, packID *bigint.BigInt) (uint64, error) {
|
||||
callMsg, err := api.BuyPrepareTxCallMsg(chainID, from, packID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ethClient, err := api.contractMaker.RPCClient.EthClient(chainID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ethClient.EstimateGas(ctx, callMsg)
|
||||
}
|
||||
|
||||
func (api *API) StickerMarketAddress(ctx context.Context, chainID uint64) (common.Address, error) {
|
||||
return stickers.StickerMarketContractAddress(chainID)
|
||||
}
|
||||
|
||||
func toCallArg(msg ethereum.CallMsg) interface{} {
|
||||
arg := map[string]interface{}{
|
||||
"from": msg.From,
|
||||
"to": msg.To,
|
||||
}
|
||||
if len(msg.Data) > 0 {
|
||||
arg["data"] = hexutil.Bytes(msg.Data)
|
||||
}
|
||||
if msg.Value != nil {
|
||||
arg["value"] = (*hexutil.Big)(msg.Value)
|
||||
}
|
||||
if msg.Gas != 0 {
|
||||
arg["gas"] = hexutil.Uint64(msg.Gas)
|
||||
}
|
||||
if msg.GasPrice != nil {
|
||||
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
|
||||
}
|
||||
return arg
|
||||
}
|
||||
79
vendor/github.com/status-im/status-go/services/subscriptions/README.md
generated
vendored
Normal file
79
vendor/github.com/status-im/status-go/services/subscriptions/README.md
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# Signal Subscriptions
|
||||
|
||||
This package implements subscriptions mechanics using [`signal`](../../signal) package.
|
||||
|
||||
It defines 3 new RPC methods in the `eth` namespace and 2 signals.
|
||||
|
||||
## Methods
|
||||
|
||||
###`eth_subscribeSignal`
|
||||
Creates a new filter and subscribes to its changes via signals.
|
||||
|
||||
Parameters: receives the method name and parameters for the filter that is created.
|
||||
|
||||
Example 1:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "eth_subscribeSignal",
|
||||
"params": ["eth_newPendingTransactionFilter", []]
|
||||
}
|
||||
```
|
||||
|
||||
Example 2:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "eth_subscribeSignal",
|
||||
"params": [
|
||||
"shh_newMessageFilter",
|
||||
[{ "symKeyID":"abcabcabcabc", "topics": ["0x12341234"] }]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Supported filters: `shh_newMessageFilter`, `eth_newFilter`, `eth_newBlockFilter`, `eth_newPendingTransactionFilter`
|
||||
(see [Ethereum documentation](https://github.com/ethereum/wiki/wiki/JSON-RPC) for respective parameters).
|
||||
|
||||
Returns: error or `subscriptionID`.
|
||||
|
||||
|
||||
###`eth_unsubscribeSignal`
|
||||
Unsubscribes and removes one filter by its ID.
|
||||
NOTE: Unsubscribing from a filter removes it.
|
||||
|
||||
Parameters: `subscriptionID` obtained from `eth_subscribeSignal`
|
||||
Returns: error if something went wrong while unsubscribing.
|
||||
|
||||
|
||||
## Signals
|
||||
|
||||
1. Subscription data received
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subscriptions.data",
|
||||
"event": {
|
||||
"subscription_id": "shh_0x01",
|
||||
"data": {
|
||||
<whisper envelope 01>,
|
||||
<whisper envelope 02>,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Subscription error received
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "subscriptions.error",
|
||||
"event": {
|
||||
"subscription_id": "shh_0x01",
|
||||
"error_message": "can not find filter with id: 0x01"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
47
vendor/github.com/status-im/status-go/services/subscriptions/api.go
generated
vendored
Normal file
47
vendor/github.com/status-im/status-go/services/subscriptions/api.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
rpcPrivateClientFunc func() *rpc.Client
|
||||
activeSubscriptions *Subscriptions
|
||||
}
|
||||
|
||||
func NewPublicAPI(rpcPrivateClientFunc func() *rpc.Client) *API {
|
||||
return &API{
|
||||
rpcPrivateClientFunc: rpcPrivateClientFunc,
|
||||
activeSubscriptions: NewSubscriptions(100 * time.Millisecond),
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) SubscribeSignal(method string, args []interface{}) (SubscriptionID, error) {
|
||||
var (
|
||||
filter filter
|
||||
err error
|
||||
namespace = method[:3]
|
||||
)
|
||||
|
||||
switch namespace {
|
||||
case "shh":
|
||||
filter, err = installShhFilter(api.rpcPrivateClientFunc(), method, args)
|
||||
case "eth":
|
||||
filter, err = installEthFilter(api.rpcPrivateClientFunc(), method, args)
|
||||
default:
|
||||
err = fmt.Errorf("unexpected namespace: %s", namespace)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("[SubscribeSignal] could not subscribe, failed to call %s: %v", method, err)
|
||||
}
|
||||
|
||||
return api.activeSubscriptions.Create(namespace, filter)
|
||||
}
|
||||
|
||||
func (api *API) UnsubscribeSignal(id string) error {
|
||||
return api.activeSubscriptions.Remove(SubscriptionID(id))
|
||||
}
|
||||
7
vendor/github.com/status-im/status-go/services/subscriptions/filters.go
generated
vendored
Normal file
7
vendor/github.com/status-im/status-go/services/subscriptions/filters.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package subscriptions
|
||||
|
||||
type filter interface {
|
||||
getID() string
|
||||
getChanges() ([]interface{}, error)
|
||||
uninstall() error
|
||||
}
|
||||
65
vendor/github.com/status-im/status-go/services/subscriptions/filters_eth.go
generated
vendored
Normal file
65
vendor/github.com/status-im/status-go/services/subscriptions/filters_eth.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
type ethFilter struct {
|
||||
id string
|
||||
rpcClient *rpc.Client
|
||||
}
|
||||
|
||||
func installEthFilter(rpcClient *rpc.Client, method string, args []interface{}) (*ethFilter, error) {
|
||||
|
||||
if err := validateEthMethod(method); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result string
|
||||
|
||||
err := rpcClient.Call(&result, rpcClient.UpstreamChainID, method, args...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := ðFilter{
|
||||
id: result,
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
|
||||
}
|
||||
|
||||
func (ef *ethFilter) getID() string {
|
||||
return ef.id
|
||||
}
|
||||
|
||||
func (ef *ethFilter) getChanges() ([]interface{}, error) {
|
||||
var result []interface{}
|
||||
|
||||
err := ef.rpcClient.Call(&result, ef.rpcClient.UpstreamChainID, "eth_getFilterChanges", ef.getID())
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (ef *ethFilter) uninstall() error {
|
||||
return ef.rpcClient.Call(nil, ef.rpcClient.UpstreamChainID, "eth_uninstallFilter", ef.getID())
|
||||
}
|
||||
|
||||
func validateEthMethod(method string) error {
|
||||
for _, allowedMethod := range []string{
|
||||
"eth_newFilter",
|
||||
"eth_newBlockFilter",
|
||||
"eth_newPendingTransactionFilter",
|
||||
} {
|
||||
if method == allowedMethod {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected filter method: %s", method)
|
||||
}
|
||||
57
vendor/github.com/status-im/status-go/services/subscriptions/filters_shh.go
generated
vendored
Normal file
57
vendor/github.com/status-im/status-go/services/subscriptions/filters_shh.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
type whisperFilter struct {
|
||||
id string
|
||||
rpcClient *rpc.Client
|
||||
}
|
||||
|
||||
func installShhFilter(rpcClient *rpc.Client, method string, args []interface{}) (*whisperFilter, error) {
|
||||
|
||||
if err := validateShhMethod(method); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result string
|
||||
|
||||
err := rpcClient.Call(&result, rpcClient.UpstreamChainID, method, args...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := &whisperFilter{
|
||||
id: result,
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
func (wf *whisperFilter) getChanges() ([]interface{}, error) {
|
||||
var result []interface{}
|
||||
|
||||
err := wf.rpcClient.Call(&result, wf.rpcClient.UpstreamChainID, "shh_getFilterMessages", wf.getID())
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (wf *whisperFilter) getID() string {
|
||||
return wf.id
|
||||
}
|
||||
|
||||
func (wf *whisperFilter) uninstall() error {
|
||||
return wf.rpcClient.Call(nil, wf.rpcClient.UpstreamChainID, "shh_deleteMessageFilter", wf.getID())
|
||||
}
|
||||
|
||||
func validateShhMethod(method string) error {
|
||||
if method != "shh_newMessageFilter" {
|
||||
return fmt.Errorf("unexpected filter method: %s", method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
51
vendor/github.com/status-im/status-go/services/subscriptions/service.go
generated
vendored
Normal file
51
vendor/github.com/status-im/status-go/services/subscriptions/service.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
gethnode "github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
gethrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
// Make sure that Service implements gethnode.Lifecycle interface.
|
||||
var _ gethnode.Lifecycle = (*Service)(nil)
|
||||
|
||||
// Service represents our own implementation of personal sign operations.
|
||||
type Service struct {
|
||||
api *API
|
||||
}
|
||||
|
||||
// New returns a new Service.
|
||||
func New(rpcPrivateClientFunc func() *rpc.Client) *Service {
|
||||
return &Service{
|
||||
api: NewPublicAPI(rpcPrivateClientFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns a new protocols list. In this case, there are none.
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
// APIs returns a list of new APIs.
|
||||
func (s *Service) APIs() []gethrpc.API {
|
||||
return []gethrpc.API{
|
||||
{
|
||||
Namespace: "eth",
|
||||
Version: "1.0",
|
||||
Service: s.api,
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start is run when a service is started.
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is run when a service is stopped.
|
||||
func (s *Service) Stop() error {
|
||||
return s.api.activeSubscriptions.removeAll()
|
||||
}
|
||||
19
vendor/github.com/status-im/status-go/services/subscriptions/signals.go
generated
vendored
Normal file
19
vendor/github.com/status-im/status-go/services/subscriptions/signals.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package subscriptions
|
||||
|
||||
import "github.com/status-im/status-go/signal"
|
||||
|
||||
type filterSignal struct {
|
||||
filterID string
|
||||
}
|
||||
|
||||
func newFilterSignal(filterID string) *filterSignal {
|
||||
return &filterSignal{filterID}
|
||||
}
|
||||
|
||||
func (s *filterSignal) SendError(err error) {
|
||||
signal.SendSubscriptionErrorEvent(s.filterID, err)
|
||||
}
|
||||
|
||||
func (s *filterSignal) SendData(data []interface{}) {
|
||||
signal.SendSubscriptionDataEvent(s.filterID, data)
|
||||
}
|
||||
82
vendor/github.com/status-im/status-go/services/subscriptions/subscription.go
generated
vendored
Normal file
82
vendor/github.com/status-im/status-go/services/subscriptions/subscription.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SubscriptionID string
|
||||
|
||||
type Subscription struct {
|
||||
mu sync.RWMutex
|
||||
id SubscriptionID
|
||||
signal *filterSignal
|
||||
quit chan struct{}
|
||||
filter filter
|
||||
started bool
|
||||
}
|
||||
|
||||
func NewSubscription(namespace string, filter filter) *Subscription {
|
||||
subscriptionID := NewSubscriptionID(namespace, filter.getID())
|
||||
return &Subscription{
|
||||
id: subscriptionID,
|
||||
signal: newFilterSignal(string(subscriptionID)),
|
||||
filter: filter,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) Start(checkPeriod time.Duration) error {
|
||||
s.mu.Lock()
|
||||
if s.started {
|
||||
s.mu.Unlock()
|
||||
return errors.New("subscription already started or used")
|
||||
}
|
||||
s.started = true
|
||||
s.quit = make(chan struct{})
|
||||
quit := s.quit
|
||||
s.mu.Unlock()
|
||||
|
||||
ticker := time.NewTicker(checkPeriod)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
filterData, err := s.filter.getChanges()
|
||||
if err != nil {
|
||||
s.signal.SendError(err)
|
||||
} else if len(filterData) > 0 {
|
||||
s.signal.SendData(filterData)
|
||||
}
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscription) Stop(uninstall bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.started {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case _, ok := <-s.quit:
|
||||
// handle a case of a closed channel
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
close(s.quit)
|
||||
}
|
||||
if !uninstall {
|
||||
return nil
|
||||
}
|
||||
return s.filter.uninstall()
|
||||
}
|
||||
|
||||
func NewSubscriptionID(namespace, filterID string) SubscriptionID {
|
||||
return SubscriptionID(fmt.Sprintf("%s-%s", namespace, filterID))
|
||||
}
|
||||
84
vendor/github.com/status-im/status-go/services/subscriptions/subscriptions.go
generated
vendored
Normal file
84
vendor/github.com/status-im/status-go/services/subscriptions/subscriptions.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package subscriptions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type Subscriptions struct {
|
||||
mu sync.Mutex
|
||||
subs map[SubscriptionID]*Subscription
|
||||
checkPeriod time.Duration
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewSubscriptions(period time.Duration) *Subscriptions {
|
||||
return &Subscriptions{
|
||||
subs: make(map[SubscriptionID]*Subscription),
|
||||
checkPeriod: period,
|
||||
log: log.New("package", "status-go/services/subsriptions.Subscriptions"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscriptions) Create(namespace string, filter filter) (SubscriptionID, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
newSub := NewSubscription(namespace, filter)
|
||||
|
||||
go func() {
|
||||
err := newSub.Start(s.checkPeriod)
|
||||
if err != nil {
|
||||
s.log.Error("error while starting subscription", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
s.subs[newSub.id] = newSub
|
||||
|
||||
return newSub.id, nil
|
||||
}
|
||||
|
||||
func (s *Subscriptions) Remove(id SubscriptionID) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
found, err := s.stopSubscription(id, true)
|
||||
if found {
|
||||
delete(s.subs, id)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Subscriptions) removeAll() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
unsubscribeErrors := make(map[SubscriptionID]error)
|
||||
|
||||
for id := range s.subs {
|
||||
_, err := s.stopSubscription(id, false)
|
||||
if err != nil {
|
||||
unsubscribeErrors[id] = err
|
||||
}
|
||||
}
|
||||
|
||||
s.subs = make(map[SubscriptionID]*Subscription)
|
||||
|
||||
if len(unsubscribeErrors) > 0 {
|
||||
return fmt.Errorf("errors while cleaning up subscriptions: %+v", unsubscribeErrors)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Subscriptions) stopSubscription(id SubscriptionID, uninstall bool) (bool, error) {
|
||||
sub, found := s.subs[id]
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
return true, sub.Stop(uninstall)
|
||||
}
|
||||
201
vendor/github.com/status-im/status-go/services/typeddata/hash.go
generated
vendored
Normal file
201
vendor/github.com/status-im/status-go/services/typeddata/hash.go
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
package typeddata
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
bytes32Type, _ = abi.NewType("bytes32", "", nil)
|
||||
int256Type, _ = abi.NewType("int256", "", nil)
|
||||
|
||||
errNotInteger = errors.New("not an integer")
|
||||
)
|
||||
|
||||
// ValidateAndHash generates a hash of TypedData and verifies that chainId in the typed data matches currently selected chain.
|
||||
func ValidateAndHash(typed TypedData, chain *big.Int) (common.Hash, error) {
|
||||
if err := typed.ValidateChainID(chain); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
||||
return encodeData(typed)
|
||||
}
|
||||
|
||||
// deps runs breadth-first traversal starting from target and collects all
|
||||
// found composite dependencies types into result slice. target always will be first
|
||||
// in the result array. all other dependencies are sorted alphabetically.
|
||||
// for example: Z{c C, a A} A{c C} and the target is Z.
|
||||
// result would be Z, A, B, C
|
||||
func deps(target string, types Types) []string {
|
||||
unique := map[string]struct{}{}
|
||||
unique[target] = struct{}{}
|
||||
visited := []string{target}
|
||||
deps := []string{}
|
||||
for len(visited) > 0 {
|
||||
current := visited[0]
|
||||
fields := types[current]
|
||||
for i := range fields {
|
||||
f := fields[i]
|
||||
if _, defined := types[f.Type]; defined {
|
||||
if _, exist := unique[f.Type]; !exist {
|
||||
visited = append(visited, f.Type)
|
||||
unique[f.Type] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
visited = visited[1:]
|
||||
deps = append(deps, current)
|
||||
}
|
||||
sort.Slice(deps[1:], func(i, j int) bool {
|
||||
return deps[1:][i] < deps[1:][j]
|
||||
})
|
||||
return deps
|
||||
}
|
||||
|
||||
func typeString(target string, types Types) string {
|
||||
b := new(bytes.Buffer)
|
||||
for _, dep := range deps(target, types) {
|
||||
b.WriteString(dep)
|
||||
b.WriteString("(")
|
||||
fields := types[dep]
|
||||
first := true
|
||||
for i := range fields {
|
||||
if !first {
|
||||
b.WriteString(",")
|
||||
} else {
|
||||
first = false
|
||||
}
|
||||
f := fields[i]
|
||||
b.WriteString(f.Type)
|
||||
b.WriteString(" ")
|
||||
b.WriteString(f.Name)
|
||||
}
|
||||
b.WriteString(")")
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func typeHash(target string, types Types) (rst common.Hash) {
|
||||
return crypto.Keccak256Hash([]byte(typeString(target, types)))
|
||||
}
|
||||
|
||||
func hashStruct(target string, data map[string]json.RawMessage, types Types) (rst common.Hash, err error) {
|
||||
fields := types[target]
|
||||
typeh := typeHash(target, types)
|
||||
args := abi.Arguments{{Type: bytes32Type}}
|
||||
vals := []interface{}{typeh}
|
||||
for i := range fields {
|
||||
f := fields[i]
|
||||
val, typ, err := toABITypeAndValue(f, data, types)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
vals = append(vals, val)
|
||||
args = append(args, abi.Argument{Name: f.Name, Type: typ})
|
||||
}
|
||||
packed, err := args.Pack(vals...)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
return crypto.Keccak256Hash(packed), nil
|
||||
}
|
||||
|
||||
func toABITypeAndValue(f Field, data map[string]json.RawMessage, types Types) (val interface{}, typ abi.Type, err error) {
|
||||
if f.Type == "string" {
|
||||
var str string
|
||||
if err = json.Unmarshal(data[f.Name], &str); err != nil {
|
||||
return
|
||||
}
|
||||
return crypto.Keccak256Hash([]byte(str)), bytes32Type, nil
|
||||
} else if f.Type == "bytes" {
|
||||
var bytes hexutil.Bytes
|
||||
if err = json.Unmarshal(data[f.Name], &bytes); err != nil {
|
||||
return
|
||||
}
|
||||
return crypto.Keccak256Hash(bytes), bytes32Type, nil
|
||||
} else if _, exist := types[f.Type]; exist {
|
||||
var obj map[string]json.RawMessage
|
||||
if err = json.Unmarshal(data[f.Name], &obj); err != nil {
|
||||
return
|
||||
}
|
||||
val, err = hashStruct(f.Type, obj, types)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return val, bytes32Type, nil
|
||||
}
|
||||
return atomicType(f, data)
|
||||
}
|
||||
|
||||
func atomicType(f Field, data map[string]json.RawMessage) (val interface{}, typ abi.Type, err error) {
|
||||
typ, err = abi.NewType(f.Type, "", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if typ.T == abi.SliceTy || typ.T == abi.ArrayTy || typ.T == abi.FunctionTy {
|
||||
return val, typ, errors.New("arrays, slices and functions are not supported")
|
||||
} else if typ.T == abi.FixedBytesTy {
|
||||
return toFixedBytes(f, data[f.Name])
|
||||
} else if typ.T == abi.AddressTy {
|
||||
val, err = toAddress(f, data[f.Name])
|
||||
} else if typ.T == abi.IntTy || typ.T == abi.UintTy {
|
||||
return toInt(f, data[f.Name])
|
||||
} else if typ.T == abi.BoolTy {
|
||||
val, err = toBool(f, data[f.Name])
|
||||
} else {
|
||||
err = fmt.Errorf("type %s is not supported", f.Type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toFixedBytes(f Field, data json.RawMessage) (rst [32]byte, typ abi.Type, err error) {
|
||||
var bytes hexutil.Bytes
|
||||
if err = json.Unmarshal(data, &bytes); err != nil {
|
||||
return
|
||||
}
|
||||
typ = bytes32Type
|
||||
rst = [32]byte{}
|
||||
// reduce the length to the advertised size
|
||||
if len(bytes) > typ.Size {
|
||||
bytes = bytes[:typ.Size]
|
||||
}
|
||||
copy(rst[:], bytes)
|
||||
return rst, typ, nil
|
||||
}
|
||||
|
||||
func toInt(f Field, data json.RawMessage) (val *big.Int, typ abi.Type, err error) {
|
||||
val = new(big.Int)
|
||||
if err = json.Unmarshal(data, &val); err != nil {
|
||||
var buf string
|
||||
err = json.Unmarshal(data, &buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ok bool
|
||||
val, ok = val.SetString(buf, 0)
|
||||
if !ok {
|
||||
err = errNotInteger
|
||||
return
|
||||
}
|
||||
}
|
||||
return val, int256Type, nil
|
||||
}
|
||||
|
||||
func toAddress(f Field, data json.RawMessage) (rst common.Address, err error) {
|
||||
err = json.Unmarshal(data, &rst)
|
||||
return
|
||||
}
|
||||
|
||||
func toBool(f Field, data json.RawMessage) (rst bool, err error) {
|
||||
err = json.Unmarshal(data, &rst)
|
||||
return
|
||||
}
|
||||
79
vendor/github.com/status-im/status-go/services/typeddata/sign.go
generated
vendored
Normal file
79
vendor/github.com/status-im/status-go/services/typeddata/sign.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
package typeddata
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||
)
|
||||
|
||||
var (
|
||||
// x19 to avoid collision with rlp encode. x01 version byte defined in EIP-191
|
||||
messagePadding = []byte{0x19, 0x01}
|
||||
)
|
||||
|
||||
func encodeData(typed TypedData) (rst common.Hash, err error) {
|
||||
domainSeparator, err := hashStruct(eip712Domain, typed.Domain, typed.Types)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
primary, err := hashStruct(typed.PrimaryType, typed.Message, typed.Types)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
return crypto.Keccak256Hash(messagePadding, domainSeparator[:], primary[:]), nil
|
||||
}
|
||||
|
||||
func encodeDataV4(typedData signercore.TypedData, chain *big.Int) ([]byte, error) {
|
||||
domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash)))
|
||||
sighash := crypto.Keccak256(rawData)
|
||||
return sighash, nil
|
||||
}
|
||||
|
||||
func HashTypedDataV4(typedData signercore.TypedData, chain *big.Int) (common.Hash, error) {
|
||||
hashBytes, err := encodeDataV4(typedData, chain)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return common.BytesToHash(hashBytes), nil
|
||||
}
|
||||
|
||||
func SignTypedDataV4(typedData signercore.TypedData, prv *ecdsa.PrivateKey, chain *big.Int) (hexutil.Bytes, error) {
|
||||
sighash, err := HashTypedDataV4(typedData, chain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := crypto.Sign(sighash[:], prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig[64] += 27
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
// Sign TypedData with a given private key. Verify that chainId in the typed data matches currently selected chain.
|
||||
func Sign(typed TypedData, prv *ecdsa.PrivateKey, chain *big.Int) ([]byte, error) {
|
||||
hash, err := ValidateAndHash(typed, chain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, err := crypto.Sign(hash[:], prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig[64] += 27
|
||||
return sig, nil
|
||||
}
|
||||
93
vendor/github.com/status-im/status-go/services/typeddata/types.go
generated
vendored
Normal file
93
vendor/github.com/status-im/status-go/services/typeddata/types.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
package typeddata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
eip712Domain = "EIP712Domain"
|
||||
chainIDKey = "chainId"
|
||||
)
|
||||
|
||||
// Types define fields for each composite type.
|
||||
type Types map[string][]Field
|
||||
|
||||
// Field stores name and solidity type of the field.
|
||||
type Field struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// Validate checks that both name and type are not empty.
|
||||
func (f Field) Validate() error {
|
||||
if len(f.Name) == 0 {
|
||||
return errors.New("`name` is required")
|
||||
}
|
||||
if len(f.Type) == 0 {
|
||||
return errors.New("`type` is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypedData defines typed data according to eip-712.
|
||||
type TypedData struct {
|
||||
Types Types `json:"types"`
|
||||
PrimaryType string `json:"primaryType"`
|
||||
Domain map[string]json.RawMessage `json:"domain"`
|
||||
Message map[string]json.RawMessage `json:"message"`
|
||||
}
|
||||
|
||||
// Validate that required fields are defined.
|
||||
// This method doesn't check if dependencies of the main type are defined, it will be validated
|
||||
// when type string is computed.
|
||||
func (t TypedData) Validate() error {
|
||||
if _, exist := t.Types[eip712Domain]; !exist {
|
||||
return fmt.Errorf("`%s` must be in `types`", eip712Domain)
|
||||
}
|
||||
if t.PrimaryType == "" {
|
||||
return errors.New("`primaryType` is required")
|
||||
}
|
||||
if _, exist := t.Types[t.PrimaryType]; !exist {
|
||||
return fmt.Errorf("primary type `%s` not defined in types", t.PrimaryType)
|
||||
}
|
||||
if t.Domain == nil {
|
||||
return errors.New("`domain` is required")
|
||||
}
|
||||
if t.Message == nil {
|
||||
return errors.New("`message` is required")
|
||||
}
|
||||
for typ := range t.Types {
|
||||
fields := t.Types[typ]
|
||||
for i := range fields {
|
||||
if err := fields[i].Validate(); err != nil {
|
||||
return fmt.Errorf("field %d from type `%s` is invalid: %v", i, typ, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateChainID accept chain as big integer and verifies if typed data belongs to the same chain.
|
||||
func (t TypedData) ValidateChainID(chain *big.Int) error {
|
||||
if _, exist := t.Domain[chainIDKey]; !exist {
|
||||
return fmt.Errorf("domain misses chain key %s", chainIDKey)
|
||||
}
|
||||
var chainID int64
|
||||
if err := json.Unmarshal(t.Domain[chainIDKey], &chainID); err != nil {
|
||||
var chainIDString string
|
||||
if err = json.Unmarshal(t.Domain[chainIDKey], &chainIDString); err != nil {
|
||||
return err
|
||||
}
|
||||
if chainID, err = strconv.ParseInt(chainIDString, 0, 64); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if chainID != chain.Int64() {
|
||||
return fmt.Errorf("chainId %d doesn't match selected chain %s", chainID, chain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user