diff --git a/cmd/populate-db/.gitignore b/cmd/populate-db/.gitignore new file mode 100644 index 000000000..647a4d420 --- /dev/null +++ b/cmd/populate-db/.gitignore @@ -0,0 +1,2 @@ +populate-db +tmp/ diff --git a/cmd/populate-db/README.md b/cmd/populate-db/README.md new file mode 100644 index 000000000..11a129cf8 --- /dev/null +++ b/cmd/populate-db/README.md @@ -0,0 +1,40 @@ +### How to build + +You must have go installed. +Then you can run, from `cmd/populate-db` + +``` +go build +``` + +which should create a `populate-db` executable + +### How to run +``` +./populate-db --added-contacts 100 --contacts 200 --public-chats 100 --one-to-one-chats 40 --number-of-messages 2 --seed-phrase "your seed phrase" +``` + + +The parameters are: + +`added-contacts`: contacts you have added +`contacts`: number of "contacts" in the database, these are not added by you +`one-to-one-chats`: the number of one to one chats open +`public-chats`: the number of public chats +`number-of-messages`: the number of messages in each chat +`seed-phrase`: the seed phrase of the account to be created + +The db will be created in the `./tmp` directory + +### How to import the db + +1) Create an account in status-react +2) Login, copy the seed phrase +3) Create a db using this script using the seed phrase +4) Copy the db to the import directory +5) Import the database +6) Login + + +Note that the db is not complete, so the app might not be fully functioning, but it +should be good enough to test performance and probably migrations diff --git a/cmd/populate-db/flags.go b/cmd/populate-db/flags.go new file mode 100644 index 000000000..be519d51c --- /dev/null +++ b/cmd/populate-db/flags.go @@ -0,0 +1,37 @@ +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 +} diff --git a/cmd/populate-db/flags_test.go b/cmd/populate-db/flags_test.go new file mode 100644 index 000000000..4331e1c73 --- /dev/null +++ b/cmd/populate-db/flags_test.go @@ -0,0 +1,96 @@ +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) + } + } +} diff --git a/cmd/populate-db/main.go b/cmd/populate-db/main.go new file mode 100644 index 000000000..efd08f5a0 --- /dev/null +++ b/cmd/populate-db/main.go @@ -0,0 +1,524 @@ +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" + "github.com/status-im/status-go/protocol/protobuf" + 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") + nMessages = flag.Int("number-of-messages", 0, "Number of messages for each chat") + 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{}) + chat.SyncedTo = 0 + chat.SyncedFrom = 0 + + err = wakuext.SaveChat(context.Background(), chat) + if err != nil { + return + } + + var messages []*common.Message + + for i := 0; i < *nMessages; i++ { + messages = append(messages, buildMessage(chat, i)) + + } + + if len(messages) > 0 { + if err := wakuext.SaveMessages(context.Background(), messages); 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{}) + chat.SyncedTo = 0 + chat.SyncedFrom = 0 + err = wakuext.SaveChat(context.Background(), chat) + if err != nil { + return + } + var messages []*common.Message + + for i := 0; i < *nMessages; i++ { + messages = append(messages, buildMessage(chat, i)) + + } + + if len(messages) > 0 { + if err := wakuext.SaveMessages(context.Background(), messages); err != nil { + return + } + } + + } + +} + +func getDefaultDataDir() string { + if home := os.Getenv("HOME"); home != "" { + return filepath.Join(home, ".statusd") + } + return "./statusd-data" +} + +func setupLogging(config *params.NodeConfig) { + if *logLevel != "" { + config.LogLevel = *logLevel + } + + colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd())) + if err := logutils.OverrideRootLogWithConfig(config, colors); err != nil { + stdlog.Fatalf("Error initializing logger: %v", err) + } +} + +var ( + errStatusServiceRequiresIPC = errors.New("to enable the StatusService on IPC, -ipc flag must be set") + errStatusServiceRequiresHTTP = errors.New("to enable the StatusService on HTTP, -http flag must be set") + errStatusServiceInvalidFlag = errors.New("-status flag valid values are: ipc, http") +) + +// printVersion prints verbose output about version and config. +func printVersion(config *params.NodeConfig) { + fmt.Println(strings.Title(config.Name)) + fmt.Println("Version:", config.Version) + fmt.Println("Network ID:", config.NetworkID) + fmt.Println("Go Version:", runtime.Version()) + fmt.Println("OS:", runtime.GOOS) + fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) + fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) + + fmt.Println("Loaded Config: ", config) +} + +func printUsage() { + usage := ` +Usage: statusd [options] +Examples: + statusd # run regular Whisper node that joins Status network + statusd -c ./default.json # run node with configuration specified in ./default.json file + statusd -c ./default.json -c ./standalone.json # run node with configuration specified in ./default.json file, after merging ./standalone.json file + statusd -c ./default.json -metrics # run node with configuration specified in ./default.json file, and expose ethereum metrics with debug_metrics jsonrpc call + +Options: +` + fmt.Fprint(os.Stderr, usage) + flag.PrintDefaults() +} + +const pathWalletRoot = "m/44'/60'/0'/0" +const pathEIP1581 = "m/43'/60'/1581'" +const pathDefaultChat = pathEIP1581 + "/0'/0" +const pathDefaultWallet = pathWalletRoot + "/0" + +var paths = []string{pathWalletRoot, pathEIP1581, pathDefaultChat, pathDefaultWallet} + +func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derivedAddresses map[string]generator.AccountInfo, mnemonic *string) (*accounts.Settings, error) { + chatKeyString := derivedAddresses[pathDefaultChat].PublicKey + + settings := &accounts.Settings{} + settings.KeyUID = generatedAccountInfo.KeyUID + settings.Address = types.HexToAddress(generatedAccountInfo.Address) + settings.WalletRootAddress = types.HexToAddress(derivedAddresses[pathWalletRoot].Address) + + // Set chat key & name + name, err := alias.GenerateFromPublicKeyString(chatKeyString) + if err != nil { + return nil, err + } + settings.Name = name + settings.PublicKey = chatKeyString + + settings.DappsAddress = types.HexToAddress(derivedAddresses[pathDefaultWallet].Address) + settings.EIP1581Address = types.HexToAddress(derivedAddresses[pathEIP1581].Address) + settings.Mnemonic = mnemonic + + signingPhrase, err := buildSigningPhrase() + if err != nil { + return nil, err + } + settings.SigningPhrase = signingPhrase + + settings.SendPushNotifications = true + settings.InstallationID = uuid.New().String() + settings.UseMailservers = true + + settings.PreviewPrivacy = true + settings.Currency = "usd" + settings.ProfilePicturesVisibility = 1 + settings.LinkPreviewRequestEnabled = true + + visibleTokens := make(map[string][]string) + visibleTokens["mainnet"] = []string{"SNT"} + visibleTokensJSON, err := json.Marshal(visibleTokens) + if err != nil { + return nil, err + } + visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON) + settings.WalletVisibleTokens = &visibleTokenJSONRaw + + // TODO: fix this + networks := make([]map[string]string, 0) + networksJSON, err := json.Marshal(networks) + if err != nil { + return nil, err + } + networkRawMessage := json.RawMessage(networksJSON) + settings.Networks = &networkRawMessage + settings.CurrentNetwork = "mainnet_rpc" + + return settings, nil +} + +func defaultNodeConfig(installationID string) (*params.NodeConfig, error) { + // Set mainnet + nodeConfig := ¶ms.NodeConfig{} + nodeConfig.NetworkID = 1 + nodeConfig.LogLevel = "ERROR" + nodeConfig.DataDir = "/ethereum/mainnet_rpc" + nodeConfig.UpstreamConfig = params.UpstreamRPCConfig{ + Enabled: true, + URL: "https://mainnet.infura.io/v3/800c641949d64d768a5070a1b0511938", + } + + nodeConfig.Name = "StatusIM" + nodeConfig.Rendezvous = true + clusterConfig, err := params.LoadClusterConfigFromFleet("eth.prod") + if err != nil { + return nil, err + } + nodeConfig.ClusterConfig = *clusterConfig + + nodeConfig.WalletConfig = params.WalletConfig{Enabled: true} + nodeConfig.LocalNotificationsConfig = params.LocalNotificationsConfig{Enabled: true} + nodeConfig.BrowsersConfig = params.BrowsersConfig{Enabled: true} + nodeConfig.PermissionsConfig = params.PermissionsConfig{Enabled: true} + nodeConfig.MailserversConfig = params.MailserversConfig{Enabled: true} + nodeConfig.EnableNTPSync = true + nodeConfig.WakuConfig = params.WakuConfig{ + Enabled: true, + LightClient: true, + MinimumPoW: 0.000001, + } + + nodeConfig.ShhextConfig = params.ShhextConfig{ + BackupDisabledDataDir: "", + InstallationID: installationID, + MaxMessageDeliveryAttempts: 6, + MailServerConfirmations: true, + VerifyTransactionURL: "", + VerifyENSURL: "", + VerifyENSContractAddress: "", + VerifyTransactionChainID: 1, + DataSyncEnabled: true, + PFSEnabled: true, + } + + // TODO: check topics + + return nodeConfig, nil +} + +func ImportAccount(seedPhrase string, backend *api.GethStatusBackend) error { + backend.UpdateRootDataDir("./tmp") + manager := backend.AccountManager() + manager.InitKeystore("./tmp") + err := backend.OpenAccounts() + if err != nil { + return err + } + generator := manager.AccountsGenerator() + generatedAccountInfo, err := generator.ImportMnemonic(seedPhrase, "") + if err != nil { + return err + } + + derivedAddresses, err := generator.DeriveAddresses(generatedAccountInfo.ID, paths) + if err != nil { + return err + } + + _, err = generator.StoreDerivedAccounts(generatedAccountInfo.ID, "", paths) + if err != nil { + return err + } + + account := multiaccounts.Account{ + KeyUID: generatedAccountInfo.KeyUID, + } + settings, err := defaultSettings(generatedAccountInfo, derivedAddresses, &seedPhrase) + if err != nil { + return err + } + + nodeConfig, err := defaultNodeConfig(settings.InstallationID) + if err != nil { + return err + } + + walletDerivedAccount := derivedAddresses[pathDefaultWallet] + walletAccount := accounts.Account{ + PublicKey: types.Hex2Bytes(walletDerivedAccount.PublicKey), + Address: types.HexToAddress(walletDerivedAccount.Address), + Color: "", + Wallet: true, + Path: pathDefaultWallet, + Name: "Ethereum account", + } + + chatDerivedAccount := derivedAddresses[pathDefaultChat] + chatAccount := accounts.Account{ + PublicKey: types.Hex2Bytes(chatDerivedAccount.PublicKey), + Address: types.HexToAddress(chatDerivedAccount.Address), + Name: settings.Name, + Chat: true, + Path: pathDefaultChat, + } + + accounts := []accounts.Account{walletAccount, chatAccount} + return backend.StartNodeWithAccountAndConfig(account, "", *settings, nodeConfig, accounts) +} + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +func buildMessage(chat *protocol.Chat, count int) *common.Message { + key, err := crypto.GenerateKey() + if err != nil { + logger.Error("failed", err) + return nil + } + + clock, timestamp := chat.NextClockAndTimestamp(&testTimeSource{}) + message := &common.Message{} + message.Text = fmt.Sprintf("test message %d", count) + message.ChatId = chat.ID + message.Clock = clock + message.Timestamp = timestamp + message.From = common.PubkeyToHex(&key.PublicKey) + data := []byte(uuid.New().String()) + message.ID = types.HexBytes(crypto.Keccak256(data)).String() + message.WhisperTimestamp = clock + message.LocalChatID = chat.ID + message.ContentType = protobuf.ChatMessage_TEXT_PLAIN + switch chat.ChatType { + case protocol.ChatTypePublic, protocol.ChatTypeProfile: + message.MessageType = protobuf.MessageType_PUBLIC_GROUP + case protocol.ChatTypeOneToOne: + message.MessageType = protobuf.MessageType_ONE_TO_ONE + case protocol.ChatTypePrivateGroupChat: + message.MessageType = protobuf.MessageType_PRIVATE_GROUP + } + + message.PrepareContent("") + return message +} + +func randomString(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} diff --git a/cmd/populate-db/signing_phrase.go b/cmd/populate-db/signing_phrase.go new file mode 100644 index 000000000..42f45c745 --- /dev/null +++ b/cmd/populate-db/signing_phrase.go @@ -0,0 +1,650 @@ +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", +} diff --git a/cmd/populate-db/sync.go b/cmd/populate-db/sync.go new file mode 100644 index 000000000..18658a3c8 --- /dev/null +++ b/cmd/populate-db/sync.go @@ -0,0 +1,54 @@ +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 +} diff --git a/services/ext/api.go b/services/ext/api.go index 7243df937..6a63546e5 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -302,6 +302,10 @@ func (api *PublicAPI) SaveChat(parent context.Context, chat *protocol.Chat) erro return api.service.messenger.SaveChat(chat) } +func (api *PublicAPI) SaveMessages(parent context.Context, messages []*common.Message) error { + return api.service.messenger.SaveMessages(messages) +} + func (api *PublicAPI) CreateOneToOneChat(parent context.Context, request *requests.CreateOneToOneChat) (*protocol.MessengerResponse, error) { return api.service.messenger.CreateOneToOneChat(request) }