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