Nimbus node support

This commit is contained in:
Pedro Pombeiro 2019-11-27 13:22:23 +01:00 committed by Pedro Pombeiro
parent 25d46c6d82
commit 6537cae606
77 changed files with 5937 additions and 2141 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)

1027
api/nimbus_backend.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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}

View File

@ -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))

View File

@ -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)

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

9
mobile/status_geth.go Normal file
View File

@ -0,0 +1,9 @@
// +build !nimbus
package statusgo
import (
"github.com/status-im/status-go/api"
)
var statusBackend = api.NewGethStatusBackend()

9
mobile/status_nimbus.go Normal file
View File

@ -0,0 +1,9 @@
// +build nimbus
package statusgo
import (
"github.com/status-im/status-go/api"
)
var statusBackend = api.NewNimbusStatusBackend()

View File

@ -1,3 +1,5 @@
// +build !nimbus
package node
import (

View File

@ -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.

View File

@ -1,3 +1,5 @@
// +build !nimbus
package node
import (

392
node/nimbus_node.go Normal file
View File

@ -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, &ethConf)
// })
// }
// 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(&ethnode); 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(&ethnode); 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(&ethnode); 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
}
}

798
node/nimbus_status_node.go Normal file
View File

@ -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,
// })
// }

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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(&params)
if err != nil {
return nil, err
}
envelope, err := message.Wrap(&params, 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
}

676
services/shhext/api_geth.go Normal file
View File

@ -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(&params)
if err != nil {
return nil, err
}
envelope, err := message.Wrap(&params, 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
}

View File

@ -1,3 +1,5 @@
// +build !nimbus
package shhext
import (

View File

@ -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(&params)
// if err != nil {
// return nil, err
// }
// envelope, err := message.Wrap(&params, 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
// }

View File

@ -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))

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -1,3 +1,5 @@
// +build !nimbus
package shhext
import (

View File

@ -1,3 +1,5 @@
// +build !nimbus
package shhext
import (

View File

@ -1,3 +1,5 @@
// +build !nimbus
package shhext
import (

View File

@ -1,3 +1,5 @@
// +build !nimbus
package shhext
import (

View File

@ -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
}

View File

@ -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
}

View File

@ -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.

View File

@ -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
}

View File

@ -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)

21
vendor/github.com/mattn/go-pointer/LICENSE generated vendored Normal file
View File

@ -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.

29
vendor/github.com/mattn/go-pointer/README.md generated vendored Normal file
View File

@ -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)

View File

@ -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);
}

1
vendor/github.com/mattn/go-pointer/doc.go generated vendored Normal file
View File

@ -0,0 +1 @@
package pointer

57
vendor/github.com/mattn/go-pointer/pointer.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View 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}

View 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"

View 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
}
}

View 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
}

View 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
}

View 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
}

View 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")
}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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, "|"))
}

View File

@ -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 {

View File

@ -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() {

View File

@ -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}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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] == ':'
}

View File

@ -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
}

6
vendor/modules.txt vendored
View File

@ -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