Handle connection state

This commit is contained in:
Andrea Maria Piana 2021-05-14 12:55:42 +02:00
parent 566e9a3ade
commit d50fee6bb2
99 changed files with 585 additions and 9216 deletions

View File

@ -1 +1 @@
0.78.1 0.79.0

View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
populate-db
tmp/

View File

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

View File

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

View File

@ -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 := &params.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)
}

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

@ -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 = &params.WakuConfig{
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
s.config = &params.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, &params.WhisperConfig{ err := server.Init(s.shh, &params.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)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
package pointer

View File

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

View File

@ -1,13 +0,0 @@
engines:
gofmt:
enabled: true
golint:
enabled: true
govet:
enabled: true
exclude_patterns:
- ".github/"
- "vendor/"
- "codegen/"
- "doc.go"

View File

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

View File

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

View File

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

View File

@ -1,8 +0,0 @@
[prune]
unused-packages = true
non-go = true
go-tests = true
[[constraint]]
name = "github.com/stretchr/testify"
version = "~1.2.0"

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = "_"
)

View File

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

View File

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

View File

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

View File

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

View File

@ -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[:])
}

View File

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

View File

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

View File

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

View File

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

3
vendor/golang.org/x/sync/AUTHORS generated vendored
View File

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

View File

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

27
vendor/golang.org/x/sync/LICENSE generated vendored
View File

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

22
vendor/golang.org/x/sync/PATENTS generated vendored
View File

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

View File

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

View File

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

View File

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

7
vendor/modules.txt vendored
View File

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