Nimbus node support
This commit is contained in:
parent
25d46c6d82
commit
6537cae606
14
Makefile
14
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
1
go.mod
1
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
|
||||
|
|
54
go.sum
54
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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// +build !nimbus
|
||||
|
||||
package statusgo
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/api"
|
||||
)
|
||||
|
||||
var statusBackend = api.NewGethStatusBackend()
|
|
@ -0,0 +1,9 @@
|
|||
// +build nimbus
|
||||
|
||||
package statusgo
|
||||
|
||||
import (
|
||||
"github.com/status-im/status-go/api"
|
||||
)
|
||||
|
||||
var statusBackend = api.NewNimbusStatusBackend()
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package node
|
||||
|
||||
import (
|
|
@ -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.
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package node
|
||||
|
||||
import (
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
// })
|
||||
// }
|
|
@ -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...)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package shhext
|
||||
|
||||
import (
|
|
@ -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
|
||||
// }
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package shhext
|
||||
|
||||
import (
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package shhext
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package shhext
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// +build !nimbus
|
||||
|
||||
package shhext
|
||||
|
||||
import (
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
|
@ -0,0 +1,9 @@
|
|||
#include <unistd.h>
|
||||
|
||||
typedef void (*callback)(void*);
|
||||
|
||||
static void call_later(int delay, callback cb, void* data) {
|
||||
sleep(delay);
|
||||
cb(data);
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package pointer
|
|
@ -0,0 +1,57 @@
|
|||
package pointer
|
||||
|
||||
// #include <stdlib.h>
|
||||
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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
40
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh
generated
vendored
Normal file
40
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/build-nimbus.sh
generated
vendored
Normal file
|
@ -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}
|
16
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go
generated
vendored
Normal file
16
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/cfuncs.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
/*
|
||||
|
||||
#include <libnimbus.h>
|
||||
|
||||
// onMessageHandler gateway function
|
||||
void onMessageHandler_cgo(received_message * msg, void* udata)
|
||||
{
|
||||
void onMessageHandler(received_message* msg, void* udata);
|
||||
onMessageHandler(msg, udata);
|
||||
}
|
||||
*/
|
||||
import "C"
|
61
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go
generated
vendored
Normal file
61
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/filter.go
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
|||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
159
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go
generated
vendored
Normal file
159
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/node.go
generated
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -Wl,-rpath,'$ORIGIN' -L${SRCDIR} -lnimbus -lm
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
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
|
||||
}
|
212
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go
generated
vendored
Normal file
212
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/public_whisper_api.go
generated
vendored
Normal file
|
@ -0,0 +1,212 @@
|
|||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
*/
|
||||
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
|
||||
}
|
64
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go
generated
vendored
Normal file
64
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/routine_queue.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
431
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go
generated
vendored
Normal file
431
vendor/github.com/status-im/status-go/eth-node/bridge/nimbus/whisper.go
generated
vendored
Normal file
|
@ -0,0 +1,431 @@
|
|||
// +build nimbus
|
||||
|
||||
package nimbusbridge
|
||||
|
||||
// https://golang.org/cmd/cgo/
|
||||
|
||||
/*
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <libnimbus.h>
|
||||
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")
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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, "|"))
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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] == ':'
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue