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  }