280 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2017 The go-ethereum Authors
 | 
						|
// This file is part of go-ethereum.
 | 
						|
//
 | 
						|
// go-ethereum is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// go-ethereum is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
// GNU General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU General Public License
 | 
						|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"math/big"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
 | 
						|
	"github.com/ethereum/go-ethereum/common"
 | 
						|
	"github.com/ethereum/go-ethereum/core"
 | 
						|
	"github.com/ethereum/go-ethereum/log"
 | 
						|
	"golang.org/x/crypto/ssh/terminal"
 | 
						|
)
 | 
						|
 | 
						|
// config contains all the configurations needed by puppeth that should be saved
 | 
						|
// between sessions.
 | 
						|
type config struct {
 | 
						|
	path      string        // File containing the configuration values
 | 
						|
	genesis   *core.Genesis // Genesis block to cache for node deploys
 | 
						|
	bootFull  []string      // Bootnodes to always connect to by full nodes
 | 
						|
	bootLight []string      // Bootnodes to always connect to by light nodes
 | 
						|
	ethstats  string        // Ethstats settings to cache for node deploys
 | 
						|
 | 
						|
	Servers map[string][]byte `json:"servers,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// servers retrieves an alphabetically sorted list of servers.
 | 
						|
func (c config) servers() []string {
 | 
						|
	servers := make([]string, 0, len(c.Servers))
 | 
						|
	for server := range c.Servers {
 | 
						|
		servers = append(servers, server)
 | 
						|
	}
 | 
						|
	sort.Strings(servers)
 | 
						|
 | 
						|
	return servers
 | 
						|
}
 | 
						|
 | 
						|
// flush dumps the contents of config to disk.
 | 
						|
func (c config) flush() {
 | 
						|
	os.MkdirAll(filepath.Dir(c.path), 0755)
 | 
						|
 | 
						|
	out, _ := json.MarshalIndent(c, "", "  ")
 | 
						|
	if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
 | 
						|
		log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type wizard struct {
 | 
						|
	network string // Network name to manage
 | 
						|
	conf    config // Configurations from previous runs
 | 
						|
 | 
						|
	servers  map[string]*sshClient // SSH connections to servers to administer
 | 
						|
	services map[string][]string   // Ethereum services known to be running on servers
 | 
						|
 | 
						|
	in *bufio.Reader // Wrapper around stdin to allow reading user input
 | 
						|
}
 | 
						|
 | 
						|
// read reads a single line from stdin, trimming if from spaces.
 | 
						|
func (w *wizard) read() string {
 | 
						|
	fmt.Printf("> ")
 | 
						|
	text, err := w.in.ReadString('\n')
 | 
						|
	if err != nil {
 | 
						|
		log.Crit("Failed to read user input", "err", err)
 | 
						|
	}
 | 
						|
	return strings.TrimSpace(text)
 | 
						|
}
 | 
						|
 | 
						|
// readString reads a single line from stdin, trimming if from spaces, enforcing
 | 
						|
// non-emptyness.
 | 
						|
func (w *wizard) readString() string {
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text != "" {
 | 
						|
			return text
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// readDefaultString reads a single line from stdin, trimming if from spaces. If
 | 
						|
// an empty line is entered, the default value is returned.
 | 
						|
func (w *wizard) readDefaultString(def string) string {
 | 
						|
	fmt.Printf("> ")
 | 
						|
	text, err := w.in.ReadString('\n')
 | 
						|
	if err != nil {
 | 
						|
		log.Crit("Failed to read user input", "err", err)
 | 
						|
	}
 | 
						|
	if text = strings.TrimSpace(text); text != "" {
 | 
						|
		return text
 | 
						|
	}
 | 
						|
	return def
 | 
						|
}
 | 
						|
 | 
						|
// readInt reads a single line from stdin, trimming if from spaces, enforcing it
 | 
						|
// to parse into an integer.
 | 
						|
func (w *wizard) readInt() int {
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		val, err := strconv.Atoi(strings.TrimSpace(text))
 | 
						|
		if err != nil {
 | 
						|
			log.Error("Invalid input, expected integer", "err", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return val
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing
 | 
						|
// it to parse into an integer. If an empty line is entered, the default value is
 | 
						|
// returned.
 | 
						|
func (w *wizard) readDefaultInt(def int) int {
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			return def
 | 
						|
		}
 | 
						|
		val, err := strconv.Atoi(strings.TrimSpace(text))
 | 
						|
		if err != nil {
 | 
						|
			log.Error("Invalid input, expected integer", "err", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return val
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
// readFloat reads a single line from stdin, trimming if from spaces, enforcing it
 | 
						|
// to parse into a float.
 | 
						|
func (w *wizard) readFloat() float64 {
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
 | 
						|
		if err != nil {
 | 
						|
			log.Error("Invalid input, expected float", "err", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return val
 | 
						|
	}
 | 
						|
}
 | 
						|
*/
 | 
						|
 | 
						|
// readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing
 | 
						|
// it to parse into a float. If an empty line is entered, the default value is returned.
 | 
						|
func (w *wizard) readDefaultFloat(def float64) float64 {
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			return def
 | 
						|
		}
 | 
						|
		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
 | 
						|
		if err != nil {
 | 
						|
			log.Error("Invalid input, expected float", "err", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return val
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// readPassword reads a single line from stdin, trimming it from the trailing new
 | 
						|
// line and returns it. The input will not be echoed.
 | 
						|
func (w *wizard) readPassword() string {
 | 
						|
	fmt.Printf("> ")
 | 
						|
	text, err := terminal.ReadPassword(int(syscall.Stdin))
 | 
						|
	if err != nil {
 | 
						|
		log.Crit("Failed to read password", "err", err)
 | 
						|
	}
 | 
						|
	fmt.Println()
 | 
						|
	return string(text)
 | 
						|
}
 | 
						|
 | 
						|
// readAddress reads a single line from stdin, trimming if from spaces and converts
 | 
						|
// it to an Ethereum address.
 | 
						|
func (w *wizard) readAddress() *common.Address {
 | 
						|
	for {
 | 
						|
		// Read the address from the user
 | 
						|
		fmt.Printf("> 0x")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		// Make sure it looks ok and return it if so
 | 
						|
		if len(text) != 40 {
 | 
						|
			log.Error("Invalid address length, please retry")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		bigaddr, _ := new(big.Int).SetString(text, 16)
 | 
						|
		address := common.BigToAddress(bigaddr)
 | 
						|
		return &address
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// readDefaultAddress reads a single line from stdin, trimming if from spaces and
 | 
						|
// converts it to an Ethereum address. If an empty line is entered, the default
 | 
						|
// value is returned.
 | 
						|
func (w *wizard) readDefaultAddress(def common.Address) common.Address {
 | 
						|
	for {
 | 
						|
		// Read the address from the user
 | 
						|
		fmt.Printf("> 0x")
 | 
						|
		text, err := w.in.ReadString('\n')
 | 
						|
		if err != nil {
 | 
						|
			log.Crit("Failed to read user input", "err", err)
 | 
						|
		}
 | 
						|
		if text = strings.TrimSpace(text); text == "" {
 | 
						|
			return def
 | 
						|
		}
 | 
						|
		// Make sure it looks ok and return it if so
 | 
						|
		if len(text) != 40 {
 | 
						|
			log.Error("Invalid address length, please retry")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		bigaddr, _ := new(big.Int).SetString(text, 16)
 | 
						|
		return common.BigToAddress(bigaddr)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// readJSON reads a raw JSON message and returns it.
 | 
						|
func (w *wizard) readJSON() string {
 | 
						|
	var blob json.RawMessage
 | 
						|
 | 
						|
	for {
 | 
						|
		fmt.Printf("> ")
 | 
						|
		if err := json.NewDecoder(w.in).Decode(&blob); err != nil {
 | 
						|
			log.Error("Invalid JSON, please try again", "err", err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		return string(blob)
 | 
						|
	}
 | 
						|
}
 |