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