github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/cmd/puppeth/wizard.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io/ioutil" 23 "math/big" 24 "net" 25 "net/url" 26 "os" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 "sync" 32 33 "github.com/peterh/liner" 34 "golang.org/x/crypto/ssh/terminal" 35 36 "github.com/scroll-tech/go-ethereum/common" 37 "github.com/scroll-tech/go-ethereum/console/prompt" 38 "github.com/scroll-tech/go-ethereum/core" 39 "github.com/scroll-tech/go-ethereum/log" 40 ) 41 42 // config contains all the configurations needed by puppeth that should be saved 43 // between sessions. 44 type config struct { 45 path string // File containing the configuration values 46 bootnodes []string // Bootnodes to always connect to by all nodes 47 ethstats string // Ethstats settings to cache for node deploys 48 49 Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys 50 Servers map[string][]byte `json:"servers,omitempty"` 51 } 52 53 // servers retrieves an alphabetically sorted list of servers. 54 func (c config) servers() []string { 55 servers := make([]string, 0, len(c.Servers)) 56 for server := range c.Servers { 57 servers = append(servers, server) 58 } 59 sort.Strings(servers) 60 61 return servers 62 } 63 64 // flush dumps the contents of config to disk. 65 func (c config) flush() { 66 os.MkdirAll(filepath.Dir(c.path), 0755) 67 68 out, _ := json.MarshalIndent(c, "", " ") 69 if err := ioutil.WriteFile(c.path, out, 0644); err != nil { 70 log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) 71 } 72 } 73 74 type wizard struct { 75 network string // Network name to manage 76 conf config // Configurations from previous runs 77 78 servers map[string]*sshClient // SSH connections to servers to administer 79 services map[string][]string // Ethereum services known to be running on servers 80 81 lock sync.Mutex // Lock to protect configs during concurrent service discovery 82 } 83 84 // prompts the user for input with the given prompt string. Returns when a value is entered. 85 // Causes the wizard to exit if ctrl-d is pressed 86 func promptInput(p string) string { 87 for { 88 text, err := prompt.Stdin.PromptInput(p) 89 if err != nil { 90 if err != liner.ErrPromptAborted { 91 log.Crit("Failed to read user input", "err", err) 92 } 93 } else { 94 return text 95 } 96 } 97 } 98 99 // read reads a single line from stdin, trimming if from spaces. 100 func (w *wizard) read() string { 101 text := promptInput("> ") 102 return strings.TrimSpace(text) 103 } 104 105 // readString reads a single line from stdin, trimming if from spaces, enforcing 106 // non-emptyness. 107 func (w *wizard) readString() string { 108 for { 109 text := promptInput("> ") 110 if text = strings.TrimSpace(text); text != "" { 111 return text 112 } 113 } 114 } 115 116 // readDefaultString reads a single line from stdin, trimming if from spaces. If 117 // an empty line is entered, the default value is returned. 118 func (w *wizard) readDefaultString(def string) string { 119 text := promptInput("> ") 120 if text = strings.TrimSpace(text); text != "" { 121 return text 122 } 123 return def 124 } 125 126 // readDefaultYesNo reads a single line from stdin, trimming if from spaces and 127 // interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default 128 // value is returned. 129 func (w *wizard) readDefaultYesNo(def bool) bool { 130 for { 131 text := promptInput("> ") 132 if text = strings.ToLower(strings.TrimSpace(text)); text == "" { 133 return def 134 } 135 if text == "y" || text == "yes" { 136 return true 137 } 138 if text == "n" || text == "no" { 139 return false 140 } 141 log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty") 142 } 143 } 144 145 // readURL reads a single line from stdin, trimming if from spaces and trying to 146 // interpret it as a URL (http, https or file). 147 func (w *wizard) readURL() *url.URL { 148 for { 149 text := promptInput("> ") 150 uri, err := url.Parse(strings.TrimSpace(text)) 151 if err != nil { 152 log.Error("Invalid input, expected URL", "err", err) 153 continue 154 } 155 return uri 156 } 157 } 158 159 // readInt reads a single line from stdin, trimming if from spaces, enforcing it 160 // to parse into an integer. 161 func (w *wizard) readInt() int { 162 for { 163 text := promptInput("> ") 164 if text = strings.TrimSpace(text); text == "" { 165 continue 166 } 167 val, err := strconv.Atoi(strings.TrimSpace(text)) 168 if err != nil { 169 log.Error("Invalid input, expected integer", "err", err) 170 continue 171 } 172 return val 173 } 174 } 175 176 // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing 177 // it to parse into an integer. If an empty line is entered, the default value is 178 // returned. 179 func (w *wizard) readDefaultInt(def int) int { 180 for { 181 text := promptInput("> ") 182 if text = strings.TrimSpace(text); text == "" { 183 return def 184 } 185 val, err := strconv.Atoi(strings.TrimSpace(text)) 186 if err != nil { 187 log.Error("Invalid input, expected integer", "err", err) 188 continue 189 } 190 return val 191 } 192 } 193 194 // readDefaultBigInt reads a single line from stdin, trimming if from spaces, 195 // enforcing it to parse into a big integer. If an empty line is entered, the 196 // default value is returned. 197 func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { 198 for { 199 text := promptInput("> ") 200 if text = strings.TrimSpace(text); text == "" { 201 return def 202 } 203 val, ok := new(big.Int).SetString(text, 0) 204 if !ok { 205 log.Error("Invalid input, expected big integer") 206 continue 207 } 208 return val 209 } 210 } 211 212 // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing 213 // it to parse into a float. If an empty line is entered, the default value is returned. 214 func (w *wizard) readDefaultFloat(def float64) float64 { 215 for { 216 text := promptInput("> ") 217 if text = strings.TrimSpace(text); text == "" { 218 return def 219 } 220 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 221 if err != nil { 222 log.Error("Invalid input, expected float", "err", err) 223 continue 224 } 225 return val 226 } 227 } 228 229 // readPassword reads a single line from stdin, trimming it from the trailing new 230 // line and returns it. The input will not be echoed. 231 func (w *wizard) readPassword() string { 232 fmt.Printf("> ") 233 text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 234 if err != nil { 235 log.Crit("Failed to read password", "err", err) 236 } 237 fmt.Println() 238 return string(text) 239 } 240 241 // readAddress reads a single line from stdin, trimming if from spaces and converts 242 // it to an Ethereum address. 243 func (w *wizard) readAddress() *common.Address { 244 for { 245 text := promptInput("> 0x") 246 if text = strings.TrimSpace(text); text == "" { 247 return nil 248 } 249 // Make sure it looks ok and return it if so 250 if len(text) != 40 { 251 log.Error("Invalid address length, please retry") 252 continue 253 } 254 bigaddr, _ := new(big.Int).SetString(text, 16) 255 address := common.BigToAddress(bigaddr) 256 return &address 257 } 258 } 259 260 // readDefaultAddress reads a single line from stdin, trimming if from spaces and 261 // converts it to an Ethereum address. If an empty line is entered, the default 262 // value is returned. 263 func (w *wizard) readDefaultAddress(def common.Address) common.Address { 264 for { 265 // Read the address from the user 266 text := promptInput("> 0x") 267 if text = strings.TrimSpace(text); text == "" { 268 return def 269 } 270 // Make sure it looks ok and return it if so 271 if len(text) != 40 { 272 log.Error("Invalid address length, please retry") 273 continue 274 } 275 bigaddr, _ := new(big.Int).SetString(text, 16) 276 return common.BigToAddress(bigaddr) 277 } 278 } 279 280 // readJSON reads a raw JSON message and returns it. 281 func (w *wizard) readJSON() string { 282 var blob json.RawMessage 283 284 for { 285 text := promptInput("> ") 286 reader := strings.NewReader(text) 287 if err := json.NewDecoder(reader).Decode(&blob); err != nil { 288 log.Error("Invalid JSON, please try again", "err", err) 289 continue 290 } 291 return string(blob) 292 } 293 } 294 295 // readIPAddress reads a single line from stdin, trimming if from spaces and 296 // returning it if it's convertible to an IP address. The reason for keeping 297 // the user input format instead of returning a Go net.IP is to match with 298 // weird formats used by ethstats, which compares IPs textually, not by value. 299 func (w *wizard) readIPAddress() string { 300 for { 301 // Read the IP address from the user 302 fmt.Printf("> ") 303 text := promptInput("> ") 304 if text = strings.TrimSpace(text); text == "" { 305 return "" 306 } 307 // Make sure it looks ok and return it if so 308 if ip := net.ParseIP(text); ip == nil { 309 log.Error("Invalid IP address, please retry") 310 continue 311 } 312 return text 313 } 314 }