diff --git a/cmd/ping-community/.gitignore b/cmd/ping-community/.gitignore new file mode 100644 index 000000000..34d318618 --- /dev/null +++ b/cmd/ping-community/.gitignore @@ -0,0 +1,3 @@ +ping-community +tmp/ +run.sh diff --git a/cmd/ping-community/README.md b/cmd/ping-community/README.md new file mode 100644 index 000000000..a9d4ed478 --- /dev/null +++ b/cmd/ping-community/README.md @@ -0,0 +1,23 @@ +## Ping community + +This script publishes a post every 2 seconds on the given community/chat-id, using the seed phrase provided. +### How to build + +You must have go installed. +Then you can run, from `cmd/ping-community` + +``` +go build +``` + +which should create a `ping-community` executable + +### How to run +``` +./ping-community --seed-phrase "your seed phrase" --community-id "community-id" --chat-id "chat-id" +``` + +The parameters are: + +`community-id`: the ID of the community +`chat-id`: the ID of the chat we want to post on diff --git a/cmd/ping-community/flags.go b/cmd/ping-community/flags.go new file mode 100644 index 000000000..be519d51c --- /dev/null +++ b/cmd/ping-community/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/ping-community/main.go b/cmd/ping-community/main.go new file mode 100644 index 000000000..a095c35ed --- /dev/null +++ b/cmd/ping-community/main.go @@ -0,0 +1,455 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + stdlog "log" + "os" + "path/filepath" + "runtime" + + "strings" + "time" + + "github.com/google/uuid" + "golang.org/x/crypto/ssh/terminal" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum/go-ethereum/p2p/enode" + "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/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" +) + +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") + version = flag.Bool("version", false, "Print version and dump configuration") + communityID = flag.String("community-id", "", "The id of the community") + chatID = flag.String("chat-id", "", "The id of the chat") + + dataDir = flag.String("dir", getDefaultDataDir(), "Directory used by node to store data") + 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") +) + +// 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 import account", "err", err) + return + } + + wakuextservice := backend.StatusNode().WakuExtService() + if wakuextservice == nil { + logger.Error("wakuext not available") + 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 + } + + messenger := wakuextservice.Messenger() + + mailserver := "enode://e4865fe6c2a9c1a563a6447990d8e9ce672644ae3e08277ce38ec1f1b690eef6320c07a5d60c3b629f5d4494f93d6b86a745a0bf64ab295bbf6579017adc6ed8@206.189.243.161:443" + + mailserverNode, err := enode.ParseV4(mailserver) + if err != nil { + logger.Error("failed to parse mailserver", "error", err) + return + } + err = backend.StatusNode().WakuExtService().UpdateMailservers([]*enode.Node{mailserverNode}) + if err != nil { + logger.Error("failed to update mailservers", "error", err) + return + + } + messenger.SetMailserver(mailserverNode.ID().Bytes()) + + time.Sleep(5 * time.Second) + community, err := messenger.RequestCommunityInfoFromMailserver(*communityID) + if err != nil { + + logger.Error("community error", "error", err) + return + + } + chat := community.Chats()[*chatID] + if chat == nil { + logger.Warn("Chat not found") + return + } + logger.Info("GOT community", "comm", chat) + + response, err := messenger.JoinCommunity(context.Background(), community.ID()) + if err != nil { + logger.Error("failed to join community", "err", err) + } + + var targetChat *protocol.Chat + + for _, c := range response.Chats() { + if strings.Contains(c.ID, *chatID) { + targetChat = c + } + } + + if targetChat == nil { + logger.Warn("chat not found") + return + } + + id := uuid.New().String() + + ticker := time.NewTicker(2 * time.Second) + count := 0 + + for { // nolint: gosimple + select { + case <-ticker.C: + count++ + timestamp := time.Now().Format(time.RFC3339) + logger.Info("Publishing", "id", id, "count", count, "time", timestamp) + inputMessage := &common.Message{} + + inputMessage.Text = fmt.Sprintf("%d\n%s\n%s", count, timestamp, id) + inputMessage.LocalChatID = targetChat.ID + inputMessage.ChatId = targetChat.ID + inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN + + _, err := messenger.SendChatMessage(context.Background(), inputMessage) + if err != nil { + logger.Error("failed to send a message", "err", err) + } + + } + } + +} + +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 + } + + logSettings := logutils.LogSettings{ + Enabled: config.LogEnabled, + MobileSystem: config.LogMobileSystem, + Level: config.LogLevel, + File: config.LogFile, + MaxSize: config.LogMaxSize, + MaxBackups: config.LogMaxBackups, + CompressRotated: config.LogCompressRotated, + } + colors := !(*logWithoutColors) && terminal.IsTerminal(int(os.Stdin.Fd())) + if err := logutils.OverrideRootLogWithConfig(logSettings, colors); err != nil { + stdlog.Fatalf("Error initializing logger: %v", err) + } +} + +// 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: ping-community [options] +Example: + ping-community --seed-phrase "your seed phrase" --community-id "community-id" --chat-id "chat-id" +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" + 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() + if err := manager.InitKeystore("./tmp"); err != nil { + return err + } + err := backend.OpenAccounts() + if err != nil { + logger.Error("failed open accounts", err) + 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, + } + + fmt.Println(nodeConfig) + accounts := []accounts.Account{walletAccount, chatAccount} + err = backend.StartNodeWithAccountAndConfig(account, "", *settings, nodeConfig, accounts) + if err != nil { + logger.Error("start node", err) + return err + } + + return nil +} diff --git a/cmd/ping-community/signing_phrase.go b/cmd/ping-community/signing_phrase.go new file mode 100644 index 000000000..42f45c745 --- /dev/null +++ b/cmd/ping-community/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", +}