From 6537cae60668b008484fd2a8808a179724099935 Mon Sep 17 00:00:00 2001 From: Pedro Pombeiro Date: Wed, 27 Nov 2019 13:22:23 +0100 Subject: [PATCH] Nimbus node support --- Makefile | 14 +- account/accounts_geth.go | 4 +- account/accounts_nimbus.go | 13 +- account/accounts_test.go | 6 +- api/geth_backend.go | 6 +- api/nimbus_backend.go | 1027 +++++++++++++++++ eth-node/bridge/geth/node.go | 4 + eth-node/bridge/geth/whisper.go | 5 + eth-node/bridge/nimbus/build-nimbus.sh | 10 +- eth-node/bridge/nimbus/node.go | 4 + eth-node/bridge/nimbus/whisper.go | 10 + eth-node/types/whisper.go | 2 + go.mod | 1 + go.sum | 54 +- mobile/status.go | 76 +- mobile/status_geth.go | 9 + mobile/status_nimbus.go | 9 + node/{status_node.go => get_status_node.go} | 2 + node/{node.go => geth_node.go} | 4 +- ...node_api_test.go => geth_node_api_test.go} | 0 node/{node_test.go => geth_node_test.go} | 2 + ..._node_test.go => geth_status_node_test.go} | 0 node/nimbus_node.go | 392 +++++++ node/nimbus_status_node.go | 798 +++++++++++++ rpc/client.go | 4 + services/accounts/service_nimbus.go | 15 + services/nimbus/service.go | 84 ++ services/nodebridge/node_service_nimbus.go | 14 + services/nodebridge/whisper_service_nimbus.go | 14 + services/rpcfilters/service_nimbus.go | 15 + services/shhext/api.go | 684 +---------- services/shhext/api_geth.go | 676 +++++++++++ .../shhext/{api_test.go => api_geth_test.go} | 2 + services/shhext/api_nimbus.go | 661 +++++++++++ services/shhext/context.go | 5 - services/shhext/context_geth.go | 14 + services/shhext/history.go | 331 ------ services/shhext/history_geth.go | 340 ++++++ .../{history_test.go => history_geth_test.go} | 2 + services/shhext/mailrequests.go | 2 + services/shhext/mailrequests_test.go | 2 + services/shhext/service.go | 2 + services/shhext/service_nimbus.go | 448 +++++++ services/status/service_nimbus.go | 16 + services/subscriptions/service.go | 2 +- services/subscriptions/service_nimbus.go | 15 + timesource/timesource.go | 4 + vendor/github.com/mattn/go-pointer/LICENSE | 21 + vendor/github.com/mattn/go-pointer/README.md | 29 + .../mattn/go-pointer/_example/callback.h | 9 + vendor/github.com/mattn/go-pointer/doc.go | 1 + vendor/github.com/mattn/go-pointer/pointer.go | 57 + .../status-go/eth-node/bridge/geth/node.go | 4 + .../status-go/eth-node/bridge/geth/whisper.go | 5 + .../eth-node/bridge/nimbus/build-nimbus.sh | 40 + .../eth-node/bridge/nimbus/cfuncs.go | 16 + .../eth-node/bridge/nimbus/filter.go | 61 + .../status-go/eth-node/bridge/nimbus/node.go | 159 +++ .../bridge/nimbus/public_whisper_api.go | 212 ++++ .../eth-node/bridge/nimbus/routine_queue.go | 64 + .../eth-node/bridge/nimbus/whisper.go | 431 +++++++ .../status-go/eth-node/types/whisper.go | 2 + vendor/golang.org/x/tools/go/analysis/doc.go | 75 +- vendor/golang.org/x/tools/go/packages/doc.go | 3 +- .../x/tools/go/packages/external.go | 7 +- .../golang.org/x/tools/go/packages/golist.go | 169 +-- .../x/tools/go/packages/loadmode_string.go | 57 + .../x/tools/go/packages/packages.go | 4 +- .../x/tools/internal/gopathwalk/walk.go | 5 +- .../golang.org/x/tools/internal/span/parse.go | 100 -- .../golang.org/x/tools/internal/span/span.go | 285 ----- .../golang.org/x/tools/internal/span/token.go | 151 --- .../x/tools/internal/span/token111.go | 39 - .../x/tools/internal/span/token112.go | 16 - .../golang.org/x/tools/internal/span/uri.go | 152 --- .../golang.org/x/tools/internal/span/utf16.go | 94 -- vendor/modules.txt | 6 +- 77 files changed, 5937 insertions(+), 2141 deletions(-) create mode 100644 api/nimbus_backend.go create mode 100644 mobile/status_geth.go create mode 100644 mobile/status_nimbus.go rename node/{status_node.go => get_status_node.go} (99%) rename node/{node.go => geth_node.go} (99%) rename node/{node_api_test.go => geth_node_api_test.go} (100%) rename node/{node_test.go => geth_node_test.go} (99%) rename node/{status_node_test.go => geth_status_node_test.go} (100%) create mode 100644 node/nimbus_node.go create mode 100644 node/nimbus_status_node.go create mode 100644 services/accounts/service_nimbus.go create mode 100644 services/nimbus/service.go create mode 100644 services/nodebridge/node_service_nimbus.go create mode 100644 services/nodebridge/whisper_service_nimbus.go create mode 100644 services/rpcfilters/service_nimbus.go create mode 100644 services/shhext/api_geth.go rename services/shhext/{api_test.go => api_geth_test.go} (99%) create mode 100644 services/shhext/api_nimbus.go create mode 100644 services/shhext/context_geth.go create mode 100644 services/shhext/history_geth.go rename services/shhext/{history_test.go => history_geth_test.go} (99%) create mode 100644 services/shhext/service_nimbus.go create mode 100644 services/status/service_nimbus.go create mode 100644 services/subscriptions/service_nimbus.go create mode 100644 vendor/github.com/mattn/go-pointer/LICENSE create mode 100644 vendor/github.com/mattn/go-pointer/README.md create mode 100644 vendor/github.com/mattn/go-pointer/_example/callback.h create mode 100644 vendor/github.com/mattn/go-pointer/doc.go create mode 100644 vendor/github.com/mattn/go-pointer/pointer.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go create mode 100644 vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go create mode 100644 vendor/golang.org/x/tools/go/packages/loadmode_string.go delete mode 100644 vendor/golang.org/x/tools/internal/span/parse.go delete mode 100644 vendor/golang.org/x/tools/internal/span/span.go delete mode 100644 vendor/golang.org/x/tools/internal/span/token.go delete mode 100644 vendor/golang.org/x/tools/internal/span/token111.go delete mode 100644 vendor/golang.org/x/tools/internal/span/token112.go delete mode 100644 vendor/golang.org/x/tools/internal/span/uri.go delete mode 100644 vendor/golang.org/x/tools/internal/span/utf16.go diff --git a/Makefile b/Makefile index 97fe08c0e..760a06b92 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,15 @@ HELP_FUN = \ print "\n"; \ } +nimbus: ##@build Build Nimbus + ./eth-node/bridge/nimbus/build-nimbus.sh + +nimbus-statusgo: nimbus ##@build Build status-go (based on Nimbus node) as statusd server + C_INCLUDE_PATH="./eth-node/bridge/nimbus" go build -mod=vendor -i -o $(GOBIN)/statusd -v -tags '$(BUILD_TAGS) nimbus' $(BUILD_FLAGS) ./cmd/statusd && \ + cp vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/libnimbus.so $(GOBIN) + @echo "Compilation done." + @echo "Run \"build/bin/statusd -h\" to view available commands." + statusgo: ##@build Build status-go as statusd server go build -i -o $(GOBIN)/statusd -v -tags '$(BUILD_TAGS)' $(BUILD_FLAGS) ./cmd/statusd @echo "Compilation done." @@ -93,7 +102,7 @@ statusgo-cross: statusgo-android statusgo-ios statusgo-android: ##@cross-compile Build status-go for Android @echo "Building status-go for Android..." gomobile init - gomobile bind -target=android -ldflags="-s -w" $(BUILD_FLAGS_MOBILE) -o build/bin/statusgo.aar github.com/status-im/status-go/mobile + gomobile bind -v -target=android -ldflags="-s -w" $(BUILD_FLAGS_MOBILE) -o build/bin/statusgo.aar github.com/status-im/status-go/mobile @echo "Android cross compilation done in build/bin/statusgo.aar" statusgo-ios: ##@cross-compile Build status-go for iOS @@ -282,7 +291,8 @@ ci: lint canary-test test-unit test-e2e ##@tests Run all linters and tests at on ci-race: lint canary-test test-unit test-e2e-race ##@tests Run all linters and tests at once + race clean: ##@other Cleanup - rm -fr build/bin/* mailserver-config.json + rm -fr build/bin/* mailserver-config.json vendor/github.com/status-im/nimbus + git clean -xf deep-clean: clean rm -Rdf .ethereumtest/StatusChain diff --git a/account/accounts_geth.go b/account/accounts_geth.go index 59031a69b..e6cf27d68 100644 --- a/account/accounts_geth.go +++ b/account/accounts_geth.go @@ -15,8 +15,8 @@ type GethManager struct { gethAccManager *accounts.Manager } -// NewManager returns new node account manager. -func NewManager() *GethManager { +// NewGethManager returns new node account manager. +func NewGethManager() *GethManager { m := &GethManager{} m.Manager = &Manager{accountsGenerator: generator.New(m)} return m diff --git a/account/accounts_nimbus.go b/account/accounts_nimbus.go index 082237213..b3ceb62ea 100644 --- a/account/accounts_nimbus.go +++ b/account/accounts_nimbus.go @@ -6,10 +6,15 @@ import ( "github.com/status-im/status-go/account/generator" ) -// NewManager returns new node account manager. -func NewManager() *Manager { - m := &Manager{} - m.accountsGenerator = generator.New(m) +// NimbusManager represents account manager interface. +type NimbusManager struct { + *Manager +} + +// NewNimbusManager returns new node account manager. +func NewNimbusManager() *NimbusManager { + m := &NimbusManager{} + m.Manager = &Manager{accountsGenerator: generator.New(m)} return m } diff --git a/account/accounts_test.go b/account/accounts_test.go index ec6c36f99..cb09199a4 100644 --- a/account/accounts_test.go +++ b/account/accounts_test.go @@ -18,7 +18,7 @@ import ( ) func TestVerifyAccountPassword(t *testing.T) { - accManager := NewManager() + accManager := NewGethManager() keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts") require.NoError(t, err) defer os.RemoveAll(keyStoreDir) //nolint: errcheck @@ -106,7 +106,7 @@ func TestVerifyAccountPasswordWithAccountBeforeEIP55(t *testing.T) { err = utils.ImportTestAccount(keyStoreDir, "test-account3-before-eip55.pk") require.NoError(t, err) - accManager := NewManager() + accManager := NewGethManager() address := types.HexToAddress(utils.TestConfig.Account3.WalletAddress) _, err = accManager.VerifyAccountPassword(keyStoreDir, address.Hex(), utils.TestConfig.Account3.Password) @@ -136,7 +136,7 @@ type testAccount struct { // SetupTest is used here for reinitializing the mock before every // test function to avoid faulty execution. func (s *ManagerTestSuite) SetupTest() { - s.accManager = NewManager() + s.accManager = NewGethManager() keyStoreDir, err := ioutil.TempDir(os.TempDir(), "accounts") s.Require().NoError(err) diff --git a/api/geth_backend.go b/api/geth_backend.go index 5eb6b1c8b..5ab90c588 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -1,3 +1,5 @@ +// +build !nimbus + package api import ( @@ -82,10 +84,10 @@ type GethStatusBackend struct { // NewGethStatusBackend create a new GethStatusBackend instance func NewGethStatusBackend() *GethStatusBackend { - defer log.Info("Status backend initialized", "version", params.Version, "commit", params.GitCommit) + defer log.Info("Status backend initialized", "backend", "geth", "version", params.Version, "commit", params.GitCommit) statusNode := node.New() - accountManager := account.NewManager() + accountManager := account.NewGethManager() transactor := transactions.NewTransactor() personalAPI := personal.NewAPI() rpcFilters := rpcfilters.New(statusNode) diff --git a/api/nimbus_backend.go b/api/nimbus_backend.go new file mode 100644 index 000000000..d18d18449 --- /dev/null +++ b/api/nimbus_backend.go @@ -0,0 +1,1027 @@ +// +build nimbus + +package api + +import ( + "context" + "database/sql" + "fmt" + "math/big" + "path/filepath" + "sync" + "time" + + "github.com/pkg/errors" + + "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/appdatabase" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/logutils" + "github.com/status-im/status-go/multiaccounts" + "github.com/status-im/status-go/multiaccounts/accounts" + "github.com/status-im/status-go/node" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/rpc" + accountssvc "github.com/status-im/status-go/services/accounts" + nimbussvc "github.com/status-im/status-go/services/nimbus" + "github.com/status-im/status-go/services/personal" + "github.com/status-im/status-go/services/rpcfilters" + "github.com/status-im/status-go/services/subscriptions" + "github.com/status-im/status-go/services/typeddata" + "github.com/status-im/status-go/signal" + "github.com/status-im/status-go/transactions" +) + +// const ( +// contractQueryTimeout = 1000 * time.Millisecond +// ) + +var ( + // ErrWhisperClearIdentitiesFailure clearing whisper identities has failed. + ErrWhisperClearIdentitiesFailure = errors.New("failed to clear whisper identities") + // ErrWhisperIdentityInjectionFailure injecting whisper identities has failed. + ErrWhisperIdentityInjectionFailure = errors.New("failed to inject identity into Whisper") + // ErrUnsupportedRPCMethod is for methods not supported by the RPC interface + ErrUnsupportedRPCMethod = errors.New("method is unsupported by RPC interface") + // ErrRPCClientUnavailable is returned if an RPC client can't be retrieved. + // This is a normal situation when a node is stopped. + ErrRPCClientUnavailable = errors.New("JSON-RPC client is unavailable") +) + +var _ StatusBackend = (*nimbusStatusBackend)(nil) + +// nimbusStatusBackend implements the Status.im service over Nimbus +type nimbusStatusBackend struct { + StatusBackend + + mu sync.Mutex + // rootDataDir is the same for all networks. + rootDataDir string + appDB *sql.DB + statusNode *node.NimbusStatusNode + // personalAPI *personal.PublicAPI + // rpcFilters *rpcfilters.Service + multiaccountsDB *multiaccounts.Database + accountManager *account.GethManager + // transactor *transactions.Transactor + connectionState connectionState + appState appState + selectedAccountShhKeyID string + log log.Logger + allowAllRPC bool // used only for tests, disables api method restrictions +} + +// NewNimbusStatusBackend create a new nimbusStatusBackend instance +func NewNimbusStatusBackend() *nimbusStatusBackend { + defer log.Info("Status backend initialized", "backend", "nimbus", "version", params.Version, "commit", params.GitCommit) + + statusNode := node.NewNimbus() + accountManager := account.NewGethManager() + // transactor := transactions.NewTransactor() + // personalAPI := personal.NewAPI() + // rpcFilters := rpcfilters.New(statusNode) + return &nimbusStatusBackend{ + statusNode: statusNode, + accountManager: accountManager, + // transactor: transactor, + // personalAPI: personalAPI, + // rpcFilters: rpcFilters, + log: log.New("package", "status-go/api.nimbusStatusBackend"), + } +} + +// StatusNode returns reference to node manager +func (b *nimbusStatusBackend) StatusNode() *node.NimbusStatusNode { + return b.statusNode +} + +// AccountManager returns reference to account manager +func (b *nimbusStatusBackend) AccountManager() *account.GethManager { + return b.accountManager +} + +// // Transactor returns reference to a status transactor +// func (b *nimbusStatusBackend) Transactor() *transactions.Transactor { +// return b.transactor +// } + +// SelectedAccountShhKeyID returns a Whisper key ID of the selected chat key pair. +func (b *nimbusStatusBackend) SelectedAccountShhKeyID() string { + return b.selectedAccountShhKeyID +} + +// IsNodeRunning confirm that node is running +func (b *nimbusStatusBackend) IsNodeRunning() bool { + return b.statusNode.IsRunning() +} + +// StartNode start Status node, fails if node is already started +func (b *nimbusStatusBackend) StartNode(config *params.NodeConfig) error { + b.mu.Lock() + defer b.mu.Unlock() + if err := b.startNode(config); err != nil { + signal.SendNodeCrashed(err) + return err + } + return nil +} + +func (b *nimbusStatusBackend) UpdateRootDataDir(datadir string) { + b.mu.Lock() + defer b.mu.Unlock() + b.rootDataDir = datadir +} + +func (b *nimbusStatusBackend) OpenAccounts() error { + b.mu.Lock() + defer b.mu.Unlock() + if b.multiaccountsDB != nil { + return nil + } + db, err := multiaccounts.InitializeDB(filepath.Join(b.rootDataDir, "accounts.sql")) + if err != nil { + return err + } + b.multiaccountsDB = db + return nil +} + +func (b *nimbusStatusBackend) GetAccounts() ([]multiaccounts.Account, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.multiaccountsDB == nil { + return nil, errors.New("accounts db wasn't initialized") + } + return b.multiaccountsDB.GetAccounts() +} + +func (b *nimbusStatusBackend) SaveAccount(account multiaccounts.Account) error { + b.mu.Lock() + defer b.mu.Unlock() + if b.multiaccountsDB == nil { + return errors.New("accounts db wasn't initialized") + } + return b.multiaccountsDB.SaveAccount(account) +} + +func (b *nimbusStatusBackend) ensureAppDBOpened(account multiaccounts.Account, password string) (err error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.appDB != nil { + return nil + } + if len(b.rootDataDir) == 0 { + return errors.New("root datadir wasn't provided") + } + path := filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", account.KeyUID)) + b.appDB, err = appdatabase.InitializeDB(path, password) + if err != nil { + return err + } + return nil +} + +// StartNodeWithKey instead of loading addresses from database this method derives address from key +// and uses it in application. +// TODO: we should use a proper struct with optional values instead of duplicating the regular functions +// with small variants for keycard, this created too many bugs +func (b *nimbusStatusBackend) startNodeWithKey(acc multiaccounts.Account, password string, keyHex string) error { + err := b.ensureAppDBOpened(acc, password) + if err != nil { + return err + } + conf, err := b.loadNodeConfig() + if err != nil { + return err + } + if err := logutils.OverrideRootLogWithConfig(conf, false); err != nil { + return err + } + accountsDB := accounts.NewDB(b.appDB) + walletAddr, err := accountsDB.GetWalletAddress() + if err != nil { + return err + } + watchAddrs, err := accountsDB.GetAddresses() + if err != nil { + return err + } + chatKey, err := crypto.HexToECDSA(keyHex) + if err != nil { + return err + } + err = b.StartNode(conf) + if err != nil { + return err + } + b.accountManager.SetChatAccount(chatKey) + _, err = b.accountManager.SelectedChatAccount() + if err != nil { + return err + } + b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...) + err = b.injectAccountIntoServices() + if err != nil { + return err + } + err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix()) + if err != nil { + return err + } + return nil +} + +func (b *nimbusStatusBackend) StartNodeWithKey(acc multiaccounts.Account, password string, keyHex string) error { + err := b.startNodeWithKey(acc, password, keyHex) + if err != nil { + // Stop node for clean up + _ = b.StopNode() + } + signal.SendLoggedIn(err) + return err +} + +func (b *nimbusStatusBackend) startNodeWithAccount(acc multiaccounts.Account, password string) error { + err := b.ensureAppDBOpened(acc, password) + if err != nil { + return err + } + conf, err := b.loadNodeConfig() + if err != nil { + return err + } + if err := logutils.OverrideRootLogWithConfig(conf, false); err != nil { + return err + } + accountsDB := accounts.NewDB(b.appDB) + chatAddr, err := accountsDB.GetChatAddress() + if err != nil { + return err + } + walletAddr, err := accountsDB.GetWalletAddress() + if err != nil { + return err + } + watchAddrs, err := accountsDB.GetAddresses() + if err != nil { + return err + } + login := account.LoginParams{ + Password: password, + ChatAddress: chatAddr, + WatchAddresses: watchAddrs, + MainAccount: walletAddr, + } + err = b.StartNode(conf) + if err != nil { + return err + } + err = b.SelectAccount(login) + if err != nil { + return err + } + err = b.multiaccountsDB.UpdateAccountTimestamp(acc.KeyUID, time.Now().Unix()) + if err != nil { + return err + } + return nil +} + +func (b *nimbusStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, password string) error { + err := b.startNodeWithAccount(acc, password) + if err != nil { + // Stop node for clean up + _ = b.StopNode() + } + signal.SendLoggedIn(err) + return err +} + +func (b *nimbusStatusBackend) SaveAccountAndStartNodeWithKey(acc multiaccounts.Account, password string, settings accounts.Settings, nodecfg *params.NodeConfig, subaccs []accounts.Account, keyHex string) error { + err := b.SaveAccount(acc) + if err != nil { + return err + } + err = b.ensureAppDBOpened(acc, password) + if err != nil { + return err + } + err = b.saveAccountsAndSettings(settings, nodecfg, subaccs) + if err != nil { + return err + } + return b.StartNodeWithKey(acc, password, keyHex) +} + +// StartNodeWithAccountAndConfig is used after account and config was generated. +// In current setup account name and config is generated on the client side. Once/if it will be generated on +// status-go side this flow can be simplified. +func (b *nimbusStatusBackend) StartNodeWithAccountAndConfig( + account multiaccounts.Account, + password string, + settings accounts.Settings, + nodecfg *params.NodeConfig, + subaccs []accounts.Account, +) error { + err := b.SaveAccount(account) + if err != nil { + return err + } + err = b.ensureAppDBOpened(account, password) + if err != nil { + return err + } + err = b.saveAccountsAndSettings(settings, nodecfg, subaccs) + if err != nil { + return err + } + return b.StartNodeWithAccount(account, password) +} + +func (b *nimbusStatusBackend) saveAccountsAndSettings(settings accounts.Settings, nodecfg *params.NodeConfig, subaccs []accounts.Account) error { + b.mu.Lock() + defer b.mu.Unlock() + accdb := accounts.NewDB(b.appDB) + err := accdb.CreateSettings(settings, *nodecfg) + if err != nil { + return err + } + return accdb.SaveAccounts(subaccs) +} + +func (b *nimbusStatusBackend) loadNodeConfig() (*params.NodeConfig, error) { + b.mu.Lock() + defer b.mu.Unlock() + var conf params.NodeConfig + err := accounts.NewDB(b.appDB).GetNodeConfig(&conf) + if err != nil { + return nil, err + } + // NodeConfig.Version should be taken from params.Version + // which is set at the compile time. + // What's cached is usually outdated so we overwrite it here. + conf.Version = params.Version + return &conf, nil +} + +func (b *nimbusStatusBackend) rpcFiltersService() nimbussvc.ServiceConstructor { + return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { + return rpcfilters.New(b.statusNode), nil + } +} + +func (b *nimbusStatusBackend) subscriptionService() nimbussvc.ServiceConstructor { + return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { + return subscriptions.New(b.statusNode), nil + } +} + +func (b *nimbusStatusBackend) accountsService(accountsFeed *event.Feed) nimbussvc.ServiceConstructor { + return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { + return accountssvc.NewService(accounts.NewDB(b.appDB), b.multiaccountsDB, b.accountManager.Manager, accountsFeed), nil + } +} + +// func (b *nimbusStatusBackend) browsersService() nimbussvc.ServiceConstructor { +// return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// return browsers.NewService(browsers.NewDB(b.appDB)), nil +// } +// } + +// func (b *nimbusStatusBackend) permissionsService() nimbussvc.ServiceConstructor { +// return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// return permissions.NewService(permissions.NewDB(b.appDB)), nil +// } +// } + +// func (b *nimbusStatusBackend) mailserversService() nimbussvc.ServiceConstructor { +// return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// return mailservers.NewService(mailservers.NewDB(b.appDB)), nil +// } +// } + +// func (b *nimbusStatusBackend) walletService(network uint64, accountsFeed *event.Feed) nimbussvc.ServiceConstructor { +// return func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// return wallet.NewService(wallet.NewDB(b.appDB, network), accountsFeed), nil +// } +// } + +func (b *nimbusStatusBackend) startNode(config *params.NodeConfig) (err error) { + // defer func() { + // if r := recover(); r != nil { + // err = fmt.Errorf("node crashed on start: %v", err) + // } + // }() + + // Start by validating configuration + if err := config.Validate(); err != nil { + return err + } + + accountsFeed := &event.Feed{} + services := []nimbussvc.ServiceConstructor{} + services = appendIf(config.UpstreamConfig.Enabled, services, b.rpcFiltersService()) + services = append(services, b.subscriptionService()) + services = appendIf(b.appDB != nil && b.multiaccountsDB != nil, services, b.accountsService(accountsFeed)) + // services = appendIf(config.BrowsersConfig.Enabled, services, b.browsersService()) + // services = appendIf(config.PermissionsConfig.Enabled, services, b.permissionsService()) + // services = appendIf(config.MailserversConfig.Enabled, services, b.mailserversService()) + // services = appendIf(config.WalletConfig.Enabled, services, b.walletService(config.NetworkID, accountsFeed)) + + // manager := b.accountManager.GetManager() + // if manager == nil { + // return errors.New("ethereum accounts.Manager is nil") + // } + if err = b.statusNode.StartWithOptions(config, node.NimbusStartOptions{ + Services: services, + // The peers discovery protocols are started manually after + // `node.ready` signal is sent. + // It was discussed in https://github.com/status-im/status-go/pull/1333. + StartDiscovery: false, + // AccountsManager: manager, + }); err != nil { + return + } + signal.SendNodeStarted() + + // b.transactor.SetNetworkID(config.NetworkID) + // b.transactor.SetRPC(b.statusNode.RPCClient(), rpc.DefaultCallTimeout) + // b.personalAPI.SetRPC(b.statusNode.RPCPrivateClient(), rpc.DefaultCallTimeout) + + if err = b.registerHandlers(); err != nil { + b.log.Error("Handler registration failed", "err", err) + return + } + b.log.Info("Handlers registered") + + if st, err := b.statusNode.StatusService(); err == nil { + st.SetAccountManager(b.accountManager) + } + + // if st, err := b.statusNode.PeerService(); err == nil { + // st.SetDiscoverer(b.StatusNode()) + // } + + // Handle a case when a node is stopped and resumed. + // If there is no account selected, an error is returned. + if _, err := b.accountManager.SelectedChatAccount(); err == nil { + if err := b.injectAccountIntoServices(); err != nil { + return err + } + } else if err != account.ErrNoAccountSelected { + return err + } + + signal.SendNodeReady() + + // if err := b.statusNode.StartDiscovery(); err != nil { + // return err + // } + + return nil +} + +// StopNode stop Status node. Stopped node cannot be resumed. +func (b *nimbusStatusBackend) StopNode() error { + b.mu.Lock() + defer b.mu.Unlock() + return b.stopNode() +} + +func (b *nimbusStatusBackend) stopNode() error { + if !b.IsNodeRunning() { + return node.ErrNoRunningNode + } + defer signal.SendNodeStopped() + return b.statusNode.Stop() +} + +// RestartNode restart running Status node, fails if node is not running +func (b *nimbusStatusBackend) RestartNode() error { + b.mu.Lock() + defer b.mu.Unlock() + + if !b.IsNodeRunning() { + return node.ErrNoRunningNode + } + + newcfg := *(b.statusNode.Config()) + if err := b.stopNode(); err != nil { + return err + } + return b.startNode(&newcfg) +} + +// ResetChainData remove chain data from data directory. +// Node is stopped, and new node is started, with clean data directory. +func (b *nimbusStatusBackend) ResetChainData() error { + panic("ResetChainData") + // b.mu.Lock() + // defer b.mu.Unlock() + // newcfg := *(b.statusNode.Config()) + // if err := b.stopNode(); err != nil { + // return err + // } + // // config is cleaned when node is stopped + // if err := b.statusNode.ResetChainData(&newcfg); err != nil { + // return err + // } + // signal.SendChainDataRemoved() + // return b.startNode(&newcfg) +} + +// CallRPC executes public RPC requests on node's in-proc RPC server. +func (b *nimbusStatusBackend) CallRPC(inputJSON string) (string, error) { + client := b.statusNode.RPCClient() + if client == nil { + return "", ErrRPCClientUnavailable + } + return client.CallRaw(inputJSON), nil +} + +// GetNodesFromContract returns a list of nodes from the contract +func (b *nimbusStatusBackend) GetNodesFromContract(rpcEndpoint string, contractAddress string) ([]string, error) { + panic("GetNodesFromContract") + // var response []string + + // ctx, cancel := context.WithTimeout(context.Background(), contractQueryTimeout) + // defer cancel() + + // ethclient, err := ethclient.DialContext(ctx, rpcEndpoint) + // if err != nil { + // return response, err + // } + + // contract, err := registry.NewNodes(types.HexToAddress(contractAddress), ethclient) + // if err != nil { + // return response, err + // } + + // nodeCount, err := contract.NodeCount(nil) + // if err != nil { + // return response, err + // } + + // one := big.NewInt(1) + // for i := big.NewInt(0); i.Cmp(nodeCount) < 0; i.Add(i, one) { + // node, err := contract.Nodes(nil, i) + // if err != nil { + // return response, err + // } + // response = append(response, node) + // } + + // return response, nil +} + +// CallPrivateRPC executes public and private RPC requests on node's in-proc RPC server. +func (b *nimbusStatusBackend) CallPrivateRPC(inputJSON string) (string, error) { + client := b.statusNode.RPCPrivateClient() + if client == nil { + return "", ErrRPCClientUnavailable + } + return client.CallRaw(inputJSON), nil +} + +// SendTransaction creates a new transaction and waits until it's complete. +func (b *nimbusStatusBackend) SendTransaction(sendArgs transactions.SendTxArgs, password string) (hash types.Hash, err error) { + panic("SendTransaction") + // verifiedAccount, err := b.getVerifiedWalletAccount(sendArgs.From.String(), password) + // if err != nil { + // return hash, err + // } + + // hash, err = b.transactor.SendTransaction(sendArgs, verifiedAccount) + // if err != nil { + // return + // } + + // go b.rpcFilters.TriggerTransactionSentToUpstreamEvent(hash) + + // return +} + +func (b *nimbusStatusBackend) SendTransactionWithSignature(sendArgs transactions.SendTxArgs, sig []byte) (hash types.Hash, err error) { + panic("SendTransactionWithSignature") + // hash, err = b.transactor.SendTransactionWithSignature(sendArgs, sig) + // if err != nil { + // return + // } + + // go b.rpcFilters.TriggerTransactionSentToUpstreamEvent(hash) + + // return +} + +// HashTransaction validate the transaction and returns new sendArgs and the transaction hash. +func (b *nimbusStatusBackend) HashTransaction(sendArgs transactions.SendTxArgs) (transactions.SendTxArgs, types.Hash, error) { + panic("HashTransaction") + // return b.transactor.HashTransaction(sendArgs) +} + +// SignMessage checks the pwd vs the selected account and passes on the signParams +// to personalAPI for message signature +func (b *nimbusStatusBackend) SignMessage(rpcParams personal.SignParams) (types.HexBytes, error) { + panic("SignMessage") + // verifiedAccount, err := b.getVerifiedWalletAccount(rpcParams.Address, rpcParams.Password) + // if err != nil { + // return types.Bytes{}, err + // } + // return b.personalAPI.Sign(rpcParams, verifiedAccount) +} + +// Recover calls the personalAPI to return address associated with the private +// key that was used to calculate the signature in the message +func (b *nimbusStatusBackend) Recover(rpcParams personal.RecoverParams) (types.Address, error) { + panic("Recover") + // return b.personalAPI.Recover(rpcParams) +} + +// SignTypedData accepts data and password. Gets verified account and signs typed data. +func (b *nimbusStatusBackend) SignTypedData(typed typeddata.TypedData, address string, password string) (types.HexBytes, error) { + account, err := b.getVerifiedWalletAccount(address, password) + if err != nil { + return types.HexBytes{}, err + } + chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID) + sig, err := typeddata.Sign(typed, account.AccountKey.PrivateKey, chain) + if err != nil { + return types.HexBytes{}, err + } + return types.HexBytes(sig), err +} + +// HashTypedData generates the hash of TypedData. +func (b *nimbusStatusBackend) HashTypedData(typed typeddata.TypedData) (types.Hash, error) { + chain := new(big.Int).SetUint64(b.StatusNode().Config().NetworkID) + hash, err := typeddata.ValidateAndHash(typed, chain) + if err != nil { + return types.Hash{}, err + } + return types.Hash(hash), err +} + +func (b *nimbusStatusBackend) getVerifiedWalletAccount(address, password string) (*account.SelectedExtKey, error) { + config := b.StatusNode().Config() + + db := accounts.NewDB(b.appDB) + exists, err := db.AddressExists(types.HexToAddress(address)) + if err != nil { + b.log.Error("failed to query db for a given address", "address", address, "error", err) + return nil, err + } + + if !exists { + b.log.Error("failed to get a selected account", "err", transactions.ErrInvalidTxSender) + return nil, transactions.ErrAccountDoesntExist + } + + key, err := b.accountManager.VerifyAccountPassword(config.KeyStoreDir, address, password) + if err != nil { + b.log.Error("failed to verify account", "account", address, "error", err) + return nil, err + } + + return &account.SelectedExtKey{ + Address: key.Address, + AccountKey: key, + }, nil +} + +// registerHandlers attaches Status callback handlers to running node +func (b *nimbusStatusBackend) registerHandlers() error { + var clients []*rpc.Client + + if c := b.StatusNode().RPCClient(); c != nil { + clients = append(clients, c) + } else { + return errors.New("RPC client unavailable") + } + + if c := b.StatusNode().RPCPrivateClient(); c != nil { + clients = append(clients, c) + } else { + return errors.New("RPC private client unavailable") + } + + for _, client := range clients { + client.RegisterHandler( + params.AccountsMethodName, + func(context.Context, ...interface{}) (interface{}, error) { + return b.accountManager.Accounts() + }, + ) + + if b.allowAllRPC { + // this should only happen in unit-tests, this variable is not available outside this package + continue + } + client.RegisterHandler(params.SendTransactionMethodName, unsupportedMethodHandler) + client.RegisterHandler(params.PersonalSignMethodName, unsupportedMethodHandler) + client.RegisterHandler(params.PersonalRecoverMethodName, unsupportedMethodHandler) + } + + return nil +} + +func unsupportedMethodHandler(ctx context.Context, rpcParams ...interface{}) (interface{}, error) { + return nil, ErrUnsupportedRPCMethod +} + +// ConnectionChange handles network state changes logic. +func (b *nimbusStatusBackend) ConnectionChange(typ string, expensive bool) { + b.mu.Lock() + defer b.mu.Unlock() + + state := connectionState{ + Type: newConnectionType(typ), + Expensive: expensive, + } + if typ == none { + state.Offline = true + } + + b.log.Info("Network state change", "old", b.connectionState, "new", state) + + b.connectionState = state + + // logic of handling state changes here + // restart node? force peers reconnect? etc +} + +// AppStateChange handles app state changes (background/foreground). +// state values: see https://facebook.github.io/react-native/docs/appstate.html +func (b *nimbusStatusBackend) AppStateChange(state string) { + s, err := parseAppState(state) + if err != nil { + log.Error("AppStateChange failed, ignoring", "error", err) + return // and do nothing + } + + b.log.Info("App State changed", "new-state", s) + b.appState = s + + // TODO: put node in low-power mode if the app is in background (or inactive) + // and normal mode if the app is in foreground. +} + +// Logout clears whisper identities. +func (b *nimbusStatusBackend) Logout() error { + b.mu.Lock() + defer b.mu.Unlock() + + err := b.cleanupServices() + if err != nil { + return err + } + err = b.closeAppDB() + if err != nil { + return err + } + + b.accountManager.Logout() + + return nil +} + +// cleanupServices stops parts of services that doesn't managed by a node and removes injected data from services. +func (b *nimbusStatusBackend) cleanupServices() error { + whisperService, err := b.statusNode.WhisperService() + switch err { + case node.ErrServiceUnknown: // Whisper was never registered + case nil: + if err := whisperService.Whisper.DeleteKeyPairs(); err != nil { + return fmt.Errorf("%s: %v", ErrWhisperClearIdentitiesFailure, err) + } + b.selectedAccountShhKeyID = "" + default: + return err + } + // if b.statusNode.Config().WalletConfig.Enabled { + // wallet, err := b.statusNode.WalletService() + // switch err { + // case node.ErrServiceUnknown: + // case nil: + // err = wallet.StopReactor() + // if err != nil { + // return err + // } + // default: + // return err + // } + // } + return nil +} + +func (b *nimbusStatusBackend) closeAppDB() error { + if b.appDB != nil { + err := b.appDB.Close() + if err != nil { + return err + } + b.appDB = nil + return nil + } + return nil +} + +// SelectAccount selects current wallet and chat accounts, by verifying that each address has corresponding account which can be decrypted +// using provided password. Once verification is done, the decrypted chat key is injected into Whisper (as a single identity, +// all previous identities are removed). +func (b *nimbusStatusBackend) SelectAccount(loginParams account.LoginParams) error { + b.mu.Lock() + defer b.mu.Unlock() + + b.AccountManager().RemoveOnboarding() + + err := b.accountManager.SelectAccount(loginParams) + if err != nil { + return err + } + + if err := b.injectAccountIntoServices(); err != nil { + return err + } + + // if err := b.startWallet(); err != nil { + // return err + // } + + return nil +} + +func (b *nimbusStatusBackend) injectAccountIntoServices() error { + chatAccount, err := b.accountManager.SelectedChatAccount() + if err != nil { + return err + } + + identity := chatAccount.AccountKey.PrivateKey + whisperService, err := b.statusNode.WhisperService() + + switch err { + case node.ErrServiceUnknown: // Whisper was never registered + case nil: + if err := whisperService.Whisper.DeleteKeyPairs(); err != nil { // err is not possible; method return value is incorrect + return err + } + b.selectedAccountShhKeyID, err = whisperService.Whisper.AddKeyPair(identity) + if err != nil { + return ErrWhisperIdentityInjectionFailure + } + default: + return err + } + + if whisperService != nil { + st, err := b.statusNode.ShhExtService() + if err != nil { + return err + } + + if err := st.InitProtocol(identity, b.appDB); err != nil { + return err + } + } + return nil +} + +// func (b *nimbusStatusBackend) startWallet() error { +// if !b.statusNode.Config().WalletConfig.Enabled { +// return nil +// } + +// wallet, err := b.statusNode.WalletService() +// if err != nil { +// return err +// } + +// watchAddresses := b.accountManager.WatchAddresses() +// mainAccountAddress, err := b.accountManager.MainAccountAddress() +// if err != nil { +// return err +// } + +// allAddresses := make([]types.Address, len(watchAddresses)+1) +// allAddresses[0] = mainAccountAddress +// copy(allAddresses[1:], watchAddresses) +// return wallet.StartReactor( +// b.statusNode.RPCClient().Ethclient(), +// allAddresses, +// new(big.Int).SetUint64(b.statusNode.Config().NetworkID), +// ) +// } + +// InjectChatAccount selects the current chat account using chatKeyHex and injects the key into whisper. +// TODO: change the interface and omit the last argument. +func (b *nimbusStatusBackend) InjectChatAccount(chatKeyHex, _ string) error { + b.mu.Lock() + defer b.mu.Unlock() + + b.accountManager.Logout() + + chatKey, err := crypto.HexToECDSA(chatKeyHex) + if err != nil { + return err + } + b.accountManager.SetChatAccount(chatKey) + + return b.injectAccountIntoServices() +} + +func appendIf(condition bool, services []nimbussvc.ServiceConstructor, service nimbussvc.ServiceConstructor) []nimbussvc.ServiceConstructor { + if !condition { + return services + } + return append(services, service) +} + +// ExtractGroupMembershipSignatures extract signatures from tuples of content/signature +func (b *nimbusStatusBackend) ExtractGroupMembershipSignatures(signaturePairs [][2]string) ([]string, error) { + return crypto.ExtractSignatures(signaturePairs) +} + +// SignGroupMembership signs a piece of data containing membership information +func (b *nimbusStatusBackend) SignGroupMembership(content string) (string, error) { + selectedChatAccount, err := b.accountManager.SelectedChatAccount() + if err != nil { + return "", err + } + + return crypto.SignStringAsHex(content, selectedChatAccount.AccountKey.PrivateKey) +} + +// EnableInstallation enables an installation for multi-device sync. +func (b *nimbusStatusBackend) EnableInstallation(installationID string) error { + st, err := b.statusNode.ShhExtService() + if err != nil { + return err + } + + if err := st.EnableInstallation(installationID); err != nil { + b.log.Error("error enabling installation", "err", err) + return err + } + + return nil +} + +// DisableInstallation disables an installation for multi-device sync. +func (b *nimbusStatusBackend) DisableInstallation(installationID string) error { + st, err := b.statusNode.ShhExtService() + if err != nil { + return err + } + + if err := st.DisableInstallation(installationID); err != nil { + b.log.Error("error disabling installation", "err", err) + return err + } + + return nil +} + +// UpdateMailservers on ShhExtService. +func (b *nimbusStatusBackend) UpdateMailservers(enodes []string) error { + // TODO + return nil + // st, err := b.statusNode.ShhExtService() + // if err != nil { + // return err + // } + // nodes := make([]*enode.Node, len(enodes)) + // for i, rawurl := range enodes { + // node, err := enode.ParseV4(rawurl) + // if err != nil { + // return err + // } + // nodes[i] = node + // } + // return st.UpdateMailservers(nodes) +} + +// SignHash exposes vanilla ECDSA signing for signing a message for Swarm +func (b *nimbusStatusBackend) SignHash(hexEncodedHash string) (string, error) { + hash, err := types.DecodeHex(hexEncodedHash) + if err != nil { + return "", fmt.Errorf("SignHash: could not unmarshal the input: %v", err) + } + + chatAccount, err := b.accountManager.SelectedChatAccount() + if err != nil { + return "", fmt.Errorf("SignHash: could not select account: %v", err.Error()) + } + + signature, err := crypto.Sign(hash, chatAccount.AccountKey.PrivateKey) + if err != nil { + return "", fmt.Errorf("SignHash: could not sign the hash: %v", err) + } + + hexEncodedSignature := types.EncodeHex(signature) + return hexEncodedSignature, nil +} diff --git a/eth-node/bridge/geth/node.go b/eth-node/bridge/geth/node.go index c4666fe5f..a629ee835 100644 --- a/eth-node/bridge/geth/node.go +++ b/eth-node/bridge/geth/node.go @@ -24,6 +24,10 @@ func NewNodeBridge(stack *node.Node) types.Node { return &gethNodeWrapper{stack: stack} } +func (w *gethNodeWrapper) Poll() { + // noop +} + func (w *gethNodeWrapper) NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifier { return gethens.NewVerifier(logger) } diff --git a/eth-node/bridge/geth/whisper.go b/eth-node/bridge/geth/whisper.go index 2423a3264..87f381c93 100644 --- a/eth-node/bridge/geth/whisper.go +++ b/eth-node/bridge/geth/whisper.go @@ -80,6 +80,11 @@ func (w *gethWhisperWrapper) DeleteKeyPair(keyID string) bool { return w.whisper.DeleteKeyPair(keyID) } +// DeleteKeyPairs removes all cryptographic identities known to the node +func (w *gethWhisperWrapper) DeleteKeyPairs() error { + return w.whisper.DeleteKeyPairs() +} + func (w *gethWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) { return w.whisper.AddSymKeyDirect(key) } diff --git a/eth-node/bridge/nimbus/build-nimbus.sh b/eth-node/bridge/nimbus/build-nimbus.sh index a139d51d6..48751f54f 100755 --- a/eth-node/bridge/nimbus/build-nimbus.sh +++ b/eth-node/bridge/nimbus/build-nimbus.sh @@ -13,7 +13,7 @@ target_dir="${GIT_ROOT}/vendor/github.com/status-im/status-go/eth-node/bridge/ni if [ -z "$nimbus_dir" ]; then # The git ref of Nimbus to fetch and build. This should represent a commit SHA or a tag, for reproducible builds - nimbus_ref='master' # TODO: Use a tag once + nimbus_ref='feature/android-api' # TODO: Use a tag once nimbus_src='https://github.com/status-im/nimbus/' nimbus_dir="${GIT_ROOT}/vendor/github.com/status-im/nimbus" @@ -32,13 +32,9 @@ if [ -z "$nimbus_dir" ]; then fi # Build Nimbus wrappers and copy them into the Nimbus bridge in status-eth-node -build_dir=$(cd $nimbus_dir && nix-build --pure --no-out-link -A wrappers) -# Ideally we'd use the static version of the Nimbus library (.a), -# however that causes link errors due to duplicate symbols: -# ${target_dir}/libnimbus.a(secp256k1.c.o): In function `secp256k1_context_create': -# (.text+0xca80): multiple definition of `secp256k1_context_create' -# /tmp/go-link-476687730/000014.o:${GIT_ROOT}/vendor/github.com/ethereum/go-ethereum/crypto/secp256k1/./libsecp256k1/src/secp256k1.c:56: first defined here +build_dir=$(nix-build --pure --no-out-link -A wrappers-native $nimbus_dir/nix/default.nix) rm -f ${target_dir}/libnimbus.* mkdir -p ${target_dir} cp -f ${build_dir}/include/* ${build_dir}/lib/libnimbus.so \ ${target_dir}/ +chmod +w ${target_dir}/libnimbus.{so,h} diff --git a/eth-node/bridge/nimbus/node.go b/eth-node/bridge/nimbus/node.go index 9e058aa08..1feff085d 100644 --- a/eth-node/bridge/nimbus/node.go +++ b/eth-node/bridge/nimbus/node.go @@ -104,6 +104,10 @@ func (n *nimbusNodeWrapper) GetWhisper(ctx interface{}) (types.Whisper, error) { return n.w, nil } +func (w *nimbusNodeWrapper) GetWaku(ctx interface{}) (types.Waku, error) { + panic("not implemented") +} + func (n *nimbusNodeWrapper) AddPeer(url string) error { urlC := C.CString(url) defer C.free(unsafe.Pointer(urlC)) diff --git a/eth-node/bridge/nimbus/whisper.go b/eth-node/bridge/nimbus/whisper.go index 917b01c5f..de241bf33 100644 --- a/eth-node/bridge/nimbus/whisper.go +++ b/eth-node/bridge/nimbus/whisper.go @@ -164,6 +164,16 @@ func (w *nimbusWhisperWrapper) DeleteKeyPair(keyID string) bool { return retVal.value.(bool) } +// DeleteKeyPairs removes all cryptographic identities known to the node +func (w *nimbusWhisperWrapper) DeleteKeyPairs() error { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + C.nimbus_delete_keypairs() + c <- callReturn{} + }) + + return retVal.err +} + func (w *nimbusWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) { retVal := w.routineQueue.Send(func(c chan<- callReturn) { keyC := C.CBytes(key) diff --git a/eth-node/types/whisper.go b/eth-node/types/whisper.go index 23529c4da..f3266bbab 100644 --- a/eth-node/types/whisper.go +++ b/eth-node/types/whisper.go @@ -31,6 +31,8 @@ type Whisper interface { AddKeyPair(key *ecdsa.PrivateKey) (string, error) // DeleteKeyPair deletes the key with the specified ID if it exists. DeleteKeyPair(keyID string) bool + // DeleteKeyPairs removes all cryptographic identities known to the node + DeleteKeyPairs() error AddSymKeyDirect(key []byte) (string, error) AddSymKeyFromPassword(password string) (string, error) DeleteSymKey(id string) bool diff --git a/go.mod b/go.mod index c87f0fa7e..25879432b 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/syndtr/goleveldb v1.0.0 go.uber.org/zap v1.13.0 golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c + golang.org/x/tools v0.0.0-20200116062425-473961ec044c // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 diff --git a/go.sum b/go.sum index 8ee4f0395..3c3c1d397 100644 --- a/go.sum +++ b/go.sum @@ -22,14 +22,11 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v0.0.0-20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM= @@ -40,7 +37,6 @@ github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8 github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aristanetworks/goarista v0.0.0-20190219163901-728bce664cf5/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= -github.com/aristanetworks/goarista v0.0.0-20190502180301-283422fc1708 h1:tS7jSmwRqSxTnonTRlDD1oHo6Q9YOK4xHS9/v4L56eg= github.com/aristanetworks/goarista v0.0.0-20190502180301-283422fc1708/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/aristanetworks/goarista v0.0.0-20191106175434-873d404c7f40 h1:ZdRuixFqR3mfx4FHzclG3COrRgWrYq0VhNgIoYoObcM= github.com/aristanetworks/goarista v0.0.0-20191106175434-873d404c7f40/go.mod h1:Z4RTxGAuYhPzcq8+EdRM+R8M48Ssle2TsWtwRKa+vns= @@ -72,7 +68,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -132,13 +127,10 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa h1:o8OuEkracbk3qH6GvlI6XpEN1HTSxkzOG42xZpfDv/s= github.com/elastic/gosigar v0.0.0-20180330100440-37f05ff46ffa/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= -github.com/elastic/gosigar v0.10.4 h1:6jfw75dsoflhBMRdO6QPzQUgLqUYTsQQQRkkcsHsuPo= github.com/elastic/gosigar v0.10.4/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/elastic/gosigar v0.10.5 h1:GzPQ+78RaAb4J63unidA/JavQRKrB6s8IOzN6Ib59jo= github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= @@ -146,7 +138,6 @@ github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo github.com/ethereum/go-ethereum v1.8.20/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/ethereum/go-ethereum v1.9.2/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc h1:jtW8jbpkO4YirRSyepBOH8E+2HEw6/hKkBvFPwhUN8c= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20180929194037-2a09253e352a h1:1znxn4+q2MrEdTk1eCk6KIV3muTYVclBIB6CTVR/zBc= github.com/fjl/memsize v0.0.0-20180929194037-2a09253e352a/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= @@ -164,7 +155,6 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -178,7 +168,6 @@ github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhD github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -273,7 +262,6 @@ github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGk github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v0.0.0-20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= @@ -312,7 +300,6 @@ github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b h1:wxtKgYHEncAU00muMD06dzLiahtGM1eouRNOzVV7tdQ= github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -341,19 +328,15 @@ github.com/libp2p/go-eventbus v0.1.0 h1:mlawomSAjjkk97QnYiEmHsLu7E136+2oCWSHRUvM github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-flow-metrics v0.0.1 h1:0gxuFd2GuK7IIP5pKljLwps6TvcuYgvG7Atqi3INF5s= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= -github.com/libp2p/go-libp2p v0.1.1 h1:52sB0TJuDk2nYMcMfHOKaPoaayDZjaYVCq6Vk1ejUTk= github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= github.com/libp2p/go-libp2p v0.4.2 h1:p0cthB0jDNHO4gH2HzS8/nAMMXbfUlFHs0jwZ4U+F2g= github.com/libp2p/go-libp2p v0.4.2/go.mod h1:MNmgUxUw5pMsdOzMlT0EE7oKjRasl+WyVwM0IBlpKgQ= -github.com/libp2p/go-libp2p-autonat v0.1.0 h1:aCWAu43Ri4nU0ZPO7NyLzUvvfqd0nE3dX0R/ZGYVgOU= github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= github.com/libp2p/go-libp2p-autonat v0.1.1 h1:WLBZcIRsjZlWdAZj9CiBSvU2wQXoUOiS1Zk1tM7DTJI= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= -github.com/libp2p/go-libp2p-blankhost v0.1.1 h1:X919sCh+KLqJcNRApj43xCSiQRYqOSI88Fdf55ngf78= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUADzkJacgZLvk= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= -github.com/libp2p/go-libp2p-circuit v0.1.0 h1:eniLL3Y9aq/sryfyV1IAHj5rlvuyj3b7iz8tSiZpdhY= github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= @@ -364,7 +347,6 @@ github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4 h1:Et6ykkTwI6PU44tr8qUF9k43vP0aduMNniShAbUJJw8= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= -github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.1.0 h1:j+R6cokKcGbnZLf4kcNwpx6mDEUPF3N6SrqMymQhmvs= github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= @@ -373,30 +355,24 @@ github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3x github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1 h1:E1xaJBQnbSiTHGI1gaBKmKhu1TUKkErKJnE8iGvirYI= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= -github.com/libp2p/go-libp2p-nat v0.0.4 h1:+KXK324yaY701On8a0aGjTnw8467kW3ExKcqW2wwmyw= github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= github.com/libp2p/go-libp2p-nat v0.0.5 h1:/mH8pXFVKleflDL1YwqMg27W9GD8kjEx7NY0P6eGc98= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= -github.com/libp2p/go-libp2p-peer v0.2.0 h1:EQ8kMjaCUwt/Y5uLgjT8iY2qg0mGUT0N1zUjer50DsY= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= -github.com/libp2p/go-libp2p-peerstore v0.1.0 h1:MKh7pRNPHSh1fLPj8u/M/s/napdmeNpoi9BRy9lPN0E= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.1.4 h1:d23fvq5oYMJ/lkkbO4oTwBp/JP+I/1m5gZJobNXCE/k= github.com/libp2p/go-libp2p-peerstore v0.1.4/go.mod h1:+4BDbDiiKf4PzpANZDAT+knVdLxvqh7hXOujessqdzs= -github.com/libp2p/go-libp2p-secio v0.1.0 h1:NNP5KLxuP97sE5Bu3iuwOWyT/dKEGMN5zSLMWdB7GTQ= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1 h1:eNWbJTdyPA7NxhP7J3c5lT97DC5d+u+IldkgCYFTPVA= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= -github.com/libp2p/go-libp2p-swarm v0.1.0 h1:HrFk2p0awrGEgch9JXK/qp/hfjqQfgNxpLWnCiWPg5s= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2 h1:T4hUpgEs2r371PweU3DuH7EOmBIdTBCwWs+FLcgx3bQ= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= -github.com/libp2p/go-libp2p-testing v0.0.4 h1:Qev57UR47GcLPXWjrunv5aLIQGO4n9mhI/8/EIrEEFc= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0 h1:WaFRj/t3HdMZGNZqnU2pS7pDRBmMeoDx7/HDNpeyT9U= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= @@ -405,7 +381,6 @@ github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07q github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.1 h1:Q3XYNiKCC2vIxrvUJL+Jg1kiyeEaIDNKLjgEjo3VQdI= github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= -github.com/libp2p/go-maddr-filter v0.0.4 h1:hx8HIuuwk34KePddrp2mM5ivgPkZ09JH4AvsALRbFUs= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= @@ -415,11 +390,9 @@ github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6 github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= -github.com/libp2p/go-nat v0.0.3 h1:l6fKV+p0Xa354EqQOQP+d8CivdLM4kl5GxC1hSc/UeI= github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= github.com/libp2p/go-nat v0.0.4 h1:KbizNnq8YIf7+Hn7+VFL/xE0eDrkPru2zIO9NMwL8UQ= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= -github.com/libp2p/go-openssl v0.0.2 h1:9pP2d3Ubaxkv7ZisLjx9BFwgOGnQdQYnfcH29HNY3ls= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3 h1:wjlG7HvQkt4Fq4cfH33Ivpwp0omaElYEi9z26qaIkIk= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= @@ -430,11 +403,9 @@ github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA2 github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0 h1:714bRJ4Zy9mdhyTLJ+ZKiROmAFwUHpeRidG+q7LTQOg= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= -github.com/libp2p/go-tcp-transport v0.1.0 h1:IGhowvEqyMFknOar4FWCKSWE0zL36UFKQtiRQD60/8o= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1 h1:yGlqURmqgNA2fvzjSgZNlHcsd/IulAnKM8Ncu+vlqnw= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= -github.com/libp2p/go-ws-transport v0.1.0 h1:F+0OvvdmPTDsVc4AjPHjV7L7Pk1B7D5QwtDcKE2oag4= github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= github.com/libp2p/go-ws-transport v0.1.2 h1:VnxQcLfSGtqupqPpBNu8fUiCv+IN1RJ2BcVqQEM+z8E= github.com/libp2p/go-ws-transport v0.1.2/go.mod h1:dsh2Ld8F+XNmzpkaAijmg5Is+e9l6/1tK/6VFOdN69Y= @@ -452,13 +423,11 @@ github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0X github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.0-20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f h1:QTRRO+ozoYgT3CQRIzNVYJRU3DB8HRnkZv6mr4ISmMA= github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= -github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -499,15 +468,12 @@ github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lg github.com/multiformats/go-multiaddr v0.1.1 h1:rVAztJYMhCQ7vEFr8FvxW3mS+HF2eY/oPbOMeS0ZDnE= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= -github.com/multiformats/go-multiaddr-dns v0.0.2 h1:/Bbsgsy3R6e3jf2qBahzNHzww6usYaZ0NhNH3sqdFS8= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= -github.com/multiformats/go-multiaddr-fmt v0.0.1 h1:5YjeOIzbX8OTKVaN72aOzGIYW7PnrZrnkDyOfAWRSMA= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= -github.com/multiformats/go-multiaddr-net v0.0.1 h1:76O59E3FavvHqNg7jvzWzsPSW5JSi/ek0E4eiDVbg9g= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1 h1:jFFKUuXTXv+3ARyHZi3XUqQO+YWMKgBdhEvuGRfnL6s= @@ -529,18 +495,15 @@ github.com/nsf/termbox-go v0.0.0-20170211012700-3540b76b9c77/go.mod h1:IuKpRQcYE github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd h1:+iAPaTbi1gZpcpDwe/BW1fx7Xoesv69hLNGPheoyhBs= github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd/go.mod h1:4soZNh0zW0LtYGdQ416i0jO0EIqMGcbtaspRS4BDvRQ= -github.com/olekukonko/tablewriter v0.0.0-20170128050532-febf2d34b54a h1:m6hB6GkmZ/suOSKZM7yx3Yt+7iZ9HNfzacCykJqgXA8= github.com/olekukonko/tablewriter v0.0.0-20170128050532-febf2d34b54a/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -551,7 +514,6 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opentracing/opentracing-go v0.0.0-20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -600,7 +562,6 @@ github.com/prometheus/prometheus v0.0.0-20170814170113-3101606756c5/go.mod h1:oA github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= @@ -608,7 +569,6 @@ github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C github.com/robertkrimen/otto v0.0.0-20170205013659-6a77b7cbc37d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -663,7 +623,6 @@ github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639 github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -698,7 +657,6 @@ github.com/wealdtech/go-string2eth v1.0.0/go.mod h1:UZA/snEybGcD6n+Pl+yoDjmexlEJ github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc h1:9lDbC6Rz4bwmou+oE6Dt4Cb2BGMur5eR/GYptkKUVHo= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= -github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f h1:M/lL30eFZTKnomXY6huvM6G0+gVquFNf6mxghaWlFUg= github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= @@ -724,13 +682,11 @@ go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= @@ -750,7 +706,6 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -762,6 +717,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -779,7 +735,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 h1:4dVFTC832rPn4pomLSz1vA+are2+dU19w1H8OngV7nc= golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -791,7 +746,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -844,7 +798,10 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 h1:LCmXVkvpQCDj724eX6irUTPCJP5GelFHxqGSWL2D1R0= golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200116062425-473961ec044c h1:D0OxfnjPaEGt7AluXNompYUYGhoY3u6+bValgqfd1vE= +golang.org/x/tools v0.0.0-20200116062425-473961ec044c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -860,11 +817,9 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -888,7 +843,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= -gopkg.in/olebedev/go-duktape.v3 v3.0.0-20180302121509-abf0ba0be5d5 h1:VWXVtmkY4YFVuF1FokZ0PUsuvtx3Di6z/m47daSP5f0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20180302121509-abf0ba0be5d5/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190709231704-1e4459ed25ff h1:uuol9OUzSvZntY1v963NAbVd7A+PHLMz1FlCe3Lorcs= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190709231704-1e4459ed25ff/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= diff --git a/mobile/status.go b/mobile/status.go index 0563dead4..7433c0ba1 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -27,8 +27,6 @@ import ( "github.com/status-im/status-go/transactions" ) -var statusBackend = api.NewGethStatusBackend() - // OpenAccounts opens database and returns accounts list. func OpenAccounts(datadir string) string { statusBackend.UpdateRootDataDir(datadir) @@ -205,49 +203,51 @@ func CallPrivateRPC(inputJSON string) string { // CreateAccount is equivalent to creating an account from the command line, // just modified to handle the function arg passing. func CreateAccount(password string) string { - info, mnemonic, err := statusBackend.AccountManager().CreateAccount(password) - errString := "" - if err != nil { - fmt.Fprintln(os.Stderr, err) - errString = err.Error() - } + panic("CreateAccount") + // info, mnemonic, err := statusBackend.AccountManager().CreateAccount(password) + // errString := "" + // if err != nil { + // fmt.Fprintln(os.Stderr, err) + // errString = err.Error() + // } - out := AccountInfo{ - Address: info.WalletAddress, - PubKey: info.WalletPubKey, - WalletAddress: info.WalletAddress, - WalletPubKey: info.WalletPubKey, - ChatAddress: info.ChatAddress, - ChatPubKey: info.ChatPubKey, - Mnemonic: mnemonic, - Error: errString, - } - outBytes, _ := json.Marshal(out) - return string(outBytes) + // out := AccountInfo{ + // Address: info.WalletAddress, + // PubKey: info.WalletPubKey, + // WalletAddress: info.WalletAddress, + // WalletPubKey: info.WalletPubKey, + // ChatAddress: info.ChatAddress, + // ChatPubKey: info.ChatPubKey, + // Mnemonic: mnemonic, + // Error: errString, + // } + // outBytes, _ := json.Marshal(out) + // return string(outBytes) } // RecoverAccount re-creates master key using given details. func RecoverAccount(password, mnemonic string) string { - info, err := statusBackend.AccountManager().RecoverAccount(password, mnemonic) + panic("RecoverAccount") + // info, err := statusBackend.AccountManager().RecoverAccount(password, mnemonic) - errString := "" - if err != nil { - fmt.Fprintln(os.Stderr, err) - errString = err.Error() - } + // errString := "" + // if err != nil { + // fmt.Fprintln(os.Stderr, err) + // errString = err.Error() + // } - out := AccountInfo{ - Address: info.WalletAddress, - PubKey: info.WalletPubKey, - WalletAddress: info.WalletAddress, - WalletPubKey: info.WalletPubKey, - ChatAddress: info.ChatAddress, - ChatPubKey: info.ChatPubKey, - Mnemonic: mnemonic, - Error: errString, - } - outBytes, _ := json.Marshal(out) - return string(outBytes) + // out := AccountInfo{ + // Address: info.WalletAddress, + // PubKey: info.WalletPubKey, + // WalletAddress: info.WalletAddress, + // WalletPubKey: info.WalletPubKey, + // ChatAddress: info.ChatAddress, + // ChatPubKey: info.ChatPubKey, + // Mnemonic: mnemonic, + // Error: errString, + // } + // outBytes, _ := json.Marshal(out) + // return string(outBytes) } // StartOnboarding initialize the onboarding with n random accounts diff --git a/mobile/status_geth.go b/mobile/status_geth.go new file mode 100644 index 000000000..0cac47478 --- /dev/null +++ b/mobile/status_geth.go @@ -0,0 +1,9 @@ +// +build !nimbus + +package statusgo + +import ( + "github.com/status-im/status-go/api" +) + +var statusBackend = api.NewGethStatusBackend() diff --git a/mobile/status_nimbus.go b/mobile/status_nimbus.go new file mode 100644 index 000000000..58d155711 --- /dev/null +++ b/mobile/status_nimbus.go @@ -0,0 +1,9 @@ +// +build nimbus + +package statusgo + +import ( + "github.com/status-im/status-go/api" +) + +var statusBackend = api.NewNimbusStatusBackend() diff --git a/node/status_node.go b/node/get_status_node.go similarity index 99% rename from node/status_node.go rename to node/get_status_node.go index d55ba4d0f..a946a3aa0 100644 --- a/node/status_node.go +++ b/node/get_status_node.go @@ -1,3 +1,5 @@ +// +build !nimbus + package node import ( diff --git a/node/node.go b/node/geth_node.go similarity index 99% rename from node/node.go rename to node/geth_node.go index b1d742d42..f334bf880 100644 --- a/node/node.go +++ b/node/geth_node.go @@ -1,3 +1,5 @@ +// +build !nimbus + package node import ( @@ -134,7 +136,7 @@ func activateNodeServices(stack *node.Node, config *params.NodeConfig, db *level return &nodebridge.NodeService{Node: gethbridge.NewNodeBridge(stack)}, nil }) if err != nil { - return fmt.Errorf("failed to register NodeBridhe: %v", err) + return fmt.Errorf("failed to register NodeBridge: %v", err) } // start Whisper service. diff --git a/node/node_api_test.go b/node/geth_node_api_test.go similarity index 100% rename from node/node_api_test.go rename to node/geth_node_api_test.go diff --git a/node/node_test.go b/node/geth_node_test.go similarity index 99% rename from node/node_test.go rename to node/geth_node_test.go index 9dce87e17..617d1d8c0 100644 --- a/node/node_test.go +++ b/node/geth_node_test.go @@ -1,3 +1,5 @@ +// +build !nimbus + package node import ( diff --git a/node/status_node_test.go b/node/geth_status_node_test.go similarity index 100% rename from node/status_node_test.go rename to node/geth_status_node_test.go diff --git a/node/nimbus_node.go b/node/nimbus_node.go new file mode 100644 index 000000000..3b4e61a5d --- /dev/null +++ b/node/nimbus_node.go @@ -0,0 +1,392 @@ +// +build nimbus + +package node + +import ( + "errors" + "fmt" + "reflect" + + "github.com/syndtr/goleveldb/leveldb" + + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "github.com/status-im/status-go/params" + nimbussvc "github.com/status-im/status-go/services/nimbus" + "github.com/status-im/status-go/services/nodebridge" + "github.com/status-im/status-go/services/shhext" + "github.com/status-im/status-go/services/status" + "github.com/status-im/status-go/timesource" +) + +// Errors related to node and services creation. +var ( + // ErrNodeMakeFailureFormat = "error creating p2p node: %s" + ErrWhisperServiceRegistrationFailure = errors.New("failed to register the Whisper service") + // ErrLightEthRegistrationFailure = errors.New("failed to register the LES service") + ErrLightEthRegistrationFailureUpstreamEnabled = errors.New("failed to register the LES service, upstream is also configured") + // ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service") + ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service") + // ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service") + // ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service") +) + +func (n *NimbusStatusNode) activateServices(config *params.NodeConfig, db *leveldb.DB) error { + // start Ethereum service if we are not expected to use an upstream server + if !config.UpstreamConfig.Enabled { + } else { + if config.LightEthConfig.Enabled { + return ErrLightEthRegistrationFailureUpstreamEnabled + } + + n.log.Info("LES protocol is disabled") + + // `personal_sign` and `personal_ecRecover` methods are important to + // keep DApps working. + // Usually, they are provided by an ETH or a LES service, but when using + // upstream, we don't start any of these, so we need to start our own + // implementation. + // if err := n.activatePersonalService(accs, config); err != nil { + // return fmt.Errorf("%v: %v", ErrPersonalServiceRegistrationFailure, err) + // } + } + + if err := n.activateNodeServices(config, db); err != nil { + return err + } + + return nil +} + +func (n *NimbusStatusNode) activateNodeServices(config *params.NodeConfig, db *leveldb.DB) error { + // start Whisper service. + if err := n.activateShhService(config, db); err != nil { + return fmt.Errorf("%v: %v", ErrWhisperServiceRegistrationFailure, err) + } + + // // start Waku service + // if err := activateWakuService(stack, config, db); err != nil { + // return fmt.Errorf("%v: %v", ErrWakuServiceRegistrationFailure, err) + // } + + // start incentivisation service + // if err := n.activateIncentivisationService(config); err != nil { + // return fmt.Errorf("%v: %v", ErrIncentivisationServiceRegistrationFailure, err) + // } + + // start status service. + if err := n.activateStatusService(config); err != nil { + return fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err) + } + + // start peer service + // if err := activatePeerService(n); err != nil { + // return fmt.Errorf("%v: %v", ErrPeerServiceRegistrationFailure, err) + // } + return nil +} + +// // activateLightEthService configures and registers the eth.Ethereum service with a given node. +// func activateLightEthService(stack *node.Node, accs *accounts.Manager, config *params.NodeConfig) error { +// if !config.LightEthConfig.Enabled { +// logger.Info("LES protocol is disabled") +// return nil +// } + +// genesis, err := calculateGenesis(config.NetworkID) +// if err != nil { +// return err +// } + +// ethConf := eth.DefaultConfig +// ethConf.Genesis = genesis +// ethConf.SyncMode = downloader.LightSync +// ethConf.NetworkId = config.NetworkID +// ethConf.DatabaseCache = config.LightEthConfig.DatabaseCache +// return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { +// // NOTE(dshulyak) here we set our instance of the accounts manager. +// // without sharing same instance selected account won't be visible for personal_* methods. +// nctx := &node.ServiceContext{} +// *nctx = *ctx +// nctx.AccountManager = accs +// return les.New(nctx, ðConf) +// }) +// } + +// func activatePersonalService(stack *node.Node, accs *accounts.Manager, config *params.NodeConfig) error { +// return stack.Register(func(*node.ServiceContext) (node.Service, error) { +// svc := personal.New(accs) +// return svc, nil +// }) +// } + +// func (n *NimbusStatusNode) activatePersonalService(accs *accounts.Manager, config *params.NodeConfig) error { +// return n.Register(func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// svc := personal.New(accs) +// return svc, nil +// }) +// } + +func (n *NimbusStatusNode) activateStatusService(config *params.NodeConfig) error { + if !config.EnableStatusService { + n.log.Info("Status service api is disabled") + return nil + } + + return n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + var service *nodebridge.WhisperService + if err := ctx.Service(&service); err != nil { + return nil, err + } + svc := status.New(service.Whisper) + return svc, nil + }) +} + +// func (n *NimbusStatusNode) activatePeerService() error { +// return n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { +// svc := peer.New() +// return svc, nil +// }) +// } + +// func registerWhisperMailServer(whisperService *whisper.Whisper, config *params.WhisperConfig) (err error) { +// var mailServer mailserver.WhisperMailServer +// whisperService.RegisterMailServer(&mailServer) + +// return mailServer.Init(whisperService, config) +// } + +// func registerWakuMailServer(wakuService *waku.Waku, config *params.WakuConfig) (err error) { +// var mailServer mailserver.WakuMailServer +// wakuService.RegisterMailServer(&mailServer) + +// return mailServer.Init(wakuService, config) +// } + +// activateShhService configures Whisper and adds it to the given node. +func (n *NimbusStatusNode) activateShhService(config *params.NodeConfig, db *leveldb.DB) (err error) { + if !config.WhisperConfig.Enabled { + n.log.Info("SHH protocol is disabled") + return nil + } + if config.EnableNTPSync { + if err = n.Register(func(*nimbussvc.ServiceContext) (nimbussvc.Service, error) { + return timesource.Default(), nil + }); err != nil { + return + } + } + + // err = n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + // return n.createShhService(ctx, &config.WhisperConfig, &config.ClusterConfig) + // }) + // if err != nil { + // return + // } + + // Register eth-node node bridge + err = n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + return &nodebridge.NodeService{Node: n.node}, nil + }) + if err != nil { + return + } + + // Register Whisper eth-node bridge + err = n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + n.log.Info("Creating WhisperService") + + var ethnode *nodebridge.NodeService + if err := ctx.Service(ðnode); err != nil { + return nil, err + } + + w, err := ethnode.Node.GetWhisper(ctx) + if err != nil { + n.log.Error("GetWhisper returned error", "err", err) + return nil, err + } + + return &nodebridge.WhisperService{Whisper: w}, nil + }) + if err != nil { + return + } + + // TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd + return n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + var ethnode *nodebridge.NodeService + if err := ctx.Service(ðnode); err != nil { + return nil, err + } + return shhext.NewNimbus(ethnode.Node, ctx, "shhext", db, config.ShhextConfig), nil + }) +} + +// activateWakuService configures Waku and adds it to the given node. +func (n *NimbusStatusNode) activateWakuService(config *params.NodeConfig, db *leveldb.DB) (err error) { + if !config.WakuConfig.Enabled { + n.log.Info("Waku protocol is disabled") + return nil + } + + panic("not implemented") + // err = n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + // return createWakuService(ctx, &config.WakuConfig, &config.ClusterConfig) + // }) + // if err != nil { + // return + // } + + // // TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd + // return n.Register(func(ctx *nimbussvc.ServiceContext) (nimbussvc.Service, error) { + // var ethnode *nodebridge.NodeService + // if err := ctx.Service(ðnode); err != nil { + // return nil, err + // } + // return shhext.New(ethnode.Node, ctx, "wakuext", shhext.EnvelopeSignalHandler{}, db, config.ShhextConfig), nil + // }) +} + +// Register injects a new service into the node's stack. The service created by +// the passed constructor must be unique in its type with regard to sibling ones. +func (n *NimbusStatusNode) Register(constructor nimbussvc.ServiceConstructor) error { + n.lock.Lock() + defer n.lock.Unlock() + + if n.isRunning() { + return ErrNodeRunning + } + n.serviceFuncs = append(n.serviceFuncs, constructor) + return nil +} + +func (n *NimbusStatusNode) startServices() error { + services := make(map[reflect.Type]nimbussvc.Service) + for _, constructor := range n.serviceFuncs { + // Create a new context for the particular service + ctxServices := make(map[reflect.Type]nimbussvc.Service) + for kind, s := range services { // copy needed for threaded access + ctxServices[kind] = s + } + ctx := nimbussvc.NewServiceContext(n.config, ctxServices) + //EventMux: n.eventmux, + //AccountManager: n.accman, + // Construct and save the service + service, err := constructor(ctx) + if err != nil { + n.log.Info("Service constructor returned error", "err", err) + return err + } + kind := reflect.TypeOf(service) + if _, exists := services[kind]; exists { + return &nimbussvc.DuplicateServiceError{Kind: kind} + } + services[kind] = service + } + // Start each of the services + var started []reflect.Type + for kind, service := range services { + // Start the next service, stopping all previous upon failure + if err := service.StartService(); err != nil { + for _, kind := range started { + services[kind].Stop() + } + + return err + } + // Mark the service started for potential cleanup + started = append(started, kind) + } + // Lastly start the configured RPC interfaces + if err := n.startRPC(services); err != nil { + for _, service := range services { + service.Stop() + } + return err + } + // Finish initializing the startup + n.services = services + + return nil +} + +// startRPC is a helper method to start all the various RPC endpoint during node +// startup. It's not meant to be called at any time afterwards as it makes certain +// assumptions about the state of the node. +func (n *NimbusStatusNode) startRPC(services map[reflect.Type]nimbussvc.Service) error { + // Gather all the possible APIs to surface + apis := []gethrpc.API{} + for _, service := range services { + apis = append(apis, service.APIs()...) + } + + // Start the various API endpoints, terminating all in case of errors + if err := n.startInProc(apis); err != nil { + return err + } + if err := n.startPublicInProc(apis, n.config.FormatAPIModules()); err != nil { + n.stopInProc() + return err + } + // All API endpoints started successfully + n.rpcAPIs = apis + return nil +} + +// startInProc initializes an in-process RPC endpoint. +func (n *NimbusStatusNode) startInProc(apis []gethrpc.API) error { + n.log.Debug("startInProc", "apis", apis) + // Register all the APIs exposed by the services + handler := gethrpc.NewServer() + for _, api := range apis { + n.log.Debug("Registering InProc", "namespace", api.Namespace) + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + n.log.Debug("InProc registered", "namespace", api.Namespace) + } + n.inprocHandler = handler + return nil +} + +// stopInProc terminates the in-process RPC endpoint. +func (n *NimbusStatusNode) stopInProc() { + if n.inprocHandler != nil { + n.inprocHandler.Stop() + n.inprocHandler = nil + } +} + +// startPublicInProc initializes an in-process RPC endpoint for public APIs. +func (n *NimbusStatusNode) startPublicInProc(apis []gethrpc.API, modules []string) error { + n.log.Debug("startPublicInProc", "apis", apis, "modules", modules) + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + + // Register all the public APIs exposed by the services + handler := gethrpc.NewServer() + for _, api := range apis { + if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + n.log.Debug("Registering InProc public", "service", api.Service, "namespace", api.Namespace) + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + n.log.Debug("InProc public registered", "service", api.Service, "namespace", api.Namespace) + } + } + n.inprocPublicHandler = handler + return nil +} + +// stopPublicInProc terminates the in-process RPC endpoint for public APIs. +func (n *NimbusStatusNode) stopPublicInProc() { + if n.inprocPublicHandler != nil { + n.inprocPublicHandler.Stop() + n.inprocPublicHandler = nil + } +} diff --git a/node/nimbus_status_node.go b/node/nimbus_status_node.go new file mode 100644 index 000000000..4f1f95bed --- /dev/null +++ b/node/nimbus_status_node.go @@ -0,0 +1,798 @@ +// +build nimbus + +package node + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "sync" + + "github.com/syndtr/goleveldb/leveldb" + + "github.com/ethereum/go-ethereum/log" + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "github.com/status-im/status-go/db" + nimbusbridge "github.com/status-im/status-go/eth-node/bridge/nimbus" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/params" + "github.com/status-im/status-go/rpc" + nimbussvc "github.com/status-im/status-go/services/nimbus" + "github.com/status-im/status-go/services/nodebridge" + "github.com/status-im/status-go/services/shhext" + "github.com/status-im/status-go/services/status" +) + +// // tickerResolution is the delta to check blockchain sync progress. +// const tickerResolution = time.Second + +// errors +var ( + ErrNodeRunning = errors.New("node is already running") + ErrNodeStopped = errors.New("node not started") + ErrNoRunningNode = errors.New("there is no running node") + ErrServiceUnknown = errors.New("service unknown") +) + +// NimbusStatusNode abstracts contained geth node and provides helper methods to +// interact with it. +type NimbusStatusNode struct { + mu sync.RWMutex + + //eventmux *event.TypeMux // Event multiplexer used between the services of a stack + + config *params.NodeConfig // Status node configuration + privateKey *ecdsa.PrivateKey + node nimbusbridge.Node + nodeRunning bool + rpcClient *rpc.Client // reference to public RPC client + rpcPrivateClient *rpc.Client // reference to private RPC client (can call private APIs) + + rpcAPIs []gethrpc.API // List of APIs currently provided by the node + inprocHandler *gethrpc.Server // In-process RPC request handler to process the API requests + inprocPublicHandler *gethrpc.Server // In-process RPC request handler to process the public API requests + + serviceFuncs []nimbussvc.ServiceConstructor // Service constructors (in dependency order) + services map[reflect.Type]nimbussvc.Service // Currently running services + + // discovery discovery.Discovery + // register *peers.Register + // peerPool *peers.PeerPool + db *leveldb.DB // used as a cache for PeerPool + + //stop chan struct{} // Channel to wait for termination notifications + lock sync.RWMutex + + log log.Logger +} + +// NewNimbus makes new instance of NimbusStatusNode. +func NewNimbus() *NimbusStatusNode { + return &NimbusStatusNode{ + //eventmux: new(event.TypeMux), + log: log.New("package", "status-go/node.NimbusStatusNode"), + } +} + +// Config exposes reference to running node's configuration +func (n *NimbusStatusNode) Config() *params.NodeConfig { + n.mu.RLock() + defer n.mu.RUnlock() + + return n.config +} + +// GethNode returns underlying geth node. +// func (n *NimbusStatusNode) GethNode() *node.Node { +// n.mu.RLock() +// defer n.mu.RUnlock() + +// return n.gethNode +// } + +// Server retrieves the currently running P2P network layer. +// func (n *NimbusStatusNode) Server() *p2p.Server { +// n.mu.RLock() +// defer n.mu.RUnlock() + +// if n.gethNode == nil { +// return nil +// } + +// return n.gethNode.Server() +// } + +// Start starts current NimbusStatusNode, failing if it's already started. +// It accepts a list of services that should be added to the node. +func (n *NimbusStatusNode) Start(config *params.NodeConfig, services ...nimbussvc.ServiceConstructor) error { + panic("Start") + return n.StartWithOptions(config, NimbusStartOptions{ + Services: services, + StartDiscovery: true, + // AccountsManager: accs, + }) +} + +// NimbusStartOptions allows to control some parameters of Start() method. +type NimbusStartOptions struct { + Node types.Node + Services []nimbussvc.ServiceConstructor + StartDiscovery bool + // AccountsManager *accounts.Manager +} + +// StartWithOptions starts current NimbusStatusNode, failing if it's already started. +// It takes some options that allows to further configure starting process. +func (n *NimbusStatusNode) StartWithOptions(config *params.NodeConfig, options NimbusStartOptions) error { + n.mu.Lock() + defer n.mu.Unlock() + + if n.isRunning() { + n.log.Debug("cannot start, node already running") + return ErrNodeRunning + } + + n.log.Debug("starting with NodeConfig", "ClusterConfig", config.ClusterConfig) + + db, err := db.Create(config.DataDir, params.StatusDatabase) + if err != nil { + return fmt.Errorf("failed to create database at %s: %v", config.DataDir, err) + } + + n.db = db + + err = n.startWithDB(config, db, options.Services) + + // continue only if there was no error when starting node with a db + if err == nil && options.StartDiscovery && n.discoveryEnabled() { + // err = n.startDiscovery() + } + + if err != nil { + if dberr := db.Close(); dberr != nil { + n.log.Error("error while closing leveldb after node crash", "error", dberr) + } + n.db = nil + return err + } + + return nil +} + +func (n *NimbusStatusNode) startWithDB(config *params.NodeConfig, db *leveldb.DB, services []nimbussvc.ServiceConstructor) error { + if err := n.createNode(config, services, db); err != nil { + return err + } + + if err := n.setupRPCClient(); err != nil { + return err + } + + return nil +} + +func (n *NimbusStatusNode) createNode(config *params.NodeConfig, services []nimbussvc.ServiceConstructor, db *leveldb.DB) error { + var privateKey *ecdsa.PrivateKey + if config.NodeKey != "" { + var err error + privateKey, err = crypto.HexToECDSA(config.NodeKey) + if err != nil { + return err + } + } + + n.privateKey = privateKey + n.node = nimbusbridge.NewNodeBridge() + + err := n.activateServices(config, db) + if err != nil { + return err + } + + if err = n.start(config, services); err != nil { + return err + } + + return nil +} + +// start starts current NimbusStatusNode, will fail if it's already started. +func (n *NimbusStatusNode) start(config *params.NodeConfig, services []nimbussvc.ServiceConstructor) error { + for _, service := range services { + if err := n.Register(service); err != nil { + return err + } + } + + n.config = config + n.startServices() + + err := n.node.StartNimbus(n.privateKey, config.ListenAddr, true) + n.nodeRunning = err == nil + return err +} + +func (n *NimbusStatusNode) setupRPCClient() (err error) { + // setup public RPC client + gethNodeClient := gethrpc.DialInProc(n.inprocPublicHandler) + n.rpcClient, err = rpc.NewClient(gethNodeClient, n.config.UpstreamConfig) + if err != nil { + return + } + + // setup private RPC client + gethNodePrivateClient := gethrpc.DialInProc(n.inprocHandler) + n.rpcPrivateClient, err = rpc.NewClient(gethNodePrivateClient, n.config.UpstreamConfig) + + return +} + +func (n *NimbusStatusNode) discoveryEnabled() bool { + return n.config != nil && (!n.config.NoDiscovery || n.config.Rendezvous) && n.config.ClusterConfig.Enabled +} + +// func (n *NimbusStatusNode) discoverNode() (*enode.Node, error) { +// if !n.isRunning() { +// return nil, nil +// } + +// server := n.gethNode.Server() +// discNode := server.Self() + +// if n.config.AdvertiseAddr == "" { +// return discNode, nil +// } + +// n.log.Info("Using AdvertiseAddr for rendezvous", "addr", n.config.AdvertiseAddr) + +// r := discNode.Record() +// r.Set(enr.IP(net.ParseIP(n.config.AdvertiseAddr))) +// if err := enode.SignV4(r, server.PrivateKey); err != nil { +// return nil, err +// } +// return enode.New(enode.ValidSchemes[r.IdentityScheme()], r) +// } + +// func (n *NimbusStatusNode) startRendezvous() (discovery.Discovery, error) { +// if !n.config.Rendezvous { +// return nil, errors.New("rendezvous is not enabled") +// } +// if len(n.config.ClusterConfig.RendezvousNodes) == 0 { +// return nil, errors.New("rendezvous node must be provided if rendezvous discovery is enabled") +// } +// maddrs := make([]ma.Multiaddr, len(n.config.ClusterConfig.RendezvousNodes)) +// for i, addr := range n.config.ClusterConfig.RendezvousNodes { +// var err error +// maddrs[i], err = ma.NewMultiaddr(addr) +// if err != nil { +// return nil, fmt.Errorf("failed to parse rendezvous node %s: %v", n.config.ClusterConfig.RendezvousNodes[0], err) +// } +// } +// node, err := n.discoverNode() +// if err != nil { +// return nil, fmt.Errorf("failed to get a discover node: %v", err) +// } + +// return discovery.NewRendezvous(maddrs, n.gethNode.Server().PrivateKey, node) +// } + +// StartDiscovery starts the peers discovery protocols depending on the node config. +func (n *NimbusStatusNode) StartDiscovery() error { + n.mu.Lock() + defer n.mu.Unlock() + + if n.discoveryEnabled() { + // return n.startDiscovery() + } + + return nil +} + +// func (n *NimbusStatusNode) startDiscovery() error { +// if n.isDiscoveryRunning() { +// return ErrDiscoveryRunning +// } + +// discoveries := []discovery.Discovery{} +// if !n.config.NoDiscovery { +// discoveries = append(discoveries, discovery.NewDiscV5( +// n.gethNode.Server().PrivateKey, +// n.config.ListenAddr, +// parseNodesV5(n.config.ClusterConfig.BootNodes))) +// } +// if n.config.Rendezvous { +// d, err := n.startRendezvous() +// if err != nil { +// return err +// } +// discoveries = append(discoveries, d) +// } +// if len(discoveries) == 0 { +// return errors.New("wasn't able to register any discovery") +// } else if len(discoveries) > 1 { +// n.discovery = discovery.NewMultiplexer(discoveries) +// } else { +// n.discovery = discoveries[0] +// } +// log.Debug( +// "using discovery", +// "instance", reflect.TypeOf(n.discovery), +// "registerTopics", n.config.RegisterTopics, +// "requireTopics", n.config.RequireTopics, +// ) +// n.register = peers.NewRegister(n.discovery, n.config.RegisterTopics...) +// options := peers.NewDefaultOptions() +// // TODO(dshulyak) consider adding a flag to define this behaviour +// options.AllowStop = len(n.config.RegisterTopics) == 0 +// options.TrustedMailServers = parseNodesToNodeID(n.config.ClusterConfig.TrustedMailServers) + +// options.MailServerRegistryAddress = n.config.MailServerRegistryAddress + +// n.peerPool = peers.NewPeerPool( +// n.discovery, +// n.config.RequireTopics, +// peers.NewCache(n.db), +// options, +// ) +// if err := n.discovery.Start(); err != nil { +// return err +// } +// if err := n.register.Start(); err != nil { +// return err +// } +// return n.peerPool.Start(n.gethNode.Server(), n.rpcClient) +// } + +// Stop will stop current NimbusStatusNode. A stopped node cannot be resumed. +func (n *NimbusStatusNode) Stop() error { + n.mu.Lock() + defer n.mu.Unlock() + + if !n.isRunning() { + return ErrNoRunningNode + } + + var errs []error + + // Terminate all subsystems and collect any errors + if err := n.stop(); err != nil && err != ErrNodeStopped { + errs = append(errs, err) + } + // Report any errors that might have occurred + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return fmt.Errorf("%v", errs) + } +} + +// StopError is returned if a Node fails to stop either any of its registered +// services or itself. +type StopError struct { + Server error + Services map[reflect.Type]error +} + +// Error generates a textual representation of the stop error. +func (e *StopError) Error() string { + return fmt.Sprintf("server: %v, services: %v", e.Server, e.Services) +} + +// stop will stop current NimbusStatusNode. A stopped node cannot be resumed. +func (n *NimbusStatusNode) stop() error { + // if n.isDiscoveryRunning() { + // if err := n.stopDiscovery(); err != nil { + // n.log.Error("Error stopping the discovery components", "error", err) + // } + // n.register = nil + // n.peerPool = nil + // n.discovery = nil + // } + + // if err := n.gethNode.Stop(); err != nil { + // return err + // } + + // Terminate the API, services and the p2p server. + n.stopPublicInProc() + n.stopInProc() + n.rpcClient = nil + n.rpcPrivateClient = nil + n.rpcAPIs = nil + + failure := &StopError{ + Services: make(map[reflect.Type]error), + } + for kind, service := range n.services { + if err := service.Stop(); err != nil { + failure.Services[kind] = err + } + } + n.services = nil + // We need to clear `node` because config is passed to `Start()` + // and may be completely different. Similarly with `config`. + if n.node != nil { + n.node.Stop() + n.node = nil + } + n.nodeRunning = false + n.config = nil + + if n.db != nil { + err := n.db.Close() + + n.db = nil + + return err + } + + if len(failure.Services) > 0 { + return failure + } + return nil +} + +func (n *NimbusStatusNode) isDiscoveryRunning() bool { + return false //n.register != nil || n.peerPool != nil || n.discovery != nil +} + +// func (n *NimbusStatusNode) stopDiscovery() error { +// n.register.Stop() +// n.peerPool.Stop() +// return n.discovery.Stop() +// } + +// ResetChainData removes chain data if node is not running. +func (n *NimbusStatusNode) ResetChainData(config *params.NodeConfig) error { + n.mu.Lock() + defer n.mu.Unlock() + + if n.isRunning() { + return ErrNodeRunning + } + + chainDataDir := filepath.Join(config.DataDir, config.Name, "lightchaindata") + if _, err := os.Stat(chainDataDir); os.IsNotExist(err) { + return err + } + err := os.RemoveAll(chainDataDir) + if err == nil { + n.log.Info("Chain data has been removed", "dir", chainDataDir) + } + return err +} + +// IsRunning confirm that node is running. +func (n *NimbusStatusNode) IsRunning() bool { + n.mu.RLock() + defer n.mu.RUnlock() + + return n.isRunning() +} + +func (n *NimbusStatusNode) isRunning() bool { + return n.node != nil && n.nodeRunning // && n.gethNode.Server() != nil +} + +// populateStaticPeers connects current node with our publicly available LES/SHH/Swarm cluster +func (n *NimbusStatusNode) populateStaticPeers() error { + if !n.config.ClusterConfig.Enabled { + n.log.Info("Static peers are disabled") + return nil + } + + for _, enode := range n.config.ClusterConfig.StaticNodes { + if err := n.addPeer(enode); err != nil { + n.log.Error("Static peer addition failed", "error", err) + return err + } + n.log.Info("Static peer added", "enode", enode) + } + + return nil +} + +func (n *NimbusStatusNode) removeStaticPeers() error { + if !n.config.ClusterConfig.Enabled { + n.log.Info("Static peers are disabled") + return nil + } + + for _, enode := range n.config.ClusterConfig.StaticNodes { + if err := n.removePeer(enode); err != nil { + n.log.Error("Static peer deletion failed", "error", err) + return err + } + n.log.Info("Static peer deleted", "enode", enode) + } + return nil +} + +// ReconnectStaticPeers removes and adds static peers to a server. +func (n *NimbusStatusNode) ReconnectStaticPeers() error { + n.mu.Lock() + defer n.mu.Unlock() + + if !n.isRunning() { + return ErrNoRunningNode + } + + if err := n.removeStaticPeers(); err != nil { + return err + } + + return n.populateStaticPeers() +} + +// AddPeer adds new static peer node +func (n *NimbusStatusNode) AddPeer(url string) error { + n.mu.RLock() + defer n.mu.RUnlock() + + return n.addPeer(url) +} + +// addPeer adds new static peer node +func (n *NimbusStatusNode) addPeer(url string) error { + if !n.isRunning() { + return ErrNoRunningNode + } + + n.node.AddPeer(url) + + return nil +} + +func (n *NimbusStatusNode) removePeer(url string) error { + if !n.isRunning() { + return ErrNoRunningNode + } + + n.node.RemovePeer(url) + + return nil +} + +// PeerCount returns the number of connected peers. +func (n *NimbusStatusNode) PeerCount() int { + n.mu.RLock() + defer n.mu.RUnlock() + + if !n.isRunning() { + return 0 + } + + return 1 + //return n.gethNode.Server().PeerCount() +} + +// Service retrieves a currently running service registered of a specific type. +func (n *NimbusStatusNode) Service(service interface{}) error { + n.lock.RLock() + defer n.lock.RUnlock() + + // Short circuit if the node's not running + if !n.isRunning() { + return ErrNodeStopped + } + // Otherwise try to find the service to return + element := reflect.ValueOf(service).Elem() + if running, ok := n.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + +// // LightEthereumService exposes reference to LES service running on top of the node +// func (n *NimbusStatusNode) LightEthereumService() (l *les.LightEthereum, err error) { +// n.mu.RLock() +// defer n.mu.RUnlock() + +// err = n.Service(&l) + +// return +// } + +// StatusService exposes reference to status service running on top of the node +func (n *NimbusStatusNode) StatusService() (st *status.Service, err error) { + n.mu.RLock() + defer n.mu.RUnlock() + + err = n.Service(&st) + + return +} + +// // PeerService exposes reference to peer service running on top of the node. +// func (n *NimbusStatusNode) PeerService() (st *peer.Service, err error) { +// n.mu.RLock() +// defer n.mu.RUnlock() + +// err = n.Service(&st) + +// return +// } + +// WhisperService exposes reference to Whisper service running on top of the node +func (n *NimbusStatusNode) WhisperService() (w *nodebridge.WhisperService, err error) { + n.mu.RLock() + defer n.mu.RUnlock() + + err = n.Service(&w) + + return +} + +// ShhExtService exposes reference to shh extension service running on top of the node +func (n *NimbusStatusNode) ShhExtService() (s *shhext.NimbusService, err error) { + n.mu.RLock() + defer n.mu.RUnlock() + + err = n.Service(&s) + + return +} + +// // WalletService returns wallet.Service instance if it was started. +// func (n *NimbusStatusNode) WalletService() (s *wallet.Service, err error) { +// n.mu.RLock() +// defer n.mu.RUnlock() +// err = n.Service(&s) +// return +// } + +// // BrowsersService returns browsers.Service instance if it was started. +// func (n *NimbusStatusNode) BrowsersService() (s *browsers.Service, err error) { +// n.mu.RLock() +// defer n.mu.RUnlock() +// err = n.Service(&s) +// return +// } + +// // PermissionsService returns browsers.Service instance if it was started. +// func (n *NimbusStatusNode) PermissionsService() (s *permissions.Service, err error) { +// n.mu.RLock() +// defer n.mu.RUnlock() +// err = n.Service(&s) +// return +// } + +// // AccountManager exposes reference to node's accounts manager +// func (n *NimbusStatusNode) AccountManager() (*accounts.Manager, error) { +// n.mu.RLock() +// defer n.mu.RUnlock() + +// if n.gethNode == nil { +// return nil, ErrNoGethNode +// } + +// return n.gethNode.AccountManager(), nil +// } + +// RPCClient exposes reference to RPC client connected to the running node. +func (n *NimbusStatusNode) RPCClient() *rpc.Client { + n.mu.RLock() + defer n.mu.RUnlock() + return n.rpcClient +} + +// RPCPrivateClient exposes reference to RPC client connected to the running node +// that can call both public and private APIs. +func (n *NimbusStatusNode) RPCPrivateClient() *rpc.Client { + n.mu.Lock() + defer n.mu.Unlock() + return n.rpcPrivateClient +} + +// ChaosModeCheckRPCClientsUpstreamURL updates RPCClient and RPCPrivateClient upstream URLs, +// if defined, without restarting the node. This is required for the Chaos Unicorn Day. +// Additionally, if the passed URL is Infura, it changes it to httpstat.us/500. +func (n *NimbusStatusNode) ChaosModeCheckRPCClientsUpstreamURL(on bool) error { + url := n.config.UpstreamConfig.URL + + if on { + if strings.Contains(url, "infura.io") { + url = "https://httpstat.us/500" + } + } + + publicClient := n.RPCClient() + if publicClient != nil { + if err := publicClient.UpdateUpstreamURL(url); err != nil { + return err + } + } + + privateClient := n.RPCPrivateClient() + if privateClient != nil { + if err := privateClient.UpdateUpstreamURL(url); err != nil { + return err + } + } + + return nil +} + +// EnsureSync waits until blockchain synchronization +// is complete and returns. +func (n *NimbusStatusNode) EnsureSync(ctx context.Context) error { + // Don't wait for any blockchain sync for the + // local private chain as blocks are never mined. + if n.config.NetworkID == 0 || n.config.NetworkID == params.StatusChainNetworkID { + return nil + } + + return n.ensureSync(ctx) +} + +func (n *NimbusStatusNode) ensureSync(ctx context.Context) error { + return errors.New("Sync not implemented") + // les, err := n.LightEthereumService() + // if err != nil { + // return fmt.Errorf("failed to get LES service: %v", err) + // } + + // downloader := les.Downloader() + // if downloader == nil { + // return errors.New("LightEthereumService downloader is nil") + // } + + // progress := downloader.Progress() + // if n.PeerCount() > 0 && progress.CurrentBlock >= progress.HighestBlock { + // n.log.Debug("Synchronization completed", "current block", progress.CurrentBlock, "highest block", progress.HighestBlock) + // return nil + // } + + // ticker := time.NewTicker(tickerResolution) + // defer ticker.Stop() + + // progressTicker := time.NewTicker(time.Minute) + // defer progressTicker.Stop() + + // for { + // select { + // case <-ctx.Done(): + // return errors.New("timeout during node synchronization") + // case <-ticker.C: + // if n.PeerCount() == 0 { + // n.log.Debug("No established connections with any peers, continue waiting for a sync") + // continue + // } + // if downloader.Synchronising() { + // n.log.Debug("Synchronization is in progress") + // continue + // } + // progress = downloader.Progress() + // if progress.CurrentBlock >= progress.HighestBlock { + // n.log.Info("Synchronization completed", "current block", progress.CurrentBlock, "highest block", progress.HighestBlock) + // return nil + // } + // n.log.Debug("Synchronization is not finished", "current", progress.CurrentBlock, "highest", progress.HighestBlock) + // case <-progressTicker.C: + // progress = downloader.Progress() + // n.log.Warn("Synchronization is not finished", "current", progress.CurrentBlock, "highest", progress.HighestBlock) + // } + // } +} + +// // Discover sets up the discovery for a specific topic. +// func (n *NimbusStatusNode) Discover(topic string, max, min int) (err error) { +// if n.peerPool == nil { +// return errors.New("peerPool not running") +// } +// return n.peerPool.UpdateTopic(topic, params.Limits{ +// Max: max, +// Min: min, +// }) +// } diff --git a/rpc/client.go b/rpc/client.go index a53fa26e7..803116252 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -155,6 +155,10 @@ func (c *Client) CallContextIgnoringLocalHandlers(ctx context.Context, result in return client.CallContext(ctx, result, method, args...) } + if c.local == nil { + c.log.Warn("Local JSON-RPC endpoint missing", "method", method) + return errors.New("missing local JSON-RPC endpoint") + } return c.local.CallContext(ctx, result, method, args...) } diff --git a/services/accounts/service_nimbus.go b/services/accounts/service_nimbus.go new file mode 100644 index 000000000..8f888d3e3 --- /dev/null +++ b/services/accounts/service_nimbus.go @@ -0,0 +1,15 @@ +// +build nimbus + +package accounts + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that Service implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*Service)(nil) + +// Start a service. +func (s *Service) StartService() error { + return nil +} diff --git a/services/nimbus/service.go b/services/nimbus/service.go new file mode 100644 index 000000000..1d63365ad --- /dev/null +++ b/services/nimbus/service.go @@ -0,0 +1,84 @@ +// +build nimbus + +package nimbus + +import ( + "errors" + "fmt" + "reflect" + + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "github.com/status-im/status-go/params" +) + +// errors +var ( + ErrNodeStopped = errors.New("node not started") + ErrServiceUnknown = errors.New("service unknown") +) + +// DuplicateServiceError is returned during Node startup if a registered service +// constructor returns a service of the same type that was already started. +type DuplicateServiceError struct { + Kind reflect.Type +} + +// Error generates a textual representation of the duplicate service error. +func (e *DuplicateServiceError) Error() string { + return fmt.Sprintf("duplicate service: %v", e.Kind) +} + +// ServiceContext is a collection of service independent options inherited from +// the protocol stack, that is passed to all constructors to be optionally used; +// as well as utility methods to operate on the service environment. +type ServiceContext struct { + config *params.NodeConfig + services map[reflect.Type]Service // Index of the already constructed services + // EventMux *event.TypeMux // Event multiplexer used for decoupled notifications + // AccountManager *accounts.Manager // Account manager created by the node. +} + +func NewServiceContext(config *params.NodeConfig, services map[reflect.Type]Service) *ServiceContext { + return &ServiceContext{ + config: config, + services: services, + } +} + +// Service retrieves a currently running service registered of a specific type. +func (ctx *ServiceContext) Service(service interface{}) error { + element := reflect.ValueOf(service).Elem() + if running, ok := ctx.services[element.Type()]; ok { + element.Set(reflect.ValueOf(running)) + return nil + } + return ErrServiceUnknown +} + +// ServiceConstructor is the function signature of the constructors needed to be +// registered for service instantiation. +type ServiceConstructor func(ctx *ServiceContext) (Service, error) + +// Service is an individual protocol that can be registered into a node. +// +// Notes: +// +// • Service life-cycle management is delegated to the node. The service is allowed to +// initialize itself upon creation, but no goroutines should be spun up outside of the +// Start method. +// +// • Restart logic is not required as the node will create a fresh instance +// every time a service is started. +type Service interface { + // APIs retrieves the list of RPC descriptors the service provides + APIs() []gethrpc.API + + // StartService is called after all services have been constructed and the networking + // layer was also initialized to spawn any goroutines required by the service. + StartService() error + + // Stop terminates all goroutines belonging to the service, blocking until they + // are all terminated. + Stop() error +} diff --git a/services/nodebridge/node_service_nimbus.go b/services/nodebridge/node_service_nimbus.go new file mode 100644 index 000000000..a321a247a --- /dev/null +++ b/services/nodebridge/node_service_nimbus.go @@ -0,0 +1,14 @@ +// +build nimbus + +package nodebridge + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that NodeService implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*NodeService)(nil) + +func (w *NodeService) StartService() error { + return nil +} diff --git a/services/nodebridge/whisper_service_nimbus.go b/services/nodebridge/whisper_service_nimbus.go new file mode 100644 index 000000000..4abef5041 --- /dev/null +++ b/services/nodebridge/whisper_service_nimbus.go @@ -0,0 +1,14 @@ +// +build nimbus + +package nodebridge + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that WhisperService implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*WhisperService)(nil) + +func (w *WhisperService) StartService() error { + return nil +} diff --git a/services/rpcfilters/service_nimbus.go b/services/rpcfilters/service_nimbus.go new file mode 100644 index 000000000..e61f8c173 --- /dev/null +++ b/services/rpcfilters/service_nimbus.go @@ -0,0 +1,15 @@ +// +build nimbus + +package rpcfilters + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that Service implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*Service)(nil) + +// StartService is run when a service is started. +func (s *Service) StartService() error { + return s.Start(nil) +} diff --git a/services/shhext/api.go b/services/shhext/api.go index 2744f68ee..645f8eea6 100644 --- a/services/shhext/api.go +++ b/services/shhext/api.go @@ -1,29 +1,10 @@ package shhext import ( - "context" - "crypto/ecdsa" - "encoding/hex" "errors" - "fmt" - "math/big" "time" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" - - "github.com/status-im/status-go/db" - "github.com/status-im/status-go/mailserver" - "github.com/status-im/status-go/services/shhext/mailservers" - "github.com/status-im/status-go/whisper/v6" - - gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" "github.com/status-im/status-go/eth-node/types" - enstypes "github.com/status-im/status-go/eth-node/types/ens" - "github.com/status-im/status-go/protocol" - "github.com/status-im/status-go/protocol/encryption/multidevice" - "github.com/status-im/status-go/protocol/transport" ) const ( @@ -151,6 +132,15 @@ type SyncMessagesRequest struct { Topics []types.TopicType `json:"topics"` } +// InitiateHistoryRequestParams type for initiating history requests from a peer. +type InitiateHistoryRequestParams struct { + Peer string + SymKeyID string + Requests []TopicRequest + Force bool + Timeout time.Duration +} + // SyncMessagesResponse is a response from the mail server // to which SyncMessagesRequest was sent. type SyncMessagesResponse struct { @@ -163,244 +153,6 @@ type SyncMessagesResponse struct { Error string `json:"error"` } -// InitiateHistoryRequestParams type for initiating history requests from a peer. -type InitiateHistoryRequestParams struct { - Peer string - SymKeyID string - Requests []TopicRequest - Force bool - Timeout time.Duration -} - -// ----- -// PUBLIC API -// ----- - -// PublicAPI extends whisper public API. -type PublicAPI struct { - service *Service - publicAPI types.PublicWhisperAPI - log log.Logger -} - -// NewPublicAPI returns instance of the public API. -func NewPublicAPI(s *Service) *PublicAPI { - return &PublicAPI{ - service: s, - publicAPI: s.w.PublicWhisperAPI(), - log: log.New("package", "status-go/services/sshext.PublicAPI"), - } -} - -func (api *PublicAPI) getPeer(rawurl string) (*enode.Node, error) { - if len(rawurl) == 0 { - return mailservers.GetFirstConnected(api.service.server, api.service.peerStore) - } - return enode.ParseV4(rawurl) -} - -// RetryConfig specifies configuration for retries with timeout and max amount of retries. -type RetryConfig struct { - BaseTimeout time.Duration - // StepTimeout defines duration increase per each retry. - StepTimeout time.Duration - MaxRetries int -} - -// RequestMessagesSync repeats MessagesRequest using configuration in retry conf. -func (api *PublicAPI) RequestMessagesSync(conf RetryConfig, r MessagesRequest) (MessagesResponse, error) { - var resp MessagesResponse - - shh := api.service.w - events := make(chan types.EnvelopeEvent, 10) - var ( - requestID types.HexBytes - err error - retries int - ) - for retries <= conf.MaxRetries { - sub := shh.SubscribeEnvelopeEvents(events) - r.Timeout = conf.BaseTimeout + conf.StepTimeout*time.Duration(retries) - timeout := r.Timeout - // FIXME this weird conversion is required because MessagesRequest expects seconds but defines time.Duration - r.Timeout = time.Duration(int(r.Timeout.Seconds())) - requestID, err = api.RequestMessages(context.Background(), r) - if err != nil { - sub.Unsubscribe() - return resp, err - } - mailServerResp, err := waitForExpiredOrCompleted(types.BytesToHash(requestID), events, timeout) - sub.Unsubscribe() - if err == nil { - resp.Cursor = hex.EncodeToString(mailServerResp.Cursor) - resp.Error = mailServerResp.Error - return resp, nil - } - retries++ - api.log.Error("[RequestMessagesSync] failed", "err", err, "retries", retries) - } - return resp, fmt.Errorf("failed to request messages after %d retries", retries) -} - -func waitForExpiredOrCompleted(requestID types.Hash, events chan types.EnvelopeEvent, timeout time.Duration) (*types.MailServerResponse, error) { - expired := fmt.Errorf("request %x expired", requestID) - after := time.NewTimer(timeout) - defer after.Stop() - for { - var ev types.EnvelopeEvent - select { - case ev = <-events: - case <-after.C: - return nil, expired - } - if ev.Hash != requestID { - continue - } - switch ev.Event { - case types.EventMailServerRequestCompleted: - data, ok := ev.Data.(*types.MailServerResponse) - if ok { - return data, nil - } - return nil, errors.New("invalid event data type") - case types.EventMailServerRequestExpired: - return nil, expired - } - } -} - -// RequestMessages sends a request for historic messages to a MailServer. -func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (types.HexBytes, error) { - api.log.Info("RequestMessages", "request", r) - shh := api.service.w - now := api.service.w.GetCurrentTime() - r.setDefaults(now) - - if r.From > r.To { - return nil, fmt.Errorf("Query range is invalid: from > to (%d > %d)", r.From, r.To) - } - - mailServerNode, err := api.getPeer(r.MailServerPeer) - if err != nil { - return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) - } - - var ( - symKey []byte - publicKey *ecdsa.PublicKey - ) - - if r.SymKeyID != "" { - symKey, err = shh.GetSymKey(r.SymKeyID) - if err != nil { - return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) - } - } else { - publicKey = mailServerNode.Pubkey() - } - - payload, err := makeMessagesRequestPayload(r) - if err != nil { - return nil, err - } - - envelope, err := makeEnvelop( - payload, - symKey, - publicKey, - api.service.nodeID, - shh.MinPow(), - now, - ) - if err != nil { - return nil, err - } - hash := envelope.Hash() - - if !r.Force { - err = api.service.requestsRegistry.Register(hash, r.Topics) - if err != nil { - return nil, err - } - } - - if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, r.Timeout*time.Second); err != nil { - if !r.Force { - api.service.requestsRegistry.Unregister(hash) - } - return nil, err - } - - return hash[:], nil -} - -// createSyncMailRequest creates SyncMailRequest. It uses a full bloom filter -// if no topics are given. -func createSyncMailRequest(r SyncMessagesRequest) (types.SyncMailRequest, error) { - var bloom []byte - if len(r.Topics) > 0 { - bloom = topicsToBloom(r.Topics...) - } else { - bloom = types.MakeFullNodeBloom() - } - - cursor, err := hex.DecodeString(r.Cursor) - if err != nil { - return types.SyncMailRequest{}, err - } - - return types.SyncMailRequest{ - Lower: r.From, - Upper: r.To, - Bloom: bloom, - Limit: r.Limit, - Cursor: cursor, - }, nil -} - -func createSyncMessagesResponse(r types.SyncEventResponse) SyncMessagesResponse { - return SyncMessagesResponse{ - Cursor: hex.EncodeToString(r.Cursor), - Error: r.Error, - } -} - -// SyncMessages sends a request to a given MailServerPeer to sync historic messages. -// MailServerPeers needs to be added as a trusted peer first. -func (api *PublicAPI) SyncMessages(ctx context.Context, r SyncMessagesRequest) (SyncMessagesResponse, error) { - log.Info("SyncMessages start", "request", r) - - var response SyncMessagesResponse - - mailServerEnode, err := enode.ParseV4(r.MailServerPeer) - if err != nil { - return response, fmt.Errorf("invalid MailServerPeer: %v", err) - } - mailServerID := mailServerEnode.ID().Bytes() - - request, err := createSyncMailRequest(r) - if err != nil { - return response, fmt.Errorf("failed to create a sync mail request: %v", err) - } - - for { - log.Info("Sending a request to sync messages", "request", request) - - resp, err := api.service.syncMessages(ctx, mailServerID, request) - if err != nil { - return response, err - } - - log.Info("Syncing messages response", "error", resp.Error, "cursor", fmt.Sprintf("%#x", resp.Cursor)) - - if resp.Error != "" || len(resp.Cursor) == 0 || !r.FollowCursor { - return createSyncMessagesResponse(resp), nil - } - - request.Cursor = resp.Cursor - } -} - type Author struct { PublicKey types.HexBytes `json:"publicKey"` Alias string `json:"alias"` @@ -413,421 +165,3 @@ type Metadata struct { MessageID types.HexBytes `json:"messageId"` Author Author `json:"author"` } - -// ConfirmMessagesProcessedByID is a method to confirm that messages was consumed by -// the client side. -// TODO: this is broken now as it requires dedup ID while a message hash should be used. -func (api *PublicAPI) ConfirmMessagesProcessedByID(messageConfirmations []*Metadata) error { - confirmationCount := len(messageConfirmations) - dedupIDs := make([][]byte, confirmationCount) - encryptionIDs := make([][]byte, confirmationCount) - for i, confirmation := range messageConfirmations { - dedupIDs[i] = confirmation.DedupID - encryptionIDs[i] = confirmation.EncryptionID - } - return api.service.ConfirmMessagesProcessed(encryptionIDs) -} - -// Post is used to send one-to-one for those who did not enabled device-to-device sync, -// in other words don't use PFS-enabled messages. Otherwise, SendDirectMessage is used. -// It's important to call PublicAPI.afterSend() so that the client receives a signal -// with confirmation that the message left the device. -func (api *PublicAPI) Post(ctx context.Context, newMessage types.NewMessage) (types.HexBytes, error) { - return api.publicAPI.Post(ctx, newMessage) -} - -// SendPublicMessage sends a public chat message to the underlying transport. -// Message's payload is a transit encoded message. -// It's important to call PublicAPI.afterSend() so that the client receives a signal -// with confirmation that the message left the device. -func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg SendPublicMessageRPC) (types.HexBytes, error) { - chat := protocol.Chat{ - Name: msg.Chat, - } - return api.service.messenger.SendRaw(ctx, chat, msg.Payload) -} - -// SendDirectMessage sends a 1:1 chat message to the underlying transport -// Message's payload is a transit encoded message. -// It's important to call PublicAPI.afterSend() so that the client receives a signal -// with confirmation that the message left the device. -func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg SendDirectMessageRPC) (types.HexBytes, error) { - chat := protocol.Chat{ - ChatType: protocol.ChatTypeOneToOne, - ID: types.EncodeHex(msg.PubKey), - } - - return api.service.messenger.SendRaw(ctx, chat, msg.Payload) -} - -func (api *PublicAPI) Join(chat protocol.Chat) error { - return api.service.messenger.Join(chat) -} - -func (api *PublicAPI) Leave(chat protocol.Chat) error { - return api.service.messenger.Leave(chat) -} - -func (api *PublicAPI) LeaveGroupChat(ctx Context, chatID string) (*protocol.MessengerResponse, error) { - return api.service.messenger.LeaveGroupChat(ctx, chatID) -} - -func (api *PublicAPI) CreateGroupChatWithMembers(ctx Context, name string, members []string) (*protocol.MessengerResponse, error) { - return api.service.messenger.CreateGroupChatWithMembers(ctx, name, members) -} - -func (api *PublicAPI) AddMembersToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { - return api.service.messenger.AddMembersToGroupChat(ctx, chatID, members) -} - -func (api *PublicAPI) RemoveMemberFromGroupChat(ctx Context, chatID string, member string) (*protocol.MessengerResponse, error) { - return api.service.messenger.RemoveMemberFromGroupChat(ctx, chatID, member) -} - -func (api *PublicAPI) AddAdminsToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { - return api.service.messenger.AddAdminsToGroupChat(ctx, chatID, members) -} - -func (api *PublicAPI) ConfirmJoiningGroup(ctx context.Context, chatID string) (*protocol.MessengerResponse, error) { - return api.service.messenger.ConfirmJoiningGroup(ctx, chatID) -} - -func (api *PublicAPI) requestMessagesUsingPayload(request db.HistoryRequest, peer, symkeyID string, payload []byte, force bool, timeout time.Duration, topics []types.TopicType) (hash types.Hash, err error) { - shh := api.service.w - now := api.service.w.GetCurrentTime() - - mailServerNode, err := api.getPeer(peer) - if err != nil { - return hash, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) - } - - var ( - symKey []byte - publicKey *ecdsa.PublicKey - ) - - if symkeyID != "" { - symKey, err = shh.GetSymKey(symkeyID) - if err != nil { - return hash, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) - } - } else { - publicKey = mailServerNode.Pubkey() - } - - envelope, err := makeEnvelop( - payload, - symKey, - publicKey, - api.service.nodeID, - shh.MinPow(), - now, - ) - if err != nil { - return hash, err - } - hash = envelope.Hash() - - err = request.Replace(hash) - if err != nil { - return hash, err - } - - if !force { - err = api.service.requestsRegistry.Register(hash, topics) - if err != nil { - return hash, err - } - } - - if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, timeout); err != nil { - if !force { - api.service.requestsRegistry.Unregister(hash) - } - return hash, err - } - - return hash, nil - -} - -// InitiateHistoryRequests is a stateful API for initiating history request for each topic. -// Caller of this method needs to define only two parameters per each TopicRequest: -// - Topic -// - Duration in nanoseconds. Will be used to determine starting time for history request. -// After that status-go will guarantee that request for this topic and date will be performed. -func (api *PublicAPI) InitiateHistoryRequests(parent context.Context, request InitiateHistoryRequestParams) (rst []types.HexBytes, err error) { - tx := api.service.storage.NewTx() - defer func() { - if err == nil { - err = tx.Commit() - } - }() - ctx := NewContextFromService(parent, api.service, tx) - requests, err := api.service.historyUpdates.CreateRequests(ctx, request.Requests) - if err != nil { - return nil, err - } - var ( - payload []byte - hash types.Hash - ) - for i := range requests { - req := requests[i] - options := CreateTopicOptionsFromRequest(req) - bloom := options.ToBloomFilterOption() - payload, err = bloom.ToMessagesRequestPayload() - if err != nil { - return rst, err - } - hash, err = api.requestMessagesUsingPayload(req, request.Peer, request.SymKeyID, payload, request.Force, request.Timeout, options.Topics()) - if err != nil { - return rst, err - } - rst = append(rst, hash.Bytes()) - } - return rst, err -} - -// CompleteRequest client must mark request completed when all envelopes were processed. -func (api *PublicAPI) CompleteRequest(parent context.Context, hex string) (err error) { - tx := api.service.storage.NewTx() - ctx := NewContextFromService(parent, api.service, tx) - err = api.service.historyUpdates.UpdateFinishedRequest(ctx, types.HexToHash(hex)) - if err == nil { - return tx.Commit() - } - return err -} - -func (api *PublicAPI) LoadFilters(parent context.Context, chats []*transport.Filter) ([]*transport.Filter, error) { - return api.service.messenger.LoadFilters(chats) -} - -func (api *PublicAPI) SaveChat(parent context.Context, chat *protocol.Chat) error { - api.log.Info("saving chat", "chat", chat) - return api.service.messenger.SaveChat(chat) -} - -func (api *PublicAPI) Chats(parent context.Context) []*protocol.Chat { - return api.service.messenger.Chats() -} - -func (api *PublicAPI) DeleteChat(parent context.Context, chatID string) error { - return api.service.messenger.DeleteChat(chatID) -} - -func (api *PublicAPI) SaveContact(parent context.Context, contact *protocol.Contact) error { - return api.service.messenger.SaveContact(contact) -} - -func (api *PublicAPI) BlockContact(parent context.Context, contact *protocol.Contact) ([]*protocol.Chat, error) { - api.log.Info("blocking contact", "contact", contact.ID) - return api.service.messenger.BlockContact(contact) -} - -func (api *PublicAPI) Contacts(parent context.Context) []*protocol.Contact { - return api.service.messenger.Contacts() -} - -func (api *PublicAPI) RemoveFilters(parent context.Context, chats []*transport.Filter) error { - return api.service.messenger.RemoveFilters(chats) -} - -// EnableInstallation enables an installation for multi-device sync. -func (api *PublicAPI) EnableInstallation(installationID string) error { - return api.service.messenger.EnableInstallation(installationID) -} - -// DisableInstallation disables an installation for multi-device sync. -func (api *PublicAPI) DisableInstallation(installationID string) error { - return api.service.messenger.DisableInstallation(installationID) -} - -// GetOurInstallations returns all the installations available given an identity -func (api *PublicAPI) GetOurInstallations() []*multidevice.Installation { - return api.service.messenger.Installations() -} - -// SetInstallationMetadata sets the metadata for our own installation -func (api *PublicAPI) SetInstallationMetadata(installationID string, data *multidevice.InstallationMetadata) error { - return api.service.messenger.SetInstallationMetadata(installationID, data) -} - -// VerifyENSNames takes a list of ensdetails and returns whether they match the public key specified -func (api *PublicAPI) VerifyENSNames(details []enstypes.ENSDetails) (map[string]enstypes.ENSResponse, error) { - return api.service.messenger.VerifyENSNames(api.service.config.VerifyENSURL, ensContractAddress, details) -} - -type ApplicationMessagesResponse struct { - Messages []*protocol.Message `json:"messages"` - Cursor string `json:"cursor"` -} - -func (api *PublicAPI) ChatMessages(chatID, cursor string, limit int) (*ApplicationMessagesResponse, error) { - messages, cursor, err := api.service.messenger.MessageByChatID(chatID, cursor, limit) - if err != nil { - return nil, err - } - - return &ApplicationMessagesResponse{ - Messages: messages, - Cursor: cursor, - }, nil -} - -func (api *PublicAPI) DeleteMessage(id string) error { - return api.service.messenger.DeleteMessage(id) -} - -func (api *PublicAPI) DeleteMessagesByChatID(id string) error { - return api.service.messenger.DeleteMessagesByChatID(id) -} - -func (api *PublicAPI) MarkMessagesSeen(chatID string, ids []string) error { - return api.service.messenger.MarkMessagesSeen(chatID, ids) -} - -func (api *PublicAPI) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error { - return api.service.messenger.UpdateMessageOutgoingStatus(id, newOutgoingStatus) -} - -func (api *PublicAPI) SendChatMessage(ctx context.Context, message *protocol.Message) (*protocol.MessengerResponse, error) { - return api.service.messenger.SendChatMessage(ctx, message) -} - -func (api *PublicAPI) ReSendChatMessage(ctx context.Context, messageID string) error { - return api.service.messenger.ReSendChatMessage(ctx, messageID) -} - -func (api *PublicAPI) RequestTransaction(ctx context.Context, chatID, value, contract, address string) (*protocol.MessengerResponse, error) { - return api.service.messenger.RequestTransaction(ctx, chatID, value, contract, address) -} - -func (api *PublicAPI) RequestAddressForTransaction(ctx context.Context, chatID, from, value, contract string) (*protocol.MessengerResponse, error) { - return api.service.messenger.RequestAddressForTransaction(ctx, chatID, from, value, contract) -} - -func (api *PublicAPI) DeclineRequestAddressForTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { - return api.service.messenger.DeclineRequestAddressForTransaction(ctx, messageID) -} - -func (api *PublicAPI) DeclineRequestTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { - return api.service.messenger.DeclineRequestTransaction(ctx, messageID) -} - -func (api *PublicAPI) AcceptRequestAddressForTransaction(ctx context.Context, messageID, address string) (*protocol.MessengerResponse, error) { - return api.service.messenger.AcceptRequestAddressForTransaction(ctx, messageID, address) -} - -func (api *PublicAPI) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature types.HexBytes) (*protocol.MessengerResponse, error) { - return api.service.messenger.SendTransaction(ctx, chatID, value, contract, transactionHash, signature) -} - -func (api *PublicAPI) AcceptRequestTransaction(ctx context.Context, transactionHash, messageID string, signature types.HexBytes) (*protocol.MessengerResponse, error) { - return api.service.messenger.AcceptRequestTransaction(ctx, transactionHash, messageID, signature) -} - -func (api *PublicAPI) SendContactUpdates(ctx context.Context, name, picture string) error { - return api.service.messenger.SendContactUpdates(ctx, name, picture) -} - -func (api *PublicAPI) SendContactUpdate(ctx context.Context, contactID, name, picture string) (*protocol.MessengerResponse, error) { - return api.service.messenger.SendContactUpdate(ctx, contactID, name, picture) -} - -func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) { - return api.service.messenger.SendPairInstallation(ctx) -} - -func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error { - return api.service.messenger.SyncDevices(ctx, name, picture) -} - -// ----- -// HELPER -// ----- - -// makeEnvelop makes an envelop for a historic messages request. -// Symmetric key is used to authenticate to MailServer. -// PK is the current node ID. -func makeEnvelop( - payload []byte, - symKey []byte, - publicKey *ecdsa.PublicKey, - nodeID *ecdsa.PrivateKey, - pow float64, - now time.Time, -) (types.Envelope, error) { - // TODO: replace with an types.Envelope creator passed to the API struct - params := whisper.MessageParams{ - PoW: pow, - Payload: payload, - WorkTime: defaultWorkTime, - Src: nodeID, - } - // Either symKey or public key is required. - // This condition is verified in `message.Wrap()` method. - if len(symKey) > 0 { - params.KeySym = symKey - } else if publicKey != nil { - params.Dst = publicKey - } - message, err := whisper.NewSentMessage(¶ms) - if err != nil { - return nil, err - } - envelope, err := message.Wrap(¶ms, now) - if err != nil { - return nil, err - } - return gethbridge.NewWhisperEnvelope(envelope), nil -} - -// makeMessagesRequestPayload makes a specific payload for MailServer -// to request historic messages. -func makeMessagesRequestPayload(r MessagesRequest) ([]byte, error) { - cursor, err := hex.DecodeString(r.Cursor) - if err != nil { - return nil, fmt.Errorf("invalid cursor: %v", err) - } - - if len(cursor) > 0 && len(cursor) != mailserver.CursorLength { - return nil, fmt.Errorf("invalid cursor size: expected %d but got %d", mailserver.CursorLength, len(cursor)) - } - - payload := mailserver.MessagesRequestPayload{ - Lower: r.From, - Upper: r.To, - Bloom: createBloomFilter(r), - Limit: r.Limit, - Cursor: cursor, - // Client must tell the MailServer if it supports batch responses. - // This can be removed in the future. - Batch: true, - } - - return rlp.EncodeToBytes(payload) -} - -func createBloomFilter(r MessagesRequest) []byte { - if len(r.Topics) > 0 { - return topicsToBloom(r.Topics...) - } - - return types.TopicToBloom(r.Topic) -} - -func topicsToBloom(topics ...types.TopicType) []byte { - i := new(big.Int) - for _, topic := range topics { - bloom := types.TopicToBloom(topic) - i.Or(i, new(big.Int).SetBytes(bloom[:])) - } - - combined := make([]byte, types.BloomFilterSize) - data := i.Bytes() - copy(combined[types.BloomFilterSize-len(data):], data[:]) - - return combined -} diff --git a/services/shhext/api_geth.go b/services/shhext/api_geth.go new file mode 100644 index 000000000..2318749e3 --- /dev/null +++ b/services/shhext/api_geth.go @@ -0,0 +1,676 @@ +// +build !nimbus + +package shhext + +import ( + "context" + "crypto/ecdsa" + "encoding/hex" + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/db" + "github.com/status-im/status-go/mailserver" + "github.com/status-im/status-go/services/shhext/mailservers" + "github.com/status-im/status-go/whisper/v6" + + gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" + "github.com/status-im/status-go/eth-node/types" + enstypes "github.com/status-im/status-go/eth-node/types/ens" + "github.com/status-im/status-go/protocol" + "github.com/status-im/status-go/protocol/encryption/multidevice" + "github.com/status-im/status-go/protocol/transport" +) + +// ----- +// PUBLIC API +// ----- + +// PublicAPI extends whisper public API. +type PublicAPI struct { + service *Service + publicAPI types.PublicWhisperAPI + log log.Logger +} + +// NewPublicAPI returns instance of the public API. +func NewPublicAPI(s *Service) *PublicAPI { + return &PublicAPI{ + service: s, + publicAPI: s.w.PublicWhisperAPI(), + log: log.New("package", "status-go/services/sshext.PublicAPI"), + } +} + +func (api *PublicAPI) getPeer(rawurl string) (*enode.Node, error) { + if len(rawurl) == 0 { + return mailservers.GetFirstConnected(api.service.server, api.service.peerStore) + } + return enode.ParseV4(rawurl) +} + +// RetryConfig specifies configuration for retries with timeout and max amount of retries. +type RetryConfig struct { + BaseTimeout time.Duration + // StepTimeout defines duration increase per each retry. + StepTimeout time.Duration + MaxRetries int +} + +// RequestMessagesSync repeats MessagesRequest using configuration in retry conf. +func (api *PublicAPI) RequestMessagesSync(conf RetryConfig, r MessagesRequest) (MessagesResponse, error) { + var resp MessagesResponse + + shh := api.service.w + events := make(chan types.EnvelopeEvent, 10) + var ( + requestID types.HexBytes + err error + retries int + ) + for retries <= conf.MaxRetries { + sub := shh.SubscribeEnvelopeEvents(events) + r.Timeout = conf.BaseTimeout + conf.StepTimeout*time.Duration(retries) + timeout := r.Timeout + // FIXME this weird conversion is required because MessagesRequest expects seconds but defines time.Duration + r.Timeout = time.Duration(int(r.Timeout.Seconds())) + requestID, err = api.RequestMessages(context.Background(), r) + if err != nil { + sub.Unsubscribe() + return resp, err + } + mailServerResp, err := waitForExpiredOrCompleted(types.BytesToHash(requestID), events, timeout) + sub.Unsubscribe() + if err == nil { + resp.Cursor = hex.EncodeToString(mailServerResp.Cursor) + resp.Error = mailServerResp.Error + return resp, nil + } + retries++ + api.log.Error("[RequestMessagesSync] failed", "err", err, "retries", retries) + } + return resp, fmt.Errorf("failed to request messages after %d retries", retries) +} + +func waitForExpiredOrCompleted(requestID types.Hash, events chan types.EnvelopeEvent, timeout time.Duration) (*types.MailServerResponse, error) { + expired := fmt.Errorf("request %x expired", requestID) + after := time.NewTimer(timeout) + defer after.Stop() + for { + var ev types.EnvelopeEvent + select { + case ev = <-events: + case <-after.C: + return nil, expired + } + if ev.Hash != requestID { + continue + } + switch ev.Event { + case types.EventMailServerRequestCompleted: + data, ok := ev.Data.(*types.MailServerResponse) + if ok { + return data, nil + } + return nil, errors.New("invalid event data type") + case types.EventMailServerRequestExpired: + return nil, expired + } + } +} + +// RequestMessages sends a request for historic messages to a MailServer. +func (api *PublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (types.HexBytes, error) { + api.log.Info("RequestMessages", "request", r) + shh := api.service.w + now := api.service.w.GetCurrentTime() + r.setDefaults(now) + + if r.From > r.To { + return nil, fmt.Errorf("Query range is invalid: from > to (%d > %d)", r.From, r.To) + } + + mailServerNode, err := api.getPeer(r.MailServerPeer) + if err != nil { + return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) + } + + var ( + symKey []byte + publicKey *ecdsa.PublicKey + ) + + if r.SymKeyID != "" { + symKey, err = shh.GetSymKey(r.SymKeyID) + if err != nil { + return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) + } + } else { + publicKey = mailServerNode.Pubkey() + } + + payload, err := makeMessagesRequestPayload(r) + if err != nil { + return nil, err + } + + envelope, err := makeEnvelop( + payload, + symKey, + publicKey, + api.service.nodeID, + shh.MinPow(), + now, + ) + if err != nil { + return nil, err + } + hash := envelope.Hash() + + if !r.Force { + err = api.service.requestsRegistry.Register(hash, r.Topics) + if err != nil { + return nil, err + } + } + + if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, r.Timeout*time.Second); err != nil { + if !r.Force { + api.service.requestsRegistry.Unregister(hash) + } + return nil, err + } + + return hash[:], nil +} + +// createSyncMailRequest creates SyncMailRequest. It uses a full bloom filter +// if no topics are given. +func createSyncMailRequest(r SyncMessagesRequest) (types.SyncMailRequest, error) { + var bloom []byte + if len(r.Topics) > 0 { + bloom = topicsToBloom(r.Topics...) + } else { + bloom = types.MakeFullNodeBloom() + } + + cursor, err := hex.DecodeString(r.Cursor) + if err != nil { + return types.SyncMailRequest{}, err + } + + return types.SyncMailRequest{ + Lower: r.From, + Upper: r.To, + Bloom: bloom, + Limit: r.Limit, + Cursor: cursor, + }, nil +} + +func createSyncMessagesResponse(r types.SyncEventResponse) SyncMessagesResponse { + return SyncMessagesResponse{ + Cursor: hex.EncodeToString(r.Cursor), + Error: r.Error, + } +} + +// SyncMessages sends a request to a given MailServerPeer to sync historic messages. +// MailServerPeers needs to be added as a trusted peer first. +func (api *PublicAPI) SyncMessages(ctx context.Context, r SyncMessagesRequest) (SyncMessagesResponse, error) { + log.Info("SyncMessages start", "request", r) + + var response SyncMessagesResponse + + mailServerEnode, err := enode.ParseV4(r.MailServerPeer) + if err != nil { + return response, fmt.Errorf("invalid MailServerPeer: %v", err) + } + mailServerID := mailServerEnode.ID().Bytes() + + request, err := createSyncMailRequest(r) + if err != nil { + return response, fmt.Errorf("failed to create a sync mail request: %v", err) + } + + for { + log.Info("Sending a request to sync messages", "request", request) + + resp, err := api.service.syncMessages(ctx, mailServerID, request) + if err != nil { + return response, err + } + + log.Info("Syncing messages response", "error", resp.Error, "cursor", fmt.Sprintf("%#x", resp.Cursor)) + + if resp.Error != "" || len(resp.Cursor) == 0 || !r.FollowCursor { + return createSyncMessagesResponse(resp), nil + } + + request.Cursor = resp.Cursor + } +} + +// ConfirmMessagesProcessedByID is a method to confirm that messages was consumed by +// the client side. +// TODO: this is broken now as it requires dedup ID while a message hash should be used. +func (api *PublicAPI) ConfirmMessagesProcessedByID(messageConfirmations []*Metadata) error { + confirmationCount := len(messageConfirmations) + dedupIDs := make([][]byte, confirmationCount) + encryptionIDs := make([][]byte, confirmationCount) + for i, confirmation := range messageConfirmations { + dedupIDs[i] = confirmation.DedupID + encryptionIDs[i] = confirmation.EncryptionID + } + return api.service.ConfirmMessagesProcessed(encryptionIDs) +} + +// Post is used to send one-to-one for those who did not enabled device-to-device sync, +// in other words don't use PFS-enabled messages. Otherwise, SendDirectMessage is used. +// It's important to call PublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *PublicAPI) Post(ctx context.Context, newMessage types.NewMessage) (types.HexBytes, error) { + return api.publicAPI.Post(ctx, newMessage) +} + +// SendPublicMessage sends a public chat message to the underlying transport. +// Message's payload is a transit encoded message. +// It's important to call PublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *PublicAPI) SendPublicMessage(ctx context.Context, msg SendPublicMessageRPC) (types.HexBytes, error) { + chat := protocol.Chat{ + Name: msg.Chat, + } + return api.service.messenger.SendRaw(ctx, chat, msg.Payload) +} + +// SendDirectMessage sends a 1:1 chat message to the underlying transport +// Message's payload is a transit encoded message. +// It's important to call PublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *PublicAPI) SendDirectMessage(ctx context.Context, msg SendDirectMessageRPC) (types.HexBytes, error) { + chat := protocol.Chat{ + ChatType: protocol.ChatTypeOneToOne, + ID: types.EncodeHex(msg.PubKey), + } + + return api.service.messenger.SendRaw(ctx, chat, msg.Payload) +} + +func (api *PublicAPI) Join(chat protocol.Chat) error { + return api.service.messenger.Join(chat) +} + +func (api *PublicAPI) Leave(chat protocol.Chat) error { + return api.service.messenger.Leave(chat) +} + +func (api *PublicAPI) LeaveGroupChat(ctx Context, chatID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.LeaveGroupChat(ctx, chatID) +} + +func (api *PublicAPI) CreateGroupChatWithMembers(ctx Context, name string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.CreateGroupChatWithMembers(ctx, name, members) +} + +func (api *PublicAPI) AddMembersToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AddMembersToGroupChat(ctx, chatID, members) +} + +func (api *PublicAPI) RemoveMemberFromGroupChat(ctx Context, chatID string, member string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RemoveMemberFromGroupChat(ctx, chatID, member) +} + +func (api *PublicAPI) AddAdminsToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AddAdminsToGroupChat(ctx, chatID, members) +} + +func (api *PublicAPI) ConfirmJoiningGroup(ctx context.Context, chatID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.ConfirmJoiningGroup(ctx, chatID) +} + +func (api *PublicAPI) requestMessagesUsingPayload(request db.HistoryRequest, peer, symkeyID string, payload []byte, force bool, timeout time.Duration, topics []types.TopicType) (hash types.Hash, err error) { + shh := api.service.w + now := api.service.w.GetCurrentTime() + + mailServerNode, err := api.getPeer(peer) + if err != nil { + return hash, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) + } + + var ( + symKey []byte + publicKey *ecdsa.PublicKey + ) + + if symkeyID != "" { + symKey, err = shh.GetSymKey(symkeyID) + if err != nil { + return hash, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) + } + } else { + publicKey = mailServerNode.Pubkey() + } + + envelope, err := makeEnvelop( + payload, + symKey, + publicKey, + api.service.nodeID, + shh.MinPow(), + now, + ) + if err != nil { + return hash, err + } + hash = envelope.Hash() + + err = request.Replace(hash) + if err != nil { + return hash, err + } + + if !force { + err = api.service.requestsRegistry.Register(hash, topics) + if err != nil { + return hash, err + } + } + + if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, timeout); err != nil { + if !force { + api.service.requestsRegistry.Unregister(hash) + } + return hash, err + } + + return hash, nil + +} + +// InitiateHistoryRequests is a stateful API for initiating history request for each topic. +// Caller of this method needs to define only two parameters per each TopicRequest: +// - Topic +// - Duration in nanoseconds. Will be used to determine starting time for history request. +// After that status-go will guarantee that request for this topic and date will be performed. +func (api *PublicAPI) InitiateHistoryRequests(parent context.Context, request InitiateHistoryRequestParams) (rst []types.HexBytes, err error) { + tx := api.service.storage.NewTx() + defer func() { + if err == nil { + err = tx.Commit() + } + }() + ctx := NewContextFromService(parent, api.service, tx) + requests, err := api.service.historyUpdates.CreateRequests(ctx, request.Requests) + if err != nil { + return nil, err + } + var ( + payload []byte + hash types.Hash + ) + for i := range requests { + req := requests[i] + options := CreateTopicOptionsFromRequest(req) + bloom := options.ToBloomFilterOption() + payload, err = bloom.ToMessagesRequestPayload() + if err != nil { + return rst, err + } + hash, err = api.requestMessagesUsingPayload(req, request.Peer, request.SymKeyID, payload, request.Force, request.Timeout, options.Topics()) + if err != nil { + return rst, err + } + rst = append(rst, hash.Bytes()) + } + return rst, err +} + +// CompleteRequest client must mark request completed when all envelopes were processed. +func (api *PublicAPI) CompleteRequest(parent context.Context, hex string) (err error) { + tx := api.service.storage.NewTx() + ctx := NewContextFromService(parent, api.service, tx) + err = api.service.historyUpdates.UpdateFinishedRequest(ctx, types.HexToHash(hex)) + if err == nil { + return tx.Commit() + } + return err +} + +func (api *PublicAPI) LoadFilters(parent context.Context, chats []*transport.Filter) ([]*transport.Filter, error) { + return api.service.messenger.LoadFilters(chats) +} + +func (api *PublicAPI) SaveChat(parent context.Context, chat *protocol.Chat) error { + api.log.Info("saving chat", "chat", chat) + return api.service.messenger.SaveChat(chat) +} + +func (api *PublicAPI) Chats(parent context.Context) []*protocol.Chat { + return api.service.messenger.Chats() +} + +func (api *PublicAPI) DeleteChat(parent context.Context, chatID string) error { + return api.service.messenger.DeleteChat(chatID) +} + +func (api *PublicAPI) SaveContact(parent context.Context, contact *protocol.Contact) error { + return api.service.messenger.SaveContact(contact) +} + +func (api *PublicAPI) BlockContact(parent context.Context, contact *protocol.Contact) ([]*protocol.Chat, error) { + api.log.Info("blocking contact", "contact", contact.ID) + return api.service.messenger.BlockContact(contact) +} + +func (api *PublicAPI) Contacts(parent context.Context) []*protocol.Contact { + return api.service.messenger.Contacts() +} + +func (api *PublicAPI) RemoveFilters(parent context.Context, chats []*transport.Filter) error { + return api.service.messenger.RemoveFilters(chats) +} + +// EnableInstallation enables an installation for multi-device sync. +func (api *PublicAPI) EnableInstallation(installationID string) error { + return api.service.messenger.EnableInstallation(installationID) +} + +// DisableInstallation disables an installation for multi-device sync. +func (api *PublicAPI) DisableInstallation(installationID string) error { + return api.service.messenger.DisableInstallation(installationID) +} + +// GetOurInstallations returns all the installations available given an identity +func (api *PublicAPI) GetOurInstallations() []*multidevice.Installation { + return api.service.messenger.Installations() +} + +// SetInstallationMetadata sets the metadata for our own installation +func (api *PublicAPI) SetInstallationMetadata(installationID string, data *multidevice.InstallationMetadata) error { + return api.service.messenger.SetInstallationMetadata(installationID, data) +} + +// VerifyENSNames takes a list of ensdetails and returns whether they match the public key specified +func (api *PublicAPI) VerifyENSNames(details []enstypes.ENSDetails) (map[string]enstypes.ENSResponse, error) { + return api.service.messenger.VerifyENSNames(api.service.config.VerifyENSURL, ensContractAddress, details) +} + +type ApplicationMessagesResponse struct { + Messages []*protocol.Message `json:"messages"` + Cursor string `json:"cursor"` +} + +func (api *PublicAPI) ChatMessages(chatID, cursor string, limit int) (*ApplicationMessagesResponse, error) { + messages, cursor, err := api.service.messenger.MessageByChatID(chatID, cursor, limit) + if err != nil { + return nil, err + } + + return &ApplicationMessagesResponse{ + Messages: messages, + Cursor: cursor, + }, nil +} + +func (api *PublicAPI) DeleteMessage(id string) error { + return api.service.messenger.DeleteMessage(id) +} + +func (api *PublicAPI) DeleteMessagesByChatID(id string) error { + return api.service.messenger.DeleteMessagesByChatID(id) +} + +func (api *PublicAPI) MarkMessagesSeen(chatID string, ids []string) error { + return api.service.messenger.MarkMessagesSeen(chatID, ids) +} + +func (api *PublicAPI) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error { + return api.service.messenger.UpdateMessageOutgoingStatus(id, newOutgoingStatus) +} + +func (api *PublicAPI) SendChatMessage(ctx context.Context, message *protocol.Message) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendChatMessage(ctx, message) +} + +func (api *PublicAPI) ReSendChatMessage(ctx context.Context, messageID string) error { + return api.service.messenger.ReSendChatMessage(ctx, messageID) +} + +func (api *PublicAPI) RequestTransaction(ctx context.Context, chatID, value, contract, address string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RequestTransaction(ctx, chatID, value, contract, address) +} + +func (api *PublicAPI) RequestAddressForTransaction(ctx context.Context, chatID, from, value, contract string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RequestAddressForTransaction(ctx, chatID, from, value, contract) +} + +func (api *PublicAPI) DeclineRequestAddressForTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.DeclineRequestAddressForTransaction(ctx, messageID) +} + +func (api *PublicAPI) DeclineRequestTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.DeclineRequestTransaction(ctx, messageID) +} + +func (api *PublicAPI) AcceptRequestAddressForTransaction(ctx context.Context, messageID, address string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptRequestAddressForTransaction(ctx, messageID, address) +} + +func (api *PublicAPI) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature types.HexBytes) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendTransaction(ctx, chatID, value, contract, transactionHash, signature) +} + +func (api *PublicAPI) AcceptRequestTransaction(ctx context.Context, transactionHash, messageID string, signature types.HexBytes) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptRequestTransaction(ctx, transactionHash, messageID, signature) +} + +func (api *PublicAPI) SendContactUpdates(ctx context.Context, name, picture string) error { + return api.service.messenger.SendContactUpdates(ctx, name, picture) +} + +func (api *PublicAPI) SendContactUpdate(ctx context.Context, contactID, name, picture string) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendContactUpdate(ctx, contactID, name, picture) +} + +func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendPairInstallation(ctx) +} + +func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error { + return api.service.messenger.SyncDevices(ctx, name, picture) +} + +// ----- +// HELPER +// ----- + +// makeEnvelop makes an envelop for a historic messages request. +// Symmetric key is used to authenticate to MailServer. +// PK is the current node ID. +func makeEnvelop( + payload []byte, + symKey []byte, + publicKey *ecdsa.PublicKey, + nodeID *ecdsa.PrivateKey, + pow float64, + now time.Time, +) (types.Envelope, error) { + // TODO: replace with an types.Envelope creator passed to the API struct + params := whisper.MessageParams{ + PoW: pow, + Payload: payload, + WorkTime: defaultWorkTime, + Src: nodeID, + } + // Either symKey or public key is required. + // This condition is verified in `message.Wrap()` method. + if len(symKey) > 0 { + params.KeySym = symKey + } else if publicKey != nil { + params.Dst = publicKey + } + message, err := whisper.NewSentMessage(¶ms) + if err != nil { + return nil, err + } + envelope, err := message.Wrap(¶ms, now) + if err != nil { + return nil, err + } + return gethbridge.NewWhisperEnvelope(envelope), nil +} + +// makeMessagesRequestPayload makes a specific payload for MailServer +// to request historic messages. +func makeMessagesRequestPayload(r MessagesRequest) ([]byte, error) { + cursor, err := hex.DecodeString(r.Cursor) + if err != nil { + return nil, fmt.Errorf("invalid cursor: %v", err) + } + + if len(cursor) > 0 && len(cursor) != mailserver.CursorLength { + return nil, fmt.Errorf("invalid cursor size: expected %d but got %d", mailserver.CursorLength, len(cursor)) + } + + payload := mailserver.MessagesRequestPayload{ + Lower: r.From, + Upper: r.To, + Bloom: createBloomFilter(r), + Limit: r.Limit, + Cursor: cursor, + // Client must tell the MailServer if it supports batch responses. + // This can be removed in the future. + Batch: true, + } + + return rlp.EncodeToBytes(payload) +} + +func createBloomFilter(r MessagesRequest) []byte { + if len(r.Topics) > 0 { + return topicsToBloom(r.Topics...) + } + + return types.TopicToBloom(r.Topic) +} + +func topicsToBloom(topics ...types.TopicType) []byte { + i := new(big.Int) + for _, topic := range topics { + bloom := types.TopicToBloom(topic) + i.Or(i, new(big.Int).SetBytes(bloom[:])) + } + + combined := make([]byte, types.BloomFilterSize) + data := i.Bytes() + copy(combined[types.BloomFilterSize-len(data):], data[:]) + + return combined +} diff --git a/services/shhext/api_test.go b/services/shhext/api_geth_test.go similarity index 99% rename from services/shhext/api_test.go rename to services/shhext/api_geth_test.go index 3b5879fcb..86e9c207c 100644 --- a/services/shhext/api_test.go +++ b/services/shhext/api_geth_test.go @@ -1,3 +1,5 @@ +// +build !nimbus + package shhext import ( diff --git a/services/shhext/api_nimbus.go b/services/shhext/api_nimbus.go new file mode 100644 index 000000000..4ec231749 --- /dev/null +++ b/services/shhext/api_nimbus.go @@ -0,0 +1,661 @@ +// +build nimbus + +package shhext + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + + "github.com/status-im/status-go/eth-node/types" + enstypes "github.com/status-im/status-go/eth-node/types/ens" + "github.com/status-im/status-go/protocol" + "github.com/status-im/status-go/protocol/encryption/multidevice" + "github.com/status-im/status-go/protocol/transport" +) + +// ----- +// PUBLIC API +// ----- + +// NimbusPublicAPI extends whisper public API. +type NimbusPublicAPI struct { + service *NimbusService + publicAPI types.PublicWhisperAPI + log log.Logger +} + +// NewPublicAPI returns instance of the public API. +func NewNimbusPublicAPI(s *NimbusService) *NimbusPublicAPI { + return &NimbusPublicAPI{ + service: s, + publicAPI: s.w.PublicWhisperAPI(), + log: log.New("package", "status-go/services/sshext.NimbusPublicAPI"), + } +} + +// func (api *NimbusPublicAPI) getPeer(rawurl string) (*enode.Node, error) { +// if len(rawurl) == 0 { +// return mailservers.GetFirstConnected(api.service.server, api.service.peerStore) +// } +// return enode.ParseV4(rawurl) +// } + +// // RetryConfig specifies configuration for retries with timeout and max amount of retries. +// type RetryConfig struct { +// BaseTimeout time.Duration +// // StepTimeout defines duration increase per each retry. +// StepTimeout time.Duration +// MaxRetries int +// } + +// RequestMessagesSync repeats MessagesRequest using configuration in retry conf. +// func (api *NimbusPublicAPI) RequestMessagesSync(conf RetryConfig, r MessagesRequest) (MessagesResponse, error) { +// var resp MessagesResponse + +// shh := api.service.w +// events := make(chan types.EnvelopeEvent, 10) +// var ( +// requestID types.HexBytes +// err error +// retries int +// ) +// for retries <= conf.MaxRetries { +// sub := shh.SubscribeEnvelopeEvents(events) +// r.Timeout = conf.BaseTimeout + conf.StepTimeout*time.Duration(retries) +// timeout := r.Timeout +// // FIXME this weird conversion is required because MessagesRequest expects seconds but defines time.Duration +// r.Timeout = time.Duration(int(r.Timeout.Seconds())) +// requestID, err = api.RequestMessages(context.Background(), r) +// if err != nil { +// sub.Unsubscribe() +// return resp, err +// } +// mailServerResp, err := waitForExpiredOrCompleted(types.BytesToHash(requestID), events, timeout) +// sub.Unsubscribe() +// if err == nil { +// resp.Cursor = hex.EncodeToString(mailServerResp.Cursor) +// resp.Error = mailServerResp.Error +// return resp, nil +// } +// retries++ +// api.log.Error("[RequestMessagesSync] failed", "err", err, "retries", retries) +// } +// return resp, fmt.Errorf("failed to request messages after %d retries", retries) +// } + +// func waitForExpiredOrCompleted(requestID types.Hash, events chan types.EnvelopeEvent, timeout time.Duration) (*types.MailServerResponse, error) { +// expired := fmt.Errorf("request %x expired", requestID) +// after := time.NewTimer(timeout) +// defer after.Stop() +// for { +// var ev types.EnvelopeEvent +// select { +// case ev = <-events: +// case <-after.C: +// return nil, expired +// } +// if ev.Hash != requestID { +// continue +// } +// switch ev.Event { +// case types.EventMailServerRequestCompleted: +// data, ok := ev.Data.(*types.MailServerResponse) +// if ok { +// return data, nil +// } +// return nil, errors.New("invalid event data type") +// case types.EventMailServerRequestExpired: +// return nil, expired +// } +// } +// } + +// // RequestMessages sends a request for historic messages to a MailServer. +// func (api *NimbusPublicAPI) RequestMessages(_ context.Context, r MessagesRequest) (types.HexBytes, error) { +// api.log.Info("RequestMessages", "request", r) +// shh := api.service.w +// now := api.service.w.GetCurrentTime() +// r.setDefaults(now) + +// if r.From > r.To { +// return nil, fmt.Errorf("Query range is invalid: from > to (%d > %d)", r.From, r.To) +// } + +// mailServerNode, err := api.getPeer(r.MailServerPeer) +// if err != nil { +// return nil, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) +// } + +// var ( +// symKey []byte +// publicKey *ecdsa.PublicKey +// ) + +// if r.SymKeyID != "" { +// symKey, err = shh.GetSymKey(r.SymKeyID) +// if err != nil { +// return nil, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) +// } +// } else { +// publicKey = mailServerNode.Pubkey() +// } + +// payload, err := makeMessagesRequestPayload(r) +// if err != nil { +// return nil, err +// } + +// envelope, err := makeEnvelop( +// payload, +// symKey, +// publicKey, +// api.service.nodeID, +// shh.MinPow(), +// now, +// ) +// if err != nil { +// return nil, err +// } +// hash := envelope.Hash() + +// if !r.Force { +// err = api.service.requestsRegistry.Register(hash, r.Topics) +// if err != nil { +// return nil, err +// } +// } + +// if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, r.Timeout*time.Second); err != nil { +// if !r.Force { +// api.service.requestsRegistry.Unregister(hash) +// } +// return nil, err +// } + +// return hash[:], nil +// } + +// // createSyncMailRequest creates SyncMailRequest. It uses a full bloom filter +// // if no topics are given. +// func createSyncMailRequest(r SyncMessagesRequest) (types.SyncMailRequest, error) { +// var bloom []byte +// if len(r.Topics) > 0 { +// bloom = topicsToBloom(r.Topics...) +// } else { +// bloom = types.MakeFullNodeBloom() +// } + +// cursor, err := hex.DecodeString(r.Cursor) +// if err != nil { +// return types.SyncMailRequest{}, err +// } + +// return types.SyncMailRequest{ +// Lower: r.From, +// Upper: r.To, +// Bloom: bloom, +// Limit: r.Limit, +// Cursor: cursor, +// }, nil +// } + +// func createSyncMessagesResponse(r types.SyncEventResponse) SyncMessagesResponse { +// return SyncMessagesResponse{ +// Cursor: hex.EncodeToString(r.Cursor), +// Error: r.Error, +// } +// } + +// // SyncMessages sends a request to a given MailServerPeer to sync historic messages. +// // MailServerPeers needs to be added as a trusted peer first. +// func (api *NimbusPublicAPI) SyncMessages(ctx context.Context, r SyncMessagesRequest) (SyncMessagesResponse, error) { +// log.Info("SyncMessages start", "request", r) + +// var response SyncMessagesResponse + +// mailServerEnode, err := enode.ParseV4(r.MailServerPeer) +// if err != nil { +// return response, fmt.Errorf("invalid MailServerPeer: %v", err) +// } +// mailServerID := mailServerEnode.ID().Bytes() + +// request, err := createSyncMailRequest(r) +// if err != nil { +// return response, fmt.Errorf("failed to create a sync mail request: %v", err) +// } + +// for { +// log.Info("Sending a request to sync messages", "request", request) + +// resp, err := api.service.syncMessages(ctx, mailServerID, request) +// if err != nil { +// return response, err +// } + +// log.Info("Syncing messages response", "error", resp.Error, "cursor", fmt.Sprintf("%#x", resp.Cursor)) + +// if resp.Error != "" || len(resp.Cursor) == 0 || !r.FollowCursor { +// return createSyncMessagesResponse(resp), nil +// } + +// request.Cursor = resp.Cursor +// } +// } + +// ConfirmMessagesProcessedByID is a method to confirm that messages was consumed by +// the client side. +// TODO: this is broken now as it requires dedup ID while a message hash should be used. +func (api *NimbusPublicAPI) ConfirmMessagesProcessedByID(messageConfirmations []*Metadata) error { + confirmationCount := len(messageConfirmations) + dedupIDs := make([][]byte, confirmationCount) + encryptionIDs := make([][]byte, confirmationCount) + for i, confirmation := range messageConfirmations { + dedupIDs[i] = confirmation.DedupID + encryptionIDs[i] = confirmation.EncryptionID + } + return api.service.ConfirmMessagesProcessed(encryptionIDs) +} + +// Post is used to send one-to-one for those who did not enabled device-to-device sync, +// in other words don't use PFS-enabled messages. Otherwise, SendDirectMessage is used. +// It's important to call NimbusPublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *NimbusPublicAPI) Post(ctx context.Context, newMessage types.NewMessage) (types.HexBytes, error) { + return api.publicAPI.Post(ctx, newMessage) +} + +// SendPublicMessage sends a public chat message to the underlying transport. +// Message's payload is a transit encoded message. +// It's important to call NimbusPublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *NimbusPublicAPI) SendPublicMessage(ctx context.Context, msg SendPublicMessageRPC) (types.HexBytes, error) { + chat := protocol.Chat{ + Name: msg.Chat, + } + return api.service.messenger.SendRaw(ctx, chat, msg.Payload) +} + +// SendDirectMessage sends a 1:1 chat message to the underlying transport +// Message's payload is a transit encoded message. +// It's important to call NimbusPublicAPI.afterSend() so that the client receives a signal +// with confirmation that the message left the device. +func (api *NimbusPublicAPI) SendDirectMessage(ctx context.Context, msg SendDirectMessageRPC) (types.HexBytes, error) { + chat := protocol.Chat{ + ChatType: protocol.ChatTypeOneToOne, + ID: types.EncodeHex(msg.PubKey), + } + + return api.service.messenger.SendRaw(ctx, chat, msg.Payload) +} + +func (api *NimbusPublicAPI) Join(chat protocol.Chat) error { + return api.service.messenger.Join(chat) +} + +func (api *NimbusPublicAPI) Leave(chat protocol.Chat) error { + return api.service.messenger.Leave(chat) +} + +func (api *NimbusPublicAPI) LeaveGroupChat(ctx Context, chatID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.LeaveGroupChat(ctx, chatID) +} + +func (api *NimbusPublicAPI) CreateGroupChatWithMembers(ctx Context, name string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.CreateGroupChatWithMembers(ctx, name, members) +} + +func (api *NimbusPublicAPI) AddMembersToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AddMembersToGroupChat(ctx, chatID, members) +} + +func (api *NimbusPublicAPI) RemoveMemberFromGroupChat(ctx Context, chatID string, member string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RemoveMemberFromGroupChat(ctx, chatID, member) +} + +func (api *NimbusPublicAPI) AddAdminsToGroupChat(ctx Context, chatID string, members []string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AddAdminsToGroupChat(ctx, chatID, members) +} + +func (api *NimbusPublicAPI) ConfirmJoiningGroup(ctx context.Context, chatID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.ConfirmJoiningGroup(ctx, chatID) +} + +// func (api *NimbusPublicAPI) requestMessagesUsingPayload(request db.HistoryRequest, peer, symkeyID string, payload []byte, force bool, timeout time.Duration, topics []types.TopicType) (hash types.Hash, err error) { +// shh := api.service.w +// now := api.service.w.GetCurrentTime() + +// mailServerNode, err := api.getPeer(peer) +// if err != nil { +// return hash, fmt.Errorf("%v: %v", ErrInvalidMailServerPeer, err) +// } + +// var ( +// symKey []byte +// publicKey *ecdsa.PublicKey +// ) + +// if symkeyID != "" { +// symKey, err = shh.GetSymKey(symkeyID) +// if err != nil { +// return hash, fmt.Errorf("%v: %v", ErrInvalidSymKeyID, err) +// } +// } else { +// publicKey = mailServerNode.Pubkey() +// } + +// envelope, err := makeEnvelop( +// payload, +// symKey, +// publicKey, +// api.service.nodeID, +// shh.MinPow(), +// now, +// ) +// if err != nil { +// return hash, err +// } +// hash = envelope.Hash() + +// err = request.Replace(hash) +// if err != nil { +// return hash, err +// } + +// if !force { +// err = api.service.requestsRegistry.Register(hash, topics) +// if err != nil { +// return hash, err +// } +// } + +// if err := shh.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, timeout); err != nil { +// if !force { +// api.service.requestsRegistry.Unregister(hash) +// } +// return hash, err +// } + +// return hash, nil + +// } + +// // InitiateHistoryRequests is a stateful API for initiating history request for each topic. +// // Caller of this method needs to define only two parameters per each TopicRequest: +// // - Topic +// // - Duration in nanoseconds. Will be used to determine starting time for history request. +// // After that status-go will guarantee that request for this topic and date will be performed. +// func (api *NimbusPublicAPI) InitiateHistoryRequests(parent context.Context, request InitiateHistoryRequestParams) (rst []types.HexBytes, err error) { +// tx := api.service.storage.NewTx() +// defer func() { +// if err == nil { +// err = tx.Commit() +// } +// }() +// ctx := NewContextFromService(parent, api.service, tx) +// requests, err := api.service.historyUpdates.CreateRequests(ctx, request.Requests) +// if err != nil { +// return nil, err +// } +// var ( +// payload []byte +// hash types.Hash +// ) +// for i := range requests { +// req := requests[i] +// options := CreateTopicOptionsFromRequest(req) +// bloom := options.ToBloomFilterOption() +// payload, err = bloom.ToMessagesRequestPayload() +// if err != nil { +// return rst, err +// } +// hash, err = api.requestMessagesUsingPayload(req, request.Peer, request.SymKeyID, payload, request.Force, request.Timeout, options.Topics()) +// if err != nil { +// return rst, err +// } +// rst = append(rst, hash.Bytes()) +// } +// return rst, err +// } + +// // CompleteRequest client must mark request completed when all envelopes were processed. +// func (api *NimbusPublicAPI) CompleteRequest(parent context.Context, hex string) (err error) { +// tx := api.service.storage.NewTx() +// ctx := NewContextFromService(parent, api.service, tx) +// err = api.service.historyUpdates.UpdateFinishedRequest(ctx, types.HexToHash(hex)) +// if err == nil { +// return tx.Commit() +// } +// return err +// } + +func (api *NimbusPublicAPI) LoadFilters(parent context.Context, chats []*transport.Filter) ([]*transport.Filter, error) { + return api.service.messenger.LoadFilters(chats) +} + +func (api *NimbusPublicAPI) SaveChat(parent context.Context, chat *protocol.Chat) error { + api.log.Info("saving chat", "chat", chat) + return api.service.messenger.SaveChat(chat) +} + +func (api *NimbusPublicAPI) Chats(parent context.Context) []*protocol.Chat { + return api.service.messenger.Chats() +} + +func (api *NimbusPublicAPI) DeleteChat(parent context.Context, chatID string) error { + return api.service.messenger.DeleteChat(chatID) +} + +func (api *NimbusPublicAPI) SaveContact(parent context.Context, contact *protocol.Contact) error { + return api.service.messenger.SaveContact(contact) +} + +func (api *NimbusPublicAPI) BlockContact(parent context.Context, contact *protocol.Contact) ([]*protocol.Chat, error) { + api.log.Info("blocking contact", "contact", contact.ID) + return api.service.messenger.BlockContact(contact) +} + +func (api *NimbusPublicAPI) Contacts(parent context.Context) []*protocol.Contact { + return api.service.messenger.Contacts() +} + +func (api *NimbusPublicAPI) RemoveFilters(parent context.Context, chats []*transport.Filter) error { + return api.service.messenger.RemoveFilters(chats) +} + +// EnableInstallation enables an installation for multi-device sync. +func (api *NimbusPublicAPI) EnableInstallation(installationID string) error { + return api.service.messenger.EnableInstallation(installationID) +} + +// DisableInstallation disables an installation for multi-device sync. +func (api *NimbusPublicAPI) DisableInstallation(installationID string) error { + return api.service.messenger.DisableInstallation(installationID) +} + +// GetOurInstallations returns all the installations available given an identity +func (api *NimbusPublicAPI) GetOurInstallations() []*multidevice.Installation { + return api.service.messenger.Installations() +} + +// SetInstallationMetadata sets the metadata for our own installation +func (api *NimbusPublicAPI) SetInstallationMetadata(installationID string, data *multidevice.InstallationMetadata) error { + return api.service.messenger.SetInstallationMetadata(installationID, data) +} + +// VerifyENSNames takes a list of ensdetails and returns whether they match the public key specified +func (api *NimbusPublicAPI) VerifyENSNames(details []enstypes.ENSDetails) (map[string]enstypes.ENSResponse, error) { + return api.service.messenger.VerifyENSNames(api.service.config.VerifyENSURL, ensContractAddress, details) +} + +type ApplicationMessagesResponse struct { + Messages []*protocol.Message `json:"messages"` + Cursor string `json:"cursor"` +} + +func (api *NimbusPublicAPI) ChatMessages(chatID, cursor string, limit int) (*ApplicationMessagesResponse, error) { + messages, cursor, err := api.service.messenger.MessageByChatID(chatID, cursor, limit) + if err != nil { + return nil, err + } + + return &ApplicationMessagesResponse{ + Messages: messages, + Cursor: cursor, + }, nil +} + +func (api *NimbusPublicAPI) DeleteMessage(id string) error { + return api.service.messenger.DeleteMessage(id) +} + +func (api *NimbusPublicAPI) DeleteMessagesByChatID(id string) error { + return api.service.messenger.DeleteMessagesByChatID(id) +} + +func (api *NimbusPublicAPI) MarkMessagesSeen(chatID string, ids []string) error { + return api.service.messenger.MarkMessagesSeen(chatID, ids) +} + +func (api *NimbusPublicAPI) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error { + return api.service.messenger.UpdateMessageOutgoingStatus(id, newOutgoingStatus) +} + +func (api *NimbusPublicAPI) SendChatMessage(ctx context.Context, message *protocol.Message) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendChatMessage(ctx, message) +} + +func (api *NimbusPublicAPI) ReSendChatMessage(ctx context.Context, messageID string) error { + return api.service.messenger.ReSendChatMessage(ctx, messageID) +} + +func (api *NimbusPublicAPI) RequestTransaction(ctx context.Context, chatID, value, contract, address string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RequestTransaction(ctx, chatID, value, contract, address) +} + +func (api *NimbusPublicAPI) RequestAddressForTransaction(ctx context.Context, chatID, from, value, contract string) (*protocol.MessengerResponse, error) { + return api.service.messenger.RequestAddressForTransaction(ctx, chatID, from, value, contract) +} + +func (api *NimbusPublicAPI) DeclineRequestAddressForTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.DeclineRequestAddressForTransaction(ctx, messageID) +} + +func (api *NimbusPublicAPI) DeclineRequestTransaction(ctx context.Context, messageID string) (*protocol.MessengerResponse, error) { + return api.service.messenger.DeclineRequestTransaction(ctx, messageID) +} + +func (api *NimbusPublicAPI) AcceptRequestAddressForTransaction(ctx context.Context, messageID, address string) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptRequestAddressForTransaction(ctx, messageID, address) +} + +func (api *NimbusPublicAPI) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature types.HexBytes) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendTransaction(ctx, chatID, value, contract, transactionHash, signature) +} + +func (api *NimbusPublicAPI) AcceptRequestTransaction(ctx context.Context, transactionHash, messageID string, signature types.HexBytes) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptRequestTransaction(ctx, transactionHash, messageID, signature) +} + +func (api *NimbusPublicAPI) SendContactUpdates(ctx context.Context, name, picture string) error { + return api.service.messenger.SendContactUpdates(ctx, name, picture) +} + +func (api *NimbusPublicAPI) SendContactUpdate(ctx context.Context, contactID, name, picture string) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendContactUpdate(ctx, contactID, name, picture) +} + +func (api *NimbusPublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) { + return api.service.messenger.SendPairInstallation(ctx) +} + +func (api *NimbusPublicAPI) SyncDevices(ctx context.Context, name, picture string) error { + return api.service.messenger.SyncDevices(ctx, name, picture) +} + +// ----- +// HELPER +// ----- + +// makeEnvelop makes an envelop for a historic messages request. +// Symmetric key is used to authenticate to MailServer. +// PK is the current node ID. +// func makeEnvelop( +// payload []byte, +// symKey []byte, +// publicKey *ecdsa.PublicKey, +// nodeID *ecdsa.PrivateKey, +// pow float64, +// now time.Time, +// ) (types.Envelope, error) { +// params := whisper.MessageParams{ +// PoW: pow, +// Payload: payload, +// WorkTime: defaultWorkTime, +// Src: nodeID, +// } +// // Either symKey or public key is required. +// // This condition is verified in `message.Wrap()` method. +// if len(symKey) > 0 { +// params.KeySym = symKey +// } else if publicKey != nil { +// params.Dst = publicKey +// } +// message, err := whisper.NewSentMessage(¶ms) +// if err != nil { +// return nil, err +// } +// envelope, err := message.Wrap(¶ms, now) +// if err != nil { +// return nil, err +// } +// return nimbusbridge.NewNimbusEnvelopeWrapper(envelope), nil +// } + +// // makeMessagesRequestPayload makes a specific payload for MailServer +// // to request historic messages. +// func makeMessagesRequestPayload(r MessagesRequest) ([]byte, error) { +// cursor, err := hex.DecodeString(r.Cursor) +// if err != nil { +// return nil, fmt.Errorf("invalid cursor: %v", err) +// } + +// if len(cursor) > 0 && len(cursor) != mailserver.CursorLength { +// return nil, fmt.Errorf("invalid cursor size: expected %d but got %d", mailserver.CursorLength, len(cursor)) +// } + +// payload := mailserver.MessagesRequestPayload{ +// Lower: r.From, +// Upper: r.To, +// Bloom: createBloomFilter(r), +// Limit: r.Limit, +// Cursor: cursor, +// // Client must tell the MailServer if it supports batch responses. +// // This can be removed in the future. +// Batch: true, +// } + +// return rlp.EncodeToBytes(payload) +// } + +// func createBloomFilter(r MessagesRequest) []byte { +// if len(r.Topics) > 0 { +// return topicsToBloom(r.Topics...) +// } + +// return types.TopicToBloom(r.Topic) +// } + +// func topicsToBloom(topics ...types.TopicType) []byte { +// i := new(big.Int) +// for _, topic := range topics { +// bloom := types.TopicToBloom(topic) +// i.Or(i, new(big.Int).SetBytes(bloom[:])) +// } + +// combined := make([]byte, types.BloomFilterSize) +// data := i.Bytes() +// copy(combined[types.BloomFilterSize-len(data):], data[:]) + +// return combined +// } diff --git a/services/shhext/context.go b/services/shhext/context.go index 107506ed6..037857f68 100644 --- a/services/shhext/context.go +++ b/services/shhext/context.go @@ -23,11 +23,6 @@ var ( timeKey = NewContextKey("time") ) -// NewContextFromService creates new context instance using Service fileds directly and Storage. -func NewContextFromService(ctx context.Context, service *Service, storage db.Storage) Context { - return NewContext(ctx, service.w.GetCurrentTime, service.requestsRegistry, storage) -} - // 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)) diff --git a/services/shhext/context_geth.go b/services/shhext/context_geth.go new file mode 100644 index 000000000..e67c887ea --- /dev/null +++ b/services/shhext/context_geth.go @@ -0,0 +1,14 @@ +// +build !nimbus + +package shhext + +import ( + "context" + + "github.com/status-im/status-go/db" +) + +// NewContextFromService creates new context instance using Service fileds directly and Storage. +func NewContextFromService(ctx context.Context, service *Service, storage db.Storage) Context { + return NewContext(ctx, service.w.GetCurrentTime, service.requestsRegistry, storage) +} diff --git a/services/shhext/history.go b/services/shhext/history.go index 60ce72af5..f28665385 100644 --- a/services/shhext/history.go +++ b/services/shhext/history.go @@ -1,17 +1,9 @@ package shhext import ( - "errors" - "fmt" - "sort" - "sync" "time" - "github.com/ethereum/go-ethereum/rlp" - - "github.com/status-im/status-go/db" "github.com/status-im/status-go/eth-node/types" - "github.com/status-im/status-go/mailserver" ) const ( @@ -20,331 +12,8 @@ const ( WhisperTimeAllowance = 20 * time.Second ) -// NewHistoryUpdateReactor creates HistoryUpdateReactor instance. -func NewHistoryUpdateReactor() *HistoryUpdateReactor { - return &HistoryUpdateReactor{} -} - -// HistoryUpdateReactor responsible for tracking progress for all history requests. -// It listens for 2 events: -// - when envelope from mail server is received we will update appropriate topic on disk -// - when confirmation for request completion is received - we will set last envelope timestamp as the last timestamp -// for all TopicLists in current request. -type HistoryUpdateReactor struct { - mu sync.Mutex -} - -// UpdateFinishedRequest removes successfully finished request and updates every topic -// attached to the request. -func (reactor *HistoryUpdateReactor) UpdateFinishedRequest(ctx Context, id types.Hash) error { - reactor.mu.Lock() - defer reactor.mu.Unlock() - req, err := ctx.HistoryStore().GetRequest(id) - if err != nil { - return err - } - for i := range req.Histories() { - th := &req.Histories()[i] - th.RequestID = types.Hash{} - th.Current = th.End - th.End = time.Time{} - if err := th.Save(); err != nil { - return err - } - } - return req.Delete() -} - -// UpdateTopicHistory updates Current timestamp for the TopicHistory with a given timestamp. -func (reactor *HistoryUpdateReactor) UpdateTopicHistory(ctx Context, topic types.TopicType, timestamp time.Time) error { - reactor.mu.Lock() - defer reactor.mu.Unlock() - histories, err := ctx.HistoryStore().GetHistoriesByTopic(topic) - if err != nil { - return err - } - if len(histories) == 0 { - return fmt.Errorf("no histories for topic 0x%x", topic) - } - for i := range histories { - th := &histories[i] - // this case could happen only iff envelopes were delivered out of order - // last envelope received, request completed, then others envelopes received - // request completed, last envelope received, and then all others envelopes received - if !th.Pending() { - continue - } - if timestamp.Before(th.End) && timestamp.After(th.Current) { - th.Current = timestamp - } - err := th.Save() - if err != nil { - return err - } - } - return nil -} - // TopicRequest defines what user has to provide. type TopicRequest struct { Topic types.TopicType Duration time.Duration } - -// CreateRequests receives list of topic with desired timestamps and initiates both pending requests and requests -// that cover new topics. -func (reactor *HistoryUpdateReactor) CreateRequests(ctx Context, topicRequests []TopicRequest) ([]db.HistoryRequest, error) { - reactor.mu.Lock() - defer reactor.mu.Unlock() - seen := map[types.TopicType]struct{}{} - for i := range topicRequests { - if _, exist := seen[topicRequests[i].Topic]; exist { - return nil, errors.New("only one duration per topic is allowed") - } - seen[topicRequests[i].Topic] = struct{}{} - } - histories := map[types.TopicType]db.TopicHistory{} - for i := range topicRequests { - th, err := ctx.HistoryStore().GetHistory(topicRequests[i].Topic, topicRequests[i].Duration) - if err != nil { - return nil, err - } - histories[th.Topic] = th - } - requests, err := ctx.HistoryStore().GetAllRequests() - if err != nil { - return nil, err - } - filtered := []db.HistoryRequest{} - for i := range requests { - req := requests[i] - for _, th := range histories { - if th.Pending() { - delete(histories, th.Topic) - } - } - if !ctx.RequestRegistry().Has(req.ID) { - filtered = append(filtered, req) - } - } - adjusted, err := adjustRequestedHistories(ctx.HistoryStore(), mapToList(histories)) - if err != nil { - return nil, err - } - filtered = append(filtered, - GroupHistoriesByRequestTimespan(ctx.HistoryStore(), adjusted)...) - return RenewRequests(filtered, ctx.Time()), nil -} - -// for every history that is not included in any request check if there are other ranges with such topic in db -// if so check if they can be merged -// if not then adjust second part so that End of it will be equal to First of previous -func adjustRequestedHistories(store db.HistoryStore, histories []db.TopicHistory) ([]db.TopicHistory, error) { - adjusted := []db.TopicHistory{} - for i := range histories { - all, err := store.GetHistoriesByTopic(histories[i].Topic) - if err != nil { - return nil, err - } - th, err := adjustRequestedHistory(&histories[i], all...) - if err != nil { - return nil, err - } - if th != nil { - adjusted = append(adjusted, *th) - } - } - return adjusted, nil -} - -func adjustRequestedHistory(th *db.TopicHistory, others ...db.TopicHistory) (*db.TopicHistory, error) { - sort.Slice(others, func(i, j int) bool { - return others[i].Duration > others[j].Duration - }) - if len(others) == 1 && others[0].Duration == th.Duration { - return th, nil - } - for j := range others { - if others[j].Duration == th.Duration { - // skip instance with same duration - continue - } else if th.Duration > others[j].Duration { - if th.Current.Equal(others[j].First) { - // this condition will be reached when query for new index successfully finished - th.Current = others[j].Current - // FIXME next two db operations must be completed atomically - err := th.Save() - if err != nil { - return nil, err - } - err = others[j].Delete() - if err != nil { - return nil, err - } - } else if (others[j].First != time.Time{}) { - // select First timestamp with lowest value. if there are multiple indexes that cover such ranges: - // 6:00 - 7:00 Duration: 3h - // 7:00 - 8:00 2h - // 8:00 - 9:00 1h - // and client created new index with Duration 4h - // 4h index must have End value set to 6:00 - if (others[j].First.Before(th.End) || th.End == time.Time{}) { - th.End = others[j].First - } - } else { - // remove previous if it is covered by new one - // client created multiple indexes without any succsefully executed query - err := others[j].Delete() - if err != nil { - return nil, err - } - } - } else if th.Duration < others[j].Duration { - if !others[j].Pending() { - th = &others[j] - } else { - return nil, nil - } - } - } - return th, nil -} - -// RenewRequests re-sets current, first and end timestamps. -// Changes should not be persisted on disk in this method. -func RenewRequests(requests []db.HistoryRequest, now time.Time) []db.HistoryRequest { - zero := time.Time{} - for i := range requests { - req := requests[i] - histories := req.Histories() - for j := range histories { - history := &histories[j] - if history.Current == zero { - history.Current = now.Add(-(history.Duration)) - } - if history.First == zero { - history.First = history.Current - } - if history.End == zero { - history.End = now - } - } - } - return requests -} - -// CreateTopicOptionsFromRequest transforms histories attached to a single request to a simpler format - TopicOptions. -func CreateTopicOptionsFromRequest(req db.HistoryRequest) TopicOptions { - histories := req.Histories() - rst := make(TopicOptions, len(histories)) - for i := range histories { - history := histories[i] - rst[i] = TopicOption{ - Topic: history.Topic, - Range: Range{ - Start: uint64(history.Current.Add(-(WhisperTimeAllowance)).Unix()), - End: uint64(history.End.Unix()), - }, - } - } - return rst -} - -func mapToList(topics map[types.TopicType]db.TopicHistory) []db.TopicHistory { - rst := make([]db.TopicHistory, 0, len(topics)) - for key := range topics { - rst = append(rst, topics[key]) - } - return rst -} - -// GroupHistoriesByRequestTimespan creates requests from provided histories. -// Multiple histories will be included into the same request only if they share timespan. -func GroupHistoriesByRequestTimespan(store db.HistoryStore, histories []db.TopicHistory) []db.HistoryRequest { - requests := []db.HistoryRequest{} - for _, th := range histories { - var added bool - for i := range requests { - req := &requests[i] - histories := req.Histories() - if histories[0].SameRange(th) { - req.AddHistory(th) - added = true - } - } - if !added { - req := store.NewRequest() - req.AddHistory(th) - requests = append(requests, req) - } - } - return requests -} - -// Range of the request. -type Range struct { - Start uint64 - End uint64 -} - -// TopicOption request for a single topic. -type TopicOption struct { - Topic types.TopicType - Range Range -} - -// TopicOptions is a list of topic-based requsts. -type TopicOptions []TopicOption - -// ToBloomFilterOption creates bloom filter request from a list of topics. -func (options TopicOptions) ToBloomFilterOption() BloomFilterOption { - topics := make([]types.TopicType, len(options)) - var start, end uint64 - for i := range options { - opt := options[i] - topics[i] = opt.Topic - if opt.Range.Start > start { - start = opt.Range.Start - } - if opt.Range.End > end { - end = opt.Range.End - } - } - - return BloomFilterOption{ - Range: Range{Start: start, End: end}, - Filter: topicsToBloom(topics...), - } -} - -// Topics returns list of whisper TopicType attached to each TopicOption. -func (options TopicOptions) Topics() []types.TopicType { - rst := make([]types.TopicType, len(options)) - for i := range options { - rst[i] = options[i].Topic - } - return rst -} - -// BloomFilterOption is a request based on bloom filter. -type BloomFilterOption struct { - Range Range - Filter []byte -} - -// ToMessagesRequestPayload creates mailserver.MessagesRequestPayload and encodes it to bytes using rlp. -func (filter BloomFilterOption) ToMessagesRequestPayload() ([]byte, error) { - // TODO fix this conversion. - // we start from time.Duration which is int64, then convert to uint64 for rlp-serilizability - // why uint32 here? max uint32 is smaller than max int64 - payload := mailserver.MessagesRequestPayload{ - Lower: uint32(filter.Range.Start), - Upper: uint32(filter.Range.End), - Bloom: filter.Filter, - // Client must tell the MailServer if it supports batch responses. - // This can be removed in the future. - Batch: true, - Limit: 1000, - } - return rlp.EncodeToBytes(payload) -} diff --git a/services/shhext/history_geth.go b/services/shhext/history_geth.go new file mode 100644 index 000000000..0a21d8152 --- /dev/null +++ b/services/shhext/history_geth.go @@ -0,0 +1,340 @@ +// +build !nimbus + +package shhext + +import ( + "errors" + "fmt" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/rlp" + + "github.com/status-im/status-go/db" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/mailserver" +) + +// NewHistoryUpdateReactor creates HistoryUpdateReactor instance. +func NewHistoryUpdateReactor() *HistoryUpdateReactor { + return &HistoryUpdateReactor{} +} + +// HistoryUpdateReactor responsible for tracking progress for all history requests. +// It listens for 2 events: +// - when envelope from mail server is received we will update appropriate topic on disk +// - when confirmation for request completion is received - we will set last envelope timestamp as the last timestamp +// for all TopicLists in current request. +type HistoryUpdateReactor struct { + mu sync.Mutex +} + +// UpdateFinishedRequest removes successfully finished request and updates every topic +// attached to the request. +func (reactor *HistoryUpdateReactor) UpdateFinishedRequest(ctx Context, id types.Hash) error { + reactor.mu.Lock() + defer reactor.mu.Unlock() + req, err := ctx.HistoryStore().GetRequest(id) + if err != nil { + return err + } + for i := range req.Histories() { + th := &req.Histories()[i] + th.RequestID = types.Hash{} + th.Current = th.End + th.End = time.Time{} + if err := th.Save(); err != nil { + return err + } + } + return req.Delete() +} + +// UpdateTopicHistory updates Current timestamp for the TopicHistory with a given timestamp. +func (reactor *HistoryUpdateReactor) UpdateTopicHistory(ctx Context, topic types.TopicType, timestamp time.Time) error { + reactor.mu.Lock() + defer reactor.mu.Unlock() + histories, err := ctx.HistoryStore().GetHistoriesByTopic(topic) + if err != nil { + return err + } + if len(histories) == 0 { + return fmt.Errorf("no histories for topic 0x%x", topic) + } + for i := range histories { + th := &histories[i] + // this case could happen only iff envelopes were delivered out of order + // last envelope received, request completed, then others envelopes received + // request completed, last envelope received, and then all others envelopes received + if !th.Pending() { + continue + } + if timestamp.Before(th.End) && timestamp.After(th.Current) { + th.Current = timestamp + } + err := th.Save() + if err != nil { + return err + } + } + return nil +} + +// CreateRequests receives list of topic with desired timestamps and initiates both pending requests and requests +// that cover new topics. +func (reactor *HistoryUpdateReactor) CreateRequests(ctx Context, topicRequests []TopicRequest) ([]db.HistoryRequest, error) { + reactor.mu.Lock() + defer reactor.mu.Unlock() + seen := map[types.TopicType]struct{}{} + for i := range topicRequests { + if _, exist := seen[topicRequests[i].Topic]; exist { + return nil, errors.New("only one duration per topic is allowed") + } + seen[topicRequests[i].Topic] = struct{}{} + } + histories := map[types.TopicType]db.TopicHistory{} + for i := range topicRequests { + th, err := ctx.HistoryStore().GetHistory(topicRequests[i].Topic, topicRequests[i].Duration) + if err != nil { + return nil, err + } + histories[th.Topic] = th + } + requests, err := ctx.HistoryStore().GetAllRequests() + if err != nil { + return nil, err + } + filtered := []db.HistoryRequest{} + for i := range requests { + req := requests[i] + for _, th := range histories { + if th.Pending() { + delete(histories, th.Topic) + } + } + if !ctx.RequestRegistry().Has(req.ID) { + filtered = append(filtered, req) + } + } + adjusted, err := adjustRequestedHistories(ctx.HistoryStore(), mapToList(histories)) + if err != nil { + return nil, err + } + filtered = append(filtered, + GroupHistoriesByRequestTimespan(ctx.HistoryStore(), adjusted)...) + return RenewRequests(filtered, ctx.Time()), nil +} + +// for every history that is not included in any request check if there are other ranges with such topic in db +// if so check if they can be merged +// if not then adjust second part so that End of it will be equal to First of previous +func adjustRequestedHistories(store db.HistoryStore, histories []db.TopicHistory) ([]db.TopicHistory, error) { + adjusted := []db.TopicHistory{} + for i := range histories { + all, err := store.GetHistoriesByTopic(histories[i].Topic) + if err != nil { + return nil, err + } + th, err := adjustRequestedHistory(&histories[i], all...) + if err != nil { + return nil, err + } + if th != nil { + adjusted = append(adjusted, *th) + } + } + return adjusted, nil +} + +func adjustRequestedHistory(th *db.TopicHistory, others ...db.TopicHistory) (*db.TopicHistory, error) { + sort.Slice(others, func(i, j int) bool { + return others[i].Duration > others[j].Duration + }) + if len(others) == 1 && others[0].Duration == th.Duration { + return th, nil + } + for j := range others { + if others[j].Duration == th.Duration { + // skip instance with same duration + continue + } else if th.Duration > others[j].Duration { + if th.Current.Equal(others[j].First) { + // this condition will be reached when query for new index successfully finished + th.Current = others[j].Current + // FIXME next two db operations must be completed atomically + err := th.Save() + if err != nil { + return nil, err + } + err = others[j].Delete() + if err != nil { + return nil, err + } + } else if (others[j].First != time.Time{}) { + // select First timestamp with lowest value. if there are multiple indexes that cover such ranges: + // 6:00 - 7:00 Duration: 3h + // 7:00 - 8:00 2h + // 8:00 - 9:00 1h + // and client created new index with Duration 4h + // 4h index must have End value set to 6:00 + if (others[j].First.Before(th.End) || th.End == time.Time{}) { + th.End = others[j].First + } + } else { + // remove previous if it is covered by new one + // client created multiple indexes without any succsefully executed query + err := others[j].Delete() + if err != nil { + return nil, err + } + } + } else if th.Duration < others[j].Duration { + if !others[j].Pending() { + th = &others[j] + } else { + return nil, nil + } + } + } + return th, nil +} + +// RenewRequests re-sets current, first and end timestamps. +// Changes should not be persisted on disk in this method. +func RenewRequests(requests []db.HistoryRequest, now time.Time) []db.HistoryRequest { + zero := time.Time{} + for i := range requests { + req := requests[i] + histories := req.Histories() + for j := range histories { + history := &histories[j] + if history.Current == zero { + history.Current = now.Add(-(history.Duration)) + } + if history.First == zero { + history.First = history.Current + } + if history.End == zero { + history.End = now + } + } + } + return requests +} + +// CreateTopicOptionsFromRequest transforms histories attached to a single request to a simpler format - TopicOptions. +func CreateTopicOptionsFromRequest(req db.HistoryRequest) TopicOptions { + histories := req.Histories() + rst := make(TopicOptions, len(histories)) + for i := range histories { + history := histories[i] + rst[i] = TopicOption{ + Topic: history.Topic, + Range: Range{ + Start: uint64(history.Current.Add(-(WhisperTimeAllowance)).Unix()), + End: uint64(history.End.Unix()), + }, + } + } + return rst +} + +func mapToList(topics map[types.TopicType]db.TopicHistory) []db.TopicHistory { + rst := make([]db.TopicHistory, 0, len(topics)) + for key := range topics { + rst = append(rst, topics[key]) + } + return rst +} + +// GroupHistoriesByRequestTimespan creates requests from provided histories. +// Multiple histories will be included into the same request only if they share timespan. +func GroupHistoriesByRequestTimespan(store db.HistoryStore, histories []db.TopicHistory) []db.HistoryRequest { + requests := []db.HistoryRequest{} + for _, th := range histories { + var added bool + for i := range requests { + req := &requests[i] + histories := req.Histories() + if histories[0].SameRange(th) { + req.AddHistory(th) + added = true + } + } + if !added { + req := store.NewRequest() + req.AddHistory(th) + requests = append(requests, req) + } + } + return requests +} + +// Range of the request. +type Range struct { + Start uint64 + End uint64 +} + +// TopicOption request for a single topic. +type TopicOption struct { + Topic types.TopicType + Range Range +} + +// TopicOptions is a list of topic-based requsts. +type TopicOptions []TopicOption + +// ToBloomFilterOption creates bloom filter request from a list of topics. +func (options TopicOptions) ToBloomFilterOption() BloomFilterOption { + topics := make([]types.TopicType, len(options)) + var start, end uint64 + for i := range options { + opt := options[i] + topics[i] = opt.Topic + if opt.Range.Start > start { + start = opt.Range.Start + } + if opt.Range.End > end { + end = opt.Range.End + } + } + + return BloomFilterOption{ + Range: Range{Start: start, End: end}, + Filter: topicsToBloom(topics...), + } +} + +// Topics returns list of whisper TopicType attached to each TopicOption. +func (options TopicOptions) Topics() []types.TopicType { + rst := make([]types.TopicType, len(options)) + for i := range options { + rst[i] = options[i].Topic + } + return rst +} + +// BloomFilterOption is a request based on bloom filter. +type BloomFilterOption struct { + Range Range + Filter []byte +} + +// ToMessagesRequestPayload creates mailserver.MessagesRequestPayload and encodes it to bytes using rlp. +func (filter BloomFilterOption) ToMessagesRequestPayload() ([]byte, error) { + // TODO fix this conversion. + // we start from time.Duration which is int64, then convert to uint64 for rlp-serilizability + // why uint32 here? max uint32 is smaller than max int64 + payload := mailserver.MessagesRequestPayload{ + Lower: uint32(filter.Range.Start), + Upper: uint32(filter.Range.End), + Bloom: filter.Filter, + // Client must tell the MailServer if it supports batch responses. + // This can be removed in the future. + Batch: true, + Limit: 1000, + } + return rlp.EncodeToBytes(payload) +} diff --git a/services/shhext/history_test.go b/services/shhext/history_geth_test.go similarity index 99% rename from services/shhext/history_test.go rename to services/shhext/history_geth_test.go index 87b34a6d7..27c6f6a42 100644 --- a/services/shhext/history_test.go +++ b/services/shhext/history_geth_test.go @@ -1,3 +1,5 @@ +// +build !nimbus + package shhext import ( diff --git a/services/shhext/mailrequests.go b/services/shhext/mailrequests.go index 3c06e243f..c2a8819a9 100644 --- a/services/shhext/mailrequests.go +++ b/services/shhext/mailrequests.go @@ -1,3 +1,5 @@ +// +build !nimbus + package shhext import ( diff --git a/services/shhext/mailrequests_test.go b/services/shhext/mailrequests_test.go index f8ac4cf4b..b6901b860 100644 --- a/services/shhext/mailrequests_test.go +++ b/services/shhext/mailrequests_test.go @@ -1,3 +1,5 @@ +// +build !nimbus + package shhext import ( diff --git a/services/shhext/service.go b/services/shhext/service.go index 8a61cf2c0..0449f8c9b 100644 --- a/services/shhext/service.go +++ b/services/shhext/service.go @@ -1,3 +1,5 @@ +// +build !nimbus + package shhext import ( diff --git a/services/shhext/service_nimbus.go b/services/shhext/service_nimbus.go new file mode 100644 index 000000000..452dc93ec --- /dev/null +++ b/services/shhext/service_nimbus.go @@ -0,0 +1,448 @@ +// +build nimbus + +package shhext + +import ( + "context" + "crypto/ecdsa" + "database/sql" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/status-im/status-go/logutils" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/status-im/status-go/db" + "github.com/status-im/status-go/params" + nimbussvc "github.com/status-im/status-go/services/nimbus" + "github.com/status-im/status-go/signal" + + "github.com/syndtr/goleveldb/leveldb" + "go.uber.org/zap" + + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol" + "github.com/status-im/status-go/protocol/transport" +) + +const ( + // defaultConnectionsTarget used in Service.Start if configured connection target is 0. + defaultConnectionsTarget = 1 + // defaultTimeoutWaitAdded is a timeout to use to establish initial connections. + defaultTimeoutWaitAdded = 5 * time.Second +) + +// 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) +} + +// NimbusService is a service that provides some additional Whisper API. +type NimbusService struct { + apiName string + messenger *protocol.Messenger + identity *ecdsa.PrivateKey + cancelMessenger chan struct{} + storage db.TransactionalStorage + n types.Node + w types.Whisper + config params.ShhextConfig + // mailMonitor *MailRequestMonitor + // requestsRegistry *RequestsRegistry + // historyUpdates *HistoryUpdateReactor + // server *p2p.Server + nodeID *ecdsa.PrivateKey + // peerStore *mailservers.PeerStore + // cache *mailservers.Cache + // connManager *mailservers.ConnectionManager + // lastUsedMonitor *mailservers.LastUsedConnectionMonitor + // accountsDB *accounts.Database +} + +// Make sure that NimbusService implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*NimbusService)(nil) + +// NewNimbus returns a new shhext NimbusService. +func NewNimbus(n types.Node, ctx interface{}, apiName string, ldb *leveldb.DB, config params.ShhextConfig) *NimbusService { + w, err := n.GetWhisper(ctx) + if err != nil { + panic(err) + } + // cache := mailservers.NewCache(ldb) + // ps := mailservers.NewPeerStore(cache) + // delay := defaultRequestsDelay + // if config.RequestsDelay != 0 { + // delay = config.RequestsDelay + // } + // requestsRegistry := NewRequestsRegistry(delay) + // historyUpdates := NewHistoryUpdateReactor() + // mailMonitor := &MailRequestMonitor{ + // w: w, + // handler: handler, + // cache: map[types.Hash]EnvelopeState{}, + // requestsRegistry: requestsRegistry, + // } + return &NimbusService{ + apiName: apiName, + storage: db.NewLevelDBStorage(ldb), + n: n, + w: w, + config: config, + // mailMonitor: mailMonitor, + // requestsRegistry: requestsRegistry, + // historyUpdates: historyUpdates, + // peerStore: ps, + // cache: cache, + } +} + +func (s *NimbusService) InitProtocol(identity *ecdsa.PrivateKey, db *sql.DB) error { // nolint: gocyclo + if !s.config.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.BackupDisabledDataDir) + + if err := os.MkdirAll(dataDir, os.ModePerm); err != nil { + return err + } + + // Create a custom zap.Logger which will forward logs from status-go/protocol to status-go logger. + zapLogger, err := logutils.NewZapLoggerWithAdapter(logutils.Logger()) + if err != nil { + return err + } + + // envelopesMonitorConfig := &protocolwhisper.EnvelopesMonitorConfig{ + // MaxAttempts: s.config.MaxMessageDeliveryAttempts, + // MailserverConfirmationsEnabled: s.config.MailServerConfirmations, + // IsMailserver: func(peer types.EnodeID) bool { + // return s.peerStore.Exist(peer) + // }, + // EnvelopeEventsHandler: EnvelopeSignalHandler{}, + // Logger: zapLogger, + // } + options := buildMessengerOptions(s.config, db, nil, zapLogger) + + messenger, err := protocol.NewMessenger( + identity, + s.n, + s.config.InstallationID, + options..., + ) + if err != nil { + return err + } + // s.accountsDB = accounts.NewDB(db) + s.messenger = messenger + // Start a loop that retrieves all messages and propagates them to status-react. + s.cancelMessenger = make(chan struct{}) + go s.retrieveMessagesLoop(time.Second, s.cancelMessenger) + // go s.verifyTransactionLoop(30*time.Second, s.cancelMessenger) + + return s.messenger.Init() +} + +func (s *NimbusService) retrieveMessagesLoop(tick time.Duration, cancel <-chan struct{}) { + ticker := time.NewTicker(tick) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + response, err := s.messenger.RetrieveAll() + if err != nil { + log.Error("failed to retrieve raw messages", "err", err) + continue + } + if !response.IsEmpty() { + PublisherSignalHandler{}.NewMessages(response) + } + case <-cancel: + return + } + } +} + +// type verifyTransactionClient struct { +// chainID *big.Int +// url string +// } + +// func (c *verifyTransactionClient) TransactionByHash(ctx context.Context, hash types.Hash) (coretypes.Message, bool, error) { +// signer := gethtypes.NewEIP155Signer(c.chainID) +// client, err := ethclient.Dial(c.url) +// if err != nil { +// return coretypes.Message{}, false, err +// } + +// transaction, pending, err := client.TransactionByHash(ctx, commongethtypes.BytesToHash(hash.Bytes())) +// if err != nil { +// return coretypes.Message{}, false, err +// } + +// message, err := transaction.AsMessage(signer) +// if err != nil { +// return coretypes.Message{}, false, err +// } +// from := types.BytesToAddress(message.From().Bytes()) +// to := types.BytesToAddress(message.To().Bytes()) + +// return coretypes.NewMessage( +// from, +// &to, +// message.Nonce(), +// message.Value(), +// message.Gas(), +// message.GasPrice(), +// message.Data(), +// message.CheckNonce(), +// ), pending, nil +// } + +// func (s *Service) verifyTransactionLoop(tick time.Duration, cancel <-chan struct{}) { +// if s.config.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.GetAccounts() +// if err != nil { +// log.Error("failed to retrieve accounts", "err", err) +// } +// var wallets []types.Address +// for _, account := range accounts { +// if account.Wallet { +// 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 +// } +// if !response.IsEmpty() { +// PublisherSignalHandler{}.NewMessages(response) +// } +// case <-cancel: +// cancelVerifyTransaction() +// return +// } +// } +// } + +func (s *NimbusService) ConfirmMessagesProcessed(messageIDs [][]byte) error { + return s.messenger.ConfirmMessagesProcessed(messageIDs) +} + +func (s *NimbusService) EnableInstallation(installationID string) error { + return s.messenger.EnableInstallation(installationID) +} + +// DisableInstallation disables an installation for multi-device sync. +func (s *NimbusService) DisableInstallation(installationID string) error { + return s.messenger.DisableInstallation(installationID) +} + +// UpdateMailservers updates information about selected mail servers. +// func (s *NimbusService) UpdateMailservers(nodes []*enode.Node) error { +// // if err := s.peerStore.Update(nodes); err != nil { +// // return err +// // } +// // if s.connManager != nil { +// // s.connManager.Notify(nodes) +// // } +// return nil +// } + +// APIs returns a list of new APIs. +func (s *NimbusService) APIs() []rpc.API { + apis := []rpc.API{ + { + Namespace: s.apiName, + Version: "1.0", + Service: NewNimbusPublicAPI(s), + Public: true, + }, + } + return apis +} + +// Start is run when a service is started. +// It does nothing in this case but is required by `node.NimbusService` interface. +func (s *NimbusService) StartService() error { + if s.config.EnableConnectionManager { + // connectionsTarget := s.config.ConnectionTarget + // if connectionsTarget == 0 { + // connectionsTarget = defaultConnectionsTarget + // } + // maxFailures := s.config.MaxServerFailures + // // if not defined change server on first expired event + // if maxFailures == 0 { + // maxFailures = 1 + // } + // s.connManager = mailservers.NewConnectionManager(server, s.w, connectionsTarget, maxFailures, defaultTimeoutWaitAdded) + // s.connManager.Start() + // if err := mailservers.EnsureUsedRecordsAddedFirst(s.peerStore, s.connManager); err != nil { + // return err + // } + } + if s.config.EnableLastUsedMonitor { + // s.lastUsedMonitor = mailservers.NewLastUsedConnectionMonitor(s.peerStore, s.cache, s.w) + // s.lastUsedMonitor.Start() + } + // s.mailMonitor.Start() + // s.nodeID = server.PrivateKey + // s.server = server + return nil +} + +// Stop is run when a service is stopped. +func (s *NimbusService) Stop() error { + log.Info("Stopping shhext service") + // if s.config.EnableConnectionManager { + // s.connManager.Stop() + // } + // if s.config.EnableLastUsedMonitor { + // s.lastUsedMonitor.Stop() + // } + // s.requestsRegistry.Clear() + // s.mailMonitor.Stop() + + 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 { + return err + } + } + + return nil +} + +func (s *NimbusService) syncMessages(ctx context.Context, mailServerID []byte, r types.SyncMailRequest) (resp types.SyncEventResponse, err error) { + err = s.w.SyncMessages(mailServerID, r) + if err != nil { + return + } + + // Wait for the response which is received asynchronously as a p2p packet. + // This packet handler will send an event which contains the response payload. + events := make(chan types.EnvelopeEvent, 1024) + sub := s.w.SubscribeEnvelopeEvents(events) + defer sub.Unsubscribe() + + // Add explicit timeout context, otherwise the request + // can hang indefinitely if not specified by the sender. + // Sender is usually through netcat or some bash tool + // so it's not really possible to specify the timeout. + timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*30) + defer cancel() + + for { + select { + case event := <-events: + if event.Event != types.EventMailServerSyncFinished { + continue + } + + log.Info("received EventMailServerSyncFinished event", "data", event.Data) + + var ok bool + + resp, ok = event.Data.(types.SyncEventResponse) + if !ok { + err = fmt.Errorf("did not understand the response event data") + return + } + return + case <-timeoutCtx.Done(): + err = timeoutCtx.Err() + return + } + } +} + +func onNegotiatedFilters(filters []*transport.Filter) { + var signalFilters []*signal.Filter + for _, filter := range filters { + + signalFilter := &signal.Filter{ + ChatID: filter.ChatID, + SymKeyID: filter.SymKeyID, + Listen: filter.Listen, + FilterID: filter.FilterID, + Identity: filter.Identity, + Topic: filter.Topic, + } + + signalFilters = append(signalFilters, signalFilter) + } + if len(filters) != 0 { + handler := PublisherSignalHandler{} + handler.WhisperFilterAdded(signalFilters) + } +} + +func buildMessengerOptions( + config params.ShhextConfig, + db *sql.DB, + envelopesMonitorConfig *transport.EnvelopesMonitorConfig, + logger *zap.Logger, +) []protocol.Option { + options := []protocol.Option{ + protocol.WithCustomLogger(logger), + protocol.WithDatabase(db), + //protocol.WithEnvelopesMonitorConfig(envelopesMonitorConfig), + protocol.WithOnNegotiatedFilters(onNegotiatedFilters), + } + + if config.DataSyncEnabled { + options = append(options, protocol.WithDatasync()) + } + + // if config.VerifyTransactionURL != "" { + // client := &verifyTransactionClient{ + // url: config.VerifyTransactionURL, + // chainID: big.NewInt(config.VerifyTransactionChainID), + // } + // options = append(options, protocol.WithVerifyTransactionClient(client)) + // } + + return options +} diff --git a/services/status/service_nimbus.go b/services/status/service_nimbus.go new file mode 100644 index 000000000..f2a2f27c3 --- /dev/null +++ b/services/status/service_nimbus.go @@ -0,0 +1,16 @@ +// +build nimbus + +package status + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that Service implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*Service)(nil) + +// StartService is run when a service is started. +// It does nothing in this case but is required by `nimbussvc.Service` interface. +func (s *Service) StartService() error { + return nil +} diff --git a/services/subscriptions/service.go b/services/subscriptions/service.go index db9e62469..25a7927a2 100644 --- a/services/subscriptions/service.go +++ b/services/subscriptions/service.go @@ -8,7 +8,7 @@ import ( "github.com/status-im/status-go/node" ) -// Make sure that Service implements node.Service interface. +// Make sure that Service implements gethnode.Service interface. var _ gethnode.Service = (*Service)(nil) // Service represents our own implementation of personal sign operations. diff --git a/services/subscriptions/service_nimbus.go b/services/subscriptions/service_nimbus.go new file mode 100644 index 000000000..2abaab6da --- /dev/null +++ b/services/subscriptions/service_nimbus.go @@ -0,0 +1,15 @@ +// +build nimbus + +package subscriptions + +import ( + nimbussvc "github.com/status-im/status-go/services/nimbus" +) + +// Make sure that Service implements nimbussvc.Service interface. +var _ nimbussvc.Service = (*Service)(nil) + +// StartService is run when a service is started. +func (s *Service) StartService() error { + return nil +} diff --git a/timesource/timesource.go b/timesource/timesource.go index 50195db9b..639999272 100644 --- a/timesource/timesource.go +++ b/timesource/timesource.go @@ -189,6 +189,10 @@ func (s *NTPTimeSource) runPeriodically(fn func() error) error { return nil } +func (s *NTPTimeSource) StartService() error { + return s.runPeriodically(s.updateOffset) +} + // Start runs a goroutine that updates local offset every updatePeriod. func (s *NTPTimeSource) Start(*p2p.Server) error { return s.runPeriodically(s.updateOffset) diff --git a/vendor/github.com/mattn/go-pointer/LICENSE b/vendor/github.com/mattn/go-pointer/LICENSE new file mode 100644 index 000000000..5794eddcd --- /dev/null +++ b/vendor/github.com/mattn/go-pointer/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-pointer/README.md b/vendor/github.com/mattn/go-pointer/README.md new file mode 100644 index 000000000..c74eee22a --- /dev/null +++ b/vendor/github.com/mattn/go-pointer/README.md @@ -0,0 +1,29 @@ +# go-pointer + +Utility for cgo + +## Usage + +https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md + +In go 1.6, cgo argument can't be passed Go pointer. + +``` +var s string +C.pass_pointer(pointer.Save(&s)) +v := *(pointer.Restore(C.get_from_pointer()).(*string)) +``` + +## Installation + +``` +go get github.com/mattn/go-pointer +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-pointer/_example/callback.h b/vendor/github.com/mattn/go-pointer/_example/callback.h new file mode 100644 index 000000000..4dbed45b3 --- /dev/null +++ b/vendor/github.com/mattn/go-pointer/_example/callback.h @@ -0,0 +1,9 @@ +#include + +typedef void (*callback)(void*); + +static void call_later(int delay, callback cb, void* data) { + sleep(delay); + cb(data); +} + diff --git a/vendor/github.com/mattn/go-pointer/doc.go b/vendor/github.com/mattn/go-pointer/doc.go new file mode 100644 index 000000000..c27bd8c05 --- /dev/null +++ b/vendor/github.com/mattn/go-pointer/doc.go @@ -0,0 +1 @@ +package pointer diff --git a/vendor/github.com/mattn/go-pointer/pointer.go b/vendor/github.com/mattn/go-pointer/pointer.go new file mode 100644 index 000000000..6cdfae2ac --- /dev/null +++ b/vendor/github.com/mattn/go-pointer/pointer.go @@ -0,0 +1,57 @@ +package pointer + +// #include +import "C" +import ( + "sync" + "unsafe" +) + +var ( + mutex sync.Mutex + store = map[unsafe.Pointer]interface{}{} +) + +func Save(v interface{}) unsafe.Pointer { + if v == nil { + return nil + } + + // Generate real fake C pointer. + // This pointer will not store any data, but will bi used for indexing purposes. + // Since Go doest allow to cast dangling pointer to unsafe.Pointer, we do rally allocate one byte. + // Why we need indexing, because Go doest allow C code to store pointers to Go data. + var ptr unsafe.Pointer = C.malloc(C.size_t(1)) + if ptr == nil { + panic("can't allocate 'cgo-pointer hack index pointer': ptr == nil") + } + + mutex.Lock() + store[ptr] = v + mutex.Unlock() + + return ptr +} + +func Restore(ptr unsafe.Pointer) (v interface{}) { + if ptr == nil { + return nil + } + + mutex.Lock() + v = store[ptr] + mutex.Unlock() + return +} + +func Unref(ptr unsafe.Pointer) { + if ptr == nil { + return + } + + mutex.Lock() + delete(store, ptr) + mutex.Unlock() + + C.free(ptr) +} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go index c4666fe5f..a629ee835 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/node.go @@ -24,6 +24,10 @@ func NewNodeBridge(stack *node.Node) types.Node { return &gethNodeWrapper{stack: stack} } +func (w *gethNodeWrapper) Poll() { + // noop +} + func (w *gethNodeWrapper) NewENSVerifier(logger *zap.Logger) enstypes.ENSVerifier { return gethens.NewVerifier(logger) } diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go index 2423a3264..87f381c93 100644 --- a/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/geth/whisper.go @@ -80,6 +80,11 @@ func (w *gethWhisperWrapper) DeleteKeyPair(keyID string) bool { return w.whisper.DeleteKeyPair(keyID) } +// DeleteKeyPairs removes all cryptographic identities known to the node +func (w *gethWhisperWrapper) DeleteKeyPairs() error { + return w.whisper.DeleteKeyPairs() +} + func (w *gethWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) { return w.whisper.AddSymKeyDirect(key) } diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh new file mode 100644 index 000000000..48751f54f --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Pre-requisites: Git, Nix + +set -e + +GIT_ROOT=$(cd "${BASH_SOURCE%/*}" && git rev-parse --show-toplevel) + +# NOTE: To use a local Nimbus repository, uncomment and edit the following line +#nimbus_dir=~/src/github.com/status-im/nimbus + +target_dir="${GIT_ROOT}/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus" + +if [ -z "$nimbus_dir" ]; then + # The git ref of Nimbus to fetch and build. This should represent a commit SHA or a tag, for reproducible builds + nimbus_ref='feature/android-api' # TODO: Use a tag once + + nimbus_src='https://github.com/status-im/nimbus/' + nimbus_dir="${GIT_ROOT}/vendor/github.com/status-im/nimbus" + + trap "rm -rf $nimbus_dir" ERR INT QUIT + + # Clone nimbus repo into vendor directory, if necessary + if [ -d "$nimbus_dir" ]; then + cd $nimbus_dir && git reset --hard $nimbus_ref; cd - + else + # List fetched from vendorDeps array in https://github.com/status-im/nimbus/blob/master/nix/nimbus-wrappers.nix#L9-L12 + vendor_paths=( nim-chronicles nim-faststreams nim-json-serialization nim-chronos nim-eth nim-json nim-metrics nim-secp256k1 nim-serialization nim-stew nim-stint nimcrypto ) + vendor_path_opts="${vendor_paths[@]/#/--recurse-submodules=vendor/}" + git clone $nimbus_src --progress ${vendor_path_opts} --depth 1 -j8 -b $nimbus_ref $nimbus_dir + fi +fi + +# Build Nimbus wrappers and copy them into the Nimbus bridge in status-eth-node +build_dir=$(nix-build --pure --no-out-link -A wrappers-native $nimbus_dir/nix/default.nix) +rm -f ${target_dir}/libnimbus.* +mkdir -p ${target_dir} +cp -f ${build_dir}/include/* ${build_dir}/lib/libnimbus.so \ + ${target_dir}/ +chmod +w ${target_dir}/libnimbus.{so,h} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go new file mode 100644 index 000000000..5a1f952ad --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go @@ -0,0 +1,16 @@ +// +build nimbus + +package nimbusbridge + +/* + +#include + +// onMessageHandler gateway function +void onMessageHandler_cgo(received_message * msg, void* udata) +{ + void onMessageHandler(received_message* msg, void* udata); + onMessageHandler(msg, udata); +} +*/ +import "C" diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go new file mode 100644 index 000000000..69acb7635 --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go @@ -0,0 +1,61 @@ +// +build nimbus + +package nimbusbridge + +// https://golang.org/cmd/cgo/ + +/* +#include +#include +#include +#include +*/ +import "C" + +import ( + "unsafe" + + "github.com/status-im/status-go/eth-node/types" +) + +type nimbusFilterWrapper struct { + filter *C.filter_options + id string + own bool +} + +// NewNimbusFilterWrapper returns an object that wraps Nimbus's Filter in a types interface +func NewNimbusFilterWrapper(f *C.filter_options, id string, own bool) types.Filter { + wrapper := &nimbusFilterWrapper{ + filter: f, + id: id, + own: own, + } + return wrapper +} + +// GetNimbusFilterFrom retrieves the underlying whisper Filter struct from a wrapped Filter interface +func GetNimbusFilterFrom(f types.Filter) *C.filter_options { + return f.(*nimbusFilterWrapper).filter +} + +// ID returns the filter ID +func (w *nimbusFilterWrapper) ID() string { + return w.id +} + +// Free frees the C memory associated with the filter +func (w *nimbusFilterWrapper) Free() { + if !w.own { + panic("native filter is not owned by Go") + } + + if w.filter.privateKeyID != nil { + C.free(unsafe.Pointer(w.filter.privateKeyID)) + w.filter.privateKeyID = nil + } + if w.filter.symKeyID != nil { + C.free(unsafe.Pointer(w.filter.symKeyID)) + w.filter.symKeyID = nil + } +} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go new file mode 100644 index 000000000..1feff085d --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go @@ -0,0 +1,159 @@ +// +build nimbus + +package nimbusbridge + +// https://golang.org/cmd/cgo/ + +/* +#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR} -lnimbus -lm +#include +#include +#include +#include +*/ +import "C" +import ( + "crypto/ecdsa" + "errors" + "fmt" + "runtime" + "strconv" + "strings" + "sync" + "syscall" + "time" + "unsafe" + + "go.uber.org/zap" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + enstypes "github.com/status-im/status-go/eth-node/types/ens" +) + +type nimbusNodeWrapper struct { + mu sync.Mutex + + routineQueue *RoutineQueue + tid int + nodeStarted bool + cancelPollingChan chan struct{} + + w types.Whisper +} + +type Node interface { + types.Node + + StartNimbus(privateKey *ecdsa.PrivateKey, listenAddr string, staging bool) error + Stop() +} + +func NewNodeBridge() Node { + c := make(chan Node, 1) + go func(c chan<- Node, delay time.Duration) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + n := &nimbusNodeWrapper{ + routineQueue: NewRoutineQueue(), + tid: syscall.Gettid(), + cancelPollingChan: make(chan struct{}, 1), + } + c <- n + + for { + select { + case <-time.After(delay): + n.poll() + case <-n.cancelPollingChan: + return + } + } + }(c, 50*time.Millisecond) + + return <-c +} + +func (n *nimbusNodeWrapper) StartNimbus(privateKey *ecdsa.PrivateKey, listenAddr string, staging bool) error { + return n.routineQueue.Send(func(c chan<- callReturn) { + c <- callReturn{err: startNimbus(privateKey, listenAddr, staging)} + n.nodeStarted = true + }).err +} + +func (n *nimbusNodeWrapper) Stop() { + if n.cancelPollingChan != nil { + close(n.cancelPollingChan) + n.nodeStarted = false + n.cancelPollingChan = nil + } +} + +func (n *nimbusNodeWrapper) NewENSVerifier(_ *zap.Logger) enstypes.ENSVerifier { + panic("not implemented") +} + +func (n *nimbusNodeWrapper) GetWhisper(ctx interface{}) (types.Whisper, error) { + n.mu.Lock() + defer n.mu.Unlock() + + if n.w == nil { + n.w = NewNimbusWhisperWrapper(n.routineQueue) + } + return n.w, nil +} + +func (w *nimbusNodeWrapper) GetWaku(ctx interface{}) (types.Waku, error) { + panic("not implemented") +} + +func (n *nimbusNodeWrapper) AddPeer(url string) error { + urlC := C.CString(url) + defer C.free(unsafe.Pointer(urlC)) + if !C.nimbus_add_peer(urlC) { + return fmt.Errorf("failed to add peer: %s", url) + } + + return nil +} + +func (n *nimbusNodeWrapper) RemovePeer(url string) error { + panic("TODO: RemovePeer") +} + +func (n *nimbusNodeWrapper) poll() { + if syscall.Gettid() != n.tid { + panic("poll called from wrong thread") + } + + if n.nodeStarted { + C.nimbus_poll() + } + + n.routineQueue.HandleEvent() +} + +func startNimbus(privateKey *ecdsa.PrivateKey, listenAddr string, staging bool) error { + C.NimMain() + + if listenAddr == "" { + listenAddr = ":30304" + } + addrParts := strings.Split(listenAddr, ":") + port, err := strconv.Atoi(addrParts[len(addrParts)-1]) + if err != nil { + return fmt.Errorf("failed to parse port number from %s", listenAddr) + } + + var privateKeyC unsafe.Pointer + if privateKey != nil { + privateKeyC = C.CBytes(crypto.FromECDSA(privateKey)) + defer C.free(privateKeyC) + } + if !C.nimbus_start(C.ushort(port), true, false, 0.002, (*C.uchar)(privateKeyC), C.bool(staging)) { + return errors.New("failed to start Nimbus node") + } + + return nil +} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go new file mode 100644 index 000000000..50b37480d --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go @@ -0,0 +1,212 @@ +// +build nimbus + +package nimbusbridge + +// https://golang.org/cmd/cgo/ + +/* +#include +#include +#include +#include +*/ +import "C" + +import ( + "container/list" + "context" + "errors" + "fmt" + "sync" + "unsafe" + + "github.com/status-im/status-go/eth-node/types" +) + +type nimbusPublicWhisperAPIWrapper struct { + filterMessagesMu *sync.Mutex + filterMessages *map[string]*list.List + routineQueue *RoutineQueue +} + +// NewNimbusPublicWhisperAPIWrapper returns an object that wraps Nimbus's PublicWhisperAPI in a types interface +func NewNimbusPublicWhisperAPIWrapper(filterMessagesMu *sync.Mutex, filterMessages *map[string]*list.List, routineQueue *RoutineQueue) types.PublicWhisperAPI { + return &nimbusPublicWhisperAPIWrapper{ + filterMessagesMu: filterMessagesMu, + filterMessages: filterMessages, + routineQueue: routineQueue, + } +} + +// AddPrivateKey imports the given private key. +func (w *nimbusPublicWhisperAPIWrapper) AddPrivateKey(ctx context.Context, privateKey types.HexBytes) (string, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + privKeyC := C.CBytes(privateKey) + defer C.free(unsafe.Pointer(privKeyC)) + + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if C.nimbus_add_keypair((*C.uchar)(privKeyC), (*C.uchar)(idC)) { + c <- callReturn{value: types.EncodeHex(C.GoBytes(idC, C.ID_LEN))} + } else { + c <- callReturn{err: errors.New("failed to add private key to Nimbus")} + } + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +// GenerateSymKeyFromPassword derives a key from the given password, stores it, and returns its ID. +func (w *nimbusPublicWhisperAPIWrapper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + passwordC := C.CString(passwd) + defer C.free(unsafe.Pointer(passwordC)) + + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if C.nimbus_add_symkey_from_password(passwordC, (*C.uchar)(idC)) { + c <- callReturn{value: types.EncodeHex(C.GoBytes(idC, C.ID_LEN))} + } else { + c <- callReturn{err: errors.New("failed to add symkey to Nimbus")} + } + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +// DeleteKeyPair removes the key with the given key if it exists. +func (w *nimbusPublicWhisperAPIWrapper) DeleteKeyPair(ctx context.Context, key string) (bool, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + keyC, err := decodeHexID(key) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(keyC)) + + c <- callReturn{value: C.nimbus_delete_keypair(keyC)} + }) + if retVal.err != nil { + return false, retVal.err + } + + return retVal.value.(bool), nil +} + +// NewMessageFilter creates a new filter that can be used to poll for +// (new) messages that satisfy the given criteria. +func (w *nimbusPublicWhisperAPIWrapper) NewMessageFilter(req types.Criteria) (string, error) { + // topics := make([]whisper.TopicType, len(req.Topics)) + // for index, tt := range req.Topics { + // topics[index] = whisper.TopicType(tt) + // } + + // criteria := whisper.Criteria{ + // SymKeyID: req.SymKeyID, + // PrivateKeyID: req.PrivateKeyID, + // Sig: req.Sig, + // MinPow: req.MinPow, + // Topics: topics, + // AllowP2P: req.AllowP2P, + // } + // return w.publicWhisperAPI.NewMessageFilter(criteria) + // TODO + return "", errors.New("not implemented") +} + +// GetFilterMessages returns the messages that match the filter criteria and +// are received between the last poll and now. +func (w *nimbusPublicWhisperAPIWrapper) GetFilterMessages(id string) ([]*types.Message, error) { + idC := C.CString(id) + defer C.free(unsafe.Pointer(idC)) + + var ( + messageList *list.List + ok bool + ) + w.filterMessagesMu.Lock() + defer w.filterMessagesMu.Unlock() + if messageList, ok = (*w.filterMessages)[id]; !ok { + return nil, fmt.Errorf("no filter with ID %s", id) + } + + retVal := make([]*types.Message, messageList.Len()) + if messageList.Len() == 0 { + return retVal, nil + } + + elem := messageList.Front() + index := 0 + for elem != nil { + retVal[index] = (elem.Value).(*types.Message) + index++ + next := elem.Next() + messageList.Remove(elem) + elem = next + } + return retVal, nil +} + +// Post posts a message on the Whisper network. +// returns the hash of the message in case of success. +func (w *nimbusPublicWhisperAPIWrapper) Post(ctx context.Context, req types.NewMessage) ([]byte, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + msg := C.post_message{ + ttl: C.uint32_t(req.TTL), + powTime: C.double(req.PowTime), + powTarget: C.double(req.PowTarget), + } + if req.SigID != "" { + sourceID, err := decodeHexID(req.SigID) + if err != nil { + c <- callReturn{err: err} + return + } + msg.sourceID = sourceID + defer C.free(unsafe.Pointer(sourceID)) + } + if req.SymKeyID != "" { + symKeyID, err := decodeHexID(req.SymKeyID) + if err != nil { + c <- callReturn{err: err} + return + } + msg.symKeyID = symKeyID + defer C.free(unsafe.Pointer(symKeyID)) + } + if req.PublicKey != nil && len(req.PublicKey) > 0 { + msg.pubKey = (*C.uchar)(C.CBytes(req.PublicKey[1:])) + defer C.free(unsafe.Pointer(msg.pubKey)) + } + msg.payloadLen = C.size_t(len(req.Payload)) + msg.payload = (*C.uchar)(C.CBytes(req.Payload)) + defer C.free(unsafe.Pointer(msg.payload)) + msg.paddingLen = C.size_t(len(req.Padding)) + msg.padding = (*C.uchar)(C.CBytes(req.Padding)) + defer C.free(unsafe.Pointer(msg.padding)) + copyTopicToCBuffer(&msg.topic[0], req.Topic[:]) + + // TODO: return envelope hash once nimbus_post is improved to return it + if C.nimbus_post(&msg) { + c <- callReturn{value: make([]byte, 0)} + return + } + c <- callReturn{err: fmt.Errorf("failed to post message symkeyid=%s pubkey=%#x topic=%#x", req.SymKeyID, req.PublicKey, req.Topic[:])} + // hashC := C.nimbus_post(&msg) + // if hashC == nil { + // return nil, errors.New("Nimbus failed to post message") + // } + // return hex.DecodeString(C.GoString(hashC)) + }) + if retVal.err != nil { + return nil, retVal.err + } + + return retVal.value.([]byte), nil +} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go new file mode 100644 index 000000000..ccf0b71f4 --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go @@ -0,0 +1,64 @@ +// +build nimbus + +package nimbusbridge + +import ( + "syscall" +) + +// RoutineQueue provides a mechanism for marshalling function calls +// so that they are run in a specific thread (the thread where +// RoutineQueue is initialized). +type RoutineQueue struct { + tid int + events chan event +} + +type callReturn struct { + value interface{} + err error +} + +// NewRoutineQueue returns a new RoutineQueue object. +func NewRoutineQueue() *RoutineQueue { + q := &RoutineQueue{ + tid: syscall.Gettid(), + events: make(chan event, 20), + } + + return q +} + +// event represents an event triggered by the user. +type event struct { + f func(chan<- callReturn) + done chan callReturn +} + +func (q *RoutineQueue) HandleEvent() { + if syscall.Gettid() != q.tid { + panic("HandleEvent called from wrong thread") + } + + select { + case ev := <-q.events: + ev.f(ev.done) + default: + return + } +} + +// Send executes the passed function. This method can be called safely from a +// goroutine in order to execute a Nimbus function. It is important to note that the +// passed function won't be executed immediately, instead it will be added to +// the user events queue. +func (q *RoutineQueue) Send(f func(chan<- callReturn)) callReturn { + ev := event{f: f, done: make(chan callReturn, 1)} + defer close(ev.done) + if syscall.Gettid() == q.tid { + f(ev.done) + return <-ev.done + } + q.events <- ev + return <-ev.done +} diff --git a/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go new file mode 100644 index 000000000..de241bf33 --- /dev/null +++ b/vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go @@ -0,0 +1,431 @@ +// +build nimbus + +package nimbusbridge + +// https://golang.org/cmd/cgo/ + +/* +#include +#include +#include +#include +void onMessageHandler_cgo(received_message* msg, void* udata); // Forward declaration. +*/ +import "C" + +import ( + "container/list" + "crypto/ecdsa" + "errors" + "fmt" + "sync" + "time" + "unsafe" + + gopointer "github.com/mattn/go-pointer" + + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" +) + +type nimbusWhisperWrapper struct { + timesource func() time.Time + filters map[string]types.Filter + filterMessagesMu sync.Mutex + filterMessages map[string]*list.List + routineQueue *RoutineQueue +} + +// NewNimbusWhisperWrapper returns an object that wraps Nimbus' Whisper in a types interface +func NewNimbusWhisperWrapper(routineQueue *RoutineQueue) types.Whisper { + return &nimbusWhisperWrapper{ + timesource: func() time.Time { return time.Now() }, + filters: map[string]types.Filter{}, + filterMessages: map[string]*list.List{}, + routineQueue: routineQueue, + } +} + +func (w *nimbusWhisperWrapper) PublicWhisperAPI() types.PublicWhisperAPI { + return NewNimbusPublicWhisperAPIWrapper(&w.filterMessagesMu, &w.filterMessages, w.routineQueue) +} + +// MinPow returns the PoW value required by this node. +func (w *nimbusWhisperWrapper) MinPow() float64 { + return w.routineQueue.Send(func(c chan<- callReturn) { + c <- callReturn{value: float64(C.nimbus_get_min_pow())} + }).value.(float64) +} + +// BloomFilter returns the aggregated bloom filter for all the topics of interest. +// The nodes are required to send only messages that match the advertised bloom filter. +// If a message does not match the bloom, it will tantamount to spam, and the peer will +// be disconnected. +func (w *nimbusWhisperWrapper) BloomFilter() []byte { + return w.routineQueue.Send(func(c chan<- callReturn) { + // Allocate a buffer for Nimbus to return the bloom filter on + dataC := C.malloc(C.size_t(C.BLOOM_LEN)) + defer C.free(unsafe.Pointer(dataC)) + + C.nimbus_get_bloom_filter((*C.uchar)(dataC)) + + // Move the returned data into a Go array + data := make([]byte, C.BLOOM_LEN) + copy(data, C.GoBytes(dataC, C.BLOOM_LEN)) + c <- callReturn{value: data} + }).value.([]byte) +} + +// GetCurrentTime returns current time. +func (w *nimbusWhisperWrapper) GetCurrentTime() time.Time { + return w.timesource() +} + +// SetTimeSource assigns a particular source of time to a whisper object. +func (w *nimbusWhisperWrapper) SetTimeSource(timesource func() time.Time) { + w.timesource = timesource +} + +func (w *nimbusWhisperWrapper) SubscribeEnvelopeEvents(eventsProxy chan<- types.EnvelopeEvent) types.Subscription { + // TODO: when mailserver support is implemented + panic("not implemented") +} + +func (w *nimbusWhisperWrapper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + idC, err := decodeHexID(id) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(idC)) + privKeyC := C.malloc(types.AesKeyLength) + defer C.free(unsafe.Pointer(privKeyC)) + + if !C.nimbus_get_private_key(idC, (*C.uchar)(privKeyC)) { + c <- callReturn{err: errors.New("failed to get private key from Nimbus")} + return + } + + pk, err := crypto.ToECDSA(C.GoBytes(privKeyC, C.PRIVKEY_LEN)) + if err != nil { + c <- callReturn{err: err} + return + } + + c <- callReturn{value: pk} + }) + if retVal.err != nil { + return nil, retVal.err + } + + return retVal.value.(*ecdsa.PrivateKey), nil +} + +// AddKeyPair imports a asymmetric private key and returns a deterministic identifier. +func (w *nimbusWhisperWrapper) AddKeyPair(key *ecdsa.PrivateKey) (string, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + privKey := crypto.FromECDSA(key) + privKeyC := C.CBytes(privKey) + defer C.free(unsafe.Pointer(privKeyC)) + + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if !C.nimbus_add_keypair((*C.uchar)(privKeyC), (*C.uchar)(idC)) { + c <- callReturn{err: errors.New("failed to add keypair to Nimbus")} + return + } + + c <- callReturn{value: types.EncodeHex(C.GoBytes(idC, C.ID_LEN))} + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +// DeleteKeyPair deletes the key with the specified ID if it exists. +func (w *nimbusWhisperWrapper) DeleteKeyPair(keyID string) bool { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + keyC, err := decodeHexID(keyID) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(keyC)) + + c <- callReturn{value: C.nimbus_delete_keypair(keyC)} + }) + if retVal.err != nil { + return false + } + + return retVal.value.(bool) +} + +// DeleteKeyPairs removes all cryptographic identities known to the node +func (w *nimbusWhisperWrapper) DeleteKeyPairs() error { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + C.nimbus_delete_keypairs() + c <- callReturn{} + }) + + return retVal.err +} + +func (w *nimbusWhisperWrapper) AddSymKeyDirect(key []byte) (string, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + keyC := C.CBytes(key) + defer C.free(unsafe.Pointer(keyC)) + + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if !C.nimbus_add_symkey((*C.uchar)(keyC), (*C.uchar)(idC)) { + c <- callReturn{err: errors.New("failed to add symkey to Nimbus")} + return + } + + c <- callReturn{value: types.EncodeHex(C.GoBytes(idC, C.ID_LEN))} + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +func (w *nimbusWhisperWrapper) AddSymKeyFromPassword(password string) (string, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + passwordC := C.CString(password) + defer C.free(unsafe.Pointer(passwordC)) + + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if C.nimbus_add_symkey_from_password(passwordC, (*C.uchar)(idC)) { + id := C.GoBytes(idC, C.ID_LEN) + c <- callReturn{value: types.EncodeHex(id)} + } else { + c <- callReturn{err: errors.New("failed to add symkey to Nimbus")} + } + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +func (w *nimbusWhisperWrapper) DeleteSymKey(id string) bool { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + idC, err := decodeHexID(id) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(idC)) + + c <- callReturn{value: C.nimbus_delete_symkey(idC)} + }) + if retVal.err != nil { + return false + } + + return retVal.value.(bool) +} + +func (w *nimbusWhisperWrapper) GetSymKey(id string) ([]byte, error) { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + idC, err := decodeHexID(id) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(idC)) + + // Allocate a buffer for Nimbus to return the symkey on + dataC := C.malloc(C.size_t(C.SYMKEY_LEN)) + defer C.free(unsafe.Pointer(dataC)) + if !C.nimbus_get_symkey(idC, (*C.uchar)(dataC)) { + c <- callReturn{err: errors.New("symkey not found")} + return + } + + c <- callReturn{value: C.GoBytes(dataC, C.SYMKEY_LEN)} + }) + if retVal.err != nil { + return nil, retVal.err + } + + return retVal.value.([]byte), nil +} + +//export onMessageHandler +func onMessageHandler(msg *C.received_message, udata unsafe.Pointer) { + messageList := (gopointer.Restore(udata)).(*list.List) + + topic := types.TopicType{} + copy(topic[:], C.GoBytes(unsafe.Pointer(&msg.topic[0]), types.TopicLength)[:types.TopicLength]) + wrappedMsg := &types.Message{ + TTL: uint32(msg.ttl), + Timestamp: uint32(msg.timestamp), + Topic: topic, + Payload: C.GoBytes(unsafe.Pointer(msg.decoded), C.int(msg.decodedLen)), + PoW: float64(msg.pow), + Hash: C.GoBytes(unsafe.Pointer(&msg.hash[0]), types.HashLength), + P2P: true, + } + if msg.source != nil { + wrappedMsg.Sig = append([]byte{0x04}, C.GoBytes(unsafe.Pointer(msg.source), types.PubKeyLength)...) + } + if msg.recipientPublicKey != nil { + wrappedMsg.Dst = append([]byte{0x04}, C.GoBytes(unsafe.Pointer(msg.recipientPublicKey), types.PubKeyLength)...) + } + + messageList.PushBack(wrappedMsg) +} + +func (w *nimbusWhisperWrapper) Subscribe(opts *types.SubscriptionOptions) (string, error) { + f, err := w.createFilterWrapper("", opts) + if err != nil { + return "", err + } + + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + // Create a message store for this filter, so we can add new messages to it from the nimbus_subscribe_filter callback + messageList := list.New() + idC := C.malloc(C.size_t(C.ID_LEN)) + defer C.free(idC) + if !C.nimbus_subscribe_filter( + GetNimbusFilterFrom(f), + (C.received_msg_handler)(unsafe.Pointer(C.onMessageHandler_cgo)), gopointer.Save(messageList), + (*C.uchar)(idC)) { + c <- callReturn{err: errors.New("failed to subscribe to filter in Nimbus")} + return + } + filterID := C.GoString((*C.char)(idC)) + + w.filterMessagesMu.Lock() + w.filterMessages[filterID] = messageList // TODO: Check if this is done too late (race condition with onMessageHandler) + w.filterMessagesMu.Unlock() + + f.(*nimbusFilterWrapper).id = filterID + + c <- callReturn{value: filterID} + }) + if retVal.err != nil { + return "", retVal.err + } + + return retVal.value.(string), nil +} + +func (w *nimbusWhisperWrapper) GetFilter(id string) types.Filter { + idC := C.CString(id) + defer C.free(unsafe.Pointer(idC)) + + panic("GetFilter not implemented") + // pFilter := C.nimbus_get_filter(idC) + // return NewNimbusFilterWrapper(pFilter, id, false) +} + +func (w *nimbusWhisperWrapper) Unsubscribe(id string) error { + retVal := w.routineQueue.Send(func(c chan<- callReturn) { + idC, err := decodeHexID(id) + if err != nil { + c <- callReturn{err: err} + return + } + defer C.free(unsafe.Pointer(idC)) + + if ok := C.nimbus_unsubscribe_filter(idC); !ok { + c <- callReturn{err: errors.New("filter not found")} + return + } + + w.filterMessagesMu.Lock() + if messageList, ok := w.filterMessages[id]; ok { + gopointer.Unref(gopointer.Save(messageList)) + delete(w.filterMessages, id) + } + w.filterMessagesMu.Unlock() + + if f, ok := w.filters[id]; ok { + f.(*nimbusFilterWrapper).Free() + delete(w.filters, id) + } + + c <- callReturn{err: nil} + }) + return retVal.err +} + +func decodeHexID(id string) (*C.uint8_t, error) { + idBytes, err := types.DecodeHex(id) + if err == nil && len(idBytes) != C.ID_LEN { + err = fmt.Errorf("ID length must be %v bytes, actual length is %v", C.ID_LEN, len(idBytes)) + } + if err != nil { + return nil, err + } + + return (*C.uint8_t)(C.CBytes(idBytes)), nil +} + +// copyTopicToCBuffer copies a Go topic buffer to a C topic buffer without allocating new memory +func copyTopicToCBuffer(dst *C.uchar, topic []byte) { + if len(topic) != types.TopicLength { + panic("invalid Whisper topic buffer size") + } + + p := (*[types.TopicLength]C.uchar)(unsafe.Pointer(dst)) + for index, b := range topic { + p[index] = C.uchar(b) + } +} + +func (w *nimbusWhisperWrapper) createFilterWrapper(id string, opts *types.SubscriptionOptions) (types.Filter, error) { + if len(opts.Topics) != 1 { + return nil, errors.New("currently only 1 topic is supported by the Nimbus bridge") + } + + filter := C.filter_options{ + minPow: C.double(opts.PoW), + allowP2P: C.int(1), + } + copyTopicToCBuffer(&filter.topic[0], opts.Topics[0]) + if opts.PrivateKeyID != "" { + if idC, err := decodeHexID(opts.PrivateKeyID); err == nil { + filter.privateKeyID = idC + } else { + return nil, err + } + } + if opts.SymKeyID != "" { + if idC, err := decodeHexID(opts.SymKeyID); err == nil { + filter.symKeyID = idC + } else { + return nil, err + } + } + + return NewNimbusFilterWrapper(&filter, id, true), nil +} + +func (w *nimbusWhisperWrapper) SendMessagesRequest(peerID []byte, r types.MessagesRequest) error { + return errors.New("not implemented") +} + +// RequestHistoricMessages sends a message with p2pRequestCode to a specific peer, +// which is known to implement MailServer interface, and is supposed to process this +// request and respond with a number of peer-to-peer messages (possibly expired), +// which are not supposed to be forwarded any further. +// The whisper protocol is agnostic of the format and contents of envelope. +func (w *nimbusWhisperWrapper) RequestHistoricMessagesWithTimeout(peerID []byte, envelope types.Envelope, timeout time.Duration) error { + return errors.New("not implemented") +} + +// SyncMessages can be sent between two Mail Servers and syncs envelopes between them. +func (w *nimbusWhisperWrapper) SyncMessages(peerID []byte, req types.SyncMailRequest) error { + return errors.New("not implemented") +} diff --git a/vendor/github.com/status-im/status-go/eth-node/types/whisper.go b/vendor/github.com/status-im/status-go/eth-node/types/whisper.go index 23529c4da..f3266bbab 100644 --- a/vendor/github.com/status-im/status-go/eth-node/types/whisper.go +++ b/vendor/github.com/status-im/status-go/eth-node/types/whisper.go @@ -31,6 +31,8 @@ type Whisper interface { AddKeyPair(key *ecdsa.PrivateKey) (string, error) // DeleteKeyPair deletes the key with the specified ID if it exists. DeleteKeyPair(keyID string) bool + // DeleteKeyPairs removes all cryptographic identities known to the node + DeleteKeyPairs() error AddSymKeyDirect(key []byte) (string, error) AddSymKeyFromPassword(password string) (string, error) DeleteSymKey(id string) bool diff --git a/vendor/golang.org/x/tools/go/analysis/doc.go b/vendor/golang.org/x/tools/go/analysis/doc.go index a2353fc88..8fa4a8531 100644 --- a/vendor/golang.org/x/tools/go/analysis/doc.go +++ b/vendor/golang.org/x/tools/go/analysis/doc.go @@ -3,6 +3,7 @@ The analysis package defines the interface between a modular static analysis and an analysis driver program. + Background A static analysis is a function that inspects a package of Go code and @@ -41,9 +42,9 @@ the go/analysis/passes/ subdirectory: package unusedresult var Analyzer = &analysis.Analyzer{ - Name: "unusedresult", - Doc: "check for unused results of calls to some functions", - Run: run, + Name: "unusedresult", + Doc: "check for unused results of calls to some functions", + Run: run, ... } @@ -51,7 +52,6 @@ the go/analysis/passes/ subdirectory: ... } - An analysis driver is a program such as vet that runs a set of analyses and prints the diagnostics that they report. The driver program must import the list of Analyzers it needs. @@ -70,51 +70,18 @@ A driver may use the name, flags, and documentation to provide on-line help that describes the analyses it performs. The doc comment contains a brief one-line summary, optionally followed by paragraphs of explanation. -The vet command, shown below, is an example of a driver that runs -multiple analyzers. It is based on the multichecker package -(see the "Standalone commands" section for details). - - $ go build golang.org/x/tools/go/analysis/cmd/vet - $ ./vet help - vet is a tool for static analysis of Go programs. - - Usage: vet [-flag] [package] - - Registered analyzers: - - asmdecl report mismatches between assembly files and Go declarations - assign check for useless assignments - atomic check for common mistakes using the sync/atomic package - ... - unusedresult check for unused results of calls to some functions - - $ ./vet help unusedresult - unusedresult: check for unused results of calls to some functions - - Analyzer flags: - - -unusedresult.funcs value - comma-separated list of functions whose results must be used (default Error,String) - -unusedresult.stringmethods value - comma-separated list of names of methods of type func() string whose results must be used - - Some functions like fmt.Errorf return a result and have no side effects, - so it is always a mistake to discard the result. This analyzer reports - calls to certain functions in which the result of the call is ignored. - - The set of functions may be controlled using flags. The Analyzer type has more fields besides those shown above: type Analyzer struct { - Name string - Doc string - Flags flag.FlagSet - Run func(*Pass) (interface{}, error) - RunDespiteErrors bool - ResultType reflect.Type - Requires []*Analyzer - FactTypes []Fact + Name string + Doc string + Flags flag.FlagSet + Run func(*Pass) (interface{}, error) + RunDespiteErrors bool + ResultType reflect.Type + Requires []*Analyzer + FactTypes []Fact } The Flags field declares a set of named (global) flag variables that @@ -154,13 +121,13 @@ package being analyzed, and provides operations to the Run function for reporting diagnostics and other information back to the driver. type Pass struct { - Fset *token.FileSet - Files []*ast.File - OtherFiles []string - Pkg *types.Package - TypesInfo *types.Info - ResultOf map[*Analyzer]interface{} - Report func(Diagnostic) + Fset *token.FileSet + Files []*ast.File + OtherFiles []string + Pkg *types.Package + TypesInfo *types.Info + ResultOf map[*Analyzer]interface{} + Report func(Diagnostic) ... } @@ -245,7 +212,7 @@ package. An Analyzer that uses facts must declare their types: var Analyzer = &analysis.Analyzer{ - Name: "printf", + Name: "printf", FactTypes: []analysis.Fact{new(isWrapper)}, ... } @@ -330,7 +297,5 @@ entirety as: A tool that provides multiple analyzers can use multichecker in a similar way, giving it the list of Analyzers. - - */ package analysis diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index 3799f8ed8..4bfe28a51 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -60,8 +60,7 @@ causes Load to run in LoadFiles mode, collecting minimal information. See the documentation for type Config for details. As noted earlier, the Config.Mode controls the amount of detail -reported about the loaded packages, with each mode returning all the data of the -previous mode with some extra added. See the documentation for type LoadMode +reported about the loaded packages. See the documentation for type LoadMode for details. Most tools should pass their command-line arguments (after any flags) diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go index 6ac3e4f5b..8c8473fd0 100644 --- a/vendor/golang.org/x/tools/go/packages/external.go +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -84,13 +84,14 @@ func findExternalDriver(cfg *Config) driver { cmd.Stdin = bytes.NewReader(req) cmd.Stdout = buf cmd.Stderr = stderr - if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { - fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) - } if err := cmd.Run(); err != nil { return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) } + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) + } + var response driverResponse if err := json.Unmarshal(buf.Bytes(), &response); err != nil { return nil, err diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index c581bce97..a9a1ba89e 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -26,7 +26,6 @@ import ( "golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/semver" - "golang.org/x/tools/internal/span" ) // debug controls verbose logging. @@ -254,12 +253,7 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu if len(pkgs) == 0 { return nil } - drivercfg := *cfg - if getGoInfo().env.modulesOn { - drivercfg.BuildFlags = append(drivercfg.BuildFlags, "-mod=readonly") - } - dr, err := driver(&drivercfg, pkgs...) - + dr, err := driver(cfg, pkgs...) if err != nil { return err } @@ -270,10 +264,7 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu if err != nil { return err } - if err := addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo); err != nil { - return err - } - return nil + return addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo) } func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error { @@ -287,42 +278,43 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) } dirResponse, err := driver(cfg, pattern) - if err != nil { + if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) { + // There was an error loading the package. Try to load the file as an ad-hoc package. + // Usually the error will appear in a returned package, but may not if we're in modules mode + // and the ad-hoc is located outside a module. var queryErr error - if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { - return err // return the original error + dirResponse, queryErr = driver(cfg, query) + if queryErr != nil { + // Return the original error if the attempt to fall back failed. + return err } - } - // `go list` can report errors for files that are not listed as part of a package's GoFiles. - // In the case of an invalid Go file, we should assume that it is part of package if only - // one package is in the response. The file may have valid contents in an overlay. - if len(dirResponse.Packages) == 1 { - pkg := dirResponse.Packages[0] - for i, err := range pkg.Errors { - s := errorSpan(err) - if !s.IsValid() { - break - } - if len(pkg.CompiledGoFiles) == 0 { - break - } - dir := filepath.Dir(pkg.CompiledGoFiles[0]) - filename := filepath.Join(dir, filepath.Base(s.URI().Filename())) - if info, err := os.Stat(filename); err != nil || info.IsDir() { - break - } - if !contains(pkg.CompiledGoFiles, filename) { - pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) - pkg.GoFiles = append(pkg.GoFiles, filename) - pkg.Errors = append(pkg.Errors[:i], pkg.Errors[i+1:]...) - } + // If we get nothing back from `go list`, try to make this file into its own ad-hoc package. + if len(dirResponse.Packages) == 0 && queryErr == nil { + dirResponse.Packages = append(dirResponse.Packages, &Package{ + ID: "command-line-arguments", + PkgPath: query, + GoFiles: []string{query}, + CompiledGoFiles: []string{query}, + Imports: make(map[string]*Package), + }) + dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") } - } - // A final attempt to construct an ad-hoc package. - if len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1 { - var queryErr error - if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { - return err // return the original error + // Special case to handle issue #33482: + // If this is a file= query for ad-hoc packages where the file only exists on an overlay, + // and exists outside of a module, add the file in for the package. + if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || + filepath.ToSlash(dirResponse.Packages[0].PkgPath) == filepath.ToSlash(query)) { + if len(dirResponse.Packages[0].GoFiles) == 0 { + filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath + // TODO(matloob): check if the file is outside of a root dir? + for path := range cfg.Overlay { + if path == filename { + dirResponse.Packages[0].Errors = nil + dirResponse.Packages[0].GoFiles = []string{path} + dirResponse.Packages[0].CompiledGoFiles = []string{path} + } + } + } } } isRoot := make(map[string]bool, len(dirResponse.Roots)) @@ -350,74 +342,6 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return nil } -// adHocPackage attempts to construct an ad-hoc package given a query that failed. -func adHocPackage(cfg *Config, driver driver, pattern, query string) (*driverResponse, error) { - // There was an error loading the package. Try to load the file as an ad-hoc package. - // Usually the error will appear in a returned package, but may not if we're in modules mode - // and the ad-hoc is located outside a module. - dirResponse, err := driver(cfg, query) - if err != nil { - return nil, err - } - // If we get nothing back from `go list`, try to make this file into its own ad-hoc package. - if len(dirResponse.Packages) == 0 && err == nil { - dirResponse.Packages = append(dirResponse.Packages, &Package{ - ID: "command-line-arguments", - PkgPath: query, - GoFiles: []string{query}, - CompiledGoFiles: []string{query}, - Imports: make(map[string]*Package), - }) - dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") - } - // Special case to handle issue #33482: - // If this is a file= query for ad-hoc packages where the file only exists on an overlay, - // and exists outside of a module, add the file in for the package. - if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || dirResponse.Packages[0].PkgPath == filepath.ToSlash(query)) { - if len(dirResponse.Packages[0].GoFiles) == 0 { - filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath - // TODO(matloob): check if the file is outside of a root dir? - for path := range cfg.Overlay { - if path == filename { - dirResponse.Packages[0].Errors = nil - dirResponse.Packages[0].GoFiles = []string{path} - dirResponse.Packages[0].CompiledGoFiles = []string{path} - } - } - } - } - return dirResponse, nil -} - -func contains(files []string, filename string) bool { - for _, f := range files { - if f == filename { - return true - } - } - return false -} - -// errorSpan attempts to parse a standard `go list` error message -// by stripping off the trailing error message. -// -// It works only on errors whose message is prefixed by colon, -// followed by a space (": "). For example: -// -// attributes.go:13:1: expected 'package', found 'type' -// -func errorSpan(err Error) span.Span { - if err.Pos == "" { - input := strings.TrimSpace(err.Msg) - msgIndex := strings.Index(input, ": ") - if msgIndex < 0 { - return span.Parse(input) - } - return span.Parse(input[:msgIndex]) - } - return span.Parse(err.Pos) -} - // modCacheRegexp splits a path in a module cache into module, module version, and package. var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) @@ -749,7 +673,7 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv // Run "go list" for complete // information on the specified packages. - buf, err := invokeGo(cfg, golistargs(cfg, words)...) + buf, err := invokeGo(cfg, "list", golistargs(cfg, words)...) if err != nil { return nil, err } @@ -881,9 +805,15 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv } if p.Error != nil { + msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. + // Address golang.org/issue/35964 by appending import stack to error message. + if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 { + msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack) + } pkg.Errors = append(pkg.Errors, Error{ - Pos: p.Error.Pos, - Msg: strings.TrimSpace(p.Error.Err), // Trim to work around golang.org/issue/32363. + Pos: p.Error.Pos, + Msg: msg, + Kind: ListError, }) } @@ -947,7 +877,7 @@ func absJoin(dir string, fileses ...[]string) (res []string) { func golistargs(cfg *Config, words []string) []string { const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo fullargs := []string{ - "list", "-e", "-json", + "-e", "-json", fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-export=%t", usesExportData(cfg)), @@ -963,10 +893,13 @@ func golistargs(cfg *Config, words []string) []string { } // invokeGo returns the stdout of a go command invocation. -func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { +func invokeGo(cfg *Config, verb string, args ...string) (*bytes.Buffer, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - cmd := exec.CommandContext(cfg.Context, "go", args...) + goArgs := []string{verb} + goArgs = append(goArgs, cfg.BuildFlags...) + goArgs = append(goArgs, args...) + cmd := exec.CommandContext(cfg.Context, "go", goArgs...) // On darwin the cwd gets resolved to the real path, which breaks anything that // expects the working directory to keep the original path, including the // go command when dealing with modules. diff --git a/vendor/golang.org/x/tools/go/packages/loadmode_string.go b/vendor/golang.org/x/tools/go/packages/loadmode_string.go new file mode 100644 index 000000000..aff94a3fe --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/loadmode_string.go @@ -0,0 +1,57 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packages + +import ( + "fmt" + "strings" +) + +var allModes = []LoadMode{ + NeedName, + NeedFiles, + NeedCompiledGoFiles, + NeedImports, + NeedDeps, + NeedExportsFile, + NeedTypes, + NeedSyntax, + NeedTypesInfo, + NeedTypesSizes, +} + +var modeStrings = []string{ + "NeedName", + "NeedFiles", + "NeedCompiledGoFiles", + "NeedImports", + "NeedDeps", + "NeedExportsFile", + "NeedTypes", + "NeedSyntax", + "NeedTypesInfo", + "NeedTypesSizes", +} + +func (mod LoadMode) String() string { + m := mod + if m == 0 { + return fmt.Sprintf("LoadMode(0)") + } + var out []string + for i, x := range allModes { + if x > m { + break + } + if (m & x) != 0 { + out = append(out, modeStrings[i]) + m = m ^ x + } + } + if m != 0 { + out = append(out, "Unknown") + } + return fmt.Sprintf("LoadMode(%s)", strings.Join(out, "|")) +} diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 050cca43a..f98a0bdca 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -160,7 +160,7 @@ type Config struct { Tests bool // Overlay provides a mapping of absolute file paths to file contents. - // If the file with the given path already exists, the parser will use the + // If the file with the given path already exists, the parser will use the // alternative file contents provided by the map. // // Overlays provide incomplete support for when a given file doesn't @@ -713,7 +713,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // which would then require that such created packages be explicitly // inserted back into the Import graph as a final step after export data loading. // The Diamond test exercises this case. - if !lpkg.needtypes { + if !lpkg.needtypes && !lpkg.needsrc { return } if !lpkg.needsrc { diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index 9a61bdbf5..d06756228 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -77,6 +77,7 @@ func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root } } +// walkDir creates a walker and starts fastwalk with this walker. func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { if _, err := os.Stat(root.Path); os.IsNotExist(err) { if opts.Debug { @@ -114,7 +115,7 @@ type walker struct { ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files. } -// init initializes the walker based on its Options. +// init initializes the walker based on its Options func (w *walker) init() { var ignoredPaths []string if w.root.Type == RootModuleCache { @@ -167,6 +168,7 @@ func (w *walker) getIgnoredDirs(path string) []string { return ignoredDirs } +// shouldSkipDir reports whether the file should be skipped or not. func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { for _, ignoredDir := range w.ignoredDirs { if os.SameFile(fi, ignoredDir) { @@ -180,6 +182,7 @@ func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { return false } +// walk walks through the given path. func (w *walker) walk(path string, typ os.FileMode) error { dir := filepath.Dir(path) if typ.IsRegular() { diff --git a/vendor/golang.org/x/tools/internal/span/parse.go b/vendor/golang.org/x/tools/internal/span/parse.go deleted file mode 100644 index b3f268a38..000000000 --- a/vendor/golang.org/x/tools/internal/span/parse.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package span - -import ( - "strconv" - "strings" - "unicode/utf8" -) - -// Parse returns the location represented by the input. -// All inputs are valid locations, as they can always be a pure filename. -// The returned span will be normalized, and thus if printed may produce a -// different string. -func Parse(input string) Span { - // :0:0#0-0:0#0 - valid := input - var hold, offset int - hadCol := false - suf := rstripSuffix(input) - if suf.sep == "#" { - offset = suf.num - suf = rstripSuffix(suf.remains) - } - if suf.sep == ":" { - valid = suf.remains - hold = suf.num - hadCol = true - suf = rstripSuffix(suf.remains) - } - switch { - case suf.sep == ":": - return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{}) - case suf.sep == "-": - // we have a span, fall out of the case to continue - default: - // separator not valid, rewind to either the : or the start - return New(NewURI(valid), NewPoint(hold, 0, offset), Point{}) - } - // only the span form can get here - // at this point we still don't know what the numbers we have mean - // if have not yet seen a : then we might have either a line or a column depending - // on whether start has a column or not - // we build an end point and will fix it later if needed - end := NewPoint(suf.num, hold, offset) - hold, offset = 0, 0 - suf = rstripSuffix(suf.remains) - if suf.sep == "#" { - offset = suf.num - suf = rstripSuffix(suf.remains) - } - if suf.sep != ":" { - // turns out we don't have a span after all, rewind - return New(NewURI(valid), end, Point{}) - } - valid = suf.remains - hold = suf.num - suf = rstripSuffix(suf.remains) - if suf.sep != ":" { - // line#offset only - return New(NewURI(valid), NewPoint(hold, 0, offset), end) - } - // we have a column, so if end only had one number, it is also the column - if !hadCol { - end = NewPoint(suf.num, end.v.Line, end.v.Offset) - } - return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end) -} - -type suffix struct { - remains string - sep string - num int -} - -func rstripSuffix(input string) suffix { - if len(input) == 0 { - return suffix{"", "", -1} - } - remains := input - num := -1 - // first see if we have a number at the end - last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' }) - if last >= 0 && last < len(remains)-1 { - number, err := strconv.ParseInt(remains[last+1:], 10, 64) - if err == nil { - num = int(number) - remains = remains[:last+1] - } - } - // now see if we have a trailing separator - r, w := utf8.DecodeLastRuneInString(remains) - if r != ':' && r != '#' && r == '#' { - return suffix{input, "", -1} - } - remains = remains[:len(remains)-w] - return suffix{remains, string(r), num} -} diff --git a/vendor/golang.org/x/tools/internal/span/span.go b/vendor/golang.org/x/tools/internal/span/span.go deleted file mode 100644 index 4d2ad0986..000000000 --- a/vendor/golang.org/x/tools/internal/span/span.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package span contains support for representing with positions and ranges in -// text files. -package span - -import ( - "encoding/json" - "fmt" - "path" -) - -// Span represents a source code range in standardized form. -type Span struct { - v span -} - -// Point represents a single point within a file. -// In general this should only be used as part of a Span, as on its own it -// does not carry enough information. -type Point struct { - v point -} - -type span struct { - URI URI `json:"uri"` - Start point `json:"start"` - End point `json:"end"` -} - -type point struct { - Line int `json:"line"` - Column int `json:"column"` - Offset int `json:"offset"` -} - -// Invalid is a span that reports false from IsValid -var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}} - -var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}} - -// Converter is the interface to an object that can convert between line:column -// and offset forms for a single file. -type Converter interface { - //ToPosition converts from an offset to a line:column pair. - ToPosition(offset int) (int, int, error) - //ToOffset converts from a line:column pair to an offset. - ToOffset(line, col int) (int, error) -} - -func New(uri URI, start Point, end Point) Span { - s := Span{v: span{URI: uri, Start: start.v, End: end.v}} - s.v.clean() - return s -} - -func NewPoint(line, col, offset int) Point { - p := Point{v: point{Line: line, Column: col, Offset: offset}} - p.v.clean() - return p -} - -func Compare(a, b Span) int { - if r := CompareURI(a.URI(), b.URI()); r != 0 { - return r - } - if r := comparePoint(a.v.Start, b.v.Start); r != 0 { - return r - } - return comparePoint(a.v.End, b.v.End) -} - -func ComparePoint(a, b Point) int { - return comparePoint(a.v, b.v) -} - -func comparePoint(a, b point) int { - if !a.hasPosition() { - if a.Offset < b.Offset { - return -1 - } - if a.Offset > b.Offset { - return 1 - } - return 0 - } - if a.Line < b.Line { - return -1 - } - if a.Line > b.Line { - return 1 - } - if a.Column < b.Column { - return -1 - } - if a.Column > b.Column { - return 1 - } - return 0 -} - -func (s Span) HasPosition() bool { return s.v.Start.hasPosition() } -func (s Span) HasOffset() bool { return s.v.Start.hasOffset() } -func (s Span) IsValid() bool { return s.v.Start.isValid() } -func (s Span) IsPoint() bool { return s.v.Start == s.v.End } -func (s Span) URI() URI { return s.v.URI } -func (s Span) Start() Point { return Point{s.v.Start} } -func (s Span) End() Point { return Point{s.v.End} } -func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } -func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } - -func (p Point) HasPosition() bool { return p.v.hasPosition() } -func (p Point) HasOffset() bool { return p.v.hasOffset() } -func (p Point) IsValid() bool { return p.v.isValid() } -func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } -func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } -func (p Point) Line() int { - if !p.v.hasPosition() { - panic(fmt.Errorf("position not set in %v", p.v)) - } - return p.v.Line -} -func (p Point) Column() int { - if !p.v.hasPosition() { - panic(fmt.Errorf("position not set in %v", p.v)) - } - return p.v.Column -} -func (p Point) Offset() int { - if !p.v.hasOffset() { - panic(fmt.Errorf("offset not set in %v", p.v)) - } - return p.v.Offset -} - -func (p point) hasPosition() bool { return p.Line > 0 } -func (p point) hasOffset() bool { return p.Offset >= 0 } -func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() } -func (p point) isZero() bool { - return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) -} - -func (s *span) clean() { - //this presumes the points are already clean - if !s.End.isValid() || (s.End == point{}) { - s.End = s.Start - } -} - -func (p *point) clean() { - if p.Line < 0 { - p.Line = 0 - } - if p.Column <= 0 { - if p.Line > 0 { - p.Column = 1 - } else { - p.Column = 0 - } - } - if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { - p.Offset = -1 - } -} - -// Format implements fmt.Formatter to print the Location in a standard form. -// The format produced is one that can be read back in using Parse. -func (s Span) Format(f fmt.State, c rune) { - fullForm := f.Flag('+') - preferOffset := f.Flag('#') - // we should always have a uri, simplify if it is file format - //TODO: make sure the end of the uri is unambiguous - uri := string(s.v.URI) - if c == 'f' { - uri = path.Base(uri) - } else if !fullForm { - uri = s.v.URI.Filename() - } - fmt.Fprint(f, uri) - if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { - return - } - // see which bits of start to write - printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) - printLine := s.HasPosition() && (fullForm || !printOffset) - printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) - fmt.Fprint(f, ":") - if printLine { - fmt.Fprintf(f, "%d", s.v.Start.Line) - } - if printColumn { - fmt.Fprintf(f, ":%d", s.v.Start.Column) - } - if printOffset { - fmt.Fprintf(f, "#%d", s.v.Start.Offset) - } - // start is written, do we need end? - if s.IsPoint() { - return - } - // we don't print the line if it did not change - printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) - fmt.Fprint(f, "-") - if printLine { - fmt.Fprintf(f, "%d", s.v.End.Line) - } - if printColumn { - if printLine { - fmt.Fprint(f, ":") - } - fmt.Fprintf(f, "%d", s.v.End.Column) - } - if printOffset { - fmt.Fprintf(f, "#%d", s.v.End.Offset) - } -} - -func (s Span) WithPosition(c Converter) (Span, error) { - if err := s.update(c, true, false); err != nil { - return Span{}, err - } - return s, nil -} - -func (s Span) WithOffset(c Converter) (Span, error) { - if err := s.update(c, false, true); err != nil { - return Span{}, err - } - return s, nil -} - -func (s Span) WithAll(c Converter) (Span, error) { - if err := s.update(c, true, true); err != nil { - return Span{}, err - } - return s, nil -} - -func (s *Span) update(c Converter, withPos, withOffset bool) error { - if !s.IsValid() { - return fmt.Errorf("cannot add information to an invalid span") - } - if withPos && !s.HasPosition() { - if err := s.v.Start.updatePosition(c); err != nil { - return err - } - if s.v.End.Offset == s.v.Start.Offset { - s.v.End = s.v.Start - } else if err := s.v.End.updatePosition(c); err != nil { - return err - } - } - if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) { - if err := s.v.Start.updateOffset(c); err != nil { - return err - } - if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column { - s.v.End.Offset = s.v.Start.Offset - } else if err := s.v.End.updateOffset(c); err != nil { - return err - } - } - return nil -} - -func (p *point) updatePosition(c Converter) error { - line, col, err := c.ToPosition(p.Offset) - if err != nil { - return err - } - p.Line = line - p.Column = col - return nil -} - -func (p *point) updateOffset(c Converter) error { - offset, err := c.ToOffset(p.Line, p.Column) - if err != nil { - return err - } - p.Offset = offset - return nil -} diff --git a/vendor/golang.org/x/tools/internal/span/token.go b/vendor/golang.org/x/tools/internal/span/token.go deleted file mode 100644 index ce44541b2..000000000 --- a/vendor/golang.org/x/tools/internal/span/token.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package span - -import ( - "fmt" - "go/token" -) - -// Range represents a source code range in token.Pos form. -// It also carries the FileSet that produced the positions, so that it is -// self contained. -type Range struct { - FileSet *token.FileSet - Start token.Pos - End token.Pos -} - -// TokenConverter is a Converter backed by a token file set and file. -// It uses the file set methods to work out the conversions, which -// makes it fast and does not require the file contents. -type TokenConverter struct { - fset *token.FileSet - file *token.File -} - -// NewRange creates a new Range from a FileSet and two positions. -// To represent a point pass a 0 as the end pos. -func NewRange(fset *token.FileSet, start, end token.Pos) Range { - return Range{ - FileSet: fset, - Start: start, - End: end, - } -} - -// NewTokenConverter returns an implementation of Converter backed by a -// token.File. -func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter { - return &TokenConverter{fset: fset, file: f} -} - -// NewContentConverter returns an implementation of Converter for the -// given file content. -func NewContentConverter(filename string, content []byte) *TokenConverter { - fset := token.NewFileSet() - f := fset.AddFile(filename, -1, len(content)) - f.SetLinesForContent(content) - return &TokenConverter{fset: fset, file: f} -} - -// IsPoint returns true if the range represents a single point. -func (r Range) IsPoint() bool { - return r.Start == r.End -} - -// Span converts a Range to a Span that represents the Range. -// It will fill in all the members of the Span, calculating the line and column -// information. -func (r Range) Span() (Span, error) { - f := r.FileSet.File(r.Start) - if f == nil { - return Span{}, fmt.Errorf("file not found in FileSet") - } - s := Span{v: span{URI: FileURI(f.Name())}} - var err error - s.v.Start.Offset, err = offset(f, r.Start) - if err != nil { - return Span{}, err - } - if r.End.IsValid() { - s.v.End.Offset, err = offset(f, r.End) - if err != nil { - return Span{}, err - } - } - s.v.Start.clean() - s.v.End.clean() - s.v.clean() - converter := NewTokenConverter(r.FileSet, f) - return s.WithPosition(converter) -} - -// offset is a copy of the Offset function in go/token, but with the adjustment -// that it does not panic on invalid positions. -func offset(f *token.File, pos token.Pos) (int, error) { - if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() { - return 0, fmt.Errorf("invalid pos") - } - return int(pos) - f.Base(), nil -} - -// Range converts a Span to a Range that represents the Span for the supplied -// File. -func (s Span) Range(converter *TokenConverter) (Range, error) { - s, err := s.WithOffset(converter) - if err != nil { - return Range{}, err - } - // go/token will panic if the offset is larger than the file's size, - // so check here to avoid panicking. - if s.Start().Offset() > converter.file.Size() { - return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size()) - } - if s.End().Offset() > converter.file.Size() { - return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size()) - } - return Range{ - FileSet: converter.fset, - Start: converter.file.Pos(s.Start().Offset()), - End: converter.file.Pos(s.End().Offset()), - }, nil -} - -func (l *TokenConverter) ToPosition(offset int) (int, int, error) { - if offset > l.file.Size() { - return 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, l.file.Size()) - } - pos := l.file.Pos(offset) - p := l.fset.Position(pos) - if offset == l.file.Size() { - return p.Line + 1, 1, nil - } - return p.Line, p.Column, nil -} - -func (l *TokenConverter) ToOffset(line, col int) (int, error) { - if line < 0 { - return -1, fmt.Errorf("line is not valid") - } - lineMax := l.file.LineCount() + 1 - if line > lineMax { - return -1, fmt.Errorf("line is beyond end of file %v", lineMax) - } else if line == lineMax { - if col > 1 { - return -1, fmt.Errorf("column is beyond end of file") - } - // at the end of the file, allowing for a trailing eol - return l.file.Size(), nil - } - pos := lineStart(l.file, line) - if !pos.IsValid() { - return -1, fmt.Errorf("line is not in file") - } - // we assume that column is in bytes here, and that the first byte of a - // line is at column 1 - pos += token.Pos(col - 1) - return offset(l.file, pos) -} diff --git a/vendor/golang.org/x/tools/internal/span/token111.go b/vendor/golang.org/x/tools/internal/span/token111.go deleted file mode 100644 index bf7a5406b..000000000 --- a/vendor/golang.org/x/tools/internal/span/token111.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.12 - -package span - -import ( - "go/token" -) - -// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go -// versions <= 1.11, we borrow logic from the analysisutil package. -// TODO(rstambler): Delete this file when we no longer support Go 1.11. -func lineStart(f *token.File, line int) token.Pos { - // Use binary search to find the start offset of this line. - - min := 0 // inclusive - max := f.Size() // exclusive - for { - offset := (min + max) / 2 - pos := f.Pos(offset) - posn := f.Position(pos) - if posn.Line == line { - return pos - (token.Pos(posn.Column) - 1) - } - - if min+1 >= max { - return token.NoPos - } - - if posn.Line < line { - min = offset - } else { - max = offset - } - } -} diff --git a/vendor/golang.org/x/tools/internal/span/token112.go b/vendor/golang.org/x/tools/internal/span/token112.go deleted file mode 100644 index 017aec9c1..000000000 --- a/vendor/golang.org/x/tools/internal/span/token112.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.12 - -package span - -import ( - "go/token" -) - -// TODO(rstambler): Delete this file when we no longer support Go 1.11. -func lineStart(f *token.File, line int) token.Pos { - return f.LineStart(line) -} diff --git a/vendor/golang.org/x/tools/internal/span/uri.go b/vendor/golang.org/x/tools/internal/span/uri.go deleted file mode 100644 index e05a9e6ef..000000000 --- a/vendor/golang.org/x/tools/internal/span/uri.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package span - -import ( - "fmt" - "net/url" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "unicode" -) - -const fileScheme = "file" - -// URI represents the full URI for a file. -type URI string - -// Filename returns the file path for the given URI. -// It is an error to call this on a URI that is not a valid filename. -func (uri URI) Filename() string { - filename, err := filename(uri) - if err != nil { - panic(err) - } - return filepath.FromSlash(filename) -} - -func filename(uri URI) (string, error) { - if uri == "" { - return "", nil - } - u, err := url.ParseRequestURI(string(uri)) - if err != nil { - return "", err - } - if u.Scheme != fileScheme { - return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri) - } - if isWindowsDriveURI(u.Path) { - u.Path = u.Path[1:] - } - return u.Path, nil -} - -// NewURI returns a span URI for the string. -// It will attempt to detect if the string is a file path or uri. -func NewURI(s string) URI { - if u, err := url.PathUnescape(s); err == nil { - s = u - } - if strings.HasPrefix(s, fileScheme+"://") { - return URI(s) - } - return FileURI(s) -} - -func CompareURI(a, b URI) int { - if equalURI(a, b) { - return 0 - } - if a < b { - return -1 - } - return 1 -} - -func equalURI(a, b URI) bool { - if a == b { - return true - } - // If we have the same URI basename, we may still have the same file URIs. - if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) { - return false - } - fa, err := filename(a) - if err != nil { - return false - } - fb, err := filename(b) - if err != nil { - return false - } - // Stat the files to check if they are equal. - infoa, err := os.Stat(filepath.FromSlash(fa)) - if err != nil { - return false - } - infob, err := os.Stat(filepath.FromSlash(fb)) - if err != nil { - return false - } - return os.SameFile(infoa, infob) -} - -// FileURI returns a span URI for the supplied file path. -// It will always have the file scheme. -func FileURI(path string) URI { - if path == "" { - return "" - } - // Handle standard library paths that contain the literal "$GOROOT". - // TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT. - const prefix = "$GOROOT" - if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) { - suffix := path[len(prefix):] - path = runtime.GOROOT() + suffix - } - if !isWindowsDrivePath(path) { - if abs, err := filepath.Abs(path); err == nil { - path = abs - } - } - // Check the file path again, in case it became absolute. - if isWindowsDrivePath(path) { - path = "/" + path - } - path = filepath.ToSlash(path) - u := url.URL{ - Scheme: fileScheme, - Path: path, - } - uri := u.String() - if unescaped, err := url.PathUnescape(uri); err == nil { - uri = unescaped - } - return URI(uri) -} - -// isWindowsDrivePath returns true if the file path is of the form used by -// Windows. We check if the path begins with a drive letter, followed by a ":". -func isWindowsDrivePath(path string) bool { - if len(path) < 4 { - return false - } - return unicode.IsLetter(rune(path[0])) && path[1] == ':' -} - -// isWindowsDriveURI returns true if the file URI is of the format used by -// Windows URIs. The url.Parse package does not specially handle Windows paths -// (see https://golang.org/issue/6027). We check if the URI path has -// a drive prefix (e.g. "/C:"). If so, we trim the leading "/". -func isWindowsDriveURI(uri string) bool { - if len(uri) < 4 { - return false - } - return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':' -} diff --git a/vendor/golang.org/x/tools/internal/span/utf16.go b/vendor/golang.org/x/tools/internal/span/utf16.go deleted file mode 100644 index 561b3fa50..000000000 --- a/vendor/golang.org/x/tools/internal/span/utf16.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package span - -import ( - "fmt" - "unicode/utf16" - "unicode/utf8" -) - -// ToUTF16Column calculates the utf16 column expressed by the point given the -// supplied file contents. -// This is used to convert from the native (always in bytes) column -// representation and the utf16 counts used by some editors. -func ToUTF16Column(p Point, content []byte) (int, error) { - if content == nil { - return -1, fmt.Errorf("ToUTF16Column: missing content") - } - if !p.HasPosition() { - return -1, fmt.Errorf("ToUTF16Column: point is missing position") - } - if !p.HasOffset() { - return -1, fmt.Errorf("ToUTF16Column: point is missing offset") - } - offset := p.Offset() // 0-based - colZero := p.Column() - 1 // 0-based - if colZero == 0 { - // 0-based column 0, so it must be chr 1 - return 1, nil - } else if colZero < 0 { - return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero) - } - // work out the offset at the start of the line using the column - lineOffset := offset - colZero - if lineOffset < 0 || offset > len(content) { - return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content)) - } - // Use the offset to pick out the line start. - // This cannot panic: offset > len(content) and lineOffset < offset. - start := content[lineOffset:] - - // Now, truncate down to the supplied column. - start = start[:colZero] - - // and count the number of utf16 characters - // in theory we could do this by hand more efficiently... - return len(utf16.Encode([]rune(string(start)))) + 1, nil -} - -// FromUTF16Column advances the point by the utf16 character offset given the -// supplied line contents. -// This is used to convert from the utf16 counts used by some editors to the -// native (always in bytes) column representation. -func FromUTF16Column(p Point, chr int, content []byte) (Point, error) { - if !p.HasOffset() { - return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset") - } - // if chr is 1 then no adjustment needed - if chr <= 1 { - return p, nil - } - if p.Offset() >= len(content) { - return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content)) - } - remains := content[p.Offset():] - // scan forward the specified number of characters - for count := 1; count < chr; count++ { - if len(remains) <= 0 { - return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content") - } - r, w := utf8.DecodeRune(remains) - if r == '\n' { - // Per the LSP spec: - // - // > If the character value is greater than the line length it - // > defaults back to the line length. - break - } - remains = remains[w:] - if r >= 0x10000 { - // a two point rune - count++ - // if we finished in a two point rune, do not advance past the first - if count >= chr { - break - } - } - p.v.Column += w - p.v.Offset += w - } - return p, nil -} diff --git a/vendor/modules.txt b/vendor/modules.txt index d4c6a3ca6..be714d0ed 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -282,6 +282,8 @@ github.com/lucasb-eyer/go-colorful github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.7 github.com/mattn/go-isatty +# github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f +github.com/mattn/go-pointer # github.com/mattn/go-runewidth v0.0.4 github.com/mattn/go-runewidth # github.com/matttproud/golang_protobuf_extensions v1.0.1 @@ -371,6 +373,7 @@ github.com/status-im/rendezvous/server # github.com/status-im/status-go/eth-node v1.1.0 => ./eth-node github.com/status-im/status-go/eth-node/bridge/geth github.com/status-im/status-go/eth-node/bridge/geth/ens +github.com/status-im/status-go/eth-node/bridge/nimbus 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/crypto/ecies @@ -541,7 +544,7 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 +# golang.org/x/tools v0.0.0-20200116062425-473961ec044c golang.org/x/tools/go/analysis golang.org/x/tools/go/analysis/passes/inspect golang.org/x/tools/go/ast/astutil @@ -556,7 +559,6 @@ golang.org/x/tools/go/types/typeutil golang.org/x/tools/internal/fastwalk golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/semver -golang.org/x/tools/internal/span # gopkg.in/go-playground/validator.v9 v9.31.0 gopkg.in/go-playground/validator.v9 # gopkg.in/natefinch/lumberjack.v2 v2.0.0