Handle connection state
This commit is contained in:
parent
566e9a3ade
commit
d50fee6bb2
|
@ -97,98 +97,8 @@ func TestSubscriptionPendingTransaction(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSubscriptionWhisperEnvelopes(t *testing.T) {
|
|
||||||
backend := NewGethStatusBackend()
|
|
||||||
defer func() {
|
|
||||||
err := backend.StopNode()
|
|
||||||
if err != node.ErrNoRunningNode {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
initNodeAndLogin(t, backend)
|
|
||||||
|
|
||||||
signals := make(chan string)
|
|
||||||
defer func() {
|
|
||||||
signal.ResetDefaultNodeNotificationHandler()
|
|
||||||
close(signals)
|
|
||||||
}()
|
|
||||||
|
|
||||||
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
|
|
||||||
signals <- jsonEvent
|
|
||||||
})
|
|
||||||
|
|
||||||
topic := "0x12341234"
|
|
||||||
payload := "0x12312312"
|
|
||||||
|
|
||||||
shhGenSymKeyJSONResponse, err := backend.CallPrivateRPC(`{"jsonrpc":"2.0","method":"shh_generateSymKeyFromPassword","params":["test"],"id":119}`)
|
|
||||||
require.NoError(t, err)
|
|
||||||
symKeyID := extractResult(t, shhGenSymKeyJSONResponse)
|
|
||||||
|
|
||||||
subID := createSubscription(t, backend, fmt.Sprintf(`"shh_newMessageFilter", [{ "symKeyID": "%s", "topics": ["%s"] }]`, symKeyID, topic))
|
|
||||||
|
|
||||||
sendMessageFmt := `
|
|
||||||
{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": "shh_post",
|
|
||||||
"params": [{
|
|
||||||
"ttl": 7,
|
|
||||||
"symKeyID": "%s",
|
|
||||||
"topic": "%s",
|
|
||||||
"powTarget": 2.01,
|
|
||||||
"powTime": 2,
|
|
||||||
"payload": "%s"
|
|
||||||
}],
|
|
||||||
"id":11
|
|
||||||
}`
|
|
||||||
|
|
||||||
numberOfEnvelopes := 5
|
|
||||||
|
|
||||||
for i := 0; i < numberOfEnvelopes; i++ {
|
|
||||||
_, err = backend.CallPrivateRPC(fmt.Sprintf(sendMessageFmt, symKeyID, topic, payload))
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
total int
|
|
||||||
after = time.After(2 * time.Second)
|
|
||||||
exit bool
|
|
||||||
)
|
|
||||||
for !exit {
|
|
||||||
select {
|
|
||||||
case event := <-signals:
|
|
||||||
total += validateShhEvent(t, event, subID, topic, payload)
|
|
||||||
if total == numberOfEnvelopes {
|
|
||||||
exit = true
|
|
||||||
}
|
|
||||||
case <-after:
|
|
||||||
exit = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.Equal(t, numberOfEnvelopes, total, "total number of envelopes must be equal to sent number of envelopes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// * * * * * * * * * * utility methods below * * * * * * * * * * *
|
// * * * * * * * * * * utility methods below * * * * * * * * * * *
|
||||||
|
|
||||||
func validateShhEvent(t *testing.T, jsonEvent string, expectedSubID string, topic string, payload string) int {
|
|
||||||
result := struct {
|
|
||||||
Event signal.SubscriptionDataEvent `json:"event"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}{}
|
|
||||||
|
|
||||||
require.NoError(t, json.Unmarshal([]byte(jsonEvent), &result))
|
|
||||||
|
|
||||||
require.Equal(t, signal.EventSubscriptionsData, result.Type)
|
|
||||||
require.Equal(t, expectedSubID, result.Event.FilterID)
|
|
||||||
|
|
||||||
for _, item := range result.Event.Data {
|
|
||||||
dict := item.(map[string]interface{})
|
|
||||||
require.Equal(t, dict["topic"], topic)
|
|
||||||
require.Equal(t, dict["payload"], payload)
|
|
||||||
}
|
|
||||||
return len(result.Event.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateTxEvent(t *testing.T, expectedSubID string, jsonEvent string, txID string) {
|
func validateTxEvent(t *testing.T, expectedSubID string, jsonEvent string, txID string) {
|
||||||
result := struct {
|
result := struct {
|
||||||
Event signal.SubscriptionDataEvent `json:"event"`
|
Event signal.SubscriptionDataEvent `json:"event"`
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
gethcrypto "github.com/ethereum/go-ethereum/crypto"
|
gethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
"github.com/status-im/status-go/account"
|
"github.com/status-im/status-go/account"
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
"github.com/status-im/status-go/multiaccounts"
|
||||||
|
@ -231,7 +232,7 @@ func TestBackendAccountsConcurrently(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConnectionChangesConcurrently(t *testing.T) {
|
func TestBackendConnectionChangesConcurrently(t *testing.T) {
|
||||||
connections := [...]string{wifi, cellular, unknown}
|
connections := [...]string{connection.Wifi, connection.Cellular, connection.Unknown}
|
||||||
backend := NewGethStatusBackend()
|
backend := NewGethStatusBackend()
|
||||||
count := 3
|
count := 3
|
||||||
|
|
||||||
|
@ -251,10 +252,10 @@ func TestBackendConnectionChangesConcurrently(t *testing.T) {
|
||||||
|
|
||||||
func TestBackendConnectionChangesToOffline(t *testing.T) {
|
func TestBackendConnectionChangesToOffline(t *testing.T) {
|
||||||
b := NewGethStatusBackend()
|
b := NewGethStatusBackend()
|
||||||
b.ConnectionChange(none, false)
|
b.ConnectionChange(connection.None, false)
|
||||||
assert.True(t, b.connectionState.Offline)
|
assert.True(t, b.connectionState.Offline)
|
||||||
|
|
||||||
b.ConnectionChange(wifi, false)
|
b.ConnectionChange(connection.Wifi, false)
|
||||||
assert.False(t, b.connectionState.Offline)
|
assert.False(t, b.connectionState.Offline)
|
||||||
|
|
||||||
b.ConnectionChange("unknown-state", false)
|
b.ConnectionChange("unknown-state", false)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/status-im/status-go/account"
|
"github.com/status-im/status-go/account"
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
"github.com/status-im/status-go/appmetrics"
|
"github.com/status-im/status-go/appmetrics"
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/logutils"
|
"github.com/status-im/status-go/logutils"
|
||||||
|
@ -86,7 +87,7 @@ type GethStatusBackend struct {
|
||||||
account *multiaccounts.Account
|
account *multiaccounts.Account
|
||||||
accountManager *account.GethManager
|
accountManager *account.GethManager
|
||||||
transactor *transactions.Transactor
|
transactor *transactions.Transactor
|
||||||
connectionState connectionState
|
connectionState connection.State
|
||||||
appState appState
|
appState appState
|
||||||
selectedAccountKeyID string
|
selectedAccountKeyID string
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
@ -967,17 +968,21 @@ func (b *GethStatusBackend) ConnectionChange(typ string, expensive bool) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
state := connectionState{
|
state := connection.State{
|
||||||
Type: newConnectionType(typ),
|
Type: connection.NewConnectionType(typ),
|
||||||
Expensive: expensive,
|
Expensive: expensive,
|
||||||
}
|
}
|
||||||
if typ == none {
|
if typ == connection.None {
|
||||||
state.Offline = true
|
state.Offline = true
|
||||||
}
|
}
|
||||||
|
|
||||||
b.log.Info("Network state change", "old", b.connectionState, "new", state)
|
b.log.Info("Network state change", "old", b.connectionState, "new", state)
|
||||||
|
|
||||||
b.connectionState = state
|
b.connectionState = state
|
||||||
|
err := b.statusNode.ConnectionChanged(state)
|
||||||
|
if err != nil {
|
||||||
|
b.log.Error("failed to notify of connection changed", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
// logic of handling state changes here
|
// logic of handling state changes here
|
||||||
// restart node? force peers reconnect? etc
|
// restart node? force peers reconnect? etc
|
||||||
|
@ -1187,6 +1192,9 @@ func (b *GethStatusBackend) injectAccountsIntoServices() error {
|
||||||
if err := st.InitProtocol(identity, b.appDB, b.multiaccountsDB, acc, logutils.ZapLogger()); err != nil {
|
if err := st.InitProtocol(identity, b.appDB, b.multiaccountsDB, acc, logutils.ZapLogger()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set initial connection state
|
||||||
|
st.ConnectionChanged(b.connectionState)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
populate-db
|
|
||||||
tmp/
|
|
|
@ -1,37 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// configFlags represents an array of JSON configuration files passed to a command line utility
|
|
||||||
type configFlags []string
|
|
||||||
|
|
||||||
func (f *configFlags) String() string {
|
|
||||||
return strings.Join(*f, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *configFlags) Set(value string) error {
|
|
||||||
if !path.IsAbs(value) {
|
|
||||||
// Convert to absolute path
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value = path.Join(cwd, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the file exists
|
|
||||||
stat, err := os.Stat(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if stat.IsDir() {
|
|
||||||
return fmt.Errorf("path does not represent a file: %s", value)
|
|
||||||
}
|
|
||||||
*f = append(*f, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
// nolint: deadcode
|
|
||||||
func TestStatusFlag(t *testing.T) {
|
|
||||||
service := "status"
|
|
||||||
|
|
||||||
scenarios := []struct {
|
|
||||||
ipcEnabled bool
|
|
||||||
httpEnabled bool
|
|
||||||
flag string
|
|
||||||
err error
|
|
||||||
enabled bool
|
|
||||||
public bool
|
|
||||||
}{
|
|
||||||
// no flags
|
|
||||||
{},
|
|
||||||
// -status=ipc -ipc
|
|
||||||
{
|
|
||||||
ipcEnabled: true,
|
|
||||||
flag: "ipc",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
// -status=http -http
|
|
||||||
{
|
|
||||||
httpEnabled: true,
|
|
||||||
flag: "http",
|
|
||||||
enabled: true,
|
|
||||||
public: true,
|
|
||||||
},
|
|
||||||
// -status=ipc -http -ipc
|
|
||||||
{
|
|
||||||
httpEnabled: true,
|
|
||||||
ipcEnabled: true,
|
|
||||||
flag: "ipc",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
// -http -ipc
|
|
||||||
{
|
|
||||||
httpEnabled: true,
|
|
||||||
ipcEnabled: true,
|
|
||||||
flag: "",
|
|
||||||
},
|
|
||||||
// -status=ipc
|
|
||||||
{
|
|
||||||
err: errStatusServiceRequiresIPC,
|
|
||||||
flag: "ipc",
|
|
||||||
},
|
|
||||||
// -status=http
|
|
||||||
{
|
|
||||||
err: errStatusServiceRequiresHTTP,
|
|
||||||
flag: "http",
|
|
||||||
},
|
|
||||||
// -status=bad-value
|
|
||||||
{
|
|
||||||
err: errStatusServiceInvalidFlag,
|
|
||||||
flag: "bad-value",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, s := range scenarios {
|
|
||||||
msg := fmt.Sprintf("scenario %d", i)
|
|
||||||
|
|
||||||
c, err := params.NewNodeConfig("", 0)
|
|
||||||
require.Nil(t, err, msg)
|
|
||||||
|
|
||||||
c.IPCEnabled = s.ipcEnabled
|
|
||||||
c.HTTPEnabled = s.httpEnabled
|
|
||||||
|
|
||||||
c, err = configureStatusService(s.flag, c)
|
|
||||||
|
|
||||||
if s.err != nil {
|
|
||||||
require.Equal(t, s.err, err, msg)
|
|
||||||
require.Nil(t, c, msg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
require.Nil(t, err, msg)
|
|
||||||
require.Equal(t, s.enabled, c.EnableStatusService, msg)
|
|
||||||
|
|
||||||
modules := c.FormatAPIModules()
|
|
||||||
if s.public {
|
|
||||||
require.Contains(t, modules, service, msg)
|
|
||||||
} else {
|
|
||||||
require.NotContains(t, modules, service, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,460 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
stdlog "log"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/account/generator"
|
|
||||||
"github.com/status-im/status-go/api"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/multiaccounts"
|
|
||||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
||||||
//"github.com/status-im/status-go/appdatabase"
|
|
||||||
//gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/logutils"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/protocol"
|
|
||||||
"github.com/status-im/status-go/protocol/common"
|
|
||||||
"github.com/status-im/status-go/protocol/identity/alias"
|
|
||||||
wakuextn "github.com/status-im/status-go/services/wakuext"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testTimeSource struct{}
|
|
||||||
|
|
||||||
func (t *testTimeSource) GetCurrentTime() uint64 {
|
|
||||||
return uint64(time.Now().Unix())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
serverClientName = "Statusd"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configFiles configFlags
|
|
||||||
logLevel = flag.String("log", "", `Log level, one of: "ERROR", "WARN", "INFO", "DEBUG", and "TRACE"`)
|
|
||||||
logWithoutColors = flag.Bool("log-without-color", false, "Disables log colors")
|
|
||||||
ipcEnabled = flag.Bool("ipc", false, "Enable IPC RPC endpoint")
|
|
||||||
ipcFile = flag.String("ipcfile", "", "Set IPC file path")
|
|
||||||
seedPhrase = flag.String("seed-phrase", "", "Seed phrase")
|
|
||||||
pprofEnabled = flag.Bool("pprof", false, "Enable runtime profiling via pprof")
|
|
||||||
pprofPort = flag.Int("pprof-port", 52525, "Port for runtime profiling via pprof")
|
|
||||||
version = flag.Bool("version", false, "Print version and dump configuration")
|
|
||||||
nAddedContacts = flag.Int("added-contacts", 100, "Number of added contacts to create")
|
|
||||||
nContacts = flag.Int("contacts", 100, "Number of contacts to create")
|
|
||||||
nPublicChats = flag.Int("public-chats", 5, "Number of public chats")
|
|
||||||
nOneToOneChats = flag.Int("one-to-one-chats", 5, "Number of one to one chats")
|
|
||||||
|
|
||||||
dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data")
|
|
||||||
register = flag.Bool("register", false, "Register and make the node discoverable by other nodes")
|
|
||||||
networkID = flag.Int(
|
|
||||||
"network-id",
|
|
||||||
params.RopstenNetworkID,
|
|
||||||
fmt.Sprintf(
|
|
||||||
"A network ID: %d (Mainnet), %d (Ropsten), %d (Rinkeby), %d (Goerli)",
|
|
||||||
params.MainNetworkID, params.RopstenNetworkID, params.RinkebyNetworkID, params.GoerliNetworkID,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
listenAddr = flag.String("addr", "", "address to bind listener to")
|
|
||||||
|
|
||||||
syncAndExit = flag.Int("sync-and-exit", -1, "Timeout in minutes for blockchain sync and exit, zero means no timeout unless sync is finished")
|
|
||||||
)
|
|
||||||
|
|
||||||
// All general log messages in this package should be routed through this logger.
|
|
||||||
var logger = log.New("package", "status-go/cmd/statusd")
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&configFiles, "c", "JSON configuration file(s). Multiple configuration files can be specified, and will be merged in occurrence order")
|
|
||||||
}
|
|
||||||
|
|
||||||
// nolint:gocyclo
|
|
||||||
func main() {
|
|
||||||
colors := terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
||||||
if err := logutils.OverrideRootLog(true, "ERROR", logutils.FileOptions{}, colors); err != nil {
|
|
||||||
stdlog.Fatalf("Error initializing logger: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.Usage = printUsage
|
|
||||||
flag.Parse()
|
|
||||||
if flag.NArg() > 0 {
|
|
||||||
printUsage()
|
|
||||||
logger.Error("Extra args in command line: %v", flag.Args())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []params.Option{}
|
|
||||||
|
|
||||||
config, err := params.NewNodeConfigWithDefaultsAndFiles(
|
|
||||||
*dataDir,
|
|
||||||
uint64(*networkID),
|
|
||||||
opts,
|
|
||||||
configFiles,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
printUsage()
|
|
||||||
logger.Error(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use listenAddr if and only if explicitly provided in the arguments.
|
|
||||||
// The default value is set in params.NewNodeConfigWithDefaultsAndFiles().
|
|
||||||
if *listenAddr != "" {
|
|
||||||
config.ListenAddr = *listenAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable IPC RPC
|
|
||||||
if *ipcEnabled {
|
|
||||||
config.IPCEnabled = true
|
|
||||||
config.IPCFile = *ipcFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up logging options
|
|
||||||
setupLogging(config)
|
|
||||||
|
|
||||||
// We want statusd to be distinct from StatusIM client.
|
|
||||||
config.Name = serverClientName
|
|
||||||
|
|
||||||
if *version {
|
|
||||||
printVersion(config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
backend := api.NewGethStatusBackend()
|
|
||||||
err = ImportAccount(*seedPhrase, backend)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wakuextservice, err := backend.WakuExtService()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wakuext := wakuextn.NewPublicAPI(wakuextservice)
|
|
||||||
|
|
||||||
// This will start the push notification server as well as
|
|
||||||
// the config is set to Enabled
|
|
||||||
_, err = wakuext.StartMessenger()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to start messenger", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating added contacts")
|
|
||||||
|
|
||||||
for i := 0; i < *nAddedContacts; i++ {
|
|
||||||
key, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyString := common.PubkeyToHex(&key.PublicKey)
|
|
||||||
_, err = wakuext.AddContact(context.Background(), keyString)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed", "err", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating contacts")
|
|
||||||
|
|
||||||
for i := 0; i < *nContacts; i++ {
|
|
||||||
key, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
contact, err := protocol.BuildContactFromPublicKey(&key.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wakuext.SaveContact(context.Background(), contact)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating public chats")
|
|
||||||
|
|
||||||
for i := 0; i < *nPublicChats; i++ {
|
|
||||||
chat := protocol.CreatePublicChat(randomString(10), &testTimeSource{})
|
|
||||||
err = wakuext.SaveChat(context.Background(), chat)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("Creating one to one chats")
|
|
||||||
|
|
||||||
for i := 0; i < *nOneToOneChats; i++ {
|
|
||||||
key, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyString := common.PubkeyToHex(&key.PublicKey)
|
|
||||||
chat := protocol.CreateOneToOneChat(keyString, &key.PublicKey, &testTimeSource{})
|
|
||||||
err = wakuext.SaveChat(context.Background(), chat)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDefaultDataDir() string {
|
|
||||||
if home := os.Getenv("HOME"); home != "" {
|
|
||||||
return filepath.Join(home, ".statusd")
|
|
||||||
}
|
|
||||||
return "./statusd-data"
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupLogging(config *params.NodeConfig) {
|
|
||||||
if *logLevel != "" {
|
|
||||||
config.LogLevel = *logLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd()))
|
|
||||||
if err := logutils.OverrideRootLogWithConfig(config, colors); err != nil {
|
|
||||||
stdlog.Fatalf("Error initializing logger: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errStatusServiceRequiresIPC = errors.New("to enable the StatusService on IPC, -ipc flag must be set")
|
|
||||||
errStatusServiceRequiresHTTP = errors.New("to enable the StatusService on HTTP, -http flag must be set")
|
|
||||||
errStatusServiceInvalidFlag = errors.New("-status flag valid values are: ipc, http")
|
|
||||||
)
|
|
||||||
|
|
||||||
// printVersion prints verbose output about version and config.
|
|
||||||
func printVersion(config *params.NodeConfig) {
|
|
||||||
fmt.Println(strings.Title(config.Name))
|
|
||||||
fmt.Println("Version:", config.Version)
|
|
||||||
fmt.Println("Network ID:", config.NetworkID)
|
|
||||||
fmt.Println("Go Version:", runtime.Version())
|
|
||||||
fmt.Println("OS:", runtime.GOOS)
|
|
||||||
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
|
||||||
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
|
|
||||||
|
|
||||||
fmt.Println("Loaded Config: ", config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func printUsage() {
|
|
||||||
usage := `
|
|
||||||
Usage: statusd [options]
|
|
||||||
Examples:
|
|
||||||
statusd # run regular Whisper node that joins Status network
|
|
||||||
statusd -c ./default.json # run node with configuration specified in ./default.json file
|
|
||||||
statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file
|
|
||||||
statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call
|
|
||||||
|
|
||||||
Options:
|
|
||||||
`
|
|
||||||
fmt.Fprint(os.Stderr, usage)
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathWalletRoot = "m/44'/60'/0'/0"
|
|
||||||
const pathEIP1581 = "m/43'/60'/1581'"
|
|
||||||
const pathDefaultChat = pathEIP1581 + "/0'/0"
|
|
||||||
const pathDefaultWallet = pathWalletRoot + "/0"
|
|
||||||
|
|
||||||
var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet}
|
|
||||||
|
|
||||||
func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*accounts.Settings, error) {
|
|
||||||
chatKeyString := derivedAddresses[pathDefaultChat].PublicKey
|
|
||||||
|
|
||||||
settings := &accounts.Settings{}
|
|
||||||
settings.KeyUID = generatedAccountInfo.KeyUID
|
|
||||||
settings.Address = types.HexToAddress(generatedAccountInfo.Address)
|
|
||||||
settings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address)
|
|
||||||
|
|
||||||
// Set chat key & name
|
|
||||||
name, err := alias.GenerateFromPublicKeyString(chatKeyString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
settings.Name = name
|
|
||||||
settings.PublicKey = chatKeyString
|
|
||||||
|
|
||||||
settings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address)
|
|
||||||
settings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address)
|
|
||||||
settings.Mnemonic = mnemonic
|
|
||||||
|
|
||||||
signingPhrase, err := buildSigningPhrase()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
settings.SigningPhrase = signingPhrase
|
|
||||||
|
|
||||||
settings.SendPushNotifications = true
|
|
||||||
settings.InstallationID = uuid.New().String()
|
|
||||||
settings.UseMailservers = true
|
|
||||||
|
|
||||||
settings.PreviewPrivacy = true
|
|
||||||
settings.Currency = "usd"
|
|
||||||
settings.ProfilePicturesVisibility = 1
|
|
||||||
settings.LinkPreviewRequestEnabled = true
|
|
||||||
|
|
||||||
visibleTokens := make(map[string][]string)
|
|
||||||
visibleTokens["mainnet"] = []string{"SNT"}
|
|
||||||
visibleTokensJSON, err := json.Marshal(visibleTokens)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
|
|
||||||
settings.WalletVisibleTokens = &visibleTokenJSONRaw
|
|
||||||
|
|
||||||
// TODO: fix this
|
|
||||||
networks := make([]map[string]string, 0)
|
|
||||||
networksJSON, err := json.Marshal(networks)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
networkRawMessage := json.RawMessage(networksJSON)
|
|
||||||
settings.Networks = &networkRawMessage
|
|
||||||
settings.CurrentNetwork = "mainnet_rpc"
|
|
||||||
|
|
||||||
return settings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultNodeConfig(installationID string) (*params.NodeConfig, error) {
|
|
||||||
// Set mainnet
|
|
||||||
nodeConfig := ¶ms.NodeConfig{}
|
|
||||||
nodeConfig.NetworkID = 1
|
|
||||||
nodeConfig.LogLevel = "ERROR"
|
|
||||||
nodeConfig.DataDir = "/ethereum/mainnet_rpc"
|
|
||||||
nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{
|
|
||||||
Enabled: true,
|
|
||||||
URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938",
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeConfig.Name = "StatusIM"
|
|
||||||
nodeConfig.Rendezvous = true
|
|
||||||
clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nodeConfig.ClusterConfig = *clusterConfig
|
|
||||||
|
|
||||||
nodeConfig.WalletConfig = params.WalletConfig{Enabled: true}
|
|
||||||
nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true}
|
|
||||||
nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true}
|
|
||||||
nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true}
|
|
||||||
nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true}
|
|
||||||
nodeConfig.EnableNTPSync = true
|
|
||||||
nodeConfig.WakuConfig = params.WakuConfig{
|
|
||||||
Enabled: true,
|
|
||||||
LightClient: true,
|
|
||||||
MinimumPoW: 0.000001,
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeConfig.ShhextConfig = params.ShhextConfig{
|
|
||||||
BackupDisabledDataDir: "",
|
|
||||||
InstallationID: installationID,
|
|
||||||
MaxMessageDeliveryAttempts: 6,
|
|
||||||
MailServerConfirmations: true,
|
|
||||||
VerifyTransactionURL: "",
|
|
||||||
VerifyENSURL: "",
|
|
||||||
VerifyENSContractAddress: "",
|
|
||||||
VerifyTransactionChainID: 1,
|
|
||||||
DataSyncEnabled: true,
|
|
||||||
PFSEnabled: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: check topics
|
|
||||||
|
|
||||||
return nodeConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error {
|
|
||||||
backend.UpdateRootDataDir("./tmp")
|
|
||||||
manager := backend.AccountManager()
|
|
||||||
manager.InitKeystore("./tmp")
|
|
||||||
err := backend.OpenAccounts()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
generator := manager.AccountsGenerator()
|
|
||||||
generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
account := multiaccounts.Account{
|
|
||||||
KeyUID: generatedAccountInfo.KeyUID,
|
|
||||||
}
|
|
||||||
settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeConfig, err := defaultNodeConfig(settings.InstallationID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
walletDerivedAccount := derivedAddresses[pathDefaultWallet]
|
|
||||||
walletAccount := accounts.Account{
|
|
||||||
PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey),
|
|
||||||
Address: types.HexToAddress(walletDerivedAccount.Address),
|
|
||||||
Color: "",
|
|
||||||
Wallet: true,
|
|
||||||
Path: pathDefaultWallet,
|
|
||||||
Name: "Ethereum account",
|
|
||||||
}
|
|
||||||
|
|
||||||
chatDerivedAccount := derivedAddresses[pathDefaultChat]
|
|
||||||
chatAccount := accounts.Account{
|
|
||||||
PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey),
|
|
||||||
Address: types.HexToAddress(chatDerivedAccount.Address),
|
|
||||||
Name: settings.Name,
|
|
||||||
Chat: true,
|
|
||||||
Path: pathDefaultChat,
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts := []accounts.Account{walletAccount, chatAccount}
|
|
||||||
return backend.StartNodeWithAccountAndConfig(account, "", *settings, nodeConfig, accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
||||||
|
|
||||||
func randomString(n int) string {
|
|
||||||
b := make([]rune, n)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
|
@ -1,650 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func buildSigningPhrase() (string, error) {
|
|
||||||
length := big.NewInt(int64(len(dictionary)))
|
|
||||||
a, err := rand.Int(rand.Reader, length)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
b, err := rand.Int(rand.Reader, length)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
c, err := rand.Int(rand.Reader, length)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dictionary[a.Int64()] + " " + dictionary[b.Int64()] + " " + dictionary[c.Int64()], nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var dictionary = []string{
|
|
||||||
"acid",
|
|
||||||
"alto",
|
|
||||||
"apse",
|
|
||||||
"arch",
|
|
||||||
"area",
|
|
||||||
"army",
|
|
||||||
"atom",
|
|
||||||
"aunt",
|
|
||||||
"babe",
|
|
||||||
"baby",
|
|
||||||
"back",
|
|
||||||
"bail",
|
|
||||||
"bait",
|
|
||||||
"bake",
|
|
||||||
"ball",
|
|
||||||
"band",
|
|
||||||
"bank",
|
|
||||||
"barn",
|
|
||||||
"base",
|
|
||||||
"bass",
|
|
||||||
"bath",
|
|
||||||
"bead",
|
|
||||||
"beak",
|
|
||||||
"beam",
|
|
||||||
"bean",
|
|
||||||
"bear",
|
|
||||||
"beat",
|
|
||||||
"beef",
|
|
||||||
"beer",
|
|
||||||
"beet",
|
|
||||||
"bell",
|
|
||||||
"belt",
|
|
||||||
"bend",
|
|
||||||
"bike",
|
|
||||||
"bill",
|
|
||||||
"bird",
|
|
||||||
"bite",
|
|
||||||
"blow",
|
|
||||||
"blue",
|
|
||||||
"boar",
|
|
||||||
"boat",
|
|
||||||
"body",
|
|
||||||
"bolt",
|
|
||||||
"bomb",
|
|
||||||
"bone",
|
|
||||||
"book",
|
|
||||||
"boot",
|
|
||||||
"bore",
|
|
||||||
"boss",
|
|
||||||
"bowl",
|
|
||||||
"brow",
|
|
||||||
"bulb",
|
|
||||||
"bull",
|
|
||||||
"burn",
|
|
||||||
"bush",
|
|
||||||
"bust",
|
|
||||||
"cafe",
|
|
||||||
"cake",
|
|
||||||
"calf",
|
|
||||||
"call",
|
|
||||||
"calm",
|
|
||||||
"camp",
|
|
||||||
"cane",
|
|
||||||
"cape",
|
|
||||||
"card",
|
|
||||||
"care",
|
|
||||||
"carp",
|
|
||||||
"cart",
|
|
||||||
"case",
|
|
||||||
"cash",
|
|
||||||
"cast",
|
|
||||||
"cave",
|
|
||||||
"cell",
|
|
||||||
"cent",
|
|
||||||
"chap",
|
|
||||||
"chef",
|
|
||||||
"chin",
|
|
||||||
"chip",
|
|
||||||
"chop",
|
|
||||||
"chub",
|
|
||||||
"chug",
|
|
||||||
"city",
|
|
||||||
"clam",
|
|
||||||
"clef",
|
|
||||||
"clip",
|
|
||||||
"club",
|
|
||||||
"clue",
|
|
||||||
"coal",
|
|
||||||
"coat",
|
|
||||||
"code",
|
|
||||||
"coil",
|
|
||||||
"coin",
|
|
||||||
"coke",
|
|
||||||
"cold",
|
|
||||||
"colt",
|
|
||||||
"comb",
|
|
||||||
"cone",
|
|
||||||
"cook",
|
|
||||||
"cope",
|
|
||||||
"copy",
|
|
||||||
"cord",
|
|
||||||
"cork",
|
|
||||||
"corn",
|
|
||||||
"cost",
|
|
||||||
"crab",
|
|
||||||
"craw",
|
|
||||||
"crew",
|
|
||||||
"crib",
|
|
||||||
"crop",
|
|
||||||
"crow",
|
|
||||||
"curl",
|
|
||||||
"cyst",
|
|
||||||
"dame",
|
|
||||||
"dare",
|
|
||||||
"dark",
|
|
||||||
"dart",
|
|
||||||
"dash",
|
|
||||||
"data",
|
|
||||||
"date",
|
|
||||||
"dead",
|
|
||||||
"deal",
|
|
||||||
"dear",
|
|
||||||
"debt",
|
|
||||||
"deck",
|
|
||||||
"deep",
|
|
||||||
"deer",
|
|
||||||
"desk",
|
|
||||||
"dhow",
|
|
||||||
"diet",
|
|
||||||
"dill",
|
|
||||||
"dime",
|
|
||||||
"dirt",
|
|
||||||
"dish",
|
|
||||||
"disk",
|
|
||||||
"dock",
|
|
||||||
"doll",
|
|
||||||
"door",
|
|
||||||
"dory",
|
|
||||||
"drag",
|
|
||||||
"draw",
|
|
||||||
"drop",
|
|
||||||
"drug",
|
|
||||||
"drum",
|
|
||||||
"duck",
|
|
||||||
"dump",
|
|
||||||
"dust",
|
|
||||||
"duty",
|
|
||||||
"ease",
|
|
||||||
"east",
|
|
||||||
"eave",
|
|
||||||
"eddy",
|
|
||||||
"edge",
|
|
||||||
"envy",
|
|
||||||
"epee",
|
|
||||||
"exam",
|
|
||||||
"exit",
|
|
||||||
"face",
|
|
||||||
"fact",
|
|
||||||
"fail",
|
|
||||||
"fall",
|
|
||||||
"fame",
|
|
||||||
"fang",
|
|
||||||
"farm",
|
|
||||||
"fawn",
|
|
||||||
"fear",
|
|
||||||
"feed",
|
|
||||||
"feel",
|
|
||||||
"feet",
|
|
||||||
"file",
|
|
||||||
"fill",
|
|
||||||
"film",
|
|
||||||
"find",
|
|
||||||
"fine",
|
|
||||||
"fire",
|
|
||||||
"fish",
|
|
||||||
"flag",
|
|
||||||
"flat",
|
|
||||||
"flax",
|
|
||||||
"flow",
|
|
||||||
"foam",
|
|
||||||
"fold",
|
|
||||||
"font",
|
|
||||||
"food",
|
|
||||||
"foot",
|
|
||||||
"fork",
|
|
||||||
"form",
|
|
||||||
"fort",
|
|
||||||
"fowl",
|
|
||||||
"frog",
|
|
||||||
"fuel",
|
|
||||||
"full",
|
|
||||||
"gain",
|
|
||||||
"gale",
|
|
||||||
"galn",
|
|
||||||
"game",
|
|
||||||
"garb",
|
|
||||||
"gate",
|
|
||||||
"gear",
|
|
||||||
"gene",
|
|
||||||
"gift",
|
|
||||||
"girl",
|
|
||||||
"give",
|
|
||||||
"glad",
|
|
||||||
"glen",
|
|
||||||
"glue",
|
|
||||||
"glut",
|
|
||||||
"goal",
|
|
||||||
"goat",
|
|
||||||
"gold",
|
|
||||||
"golf",
|
|
||||||
"gong",
|
|
||||||
"good",
|
|
||||||
"gown",
|
|
||||||
"grab",
|
|
||||||
"gram",
|
|
||||||
"gray",
|
|
||||||
"grey",
|
|
||||||
"grip",
|
|
||||||
"grit",
|
|
||||||
"gyro",
|
|
||||||
"hail",
|
|
||||||
"hair",
|
|
||||||
"half",
|
|
||||||
"hall",
|
|
||||||
"hand",
|
|
||||||
"hang",
|
|
||||||
"harm",
|
|
||||||
"harp",
|
|
||||||
"hate",
|
|
||||||
"hawk",
|
|
||||||
"head",
|
|
||||||
"heat",
|
|
||||||
"heel",
|
|
||||||
"hell",
|
|
||||||
"helo",
|
|
||||||
"help",
|
|
||||||
"hemp",
|
|
||||||
"herb",
|
|
||||||
"hide",
|
|
||||||
"high",
|
|
||||||
"hill",
|
|
||||||
"hire",
|
|
||||||
"hive",
|
|
||||||
"hold",
|
|
||||||
"hole",
|
|
||||||
"home",
|
|
||||||
"hood",
|
|
||||||
"hoof",
|
|
||||||
"hook",
|
|
||||||
"hope",
|
|
||||||
"hops",
|
|
||||||
"horn",
|
|
||||||
"hose",
|
|
||||||
"host",
|
|
||||||
"hour",
|
|
||||||
"hunt",
|
|
||||||
"hurt",
|
|
||||||
"icon",
|
|
||||||
"idea",
|
|
||||||
"inch",
|
|
||||||
"iris",
|
|
||||||
"iron",
|
|
||||||
"item",
|
|
||||||
"jail",
|
|
||||||
"jeep",
|
|
||||||
"jeff",
|
|
||||||
"joey",
|
|
||||||
"join",
|
|
||||||
"joke",
|
|
||||||
"judo",
|
|
||||||
"jump",
|
|
||||||
"junk",
|
|
||||||
"jury",
|
|
||||||
"jute",
|
|
||||||
"kale",
|
|
||||||
"keep",
|
|
||||||
"kick",
|
|
||||||
"kill",
|
|
||||||
"kilt",
|
|
||||||
"kind",
|
|
||||||
"king",
|
|
||||||
"kiss",
|
|
||||||
"kite",
|
|
||||||
"knee",
|
|
||||||
"knot",
|
|
||||||
"lace",
|
|
||||||
"lack",
|
|
||||||
"lady",
|
|
||||||
"lake",
|
|
||||||
"lamb",
|
|
||||||
"lamp",
|
|
||||||
"land",
|
|
||||||
"lark",
|
|
||||||
"lava",
|
|
||||||
"lawn",
|
|
||||||
"lead",
|
|
||||||
"leaf",
|
|
||||||
"leek",
|
|
||||||
"lier",
|
|
||||||
"life",
|
|
||||||
"lift",
|
|
||||||
"lily",
|
|
||||||
"limo",
|
|
||||||
"line",
|
|
||||||
"link",
|
|
||||||
"lion",
|
|
||||||
"lisa",
|
|
||||||
"list",
|
|
||||||
"load",
|
|
||||||
"loaf",
|
|
||||||
"loan",
|
|
||||||
"lock",
|
|
||||||
"loft",
|
|
||||||
"long",
|
|
||||||
"look",
|
|
||||||
"loss",
|
|
||||||
"lout",
|
|
||||||
"love",
|
|
||||||
"luck",
|
|
||||||
"lung",
|
|
||||||
"lute",
|
|
||||||
"lynx",
|
|
||||||
"lyre",
|
|
||||||
"maid",
|
|
||||||
"mail",
|
|
||||||
"main",
|
|
||||||
"make",
|
|
||||||
"male",
|
|
||||||
"mall",
|
|
||||||
"manx",
|
|
||||||
"many",
|
|
||||||
"mare",
|
|
||||||
"mark",
|
|
||||||
"mask",
|
|
||||||
"mass",
|
|
||||||
"mate",
|
|
||||||
"math",
|
|
||||||
"meal",
|
|
||||||
"meat",
|
|
||||||
"meet",
|
|
||||||
"menu",
|
|
||||||
"mess",
|
|
||||||
"mice",
|
|
||||||
"midi",
|
|
||||||
"mile",
|
|
||||||
"milk",
|
|
||||||
"mime",
|
|
||||||
"mind",
|
|
||||||
"mine",
|
|
||||||
"mini",
|
|
||||||
"mint",
|
|
||||||
"miss",
|
|
||||||
"mist",
|
|
||||||
"moat",
|
|
||||||
"mode",
|
|
||||||
"mole",
|
|
||||||
"mood",
|
|
||||||
"moon",
|
|
||||||
"most",
|
|
||||||
"moth",
|
|
||||||
"move",
|
|
||||||
"mule",
|
|
||||||
"mutt",
|
|
||||||
"nail",
|
|
||||||
"name",
|
|
||||||
"neat",
|
|
||||||
"neck",
|
|
||||||
"need",
|
|
||||||
"neon",
|
|
||||||
"nest",
|
|
||||||
"news",
|
|
||||||
"node",
|
|
||||||
"nose",
|
|
||||||
"note",
|
|
||||||
"oboe",
|
|
||||||
"okra",
|
|
||||||
"open",
|
|
||||||
"oval",
|
|
||||||
"oven",
|
|
||||||
"oxen",
|
|
||||||
"pace",
|
|
||||||
"pack",
|
|
||||||
"page",
|
|
||||||
"pail",
|
|
||||||
"pain",
|
|
||||||
"pair",
|
|
||||||
"palm",
|
|
||||||
"pard",
|
|
||||||
"park",
|
|
||||||
"part",
|
|
||||||
"pass",
|
|
||||||
"past",
|
|
||||||
"path",
|
|
||||||
"peak",
|
|
||||||
"pear",
|
|
||||||
"peen",
|
|
||||||
"peer",
|
|
||||||
"pelt",
|
|
||||||
"perp",
|
|
||||||
"pest",
|
|
||||||
"pick",
|
|
||||||
"pier",
|
|
||||||
"pike",
|
|
||||||
"pile",
|
|
||||||
"pimp",
|
|
||||||
"pine",
|
|
||||||
"ping",
|
|
||||||
"pink",
|
|
||||||
"pint",
|
|
||||||
"pipe",
|
|
||||||
"piss",
|
|
||||||
"pith",
|
|
||||||
"plan",
|
|
||||||
"play",
|
|
||||||
"plot",
|
|
||||||
"plow",
|
|
||||||
"poem",
|
|
||||||
"poet",
|
|
||||||
"pole",
|
|
||||||
"polo",
|
|
||||||
"pond",
|
|
||||||
"pony",
|
|
||||||
"poof",
|
|
||||||
"pool",
|
|
||||||
"port",
|
|
||||||
"post",
|
|
||||||
"prow",
|
|
||||||
"pull",
|
|
||||||
"puma",
|
|
||||||
"pump",
|
|
||||||
"pupa",
|
|
||||||
"push",
|
|
||||||
"quit",
|
|
||||||
"race",
|
|
||||||
"rack",
|
|
||||||
"raft",
|
|
||||||
"rage",
|
|
||||||
"rail",
|
|
||||||
"rain",
|
|
||||||
"rake",
|
|
||||||
"rank",
|
|
||||||
"rate",
|
|
||||||
"read",
|
|
||||||
"rear",
|
|
||||||
"reef",
|
|
||||||
"rent",
|
|
||||||
"rest",
|
|
||||||
"rice",
|
|
||||||
"rich",
|
|
||||||
"ride",
|
|
||||||
"ring",
|
|
||||||
"rise",
|
|
||||||
"risk",
|
|
||||||
"road",
|
|
||||||
"robe",
|
|
||||||
"rock",
|
|
||||||
"role",
|
|
||||||
"roll",
|
|
||||||
"roof",
|
|
||||||
"room",
|
|
||||||
"root",
|
|
||||||
"rope",
|
|
||||||
"rose",
|
|
||||||
"ruin",
|
|
||||||
"rule",
|
|
||||||
"rush",
|
|
||||||
"ruth",
|
|
||||||
"sack",
|
|
||||||
"safe",
|
|
||||||
"sage",
|
|
||||||
"sail",
|
|
||||||
"sale",
|
|
||||||
"salt",
|
|
||||||
"sand",
|
|
||||||
"sari",
|
|
||||||
"sash",
|
|
||||||
"save",
|
|
||||||
"scow",
|
|
||||||
"seal",
|
|
||||||
"seat",
|
|
||||||
"seed",
|
|
||||||
"self",
|
|
||||||
"sell",
|
|
||||||
"shed",
|
|
||||||
"shin",
|
|
||||||
"ship",
|
|
||||||
"shoe",
|
|
||||||
"shop",
|
|
||||||
"shot",
|
|
||||||
"show",
|
|
||||||
"sick",
|
|
||||||
"side",
|
|
||||||
"sign",
|
|
||||||
"silk",
|
|
||||||
"sill",
|
|
||||||
"silo",
|
|
||||||
"sing",
|
|
||||||
"sink",
|
|
||||||
"site",
|
|
||||||
"size",
|
|
||||||
"skin",
|
|
||||||
"sled",
|
|
||||||
"slip",
|
|
||||||
"smog",
|
|
||||||
"snob",
|
|
||||||
"snow",
|
|
||||||
"soap",
|
|
||||||
"sock",
|
|
||||||
"soda",
|
|
||||||
"sofa",
|
|
||||||
"soft",
|
|
||||||
"soil",
|
|
||||||
"song",
|
|
||||||
"soot",
|
|
||||||
"sort",
|
|
||||||
"soup",
|
|
||||||
"spot",
|
|
||||||
"spur",
|
|
||||||
"stag",
|
|
||||||
"star",
|
|
||||||
"stay",
|
|
||||||
"stem",
|
|
||||||
"step",
|
|
||||||
"stew",
|
|
||||||
"stop",
|
|
||||||
"stud",
|
|
||||||
"suck",
|
|
||||||
"suit",
|
|
||||||
"swan",
|
|
||||||
"swim",
|
|
||||||
"tail",
|
|
||||||
"tale",
|
|
||||||
"talk",
|
|
||||||
"tank",
|
|
||||||
"tard",
|
|
||||||
"task",
|
|
||||||
"taxi",
|
|
||||||
"team",
|
|
||||||
"tear",
|
|
||||||
"teen",
|
|
||||||
"tell",
|
|
||||||
"temp",
|
|
||||||
"tent",
|
|
||||||
"term",
|
|
||||||
"test",
|
|
||||||
"text",
|
|
||||||
"thaw",
|
|
||||||
"tile",
|
|
||||||
"till",
|
|
||||||
"time",
|
|
||||||
"tire",
|
|
||||||
"toad",
|
|
||||||
"toga",
|
|
||||||
"togs",
|
|
||||||
"tone",
|
|
||||||
"tool",
|
|
||||||
"toot",
|
|
||||||
"tote",
|
|
||||||
"tour",
|
|
||||||
"town",
|
|
||||||
"tram",
|
|
||||||
"tray",
|
|
||||||
"tree",
|
|
||||||
"trim",
|
|
||||||
"trip",
|
|
||||||
"tuba",
|
|
||||||
"tube",
|
|
||||||
"tuna",
|
|
||||||
"tune",
|
|
||||||
"turn",
|
|
||||||
"tutu",
|
|
||||||
"twig",
|
|
||||||
"type",
|
|
||||||
"unit",
|
|
||||||
"user",
|
|
||||||
"vane",
|
|
||||||
"vase",
|
|
||||||
"vast",
|
|
||||||
"veal",
|
|
||||||
"veil",
|
|
||||||
"vein",
|
|
||||||
"vest",
|
|
||||||
"vibe",
|
|
||||||
"view",
|
|
||||||
"vise",
|
|
||||||
"wait",
|
|
||||||
"wake",
|
|
||||||
"walk",
|
|
||||||
"wall",
|
|
||||||
"wash",
|
|
||||||
"wasp",
|
|
||||||
"wave",
|
|
||||||
"wear",
|
|
||||||
"weed",
|
|
||||||
"week",
|
|
||||||
"well",
|
|
||||||
"west",
|
|
||||||
"whip",
|
|
||||||
"wife",
|
|
||||||
"will",
|
|
||||||
"wind",
|
|
||||||
"wine",
|
|
||||||
"wing",
|
|
||||||
"wire",
|
|
||||||
"wish",
|
|
||||||
"wolf",
|
|
||||||
"wood",
|
|
||||||
"wool",
|
|
||||||
"word",
|
|
||||||
"work",
|
|
||||||
"worm",
|
|
||||||
"wrap",
|
|
||||||
"wren",
|
|
||||||
"yard",
|
|
||||||
"yarn",
|
|
||||||
"yawl",
|
|
||||||
"year",
|
|
||||||
"yoga",
|
|
||||||
"yoke",
|
|
||||||
"yurt",
|
|
||||||
"zinc",
|
|
||||||
"zone",
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/node"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createContextFromTimeout(timeout int) (context.Context, context.CancelFunc) {
|
|
||||||
if timeout == 0 {
|
|
||||||
return context.WithCancel(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.WithTimeout(context.Background(), time.Duration(timeout)*time.Minute)
|
|
||||||
}
|
|
||||||
|
|
||||||
// syncAndStopNode tries to sync the blockchain and stop the node.
|
|
||||||
// It returns an exit code (`0` if successful or `1` in case of error)
|
|
||||||
// that can be used in `os.Exit` to exit immediately when the function returns.
|
|
||||||
// The special exit code `-1` is used if execution was interrupted.
|
|
||||||
func syncAndStopNode(interruptCh <-chan struct{}, statusNode *node.StatusNode, timeout int) (exitCode int) {
|
|
||||||
|
|
||||||
logger.Info("syncAndStopNode: node will synchronize the chain and exit", "timeoutInMins", timeout)
|
|
||||||
|
|
||||||
ctx, cancel := createContextFromTimeout(timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
doneSync := make(chan struct{})
|
|
||||||
errSync := make(chan error)
|
|
||||||
go func() {
|
|
||||||
if err := statusNode.EnsureSync(ctx); err != nil {
|
|
||||||
errSync <- err
|
|
||||||
}
|
|
||||||
close(doneSync)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errSync:
|
|
||||||
logger.Error("syncAndStopNode: failed to sync the chain", "error", err)
|
|
||||||
exitCode = 1
|
|
||||||
case <-doneSync:
|
|
||||||
case <-interruptCh:
|
|
||||||
// cancel context and return immediately if interrupted
|
|
||||||
// `-1` is used as a special exit code to denote interruption
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := statusNode.Stop(); err != nil {
|
|
||||||
logger.Error("syncAndStopNode: failed to stop the node", "error", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
package api
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// connectionState represents device connection state and type,
|
// State represents device connection state and type,
|
||||||
// as reported by mobile framework.
|
// as reported by mobile framework.
|
||||||
//
|
//
|
||||||
// Zero value represents default assumption about network (online and unknown type).
|
// Zero value represents default assumption about network (online and unknown type).
|
||||||
type connectionState struct {
|
type State struct {
|
||||||
Offline bool `json:"offline"`
|
Offline bool `json:"offline"`
|
||||||
Type connectionType `json:"type"`
|
Type connectionType `json:"type"`
|
||||||
Expensive bool `json:"expensive"`
|
Expensive bool `json:"expensive"`
|
||||||
|
@ -22,19 +22,19 @@ type connectionState struct {
|
||||||
type connectionType byte
|
type connectionType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
offline = "offline"
|
Offline = "offline"
|
||||||
wifi = "wifi"
|
Wifi = "wifi"
|
||||||
cellular = "cellular"
|
Cellular = "cellular"
|
||||||
unknown = "unknown"
|
Unknown = "unknown"
|
||||||
none = "none"
|
None = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newConnectionType creates new connectionType from string.
|
// NewConnectionType creates new connectionType from string.
|
||||||
func newConnectionType(s string) connectionType {
|
func NewConnectionType(s string) connectionType {
|
||||||
switch s {
|
switch s {
|
||||||
case cellular:
|
case Cellular:
|
||||||
return connectionCellular
|
return connectionCellular
|
||||||
case wifi:
|
case Wifi:
|
||||||
return connectionWifi
|
return connectionWifi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,20 +48,24 @@ const (
|
||||||
connectionWifi // WIFI or iOS simulator
|
connectionWifi // WIFI or iOS simulator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c State) IsExpensive() bool {
|
||||||
|
return c.Expensive || c.Type == connectionCellular
|
||||||
|
}
|
||||||
|
|
||||||
// string formats ConnectionState for logs. Implements Stringer.
|
// string formats ConnectionState for logs. Implements Stringer.
|
||||||
func (c connectionState) String() string {
|
func (c State) String() string {
|
||||||
if c.Offline {
|
if c.Offline {
|
||||||
return offline
|
return Offline
|
||||||
}
|
}
|
||||||
|
|
||||||
var typ string
|
var typ string
|
||||||
switch c.Type {
|
switch c.Type {
|
||||||
case connectionWifi:
|
case connectionWifi:
|
||||||
typ = wifi
|
typ = Wifi
|
||||||
case connectionCellular:
|
case connectionCellular:
|
||||||
typ = cellular
|
typ = Cellular
|
||||||
default:
|
default:
|
||||||
typ = unknown
|
typ = Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Expensive {
|
if c.Expensive {
|
|
@ -1,56 +1,56 @@
|
||||||
package api
|
package connection
|
||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestConnectionType(t *testing.T) {
|
func TestConnectionType(t *testing.T) {
|
||||||
c := newConnectionType("wifi")
|
c := NewConnectionType("wifi")
|
||||||
if c != connectionWifi {
|
if c != connectionWifi {
|
||||||
t.Fatalf("Wrong connection type: %v", c)
|
t.Fatalf("Wrong connection type: %v", c)
|
||||||
}
|
}
|
||||||
c = newConnectionType("cellular")
|
c = NewConnectionType("cellular")
|
||||||
if c != connectionCellular {
|
if c != connectionCellular {
|
||||||
t.Fatalf("Wrong connection type: %v", c)
|
t.Fatalf("Wrong connection type: %v", c)
|
||||||
}
|
}
|
||||||
c = newConnectionType("bluetooth")
|
c = NewConnectionType("bluetooth")
|
||||||
if c != connectionUnknown {
|
if c != connectionUnknown {
|
||||||
t.Fatalf("Wrong connection type: %v", c)
|
t.Fatalf("Wrong connection type: %v", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectionState(t *testing.T) {
|
func TestState(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
state connectionState
|
state State
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"zero value",
|
"zero value",
|
||||||
connectionState{},
|
State{},
|
||||||
"unknown",
|
"unknown",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"offline",
|
"offline",
|
||||||
connectionState{Offline: true},
|
State{Offline: true},
|
||||||
"offline",
|
"offline",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wifi",
|
"wifi",
|
||||||
connectionState{Type: connectionWifi},
|
State{Type: connectionWifi},
|
||||||
"wifi",
|
"wifi",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"wifi tethered",
|
"wifi tethered",
|
||||||
connectionState{Type: connectionWifi, Expensive: true},
|
State{Type: connectionWifi, Expensive: true},
|
||||||
"wifi (expensive)",
|
"wifi (expensive)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"unknown",
|
"unknown",
|
||||||
connectionState{Type: connectionUnknown},
|
State{Type: connectionUnknown},
|
||||||
"unknown",
|
"unknown",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cellular",
|
"cellular",
|
||||||
connectionState{Type: connectionCellular},
|
State{Type: connectionCellular},
|
||||||
"cellular",
|
"cellular",
|
||||||
},
|
},
|
||||||
}
|
}
|
4
go.mod
4
go.mod
|
@ -13,7 +13,6 @@ replace github.com/nfnt/resize => github.com/status-im/resize v0.0.0-20201215164
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.6.0 // indirect
|
github.com/PuerkitoBio/goquery v1.6.0 // indirect
|
||||||
github.com/beevik/ntp v0.2.0
|
github.com/beevik/ntp v0.2.0
|
||||||
github.com/btcsuite/btcd v0.20.1-beta
|
|
||||||
github.com/cenkalti/backoff/v3 v3.2.2
|
github.com/cenkalti/backoff/v3 v3.2.2
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/deckarep/golang-set v1.7.1
|
github.com/deckarep/golang-set v1.7.1
|
||||||
|
@ -38,7 +37,6 @@ require (
|
||||||
github.com/mat/besticon v3.12.0+incompatible
|
github.com/mat/besticon v3.12.0+incompatible
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||||
github.com/mattn/go-pointer v0.0.0-20190911064623-a0a44394634f
|
|
||||||
github.com/mattn/go-runewidth v0.0.6 // indirect
|
github.com/mattn/go-runewidth v0.0.6 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.12.0 // indirect
|
github.com/mattn/go-sqlite3 v1.12.0 // indirect
|
||||||
github.com/multiformats/go-multiaddr v0.1.1
|
github.com/multiformats/go-multiaddr v0.1.1
|
||||||
|
@ -54,7 +52,6 @@ require (
|
||||||
github.com/pborman/uuid v1.2.0
|
github.com/pborman/uuid v1.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.5.0
|
github.com/prometheus/client_golang v1.5.0
|
||||||
github.com/prometheus/common v0.9.1
|
|
||||||
github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect
|
github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect
|
||||||
github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect
|
github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect
|
||||||
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
|
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
|
||||||
|
@ -76,7 +73,6 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
|
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
|
||||||
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd // indirect
|
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd // indirect
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
|
||||||
golang.org/x/text v0.3.3 // indirect
|
golang.org/x/text v0.3.3 // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
golang.org/x/tools v0.0.0-20200211045251-2de505fc5306 // indirect
|
golang.org/x/tools v0.0.0-20200211045251-2de505fc5306 // indirect
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -24,10 +24,8 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
|
||||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
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/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-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/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-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/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 v0.0.0-20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||||
github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM=
|
github.com/allegro/bigcache v1.2.0 h1:qDaE0QoF29wKBb3+pXFrJFy1ihe5OT9OiXhg1t85SxM=
|
||||||
|
@ -441,8 +439,6 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||||
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/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||||
|
@ -621,8 +617,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
||||||
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc=
|
||||||
github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY=
|
github.com/status-im/doubleratchet v3.0.0+incompatible h1:aJ1ejcSERpSzmWZBgtfYtiU2nF0Q8ZkGyuEPYETXkCY=
|
||||||
github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU=
|
github.com/status-im/doubleratchet v3.0.0+incompatible/go.mod h1:1sqR0+yhiM/bd+wrdX79AOt2csZuJOni0nUDzKNuqOU=
|
||||||
github.com/status-im/go-ethereum v1.9.5-status.11 h1:97yCttJkIXoTZGrew7Lkl6uOGaeYELdp7gMnKBm1YwY=
|
|
||||||
github.com/status-im/go-ethereum v1.9.5-status.11/go.mod h1:YyH5DKB6+z+Vaya7eIm67pnuPZ1oiUMbbsZW41ktN0g=
|
|
||||||
github.com/status-im/go-ethereum v1.9.5-status.12 h1:+QZE2x5zF1CvNo3V+WaCzeOoar8pdh5Y7BGaAJ83pOw=
|
github.com/status-im/go-ethereum v1.9.5-status.12 h1:+QZE2x5zF1CvNo3V+WaCzeOoar8pdh5Y7BGaAJ83pOw=
|
||||||
github.com/status-im/go-ethereum v1.9.5-status.12/go.mod h1:YyH5DKB6+z+Vaya7eIm67pnuPZ1oiUMbbsZW41ktN0g=
|
github.com/status-im/go-ethereum v1.9.5-status.12/go.mod h1:YyH5DKB6+z+Vaya7eIm67pnuPZ1oiUMbbsZW41ktN0g=
|
||||||
github.com/status-im/go-multiaddr-ethv4 v1.2.0 h1:OT84UsUzTCwguqCpJqkrCMiL4VZ1SvUtH9a5MsZupBk=
|
github.com/status-im/go-multiaddr-ethv4 v1.2.0 h1:OT84UsUzTCwguqCpJqkrCMiL4VZ1SvUtH9a5MsZupBk=
|
||||||
|
@ -649,7 +643,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/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU=
|
||||||
github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc=
|
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.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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v0.0.0-20170809224252-890a5c3458b4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
@ -779,7 +772,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -858,7 +850,6 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/whisper"
|
waku "github.com/status-im/status-go/waku/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCleaner(t *testing.T) {
|
func TestCleaner(t *testing.T) {
|
||||||
|
@ -88,8 +88,8 @@ func BenchmarkCleanerPruneM100_000_B100(b *testing.B) {
|
||||||
benchmarkCleanerPrune(b, 100000, 100)
|
benchmarkCleanerPrune(b, 100000, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTestServer(t *testing.T) *WhisperMailServer {
|
func setupTestServer(t *testing.T) *WakuMailServer {
|
||||||
var s WhisperMailServer
|
var s WakuMailServer
|
||||||
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
|
||||||
|
|
||||||
s.ms = &mailServer{
|
s.ms = &mailServer{
|
||||||
|
@ -97,13 +97,13 @@ func setupTestServer(t *testing.T) *WhisperMailServer {
|
||||||
ldb: db,
|
ldb: db,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
},
|
},
|
||||||
adapter: &whisperAdapter{},
|
adapter: &wakuAdapter{},
|
||||||
}
|
}
|
||||||
s.minRequestPoW = powRequirement
|
s.minRequestPoW = powRequirement
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveEnvelope(t *testing.T, sentTime time.Time, server *WhisperMailServer) *whisper.Envelope {
|
func archiveEnvelope(t *testing.T, sentTime time.Time, server *WakuMailServer) *waku.Envelope {
|
||||||
env, err := generateEnvelope(sentTime)
|
env, err := generateEnvelope(sentTime)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
server.Archive(env)
|
server.Archive(env)
|
||||||
|
@ -117,7 +117,7 @@ func testPrune(t *testing.T, u time.Time, expected int, c *dbCleaner) {
|
||||||
require.Equal(t, expected, n)
|
require.Equal(t, expected, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessagesCount(t *testing.T, expected int, s *WhisperMailServer) {
|
func testMessagesCount(t *testing.T, expected int, s *WakuMailServer) {
|
||||||
count := countMessages(t, s.ms.db)
|
count := countMessages(t, s.ms.db)
|
||||||
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
|
require.Equal(t, expected, count, fmt.Sprintf("expected %d message, got: %d", expected, count))
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ func countMessages(t *testing.T, db DB) int {
|
||||||
defer func() { _ = i.Release() }()
|
defer func() { _ = i.Release() }()
|
||||||
|
|
||||||
for i.Next() {
|
for i.Next() {
|
||||||
var env whisper.Envelope
|
var env waku.Envelope
|
||||||
value, err := i.GetEnvelope(query.bloom)
|
value, err := i.GetEnvelope(query.bloom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -18,6 +18,7 @@ package mailserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -155,6 +156,72 @@ func (s *WakuMailServer) DeliverMail(peerID []byte, req *wakucommon.Envelope) {
|
||||||
s.ms.DeliverMail(types.BytesToHash(peerID), types.Hash(req.Hash()), payload)
|
s.ms.DeliverMail(types.BytesToHash(peerID), types.Hash(req.Hash()), payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bloomFromReceivedMessage for a given whisper.ReceivedMessage it extracts the
|
||||||
|
// used bloom filter.
|
||||||
|
func (s *WakuMailServer) bloomFromReceivedMessage(msg *wakucommon.ReceivedMessage) ([]byte, error) {
|
||||||
|
payloadSize := len(msg.Payload)
|
||||||
|
|
||||||
|
if payloadSize < 8 {
|
||||||
|
return nil, errors.New("Undersized p2p request")
|
||||||
|
} else if payloadSize == 8 {
|
||||||
|
return wakucommon.MakeFullNodeBloom(), nil
|
||||||
|
} else if payloadSize < 8+wakucommon.BloomFilterSize {
|
||||||
|
return nil, errors.New("Undersized bloom filter in p2p request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg.Payload[8 : 8+wakucommon.BloomFilterSize], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WakuMailServer) decompositeRequest(peerID []byte, request *wakucommon.Envelope) (MessagesRequestPayload, error) {
|
||||||
|
var (
|
||||||
|
payload MessagesRequestPayload
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if s.minRequestPoW > 0.0 && request.PoW() < s.minRequestPoW {
|
||||||
|
return payload, fmt.Errorf("PoW() is too low")
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted := s.openEnvelope(request)
|
||||||
|
if decrypted == nil {
|
||||||
|
return payload, fmt.Errorf("failed to decrypt p2p request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkMsgSignature(decrypted.Src, peerID); err != nil {
|
||||||
|
return payload, err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.Bloom, err = s.bloomFromReceivedMessage(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return payload, err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload.Lower = binary.BigEndian.Uint32(decrypted.Payload[:4])
|
||||||
|
payload.Upper = binary.BigEndian.Uint32(decrypted.Payload[4:8])
|
||||||
|
|
||||||
|
if payload.Upper < payload.Lower {
|
||||||
|
err := fmt.Errorf("query range is invalid: from > to (%d > %d)", payload.Lower, payload.Upper)
|
||||||
|
return payload, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerTime := time.Unix(int64(payload.Lower), 0)
|
||||||
|
upperTime := time.Unix(int64(payload.Upper), 0)
|
||||||
|
if upperTime.Sub(lowerTime) > maxQueryRange {
|
||||||
|
err := fmt.Errorf("query range too big for peer %s", string(peerID))
|
||||||
|
return payload, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decrypted.Payload) >= requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength {
|
||||||
|
payload.Limit = binary.BigEndian.Uint32(decrypted.Payload[requestTimeRangeLength+wakucommon.BloomFilterSize:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decrypted.Payload) == requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength+DBKeyLength {
|
||||||
|
payload.Cursor = decrypted.Payload[requestTimeRangeLength+wakucommon.BloomFilterSize+requestLimitLength:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *WakuMailServer) setupDecryptor(password, asymKey string) error {
|
func (s *WakuMailServer) setupDecryptor(password, asymKey string) error {
|
||||||
s.symFilter = nil
|
s.symFilter = nil
|
||||||
s.asymFilter = nil
|
s.asymFilter = nil
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/whisper"
|
waku "github.com/status-im/status-go/waku/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPostgresDB_BuildIteratorWithBloomFilter(t *testing.T) {
|
func TestPostgresDB_BuildIteratorWithBloomFilter(t *testing.T) {
|
||||||
|
@ -44,10 +44,10 @@ func TestPostgresDB_BuildIteratorWithBloomFilter(t *testing.T) {
|
||||||
rawValue, err := iter.GetEnvelope(nil)
|
rawValue, err := iter.GetEnvelope(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, rawValue)
|
require.NotEmpty(t, rawValue)
|
||||||
var receivedEnvelope whisper.Envelope
|
var receivedEnvelope waku.Envelope
|
||||||
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, whisper.BytesToTopic(topic), receivedEnvelope.Topic)
|
require.EqualValues(t, waku.BytesToTopic(topic), receivedEnvelope.Topic)
|
||||||
|
|
||||||
err = iter.Release()
|
err = iter.Release()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -77,10 +77,10 @@ func TestPostgresDB_BuildIteratorWithTopic(t *testing.T) {
|
||||||
rawValue, err := iter.GetEnvelope(nil)
|
rawValue, err := iter.GetEnvelope(nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, rawValue)
|
require.NotEmpty(t, rawValue)
|
||||||
var receivedEnvelope whisper.Envelope
|
var receivedEnvelope waku.Envelope
|
||||||
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
err = rlp.DecodeBytes(rawValue, &receivedEnvelope)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.EqualValues(t, whisper.BytesToTopic(topic), receivedEnvelope.Topic)
|
require.EqualValues(t, waku.BytesToTopic(topic), receivedEnvelope.Topic)
|
||||||
|
|
||||||
err = iter.Release()
|
err = iter.Release()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -92,15 +92,15 @@ func newTestEnvelope(topic []byte) (types.Envelope, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
params := whisper.MessageParams{
|
params := waku.MessageParams{
|
||||||
TTL: 10,
|
TTL: 10,
|
||||||
PoW: 2.0,
|
PoW: 2.0,
|
||||||
Payload: []byte("hello world"),
|
Payload: []byte("hello world"),
|
||||||
WorkTime: 1,
|
WorkTime: 1,
|
||||||
Topic: whisper.BytesToTopic(topic),
|
Topic: waku.BytesToTopic(topic),
|
||||||
Dst: &privateKey.PublicKey,
|
Dst: &privateKey.PublicKey,
|
||||||
}
|
}
|
||||||
message, err := whisper.NewSentMessage(¶ms)
|
message, err := waku.NewSentMessage(¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package mailserver
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -35,7 +34,8 @@ import (
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/whisper"
|
waku "github.com/status-im/status-go/waku"
|
||||||
|
wakucommon "github.com/status-im/status-go/waku/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
const powRequirement = 0.00001
|
const powRequirement = 0.00001
|
||||||
|
@ -59,28 +59,23 @@ func TestMailserverSuite(t *testing.T) {
|
||||||
|
|
||||||
type MailserverSuite struct {
|
type MailserverSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
server *WhisperMailServer
|
server *WakuMailServer
|
||||||
shh *whisper.Whisper
|
shh *waku.Waku
|
||||||
config *params.WhisperConfig
|
config *params.WakuConfig
|
||||||
dataDir string
|
dataDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) SetupTest() {
|
func (s *MailserverSuite) SetupTest() {
|
||||||
s.server = &WhisperMailServer{}
|
s.server = &WakuMailServer{}
|
||||||
s.shh = whisper.New(&whisper.DefaultConfig)
|
s.shh = waku.New(&waku.DefaultConfig, nil)
|
||||||
s.shh.RegisterMailServer(s.server)
|
s.shh.RegisterMailServer(s.server)
|
||||||
|
|
||||||
tmpDir, err := ioutil.TempDir("", "mailserver-test")
|
tmpDir, err := ioutil.TempDir("", "mailserver-test")
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.dataDir = tmpDir
|
s.dataDir = tmpDir
|
||||||
|
|
||||||
// required files to validate mail server decryption method
|
s.config = ¶ms.WakuConfig{
|
||||||
privateKey, err := crypto.GenerateKey()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
s.config = ¶ms.WhisperConfig{
|
|
||||||
DataDir: tmpDir,
|
DataDir: tmpDir,
|
||||||
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(privateKey)),
|
|
||||||
MailServerPassword: "testpassword",
|
MailServerPassword: "testpassword",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,30 +85,18 @@ func (s *MailserverSuite) TearDownTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) TestInit() {
|
func (s *MailserverSuite) TestInit() {
|
||||||
asymKey, err := crypto.GenerateKey()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
config params.WhisperConfig
|
config params.WakuConfig
|
||||||
expectedError error
|
expectedError error
|
||||||
info string
|
info string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config: params.WhisperConfig{DataDir: ""},
|
config: params.WakuConfig{DataDir: ""},
|
||||||
expectedError: errDirectoryNotProvided,
|
expectedError: errDirectoryNotProvided,
|
||||||
info: "config with empty DataDir",
|
info: "config with empty DataDir",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: params.WhisperConfig{
|
config: params.WakuConfig{
|
||||||
DataDir: s.config.DataDir,
|
|
||||||
MailServerPassword: "",
|
|
||||||
MailServerAsymKey: "",
|
|
||||||
},
|
|
||||||
expectedError: errDecryptionMethodNotProvided,
|
|
||||||
info: "config with an empty password and empty asym key",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: params.WhisperConfig{
|
|
||||||
DataDir: s.config.DataDir,
|
DataDir: s.config.DataDir,
|
||||||
MailServerPassword: "pwd",
|
MailServerPassword: "pwd",
|
||||||
},
|
},
|
||||||
|
@ -121,24 +104,7 @@ func (s *MailserverSuite) TestInit() {
|
||||||
info: "config with correct DataDir and Password",
|
info: "config with correct DataDir and Password",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: params.WhisperConfig{
|
config: params.WakuConfig{
|
||||||
DataDir: s.config.DataDir,
|
|
||||||
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(asymKey)),
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
info: "config with correct DataDir and AsymKey",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: params.WhisperConfig{
|
|
||||||
DataDir: s.config.DataDir,
|
|
||||||
MailServerAsymKey: hex.EncodeToString(crypto.FromECDSA(asymKey)),
|
|
||||||
MailServerPassword: "pwd",
|
|
||||||
},
|
|
||||||
expectedError: nil,
|
|
||||||
info: "config with both asym key and password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config: params.WhisperConfig{
|
|
||||||
DataDir: s.config.DataDir,
|
DataDir: s.config.DataDir,
|
||||||
MailServerPassword: "pwd",
|
MailServerPassword: "pwd",
|
||||||
MailServerRateLimit: 5,
|
MailServerRateLimit: 5,
|
||||||
|
@ -150,13 +116,15 @@ func (s *MailserverSuite) TestInit() {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
s.T().Run(tc.info, func(*testing.T) {
|
s.T().Run(tc.info, func(*testing.T) {
|
||||||
mailServer := &WhisperMailServer{}
|
mailServer := &WakuMailServer{}
|
||||||
shh := whisper.New(&whisper.DefaultConfig)
|
shh := waku.New(&waku.DefaultConfig, nil)
|
||||||
shh.RegisterMailServer(mailServer)
|
shh.RegisterMailServer(mailServer)
|
||||||
|
|
||||||
err := mailServer.Init(shh, &tc.config)
|
err := mailServer.Init(shh, &tc.config)
|
||||||
s.Equal(tc.expectedError, err)
|
s.Require().Equal(tc.expectedError, err)
|
||||||
defer mailServer.Close()
|
if err == nil {
|
||||||
|
defer mailServer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// db should be open only if there was no error
|
// db should be open only if there was no error
|
||||||
if tc.expectedError == nil {
|
if tc.expectedError == nil {
|
||||||
|
@ -172,81 +140,8 @@ func (s *MailserverSuite) TestInit() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) TestSetupRequestMessageDecryptor() {
|
|
||||||
// without configured Password and AsymKey
|
|
||||||
config := *s.config
|
|
||||||
config.MailServerAsymKey = ""
|
|
||||||
config.MailServerPassword = ""
|
|
||||||
s.Error(errDecryptionMethodNotProvided, s.server.Init(s.shh, &config))
|
|
||||||
|
|
||||||
// Password should work ok
|
|
||||||
config = *s.config
|
|
||||||
config.MailServerAsymKey = "" // clear asym key field
|
|
||||||
s.NoError(s.server.Init(s.shh, &config))
|
|
||||||
s.Require().NotNil(s.server.symFilter)
|
|
||||||
s.NotNil(s.server.symFilter.KeySym)
|
|
||||||
s.Nil(s.server.asymFilter)
|
|
||||||
s.server.Close()
|
|
||||||
|
|
||||||
// AsymKey can also be used
|
|
||||||
config = *s.config
|
|
||||||
config.MailServerPassword = "" // clear password field
|
|
||||||
s.NoError(s.server.Init(s.shh, &config))
|
|
||||||
s.Nil(s.server.symFilter) // important: symmetric filter should be nil
|
|
||||||
s.Require().NotNil(s.server.asymFilter)
|
|
||||||
s.Equal(config.MailServerAsymKey, hex.EncodeToString(crypto.FromECDSA(s.server.asymFilter.KeyAsym)))
|
|
||||||
s.server.Close()
|
|
||||||
|
|
||||||
// when Password and AsymKey are set, both are supported
|
|
||||||
config = *s.config
|
|
||||||
s.NoError(s.server.Init(s.shh, &config))
|
|
||||||
s.Require().NotNil(s.server.symFilter)
|
|
||||||
s.NotNil(s.server.symFilter.KeySym)
|
|
||||||
s.NotNil(s.server.asymFilter.KeyAsym)
|
|
||||||
s.server.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestOpenEnvelopeWithSymKey() {
|
|
||||||
// Setup the server with a sym key
|
|
||||||
config := *s.config
|
|
||||||
config.MailServerAsymKey = "" // clear asym key
|
|
||||||
s.NoError(s.server.Init(s.shh, &config))
|
|
||||||
|
|
||||||
// Prepare a valid envelope
|
|
||||||
s.Require().NotNil(s.server.symFilter)
|
|
||||||
symKey := s.server.symFilter.KeySym
|
|
||||||
env, err := generateEnvelopeWithKeys(time.Now(), symKey, nil)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Test openEnvelope with a valid envelope
|
|
||||||
d := s.server.openEnvelope(env)
|
|
||||||
s.NotNil(d)
|
|
||||||
s.Equal(testPayload, d.Payload)
|
|
||||||
s.server.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestOpenEnvelopeWithAsymKey() {
|
|
||||||
// Setup the server with an asymmetric key
|
|
||||||
config := *s.config
|
|
||||||
config.MailServerPassword = "" // clear password field
|
|
||||||
s.NoError(s.server.Init(s.shh, &config))
|
|
||||||
|
|
||||||
// Prepare a valid envelope
|
|
||||||
s.Require().NotNil(s.server.asymFilter)
|
|
||||||
pubKey := s.server.asymFilter.KeyAsym.PublicKey
|
|
||||||
env, err := generateEnvelopeWithKeys(time.Now(), nil, &pubKey)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// Test openEnvelope with a valid asymmetric key
|
|
||||||
d := s.server.openEnvelope(env)
|
|
||||||
s.NotNil(d)
|
|
||||||
s.Equal(testPayload, d.Payload)
|
|
||||||
s.server.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) TestArchive() {
|
func (s *MailserverSuite) TestArchive() {
|
||||||
config := *s.config
|
config := *s.config
|
||||||
config.MailServerAsymKey = "" // clear asym key
|
|
||||||
|
|
||||||
err := s.server.Init(s.shh, &config)
|
err := s.server.Init(s.shh, &config)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -294,7 +189,7 @@ func (s *MailserverSuite) TestRequestPaginationLimit() {
|
||||||
defer s.server.Close()
|
defer s.server.Close()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sentEnvelopes []*whisper.Envelope
|
sentEnvelopes []*wakucommon.Envelope
|
||||||
sentHashes []common.Hash
|
sentHashes []common.Hash
|
||||||
receivedHashes []common.Hash
|
receivedHashes []common.Hash
|
||||||
archiveKeys []string
|
archiveKeys []string
|
||||||
|
@ -490,7 +385,7 @@ func (s *MailserverSuite) TestProcessRequestDeadlockHandling() {
|
||||||
s.setupServer(s.server)
|
s.setupServer(s.server)
|
||||||
defer s.server.Close()
|
defer s.server.Close()
|
||||||
|
|
||||||
var archievedEnvelopes []*whisper.Envelope
|
var archievedEnvelopes []*wakucommon.Envelope
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
count := uint32(10)
|
count := uint32(10)
|
||||||
|
@ -584,7 +479,7 @@ func (s *MailserverSuite) TestProcessRequestDeadlockHandling() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) messageExists(envelope *whisper.Envelope, low, upp uint32, bloom []byte, limit uint32) bool {
|
func (s *MailserverSuite) messageExists(envelope *wakucommon.Envelope, low, upp uint32, bloom []byte, limit uint32) bool {
|
||||||
receivedHashes, _, _ := processRequestAndCollectHashes(s.server, MessagesRequestPayload{
|
receivedHashes, _, _ := processRequestAndCollectHashes(s.server, MessagesRequestPayload{
|
||||||
Lower: low,
|
Lower: low,
|
||||||
Upper: upp,
|
Upper: upp,
|
||||||
|
@ -599,49 +494,13 @@ func (s *MailserverSuite) messageExists(envelope *whisper.Envelope, low, upp uin
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) TestBloomFromReceivedMessage() {
|
func (s *MailserverSuite) setupServer(server *WakuMailServer) {
|
||||||
testCases := []struct {
|
|
||||||
msg whisper.ReceivedMessage
|
|
||||||
expectedBloom []byte
|
|
||||||
expectedErr error
|
|
||||||
info string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
msg: whisper.ReceivedMessage{},
|
|
||||||
expectedBloom: []byte(nil),
|
|
||||||
expectedErr: errors.New("Undersized p2p request"),
|
|
||||||
info: "getting bloom filter for an empty whisper message should produce an error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: whisper.ReceivedMessage{Payload: []byte("hohohohoho")},
|
|
||||||
expectedBloom: []byte(nil),
|
|
||||||
expectedErr: errors.New("Undersized bloom filter in p2p request"),
|
|
||||||
info: "getting bloom filter for a malformed whisper message should produce an error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
msg: whisper.ReceivedMessage{Payload: []byte("12345678")},
|
|
||||||
expectedBloom: whisper.MakeFullNodeBloom(),
|
|
||||||
expectedErr: nil,
|
|
||||||
info: "getting bloom filter for a valid whisper message should be successful",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
s.T().Run(tc.info, func(*testing.T) {
|
|
||||||
bloom, err := s.server.bloomFromReceivedMessage(&tc.msg)
|
|
||||||
s.Equal(tc.expectedErr, err)
|
|
||||||
s.Equal(tc.expectedBloom, bloom)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MailserverSuite) setupServer(server *WhisperMailServer) {
|
|
||||||
const password = "password_for_this_test"
|
const password = "password_for_this_test"
|
||||||
|
|
||||||
s.shh = whisper.New(&whisper.DefaultConfig)
|
s.shh = waku.New(&waku.DefaultConfig, nil)
|
||||||
s.shh.RegisterMailServer(server)
|
s.shh.RegisterMailServer(server)
|
||||||
|
|
||||||
err := server.Init(s.shh, ¶ms.WhisperConfig{
|
err := server.Init(s.shh, ¶ms.WakuConfig{
|
||||||
DataDir: s.dataDir,
|
DataDir: s.dataDir,
|
||||||
MailServerPassword: password,
|
MailServerPassword: password,
|
||||||
MinimumPoW: powRequirement,
|
MinimumPoW: powRequirement,
|
||||||
|
@ -656,8 +515,8 @@ func (s *MailserverSuite) setupServer(server *WhisperMailServer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) prepareRequest(envelopes []*whisper.Envelope, limit uint32) (
|
func (s *MailserverSuite) prepareRequest(envelopes []*wakucommon.Envelope, limit uint32) (
|
||||||
[]byte, *whisper.Envelope, error,
|
[]byte, *wakucommon.Envelope, error,
|
||||||
) {
|
) {
|
||||||
if len(envelopes) == 0 {
|
if len(envelopes) == 0 {
|
||||||
return nil, nil, errors.New("envelopes is empty")
|
return nil, nil, errors.New("envelopes is empty")
|
||||||
|
@ -676,7 +535,7 @@ func (s *MailserverSuite) prepareRequest(envelopes []*whisper.Envelope, limit ui
|
||||||
return peerID, request, nil
|
return peerID, request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) defaultServerParams(env *whisper.Envelope) *ServerTestParams {
|
func (s *MailserverSuite) defaultServerParams(env *wakucommon.Envelope) *ServerTestParams {
|
||||||
id, err := s.shh.NewKeyPair()
|
id, err := s.shh.NewKeyPair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
s.T().Fatalf("failed to generate new key pair with seed %d: %s.", seed, err)
|
||||||
|
@ -697,7 +556,7 @@ func (s *MailserverSuite) defaultServerParams(env *whisper.Envelope) *ServerTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) createRequest(p *ServerTestParams) *whisper.Envelope {
|
func (s *MailserverSuite) createRequest(p *ServerTestParams) *wakucommon.Envelope {
|
||||||
bloom := types.TopicToBloom(p.topic)
|
bloom := types.TopicToBloom(p.topic)
|
||||||
data := make([]byte, 8)
|
data := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint32(data, p.low)
|
binary.BigEndian.PutUint32(data, p.low)
|
||||||
|
@ -713,22 +572,22 @@ func (s *MailserverSuite) createRequest(p *ServerTestParams) *whisper.Envelope {
|
||||||
return s.createEnvelope(p.topic, data, p.key)
|
return s.createEnvelope(p.topic, data, p.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MailserverSuite) createEnvelope(topic types.TopicType, data []byte, srcKey *ecdsa.PrivateKey) *whisper.Envelope {
|
func (s *MailserverSuite) createEnvelope(topic types.TopicType, data []byte, srcKey *ecdsa.PrivateKey) *wakucommon.Envelope {
|
||||||
key, err := s.shh.GetSymKey(keyID)
|
key, err := s.shh.GetSymKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
s.T().Fatalf("failed to retrieve sym key with seed %d: %s.", seed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
params := &whisper.MessageParams{
|
params := &wakucommon.MessageParams{
|
||||||
KeySym: key,
|
KeySym: key,
|
||||||
Topic: whisper.TopicType(topic),
|
Topic: wakucommon.TopicType(topic),
|
||||||
Payload: data,
|
Payload: data,
|
||||||
PoW: powRequirement * 2,
|
PoW: powRequirement * 2,
|
||||||
WorkTime: 2,
|
WorkTime: 2,
|
||||||
Src: srcKey,
|
Src: srcKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := whisper.NewSentMessage(params)
|
msg, err := wakucommon.NewSentMessage(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
s.T().Fatalf("failed to create new message with seed %d: %s.", seed, err)
|
||||||
}
|
}
|
||||||
|
@ -740,9 +599,9 @@ func (s *MailserverSuite) createEnvelope(topic types.TopicType, data []byte, src
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.PublicKey) (*whisper.Envelope, error) {
|
func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.PublicKey) (*wakucommon.Envelope, error) {
|
||||||
params := &whisper.MessageParams{
|
params := &wakucommon.MessageParams{
|
||||||
Topic: whisper.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
Topic: wakucommon.TopicType{0x1F, 0x7E, 0xA1, 0x7F},
|
||||||
Payload: testPayload,
|
Payload: testPayload,
|
||||||
PoW: powRequirement,
|
PoW: powRequirement,
|
||||||
WorkTime: 2,
|
WorkTime: 2,
|
||||||
|
@ -754,7 +613,7 @@ func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.
|
||||||
params.Dst = keyAsym
|
params.Dst = keyAsym
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := whisper.NewSentMessage(params)
|
msg, err := wakucommon.NewSentMessage(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err)
|
return nil, fmt.Errorf("failed to create new message with seed %d: %s", seed, err)
|
||||||
}
|
}
|
||||||
|
@ -766,12 +625,12 @@ func generateEnvelopeWithKeys(sentTime time.Time, keySym []byte, keyAsym *ecdsa.
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateEnvelope(sentTime time.Time) (*whisper.Envelope, error) {
|
func generateEnvelope(sentTime time.Time) (*wakucommon.Envelope, error) {
|
||||||
h := crypto.Keccak256Hash([]byte("test sample data"))
|
h := crypto.Keccak256Hash([]byte("test sample data"))
|
||||||
return generateEnvelopeWithKeys(sentTime, h[:], nil)
|
return generateEnvelopeWithKeys(sentTime, h[:], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processRequestAndCollectHashes(server *WhisperMailServer, payload MessagesRequestPayload) ([]common.Hash, []byte, types.Hash) {
|
func processRequestAndCollectHashes(server *WakuMailServer, payload MessagesRequestPayload) ([]common.Hash, []byte, types.Hash) {
|
||||||
iter, _ := server.ms.createIterator(payload)
|
iter, _ := server.ms.createIterator(payload)
|
||||||
defer func() { _ = iter.Release() }()
|
defer func() { _ = iter.Release() }()
|
||||||
bundles := make(chan []rlp.RawValue, 10)
|
bundles := make(chan []rlp.RawValue, 10)
|
||||||
|
@ -781,7 +640,7 @@ func processRequestAndCollectHashes(server *WhisperMailServer, payload MessagesR
|
||||||
go func() {
|
go func() {
|
||||||
for bundle := range bundles {
|
for bundle := range bundles {
|
||||||
for _, rawEnvelope := range bundle {
|
for _, rawEnvelope := range bundle {
|
||||||
var env *whisper.Envelope
|
var env *wakucommon.Envelope
|
||||||
if err := rlp.DecodeBytes(rawEnvelope, &env); err != nil {
|
if err := rlp.DecodeBytes(rawEnvelope, &env); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -555,7 +555,7 @@ func (db *Database) GetNotificationsEnabled() (bool, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetUseMailservers() (bool, error) {
|
func (db *Database) CanUseMailservers() (bool, error) {
|
||||||
var result bool
|
var result bool
|
||||||
err := db.db.QueryRow("SELECT use_mailservers FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
err := db.db.QueryRow("SELECT use_mailservers FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
@ -564,6 +564,15 @@ func (db *Database) GetUseMailservers() (bool, error) {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Database) CanSyncOnMobileNetwork() (bool, error) {
|
||||||
|
var result bool
|
||||||
|
err := db.db.QueryRow("SELECT syncing_on_mobile_network FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
func (db *Database) GetMessagesFromContactsOnly() (bool, error) {
|
func (db *Database) GetMessagesFromContactsOnly() (bool, error) {
|
||||||
var result bool
|
var result bool
|
||||||
err := db.db.QueryRow("SELECT messages_from_contacts_only FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
err := db.db.QueryRow("SELECT messages_from_contacts_only FROM settings WHERE synthetic_id = 'id'").Scan(&result)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/db"
|
"github.com/status-im/status-go/db"
|
||||||
"github.com/status-im/status-go/discovery"
|
"github.com/status-im/status-go/discovery"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
|
@ -596,6 +597,16 @@ func (n *StatusNode) WakuExtService() (s *wakuext.Service, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *StatusNode) ConnectionChanged(state connection.State) error {
|
||||||
|
service, err := n.WakuExtService()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
service.ConnectionChanged(state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// WalletService returns wallet.Service instance if it was started.
|
// WalletService returns wallet.Service instance if it was started.
|
||||||
func (n *StatusNode) WalletService() (s *wallet.Service, err error) {
|
func (n *StatusNode) WalletService() (s *wallet.Service, err error) {
|
||||||
n.mu.RLock()
|
n.mu.RLock()
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/whisper"
|
"github.com/status-im/status-go/waku"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWhisperLightModeEnabledSetsEmptyBloomFilter(t *testing.T) {
|
func TestWakuLightModeEnabledSetsEmptyBloomFilter(t *testing.T) {
|
||||||
config := params.NodeConfig{
|
config := params.NodeConfig{
|
||||||
EnableNTPSync: true,
|
EnableNTPSync: true,
|
||||||
WhisperConfig: params.WhisperConfig{
|
WakuConfig: params.WakuConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
LightClient: true,
|
LightClient: true,
|
||||||
},
|
},
|
||||||
|
@ -25,19 +25,19 @@ func TestWhisperLightModeEnabledSetsEmptyBloomFilter(t *testing.T) {
|
||||||
require.NoError(t, node.Stop())
|
require.NoError(t, node.Stop())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var whisper *whisper.Whisper
|
var waku *waku.Waku
|
||||||
require.NoError(t, node.gethService(&whisper))
|
require.NoError(t, node.gethService(&waku))
|
||||||
|
|
||||||
bloomFilter := whisper.BloomFilter()
|
bloomFilter := waku.BloomFilter()
|
||||||
expectedEmptyBloomFilter := make([]byte, 64)
|
expectedEmptyBloomFilter := make([]byte, 64)
|
||||||
require.NotNil(t, bloomFilter)
|
require.NotNil(t, bloomFilter)
|
||||||
require.Equal(t, expectedEmptyBloomFilter, bloomFilter)
|
require.Equal(t, expectedEmptyBloomFilter, bloomFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWhisperLightModeEnabledSetsNilBloomFilter(t *testing.T) {
|
func TestWakuLightModeEnabledSetsNilBloomFilter(t *testing.T) {
|
||||||
config := params.NodeConfig{
|
config := params.NodeConfig{
|
||||||
EnableNTPSync: true,
|
EnableNTPSync: true,
|
||||||
WhisperConfig: params.WhisperConfig{
|
WakuConfig: params.WakuConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
LightClient: false,
|
LightClient: false,
|
||||||
},
|
},
|
||||||
|
@ -48,78 +48,7 @@ func TestWhisperLightModeEnabledSetsNilBloomFilter(t *testing.T) {
|
||||||
require.NoError(t, node.Stop())
|
require.NoError(t, node.Stop())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var whisper *whisper.Whisper
|
var waku *waku.Waku
|
||||||
require.NoError(t, node.gethService(&whisper))
|
require.NoError(t, node.gethService(&waku))
|
||||||
require.Nil(t, whisper.BloomFilter())
|
require.Nil(t, waku.BloomFilter())
|
||||||
}
|
|
||||||
|
|
||||||
func TestBridgeSetup(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
Name string
|
|
||||||
Skip string
|
|
||||||
Cfg params.NodeConfig
|
|
||||||
ErrorMessage string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "no whisper and waku",
|
|
||||||
Cfg: params.NodeConfig{
|
|
||||||
BridgeConfig: params.BridgeConfig{Enabled: true},
|
|
||||||
},
|
|
||||||
ErrorMessage: "setup bridge: failed to get Whisper: unknown service",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "only whisper",
|
|
||||||
Cfg: params.NodeConfig{
|
|
||||||
WhisperConfig: params.WhisperConfig{
|
|
||||||
Enabled: true,
|
|
||||||
LightClient: false,
|
|
||||||
},
|
|
||||||
BridgeConfig: params.BridgeConfig{Enabled: true},
|
|
||||||
},
|
|
||||||
ErrorMessage: "setup bridge: failed to get Waku: unknown service",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "only waku",
|
|
||||||
Cfg: params.NodeConfig{
|
|
||||||
WakuConfig: params.WakuConfig{
|
|
||||||
Enabled: true,
|
|
||||||
LightClient: false,
|
|
||||||
},
|
|
||||||
BridgeConfig: params.BridgeConfig{Enabled: true},
|
|
||||||
},
|
|
||||||
ErrorMessage: "setup bridge: failed to get Whisper: unknown service",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "both",
|
|
||||||
Skip: "This test is flaky, setting it as skip for now",
|
|
||||||
Cfg: params.NodeConfig{
|
|
||||||
WhisperConfig: params.WhisperConfig{
|
|
||||||
Enabled: true,
|
|
||||||
LightClient: false,
|
|
||||||
},
|
|
||||||
WakuConfig: params.WakuConfig{
|
|
||||||
Enabled: true,
|
|
||||||
LightClient: false,
|
|
||||||
},
|
|
||||||
BridgeConfig: params.BridgeConfig{Enabled: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
if tc.Skip != "" {
|
|
||||||
t.Skip(tc.Skip)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
|
||||||
node := New()
|
|
||||||
err := node.Start(&tc.Cfg, &accounts.Manager{})
|
|
||||||
if err != nil {
|
|
||||||
require.EqualError(t, err, tc.ErrorMessage)
|
|
||||||
} else if tc.ErrorMessage != "" {
|
|
||||||
t.Fatalf("expected an error: %s", tc.ErrorMessage)
|
|
||||||
}
|
|
||||||
require.NoError(t, node.Stop())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
gethnode "github.com/ethereum/go-ethereum/node"
|
gethnode "github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
|
||||||
"github.com/status-im/status-go/whisper"
|
"github.com/status-im/status-go/waku"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ func TestStatusNodeWithDataDir(t *testing.T) {
|
||||||
func TestStatusNodeServiceGetters(t *testing.T) {
|
func TestStatusNodeServiceGetters(t *testing.T) {
|
||||||
config := params.NodeConfig{
|
config := params.NodeConfig{
|
||||||
EnableNTPSync: true,
|
EnableNTPSync: true,
|
||||||
WhisperConfig: params.WhisperConfig{
|
WakuConfig: params.WakuConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
LightEthConfig: params.LightEthConfig{
|
LightEthConfig: params.LightEthConfig{
|
||||||
|
@ -110,9 +110,9 @@ func TestStatusNodeServiceGetters(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
getter: func() (interface{}, error) {
|
getter: func() (interface{}, error) {
|
||||||
return n.WhisperService()
|
return n.WakuService()
|
||||||
},
|
},
|
||||||
typ: reflect.TypeOf(&whisper.Whisper{}),
|
typ: reflect.TypeOf(&waku.Waku{}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
getter: func() (interface{}, error) {
|
getter: func() (interface{}, error) {
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/t/utils"
|
"github.com/status-im/status-go/t/utils"
|
||||||
)
|
)
|
||||||
|
@ -31,11 +29,10 @@ func TestNewNodeConfigWithDefaults(t *testing.T) {
|
||||||
assert.Equal(t, "/some/data/path/keystore", c.KeyStoreDir)
|
assert.Equal(t, "/some/data/path/keystore", c.KeyStoreDir)
|
||||||
assert.Equal(t, true, c.EnableNTPSync)
|
assert.Equal(t, true, c.EnableNTPSync)
|
||||||
// assert Whisper
|
// assert Whisper
|
||||||
assert.Equal(t, true, c.WhisperConfig.Enabled)
|
assert.Equal(t, true, c.WakuConfig.Enabled)
|
||||||
assert.Equal(t, "/some/data/path/wnode", c.WhisperConfig.DataDir)
|
assert.Equal(t, "/some/data/path/waku", c.WakuConfig.DataDir)
|
||||||
// assert MailServer
|
// assert MailServer
|
||||||
assert.Equal(t, true, c.WhisperConfig.EnableMailServer)
|
assert.Equal(t, false, c.WakuConfig.EnableMailServer)
|
||||||
assert.NotEmpty(t, c.WhisperConfig.MailServerPassword)
|
|
||||||
// assert cluster
|
// assert cluster
|
||||||
assert.Equal(t, false, c.NoDiscovery)
|
assert.Equal(t, false, c.NoDiscovery)
|
||||||
assert.Equal(t, params.FleetProd, c.ClusterConfig.Fleet)
|
assert.Equal(t, params.FleetProd, c.ClusterConfig.Fleet)
|
||||||
|
@ -45,9 +42,6 @@ func TestNewNodeConfigWithDefaults(t *testing.T) {
|
||||||
assert.NotEmpty(t, c.ClusterConfig.PushNotificationsServers)
|
assert.NotEmpty(t, c.ClusterConfig.PushNotificationsServers)
|
||||||
// assert LES
|
// assert LES
|
||||||
assert.Equal(t, true, c.LightEthConfig.Enabled)
|
assert.Equal(t, true, c.LightEthConfig.Enabled)
|
||||||
// assert peers limits
|
|
||||||
assert.Contains(t, c.RequireTopics, params.WhisperDiscv5Topic)
|
|
||||||
assert.Contains(t, c.RequireTopics, discv5.Topic(params.LesTopic(int(c.NetworkID))))
|
|
||||||
// assert other
|
// assert other
|
||||||
assert.Equal(t, false, c.HTTPEnabled)
|
assert.Equal(t, false, c.HTTPEnabled)
|
||||||
assert.Equal(t, false, c.IPCEnabled)
|
assert.Equal(t, false, c.IPCEnabled)
|
||||||
|
@ -216,104 +210,6 @@ func TestNodeConfigValidate(t *testing.T) {
|
||||||
}`,
|
}`,
|
||||||
Error: "Rendezvous is enabled, but ClusterConfig.RendezvousNodes is empty",
|
Error: "Rendezvous is enabled, but ClusterConfig.RendezvousNodes is empty",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "Validate that WhisperConfig.DataDir is checked to not be empty if mailserver is enabled",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"MailserverPassword": "foo"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Error: "WhisperConfig.DataDir must be specified when WhisperConfig.EnableMailServer is true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Validate that WhisperConfig.DataDir is checked to not be empty if mailserver is enabled",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/other/dir",
|
|
||||||
"MailserverPassword": "foo"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Error: "WhisperConfig.DataDir must start with DataDir fragment",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Validate that check for WhisperConfig.DataDir passes if it is not empty and mailserver is enabled",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"MailserverPassword": "foo"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
CheckFunc: func(t *testing.T, config *params.NodeConfig) {
|
|
||||||
require.Equal(t, "foo", config.WhisperConfig.MailServerPassword)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Validate that WhisperConfig.MailserverPassword and WhisperConfig.MailServerAsymKey are checked to not be empty if mailserver is enabled",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/some/dir"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Error: "WhisperConfig.MailServerPassword or WhisperConfig.MailServerAsymKey must be specified when WhisperConfig.EnableMailServer is true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Validate that WhisperConfig.MailServerAsymKey is checked to not be empty if mailserver is enabled",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"MailServerAsymKey": "06c365919f1fc8e13ff79a84f1dd14b7e45b869aa5fc0e34940481ee20d32f90"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
CheckFunc: func(t *testing.T, config *params.NodeConfig) {
|
|
||||||
require.Equal(t, "06c365919f1fc8e13ff79a84f1dd14b7e45b869aa5fc0e34940481ee20d32f90", config.WhisperConfig.MailServerAsymKey)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Validate that WhisperConfig.MailServerAsymKey is checked for validity",
|
|
||||||
Config: `{
|
|
||||||
"NetworkId": 1,
|
|
||||||
"DataDir": "/some/dir",
|
|
||||||
"KeyStoreDir": "/some/dir",
|
|
||||||
"NoDiscovery": true,
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "/foo",
|
|
||||||
"MailServerAsymKey": "bar"
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
Error: "WhisperConfig.MailServerAsymKey is invalid",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "Validate that PFSEnabled & InstallationID are checked for validity",
|
Name: "Validate that PFSEnabled & InstallationID are checked for validity",
|
||||||
Config: `{
|
Config: `{
|
||||||
|
@ -321,7 +217,7 @@ func TestNodeConfigValidate(t *testing.T) {
|
||||||
"DataDir": "/some/dir",
|
"DataDir": "/some/dir",
|
||||||
"KeyStoreDir": "/some/dir",
|
"KeyStoreDir": "/some/dir",
|
||||||
"NoDiscovery": true,
|
"NoDiscovery": true,
|
||||||
"WhisperConfig": {
|
"WakuConfig": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"DataDir": "/foo"
|
"DataDir": "/foo"
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"github.com/status-im/status-go/discovery"
|
"github.com/status-im/status-go/discovery"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PeerPoolSimulationSuite struct {
|
type PeerPoolSimulationSuite struct {
|
||||||
|
@ -62,7 +61,6 @@ func (s *PeerPoolSimulationSuite) SetupTest() {
|
||||||
s.discovery = make([]discovery.Discovery, 3)
|
s.discovery = make([]discovery.Discovery, 3)
|
||||||
for i := range s.peers {
|
for i := range s.peers {
|
||||||
key, _ := crypto.GenerateKey()
|
key, _ := crypto.GenerateKey()
|
||||||
whisper := whisper.New(nil)
|
|
||||||
peer := &p2p.Server{
|
peer := &p2p.Server{
|
||||||
Config: p2p.Config{
|
Config: p2p.Config{
|
||||||
MaxPeers: 10,
|
MaxPeers: 10,
|
||||||
|
@ -71,7 +69,6 @@ func (s *PeerPoolSimulationSuite) SetupTest() {
|
||||||
PrivateKey: key,
|
PrivateKey: key,
|
||||||
NoDiscovery: true,
|
NoDiscovery: true,
|
||||||
BootstrapNodesV5: []*discv5.Node{bootnodeV5},
|
BootstrapNodesV5: []*discv5.Node{bootnodeV5},
|
||||||
Protocols: whisper.Protocols(),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.NoError(peer.Start())
|
s.NoError(peer.Start())
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/t/helpers"
|
"github.com/status-im/status-go/t/helpers"
|
||||||
"github.com/status-im/status-go/whisper"
|
"github.com/status-im/status-go/waku"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TopicPoolSuite struct {
|
type TopicPoolSuite struct {
|
||||||
|
@ -467,7 +467,7 @@ func TestServerIgnoresInboundPeer(t *testing.T) {
|
||||||
topicPool.running = 1
|
topicPool.running = 1
|
||||||
topicPool.maxCachedPeers = 0
|
topicPool.maxCachedPeers = 0
|
||||||
|
|
||||||
whisper := whisper.New(nil)
|
waku := waku.New(&waku.DefaultConfig, nil)
|
||||||
srvkey, err := crypto.GenerateKey()
|
srvkey, err := crypto.GenerateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
server := &p2p.Server{
|
server := &p2p.Server{
|
||||||
|
@ -477,7 +477,7 @@ func TestServerIgnoresInboundPeer(t *testing.T) {
|
||||||
ListenAddr: ":0",
|
ListenAddr: ":0",
|
||||||
PrivateKey: srvkey,
|
PrivateKey: srvkey,
|
||||||
NoDiscovery: true,
|
NoDiscovery: true,
|
||||||
Protocols: whisper.Protocols(),
|
Protocols: waku.Protocols(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
require.NoError(t, server.Start())
|
require.NoError(t, server.Start())
|
||||||
|
@ -490,7 +490,7 @@ func TestServerIgnoresInboundPeer(t *testing.T) {
|
||||||
ListenAddr: ":0",
|
ListenAddr: ":0",
|
||||||
PrivateKey: clientkey,
|
PrivateKey: clientkey,
|
||||||
NoDiscovery: true,
|
NoDiscovery: true,
|
||||||
Protocols: whisper.Protocols(),
|
Protocols: waku.Protocols(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
require.NoError(t, client.Start())
|
require.NoError(t, client.Start())
|
||||||
|
|
|
@ -292,6 +292,7 @@ func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey, timesource comm
|
||||||
Name: name,
|
Name: name,
|
||||||
Timestamp: int64(timesource.GetCurrentTime()),
|
Timestamp: int64(timesource.GetCurrentTime()),
|
||||||
Active: true,
|
Active: true,
|
||||||
|
Joined: int64(timesource.GetCurrentTime()),
|
||||||
ChatType: ChatTypeOneToOne,
|
ChatType: ChatTypeOneToOne,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,6 +310,7 @@ func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat,
|
||||||
Color: color,
|
Color: color,
|
||||||
ID: orgID + chatID,
|
ID: orgID + chatID,
|
||||||
Timestamp: int64(timesource.GetCurrentTime()),
|
Timestamp: int64(timesource.GetCurrentTime()),
|
||||||
|
Joined: int64(timesource.GetCurrentTime()),
|
||||||
ChatType: ChatTypeCommunityChat,
|
ChatType: ChatTypeCommunityChat,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,6 +350,7 @@ func CreatePublicChat(name string, timesource common.TimeSource) *Chat {
|
||||||
Name: name,
|
Name: name,
|
||||||
Active: true,
|
Active: true,
|
||||||
Timestamp: int64(timesource.GetCurrentTime()),
|
Timestamp: int64(timesource.GetCurrentTime()),
|
||||||
|
Joined: int64(timesource.GetCurrentTime()),
|
||||||
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
||||||
ChatType: ChatTypePublic,
|
ChatType: ChatTypePublic,
|
||||||
}
|
}
|
||||||
|
@ -365,6 +368,7 @@ func CreateProfileChat(pubkey string, timesource common.TimeSource) *Chat {
|
||||||
Name: id,
|
Name: id,
|
||||||
Active: true,
|
Active: true,
|
||||||
Timestamp: int64(timesource.GetCurrentTime()),
|
Timestamp: int64(timesource.GetCurrentTime()),
|
||||||
|
Joined: int64(timesource.GetCurrentTime()),
|
||||||
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec
|
||||||
ChatType: ChatTypeProfile,
|
ChatType: ChatTypeProfile,
|
||||||
Profile: pubkey,
|
Profile: pubkey,
|
||||||
|
|
|
@ -420,18 +420,6 @@ func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
|
||||||
s.Require().Len(response.Communities(), 1)
|
s.Require().Len(response.Communities(), 1)
|
||||||
s.Require().True(response.Communities()[0].Joined())
|
s.Require().True(response.Communities()[0].Joined())
|
||||||
s.Require().Len(response.Chats(), 1)
|
s.Require().Len(response.Chats(), 1)
|
||||||
s.Require().Len(response.Filters, 2)
|
|
||||||
|
|
||||||
var orgFilterFound bool
|
|
||||||
var chatFilterFound bool
|
|
||||||
for _, f := range response.Filters {
|
|
||||||
orgFilterFound = orgFilterFound || f.ChatID == response.Communities()[0].IDString()
|
|
||||||
chatFilterFound = chatFilterFound || f.ChatID == response.Chats()[0].ID
|
|
||||||
}
|
|
||||||
// Make sure an community filter has been created
|
|
||||||
s.Require().True(orgFilterFound)
|
|
||||||
// Make sure the chat filter has been created
|
|
||||||
s.Require().True(chatFilterFound)
|
|
||||||
|
|
||||||
chatID := response.Chats()[0].ID
|
chatID := response.Chats()[0].ID
|
||||||
inputMessage := &common.Message{}
|
inputMessage := &common.Message{}
|
||||||
|
@ -479,9 +467,8 @@ func (s *MessengerCommunitiesSuite) TestImportCommunity() {
|
||||||
privateKey, err := s.bob.ExportCommunity(community.ID())
|
privateKey, err := s.bob.ExportCommunity(community.ID())
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
response, err = s.alice.ImportCommunity(privateKey)
|
_, err = s.alice.ImportCommunity(privateKey)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().Len(response.Filters, 1)
|
|
||||||
|
|
||||||
// Invite user on bob side
|
// Invite user on bob side
|
||||||
newUser, err := crypto.GenerateKey()
|
newUser, err := crypto.GenerateKey()
|
||||||
|
|
|
@ -312,19 +312,23 @@ func (m *MessageHandler) HandleSyncInstallationContact(state *ReceivedMessageSta
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MessageHandler) HandleSyncInstallationPublicChat(state *ReceivedMessageState, message protobuf.SyncInstallationPublicChat) bool {
|
func (m *MessageHandler) HandleSyncInstallationPublicChat(state *ReceivedMessageState, message protobuf.SyncInstallationPublicChat) *Chat {
|
||||||
chatID := message.Id
|
chatID := message.Id
|
||||||
_, ok := state.AllChats.Load(chatID)
|
_, ok := state.AllChats.Load(chatID)
|
||||||
if ok {
|
if ok {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
chat := CreatePublicChat(chatID, state.Timesource)
|
chat := CreatePublicChat(chatID, state.Timesource)
|
||||||
|
|
||||||
state.AllChats.Store(chat.ID, chat)
|
timestamp := uint32(state.Timesource.GetCurrentTime() / 1000)
|
||||||
state.Response.AddChat(chat)
|
chat.SyncedTo = timestamp
|
||||||
|
chat.SyncedFrom = timestamp
|
||||||
|
|
||||||
return true
|
state.AllChats.Store(chat.ID, chat)
|
||||||
|
|
||||||
|
state.Response.AddChat(chat)
|
||||||
|
return chat
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MessageHandler) HandlePinMessage(state *ReceivedMessageState, message protobuf.PinMessage) error {
|
func (m *MessageHandler) HandlePinMessage(state *ReceivedMessageState, message protobuf.PinMessage) error {
|
||||||
|
|
|
@ -1369,7 +1369,7 @@ func (db sqlitePersistence) ClearHistory(chat *Chat, currentClockValue uint64) (
|
||||||
// don't shadow original error
|
// don't shadow original error
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
}()
|
}()
|
||||||
err = db.clearHistory(chat, currentClockValue, tx)
|
err = db.clearHistory(chat, currentClockValue, tx, false)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1403,13 +1403,13 @@ func (db sqlitePersistence) deactivateChat(chat *Chat, currentClockValue uint64,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.clearHistory(chat, currentClockValue, tx)
|
return db.clearHistory(chat, currentClockValue, tx, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db sqlitePersistence) clearHistory(chat *Chat, currentClockValue uint64, tx *sql.Tx) error {
|
func (db sqlitePersistence) clearHistory(chat *Chat, currentClockValue uint64, tx *sql.Tx, deactivate bool) error {
|
||||||
// Set deleted at clock value if it's not a public chat so that
|
// Set deleted at clock value if it's not a public chat so that
|
||||||
// old messages will be discarded
|
// old messages will be discarded, or if it's a straight clear history
|
||||||
if !chat.Public() && !chat.ProfileUpdates() && !chat.Timeline() {
|
if !deactivate || (!chat.Public() && !chat.ProfileUpdates() && !chat.Timeline()) {
|
||||||
if chat.LastMessage != nil && chat.LastMessage.Clock != 0 {
|
if chat.LastMessage != nil && chat.LastMessage.Clock != 0 {
|
||||||
chat.DeletedAtClockValue = chat.LastMessage.Clock
|
chat.DeletedAtClockValue = chat.LastMessage.Clock
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
userimage "github.com/status-im/status-go/images"
|
userimage "github.com/status-im/status-go/images"
|
||||||
|
@ -105,6 +106,7 @@ type Messenger struct {
|
||||||
mailserversDatabase *mailservers.Database
|
mailserversDatabase *mailservers.Database
|
||||||
quit chan struct{}
|
quit chan struct{}
|
||||||
requestedCommunities map[string]*transport.Filter
|
requestedCommunities map[string]*transport.Filter
|
||||||
|
connectionState connection.State
|
||||||
|
|
||||||
// TODO(samyoul) Determine if/how the remaining usage of this mutex can be removed
|
// TODO(samyoul) Determine if/how the remaining usage of this mutex can be removed
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
@ -733,17 +735,15 @@ func (m *Messenger) adaptIdentityImageToProtobuf(img *userimage.IdentityImage) *
|
||||||
|
|
||||||
// handleSharedSecrets process the negotiated secrets received from the encryption layer
|
// handleSharedSecrets process the negotiated secrets received from the encryption layer
|
||||||
func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error {
|
func (m *Messenger) handleSharedSecrets(secrets []*sharedsecret.Secret) error {
|
||||||
var result []*transport.Filter
|
|
||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
fSecret := types.NegotiatedSecret{
|
fSecret := types.NegotiatedSecret{
|
||||||
PublicKey: secret.Identity,
|
PublicKey: secret.Identity,
|
||||||
Key: secret.Key,
|
Key: secret.Key,
|
||||||
}
|
}
|
||||||
filter, err := m.transport.ProcessNegotiatedSecret(fSecret)
|
_, err := m.transport.ProcessNegotiatedSecret(fSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
result = append(result, filter)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -933,7 +933,6 @@ func (m *Messenger) Init() error {
|
||||||
publicKeys []*ecdsa.PublicKey
|
publicKeys []*ecdsa.PublicKey
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.Info("1")
|
|
||||||
joinedCommunities, err := m.communitiesManager.Joined()
|
joinedCommunities, err := m.communitiesManager.Joined()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -958,7 +957,6 @@ func (m *Messenger) Init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("2")
|
|
||||||
|
|
||||||
// Get chat IDs and public keys from the existing chats.
|
// Get chat IDs and public keys from the existing chats.
|
||||||
// TODO: Get only active chats by the query.
|
// TODO: Get only active chats by the query.
|
||||||
|
@ -966,7 +964,6 @@ func (m *Messenger) Init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("3")
|
|
||||||
for _, chat := range chats {
|
for _, chat := range chats {
|
||||||
if err := chat.Validate(); err != nil {
|
if err := chat.Validate(); err != nil {
|
||||||
logger.Warn("failed to validate chat", zap.Error(err))
|
logger.Warn("failed to validate chat", zap.Error(err))
|
||||||
|
@ -1006,19 +1003,17 @@ func (m *Messenger) Init() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// uspert profile chat
|
||||||
err = m.ensureMyOwnProfileChat()
|
err = m.ensureMyOwnProfileChat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// uspert profile chat
|
|
||||||
logger.Info("4")
|
|
||||||
|
|
||||||
// Get chat IDs and public keys from the contacts.
|
// Get chat IDs and public keys from the contacts.
|
||||||
contacts, err := m.persistence.Contacts()
|
contacts, err := m.persistence.Contacts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("5")
|
|
||||||
for idx, contact := range contacts {
|
for idx, contact := range contacts {
|
||||||
m.allContacts.Store(contact.ID, contacts[idx])
|
m.allContacts.Store(contact.ID, contacts[idx])
|
||||||
// We only need filters for contacts added by us and not blocked.
|
// We only need filters for contacts added by us and not blocked.
|
||||||
|
@ -1032,7 +1027,6 @@ func (m *Messenger) Init() error {
|
||||||
}
|
}
|
||||||
publicKeys = append(publicKeys, publicKey)
|
publicKeys = append(publicKeys, publicKey)
|
||||||
}
|
}
|
||||||
logger.Info("6")
|
|
||||||
|
|
||||||
installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
|
installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1042,10 +1036,8 @@ func (m *Messenger) Init() error {
|
||||||
for _, installation := range installations {
|
for _, installation := range installations {
|
||||||
m.allInstallations.Store(installation.ID, installation)
|
m.allInstallations.Store(installation.ID, installation)
|
||||||
}
|
}
|
||||||
logger.Info("7")
|
|
||||||
|
|
||||||
_, err = m.transport.InitFilters(publicChatIDs, publicKeys)
|
_, err = m.transport.InitFilters(publicChatIDs, publicKeys)
|
||||||
logger.Info("8")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,6 +1155,7 @@ func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string,
|
||||||
chat.LastClockValue = clock
|
chat.LastClockValue = clock
|
||||||
|
|
||||||
chat.updateChatFromGroupMembershipChanges(group)
|
chat.updateChatFromGroupMembershipChanges(group)
|
||||||
|
chat.Joined = int64(m.getTimesource().GetCurrentTime())
|
||||||
|
|
||||||
clock, _ = chat.NextClockAndTimestamp(m.getTimesource())
|
clock, _ = chat.NextClockAndTimestamp(m.getTimesource())
|
||||||
|
|
||||||
|
@ -1697,6 +1690,7 @@ func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*Me
|
||||||
}
|
}
|
||||||
|
|
||||||
chat.updateChatFromGroupMembershipChanges(group)
|
chat.updateChatFromGroupMembershipChanges(group)
|
||||||
|
chat.Joined = int64(m.getTimesource().GetCurrentTime())
|
||||||
|
|
||||||
response.AddChat(chat)
|
response.AddChat(chat)
|
||||||
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
response.Messages = buildSystemMessages([]v1protocol.MembershipUpdateEvent{event}, m.systemMessagesTranslations)
|
||||||
|
@ -2567,10 +2561,16 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
|
||||||
|
|
||||||
p := msg.ParsedMessage.Interface().(protobuf.SyncInstallationPublicChat)
|
p := msg.ParsedMessage.Interface().(protobuf.SyncInstallationPublicChat)
|
||||||
logger.Debug("Handling SyncInstallationPublicChat", zap.Any("message", p))
|
logger.Debug("Handling SyncInstallationPublicChat", zap.Any("message", p))
|
||||||
added := m.handler.HandleSyncInstallationPublicChat(messageState, p)
|
addedChat := m.handler.HandleSyncInstallationPublicChat(messageState, p)
|
||||||
|
|
||||||
// We re-register as we want to receive mentions from the newly joined public chat
|
// We join and re-register as we want to receive mentions from the newly joined public chat
|
||||||
if added {
|
if addedChat != nil {
|
||||||
|
_, err = m.Join(addedChat)
|
||||||
|
if err != nil {
|
||||||
|
allMessagesProcessed = false
|
||||||
|
logger.Error("error joining chat", zap.Error(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
logger.Debug("newly synced public chat, re-registering for push notifications")
|
logger.Debug("newly synced public chat, re-registering for push notifications")
|
||||||
err := m.reregisterForPushNotifications()
|
err := m.reregisterForPushNotifications()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -47,6 +47,7 @@ func (m *Messenger) CreatePublicChat(request *requests.CreatePublicChat) (*Messe
|
||||||
chat, ok := m.allChats.Load(chatID)
|
chat, ok := m.allChats.Load(chatID)
|
||||||
if !ok {
|
if !ok {
|
||||||
chat = CreatePublicChat(chatID, m.getTimesource())
|
chat = CreatePublicChat(chatID, m.getTimesource())
|
||||||
|
|
||||||
}
|
}
|
||||||
chat.Active = true
|
chat.Active = true
|
||||||
|
|
||||||
|
@ -56,16 +57,36 @@ func (m *Messenger) CreatePublicChat(request *requests.CreatePublicChat) (*Messe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store chat
|
||||||
|
m.allChats.Store(chat.ID, chat)
|
||||||
|
|
||||||
|
willSync, err := m.scheduleSyncChat(chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the synced to, synced from to the default time
|
||||||
|
if !willSync {
|
||||||
|
timestamp := uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncInterval
|
||||||
|
chat.SyncedTo = timestamp
|
||||||
|
chat.SyncedFrom = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
err = m.saveChat(chat)
|
err = m.saveChat(chat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync if it was created
|
||||||
|
if !ok {
|
||||||
|
if err := m.syncPublicChat(context.Background(), chat); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response := &MessengerResponse{}
|
response := &MessengerResponse{}
|
||||||
response.AddChat(chat)
|
response.AddChat(chat)
|
||||||
|
|
||||||
m.scheduleSyncChat(chat)
|
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,16 +116,33 @@ func (m *Messenger) CreateProfileChat(request *requests.CreateProfileChat) (*Mes
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.saveChat(chat)
|
// Store chat
|
||||||
if err != nil {
|
m.allChats.Store(chat.ID, chat)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response := &MessengerResponse{}
|
response := &MessengerResponse{}
|
||||||
response.AddChat(chat)
|
response.AddChat(chat)
|
||||||
|
|
||||||
m.scheduleSyncChat(chat)
|
willSync, err := m.scheduleSyncChat(chat)
|
||||||
m.scheduleSyncFilter(filter)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the synced to, synced from to the default time
|
||||||
|
if !willSync {
|
||||||
|
timestamp := uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncInterval
|
||||||
|
chat.SyncedTo = timestamp
|
||||||
|
chat.SyncedFrom = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = m.scheduleSyncFilters([]*transport.Filter{filter})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.saveChat(chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
@ -131,19 +169,28 @@ func (m *Messenger) CreateOneToOneChat(request *requests.CreateOneToOneChat) (*M
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.saveChat(chat)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(Samyoul) remove storing of an updated reference pointer?
|
// TODO(Samyoul) remove storing of an updated reference pointer?
|
||||||
m.allChats.Store(chatID, chat)
|
m.allChats.Store(chatID, chat)
|
||||||
|
|
||||||
response := &MessengerResponse{}
|
response := &MessengerResponse{}
|
||||||
response.AddChat(chat)
|
response.AddChat(chat)
|
||||||
|
|
||||||
m.scheduleSyncFilters(filters)
|
willSync, err := m.scheduleSyncFilters(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the synced to, synced from to the default time
|
||||||
|
if !willSync {
|
||||||
|
timestamp := uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncInterval
|
||||||
|
chat.SyncedTo = timestamp
|
||||||
|
chat.SyncedFrom = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.saveChat(chat)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return response, nil
|
return response, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -340,12 +387,12 @@ func (m *Messenger) ensureTimelineChat() error {
|
||||||
|
|
||||||
func (m *Messenger) ensureMyOwnProfileChat() error {
|
func (m *Messenger) ensureMyOwnProfileChat() error {
|
||||||
chatID := common.PubkeyToHex(&m.identity.PublicKey)
|
chatID := common.PubkeyToHex(&m.identity.PublicKey)
|
||||||
chat, ok := m.allChats.Load(chatID)
|
_, ok := m.allChats.Load(chatID)
|
||||||
if ok {
|
if ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
chat = m.buildProfileChat(chatID)
|
chat := m.buildProfileChat(chatID)
|
||||||
|
|
||||||
chat.Active = true
|
chat.Active = true
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,18 @@ func (m *Messenger) joinCommunity(communityID types.HexBytes) (*MessengerRespons
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.scheduleSyncFilters(filters)
|
willSync, err := m.scheduleSyncFilters(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !willSync {
|
||||||
|
timestamp := uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncInterval
|
||||||
|
for idx := range chats {
|
||||||
|
chats[idx].SyncedTo = timestamp
|
||||||
|
chats[idx].SyncedFrom = timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response.AddCommunity(community)
|
response.AddCommunity(community)
|
||||||
|
|
||||||
|
@ -308,7 +319,10 @@ func (m *Messenger) CreateCommunityChat(communityID types.HexBytes, c *protobuf.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.scheduleSyncFilters(filters)
|
_, err = m.scheduleSyncFilters(filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &response, m.saveChats(chats)
|
return &response, m.saveChats(chats)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"github.com/status-im/status-go/protocol/common"
|
||||||
|
@ -15,19 +16,48 @@ import (
|
||||||
"github.com/status-im/status-go/services/mailservers"
|
"github.com/status-im/status-go/services/mailservers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Messenger) connectedToMailserver() bool {
|
// defaultSyncInterval indicates how far back in seconds we should sync a filter
|
||||||
return m.online() && m.mailserver != nil
|
var defaultSyncInterval uint32 = 60 * 60 * 24
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Messenger) scheduleSyncChat(chat *Chat) {
|
// tolerance is how many seconds of potentially out-of-order messages we want to fetch
|
||||||
useMailservers, err := m.settings.GetUseMailservers()
|
var tolerance uint32 = 60
|
||||||
if err != nil {
|
|
||||||
m.logger.Error("failed to get use mailservers", zap.Error(err))
|
func (m *Messenger) shouldSync() (bool, error) {
|
||||||
return
|
if m.mailserver == nil || !m.online() {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useMailservers || !m.connectedToMailserver() {
|
useMailserver, err := m.settings.CanUseMailservers()
|
||||||
return
|
if err != nil {
|
||||||
|
m.logger.Error("failed to get use mailservers", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !useMailserver {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.connectionState.IsExpensive() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syncingOnMobileNetwork, err := m.settings.CanSyncOnMobileNetwork()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncingOnMobileNetwork, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) scheduleSyncChat(chat *Chat) (bool, error) {
|
||||||
|
shouldSync, err := m.shouldSync()
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("failed to get should sync", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shouldSync {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -43,21 +73,25 @@ func (m *Messenger) scheduleSyncChat(chat *Chat) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) scheduleSyncFilter(filter *transport.Filter) {
|
func (m *Messenger) scheduleSyncFilter(filter *transport.Filter) {
|
||||||
m.scheduleSyncFilters([]*transport.Filter{filter})
|
_, err := m.scheduleSyncFilters([]*transport.Filter{filter})
|
||||||
|
|
||||||
}
|
|
||||||
func (m *Messenger) scheduleSyncFilters(filters []*transport.Filter) {
|
|
||||||
useMailservers, err := m.settings.GetUseMailservers()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("failed to get use mailservers", zap.Error(err))
|
m.logger.Error("failed to schedule syncing filters", zap.Error(err))
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useMailservers || !m.connectedToMailserver() {
|
}
|
||||||
return
|
func (m *Messenger) scheduleSyncFilters(filters []*transport.Filter) (bool, error) {
|
||||||
|
shouldSync, err := m.shouldSync()
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Error("failed to get shouldSync", zap.Error(err))
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shouldSync {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -73,6 +107,7 @@ func (m *Messenger) scheduleSyncFilters(filters []*transport.Filter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) calculateMailserverTo() uint32 {
|
func (m *Messenger) calculateMailserverTo() uint32 {
|
||||||
|
@ -135,31 +170,27 @@ func (m *Messenger) syncChat(chatID string) (*MessengerResponse, error) {
|
||||||
return m.syncFilters(filters)
|
return m.syncFilters(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) defaultSyncInterval() uint32 {
|
|
||||||
return 60 * 60 * 24
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Messenger) defaultSyncPeriod() uint32 {
|
func (m *Messenger) defaultSyncPeriod() uint32 {
|
||||||
return uint32(m.getTimesource().GetCurrentTime()/1000) - m.defaultSyncInterval()
|
return uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
// capSyncPeriod caps the sync period to the default
|
// calculateSyncPeriod caps the sync period to the default
|
||||||
func (m *Messenger) capSyncPeriod(period uint32) uint32 {
|
func (m *Messenger) calculateSyncPeriod(period uint32) uint32 {
|
||||||
d := uint32(m.defaultSyncPeriod())
|
d := m.defaultSyncPeriod()
|
||||||
if d > period {
|
if d > period {
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
return period
|
return period - tolerance
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestAllHistoricMessages requests all the historic messages for any topic
|
// RequestAllHistoricMessages requests all the historic messages for any topic
|
||||||
func (m *Messenger) RequestAllHistoricMessages() (*MessengerResponse, error) {
|
func (m *Messenger) RequestAllHistoricMessages() (*MessengerResponse, error) {
|
||||||
useMailservers, err := m.settings.GetUseMailservers()
|
shouldSync, err := m.shouldSync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useMailservers || !m.connectedToMailserver() {
|
if !shouldSync {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,8 +211,6 @@ func (m *Messenger) syncFilters(filters []*transport.Filter) (*MessengerResponse
|
||||||
|
|
||||||
batches := make(map[int]MailserverBatch)
|
batches := make(map[int]MailserverBatch)
|
||||||
|
|
||||||
var syncedChatIDs []string
|
|
||||||
|
|
||||||
to := m.calculateMailserverTo()
|
to := m.calculateMailserverTo()
|
||||||
var syncedTopics []mailservers.MailserverTopic
|
var syncedTopics []mailservers.MailserverTopic
|
||||||
for _, filter := range filters {
|
for _, filter := range filters {
|
||||||
|
@ -197,8 +226,6 @@ func (m *Messenger) syncFilters(filters []*transport.Filter) (*MessengerResponse
|
||||||
chatID = filter.ChatID
|
chatID = filter.ChatID
|
||||||
}
|
}
|
||||||
|
|
||||||
syncedChatIDs = append(syncedChatIDs, chatID)
|
|
||||||
|
|
||||||
topicData, ok := topicsData[filter.Topic.String()]
|
topicData, ok := topicsData[filter.Topic.String()]
|
||||||
if !ok {
|
if !ok {
|
||||||
topicData = mailservers.MailserverTopic{
|
topicData = mailservers.MailserverTopic{
|
||||||
|
@ -208,7 +235,7 @@ func (m *Messenger) syncFilters(filters []*transport.Filter) (*MessengerResponse
|
||||||
}
|
}
|
||||||
batch, ok := batches[topicData.LastRequest]
|
batch, ok := batches[topicData.LastRequest]
|
||||||
if !ok {
|
if !ok {
|
||||||
from := m.capSyncPeriod(uint32(topicData.LastRequest))
|
from := m.calculateSyncPeriod(uint32(topicData.LastRequest))
|
||||||
batch = MailserverBatch{From: from, To: to}
|
batch = MailserverBatch{From: from, To: to}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,9 +287,6 @@ func (m *Messenger) syncFilters(filters []*transport.Filter) (*MessengerResponse
|
||||||
response.AddMessage(gap)
|
response.AddMessage(gap)
|
||||||
messagesToBeSaved = append(messagesToBeSaved, gap)
|
messagesToBeSaved = append(messagesToBeSaved, gap)
|
||||||
}
|
}
|
||||||
// Calculate gaps
|
|
||||||
// If last-synced is 0, no gaps
|
|
||||||
// If last-synced < from, create gap from
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +388,7 @@ func (m *Messenger) SyncChatFromSyncedFrom(chatID string) (uint32, error) {
|
||||||
batch := MailserverBatch{
|
batch := MailserverBatch{
|
||||||
ChatIDs: []string{chatID},
|
ChatIDs: []string{chatID},
|
||||||
To: chat.SyncedFrom,
|
To: chat.SyncedFrom,
|
||||||
From: chat.SyncedFrom - m.defaultSyncInterval(),
|
From: chat.SyncedFrom - defaultSyncInterval,
|
||||||
Topics: topics,
|
Topics: topics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,3 +463,7 @@ func (m *Messenger) LoadFilters(filters []*transport.Filter) ([]*transport.Filte
|
||||||
func (m *Messenger) RemoveFilters(filters []*transport.Filter) error {
|
func (m *Messenger) RemoveFilters(filters []*transport.Filter) error {
|
||||||
return m.transport.RemoveFilters(filters)
|
return m.transport.RemoveFilters(filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) ConnectionChanged(state connection.State) {
|
||||||
|
m.connectionState = state
|
||||||
|
}
|
||||||
|
|
|
@ -80,10 +80,30 @@ func (s *MessengerMuteSuite) TestSetMute() {
|
||||||
|
|
||||||
s.Require().NoError(s.m.MuteChat(chatID))
|
s.Require().NoError(s.m.MuteChat(chatID))
|
||||||
|
|
||||||
s.Require().Len(s.m.Chats(), 1)
|
allChats := s.m.Chats()
|
||||||
s.Require().True(s.m.Chats()[0].Muted)
|
s.Require().Len(allChats, 3)
|
||||||
|
|
||||||
|
var actualChat *Chat
|
||||||
|
|
||||||
|
for idx := range allChats {
|
||||||
|
if chat.ID == allChats[idx].ID {
|
||||||
|
actualChat = allChats[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Require().NotNil(actualChat)
|
||||||
|
s.Require().True(actualChat.Muted)
|
||||||
|
|
||||||
s.Require().NoError(s.m.UnmuteChat(chatID))
|
s.Require().NoError(s.m.UnmuteChat(chatID))
|
||||||
s.Require().False(s.m.Chats()[0].Muted)
|
|
||||||
|
allChats = s.m.Chats()
|
||||||
|
|
||||||
|
for idx := range allChats {
|
||||||
|
if chat.ID == allChats[idx].ID {
|
||||||
|
actualChat = allChats[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Require().False(actualChat.Muted)
|
||||||
s.Require().NoError(theirMessenger.Shutdown())
|
s.Require().NoError(theirMessenger.Shutdown())
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ func (s *MessengerSuite) TestInit() {
|
||||||
err = s.m.SaveContact(&contact)
|
err = s.m.SaveContact(&contact)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
},
|
},
|
||||||
AddedFilters: 0,
|
AddedFilters: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "added by them contact",
|
Name: "added by them contact",
|
||||||
|
@ -304,7 +304,7 @@ func (s *MessengerSuite) TestInit() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
filters := s.m.transport.Filters()
|
filters := s.m.transport.Filters()
|
||||||
expectedFilters += tc.AddedFilters
|
expectedFilters += tc.AddedFilters
|
||||||
s.Equal(expectedFilters, len(filters))
|
s.Equal(expectedFilters+1, len(filters))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -356,8 +356,11 @@ func (s *MessengerSuite) TestMarkMessagesSeen() {
|
||||||
s.Require().Equal(uint64(0), count)
|
s.Require().Equal(uint64(0), count)
|
||||||
|
|
||||||
chats := s.m.Chats()
|
chats := s.m.Chats()
|
||||||
s.Require().Len(chats, 1)
|
for _, c := range chats {
|
||||||
s.Require().Equal(uint(1), chats[0].UnviewedMessagesCount)
|
if c.ID == chat.ID {
|
||||||
|
s.Require().Equal(uint(1), c.UnviewedMessagesCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerSuite) TestMarkAllRead() {
|
func (s *MessengerSuite) TestMarkAllRead() {
|
||||||
|
@ -379,8 +382,12 @@ func (s *MessengerSuite) TestMarkAllRead() {
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
chats := s.m.Chats()
|
chats := s.m.Chats()
|
||||||
s.Require().Len(chats, 1)
|
s.Require().Len(chats, 3)
|
||||||
s.Require().Equal(uint(0), chats[0].UnviewedMessagesCount)
|
for idx := range chats {
|
||||||
|
if chats[idx].ID == chat.ID {
|
||||||
|
s.Require().Equal(uint(0), chats[idx].UnviewedMessagesCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerSuite) TestSendPublic() {
|
func (s *MessengerSuite) TestSendPublic() {
|
||||||
|
@ -1044,11 +1051,7 @@ func (s *MessengerSuite) TestChatPersistencePublic() {
|
||||||
|
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
savedChats := s.m.Chats()
|
savedChats := s.m.Chats()
|
||||||
s.Require().Equal(1, len(savedChats))
|
s.Require().Equal(3, len(savedChats))
|
||||||
|
|
||||||
actualChat := savedChats[0]
|
|
||||||
|
|
||||||
s.Require().Equal(chat, actualChat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerSuite) TestDeleteChat() {
|
func (s *MessengerSuite) TestDeleteChat() {
|
||||||
|
@ -1068,11 +1071,11 @@ func (s *MessengerSuite) TestDeleteChat() {
|
||||||
|
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
savedChats := s.m.Chats()
|
savedChats := s.m.Chats()
|
||||||
s.Require().Equal(1, len(savedChats))
|
s.Require().Equal(3, len(savedChats))
|
||||||
|
|
||||||
s.Require().NoError(s.m.DeleteChat(chatID))
|
s.Require().NoError(s.m.DeleteChat(chatID))
|
||||||
savedChats = s.m.Chats()
|
savedChats = s.m.Chats()
|
||||||
s.Require().Equal(0, len(savedChats))
|
s.Require().Equal(2, len(savedChats))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerSuite) TestChatPersistenceUpdate() {
|
func (s *MessengerSuite) TestChatPersistenceUpdate() {
|
||||||
|
@ -1091,18 +1094,29 @@ func (s *MessengerSuite) TestChatPersistenceUpdate() {
|
||||||
|
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
savedChats := s.m.Chats()
|
savedChats := s.m.Chats()
|
||||||
s.Require().Equal(1, len(savedChats))
|
s.Require().Equal(3, len(savedChats))
|
||||||
|
|
||||||
actualChat := savedChats[0]
|
var actualChat *Chat
|
||||||
|
for idx := range savedChats {
|
||||||
|
if savedChats[idx].ID == chat.ID {
|
||||||
|
actualChat = chat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Require().NotNil(actualChat)
|
||||||
s.Require().Equal(chat, actualChat)
|
s.Require().Equal(chat, actualChat)
|
||||||
|
|
||||||
chat.Name = "updated-name-1"
|
chat.Name = "updated-name-1"
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
updatedChats := s.m.Chats()
|
|
||||||
s.Require().Equal(1, len(updatedChats))
|
|
||||||
|
|
||||||
actualUpdatedChat := updatedChats[0]
|
var actualUpdatedChat *Chat
|
||||||
|
updatedChats := s.m.Chats()
|
||||||
|
|
||||||
|
for idx := range updatedChats {
|
||||||
|
if updatedChats[idx].ID == chat.ID {
|
||||||
|
actualUpdatedChat = chat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.Require().Equal(chat, actualUpdatedChat)
|
s.Require().Equal(chat, actualUpdatedChat)
|
||||||
}
|
}
|
||||||
|
@ -1133,9 +1147,15 @@ func (s *MessengerSuite) TestChatPersistenceOneToOne() {
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
s.Require().NoError(s.m.SaveContact(&contact))
|
s.Require().NoError(s.m.SaveContact(&contact))
|
||||||
savedChats := s.m.Chats()
|
savedChats := s.m.Chats()
|
||||||
s.Require().Equal(1, len(savedChats))
|
s.Require().Equal(3, len(savedChats))
|
||||||
|
|
||||||
actualChat := savedChats[0]
|
var actualChat *Chat
|
||||||
|
for idx := range savedChats {
|
||||||
|
if chat.ID == savedChats[idx].ID {
|
||||||
|
actualChat = savedChats[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
actualPk, err := actualChat.PublicKey()
|
actualPk, err := actualChat.PublicKey()
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
@ -1210,9 +1230,15 @@ func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() {
|
||||||
}
|
}
|
||||||
s.Require().NoError(s.m.SaveChat(chat))
|
s.Require().NoError(s.m.SaveChat(chat))
|
||||||
savedChats := s.m.Chats()
|
savedChats := s.m.Chats()
|
||||||
s.Require().Equal(1, len(savedChats))
|
s.Require().Equal(3, len(savedChats))
|
||||||
|
|
||||||
actualChat := savedChats[0]
|
var actualChat *Chat
|
||||||
|
for idx := range savedChats {
|
||||||
|
if savedChats[idx].ID == chat.ID {
|
||||||
|
actualChat = savedChats[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
s.Require().Equal(chat, actualChat)
|
s.Require().Equal(chat, actualChat)
|
||||||
}
|
}
|
||||||
|
@ -1365,17 +1391,25 @@ func (s *MessengerSuite) TestBlockContact() {
|
||||||
response, err := s.m.BlockContact(&contact)
|
response, err := s.m.BlockContact(&contact)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
|
var actualChat2, actualChat3 *Chat
|
||||||
|
for idx := range response {
|
||||||
|
if response[idx].ID == chat2.ID {
|
||||||
|
actualChat2 = response[idx]
|
||||||
|
} else if response[idx].ID == chat3.ID {
|
||||||
|
actualChat3 = response[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
// The new unviewed count is updated
|
// The new unviewed count is updated
|
||||||
s.Require().Equal(uint(1), response[0].UnviewedMessagesCount)
|
s.Require().Equal(uint(1), actualChat3.UnviewedMessagesCount)
|
||||||
s.Require().Equal(uint(2), response[1].UnviewedMessagesCount)
|
s.Require().Equal(uint(2), actualChat2.UnviewedMessagesCount)
|
||||||
|
|
||||||
// The new message content is updated
|
// The new message content is updated
|
||||||
s.Require().NotNil(response[0].LastMessage)
|
s.Require().NotNil(actualChat3.LastMessage)
|
||||||
|
|
||||||
s.Require().Equal("test-7", response[0].LastMessage.ID)
|
s.Require().Equal("test-7", actualChat3.LastMessage.ID)
|
||||||
|
|
||||||
s.Require().NotNil(response[1].LastMessage)
|
s.Require().NotNil(actualChat2.LastMessage)
|
||||||
s.Require().Equal("test-5", response[1].LastMessage.ID)
|
s.Require().Equal("test-5", actualChat2.LastMessage.ID)
|
||||||
|
|
||||||
// The contact is updated
|
// The contact is updated
|
||||||
savedContacts := s.m.Contacts()
|
savedContacts := s.m.Contacts()
|
||||||
|
@ -1384,7 +1418,7 @@ func (s *MessengerSuite) TestBlockContact() {
|
||||||
|
|
||||||
// The chat is deleted
|
// The chat is deleted
|
||||||
actualChats := s.m.Chats()
|
actualChats := s.m.Chats()
|
||||||
s.Require().Equal(2, len(actualChats))
|
s.Require().Equal(4, len(actualChats))
|
||||||
|
|
||||||
// The messages have been deleted
|
// The messages have been deleted
|
||||||
chat2Messages, _, err := s.m.MessageByChatID(chat2.ID, "", 20)
|
chat2Messages, _, err := s.m.MessageByChatID(chat2.ID, "", 20)
|
||||||
|
@ -2155,11 +2189,6 @@ type MockEthClient struct {
|
||||||
messages map[string]MockTransaction
|
messages map[string]MockTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockSendMessagesRequest struct {
|
|
||||||
types.Waku
|
|
||||||
req types.MessagesRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m MockEthClient) TransactionByHash(ctx context.Context, hash types.Hash) (coretypes.Message, coretypes.TransactionStatus, error) {
|
func (m MockEthClient) TransactionByHash(ctx context.Context, hash types.Hash) (coretypes.Message, coretypes.TransactionStatus, error) {
|
||||||
mockTransaction, ok := m.messages[hash.Hex()]
|
mockTransaction, ok := m.messages[hash.Hex()]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -2168,11 +2197,6 @@ func (m MockEthClient) TransactionByHash(ctx context.Context, hash types.Hash) (
|
||||||
return mockTransaction.Message, mockTransaction.Status, nil
|
return mockTransaction.Message, mockTransaction.Status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockSendMessagesRequest) SendMessagesRequest(peerID []byte, request types.MessagesRequest) error {
|
|
||||||
m.req = request
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MessengerSuite) TestMessageJSON() {
|
func (s *MessengerSuite) TestMessageJSON() {
|
||||||
message := &common.Message{
|
message := &common.Message{
|
||||||
ID: "test-1",
|
ID: "test-1",
|
||||||
|
@ -2187,36 +2211,8 @@ func (s *MessengerSuite) TestMessageJSON() {
|
||||||
From: "from-field",
|
From: "from-field",
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedJSON := `{"id":"test-1","whisperTimestamp":0,"from":"from-field","alias":"alias","identicon":"","seen":false,"quotedMessage":null,"rtl":false,"lineCount":0,"text":"test-1","chatId":"remote-chat-id","localChatId":"local-chat-id","clock":1,"replace":"","responseTo":"","ensName":"","sticker":null,"commandParameters":null,"timestamp":0,"contentType":0,"messageType":0}`
|
_, err := json.Marshal(message)
|
||||||
|
|
||||||
messageJSON, err := json.Marshal(message)
|
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().Equal(expectedJSON, string(messageJSON))
|
|
||||||
|
|
||||||
decodedMessage := &common.Message{}
|
|
||||||
err = json.Unmarshal([]byte(expectedJSON), decodedMessage)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Equal(message, decodedMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MessengerSuite) TestRequestHistoricMessagesRequest() {
|
|
||||||
shh := &mockSendMessagesRequest{
|
|
||||||
Waku: s.shh,
|
|
||||||
}
|
|
||||||
m := s.newMessenger(shh)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
|
|
||||||
defer cancel()
|
|
||||||
m.mailserver = []byte("mailserver-id")
|
|
||||||
cursor, err := m.RequestHistoricMessages(ctx, 10, 20, []byte{0x01}, true)
|
|
||||||
s.EqualError(err, ctx.Err().Error())
|
|
||||||
s.Empty(cursor)
|
|
||||||
// verify request is correct
|
|
||||||
s.NotEmpty(shh.req.ID)
|
|
||||||
s.EqualValues(10, shh.req.From)
|
|
||||||
s.EqualValues(20, shh.req.To)
|
|
||||||
s.EqualValues(100, shh.req.Limit)
|
|
||||||
s.Equal([]byte{0x01}, shh.req.Cursor)
|
|
||||||
s.NotEmpty(shh.req.Bloom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MessengerSuite) TestSentEventTracking() {
|
func (s *MessengerSuite) TestSentEventTracking() {
|
||||||
|
|
|
@ -130,8 +130,8 @@ func (db sqlitePersistence) saveChat(tx *sql.Tx, chat Chat) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert record
|
// Insert record
|
||||||
stmt, err := tx.Prepare(`INSERT INTO chats(id, name, color, active, type, timestamp, deleted_at_clock_value, unviewed_message_count, last_clock_value, last_message, members, membership_updates, muted, invitation_admin, profile, community_id, joined)
|
stmt, err := tx.Prepare(`INSERT INTO chats(id, name, color, active, type, timestamp, deleted_at_clock_value, unviewed_message_count, last_clock_value, last_message, members, membership_updates, muted, invitation_admin, profile, community_id, joined, synced_from, synced_to)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?,?,?, ?)`)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?,?,?,?,?,?)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,8 @@ func (db sqlitePersistence) saveChat(tx *sql.Tx, chat Chat) error {
|
||||||
chat.Profile,
|
chat.Profile,
|
||||||
chat.CommunityID,
|
chat.CommunityID,
|
||||||
chat.Joined,
|
chat.Joined,
|
||||||
|
chat.SyncedFrom,
|
||||||
|
chat.SyncedTo,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
var ErrAcceptRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id")
|
var ErrAcceptRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id")
|
||||||
|
|
||||||
type AcceptRequestToJoinCommunity struct {
|
type AcceptRequestToJoinCommunity struct {
|
||||||
ID types.HexBytes
|
ID types.HexBytes `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *AcceptRequestToJoinCommunity) Validate() error {
|
func (j *AcceptRequestToJoinCommunity) Validate() error {
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
var ErrClearHistoryInvalidID = errors.New("clear-history: invalid id")
|
var ErrClearHistoryInvalidID = errors.New("clear-history: invalid id")
|
||||||
|
|
||||||
type ClearHistory struct {
|
type ClearHistory struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *ClearHistory) Validate() error {
|
func (c *ClearHistory) Validate() error {
|
||||||
if len(j.ID) == 0 {
|
if len(c.ID) == 0 {
|
||||||
return ErrClearHistoryInvalidID
|
return ErrClearHistoryInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
var ErrCreateOneToOneChatInvalidID = errors.New("create-one-to-one-chat: invalid id")
|
var ErrCreateOneToOneChatInvalidID = errors.New("create-one-to-one-chat: invalid id")
|
||||||
|
|
||||||
type CreateOneToOneChat struct {
|
type CreateOneToOneChat struct {
|
||||||
ID types.HexBytes
|
ID types.HexBytes `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CreateOneToOneChat) Validate() error {
|
func (c *CreateOneToOneChat) Validate() error {
|
||||||
if len(j.ID) == 0 {
|
if len(c.ID) == 0 {
|
||||||
return ErrCreateOneToOneChatInvalidID
|
return ErrCreateOneToOneChatInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
var ErrCreateProfileChatInvalidID = errors.New("create-public-chat: invalid id")
|
var ErrCreateProfileChatInvalidID = errors.New("create-public-chat: invalid id")
|
||||||
|
|
||||||
type CreateProfileChat struct {
|
type CreateProfileChat struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CreateProfileChat) Validate() error {
|
func (c *CreateProfileChat) Validate() error {
|
||||||
if len(j.ID) == 0 {
|
if len(c.ID) == 0 {
|
||||||
return ErrCreateProfileChatInvalidID
|
return ErrCreateProfileChatInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
var ErrCreatePublicChatInvalidID = errors.New("create-public-chat: invalid id")
|
var ErrCreatePublicChatInvalidID = errors.New("create-public-chat: invalid id")
|
||||||
|
|
||||||
type CreatePublicChat struct {
|
type CreatePublicChat struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CreatePublicChat) Validate() error {
|
func (c *CreatePublicChat) Validate() error {
|
||||||
if len(j.ID) == 0 {
|
if len(c.ID) == 0 {
|
||||||
return ErrCreatePublicChatInvalidID
|
return ErrCreatePublicChatInvalidID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
var ErrDeactivateChatInvalidID = errors.New("deactivate-chat: invalid id")
|
var ErrDeactivateChatInvalidID = errors.New("deactivate-chat: invalid id")
|
||||||
|
|
||||||
type DeactivateChat struct {
|
type DeactivateChat struct {
|
||||||
ID string
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *DeactivateChat) Validate() error {
|
func (j *DeactivateChat) Validate() error {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
var ErrDeclineRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id")
|
var ErrDeclineRequestToJoinCommunityInvalidID = errors.New("accept-request-to-join-community: invalid id")
|
||||||
|
|
||||||
type DeclineRequestToJoinCommunity struct {
|
type DeclineRequestToJoinCommunity struct {
|
||||||
ID types.HexBytes
|
ID types.HexBytes `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *DeclineRequestToJoinCommunity) Validate() error {
|
func (j *DeclineRequestToJoinCommunity) Validate() error {
|
||||||
|
|
|
@ -10,8 +10,8 @@ var ErrInviteUsersToCommunityInvalidID = errors.New("invite-users-to-community:
|
||||||
var ErrInviteUsersToCommunityEmptyUsers = errors.New("invite-users-to-community: empty users")
|
var ErrInviteUsersToCommunityEmptyUsers = errors.New("invite-users-to-community: empty users")
|
||||||
|
|
||||||
type InviteUsersToCommunity struct {
|
type InviteUsersToCommunity struct {
|
||||||
CommunityID types.HexBytes
|
CommunityID types.HexBytes `json:"communityId"`
|
||||||
Users []types.HexBytes
|
Users []types.HexBytes `json:"users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *InviteUsersToCommunity) Validate() error {
|
func (j *InviteUsersToCommunity) Validate() error {
|
||||||
|
|
|
@ -10,8 +10,8 @@ var ErrShareCommunityInvalidID = errors.New("share-community: invalid id")
|
||||||
var ErrShareCommunityEmptyUsers = errors.New("share-community: empty users")
|
var ErrShareCommunityEmptyUsers = errors.New("share-community: empty users")
|
||||||
|
|
||||||
type ShareCommunity struct {
|
type ShareCommunity struct {
|
||||||
CommunityID types.HexBytes
|
CommunityID types.HexBytes `json:"communityId"`
|
||||||
Users []types.HexBytes
|
Users []types.HexBytes `json:"users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *ShareCommunity) Validate() error {
|
func (j *ShareCommunity) Validate() error {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/whisper"
|
"github.com/status-im/status-go/waku"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testKeysPersistence struct {
|
type testKeysPersistence struct {
|
||||||
|
@ -90,9 +90,9 @@ func (s *FiltersManagerSuite) SetupTest() {
|
||||||
|
|
||||||
keysPersistence := newTestKeysPersistence()
|
keysPersistence := newTestKeysPersistence()
|
||||||
|
|
||||||
whisper := gethbridge.NewGethWhisperWrapper(whisper.New(nil))
|
waku := gethbridge.NewGethWakuWrapper(waku.New(&waku.DefaultConfig, nil))
|
||||||
|
|
||||||
s.chats, err = NewFiltersManager(keysPersistence, whisper, s.manager[0].privateKey, s.logger)
|
s.chats, err = NewFiltersManager(keysPersistence, waku, s.manager[0].privateKey, s.logger)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"math/big"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -475,8 +474,8 @@ func createMessagesRequest(from, to uint32, cursor []byte, topics []types.TopicT
|
||||||
// uuid is 16 bytes, converted to hex it's 32 bytes as expected by types.MessagesRequest
|
// uuid is 16 bytes, converted to hex it's 32 bytes as expected by types.MessagesRequest
|
||||||
id := []byte(hex.EncodeToString(aUUID[:]))
|
id := []byte(hex.EncodeToString(aUUID[:]))
|
||||||
var topicBytes [][]byte
|
var topicBytes [][]byte
|
||||||
for _, t := range topics {
|
for idx := range topics {
|
||||||
topicBytes = append(topicBytes, t[:])
|
topicBytes = append(topicBytes, topics[idx][:])
|
||||||
}
|
}
|
||||||
return types.MessagesRequest{
|
return types.MessagesRequest{
|
||||||
ID: id,
|
ID: id,
|
||||||
|
@ -488,20 +487,6 @@ func createMessagesRequest(from, to uint32, cursor []byte, topics []types.TopicT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) waitForRequestCompleted(ctx context.Context, requestID []byte, events chan types.EnvelopeEvent) (*types.MailServerResponse, error) {
|
func (t *Transport) waitForRequestCompleted(ctx context.Context, requestID []byte, events chan types.EnvelopeEvent) (*types.MailServerResponse, error) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -1,520 +0,0 @@
|
||||||
package waku
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"database/sql"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNoMailservers returned if there is no configured mailservers that can be used.
|
|
||||||
ErrNoMailservers = errors.New("no configured mailservers")
|
|
||||||
)
|
|
||||||
|
|
||||||
type wakuServiceKeysManager struct {
|
|
||||||
waku types.Waku
|
|
||||||
|
|
||||||
// Identity of the current user.
|
|
||||||
privateKey *ecdsa.PrivateKey
|
|
||||||
|
|
||||||
passToSymKeyMutex sync.RWMutex
|
|
||||||
passToSymKeyCache map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *wakuServiceKeysManager) AddOrGetKeyPair(priv *ecdsa.PrivateKey) (string, error) {
|
|
||||||
// caching is handled in waku
|
|
||||||
return m.waku.AddKeyPair(priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *wakuServiceKeysManager) AddOrGetSymKeyFromPassword(password string) (string, error) {
|
|
||||||
m.passToSymKeyMutex.Lock()
|
|
||||||
defer m.passToSymKeyMutex.Unlock()
|
|
||||||
|
|
||||||
if val, ok := m.passToSymKeyCache[password]; ok {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := m.waku.AddSymKeyFromPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.passToSymKeyCache[password] = id
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *wakuServiceKeysManager) RawSymKey(id string) ([]byte, error) {
|
|
||||||
return m.waku.GetSymKey(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*Transport) error
|
|
||||||
|
|
||||||
// Transport is a transport based on Whisper service.
|
|
||||||
type Transport struct {
|
|
||||||
waku types.Waku
|
|
||||||
api types.PublicWakuAPI // only PublicWakuAPI implements logic to send messages
|
|
||||||
keysManager *wakuServiceKeysManager
|
|
||||||
filters *transport.FiltersManager
|
|
||||||
logger *zap.Logger
|
|
||||||
cache *transport.ProcessedMessageIDsCache
|
|
||||||
|
|
||||||
mailservers []string
|
|
||||||
envelopesMonitor *EnvelopesMonitor
|
|
||||||
quit chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransport returns a new Transport.
|
|
||||||
// TODO: leaving a chat should verify that for a given public key
|
|
||||||
// there are no other chats. It may happen that we leave a private chat
|
|
||||||
// but still have a public chat for a given public key.
|
|
||||||
func NewTransport(
|
|
||||||
waku types.Waku,
|
|
||||||
privateKey *ecdsa.PrivateKey,
|
|
||||||
db *sql.DB,
|
|
||||||
mailservers []string,
|
|
||||||
envelopesMonitorConfig *transport.EnvelopesMonitorConfig,
|
|
||||||
logger *zap.Logger,
|
|
||||||
opts ...Option,
|
|
||||||
) (*Transport, error) {
|
|
||||||
filtersManager, err := transport.NewFiltersManager(newSQLitePersistence(db), waku, privateKey, logger)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var envelopesMonitor *EnvelopesMonitor
|
|
||||||
if envelopesMonitorConfig != nil {
|
|
||||||
envelopesMonitor = NewEnvelopesMonitor(waku, *envelopesMonitorConfig)
|
|
||||||
envelopesMonitor.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
var api types.PublicWhisperAPI
|
|
||||||
if waku != nil {
|
|
||||||
api = waku.PublicWakuAPI()
|
|
||||||
}
|
|
||||||
t := &Transport{
|
|
||||||
waku: waku,
|
|
||||||
api: api,
|
|
||||||
cache: transport.NewProcessedMessageIDsCache(db),
|
|
||||||
envelopesMonitor: envelopesMonitor,
|
|
||||||
quit: make(chan struct{}),
|
|
||||||
keysManager: &wakuServiceKeysManager{
|
|
||||||
waku: waku,
|
|
||||||
privateKey: privateKey,
|
|
||||||
passToSymKeyCache: make(map[string]string),
|
|
||||||
},
|
|
||||||
filters: filtersManager,
|
|
||||||
mailservers: mailservers,
|
|
||||||
logger: logger.With(zap.Namespace("Transport")),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
if err := opt(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.cleanFiltersLoop()
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitFilters(chatIDs []string, publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.Init(chatIDs, publicKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitPublicFilters(chatIDs []string) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitPublicFilters(chatIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Filters() []*transport.Filter {
|
|
||||||
return a.filters.Filters()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) FilterByChatID(chatID string) *transport.Filter {
|
|
||||||
return a.filters.FilterByChatID(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitWithFilters(filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitCommunityFilters(pks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RemoveFilters(filters []*transport.Filter) error {
|
|
||||||
return a.filters.Remove(filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RemoveFilterByChatID(chatID string) (*transport.Filter, error) {
|
|
||||||
return a.filters.RemoveFilterByChatID(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) ResetFilters() error {
|
|
||||||
return a.filters.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) ProcessNegotiatedSecret(secret types.NegotiatedSecret) (*transport.Filter, error) {
|
|
||||||
filter, err := a.filters.LoadNegotiated(secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return filter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinPublic(chatID string) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadPublic(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeavePublic(chatID string) error {
|
|
||||||
chat := a.filters.Filter(chatID)
|
|
||||||
if chat != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return a.filters.Remove(chat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadContactCode(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error {
|
|
||||||
filters := a.filters.FiltersByPublicKey(publicKey)
|
|
||||||
return a.filters.Remove(filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) {
|
|
||||||
var filters []*transport.Filter
|
|
||||||
for _, pk := range publicKeys {
|
|
||||||
f, err := a.filters.LoadContactCode(pk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
filters = append(filters, f)
|
|
||||||
|
|
||||||
}
|
|
||||||
return filters, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeaveGroup(publicKeys []*ecdsa.PublicKey) error {
|
|
||||||
for _, publicKey := range publicKeys {
|
|
||||||
filters := a.filters.FiltersByPublicKey(publicKey)
|
|
||||||
if err := a.filters.Remove(filters...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RetrieveRawAll() (map[transport.Filter][]*types.Message, error) {
|
|
||||||
result := make(map[transport.Filter][]*types.Message)
|
|
||||||
|
|
||||||
allFilters := a.filters.Filters()
|
|
||||||
for _, filter := range allFilters {
|
|
||||||
// Don't pull from filters we don't listen to
|
|
||||||
if !filter.Listen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
msgs, err := a.api.GetFilterMessages(filter.FilterID)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Warn("failed to fetch messages", zap.Error(err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(msgs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ids := make([]string, len(msgs))
|
|
||||||
for i := range msgs {
|
|
||||||
id := types.EncodeHex(msgs[i].Hash)
|
|
||||||
ids[i] = id
|
|
||||||
}
|
|
||||||
|
|
||||||
hits, err := a.cache.Hits(ids)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("failed to check messages exists", zap.Error(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range msgs {
|
|
||||||
// Exclude anything that is a cache hit
|
|
||||||
if !hits[types.EncodeHex(msgs[i].Hash)] {
|
|
||||||
result[*filter] = append(result[*filter], msgs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendPublic sends a new message using the Whisper service.
|
|
||||||
// For public filters, chat name is used as an ID as well as
|
|
||||||
// a topic.
|
|
||||||
func (a *Transport) SendPublic(ctx context.Context, newMessage *types.NewMessage, chatName string) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPublic(chatName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.SymKeyID = filter.SymKeyID
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
|
|
||||||
return a.api.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateWithSharedSecret(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey, secret []byte) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadNegotiated(types.NegotiatedSecret{
|
|
||||||
PublicKey: publicKey,
|
|
||||||
Key: secret,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.SymKeyID = filter.SymKeyID
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = nil
|
|
||||||
|
|
||||||
return a.api.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateWithPartitioned(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPartitioned(publicKey, a.keysManager.privateKey, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.api.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateOnPersonalTopic(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPersonal(publicKey, a.keysManager.privateKey, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.api.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LoadKeyFilters(key *ecdsa.PrivateKey) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadEphemeral(&key.PublicKey, key, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We load the filter to make sure we can post on it
|
|
||||||
filter, err := a.filters.LoadPublic(transport.PubkeyToHex(publicKey)[2:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
a.logger.Debug("SENDING message", zap.Binary("topic", filter.Topic[:]))
|
|
||||||
|
|
||||||
return a.api.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) cleanFilters() error {
|
|
||||||
return a.filters.RemoveNoListenFilters()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) addSig(newMessage *types.NewMessage) error {
|
|
||||||
sigID, err := a.keysManager.AddOrGetKeyPair(a.keysManager.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newMessage.SigID = sigID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Track(identifiers [][]byte, hash []byte, newMessage *types.NewMessage) {
|
|
||||||
if a.envelopesMonitor != nil {
|
|
||||||
a.envelopesMonitor.Add(identifiers, types.BytesToHash(hash), *newMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentTime returns the current unix timestamp in milliseconds
|
|
||||||
func (a *Transport) GetCurrentTime() uint64 {
|
|
||||||
return uint64(a.waku.GetCurrentTime().UnixNano() / int64(time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) MaxMessageSize() uint32 {
|
|
||||||
return a.waku.MaxMessageSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Stop() error {
|
|
||||||
close(a.quit)
|
|
||||||
if a.envelopesMonitor != nil {
|
|
||||||
a.envelopesMonitor.Stop()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanFiltersLoop cleans up the topic we create for the only purpose
|
|
||||||
// of sending messages.
|
|
||||||
// Whenever we send a message we also need to listen to that particular topic
|
|
||||||
// but in case of asymettric topics, we are not interested in listening to them.
|
|
||||||
// We therefore periodically clean them up so we don't receive unnecessary data.
|
|
||||||
|
|
||||||
func (a *Transport) cleanFiltersLoop() {
|
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Minute)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-a.quit:
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
err := a.cleanFilters()
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("failed to clean up topics", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendMessagesRequestForTopics(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
topics []types.TopicType,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
|
|
||||||
r := createMessagesRequest(from, to, previousCursor, topics)
|
|
||||||
r.SetDefaults(a.waku.GetCurrentTime())
|
|
||||||
|
|
||||||
events := make(chan types.EnvelopeEvent, 10)
|
|
||||||
sub := a.waku.SubscribeEnvelopeEvents(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
|
|
||||||
err = a.waku.SendMessagesRequest(peerID, r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !waitForResponse {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := a.waitForRequestCompleted(ctx, r.ID, events)
|
|
||||||
if err == nil && resp != nil && resp.Error != nil {
|
|
||||||
err = resp.Error
|
|
||||||
} else if err == nil && resp != nil {
|
|
||||||
cursor = resp.Cursor
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestHistoricMessages requests historic messages for all registered filters.
|
|
||||||
func (a *Transport) SendMessagesRequest(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
|
|
||||||
topics := make([]types.TopicType, len(a.Filters()))
|
|
||||||
for _, f := range a.Filters() {
|
|
||||||
topics = append(topics, f.Topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.SendMessagesRequestForTopics(ctx, peerID, from, to, previousCursor, topics, waitForResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendMessagesRequestForFilter(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
filter *transport.Filter,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
|
|
||||||
topics := make([]types.TopicType, len(a.Filters()))
|
|
||||||
topics = append(topics, filter.Topic)
|
|
||||||
|
|
||||||
return a.SendMessagesRequestForTopics(ctx, peerID, from, to, previousCursor, topics, waitForResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) waitForRequestCompleted(ctx context.Context, requestID []byte, events chan types.EnvelopeEvent) (*types.MailServerResponse, error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-events:
|
|
||||||
if !bytes.Equal(ev.Hash.Bytes(), requestID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ev.Event != types.EventMailServerRequestCompleted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, ok := ev.Data.(*types.MailServerResponse)
|
|
||||||
if ok {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConfirmMessagesProcessed marks the messages as processed in the cache so
|
|
||||||
// they won't be passed to the next layer anymore
|
|
||||||
func (a *Transport) ConfirmMessagesProcessed(ids []string, timestamp uint64) error {
|
|
||||||
return a.cache.Add(ids, timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanMessagesProcessed clears the messages that are older than timestamp
|
|
||||||
func (a *Transport) CleanMessagesProcessed(timestamp uint64) error {
|
|
||||||
return a.cache.Clean(timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SetEnvelopeEventsHandler(handler transport.EnvelopeEventsHandler) error {
|
|
||||||
if a.envelopesMonitor == nil {
|
|
||||||
return errors.New("Current transport has no envelopes monitor")
|
|
||||||
}
|
|
||||||
a.envelopesMonitor.handler = handler
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,331 +0,0 @@
|
||||||
// Code generated by go-bindata. DO NOT EDIT.
|
|
||||||
// sources:
|
|
||||||
// 1561059285_add_whisper_keys.down.sql (25B)
|
|
||||||
// 1561059285_add_whisper_keys.up.sql (112B)
|
|
||||||
// doc.go (373B)
|
|
||||||
|
|
||||||
package sqlite
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
|
||||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = io.Copy(&buf, gz)
|
|
||||||
clErr := gz.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read %q: %v", name, err)
|
|
||||||
}
|
|
||||||
if clErr != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type asset struct {
|
|
||||||
bytes []byte
|
|
||||||
info os.FileInfo
|
|
||||||
digest [sha256.Size]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type bindataFileInfo struct {
|
|
||||||
name string
|
|
||||||
size int64
|
|
||||||
mode os.FileMode
|
|
||||||
modTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fi bindataFileInfo) Name() string {
|
|
||||||
return fi.name
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Size() int64 {
|
|
||||||
return fi.size
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
|
||||||
return fi.mode
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) ModTime() time.Time {
|
|
||||||
return fi.modTime
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) IsDir() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func (fi bindataFileInfo) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var __1561059285_add_whisper_keysDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xcf\xc8\x2c\x2e\x48\x2d\x8a\xcf\x4e\xad\x2c\xb6\xe6\x02\x04\x00\x00\xff\xff\x42\x93\x8e\x79\x19\x00\x00\x00")
|
|
||||||
|
|
||||||
func _1561059285_add_whisper_keysDownSqlBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
__1561059285_add_whisper_keysDownSql,
|
|
||||||
"1561059285_add_whisper_keys.down.sql",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _1561059285_add_whisper_keysDownSql() (*asset, error) {
|
|
||||||
bytes, err := _1561059285_add_whisper_keysDownSqlBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
info := bindataFileInfo{name: "1561059285_add_whisper_keys.down.sql", size: 25, mode: os.FileMode(0644), modTime: time.Unix(1607354881, 0)}
|
|
||||||
=======
|
|
||||||
info := bindataFileInfo{name: "1561059285_add_whisper_keys.down.sql", size: 25, mode: os.FileMode(0644), modTime: time.Unix(1619180307, 0)}
|
|
||||||
>>>>>>> 21a705ec (create gaps)
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb9, 0x31, 0x3f, 0xce, 0xfa, 0x44, 0x36, 0x1b, 0xb0, 0xec, 0x5d, 0xb, 0x90, 0xb, 0x21, 0x4f, 0xd5, 0xe5, 0x50, 0xed, 0xc7, 0x43, 0xdf, 0x83, 0xb4, 0x3a, 0xc1, 0x55, 0x2e, 0x53, 0x7c, 0x67}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var __1561059285_add_whisper_keysUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x04\xc0\xb1\x0a\xc2\x40\x0c\x06\xe0\xfd\x9e\xe2\x1f\x15\x7c\x03\xa7\xde\x19\x35\x18\x13\x09\x29\xb5\x53\x11\x3d\x68\xe9\x22\x56\x90\xbe\xbd\x5f\x71\x6a\x82\x10\x4d\x16\xc2\x6f\x9c\x96\x77\xfd\x0c\x73\x5d\x17\x6c\x12\xf0\x1c\x1f\xdf\x61\x7a\x21\xe8\x1e\xb8\x39\x5f\x1b\xef\x71\xa1\x1e\xa6\x28\xa6\x47\xe1\x12\xe0\x93\x9a\xd3\x2e\x01\x73\x5d\x91\xc5\x32\xd4\x02\xda\x8a\xa4\x2d\x3a\x8e\xb3\xb5\x01\xb7\x8e\x0f\xfb\xf4\x0f\x00\x00\xff\xff\x6e\x23\x28\x7d\x70\x00\x00\x00")
|
|
||||||
|
|
||||||
func _1561059285_add_whisper_keysUpSqlBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
__1561059285_add_whisper_keysUpSql,
|
|
||||||
"1561059285_add_whisper_keys.up.sql",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _1561059285_add_whisper_keysUpSql() (*asset, error) {
|
|
||||||
bytes, err := _1561059285_add_whisper_keysUpSqlBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
info := bindataFileInfo{name: "1561059285_add_whisper_keys.up.sql", size: 112, mode: os.FileMode(0644), modTime: time.Unix(1607354881, 0)}
|
|
||||||
=======
|
|
||||||
info := bindataFileInfo{name: "1561059285_add_whisper_keys.up.sql", size: 112, mode: os.FileMode(0644), modTime: time.Unix(1619180307, 0)}
|
|
||||||
>>>>>>> 21a705ec (create gaps)
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x25, 0x41, 0xc, 0x92, 0xdd, 0x9e, 0xff, 0x5d, 0xd0, 0x93, 0xe4, 0x24, 0x50, 0x29, 0xcf, 0xc6, 0xf7, 0x49, 0x3c, 0x73, 0xd9, 0x8c, 0xfa, 0xf2, 0xcf, 0xf6, 0x6f, 0xbc, 0x31, 0xe6, 0xf7, 0xe2}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x8f\x3d\x72\xeb\x30\x0c\x84\x7b\x9d\x62\xc7\x8d\x9b\x27\xb2\x79\x55\xba\x94\xe9\x73\x01\x98\x5a\x91\x18\x4b\xa4\x42\xc0\x7f\xb7\xcf\xc8\xe3\xc2\x5d\xda\x1d\x7c\x1f\x76\x63\xc4\x77\x51\xc3\xac\x0b\xa1\x86\xca\x44\x33\xe9\x0f\x9c\x98\xe4\x62\xc4\x21\xab\x97\xcb\x29\xa4\xb6\x46\x73\xf1\x8b\x8d\xba\xc6\x55\x73\x17\x67\xbc\xfe\x3f\x0c\x31\x22\x49\x3d\x3a\x8a\xd4\x69\xe1\xd3\x65\x30\x97\xee\x5a\x33\x6e\xea\x05\x82\xad\x73\xd6\x7b\xc0\xa7\x63\xa1\x98\xc3\x8b\xf8\xd1\xe0\x85\x48\x62\xdc\x35\x73\xeb\xc8\x6d\x3c\x69\x9d\xc4\x25\xec\xd1\xd7\xfc\x96\xec\x0d\x93\x2c\x0b\x27\xcc\xbd\xad\x4f\xd6\x64\x25\x26\xed\x4c\xde\xfa\xe3\x1f\xc4\x8c\x8e\x2a\x2b\x6d\xe7\x8b\x5c\x89\xda\x5e\xef\x21\x75\xfa\x7b\x11\x6e\xad\x9f\x0d\x62\xe0\x7d\x63\x72\x4e\x61\x18\x36\x49\x67\xc9\x84\xfd\x2c\xea\x1c\x86\x18\x73\xfb\xc8\xac\xdc\xa9\xf7\x8e\xe3\x76\xce\xaf\x2b\x8c\x0d\x21\xbc\xd4\xda\xaa\x85\xdc\x10\x86\xdf\x00\x00\x00\xff\xff\x21\xa5\x75\x05\x75\x01\x00\x00")
|
|
||||||
|
|
||||||
func docGoBytes() ([]byte, error) {
|
|
||||||
return bindataRead(
|
|
||||||
_docGo,
|
|
||||||
"doc.go",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func docGo() (*asset, error) {
|
|
||||||
bytes, err := docGoBytes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
info := bindataFileInfo{name: "doc.go", size: 373, mode: os.FileMode(0644), modTime: time.Unix(1607354881, 0)}
|
|
||||||
=======
|
|
||||||
info := bindataFileInfo{name: "doc.go", size: 373, mode: os.FileMode(0644), modTime: time.Unix(1619180307, 0)}
|
|
||||||
>>>>>>> 21a705ec (create gaps)
|
|
||||||
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x23, 0x6a, 0xc1, 0xce, 0x94, 0xf6, 0xef, 0xf1, 0x97, 0x95, 0xb, 0x35, 0xaf, 0x5f, 0xe7, 0x5f, 0xac, 0x6e, 0xb8, 0xab, 0xba, 0xb5, 0x35, 0x97, 0x22, 0x36, 0x11, 0xce, 0x44, 0xfc, 0xfa, 0xac}}
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asset loads and returns the asset for the given name.
|
|
||||||
// It returns an error if the asset could not be found or
|
|
||||||
// could not be loaded.
|
|
||||||
func Asset(name string) ([]byte, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.bytes, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetString returns the asset contents as a string (instead of a []byte).
|
|
||||||
func AssetString(name string) (string, error) {
|
|
||||||
data, err := Asset(name)
|
|
||||||
return string(data), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustAsset is like Asset but panics when Asset would return an error.
|
|
||||||
// It simplifies safe initialization of global variables.
|
|
||||||
func MustAsset(name string) []byte {
|
|
||||||
a, err := Asset(name)
|
|
||||||
if err != nil {
|
|
||||||
panic("asset: Asset(" + name + "): " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustAssetString is like AssetString but panics when Asset would return an
|
|
||||||
// error. It simplifies safe initialization of global variables.
|
|
||||||
func MustAssetString(name string) string {
|
|
||||||
return string(MustAsset(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetInfo loads and returns the asset info for the given name.
|
|
||||||
// It returns an error if the asset could not be found or
|
|
||||||
// could not be loaded.
|
|
||||||
func AssetInfo(name string) (os.FileInfo, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.info, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetDigest returns the digest of the file with the given name. It returns an
|
|
||||||
// error if the asset could not be found or the digest could not be loaded.
|
|
||||||
func AssetDigest(name string) ([sha256.Size]byte, error) {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
if f, ok := _bindata[canonicalName]; ok {
|
|
||||||
a, err := f()
|
|
||||||
if err != nil {
|
|
||||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
|
|
||||||
}
|
|
||||||
return a.digest, nil
|
|
||||||
}
|
|
||||||
return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Digests returns a map of all known files and their checksums.
|
|
||||||
func Digests() (map[string][sha256.Size]byte, error) {
|
|
||||||
mp := make(map[string][sha256.Size]byte, len(_bindata))
|
|
||||||
for name := range _bindata {
|
|
||||||
a, err := _bindata[name]()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mp[name] = a.digest
|
|
||||||
}
|
|
||||||
return mp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetNames returns the names of the assets.
|
|
||||||
func AssetNames() []string {
|
|
||||||
names := make([]string, 0, len(_bindata))
|
|
||||||
for name := range _bindata {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
|
||||||
var _bindata = map[string]func() (*asset, error){
|
|
||||||
"1561059285_add_whisper_keys.down.sql": _1561059285_add_whisper_keysDownSql,
|
|
||||||
|
|
||||||
"1561059285_add_whisper_keys.up.sql": _1561059285_add_whisper_keysUpSql,
|
|
||||||
|
|
||||||
"doc.go": docGo,
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetDir returns the file names below a certain
|
|
||||||
// directory embedded in the file by go-bindata.
|
|
||||||
// For example if you run go-bindata on data/... and data contains the
|
|
||||||
// following hierarchy:
|
|
||||||
// data/
|
|
||||||
// foo.txt
|
|
||||||
// img/
|
|
||||||
// a.png
|
|
||||||
// b.png
|
|
||||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
|
||||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
|
||||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
|
||||||
// AssetDir("") will return []string{"data"}.
|
|
||||||
func AssetDir(name string) ([]string, error) {
|
|
||||||
node := _bintree
|
|
||||||
if len(name) != 0 {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
pathList := strings.Split(canonicalName, "/")
|
|
||||||
for _, p := range pathList {
|
|
||||||
node = node.Children[p]
|
|
||||||
if node == nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if node.Func != nil {
|
|
||||||
return nil, fmt.Errorf("Asset %s not found", name)
|
|
||||||
}
|
|
||||||
rv := make([]string, 0, len(node.Children))
|
|
||||||
for childName := range node.Children {
|
|
||||||
rv = append(rv, childName)
|
|
||||||
}
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type bintree struct {
|
|
||||||
Func func() (*asset, error)
|
|
||||||
Children map[string]*bintree
|
|
||||||
}
|
|
||||||
|
|
||||||
var _bintree = &bintree{nil, map[string]*bintree{
|
|
||||||
"1561059285_add_whisper_keys.down.sql": &bintree{_1561059285_add_whisper_keysDownSql, map[string]*bintree{}},
|
|
||||||
"1561059285_add_whisper_keys.up.sql": &bintree{_1561059285_add_whisper_keysUpSql, map[string]*bintree{}},
|
|
||||||
"doc.go": &bintree{docGo, map[string]*bintree{}},
|
|
||||||
}}
|
|
||||||
|
|
||||||
// RestoreAsset restores an asset under the given directory.
|
|
||||||
func RestoreAsset(dir, name string) error {
|
|
||||||
data, err := Asset(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info, err := AssetInfo(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreAssets restores an asset under the given directory recursively.
|
|
||||||
func RestoreAssets(dir, name string) error {
|
|
||||||
children, err := AssetDir(name)
|
|
||||||
// File
|
|
||||||
if err != nil {
|
|
||||||
return RestoreAsset(dir, name)
|
|
||||||
}
|
|
||||||
// Dir
|
|
||||||
for _, child := range children {
|
|
||||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func _filePath(dir, name string) string {
|
|
||||||
canonicalName := strings.Replace(name, "\\", "/", -1)
|
|
||||||
return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
|
|
||||||
}
|
|
|
@ -1,525 +0,0 @@
|
||||||
package whisper
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"database/sql"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNoMailservers returned if there is no configured mailservers that can be used.
|
|
||||||
ErrNoMailservers = errors.New("no configured mailservers")
|
|
||||||
)
|
|
||||||
|
|
||||||
type whisperServiceKeysManager struct {
|
|
||||||
shh types.Whisper
|
|
||||||
|
|
||||||
// Identity of the current user.
|
|
||||||
privateKey *ecdsa.PrivateKey
|
|
||||||
|
|
||||||
passToSymKeyMutex sync.RWMutex
|
|
||||||
passToSymKeyCache map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *whisperServiceKeysManager) AddOrGetKeyPair(priv *ecdsa.PrivateKey) (string, error) {
|
|
||||||
// caching is handled in Whisper
|
|
||||||
return m.shh.AddKeyPair(priv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *whisperServiceKeysManager) AddOrGetSymKeyFromPassword(password string) (string, error) {
|
|
||||||
m.passToSymKeyMutex.Lock()
|
|
||||||
defer m.passToSymKeyMutex.Unlock()
|
|
||||||
|
|
||||||
if val, ok := m.passToSymKeyCache[password]; ok {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := m.shh.AddSymKeyFromPassword(password)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.passToSymKeyCache[password] = id
|
|
||||||
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *whisperServiceKeysManager) RawSymKey(id string) ([]byte, error) {
|
|
||||||
return m.shh.GetSymKey(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Option func(*Transport) error
|
|
||||||
|
|
||||||
// Transport is a transport based on Whisper service.
|
|
||||||
type Transport struct {
|
|
||||||
shh types.Whisper
|
|
||||||
shhAPI types.PublicWhisperAPI // only PublicWhisperAPI implements logic to send messages
|
|
||||||
keysManager *whisperServiceKeysManager
|
|
||||||
filters *transport.FiltersManager
|
|
||||||
logger *zap.Logger
|
|
||||||
|
|
||||||
mailservers []string
|
|
||||||
envelopesMonitor *EnvelopesMonitor
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransport returns a new Transport.
|
|
||||||
// TODO: leaving a chat should verify that for a given public key
|
|
||||||
// there are no other chats. It may happen that we leave a private chat
|
|
||||||
// but still have a public chat for a given public key.
|
|
||||||
func NewTransport(
|
|
||||||
shh types.Whisper,
|
|
||||||
privateKey *ecdsa.PrivateKey,
|
|
||||||
db *sql.DB,
|
|
||||||
mailservers []string,
|
|
||||||
envelopesMonitorConfig *transport.EnvelopesMonitorConfig,
|
|
||||||
logger *zap.Logger,
|
|
||||||
opts ...Option,
|
|
||||||
) (*Transport, error) {
|
|
||||||
filtersManager, err := transport.NewFiltersManager(newSQLitePersistence(db), shh, privateKey, logger)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var envelopesMonitor *EnvelopesMonitor
|
|
||||||
if envelopesMonitorConfig != nil {
|
|
||||||
envelopesMonitor = NewEnvelopesMonitor(shh, *envelopesMonitorConfig)
|
|
||||||
envelopesMonitor.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
var shhAPI types.PublicWhisperAPI
|
|
||||||
if shh != nil {
|
|
||||||
shhAPI = shh.PublicWhisperAPI()
|
|
||||||
}
|
|
||||||
t := &Transport{
|
|
||||||
shh: shh,
|
|
||||||
shhAPI: shhAPI,
|
|
||||||
envelopesMonitor: envelopesMonitor,
|
|
||||||
keysManager: &whisperServiceKeysManager{
|
|
||||||
shh: shh,
|
|
||||||
privateKey: privateKey,
|
|
||||||
passToSymKeyCache: make(map[string]string),
|
|
||||||
},
|
|
||||||
filters: filtersManager,
|
|
||||||
mailservers: mailservers,
|
|
||||||
logger: logger.With(zap.Namespace("Transport")),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
if err := opt(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitFilters(chatIDs []string, publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.Init(chatIDs, publicKeys)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitPublicFilters(chatIDs []string) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitPublicFilters(chatIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) InitCommunityFilters(pks []*ecdsa.PrivateKey) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitCommunityFilters(pks)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Filters() []*transport.Filter {
|
|
||||||
return a.filters.Filters()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) FilterByChatID(chatID string) *transport.Filter {
|
|
||||||
return a.filters.FilterByChatID(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) {
|
|
||||||
return a.filters.InitWithFilters(filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendCommunityMessage(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We load the filter to make sure we can post on it
|
|
||||||
filter, err := a.filters.LoadPublic(transport.PubkeyToHex(publicKey))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RemoveFilters(filters []*transport.Filter) error {
|
|
||||||
return a.filters.Remove(filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RemoveFilterByChatID(chatID string) (*transport.Filter, error) {
|
|
||||||
return a.filters.RemoveFilterByChatID(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) ResetFilters() error {
|
|
||||||
return a.filters.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) ProcessNegotiatedSecret(secret types.NegotiatedSecret) (*transport.Filter, error) {
|
|
||||||
filter, err := a.filters.LoadNegotiated(secret)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return filter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinPublic(chatID string) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadPublic(chatID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeavePublic(chatID string) error {
|
|
||||||
chat := a.filters.Filter(chatID)
|
|
||||||
if chat != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return a.filters.Remove(chat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinPrivate(publicKey *ecdsa.PublicKey) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadContactCode(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeavePrivate(publicKey *ecdsa.PublicKey) error {
|
|
||||||
filters := a.filters.FiltersByPublicKey(publicKey)
|
|
||||||
return a.filters.Remove(filters...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) JoinGroup(publicKeys []*ecdsa.PublicKey) ([]*transport.Filter, error) {
|
|
||||||
var filters []*transport.Filter
|
|
||||||
for _, pk := range publicKeys {
|
|
||||||
f, err := a.filters.LoadContactCode(pk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
filters = append(filters, f)
|
|
||||||
}
|
|
||||||
return filters, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LeaveGroup(publicKeys []*ecdsa.PublicKey) error {
|
|
||||||
for _, publicKey := range publicKeys {
|
|
||||||
filters := a.filters.FiltersByPublicKey(publicKey)
|
|
||||||
if err := a.filters.Remove(filters...); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
Message *types.Message
|
|
||||||
Public bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RetrieveAllMessages() ([]Message, error) {
|
|
||||||
var messages []Message
|
|
||||||
|
|
||||||
for _, filter := range a.filters.Filters() {
|
|
||||||
filterMsgs, err := a.shhAPI.GetFilterMessages(filter.FilterID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range filterMsgs {
|
|
||||||
messages = append(messages, Message{
|
|
||||||
Message: m,
|
|
||||||
Public: filter.IsPublic(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return messages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RetrievePublicMessages(chatID string) ([]*types.Message, error) {
|
|
||||||
filter, err := a.filters.LoadPublic(chatID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.shhAPI.GetFilterMessages(filter.FilterID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RetrievePrivateMessages(publicKey *ecdsa.PublicKey) ([]*types.Message, error) {
|
|
||||||
chats := a.filters.FiltersByPublicKey(publicKey)
|
|
||||||
discoveryChats, err := a.filters.Init(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []*types.Message
|
|
||||||
|
|
||||||
for _, chat := range append(chats, discoveryChats...) {
|
|
||||||
filterMsgs, err := a.shhAPI.GetFilterMessages(chat.FilterID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, filterMsgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) RetrieveRawAll() (map[transport.Filter][]*types.Message, error) {
|
|
||||||
result := make(map[transport.Filter][]*types.Message)
|
|
||||||
|
|
||||||
allFilters := a.filters.Filters()
|
|
||||||
for _, filter := range allFilters {
|
|
||||||
msgs, err := a.shhAPI.GetFilterMessages(filter.FilterID)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result[*filter] = append(result[*filter], msgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendPublic sends a new message using the Whisper service.
|
|
||||||
// For public filters, chat name is used as an ID as well as
|
|
||||||
// a topic.
|
|
||||||
func (a *Transport) SendPublic(ctx context.Context, newMessage *types.NewMessage, chatName string) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPublic(chatName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.SymKeyID = filter.SymKeyID
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateWithSharedSecret(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey, secret []byte) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadNegotiated(types.NegotiatedSecret{
|
|
||||||
PublicKey: publicKey,
|
|
||||||
Key: secret,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.SymKeyID = filter.SymKeyID
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = nil
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateWithPartitioned(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPartitioned(publicKey, a.keysManager.privateKey, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateOnPersonalTopic(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter, err := a.filters.LoadPersonal(publicKey, a.keysManager.privateKey, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newMessage.Topic = filter.Topic
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendPrivateOnDiscovery(ctx context.Context, newMessage *types.NewMessage, publicKey *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
if err := a.addSig(newMessage); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is no need to load any chat
|
|
||||||
// because listening on the discovery topic
|
|
||||||
// is done automatically.
|
|
||||||
// TODO: change this anyway, it should be explicit
|
|
||||||
// and idempotent.
|
|
||||||
|
|
||||||
newMessage.Topic = types.BytesToTopic(transport.ToTopic(transport.DiscoveryTopic()))
|
|
||||||
newMessage.PublicKey = crypto.FromECDSAPub(publicKey)
|
|
||||||
|
|
||||||
return a.shhAPI.Post(ctx, *newMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) addSig(newMessage *types.NewMessage) error {
|
|
||||||
sigID, err := a.keysManager.AddOrGetKeyPair(a.keysManager.privateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newMessage.SigID = sigID
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Track(identifiers [][]byte, hash []byte, newMessage *types.NewMessage) {
|
|
||||||
if a.envelopesMonitor != nil {
|
|
||||||
a.envelopesMonitor.Add(identifiers, types.BytesToHash(hash), *newMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentTime returns the current unix timestamp in milliseconds
|
|
||||||
func (a *Transport) GetCurrentTime() uint64 {
|
|
||||||
return uint64(a.shh.GetCurrentTime().UnixNano() / int64(time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) MaxMessageSize() uint32 {
|
|
||||||
return a.shh.MaxMessageSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) Stop() error {
|
|
||||||
if a.envelopesMonitor != nil {
|
|
||||||
a.envelopesMonitor.Stop()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestHistoricMessages requests historic messages for all registered filters.
|
|
||||||
func (a *Transport) SendMessagesRequest(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
topics := make([]types.TopicType, len(a.Filters()))
|
|
||||||
for _, f := range a.Filters() {
|
|
||||||
topics = append(topics, f.Topic)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := createMessagesRequest(from, to, previousCursor, topics)
|
|
||||||
r.SetDefaults(a.shh.GetCurrentTime())
|
|
||||||
|
|
||||||
events := make(chan types.EnvelopeEvent, 10)
|
|
||||||
sub := a.shh.SubscribeEnvelopeEvents(events)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
|
|
||||||
err = a.shh.SendMessagesRequest(peerID, r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !waitForResponse {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := a.waitForRequestCompleted(ctx, r.ID, events)
|
|
||||||
if err == nil && resp != nil && resp.Error != nil {
|
|
||||||
err = resp.Error
|
|
||||||
} else if err == nil && resp != nil {
|
|
||||||
cursor = resp.Cursor
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: kozieiev: fix
|
|
||||||
func (a *Transport) SendMessagesRequestForFilter(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
filter *transport.Filter,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SendMessagesRequestForTopics(
|
|
||||||
ctx context.Context,
|
|
||||||
peerID []byte,
|
|
||||||
from, to uint32,
|
|
||||||
previousCursor []byte,
|
|
||||||
topics []types.TopicType,
|
|
||||||
waitForResponse bool,
|
|
||||||
) (cursor []byte, err error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) LoadKeyFilters(key *ecdsa.PrivateKey) (*transport.Filter, error) {
|
|
||||||
return a.filters.LoadPartitioned(&key.PublicKey, key, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) waitForRequestCompleted(ctx context.Context, requestID []byte, events chan types.EnvelopeEvent) (*types.MailServerResponse, error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-events:
|
|
||||||
a.logger.Debug(
|
|
||||||
"waiting for request completed and received an event",
|
|
||||||
zap.Binary("requestID", requestID),
|
|
||||||
zap.Any("event", ev),
|
|
||||||
)
|
|
||||||
if !bytes.Equal(ev.Hash.Bytes(), requestID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ev.Event != types.EventMailServerRequestCompleted {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, ok := ev.Data.(*types.MailServerResponse)
|
|
||||||
if ok {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: currently not used as whisper is not maintained anymore
|
|
||||||
func (a *Transport) ConfirmMessagesProcessed(ids []string, timestamp uint64) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: currently not used as whisper is not maintained anymore
|
|
||||||
func (a *Transport) CleanMessagesProcessed(timestamp uint64) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Transport) SetEnvelopeEventsHandler(handler transport.EnvelopeEventsHandler) error {
|
|
||||||
if a.envelopesMonitor == nil {
|
|
||||||
return errors.New("Current transport has no envelopes monitor")
|
|
||||||
}
|
|
||||||
a.envelopesMonitor.handler = handler
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/connection"
|
||||||
"github.com/status-im/status-go/db"
|
"github.com/status-im/status-go/db"
|
||||||
coretypes "github.com/status-im/status-go/eth-node/core/types"
|
coretypes "github.com/status-im/status-go/eth-node/core/types"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
|
@ -38,13 +39,6 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
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.
|
// EnvelopeEventsHandler used for two different event types.
|
||||||
type EnvelopeEventsHandler interface {
|
type EnvelopeEventsHandler interface {
|
||||||
EnvelopeSent([][]byte)
|
EnvelopeSent([][]byte)
|
||||||
|
@ -312,9 +306,7 @@ func (s *Service) DisableInstallation(installationID string) error {
|
||||||
|
|
||||||
// UpdateMailservers updates information about selected mail servers.
|
// UpdateMailservers updates information about selected mail servers.
|
||||||
func (s *Service) UpdateMailservers(nodes []*enode.Node) error {
|
func (s *Service) UpdateMailservers(nodes []*enode.Node) error {
|
||||||
log.Info("updating nodes", "nodes", nodes, "messenger", s.messenger)
|
|
||||||
if len(nodes) > 0 && s.messenger != nil {
|
if len(nodes) > 0 && s.messenger != nil {
|
||||||
log.Info("Setting messenger")
|
|
||||||
s.messenger.SetMailserver(nodes[0].ID().Bytes())
|
s.messenger.SetMailserver(nodes[0].ID().Bytes())
|
||||||
}
|
}
|
||||||
for _, peer := range nodes {
|
for _, peer := range nodes {
|
||||||
|
@ -424,3 +416,9 @@ func buildMessengerOptions(
|
||||||
|
|
||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ConnectionChanged(state connection.State) {
|
||||||
|
if s.messenger != nil {
|
||||||
|
s.messenger.ConnectionChanged(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/services/ext"
|
|
||||||
"github.com/status-im/status-go/services/shhext"
|
|
||||||
"github.com/status-im/status-go/services/wakuext"
|
|
||||||
"github.com/status-im/status-go/waku"
|
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestShhextAndWakuextInSingleNode(t *testing.T) {
|
|
||||||
aNode, err := node.New(&node.Config{
|
|
||||||
P2P: p2p.Config{
|
|
||||||
MaxPeers: math.MaxInt32,
|
|
||||||
NoDiscovery: true,
|
|
||||||
},
|
|
||||||
NoUSB: true,
|
|
||||||
}) // in-memory node as no data dir
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// register waku and whisper services
|
|
||||||
wakuWrapper := gethbridge.NewGethWakuWrapper(waku.New(nil, nil))
|
|
||||||
err = aNode.Register(func(*node.ServiceContext) (node.Service, error) {
|
|
||||||
return gethbridge.GetGethWakuFrom(wakuWrapper), nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
whisperWrapper := gethbridge.NewGethWhisperWrapper(whisper.New(nil))
|
|
||||||
err = aNode.Register(func(*node.ServiceContext) (node.Service, error) {
|
|
||||||
return gethbridge.GetGethWhisperFrom(whisperWrapper), nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
nodeWrapper := ext.NewTestNodeWrapper(whisperWrapper, wakuWrapper)
|
|
||||||
|
|
||||||
// register ext services
|
|
||||||
err = aNode.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
return wakuext.New(params.ShhextConfig{}, nodeWrapper, ctx, ext.EnvelopeSignalHandler{}, nil), nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = aNode.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
return shhext.New(params.ShhextConfig{}, nodeWrapper, ctx, ext.EnvelopeSignalHandler{}, nil), nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// start node
|
|
||||||
err = aNode.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() { require.NoError(t, aNode.Stop()) }()
|
|
||||||
|
|
||||||
// verify the services are available
|
|
||||||
rpc, err := aNode.Attach()
|
|
||||||
require.NoError(t, err)
|
|
||||||
var result string
|
|
||||||
err = rpc.Call(&result, "shhext_echo", "shhext test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "shhext test", result)
|
|
||||||
err = rpc.Call(&result, "wakuext_echo", "wakuext test")
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "wakuext test", result)
|
|
||||||
}
|
|
|
@ -1,7 +1,9 @@
|
||||||
package wakuext
|
package wakuext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
@ -69,3 +71,58 @@ func makeEnvelop(
|
||||||
}
|
}
|
||||||
return gethbridge.NewWakuEnvelope(envelope), nil
|
return gethbridge.NewWakuEnvelope(envelope), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestMessages sends a request for historic messages to a MailServer.
|
||||||
|
func (api *PublicAPI) RequestMessages(_ context.Context, r ext.MessagesRequest) (types.HexBytes, error) {
|
||||||
|
api.log.Info("RequestMessages", "request", r)
|
||||||
|
|
||||||
|
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.service.GetPeer(r.MailServerPeer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v", ext.ErrInvalidMailServerPeer, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
symKey []byte
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.SymKeyID != "" {
|
||||||
|
symKey, err = api.service.w.GetSymKey(r.SymKeyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v", ext.ErrInvalidSymKeyID, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
publicKey = mailServerNode.Pubkey()
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := ext.MakeMessagesRequestPayload(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
envelope, err := makeEnvelop(
|
||||||
|
payload,
|
||||||
|
symKey,
|
||||||
|
publicKey,
|
||||||
|
api.service.NodeID(),
|
||||||
|
api.service.w.MinPow(),
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash := envelope.Hash()
|
||||||
|
|
||||||
|
if err := api.service.w.RequestHistoricMessagesWithTimeout(mailServerNode.ID().Bytes(), envelope, r.Timeout*time.Second); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash[:], nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,11 +2,9 @@ package wakuext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -19,10 +17,8 @@ import (
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/status-im/status-go/appdatabase"
|
"github.com/status-im/status-go/appdatabase"
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
|
@ -32,8 +28,6 @@ import (
|
||||||
"github.com/status-im/status-go/services/ext"
|
"github.com/status-im/status-go/services/ext"
|
||||||
"github.com/status-im/status-go/t/helpers"
|
"github.com/status-im/status-go/t/helpers"
|
||||||
"github.com/status-im/status-go/waku"
|
"github.com/status-im/status-go/waku"
|
||||||
wakucommon "github.com/status-im/status-go/waku/common"
|
|
||||||
v0 "github.com/status-im/status-go/waku/v0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestMessagesErrors(t *testing.T) {
|
func TestRequestMessagesErrors(t *testing.T) {
|
||||||
|
@ -267,11 +261,6 @@ func (s *ShhExtSuite) TestMultipleRequestMessagesWithoutForce() {
|
||||||
Topics: []types.TopicType{{1}},
|
Topics: []types.TopicType{{1}},
|
||||||
})
|
})
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
_, err = api.RequestMessages(context.Background(), ext.MessagesRequest{
|
|
||||||
MailServerPeer: s.nodes[1].Server().Self().URLv4(),
|
|
||||||
Topics: []types.TopicType{{1}},
|
|
||||||
})
|
|
||||||
s.EqualError(err, "another request with the same topics was sent less than 3s ago. Please wait for a bit longer, or set `force` to true in request parameters")
|
|
||||||
_, err = api.RequestMessages(context.Background(), ext.MessagesRequest{
|
_, err = api.RequestMessages(context.Background(), ext.MessagesRequest{
|
||||||
MailServerPeer: s.nodes[1].Server().Self().URLv4(),
|
MailServerPeer: s.nodes[1].Server().Self().URLv4(),
|
||||||
Topics: []types.TopicType{{2}},
|
Topics: []types.TopicType{{2}},
|
||||||
|
@ -290,135 +279,3 @@ func (s *ShhExtSuite) TestFailedRequestWithUnknownMailServerPeer() {
|
||||||
})
|
})
|
||||||
s.EqualError(err, "could not find peer with ID: 10841e6db5c02fc331bf36a8d2a9137a1696d9d3b6b1f872f780e02aa8ec5bba")
|
s.EqualError(err, "could not find peer with ID: 10841e6db5c02fc331bf36a8d2a9137a1696d9d3b6b1f872f780e02aa8ec5bba")
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
// internal waku protocol codes
|
|
||||||
p2pRequestCompleteCode = 125
|
|
||||||
)
|
|
||||||
|
|
||||||
type WakuNodeMockSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
|
|
||||||
localWakuAPI *waku.PublicWakuAPI
|
|
||||||
localAPI *PublicAPI
|
|
||||||
localNode *enode.Node
|
|
||||||
remoteRW *p2p.MsgPipeRW
|
|
||||||
|
|
||||||
localService *Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WakuNodeMockSuite) SetupTest() {
|
|
||||||
db, err := leveldb.Open(storage.NewMemStorage(), nil)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
conf := &waku.Config{
|
|
||||||
MinimumAcceptedPoW: 0,
|
|
||||||
MaxMessageSize: 100 << 10,
|
|
||||||
EnableConfirmations: true,
|
|
||||||
}
|
|
||||||
w := waku.New(conf, nil)
|
|
||||||
w2 := waku.New(nil, nil)
|
|
||||||
s.Require().NoError(w.Start(nil))
|
|
||||||
pkey, err := crypto.GenerateKey()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
node := enode.NewV4(&pkey.PublicKey, net.ParseIP("127.0.0.1"), 1, 1)
|
|
||||||
rw1, rw2 := p2p.MsgPipe()
|
|
||||||
peer := v0.NewPeer(w, p2p.NewPeer(node.ID(), "1", []p2p.Cap{{"shh", 6}}), rw2, nil)
|
|
||||||
go func() {
|
|
||||||
err := w.HandlePeer(peer, rw2)
|
|
||||||
panic(err)
|
|
||||||
}()
|
|
||||||
wakuWrapper := gethbridge.NewGethWakuWrapper(w)
|
|
||||||
|
|
||||||
peer1 := v0.NewPeer(w2, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), rw1, nil)
|
|
||||||
err = peer1.Start()
|
|
||||||
s.Require().NoError(err, "failed run message loop")
|
|
||||||
|
|
||||||
nodeWrapper := ext.NewTestNodeWrapper(nil, wakuWrapper)
|
|
||||||
s.localService = New(
|
|
||||||
params.ShhextConfig{MailServerConfirmations: true, MaxMessageDeliveryAttempts: 3},
|
|
||||||
nodeWrapper,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
db,
|
|
||||||
)
|
|
||||||
s.Require().NoError(s.localService.UpdateMailservers([]*enode.Node{node}))
|
|
||||||
|
|
||||||
s.localWakuAPI = waku.NewPublicWakuAPI(w)
|
|
||||||
s.localAPI = NewPublicAPI(s.localService)
|
|
||||||
s.localNode = node
|
|
||||||
s.remoteRW = rw1
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestMessagesSync(t *testing.T) {
|
|
||||||
suite.Run(t, new(RequestMessagesSyncSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestMessagesSyncSuite struct {
|
|
||||||
WakuNodeMockSuite
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Disabling this for now as too flaky
|
|
||||||
/*
|
|
||||||
func (s *RequestMessagesSyncSuite) TestExpired() {
|
|
||||||
// intentionally discarding all requests, so that request will timeout
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
msg, err := s.remoteRW.ReadMsg()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().NoError(msg.Discard())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
_, err := s.localAPI.RequestMessagesSync(
|
|
||||||
ext.RetryConfig{
|
|
||||||
BaseTimeout: time.Millisecond * 100,
|
|
||||||
},
|
|
||||||
ext.MessagesRequest{
|
|
||||||
MailServerPeer: s.localNode.String(),
|
|
||||||
Topics: []common.TopicType{{0x01, 0x02, 0x03, 0x04}},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
s.Require().EqualError(err, "failed to request messages after 1 retries")
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (s *RequestMessagesSyncSuite) testCompletedFromAttempt(target int) {
|
|
||||||
const cursorSize = 36 // taken from mailserver_response.go from waku package
|
|
||||||
cursor := [cursorSize]byte{}
|
|
||||||
cursor[0] = 0x01
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
attempt := 0
|
|
||||||
for {
|
|
||||||
attempt++
|
|
||||||
msg, err := s.remoteRW.ReadMsg()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
if attempt < target {
|
|
||||||
s.Require().NoError(msg.Discard())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var e wakucommon.Envelope
|
|
||||||
s.Require().NoError(msg.Decode(&e))
|
|
||||||
s.Require().NoError(p2p.Send(s.remoteRW, p2pRequestCompleteCode, waku.CreateMailServerRequestCompletedPayload(e.Hash(), common.Hash{}, cursor[:])))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
resp, err := s.localAPI.RequestMessagesSync(
|
|
||||||
ext.RetryConfig{
|
|
||||||
BaseTimeout: time.Second,
|
|
||||||
MaxRetries: target,
|
|
||||||
},
|
|
||||||
ext.MessagesRequest{
|
|
||||||
MailServerPeer: s.localNode.String(),
|
|
||||||
Force: true, // force true is convenient here because timeout is less then default delay (3s)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Equal(ext.MessagesResponse{Cursor: hex.EncodeToString(cursor[:])}, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RequestMessagesSyncSuite) TestCompletedFromFirstAttempt() {
|
|
||||||
s.testCompletedFromAttempt(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RequestMessagesSyncSuite) TestCompletedFromSecondAttempt() {
|
|
||||||
s.testCompletedFromAttempt(2)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
/*
|
|
||||||
Package benchmarks contains tests that can be used
|
|
||||||
to run benchmarks and stress tests of our cluster components.
|
|
||||||
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
1. Start a Whisper node with mail server capability:
|
|
||||||
./build/bin/statusd -c mailserver-config.json
|
|
||||||
|
|
||||||
where mailserver-config.json contains:
|
|
||||||
``` json
|
|
||||||
{
|
|
||||||
"NetworkId": 4,
|
|
||||||
"DataDir": "./ethereumtest/rinkeby_rpc",
|
|
||||||
"BackupDisabledDataDir": "./ethereumtest/rinkeby_rpc",
|
|
||||||
"KeyStoreDir": "./ethereumtest/keystore",
|
|
||||||
"MaxPeers": 100,
|
|
||||||
"LogLevel": "DEBUG",
|
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"EnableMailServer": true,
|
|
||||||
"DataDir": "./ethereumtest/wnode",
|
|
||||||
"MinimumPoW": 0.002,
|
|
||||||
"MailServerPassword": "status-offline-inbox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Generate some messages:
|
|
||||||
go test -v -timeout=30s -run TestSendMessages ./t/benchmarks \
|
|
||||||
-peerurl=$ENODE_ADDR \
|
|
||||||
-msgcount=200 \
|
|
||||||
-msgbatchsize=50
|
|
||||||
3. Retrieve them from the mail server:
|
|
||||||
go test -v -timeout=30s -parallel 20 \
|
|
||||||
-run TestConcurrentMailserverPeers
|
|
||||||
./t/benchmarks \
|
|
||||||
-peerurl=$ENODE_ADDR \
|
|
||||||
-msgcount=200
|
|
||||||
|
|
||||||
The result of the last command will tell you how long it took to
|
|
||||||
retrieve 200 messages with 20 concurrent peers (20 * 200 messages
|
|
||||||
in total).
|
|
||||||
|
|
||||||
The result may be affected due to limitations of the host
|
|
||||||
on which it was called. It's recommended running mail server
|
|
||||||
on a different machine and running the third command
|
|
||||||
from some beefy server.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package benchmarks
|
|
|
@ -1,27 +0,0 @@
|
||||||
package benchmarks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// general
|
|
||||||
peerURL = flag.String("peerurl", "", "Peer raw URL to which send messages")
|
|
||||||
// mailserver tests
|
|
||||||
ccyPeers = flag.Int("ccypeers", 1, "Number of concurrent peers requesting messages")
|
|
||||||
// messages tests
|
|
||||||
msgPass = flag.String("msgpass", "message-pass", "Password to create sym key from")
|
|
||||||
msgCount = flag.Int64("msgcount", 100, "Number of messages to send")
|
|
||||||
msgSize = flag.Int64("msgsize", int64(1024), "Message size in bytes")
|
|
||||||
msgBatchSize = flag.Int64("msgbatchsize", int64(20), "Number of messages to send in a batch")
|
|
||||||
)
|
|
||||||
|
|
||||||
var peerEnode *enode.Node
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
peerEnode = enode.MustParseV4(*peerURL)
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
package benchmarks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/services/shhext"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
|
|
||||||
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/services/ext"
|
|
||||||
"github.com/status-im/status-go/services/nodebridge"
|
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
mailServerPass = "status-offline-inbox"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestConcurrentMailserverPeers runs `ccyPeers` tests in parallel
|
|
||||||
// that require messages from a MailServer.
|
|
||||||
//
|
|
||||||
// It can be used to test the maximum number of concurrent MailServer peers.
|
|
||||||
//
|
|
||||||
// Messages stored by the MailServer must be generated separately.
|
|
||||||
// Take a look at TestSendMessages test.
|
|
||||||
func TestConcurrentMailserverPeers(t *testing.T) {
|
|
||||||
// Request for messages from mail server
|
|
||||||
for i := 0; i < *ccyPeers; i++ {
|
|
||||||
t.Run(fmt.Sprintf("Peer #%d", i), testMailserverPeer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMailserverPeer(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
shhService := createWhisperService()
|
|
||||||
shhAPI := whisper.NewPublicWhisperAPI(shhService)
|
|
||||||
config := params.ShhextConfig{
|
|
||||||
BackupDisabledDataDir: os.TempDir(),
|
|
||||||
InstallationID: "1",
|
|
||||||
}
|
|
||||||
|
|
||||||
// create node with services
|
|
||||||
n, err := createNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = n.Register(func(_ *node.ServiceContext) (node.Service, error) {
|
|
||||||
return shhService, nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
// Register status-eth-node node bridge
|
|
||||||
err = n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
return &nodebridge.NodeService{Node: gethbridge.NewNodeBridge(n)}, nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
var ethnode *nodebridge.NodeService
|
|
||||||
if err := ctx.Service(ðnode); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w, err := ethnode.Node.GetWhisper(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &nodebridge.WhisperService{Whisper: w}, nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
// register mail service as well
|
|
||||||
err = n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
return shhext.New(config, gethbridge.NewNodeBridge(n), ctx, nil, nil), nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
var mailService *shhext.Service
|
|
||||||
require.NoError(t, n.Service(&mailService))
|
|
||||||
shhextAPI := shhext.NewPublicAPI(mailService)
|
|
||||||
|
|
||||||
// start node
|
|
||||||
require.NoError(t, n.Start())
|
|
||||||
defer func() { require.NoError(t, n.Stop()) }()
|
|
||||||
|
|
||||||
// add mail server as a peer
|
|
||||||
require.NoError(t, addPeerWithConfirmation(n.Server(), peerEnode))
|
|
||||||
|
|
||||||
// sym key to decrypt messages
|
|
||||||
msgSymKeyID, err := shhService.AddSymKeyFromPassword(*msgPass)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// prepare new filter for messages from mail server
|
|
||||||
filterID, err := shhAPI.NewMessageFilter(whisper.Criteria{
|
|
||||||
SymKeyID: msgSymKeyID,
|
|
||||||
Topics: []whisper.TopicType{topic},
|
|
||||||
AllowP2P: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
messages, err := shhAPI.GetFilterMessages(filterID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, messages, 0)
|
|
||||||
|
|
||||||
// request messages from mail server
|
|
||||||
symKeyID, err := shhService.AddSymKeyFromPassword(mailServerPass)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ok, err := shhAPI.MarkTrustedPeer(context.TODO(), *peerURL)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, ok)
|
|
||||||
requestID, err := shhextAPI.RequestMessages(context.TODO(), ext.MessagesRequest{
|
|
||||||
MailServerPeer: *peerURL,
|
|
||||||
SymKeyID: symKeyID,
|
|
||||||
Topic: types.TopicType(topic),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, requestID)
|
|
||||||
// wait for all messages
|
|
||||||
require.NoError(t, waitForMessages(t, *msgCount, shhAPI, filterID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForMessages(t *testing.T, messagesCount int64, shhAPI *whisper.PublicWhisperAPI, filterID string) error {
|
|
||||||
received := int64(0)
|
|
||||||
for range time.After(time.Second) {
|
|
||||||
messages, err := shhAPI.GetFilterMessages(filterID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
received += int64(len(messages))
|
|
||||||
|
|
||||||
fmt.Printf("Received %d messages so far\n", received)
|
|
||||||
|
|
||||||
if received >= messagesCount {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
package benchmarks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestSendMessages sends messages to a peer.
|
|
||||||
//
|
|
||||||
// Because of batching outgoing messages in Whisper V6,
|
|
||||||
// we need to pause and wait until the pending queue
|
|
||||||
// is emptied in Whisper API. Otherwise, the batch
|
|
||||||
// will be too large for the peer to consume it.
|
|
||||||
// It's a potential bug that Whisper code performs
|
|
||||||
// packet.Size > whisper.MaxMessageSize()
|
|
||||||
// check instead of checking the size of each individual message.
|
|
||||||
func TestSendMessages(t *testing.T) {
|
|
||||||
shhService := createWhisperService()
|
|
||||||
shhAPI := whisper.NewPublicWhisperAPI(shhService)
|
|
||||||
|
|
||||||
n, err := createNode()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = n.Register(func(_ *node.ServiceContext) (node.Service, error) {
|
|
||||||
return shhService, nil
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = n.Start()
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer func() { require.NoError(t, n.Stop()) }()
|
|
||||||
|
|
||||||
err = addPeerWithConfirmation(n.Server(), peerEnode)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
symKeyID, err := shhService.AddSymKeyFromPassword(*msgPass)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
payload := make([]byte, *msgSize)
|
|
||||||
_, _ = rand.Read(payload)
|
|
||||||
|
|
||||||
envelopeEvents := make(chan whisper.EnvelopeEvent, 100)
|
|
||||||
sub := shhService.SubscribeEnvelopeEvents(envelopeEvents)
|
|
||||||
defer sub.Unsubscribe()
|
|
||||||
|
|
||||||
batchSent := make(chan struct{})
|
|
||||||
envelopesSent := int64(0)
|
|
||||||
go func() {
|
|
||||||
for ev := range envelopeEvents {
|
|
||||||
if ev.Event != whisper.EventEnvelopeSent {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
envelopesSent++
|
|
||||||
|
|
||||||
if envelopesSent%(*msgBatchSize) == 0 {
|
|
||||||
fmt.Printf("Sent a batch and %d messages\n", envelopesSent)
|
|
||||||
batchSent <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if envelopesSent == *msgCount {
|
|
||||||
fmt.Println("Sent all messages")
|
|
||||||
close(batchSent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := int64(1); i <= *msgCount; i++ {
|
|
||||||
_, err := shhAPI.Post(context.TODO(), whisper.NewMessage{
|
|
||||||
SymKeyID: symKeyID,
|
|
||||||
TTL: 30,
|
|
||||||
Topic: topic,
|
|
||||||
Payload: payload,
|
|
||||||
PowTime: 10,
|
|
||||||
PowTarget: 0.005,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
if i%(*msgBatchSize) == 0 {
|
|
||||||
fmt.Println("Waiting for a batch")
|
|
||||||
<-batchSent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Waiting for all messages to be sent")
|
|
||||||
<-batchSent
|
|
||||||
require.Equal(t, *msgCount, envelopesSent)
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package benchmarks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
topic = whisper.TopicType{0xfa, 0xfb, 0xfc, 0xfd}
|
|
||||||
)
|
|
||||||
|
|
||||||
func createNode() (*node.Node, error) {
|
|
||||||
key, err := crypto.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return node.New(&node.Config{
|
|
||||||
DataDir: "",
|
|
||||||
P2P: p2p.Config{
|
|
||||||
PrivateKey: key,
|
|
||||||
DiscoveryV5: false,
|
|
||||||
NoDiscovery: true,
|
|
||||||
MaxPeers: 1,
|
|
||||||
NAT: nat.Any(),
|
|
||||||
},
|
|
||||||
NoUSB: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPeerWithConfirmation(server *p2p.Server, node *enode.Node) error {
|
|
||||||
ch := make(chan *p2p.PeerEvent, server.MaxPeers)
|
|
||||||
subscription := server.SubscribeEvents(ch)
|
|
||||||
defer subscription.Unsubscribe()
|
|
||||||
|
|
||||||
server.AddPeer(node)
|
|
||||||
|
|
||||||
ev := <-ch
|
|
||||||
if ev.Type != p2p.PeerEventTypeAdd || ev.Peer == node.ID() {
|
|
||||||
return fmt.Errorf("got unexpected event: %+v", ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createWhisperService() *whisper.Whisper {
|
|
||||||
whisperServiceConfig := &whisper.Config{
|
|
||||||
MaxMessageSize: whisper.DefaultMaxMessageSize,
|
|
||||||
MinimumAcceptedPOW: 0.005,
|
|
||||||
}
|
|
||||||
return whisper.New(whisperServiceConfig)
|
|
||||||
}
|
|
|
@ -58,7 +58,7 @@ func (s *DevNodeSuite) SetupTest() {
|
||||||
networks := json.RawMessage("{}")
|
networks := json.RawMessage("{}")
|
||||||
settings := accounts.Settings{Networks: &networks}
|
settings := accounts.Settings{Networks: &networks}
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
config.WhisperConfig.Enabled = false
|
config.WakuConfig.Enabled = false
|
||||||
config.LightEthConfig.Enabled = false
|
config.LightEthConfig.Enabled = false
|
||||||
config.UpstreamConfig.Enabled = true
|
config.UpstreamConfig.Enabled = true
|
||||||
config.WalletConfig.Enabled = true
|
config.WalletConfig.Enabled = true
|
||||||
|
|
|
@ -4,14 +4,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/les"
|
"github.com/ethereum/go-ethereum/les"
|
||||||
gethnode "github.com/ethereum/go-ethereum/node"
|
gethnode "github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
"github.com/status-im/status-go/node"
|
"github.com/status-im/status-go/node"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/whisper"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
@ -66,20 +64,6 @@ func (s *ManagerTestSuite) TestReferencesWithoutStartedNode() {
|
||||||
},
|
},
|
||||||
node.ErrNoRunningNode,
|
node.ErrNoRunningNode,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"non-null manager, no running node, get Whisper",
|
|
||||||
func() (interface{}, error) {
|
|
||||||
return s.StatusNode.WhisperService()
|
|
||||||
},
|
|
||||||
node.ErrNoRunningNode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"non-null manager, no running node, get RPC Client",
|
|
||||||
func() (interface{}, error) {
|
|
||||||
return s.StatusNode.RPCClient(), nil
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
s.T().Log(tc.name)
|
s.T().Log(tc.name)
|
||||||
|
@ -122,20 +106,6 @@ func (s *ManagerTestSuite) TestReferencesWithStartedNode() {
|
||||||
},
|
},
|
||||||
&les.LightEthereum{},
|
&les.LightEthereum{},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"node is running, get Whisper",
|
|
||||||
func() (interface{}, error) {
|
|
||||||
return s.StatusNode.WhisperService()
|
|
||||||
},
|
|
||||||
&whisper.Whisper{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"node is running, get AccountManager",
|
|
||||||
func() (interface{}, error) {
|
|
||||||
return s.StatusNode.AccountManager()
|
|
||||||
},
|
|
||||||
&accounts.Manager{},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"node is running, get RPC Client",
|
"node is running, get RPC Client",
|
||||||
func() (interface{}, error) {
|
func() (interface{}, error) {
|
||||||
|
@ -305,9 +275,9 @@ func (s *ManagerTestSuite) TestStartWithUpstreamEnabled() {
|
||||||
// progress <- struct{}{}
|
// progress <- struct{}{}
|
||||||
// },
|
// },
|
||||||
// func(config *params.NodeConfig) {
|
// func(config *params.NodeConfig) {
|
||||||
// log.Info("WhisperService()")
|
// log.Info("WakuService()")
|
||||||
// _, err := s.StatusNode.WhisperService()
|
// _, err := s.StatusNode.WakuService()
|
||||||
// s.T().Logf("WhisperService(), error: %v", err)
|
// s.T().Logf("WakuService(), error: %v", err)
|
||||||
// progress <- struct{}{}
|
// progress <- struct{}{}
|
||||||
// },
|
// },
|
||||||
// func(config *params.NodeConfig) {
|
// func(config *params.NodeConfig) {
|
||||||
|
|
|
@ -62,13 +62,6 @@ func (s *RPCTestSuite) TestCallRPC() {
|
||||||
validator func(resultJSON string)
|
validator func(resultJSON string)
|
||||||
}
|
}
|
||||||
var rpcCalls = []rpcCall{
|
var rpcCalls = []rpcCall{
|
||||||
{
|
|
||||||
`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`,
|
|
||||||
func(resultJSON string) {
|
|
||||||
expected := `{"jsonrpc":"2.0","id":67,"result":"6.0"}`
|
|
||||||
s.Equal(expected, resultJSON)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`,
|
`{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`,
|
||||||
func(resultJSON string) {
|
func(resultJSON string) {
|
||||||
|
@ -125,22 +118,6 @@ func (s *RPCTestSuite) TestCallRPC() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCallRawResult checks if returned response is a valid JSON-RPC response.
|
|
||||||
func (s *RPCTestSuite) TestCallRawResult() {
|
|
||||||
nodeConfig, err := MakeTestNodeConfig(GetNetworkID())
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
s.NoError(s.StatusNode.Start(nodeConfig, nil))
|
|
||||||
|
|
||||||
client := s.StatusNode.RPCPrivateClient()
|
|
||||||
s.NotNil(client)
|
|
||||||
|
|
||||||
jsonResult := client.CallRaw(`{"jsonrpc":"2.0","method":"shh_version","params":[],"id":67}`)
|
|
||||||
s.Equal(`{"jsonrpc":"2.0","id":67,"result":"6.0"}`, jsonResult)
|
|
||||||
|
|
||||||
s.NoError(s.StatusNode.Stop())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestCallRawResultGetTransactionReceipt checks if returned response
|
// TestCallRawResultGetTransactionReceipt checks if returned response
|
||||||
// for a not yet mined transaction is "result": null.
|
// for a not yet mined transaction is "result": null.
|
||||||
// Issue: https://github.com/status-im/status-go/issues/547
|
// Issue: https://github.com/status-im/status-go/issues/547
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/account"
|
|
||||||
"github.com/status-im/status-go/params"
|
|
||||||
"github.com/status-im/status-go/services/status"
|
|
||||||
"github.com/status-im/status-go/t/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type statusTestParams struct {
|
|
||||||
Address string
|
|
||||||
Password string
|
|
||||||
HandlerFactory func(string, string) func(string)
|
|
||||||
ExpectedError error
|
|
||||||
ChannelName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusAPISuite(t *testing.T) {
|
|
||||||
utils.Init()
|
|
||||||
s := new(StatusAPISuite)
|
|
||||||
s.upstream = false
|
|
||||||
suite.Run(t, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusAPISuiteUpstream(t *testing.T) {
|
|
||||||
utils.Init()
|
|
||||||
s := new(StatusAPISuite)
|
|
||||||
s.upstream = true
|
|
||||||
suite.Run(t, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusAPISuite struct {
|
|
||||||
BaseJSONRPCSuite
|
|
||||||
upstream bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) TestAccessibleStatusAPIs() {
|
|
||||||
if s.upstream && utils.GetNetworkID() == params.StatusChainNetworkID {
|
|
||||||
s.T().Skip()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.SetupTest(s.upstream, true, false)
|
|
||||||
s.NoError(err)
|
|
||||||
defer func() {
|
|
||||||
err := s.Backend.StopNode()
|
|
||||||
s.NoError(err)
|
|
||||||
}()
|
|
||||||
// These status APIs should be unavailable
|
|
||||||
s.AssertAPIMethodUnexported("status_login")
|
|
||||||
s.AssertAPIMethodUnexported("status_signup")
|
|
||||||
|
|
||||||
// These status APIs should be available only for IPC
|
|
||||||
s.AssertAPIMethodExportedPrivately("status_login")
|
|
||||||
s.AssertAPIMethodExportedPrivately("status_signup")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) TestStatusLoginSuccess() {
|
|
||||||
addressKeyID := s.testStatusLogin(statusTestParams{
|
|
||||||
Address: utils.TestConfig.Account1.WalletAddress,
|
|
||||||
Password: utils.TestConfig.Account1.Password,
|
|
||||||
})
|
|
||||||
s.NotEmpty(addressKeyID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) TestStatusLoginInvalidAddress() {
|
|
||||||
s.testStatusLogin(statusTestParams{
|
|
||||||
Address: "invalidaccount",
|
|
||||||
Password: utils.TestConfig.Account1.Password,
|
|
||||||
ExpectedError: account.ErrAddressToAccountMappingFailure,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) TestStatusLoginInvalidPassword() {
|
|
||||||
s.testStatusLogin(statusTestParams{
|
|
||||||
Address: "invalidaccount",
|
|
||||||
Password: utils.TestConfig.Account1.Password,
|
|
||||||
ExpectedError: account.ErrAddressToAccountMappingFailure,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) TestStatusSignupSuccess() {
|
|
||||||
var pwd = "randompassword"
|
|
||||||
|
|
||||||
res := s.testStatusSignup(statusTestParams{
|
|
||||||
Password: pwd,
|
|
||||||
})
|
|
||||||
s.NotEmpty(res.WalletAddress)
|
|
||||||
s.NotEmpty(res.WalletPubkey)
|
|
||||||
s.Equal(12, len(strings.Split(res.Mnemonic, " ")))
|
|
||||||
|
|
||||||
// I should be able to login with the newly created account
|
|
||||||
_ = s.testStatusLogin(statusTestParams{
|
|
||||||
Address: res.WalletAddress,
|
|
||||||
Password: pwd,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) testStatusLogin(testParams statusTestParams) *status.LoginResponse {
|
|
||||||
// Test upstream if that's not StatusChain
|
|
||||||
if s.upstream && utils.GetNetworkID() == params.StatusChainNetworkID {
|
|
||||||
s.T().Skip()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.SetupTest(s.upstream, true, false)
|
|
||||||
s.NoError(err)
|
|
||||||
defer func() {
|
|
||||||
err := s.Backend.StopNode()
|
|
||||||
s.NoError(err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
req := status.LoginRequest{
|
|
||||||
Addr: testParams.Address,
|
|
||||||
Password: testParams.Password,
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(req)
|
|
||||||
|
|
||||||
basicCall := fmt.Sprintf(
|
|
||||||
`{"jsonrpc":"2.0","method":"status_login","params":[%s],"id":67}`,
|
|
||||||
body)
|
|
||||||
|
|
||||||
result, err := s.Backend.CallPrivateRPC(basicCall)
|
|
||||||
s.NoError(err)
|
|
||||||
if testParams.ExpectedError == nil {
|
|
||||||
var r struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Result *status.LoginResponse `json:"result"`
|
|
||||||
}
|
|
||||||
s.NoError(json.Unmarshal([]byte(result), &r))
|
|
||||||
s.Empty(r.Error)
|
|
||||||
|
|
||||||
return r.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Contains(result, testParams.ExpectedError.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatusAPISuite) testStatusSignup(testParams statusTestParams) *status.SignupResponse {
|
|
||||||
// Test upstream if that's not StatusChain
|
|
||||||
if s.upstream && utils.GetNetworkID() == params.StatusChainNetworkID {
|
|
||||||
s.T().Skip()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.SetupTest(s.upstream, true, false)
|
|
||||||
s.NoError(err)
|
|
||||||
defer func() {
|
|
||||||
err := s.Backend.StopNode()
|
|
||||||
s.NoError(err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
req := status.SignupRequest{
|
|
||||||
Password: testParams.Password,
|
|
||||||
}
|
|
||||||
body, _ := json.Marshal(req)
|
|
||||||
|
|
||||||
basicCall := fmt.Sprintf(
|
|
||||||
`{"jsonrpc":"2.0","method":"status_signup","params":[%s],"id":67}`,
|
|
||||||
body)
|
|
||||||
|
|
||||||
result, err := s.Backend.CallPrivateRPC(basicCall)
|
|
||||||
s.NoError(err)
|
|
||||||
|
|
||||||
if testParams.ExpectedError == nil {
|
|
||||||
var r struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
Result *status.SignupResponse `json:"result"`
|
|
||||||
}
|
|
||||||
s.NoError(json.Unmarshal([]byte(result), &r))
|
|
||||||
s.Empty(r.Error)
|
|
||||||
|
|
||||||
return r.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Contains(result, testParams.ExpectedError.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/status-im/status-go/signal"
|
"github.com/status-im/status-go/signal"
|
||||||
"github.com/status-im/status-go/t/utils"
|
"github.com/status-im/status-go/t/utils"
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
"github.com/status-im/status-go/whisper"
|
"github.com/status-im/status-go/waku"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusNodeTestSuite defines a test suite with StatusNode.
|
// StatusNodeTestSuite defines a test suite with StatusNode.
|
||||||
|
@ -167,13 +167,13 @@ func (s *BackendTestSuite) RestartTestNode() {
|
||||||
s.True(s.Backend.IsNodeRunning())
|
s.True(s.Backend.IsNodeRunning())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhisperService returns a reference to the Whisper service.
|
// WakuService returns a reference to the Waku service.
|
||||||
func (s *BackendTestSuite) WhisperService() *whisper.Whisper {
|
func (s *BackendTestSuite) WakuService() *waku.Waku {
|
||||||
whisperService, err := s.Backend.StatusNode().WhisperService()
|
wakuService, err := s.Backend.StatusNode().WakuService()
|
||||||
s.NoError(err)
|
s.NoError(err)
|
||||||
s.NotNil(whisperService)
|
s.NotNil(wakuService)
|
||||||
|
|
||||||
return whisperService
|
return wakuService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transactor returns a reference to the Transactor.
|
// Transactor returns a reference to the Transactor.
|
||||||
|
|
|
@ -26,7 +26,7 @@ func WithDataDir(dataDir string) TestNodeOption {
|
||||||
return func(config *params.NodeConfig) {
|
return func(config *params.NodeConfig) {
|
||||||
config.DataDir = dataDir
|
config.DataDir = dataDir
|
||||||
config.KeyStoreDir = path.Join(dataDir, "keystore")
|
config.KeyStoreDir = path.Join(dataDir, "keystore")
|
||||||
config.WhisperConfig.DataDir = path.Join(dataDir, "wnode")
|
config.WakuConfig.DataDir = path.Join(dataDir, "wnode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,11 +259,6 @@ func MakeTestNodeConfig(networkID int) (*params.NodeConfig, error) {
|
||||||
"LightEthConfig": {
|
"LightEthConfig": {
|
||||||
"Enabled": true
|
"Enabled": true
|
||||||
},
|
},
|
||||||
"WhisperConfig": {
|
|
||||||
"Enabled": true,
|
|
||||||
"DataDir": "` + path.Join(testDir, "wnode") + `",
|
|
||||||
"EnableNTPSync": false
|
|
||||||
},
|
|
||||||
"ShhextConfig": {
|
"ShhextConfig": {
|
||||||
"BackupDisabledDataDir": "` + testDir + `"
|
"BackupDisabledDataDir": "` + testDir + `"
|
||||||
}
|
}
|
||||||
|
@ -293,10 +288,8 @@ func MakeTestNodeConfigWithDataDir(name, dataDir string, networkID uint64) (*par
|
||||||
cfg.EnableNTPSync = true
|
cfg.EnableNTPSync = true
|
||||||
cfg.NoDiscovery = true
|
cfg.NoDiscovery = true
|
||||||
cfg.LightEthConfig.Enabled = false
|
cfg.LightEthConfig.Enabled = false
|
||||||
cfg.WhisperConfig.Enabled = true
|
|
||||||
if dataDir != "" {
|
if dataDir != "" {
|
||||||
cfg.KeyStoreDir = path.Join(dataDir, "keystore")
|
cfg.KeyStoreDir = path.Join(dataDir, "keystore")
|
||||||
cfg.WhisperConfig.DataDir = path.Join(dataDir, "wnode")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only attempt to validate if a dataDir is specified, we only support in-memory DB for tests
|
// Only attempt to validate if a dataDir is specified, we only support in-memory DB for tests
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
|
@ -1,29 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,9 +0,0 @@
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
typedef void (*callback)(void*);
|
|
||||||
|
|
||||||
static void call_later(int delay, callback cb, void* data) {
|
|
||||||
sleep(delay);
|
|
||||||
cb(data);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package pointer
|
|
|
@ -1,57 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
engines:
|
|
||||||
gofmt:
|
|
||||||
enabled: true
|
|
||||||
golint:
|
|
||||||
enabled: true
|
|
||||||
govet:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
exclude_patterns:
|
|
||||||
- ".github/"
|
|
||||||
- "vendor/"
|
|
||||||
- "codegen/"
|
|
||||||
- "doc.go"
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
|
@ -1,25 +0,0 @@
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.8
|
|
||||||
- 1.9
|
|
||||||
- tip
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- CC_TEST_REPORTER_ID=68feaa3410049ce73e145287acbcdacc525087a30627f96f04e579e75bd71c00
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
|
||||||
- chmod +x ./cc-test-reporter
|
|
||||||
- ./cc-test-reporter before-build
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get github.com/go-task/task/cmd/task
|
|
||||||
|
|
||||||
script:
|
|
||||||
- task dl-deps
|
|
||||||
- task lint
|
|
||||||
- task test-coverage
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
|
|
|
@ -1,30 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/davecgh/go-spew"
|
|
||||||
packages = ["spew"]
|
|
||||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
version = "v1.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
packages = [
|
|
||||||
"assert",
|
|
||||||
"require"
|
|
||||||
]
|
|
||||||
revision = "b91bfb9ebec76498946beb6af7c0230c7cc7ba6c"
|
|
||||||
version = "v1.2.0"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "2d160a7dea4ffd13c6c31dab40373822f9d78c73beba016d662bef8f7a998876"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
|
@ -1,8 +0,0 @@
|
||||||
[prune]
|
|
||||||
unused-packages = true
|
|
||||||
non-go = true
|
|
||||||
go-tests = true
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
version = "~1.2.0"
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2014 Stretchr, Inc.
|
|
||||||
Copyright (c) 2017-2018 objx contributors
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,80 +0,0 @@
|
||||||
# Objx
|
|
||||||
[![Build Status](https://travis-ci.org/stretchr/objx.svg?branch=master)](https://travis-ci.org/stretchr/objx)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/objx)](https://goreportcard.com/report/github.com/stretchr/objx)
|
|
||||||
[![Maintainability](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/maintainability)](https://codeclimate.com/github/stretchr/objx/maintainability)
|
|
||||||
[![Test Coverage](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/test_coverage)](https://codeclimate.com/github/stretchr/objx/test_coverage)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/stretchr/objx/-/badge.svg)](https://sourcegraph.com/github.com/stretchr/objx)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/stretchr/objx?status.svg)](https://godoc.org/github.com/stretchr/objx)
|
|
||||||
|
|
||||||
Objx - Go package for dealing with maps, slices, JSON and other data.
|
|
||||||
|
|
||||||
Get started:
|
|
||||||
|
|
||||||
- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date)
|
|
||||||
- Check out the API Documentation http://godoc.org/github.com/stretchr/objx
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc.
|
|
||||||
|
|
||||||
### Pattern
|
|
||||||
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going:
|
|
||||||
|
|
||||||
m, err := objx.FromJSON(json)
|
|
||||||
|
|
||||||
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking.
|
|
||||||
|
|
||||||
Use `Get` to access the value you're interested in. You can use dot and array
|
|
||||||
notation too:
|
|
||||||
|
|
||||||
m.Get("places[0].latlng")
|
|
||||||
|
|
||||||
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
|
|
||||||
|
|
||||||
if m.Get("code").IsStr() { // Your code... }
|
|
||||||
|
|
||||||
Or you can just assume the type, and use one of the strong type methods to extract the real value:
|
|
||||||
|
|
||||||
m.Get("code").Int()
|
|
||||||
|
|
||||||
If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value.
|
|
||||||
|
|
||||||
Get("code").Int(-1)
|
|
||||||
|
|
||||||
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below.
|
|
||||||
|
|
||||||
### Reading data
|
|
||||||
A simple example of how to use Objx:
|
|
||||||
|
|
||||||
// Use MustFromJSON to make an objx.Map from some JSON
|
|
||||||
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
|
||||||
|
|
||||||
// Get the details
|
|
||||||
name := m.Get("name").Str()
|
|
||||||
age := m.Get("age").Int()
|
|
||||||
|
|
||||||
// Get their nickname (or use their name if they don't have one)
|
|
||||||
nickname := m.Get("nickname").Str(name)
|
|
||||||
|
|
||||||
### Ranging
|
|
||||||
Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect:
|
|
||||||
|
|
||||||
m := objx.MustFromJSON(json)
|
|
||||||
for key, value := range m {
|
|
||||||
// Your code...
|
|
||||||
}
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
To install Objx, use go get:
|
|
||||||
|
|
||||||
go get github.com/stretchr/objx
|
|
||||||
|
|
||||||
### Staying up to date
|
|
||||||
To update Objx to the latest version, run:
|
|
||||||
|
|
||||||
go get -u github.com/stretchr/objx
|
|
||||||
|
|
||||||
### Supported go versions
|
|
||||||
We support the lastest two major Go versions, which are 1.8 and 1.9 at the moment.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
Please feel free to submit issues, fork the repository and send pull requests!
|
|
|
@ -1,32 +0,0 @@
|
||||||
default:
|
|
||||||
deps: [test]
|
|
||||||
|
|
||||||
dl-deps:
|
|
||||||
desc: Downloads cli dependencies
|
|
||||||
cmds:
|
|
||||||
- go get -u github.com/golang/lint/golint
|
|
||||||
- go get -u github.com/golang/dep/cmd/dep
|
|
||||||
|
|
||||||
update-deps:
|
|
||||||
desc: Updates dependencies
|
|
||||||
cmds:
|
|
||||||
- dep ensure
|
|
||||||
- dep ensure -update
|
|
||||||
|
|
||||||
lint:
|
|
||||||
desc: Runs golint
|
|
||||||
cmds:
|
|
||||||
- go fmt $(go list ./... | grep -v /vendor/)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- golint $(ls *.go | grep -v "doc.go")
|
|
||||||
silent: true
|
|
||||||
|
|
||||||
test:
|
|
||||||
desc: Runs go tests
|
|
||||||
cmds:
|
|
||||||
- go test -race .
|
|
||||||
|
|
||||||
test-coverage:
|
|
||||||
desc: Runs go tests and calucates test coverage
|
|
||||||
cmds:
|
|
||||||
- go test -coverprofile=c.out .
|
|
|
@ -1,148 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// arrayAccesRegexString is the regex used to extract the array number
|
|
||||||
// from the access path
|
|
||||||
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
|
|
||||||
|
|
||||||
// arrayAccesRegex is the compiled arrayAccesRegexString
|
|
||||||
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
|
|
||||||
|
|
||||||
// Get gets the value using the specified selector and
|
|
||||||
// returns it inside a new Obj object.
|
|
||||||
//
|
|
||||||
// If it cannot find the value, Get will return a nil
|
|
||||||
// value inside an instance of Obj.
|
|
||||||
//
|
|
||||||
// Get can only operate directly on map[string]interface{} and []interface.
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To access the title of the third chapter of the second book, do:
|
|
||||||
//
|
|
||||||
// o.Get("books[1].chapters[2].title")
|
|
||||||
func (m Map) Get(selector string) *Value {
|
|
||||||
rawObj := access(m, selector, nil, false)
|
|
||||||
return &Value{data: rawObj}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set sets the value using the specified selector and
|
|
||||||
// returns the object on which Set was called.
|
|
||||||
//
|
|
||||||
// Set can only operate directly on map[string]interface{} and []interface
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To set the title of the third chapter of the second book, do:
|
|
||||||
//
|
|
||||||
// o.Set("books[1].chapters[2].title","Time to Go")
|
|
||||||
func (m Map) Set(selector string, value interface{}) Map {
|
|
||||||
access(m, selector, value, true)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// access accesses the object using the selector and performs the
|
|
||||||
// appropriate action.
|
|
||||||
func access(current, selector, value interface{}, isSet bool) interface{} {
|
|
||||||
switch selector.(type) {
|
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
|
||||||
if array, ok := current.([]interface{}); ok {
|
|
||||||
index := intFromInterface(selector)
|
|
||||||
if index >= len(array) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return array[index]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case string:
|
|
||||||
selStr := selector.(string)
|
|
||||||
selSegs := strings.SplitN(selStr, PathSeparator, 2)
|
|
||||||
thisSel := selSegs[0]
|
|
||||||
index := -1
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.Contains(thisSel, "[") {
|
|
||||||
arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)
|
|
||||||
if len(arrayMatches) > 0 {
|
|
||||||
// Get the key into the map
|
|
||||||
thisSel = arrayMatches[1]
|
|
||||||
|
|
||||||
// Get the index into the array at the key
|
|
||||||
index, err = strconv.Atoi(arrayMatches[2])
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// This should never happen. If it does, something has gone
|
|
||||||
// seriously wrong. Panic.
|
|
||||||
panic("objx: Array index is not an integer. Must use array[int].")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if curMap, ok := current.(Map); ok {
|
|
||||||
current = map[string]interface{}(curMap)
|
|
||||||
}
|
|
||||||
// get the object in question
|
|
||||||
switch current.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
curMSI := current.(map[string]interface{})
|
|
||||||
if len(selSegs) <= 1 && isSet {
|
|
||||||
curMSI[thisSel] = value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
current = curMSI[thisSel]
|
|
||||||
default:
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
// do we need to access the item of an array?
|
|
||||||
if index > -1 {
|
|
||||||
if array, ok := current.([]interface{}); ok {
|
|
||||||
if index < len(array) {
|
|
||||||
current = array[index]
|
|
||||||
} else {
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(selSegs) > 1 {
|
|
||||||
current = access(current, selSegs[1], value, isSet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
// intFromInterface converts an interface object to the largest
|
|
||||||
// representation of an unsigned integer using a type switch and
|
|
||||||
// assertions
|
|
||||||
func intFromInterface(selector interface{}) int {
|
|
||||||
var value int
|
|
||||||
switch selector.(type) {
|
|
||||||
case int:
|
|
||||||
value = selector.(int)
|
|
||||||
case int8:
|
|
||||||
value = int(selector.(int8))
|
|
||||||
case int16:
|
|
||||||
value = int(selector.(int16))
|
|
||||||
case int32:
|
|
||||||
value = int(selector.(int32))
|
|
||||||
case int64:
|
|
||||||
value = int(selector.(int64))
|
|
||||||
case uint:
|
|
||||||
value = int(selector.(uint))
|
|
||||||
case uint8:
|
|
||||||
value = int(selector.(uint8))
|
|
||||||
case uint16:
|
|
||||||
value = int(selector.(uint16))
|
|
||||||
case uint32:
|
|
||||||
value = int(selector.(uint32))
|
|
||||||
case uint64:
|
|
||||||
value = int(selector.(uint64))
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
const (
|
|
||||||
// PathSeparator is the character used to separate the elements
|
|
||||||
// of the keypath.
|
|
||||||
//
|
|
||||||
// For example, `location.address.city`
|
|
||||||
PathSeparator string = "."
|
|
||||||
|
|
||||||
// SignatureSeparator is the character that is used to
|
|
||||||
// separate the Base64 string from the security signature.
|
|
||||||
SignatureSeparator = "_"
|
|
||||||
)
|
|
|
@ -1,108 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSON converts the contained object to a JSON string
|
|
||||||
// representation
|
|
||||||
func (m Map) JSON() (string, error) {
|
|
||||||
result, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
err = errors.New("objx: JSON encode failed with: " + err.Error())
|
|
||||||
}
|
|
||||||
return string(result), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustJSON converts the contained object to a JSON string
|
|
||||||
// representation and panics if there is an error
|
|
||||||
func (m Map) MustJSON() string {
|
|
||||||
result, err := m.JSON()
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation
|
|
||||||
func (m Map) Base64() (string, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
|
|
||||||
jsonData, err := m.JSON()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
|
|
||||||
_, err = encoder.Write([]byte(jsonData))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
_ = encoder.Close()
|
|
||||||
|
|
||||||
return buf.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and panics
|
|
||||||
// if there is an error
|
|
||||||
func (m Map) MustBase64() string {
|
|
||||||
result, err := m.Base64()
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and signs it
|
|
||||||
// using the provided key.
|
|
||||||
func (m Map) SignedBase64(key string) (string, error) {
|
|
||||||
base64, err := m.Base64()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := HashWithKey(base64, key)
|
|
||||||
return base64 + SignatureSeparator + sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustSignedBase64 converts the contained object to a Base64 string
|
|
||||||
// representation of the JSON string representation and signs it
|
|
||||||
// using the provided key and panics if there is an error
|
|
||||||
func (m Map) MustSignedBase64(key string) string {
|
|
||||||
result, err := m.SignedBase64(key)
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
URL Query
|
|
||||||
------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
// URLValues creates a url.Values object from an Obj. This
|
|
||||||
// function requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) URLValues() url.Values {
|
|
||||||
vals := make(url.Values)
|
|
||||||
for k, v := range m {
|
|
||||||
//TODO: can this be done without sprintf?
|
|
||||||
vals.Set(k, fmt.Sprintf("%v", v))
|
|
||||||
}
|
|
||||||
return vals
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLQuery gets an encoded URL query representing the given
|
|
||||||
// Obj. This function requires that the wrapped object be a
|
|
||||||
// map[string]interface{}
|
|
||||||
func (m Map) URLQuery() (string, error) {
|
|
||||||
return m.URLValues().Encode(), nil
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Objx - Go package for dealing with maps, slices, JSON and other data.
|
|
||||||
|
|
||||||
Overview
|
|
||||||
|
|
||||||
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
|
|
||||||
a powerful `Get` method (among others) that allows you to easily and quickly get
|
|
||||||
access to data within the map, without having to worry too much about type assertions,
|
|
||||||
missing data, default values etc.
|
|
||||||
|
|
||||||
Pattern
|
|
||||||
|
|
||||||
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy.
|
|
||||||
Call one of the `objx.` functions to create your `objx.Map` to get going:
|
|
||||||
|
|
||||||
m, err := objx.FromJSON(json)
|
|
||||||
|
|
||||||
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
|
|
||||||
the rest will be optimistic and try to figure things out without panicking.
|
|
||||||
|
|
||||||
Use `Get` to access the value you're interested in. You can use dot and array
|
|
||||||
notation too:
|
|
||||||
|
|
||||||
m.Get("places[0].latlng")
|
|
||||||
|
|
||||||
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
|
|
||||||
|
|
||||||
if m.Get("code").IsStr() { // Your code... }
|
|
||||||
|
|
||||||
Or you can just assume the type, and use one of the strong type methods to extract the real value:
|
|
||||||
|
|
||||||
m.Get("code").Int()
|
|
||||||
|
|
||||||
If there's no value there (or if it's the wrong type) then a default value will be returned,
|
|
||||||
or you can be explicit about the default value.
|
|
||||||
|
|
||||||
Get("code").Int(-1)
|
|
||||||
|
|
||||||
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating,
|
|
||||||
manipulating and selecting that data. You can find out more by exploring the index below.
|
|
||||||
|
|
||||||
Reading data
|
|
||||||
|
|
||||||
A simple example of how to use Objx:
|
|
||||||
|
|
||||||
// Use MustFromJSON to make an objx.Map from some JSON
|
|
||||||
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
|
||||||
|
|
||||||
// Get the details
|
|
||||||
name := m.Get("name").Str()
|
|
||||||
age := m.Get("age").Int()
|
|
||||||
|
|
||||||
// Get their nickname (or use their name if they don't have one)
|
|
||||||
nickname := m.Get("nickname").Str(name)
|
|
||||||
|
|
||||||
Ranging
|
|
||||||
|
|
||||||
Since `objx.Map` is a `map[string]interface{}` you can treat it as such.
|
|
||||||
For example, to `range` the data, do what you would expect:
|
|
||||||
|
|
||||||
m := objx.MustFromJSON(json)
|
|
||||||
for key, value := range m {
|
|
||||||
// Your code...
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
package objx
|
|
|
@ -1,190 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MSIConvertable is an interface that defines methods for converting your
|
|
||||||
// custom types to a map[string]interface{} representation.
|
|
||||||
type MSIConvertable interface {
|
|
||||||
// MSI gets a map[string]interface{} (msi) representing the
|
|
||||||
// object.
|
|
||||||
MSI() map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map provides extended functionality for working with
|
|
||||||
// untyped data, in particular map[string]interface (msi).
|
|
||||||
type Map map[string]interface{}
|
|
||||||
|
|
||||||
// Value returns the internal value instance
|
|
||||||
func (m Map) Value() *Value {
|
|
||||||
return &Value{data: m}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nil represents a nil Map.
|
|
||||||
var Nil = New(nil)
|
|
||||||
|
|
||||||
// New creates a new Map containing the map[string]interface{} in the data argument.
|
|
||||||
// If the data argument is not a map[string]interface, New attempts to call the
|
|
||||||
// MSI() method on the MSIConvertable interface to create one.
|
|
||||||
func New(data interface{}) Map {
|
|
||||||
if _, ok := data.(map[string]interface{}); !ok {
|
|
||||||
if converter, ok := data.(MSIConvertable); ok {
|
|
||||||
data = converter.MSI()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Map(data.(map[string]interface{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MSI creates a map[string]interface{} and puts it inside a new Map.
|
|
||||||
//
|
|
||||||
// The arguments follow a key, value pattern.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Returns nil if any key argument is non-string or if there are an odd number of arguments.
|
|
||||||
//
|
|
||||||
// Example
|
|
||||||
//
|
|
||||||
// To easily create Maps:
|
|
||||||
//
|
|
||||||
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
|
|
||||||
//
|
|
||||||
// // creates an Map equivalent to
|
|
||||||
// m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}}
|
|
||||||
func MSI(keyAndValuePairs ...interface{}) Map {
|
|
||||||
newMap := Map{}
|
|
||||||
keyAndValuePairsLen := len(keyAndValuePairs)
|
|
||||||
if keyAndValuePairsLen%2 != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
|
|
||||||
key := keyAndValuePairs[i]
|
|
||||||
value := keyAndValuePairs[i+1]
|
|
||||||
|
|
||||||
// make sure the key is a string
|
|
||||||
keyString, keyStringOK := key.(string)
|
|
||||||
if !keyStringOK {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
newMap[keyString] = value
|
|
||||||
}
|
|
||||||
return newMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// ****** Conversion Constructors
|
|
||||||
|
|
||||||
// MustFromJSON creates a new Map containing the data specified in the
|
|
||||||
// jsonString.
|
|
||||||
//
|
|
||||||
// Panics if the JSON is invalid.
|
|
||||||
func MustFromJSON(jsonString string) Map {
|
|
||||||
o, err := FromJSON(jsonString)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromJSON failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromJSON creates a new Map containing the data specified in the
|
|
||||||
// jsonString.
|
|
||||||
//
|
|
||||||
// Returns an error if the JSON is invalid.
|
|
||||||
func FromJSON(jsonString string) (Map, error) {
|
|
||||||
var data interface{}
|
|
||||||
err := json.Unmarshal([]byte(jsonString), &data)
|
|
||||||
if err != nil {
|
|
||||||
return Nil, err
|
|
||||||
}
|
|
||||||
return New(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func FromBase64(base64String string) (Map, error) {
|
|
||||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
|
|
||||||
decoded, err := ioutil.ReadAll(decoder)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromJSON(string(decoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string and panics if there is an error.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func MustFromBase64(base64String string) Map {
|
|
||||||
result, err := FromBase64(base64String)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromBase64 failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromSignedBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by SignedBase64
|
|
||||||
func FromSignedBase64(base64String, key string) (Map, error) {
|
|
||||||
parts := strings.Split(base64String, SignatureSeparator)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.New("objx: Signed base64 string is malformed")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := HashWithKey(parts[0], key)
|
|
||||||
if parts[1] != sig {
|
|
||||||
return nil, errors.New("objx: Signature for base64 data does not match")
|
|
||||||
}
|
|
||||||
return FromBase64(parts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromSignedBase64 creates a new Obj containing the data specified
|
|
||||||
// in the Base64 string and panics if there is an error.
|
|
||||||
//
|
|
||||||
// The string is an encoded JSON string returned by Base64
|
|
||||||
func MustFromSignedBase64(base64String, key string) Map {
|
|
||||||
result, err := FromSignedBase64(base64String, key)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromURLQuery generates a new Obj by parsing the specified
|
|
||||||
// query.
|
|
||||||
//
|
|
||||||
// For queries with multiple values, the first value is selected.
|
|
||||||
func FromURLQuery(query string) (Map, error) {
|
|
||||||
vals, err := url.ParseQuery(query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := Map{}
|
|
||||||
for k, vals := range vals {
|
|
||||||
m[k] = vals[0]
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustFromURLQuery generates a new Obj by parsing the specified
|
|
||||||
// query.
|
|
||||||
//
|
|
||||||
// For queries with multiple values, the first value is selected.
|
|
||||||
//
|
|
||||||
// Panics if it encounters an error
|
|
||||||
func MustFromURLQuery(query string) Map {
|
|
||||||
o, err := FromURLQuery(query)
|
|
||||||
if err != nil {
|
|
||||||
panic("objx: MustFromURLQuery failed with error: " + err.Error())
|
|
||||||
}
|
|
||||||
return o
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
// Exclude returns a new Map with the keys in the specified []string
|
|
||||||
// excluded.
|
|
||||||
func (m Map) Exclude(exclude []string) Map {
|
|
||||||
excluded := make(Map)
|
|
||||||
for k, v := range m {
|
|
||||||
if !contains(exclude, k) {
|
|
||||||
excluded[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return excluded
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a shallow copy of the Obj.
|
|
||||||
func (m Map) Copy() Map {
|
|
||||||
copied := Map{}
|
|
||||||
for k, v := range m {
|
|
||||||
copied[k] = v
|
|
||||||
}
|
|
||||||
return copied
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge blends the specified map with a copy of this map and returns the result.
|
|
||||||
//
|
|
||||||
// Keys that appear in both will be selected from the specified map.
|
|
||||||
// This method requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) Merge(merge Map) Map {
|
|
||||||
return m.Copy().MergeHere(merge)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MergeHere blends the specified map with this map and returns the current map.
|
|
||||||
//
|
|
||||||
// Keys that appear in both will be selected from the specified map. The original map
|
|
||||||
// will be modified. This method requires that
|
|
||||||
// the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) MergeHere(merge Map) Map {
|
|
||||||
for k, v := range merge {
|
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform builds a new Obj giving the transformer a chance
|
|
||||||
// to change the keys and values as it goes. This method requires that
|
|
||||||
// the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
|
|
||||||
newMap := Map{}
|
|
||||||
for k, v := range m {
|
|
||||||
modifiedKey, modifiedVal := transformer(k, v)
|
|
||||||
newMap[modifiedKey] = modifiedVal
|
|
||||||
}
|
|
||||||
return newMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransformKeys builds a new map using the specified key mapping.
|
|
||||||
//
|
|
||||||
// Unspecified keys will be unaltered.
|
|
||||||
// This method requires that the wrapped object be a map[string]interface{}
|
|
||||||
func (m Map) TransformKeys(mapping map[string]string) Map {
|
|
||||||
return m.Transform(func(key string, value interface{}) (string, interface{}) {
|
|
||||||
if newKey, ok := mapping[key]; ok {
|
|
||||||
return newKey, value
|
|
||||||
}
|
|
||||||
return key, value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a string slice contains a string
|
|
||||||
func contains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HashWithKey hashes the specified string using the security key
|
|
||||||
func HashWithKey(data, key string) string {
|
|
||||||
d := sha1.Sum([]byte(data + ":" + key))
|
|
||||||
return hex.EncodeToString(d[:])
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
// Has gets whether there is something at the specified selector
|
|
||||||
// or not.
|
|
||||||
//
|
|
||||||
// If m is nil, Has will always return false.
|
|
||||||
func (m Map) Has(selector string) bool {
|
|
||||||
if m == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !m.Get(selector).IsNil()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNil gets whether the data is nil or not.
|
|
||||||
func (v *Value) IsNil() bool {
|
|
||||||
return v == nil || v.data == nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,53 +0,0 @@
|
||||||
package objx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Value provides methods for extracting interface{} data in various
|
|
||||||
// types.
|
|
||||||
type Value struct {
|
|
||||||
// data contains the raw data being managed by this Value
|
|
||||||
data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Data returns the raw data contained by this Value
|
|
||||||
func (v *Value) Data() interface{} {
|
|
||||||
return v.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the value always as a string
|
|
||||||
func (v *Value) String() string {
|
|
||||||
switch {
|
|
||||||
case v.IsStr():
|
|
||||||
return v.Str()
|
|
||||||
case v.IsBool():
|
|
||||||
return strconv.FormatBool(v.Bool())
|
|
||||||
case v.IsFloat32():
|
|
||||||
return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32)
|
|
||||||
case v.IsFloat64():
|
|
||||||
return strconv.FormatFloat(v.Float64(), 'f', -1, 64)
|
|
||||||
case v.IsInt():
|
|
||||||
return strconv.FormatInt(int64(v.Int()), 10)
|
|
||||||
case v.IsInt8():
|
|
||||||
return strconv.FormatInt(int64(v.Int8()), 10)
|
|
||||||
case v.IsInt16():
|
|
||||||
return strconv.FormatInt(int64(v.Int16()), 10)
|
|
||||||
case v.IsInt32():
|
|
||||||
return strconv.FormatInt(int64(v.Int32()), 10)
|
|
||||||
case v.IsInt64():
|
|
||||||
return strconv.FormatInt(v.Int64(), 10)
|
|
||||||
case v.IsUint():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint()), 10)
|
|
||||||
case v.IsUint8():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint8()), 10)
|
|
||||||
case v.IsUint16():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint16()), 10)
|
|
||||||
case v.IsUint32():
|
|
||||||
return strconv.FormatUint(uint64(v.Uint32()), 10)
|
|
||||||
case v.IsUint64():
|
|
||||||
return strconv.FormatUint(v.Uint64(), 10)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%#v", v.Data())
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Package mock provides a system by which it is possible to mock your objects
|
|
||||||
// and verify calls are happening as expected.
|
|
||||||
//
|
|
||||||
// Example Usage
|
|
||||||
//
|
|
||||||
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
|
|
||||||
// embedded into a test object as shown below:
|
|
||||||
//
|
|
||||||
// type MyTestObject struct {
|
|
||||||
// // add a Mock object instance
|
|
||||||
// mock.Mock
|
|
||||||
//
|
|
||||||
// // other fields go here as normal
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// When implementing the methods of an interface, you wire your functions up
|
|
||||||
// to call the Mock.Called(args...) method, and return the appropriate values.
|
|
||||||
//
|
|
||||||
// For example, to mock a method that saves the name and age of a person and returns
|
|
||||||
// the year of their birth or an error, you might write this:
|
|
||||||
//
|
|
||||||
// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
|
|
||||||
// args := o.Called(firstname, lastname, age)
|
|
||||||
// return args.Int(0), args.Error(1)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The Int, Error and Bool methods are examples of strongly typed getters that take the argument
|
|
||||||
// index position. Given this argument list:
|
|
||||||
//
|
|
||||||
// (12, true, "Something")
|
|
||||||
//
|
|
||||||
// You could read them out strongly typed like this:
|
|
||||||
//
|
|
||||||
// args.Int(0)
|
|
||||||
// args.Bool(1)
|
|
||||||
// args.String(2)
|
|
||||||
//
|
|
||||||
// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion:
|
|
||||||
//
|
|
||||||
// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
|
|
||||||
//
|
|
||||||
// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those
|
|
||||||
// cases you should check for nil first.
|
|
||||||
package mock
|
|
|
@ -1,917 +0,0 @@
|
||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/pmezard/go-difflib/difflib"
|
|
||||||
"github.com/stretchr/objx"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestingT is an interface wrapper around *testing.T
|
|
||||||
type TestingT interface {
|
|
||||||
Logf(format string, args ...interface{})
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Call
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Call represents a method call and is used for setting expectations,
|
|
||||||
// as well as recording activity.
|
|
||||||
type Call struct {
|
|
||||||
Parent *Mock
|
|
||||||
|
|
||||||
// The name of the method that was or will be called.
|
|
||||||
Method string
|
|
||||||
|
|
||||||
// Holds the arguments of the method.
|
|
||||||
Arguments Arguments
|
|
||||||
|
|
||||||
// Holds the arguments that should be returned when
|
|
||||||
// this method is called.
|
|
||||||
ReturnArguments Arguments
|
|
||||||
|
|
||||||
// Holds the caller info for the On() call
|
|
||||||
callerInfo []string
|
|
||||||
|
|
||||||
// The number of times to return the return arguments when setting
|
|
||||||
// expectations. 0 means to always return the value.
|
|
||||||
Repeatability int
|
|
||||||
|
|
||||||
// Amount of times this call has been called
|
|
||||||
totalCalls int
|
|
||||||
|
|
||||||
// Call to this method can be optional
|
|
||||||
optional bool
|
|
||||||
|
|
||||||
// Holds a channel that will be used to block the Return until it either
|
|
||||||
// receives a message or is closed. nil means it returns immediately.
|
|
||||||
WaitFor <-chan time.Time
|
|
||||||
|
|
||||||
waitTime time.Duration
|
|
||||||
|
|
||||||
// Holds a handler used to manipulate arguments content that are passed by
|
|
||||||
// reference. It's useful when mocking methods such as unmarshalers or
|
|
||||||
// decoders.
|
|
||||||
RunFn func(Arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call {
|
|
||||||
return &Call{
|
|
||||||
Parent: parent,
|
|
||||||
Method: methodName,
|
|
||||||
Arguments: methodArguments,
|
|
||||||
ReturnArguments: make([]interface{}, 0),
|
|
||||||
callerInfo: callerInfo,
|
|
||||||
Repeatability: 0,
|
|
||||||
WaitFor: nil,
|
|
||||||
RunFn: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Call) lock() {
|
|
||||||
c.Parent.mutex.Lock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Call) unlock() {
|
|
||||||
c.Parent.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return specifies the return arguments for the expectation.
|
|
||||||
//
|
|
||||||
// Mock.On("DoSomething").Return(errors.New("failed"))
|
|
||||||
func (c *Call) Return(returnArguments ...interface{}) *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
|
|
||||||
c.ReturnArguments = returnArguments
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once indicates that that the mock should only return the value once.
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
|
|
||||||
func (c *Call) Once() *Call {
|
|
||||||
return c.Times(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Twice indicates that that the mock should only return the value twice.
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
|
|
||||||
func (c *Call) Twice() *Call {
|
|
||||||
return c.Times(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Times indicates that that the mock should only return the indicated number
|
|
||||||
// of times.
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
|
|
||||||
func (c *Call) Times(i int) *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
c.Repeatability = i
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitUntil sets the channel that will block the mock's return until its closed
|
|
||||||
// or a message is received.
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second))
|
|
||||||
func (c *Call) WaitUntil(w <-chan time.Time) *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
c.WaitFor = w
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// After sets how long to block until the call returns
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2).After(time.Second)
|
|
||||||
func (c *Call) After(d time.Duration) *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
c.waitTime = d
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run sets a handler to be called before returning. It can be used when
|
|
||||||
// mocking a method (such as an unmarshaler) that takes a pointer to a struct and
|
|
||||||
// sets properties in such struct
|
|
||||||
//
|
|
||||||
// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) {
|
|
||||||
// arg := args.Get(0).(*map[string]interface{})
|
|
||||||
// arg["foo"] = "bar"
|
|
||||||
// })
|
|
||||||
func (c *Call) Run(fn func(args Arguments)) *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
c.RunFn = fn
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maybe allows the method call to be optional. Not calling an optional method
|
|
||||||
// will not cause an error while asserting expectations
|
|
||||||
func (c *Call) Maybe() *Call {
|
|
||||||
c.lock()
|
|
||||||
defer c.unlock()
|
|
||||||
c.optional = true
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// On chains a new expectation description onto the mocked interface. This
|
|
||||||
// allows syntax like.
|
|
||||||
//
|
|
||||||
// Mock.
|
|
||||||
// On("MyMethod", 1).Return(nil).
|
|
||||||
// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error"))
|
|
||||||
//go:noinline
|
|
||||||
func (c *Call) On(methodName string, arguments ...interface{}) *Call {
|
|
||||||
return c.Parent.On(methodName, arguments...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock is the workhorse used to track activity on another object.
|
|
||||||
// For an example of its usage, refer to the "Example Usage" section at the top
|
|
||||||
// of this document.
|
|
||||||
type Mock struct {
|
|
||||||
// Represents the calls that are expected of
|
|
||||||
// an object.
|
|
||||||
ExpectedCalls []*Call
|
|
||||||
|
|
||||||
// Holds the calls that were made to this mocked object.
|
|
||||||
Calls []Call
|
|
||||||
|
|
||||||
// test is An optional variable that holds the test struct, to be used when an
|
|
||||||
// invalid mock call was made.
|
|
||||||
test TestingT
|
|
||||||
|
|
||||||
// TestData holds any data that might be useful for testing. Testify ignores
|
|
||||||
// this data completely allowing you to do whatever you like with it.
|
|
||||||
testData objx.Map
|
|
||||||
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestData holds any data that might be useful for testing. Testify ignores
|
|
||||||
// this data completely allowing you to do whatever you like with it.
|
|
||||||
func (m *Mock) TestData() objx.Map {
|
|
||||||
|
|
||||||
if m.testData == nil {
|
|
||||||
m.testData = make(objx.Map)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.testData
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Setting expectations
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Test sets the test struct variable of the mock object
|
|
||||||
func (m *Mock) Test(t TestingT) {
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
m.test = t
|
|
||||||
}
|
|
||||||
|
|
||||||
// fail fails the current test with the given formatted format and args.
|
|
||||||
// In case that a test was defined, it uses the test APIs for failing a test,
|
|
||||||
// otherwise it uses panic.
|
|
||||||
func (m *Mock) fail(format string, args ...interface{}) {
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
|
|
||||||
if m.test == nil {
|
|
||||||
panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
m.test.Errorf(format, args...)
|
|
||||||
m.test.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// On starts a description of an expectation of the specified method
|
|
||||||
// being called.
|
|
||||||
//
|
|
||||||
// Mock.On("MyMethod", arg1, arg2)
|
|
||||||
func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
|
|
||||||
for _, arg := range arguments {
|
|
||||||
if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
|
|
||||||
panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
c := newCall(m, methodName, assert.CallerInfo(), arguments...)
|
|
||||||
m.ExpectedCalls = append(m.ExpectedCalls, c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// /*
|
|
||||||
// Recording and responding to activity
|
|
||||||
// */
|
|
||||||
|
|
||||||
func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) {
|
|
||||||
var expectedCall *Call
|
|
||||||
|
|
||||||
for i, call := range m.ExpectedCalls {
|
|
||||||
if call.Method == method {
|
|
||||||
_, diffCount := call.Arguments.Diff(arguments)
|
|
||||||
if diffCount == 0 {
|
|
||||||
expectedCall = call
|
|
||||||
if call.Repeatability > -1 {
|
|
||||||
return i, call
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, expectedCall
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) {
|
|
||||||
var diffCount int
|
|
||||||
var closestCall *Call
|
|
||||||
var err string
|
|
||||||
|
|
||||||
for _, call := range m.expectedCalls() {
|
|
||||||
if call.Method == method {
|
|
||||||
|
|
||||||
errInfo, tempDiffCount := call.Arguments.Diff(arguments)
|
|
||||||
if tempDiffCount < diffCount || diffCount == 0 {
|
|
||||||
diffCount = tempDiffCount
|
|
||||||
closestCall = call
|
|
||||||
err = errInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return closestCall, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func callString(method string, arguments Arguments, includeArgumentValues bool) string {
|
|
||||||
|
|
||||||
var argValsString string
|
|
||||||
if includeArgumentValues {
|
|
||||||
var argVals []string
|
|
||||||
for argIndex, arg := range arguments {
|
|
||||||
argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg))
|
|
||||||
}
|
|
||||||
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called tells the mock object that a method has been called, and gets an array
|
|
||||||
// of arguments to return. Panics if the call is unexpected (i.e. not preceded by
|
|
||||||
// appropriate .On .Return() calls)
|
|
||||||
// If Call.WaitFor is set, blocks until the channel is closed or receives a message.
|
|
||||||
func (m *Mock) Called(arguments ...interface{}) Arguments {
|
|
||||||
// get the calling function's name
|
|
||||||
pc, _, _, ok := runtime.Caller(1)
|
|
||||||
if !ok {
|
|
||||||
panic("Couldn't get the caller information")
|
|
||||||
}
|
|
||||||
functionPath := runtime.FuncForPC(pc).Name()
|
|
||||||
//Next four lines are required to use GCCGO function naming conventions.
|
|
||||||
//For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock
|
|
||||||
//uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree
|
|
||||||
//With GCCGO we need to remove interface information starting from pN<dd>.
|
|
||||||
re := regexp.MustCompile("\\.pN\\d+_")
|
|
||||||
if re.MatchString(functionPath) {
|
|
||||||
functionPath = re.Split(functionPath, -1)[0]
|
|
||||||
}
|
|
||||||
parts := strings.Split(functionPath, ".")
|
|
||||||
functionName := parts[len(parts)-1]
|
|
||||||
return m.MethodCalled(functionName, arguments...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MethodCalled tells the mock object that the given method has been called, and gets
|
|
||||||
// an array of arguments to return. Panics if the call is unexpected (i.e. not preceded
|
|
||||||
// by appropriate .On .Return() calls)
|
|
||||||
// If Call.WaitFor is set, blocks until the channel is closed or receives a message.
|
|
||||||
func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments {
|
|
||||||
m.mutex.Lock()
|
|
||||||
//TODO: could combine expected and closes in single loop
|
|
||||||
found, call := m.findExpectedCall(methodName, arguments...)
|
|
||||||
|
|
||||||
if found < 0 {
|
|
||||||
// expected call found but it has already been called with repeatable times
|
|
||||||
if call != nil {
|
|
||||||
m.mutex.Unlock()
|
|
||||||
m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo())
|
|
||||||
}
|
|
||||||
// we have to fail here - because we don't know what to do
|
|
||||||
// as the return arguments. This is because:
|
|
||||||
//
|
|
||||||
// a) this is a totally unexpected call to this method,
|
|
||||||
// b) the arguments are not what was expected, or
|
|
||||||
// c) the developer has forgotten to add an accompanying On...Return pair.
|
|
||||||
closestCall, mismatch := m.findClosestCall(methodName, arguments...)
|
|
||||||
m.mutex.Unlock()
|
|
||||||
|
|
||||||
if closestCall != nil {
|
|
||||||
m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s",
|
|
||||||
callString(methodName, arguments, true),
|
|
||||||
callString(methodName, closestCall.Arguments, true),
|
|
||||||
diffArguments(closestCall.Arguments, arguments),
|
|
||||||
strings.TrimSpace(mismatch),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if call.Repeatability == 1 {
|
|
||||||
call.Repeatability = -1
|
|
||||||
} else if call.Repeatability > 1 {
|
|
||||||
call.Repeatability--
|
|
||||||
}
|
|
||||||
call.totalCalls++
|
|
||||||
|
|
||||||
// add the call
|
|
||||||
m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...))
|
|
||||||
m.mutex.Unlock()
|
|
||||||
|
|
||||||
// block if specified
|
|
||||||
if call.WaitFor != nil {
|
|
||||||
<-call.WaitFor
|
|
||||||
} else {
|
|
||||||
time.Sleep(call.waitTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mutex.Lock()
|
|
||||||
runFn := call.RunFn
|
|
||||||
m.mutex.Unlock()
|
|
||||||
|
|
||||||
if runFn != nil {
|
|
||||||
runFn(arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mutex.Lock()
|
|
||||||
returnArgs := call.ReturnArguments
|
|
||||||
m.mutex.Unlock()
|
|
||||||
|
|
||||||
return returnArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Assertions
|
|
||||||
*/
|
|
||||||
|
|
||||||
type assertExpectationser interface {
|
|
||||||
AssertExpectations(TestingT) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssertExpectationsForObjects asserts that everything specified with On and Return
|
|
||||||
// of the specified objects was in fact called as expected.
|
|
||||||
//
|
|
||||||
// Calls may have occurred in any order.
|
|
||||||
func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
for _, obj := range testObjects {
|
|
||||||
if m, ok := obj.(Mock); ok {
|
|
||||||
t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)")
|
|
||||||
obj = &m
|
|
||||||
}
|
|
||||||
m := obj.(assertExpectationser)
|
|
||||||
if !m.AssertExpectations(t) {
|
|
||||||
t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssertExpectations asserts that everything specified with On and Return was
|
|
||||||
// in fact called as expected. Calls may have occurred in any order.
|
|
||||||
func (m *Mock) AssertExpectations(t TestingT) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
var somethingMissing bool
|
|
||||||
var failedExpectations int
|
|
||||||
|
|
||||||
// iterate through each expectation
|
|
||||||
expectedCalls := m.expectedCalls()
|
|
||||||
for _, expectedCall := range expectedCalls {
|
|
||||||
if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 {
|
|
||||||
somethingMissing = true
|
|
||||||
failedExpectations++
|
|
||||||
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
|
|
||||||
} else {
|
|
||||||
if expectedCall.Repeatability > 0 {
|
|
||||||
somethingMissing = true
|
|
||||||
failedExpectations++
|
|
||||||
t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo)
|
|
||||||
} else {
|
|
||||||
t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if somethingMissing {
|
|
||||||
t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo())
|
|
||||||
}
|
|
||||||
|
|
||||||
return !somethingMissing
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssertNumberOfCalls asserts that the method was called expectedCalls times.
|
|
||||||
func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
var actualCalls int
|
|
||||||
for _, call := range m.calls() {
|
|
||||||
if call.Method == methodName {
|
|
||||||
actualCalls++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssertCalled asserts that the method was called.
|
|
||||||
// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method.
|
|
||||||
func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
if !m.methodWasCalled(methodName, arguments) {
|
|
||||||
var calledWithArgs []string
|
|
||||||
for _, call := range m.calls() {
|
|
||||||
calledWithArgs = append(calledWithArgs, fmt.Sprintf("%v", call.Arguments))
|
|
||||||
}
|
|
||||||
if len(calledWithArgs) == 0 {
|
|
||||||
return assert.Fail(t, "Should have called with given arguments",
|
|
||||||
fmt.Sprintf("Expected %q to have been called with:\n%v\nbut no actual calls happened", methodName, arguments))
|
|
||||||
}
|
|
||||||
return assert.Fail(t, "Should have called with given arguments",
|
|
||||||
fmt.Sprintf("Expected %q to have been called with:\n%v\nbut actual calls were:\n %v", methodName, arguments, strings.Join(calledWithArgs, "\n")))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssertNotCalled asserts that the method was not called.
|
|
||||||
// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method.
|
|
||||||
func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
m.mutex.Lock()
|
|
||||||
defer m.mutex.Unlock()
|
|
||||||
if m.methodWasCalled(methodName, arguments) {
|
|
||||||
return assert.Fail(t, "Should not have called with given arguments",
|
|
||||||
fmt.Sprintf("Expected %q to not have been called with:\n%v\nbut actually it was.", methodName, arguments))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
|
|
||||||
for _, call := range m.calls() {
|
|
||||||
if call.Method == methodName {
|
|
||||||
|
|
||||||
_, differences := Arguments(expected).Diff(call.Arguments)
|
|
||||||
|
|
||||||
if differences == 0 {
|
|
||||||
// found the expected call
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// we didn't find the expected call
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) expectedCalls() []*Call {
|
|
||||||
return append([]*Call{}, m.ExpectedCalls...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mock) calls() []Call {
|
|
||||||
return append([]Call{}, m.Calls...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Arguments
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Arguments holds an array of method arguments or return values.
|
|
||||||
type Arguments []interface{}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Anything is used in Diff and Assert when the argument being tested
|
|
||||||
// shouldn't be taken into consideration.
|
|
||||||
Anything = "mock.Anything"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AnythingOfTypeArgument is a string that contains the type of an argument
|
|
||||||
// for use when type checking. Used in Diff and Assert.
|
|
||||||
type AnythingOfTypeArgument string
|
|
||||||
|
|
||||||
// AnythingOfType returns an AnythingOfTypeArgument object containing the
|
|
||||||
// name of the type to check for. Used in Diff and Assert.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
// Assert(t, AnythingOfType("string"), AnythingOfType("int"))
|
|
||||||
func AnythingOfType(t string) AnythingOfTypeArgument {
|
|
||||||
return AnythingOfTypeArgument(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTypeArgument is a struct that contains the type of an argument
|
|
||||||
// for use when type checking. This is an alternative to AnythingOfType.
|
|
||||||
// Used in Diff and Assert.
|
|
||||||
type IsTypeArgument struct {
|
|
||||||
t interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsType returns an IsTypeArgument object containing the type to check for.
|
|
||||||
// You can provide a zero-value of the type to check. This is an
|
|
||||||
// alternative to AnythingOfType. Used in Diff and Assert.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
// Assert(t, IsType(""), IsType(0))
|
|
||||||
func IsType(t interface{}) *IsTypeArgument {
|
|
||||||
return &IsTypeArgument{t: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
// argumentMatcher performs custom argument matching, returning whether or
|
|
||||||
// not the argument is matched by the expectation fixture function.
|
|
||||||
type argumentMatcher struct {
|
|
||||||
// fn is a function which accepts one argument, and returns a bool.
|
|
||||||
fn reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f argumentMatcher) Matches(argument interface{}) bool {
|
|
||||||
expectType := f.fn.Type().In(0)
|
|
||||||
expectTypeNilSupported := false
|
|
||||||
switch expectType.Kind() {
|
|
||||||
case reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Ptr:
|
|
||||||
expectTypeNilSupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
argType := reflect.TypeOf(argument)
|
|
||||||
var arg reflect.Value
|
|
||||||
if argType == nil {
|
|
||||||
arg = reflect.New(expectType).Elem()
|
|
||||||
} else {
|
|
||||||
arg = reflect.ValueOf(argument)
|
|
||||||
}
|
|
||||||
|
|
||||||
if argType == nil && !expectTypeNilSupported {
|
|
||||||
panic(errors.New("attempting to call matcher with nil for non-nil expected type"))
|
|
||||||
}
|
|
||||||
if argType == nil || argType.AssignableTo(expectType) {
|
|
||||||
result := f.fn.Call([]reflect.Value{arg})
|
|
||||||
return result[0].Bool()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f argumentMatcher) String() string {
|
|
||||||
return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchedBy can be used to match a mock call based on only certain properties
|
|
||||||
// from a complex struct or some calculation. It takes a function that will be
|
|
||||||
// evaluated with the called argument and will return true when there's a match
|
|
||||||
// and false otherwise.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" }))
|
|
||||||
//
|
|
||||||
// |fn|, must be a function accepting a single argument (of the expected type)
|
|
||||||
// which returns a bool. If |fn| doesn't match the required signature,
|
|
||||||
// MatchedBy() panics.
|
|
||||||
func MatchedBy(fn interface{}) argumentMatcher {
|
|
||||||
fnType := reflect.TypeOf(fn)
|
|
||||||
|
|
||||||
if fnType.Kind() != reflect.Func {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: %s is not a func", fn))
|
|
||||||
}
|
|
||||||
if fnType.NumIn() != 1 {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn))
|
|
||||||
}
|
|
||||||
if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn))
|
|
||||||
}
|
|
||||||
|
|
||||||
return argumentMatcher{fn: reflect.ValueOf(fn)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get Returns the argument at the specified index.
|
|
||||||
func (args Arguments) Get(index int) interface{} {
|
|
||||||
if index+1 > len(args) {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args)))
|
|
||||||
}
|
|
||||||
return args[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is gets whether the objects match the arguments specified.
|
|
||||||
func (args Arguments) Is(objects ...interface{}) bool {
|
|
||||||
for i, obj := range args {
|
|
||||||
if obj != objects[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff gets a string describing the differences between the arguments
|
|
||||||
// and the specified objects.
|
|
||||||
//
|
|
||||||
// Returns the diff string and number of differences found.
|
|
||||||
func (args Arguments) Diff(objects []interface{}) (string, int) {
|
|
||||||
//TODO: could return string as error and nil for No difference
|
|
||||||
|
|
||||||
var output = "\n"
|
|
||||||
var differences int
|
|
||||||
|
|
||||||
var maxArgCount = len(args)
|
|
||||||
if len(objects) > maxArgCount {
|
|
||||||
maxArgCount = len(objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < maxArgCount; i++ {
|
|
||||||
var actual, expected interface{}
|
|
||||||
var actualFmt, expectedFmt string
|
|
||||||
|
|
||||||
if len(objects) <= i {
|
|
||||||
actual = "(Missing)"
|
|
||||||
actualFmt = "(Missing)"
|
|
||||||
} else {
|
|
||||||
actual = objects[i]
|
|
||||||
actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) <= i {
|
|
||||||
expected = "(Missing)"
|
|
||||||
expectedFmt = "(Missing)"
|
|
||||||
} else {
|
|
||||||
expected = args[i]
|
|
||||||
expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
if matcher, ok := expected.(argumentMatcher); ok {
|
|
||||||
if matcher.Matches(actual) {
|
|
||||||
output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher)
|
|
||||||
} else {
|
|
||||||
differences++
|
|
||||||
output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher)
|
|
||||||
}
|
|
||||||
} else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() {
|
|
||||||
|
|
||||||
// type checking
|
|
||||||
if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) {
|
|
||||||
// not match
|
|
||||||
differences++
|
|
||||||
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if reflect.TypeOf(expected) == reflect.TypeOf((*IsTypeArgument)(nil)) {
|
|
||||||
t := expected.(*IsTypeArgument).t
|
|
||||||
if reflect.TypeOf(t) != reflect.TypeOf(actual) {
|
|
||||||
differences++
|
|
||||||
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// normal checking
|
|
||||||
|
|
||||||
if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
|
|
||||||
// match
|
|
||||||
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt)
|
|
||||||
} else {
|
|
||||||
// not match
|
|
||||||
differences++
|
|
||||||
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if differences == 0 {
|
|
||||||
return "No differences.", differences
|
|
||||||
}
|
|
||||||
|
|
||||||
return output, differences
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert compares the arguments with the specified objects and fails if
|
|
||||||
// they do not exactly match.
|
|
||||||
func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
|
|
||||||
if h, ok := t.(tHelper); ok {
|
|
||||||
h.Helper()
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the differences
|
|
||||||
diff, diffCount := args.Diff(objects)
|
|
||||||
|
|
||||||
if diffCount == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// there are differences... report them...
|
|
||||||
t.Logf(diff)
|
|
||||||
t.Errorf("%sArguments do not match.", assert.CallerInfo())
|
|
||||||
|
|
||||||
return false
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// String gets the argument at the specified index. Panics if there is no argument, or
|
|
||||||
// if the argument is of the wrong type.
|
|
||||||
//
|
|
||||||
// If no index is provided, String() returns a complete string representation
|
|
||||||
// of the arguments.
|
|
||||||
func (args Arguments) String(indexOrNil ...int) string {
|
|
||||||
|
|
||||||
if len(indexOrNil) == 0 {
|
|
||||||
// normal String() method - return a string representation of the args
|
|
||||||
var argsStr []string
|
|
||||||
for _, arg := range args {
|
|
||||||
argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg)))
|
|
||||||
}
|
|
||||||
return strings.Join(argsStr, ",")
|
|
||||||
} else if len(indexOrNil) == 1 {
|
|
||||||
// Index has been specified - get the argument at that index
|
|
||||||
var index = indexOrNil[0]
|
|
||||||
var s string
|
|
||||||
var ok bool
|
|
||||||
if s, ok = args.Get(index).(string); !ok {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index)))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil)))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int gets the argument at the specified index. Panics if there is no argument, or
|
|
||||||
// if the argument is of the wrong type.
|
|
||||||
func (args Arguments) Int(index int) int {
|
|
||||||
var s int
|
|
||||||
var ok bool
|
|
||||||
if s, ok = args.Get(index).(int); !ok {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error gets the argument at the specified index. Panics if there is no argument, or
|
|
||||||
// if the argument is of the wrong type.
|
|
||||||
func (args Arguments) Error(index int) error {
|
|
||||||
obj := args.Get(index)
|
|
||||||
var s error
|
|
||||||
var ok bool
|
|
||||||
if obj == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if s, ok = obj.(error); !ok {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool gets the argument at the specified index. Panics if there is no argument, or
|
|
||||||
// if the argument is of the wrong type.
|
|
||||||
func (args Arguments) Bool(index int) bool {
|
|
||||||
var s bool
|
|
||||||
var ok bool
|
|
||||||
if s, ok = args.Get(index).(bool); !ok {
|
|
||||||
panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index)))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
|
|
||||||
t := reflect.TypeOf(v)
|
|
||||||
k := t.Kind()
|
|
||||||
|
|
||||||
if k == reflect.Ptr {
|
|
||||||
t = t.Elem()
|
|
||||||
k = t.Kind()
|
|
||||||
}
|
|
||||||
return t, k
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffArguments(expected Arguments, actual Arguments) string {
|
|
||||||
if len(expected) != len(actual) {
|
|
||||||
return fmt.Sprintf("Provided %v arguments, mocked for %v arguments", len(expected), len(actual))
|
|
||||||
}
|
|
||||||
|
|
||||||
for x := range expected {
|
|
||||||
if diffString := diff(expected[x], actual[x]); diffString != "" {
|
|
||||||
return fmt.Sprintf("Difference found in argument %v:\n\n%s", x, diffString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// diff returns a diff of both values as long as both are of the same type and
|
|
||||||
// are a struct, map, slice or array. Otherwise it returns an empty string.
|
|
||||||
func diff(expected interface{}, actual interface{}) string {
|
|
||||||
if expected == nil || actual == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
et, ek := typeAndKind(expected)
|
|
||||||
at, _ := typeAndKind(actual)
|
|
||||||
|
|
||||||
if et != at {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
e := spewConfig.Sdump(expected)
|
|
||||||
a := spewConfig.Sdump(actual)
|
|
||||||
|
|
||||||
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
|
||||||
A: difflib.SplitLines(e),
|
|
||||||
B: difflib.SplitLines(a),
|
|
||||||
FromFile: "Expected",
|
|
||||||
FromDate: "",
|
|
||||||
ToFile: "Actual",
|
|
||||||
ToDate: "",
|
|
||||||
Context: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
return diff
|
|
||||||
}
|
|
||||||
|
|
||||||
var spewConfig = spew.ConfigState{
|
|
||||||
Indent: " ",
|
|
||||||
DisablePointerAddresses: true,
|
|
||||||
DisableCapacities: true,
|
|
||||||
SortKeys: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
type tHelper interface {
|
|
||||||
Helper()
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# This source code refers to The Go Authors for copyright purposes.
|
|
||||||
# The master list of authors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/AUTHORS.
|
|
|
@ -1,3 +0,0 @@
|
||||||
# This source code was written by the Go contributors.
|
|
||||||
# The master list of contributors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,22 +0,0 @@
|
||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
|
@ -1,17 +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.9
|
|
||||||
|
|
||||||
package syncmap
|
|
||||||
|
|
||||||
import "sync" // home to the standard library's sync.map implementation as of Go 1.9
|
|
||||||
|
|
||||||
// Map is a concurrent map with amortized-constant-time loads, stores, and deletes.
|
|
||||||
// It is safe for multiple goroutines to call a Map's methods concurrently.
|
|
||||||
//
|
|
||||||
// The zero Map is valid and empty.
|
|
||||||
//
|
|
||||||
// A Map must not be copied after first use.
|
|
||||||
type Map = sync.Map
|
|
|
@ -1,8 +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 syncmap provides a concurrent map implementation.
|
|
||||||
// This was the prototype for sync.Map which was added to the standard library's
|
|
||||||
// sync package in Go 1.9. https://golang.org/pkg/sync/#Map.
|
|
||||||
package syncmap
|
|
|
@ -1,370 +0,0 @@
|
||||||
// Copyright 2016 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.9
|
|
||||||
|
|
||||||
package syncmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Map is a concurrent map with amortized-constant-time loads, stores, and deletes.
|
|
||||||
// It is safe for multiple goroutines to call a Map's methods concurrently.
|
|
||||||
//
|
|
||||||
// The zero Map is valid and empty.
|
|
||||||
//
|
|
||||||
// A Map must not be copied after first use.
|
|
||||||
type Map struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
|
|
||||||
// read contains the portion of the map's contents that are safe for
|
|
||||||
// concurrent access (with or without mu held).
|
|
||||||
//
|
|
||||||
// The read field itself is always safe to load, but must only be stored with
|
|
||||||
// mu held.
|
|
||||||
//
|
|
||||||
// Entries stored in read may be updated concurrently without mu, but updating
|
|
||||||
// a previously-expunged entry requires that the entry be copied to the dirty
|
|
||||||
// map and unexpunged with mu held.
|
|
||||||
read atomic.Value // readOnly
|
|
||||||
|
|
||||||
// dirty contains the portion of the map's contents that require mu to be
|
|
||||||
// held. To ensure that the dirty map can be promoted to the read map quickly,
|
|
||||||
// it also includes all of the non-expunged entries in the read map.
|
|
||||||
//
|
|
||||||
// Expunged entries are not stored in the dirty map. An expunged entry in the
|
|
||||||
// clean map must be unexpunged and added to the dirty map before a new value
|
|
||||||
// can be stored to it.
|
|
||||||
//
|
|
||||||
// If the dirty map is nil, the next write to the map will initialize it by
|
|
||||||
// making a shallow copy of the clean map, omitting stale entries.
|
|
||||||
dirty map[interface{}]*entry
|
|
||||||
|
|
||||||
// misses counts the number of loads since the read map was last updated that
|
|
||||||
// needed to lock mu to determine whether the key was present.
|
|
||||||
//
|
|
||||||
// Once enough misses have occurred to cover the cost of copying the dirty
|
|
||||||
// map, the dirty map will be promoted to the read map (in the unamended
|
|
||||||
// state) and the next store to the map will make a new dirty copy.
|
|
||||||
misses int
|
|
||||||
}
|
|
||||||
|
|
||||||
// readOnly is an immutable struct stored atomically in the Map.read field.
|
|
||||||
type readOnly struct {
|
|
||||||
m map[interface{}]*entry
|
|
||||||
amended bool // true if the dirty map contains some key not in m.
|
|
||||||
}
|
|
||||||
|
|
||||||
// expunged is an arbitrary pointer that marks entries which have been deleted
|
|
||||||
// from the dirty map.
|
|
||||||
var expunged = unsafe.Pointer(new(interface{}))
|
|
||||||
|
|
||||||
// An entry is a slot in the map corresponding to a particular key.
|
|
||||||
type entry struct {
|
|
||||||
// p points to the interface{} value stored for the entry.
|
|
||||||
//
|
|
||||||
// If p == nil, the entry has been deleted and m.dirty == nil.
|
|
||||||
//
|
|
||||||
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
|
|
||||||
// is missing from m.dirty.
|
|
||||||
//
|
|
||||||
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
|
|
||||||
// != nil, in m.dirty[key].
|
|
||||||
//
|
|
||||||
// An entry can be deleted by atomic replacement with nil: when m.dirty is
|
|
||||||
// next created, it will atomically replace nil with expunged and leave
|
|
||||||
// m.dirty[key] unset.
|
|
||||||
//
|
|
||||||
// An entry's associated value can be updated by atomic replacement, provided
|
|
||||||
// p != expunged. If p == expunged, an entry's associated value can be updated
|
|
||||||
// only after first setting m.dirty[key] = e so that lookups using the dirty
|
|
||||||
// map find the entry.
|
|
||||||
p unsafe.Pointer // *interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEntry(i interface{}) *entry {
|
|
||||||
return &entry{p: unsafe.Pointer(&i)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load returns the value stored in the map for a key, or nil if no
|
|
||||||
// value is present.
|
|
||||||
// The ok result indicates whether value was found in the map.
|
|
||||||
func (m *Map) Load(key interface{}) (value interface{}, ok bool) {
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
e, ok := read.m[key]
|
|
||||||
if !ok && read.amended {
|
|
||||||
m.mu.Lock()
|
|
||||||
// Avoid reporting a spurious miss if m.dirty got promoted while we were
|
|
||||||
// blocked on m.mu. (If further loads of the same key will not miss, it's
|
|
||||||
// not worth copying the dirty map for this key.)
|
|
||||||
read, _ = m.read.Load().(readOnly)
|
|
||||||
e, ok = read.m[key]
|
|
||||||
if !ok && read.amended {
|
|
||||||
e, ok = m.dirty[key]
|
|
||||||
// Regardless of whether the entry was present, record a miss: this key
|
|
||||||
// will take the slow path until the dirty map is promoted to the read
|
|
||||||
// map.
|
|
||||||
m.missLocked()
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return e.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) load() (value interface{}, ok bool) {
|
|
||||||
p := atomic.LoadPointer(&e.p)
|
|
||||||
if p == nil || p == expunged {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return *(*interface{})(p), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store sets the value for a key.
|
|
||||||
func (m *Map) Store(key, value interface{}) {
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
if e, ok := read.m[key]; ok && e.tryStore(&value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
read, _ = m.read.Load().(readOnly)
|
|
||||||
if e, ok := read.m[key]; ok {
|
|
||||||
if e.unexpungeLocked() {
|
|
||||||
// The entry was previously expunged, which implies that there is a
|
|
||||||
// non-nil dirty map and this entry is not in it.
|
|
||||||
m.dirty[key] = e
|
|
||||||
}
|
|
||||||
e.storeLocked(&value)
|
|
||||||
} else if e, ok := m.dirty[key]; ok {
|
|
||||||
e.storeLocked(&value)
|
|
||||||
} else {
|
|
||||||
if !read.amended {
|
|
||||||
// We're adding the first new key to the dirty map.
|
|
||||||
// Make sure it is allocated and mark the read-only map as incomplete.
|
|
||||||
m.dirtyLocked()
|
|
||||||
m.read.Store(readOnly{m: read.m, amended: true})
|
|
||||||
}
|
|
||||||
m.dirty[key] = newEntry(value)
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryStore stores a value if the entry has not been expunged.
|
|
||||||
//
|
|
||||||
// If the entry is expunged, tryStore returns false and leaves the entry
|
|
||||||
// unchanged.
|
|
||||||
func (e *entry) tryStore(i *interface{}) bool {
|
|
||||||
p := atomic.LoadPointer(&e.p)
|
|
||||||
if p == expunged {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
p = atomic.LoadPointer(&e.p)
|
|
||||||
if p == expunged {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unexpungeLocked ensures that the entry is not marked as expunged.
|
|
||||||
//
|
|
||||||
// If the entry was previously expunged, it must be added to the dirty map
|
|
||||||
// before m.mu is unlocked.
|
|
||||||
func (e *entry) unexpungeLocked() (wasExpunged bool) {
|
|
||||||
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// storeLocked unconditionally stores a value to the entry.
|
|
||||||
//
|
|
||||||
// The entry must be known not to be expunged.
|
|
||||||
func (e *entry) storeLocked(i *interface{}) {
|
|
||||||
atomic.StorePointer(&e.p, unsafe.Pointer(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrStore returns the existing value for the key if present.
|
|
||||||
// Otherwise, it stores and returns the given value.
|
|
||||||
// The loaded result is true if the value was loaded, false if stored.
|
|
||||||
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) {
|
|
||||||
// Avoid locking if it's a clean hit.
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
if e, ok := read.m[key]; ok {
|
|
||||||
actual, loaded, ok := e.tryLoadOrStore(value)
|
|
||||||
if ok {
|
|
||||||
return actual, loaded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
read, _ = m.read.Load().(readOnly)
|
|
||||||
if e, ok := read.m[key]; ok {
|
|
||||||
if e.unexpungeLocked() {
|
|
||||||
m.dirty[key] = e
|
|
||||||
}
|
|
||||||
actual, loaded, _ = e.tryLoadOrStore(value)
|
|
||||||
} else if e, ok := m.dirty[key]; ok {
|
|
||||||
actual, loaded, _ = e.tryLoadOrStore(value)
|
|
||||||
m.missLocked()
|
|
||||||
} else {
|
|
||||||
if !read.amended {
|
|
||||||
// We're adding the first new key to the dirty map.
|
|
||||||
// Make sure it is allocated and mark the read-only map as incomplete.
|
|
||||||
m.dirtyLocked()
|
|
||||||
m.read.Store(readOnly{m: read.m, amended: true})
|
|
||||||
}
|
|
||||||
m.dirty[key] = newEntry(value)
|
|
||||||
actual, loaded = value, false
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
|
|
||||||
return actual, loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryLoadOrStore atomically loads or stores a value if the entry is not
|
|
||||||
// expunged.
|
|
||||||
//
|
|
||||||
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
|
|
||||||
// returns with ok==false.
|
|
||||||
func (e *entry) tryLoadOrStore(i interface{}) (actual interface{}, loaded, ok bool) {
|
|
||||||
p := atomic.LoadPointer(&e.p)
|
|
||||||
if p == expunged {
|
|
||||||
return nil, false, false
|
|
||||||
}
|
|
||||||
if p != nil {
|
|
||||||
return *(*interface{})(p), true, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the interface after the first load to make this method more amenable
|
|
||||||
// to escape analysis: if we hit the "load" path or the entry is expunged, we
|
|
||||||
// shouldn't bother heap-allocating.
|
|
||||||
ic := i
|
|
||||||
for {
|
|
||||||
if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
|
|
||||||
return i, false, true
|
|
||||||
}
|
|
||||||
p = atomic.LoadPointer(&e.p)
|
|
||||||
if p == expunged {
|
|
||||||
return nil, false, false
|
|
||||||
}
|
|
||||||
if p != nil {
|
|
||||||
return *(*interface{})(p), true, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the value for a key.
|
|
||||||
func (m *Map) Delete(key interface{}) {
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
e, ok := read.m[key]
|
|
||||||
if !ok && read.amended {
|
|
||||||
m.mu.Lock()
|
|
||||||
read, _ = m.read.Load().(readOnly)
|
|
||||||
e, ok = read.m[key]
|
|
||||||
if !ok && read.amended {
|
|
||||||
delete(m.dirty, key)
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
e.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) delete() (hadValue bool) {
|
|
||||||
for {
|
|
||||||
p := atomic.LoadPointer(&e.p)
|
|
||||||
if p == nil || p == expunged {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range calls f sequentially for each key and value present in the map.
|
|
||||||
// If f returns false, range stops the iteration.
|
|
||||||
//
|
|
||||||
// Range does not necessarily correspond to any consistent snapshot of the Map's
|
|
||||||
// contents: no key will be visited more than once, but if the value for any key
|
|
||||||
// is stored or deleted concurrently, Range may reflect any mapping for that key
|
|
||||||
// from any point during the Range call.
|
|
||||||
//
|
|
||||||
// Range may be O(N) with the number of elements in the map even if f returns
|
|
||||||
// false after a constant number of calls.
|
|
||||||
func (m *Map) Range(f func(key, value interface{}) bool) {
|
|
||||||
// We need to be able to iterate over all of the keys that were already
|
|
||||||
// present at the start of the call to Range.
|
|
||||||
// If read.amended is false, then read.m satisfies that property without
|
|
||||||
// requiring us to hold m.mu for a long time.
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
if read.amended {
|
|
||||||
// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
|
|
||||||
// (assuming the caller does not break out early), so a call to Range
|
|
||||||
// amortizes an entire copy of the map: we can promote the dirty copy
|
|
||||||
// immediately!
|
|
||||||
m.mu.Lock()
|
|
||||||
read, _ = m.read.Load().(readOnly)
|
|
||||||
if read.amended {
|
|
||||||
read = readOnly{m: m.dirty}
|
|
||||||
m.read.Store(read)
|
|
||||||
m.dirty = nil
|
|
||||||
m.misses = 0
|
|
||||||
}
|
|
||||||
m.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, e := range read.m {
|
|
||||||
v, ok := e.load()
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !f(k, v) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Map) missLocked() {
|
|
||||||
m.misses++
|
|
||||||
if m.misses < len(m.dirty) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.read.Store(readOnly{m: m.dirty})
|
|
||||||
m.dirty = nil
|
|
||||||
m.misses = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Map) dirtyLocked() {
|
|
||||||
if m.dirty != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
read, _ := m.read.Load().(readOnly)
|
|
||||||
m.dirty = make(map[interface{}]*entry, len(read.m))
|
|
||||||
for k, e := range read.m {
|
|
||||||
if !e.tryExpungeLocked() {
|
|
||||||
m.dirty[k] = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *entry) tryExpungeLocked() (isExpunged bool) {
|
|
||||||
p := atomic.LoadPointer(&e.p)
|
|
||||||
for p == nil {
|
|
||||||
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
p = atomic.LoadPointer(&e.p)
|
|
||||||
}
|
|
||||||
return p == expunged
|
|
||||||
}
|
|
|
@ -302,8 +302,6 @@ github.com/mat/besticon/ico
|
||||||
github.com/mattn/go-colorable
|
github.com/mattn/go-colorable
|
||||||
# github.com/mattn/go-isatty v0.0.10
|
# github.com/mattn/go-isatty v0.0.10
|
||||||
github.com/mattn/go-isatty
|
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.6
|
# github.com/mattn/go-runewidth v0.0.6
|
||||||
github.com/mattn/go-runewidth
|
github.com/mattn/go-runewidth
|
||||||
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||||
|
@ -408,11 +406,8 @@ github.com/status-im/tcp-shaker
|
||||||
github.com/steakknife/bloomfilter
|
github.com/steakknife/bloomfilter
|
||||||
# github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3
|
# github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3
|
||||||
github.com/steakknife/hamming
|
github.com/steakknife/hamming
|
||||||
# github.com/stretchr/objx v0.1.1
|
|
||||||
github.com/stretchr/objx
|
|
||||||
# github.com/stretchr/testify v1.5.1
|
# github.com/stretchr/testify v1.5.1
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
github.com/stretchr/testify/mock
|
|
||||||
github.com/stretchr/testify/require
|
github.com/stretchr/testify/require
|
||||||
github.com/stretchr/testify/suite
|
github.com/stretchr/testify/suite
|
||||||
# github.com/syndtr/goleveldb v1.0.0
|
# github.com/syndtr/goleveldb v1.0.0
|
||||||
|
@ -528,8 +523,6 @@ golang.org/x/net/internal/iana
|
||||||
golang.org/x/net/internal/socket
|
golang.org/x/net/internal/socket
|
||||||
golang.org/x/net/ipv4
|
golang.org/x/net/ipv4
|
||||||
golang.org/x/net/publicsuffix
|
golang.org/x/net/publicsuffix
|
||||||
# golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
|
||||||
golang.org/x/sync/syncmap
|
|
||||||
# golang.org/x/sys v0.0.0-20200122134326-e047566fdf82
|
# golang.org/x/sys v0.0.0-20200122134326-e047566fdf82
|
||||||
golang.org/x/sys/cpu
|
golang.org/x/sys/cpu
|
||||||
golang.org/x/sys/unix
|
golang.org/x/sys/unix
|
||||||
|
|
Loading…
Reference in New Issue