github.com/ConsenSys/Quorum@v20.10.0+incompatible/cmd/geth/consolecmd.go (about)

     1  // Copyright 2016 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  	"context"
    21  	"crypto/tls"
    22  	"crypto/x509"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"net/url"
    27  	"os"
    28  	"os/signal"
    29  	"path/filepath"
    30  	"strings"
    31  	"syscall"
    32  
    33  	"github.com/ethereum/go-ethereum/cmd/utils"
    34  	"github.com/ethereum/go-ethereum/console"
    35  	"github.com/ethereum/go-ethereum/log"
    36  	"github.com/ethereum/go-ethereum/node"
    37  	"github.com/ethereum/go-ethereum/plugin/security"
    38  	"github.com/ethereum/go-ethereum/rpc"
    39  	"gopkg.in/urfave/cli.v1"
    40  )
    41  
    42  var (
    43  	consoleFlags   = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag}
    44  	rpcClientFlags = []cli.Flag{utils.RPCClientToken, utils.RPCClientTLSCert, utils.RPCClientTLSCaCert, utils.RPCClientTLSCipherSuites, utils.RPCClientTLSInsecureSkipVerify}
    45  
    46  	consoleCommand = cli.Command{
    47  		Action:   utils.MigrateFlags(localConsole),
    48  		Name:     "console",
    49  		Usage:    "Start an interactive JavaScript environment",
    50  		Flags:    append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...),
    51  		Category: "CONSOLE COMMANDS",
    52  		Description: `
    53  The Geth console is an interactive shell for the JavaScript runtime environment
    54  which exposes a node admin interface as well as the Ðapp JavaScript API.
    55  See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.`,
    56  	}
    57  
    58  	attachCommand = cli.Command{
    59  		Action:    utils.MigrateFlags(remoteConsole),
    60  		Name:      "attach",
    61  		Usage:     "Start an interactive JavaScript environment (connect to node)",
    62  		ArgsUsage: "[endpoint]",
    63  		Flags:     append(append(consoleFlags, utils.DataDirFlag), rpcClientFlags...),
    64  		Category:  "CONSOLE COMMANDS",
    65  		Description: `
    66  The Geth console is an interactive shell for the JavaScript runtime environment
    67  which exposes a node admin interface as well as the Ðapp JavaScript API.
    68  See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console.
    69  This command allows to open a console on a running geth node.`,
    70  	}
    71  
    72  	javascriptCommand = cli.Command{
    73  		Action:    utils.MigrateFlags(ephemeralConsole),
    74  		Name:      "js",
    75  		Usage:     "Execute the specified JavaScript files",
    76  		ArgsUsage: "<jsfile> [jsfile...]",
    77  		Flags:     append(nodeFlags, consoleFlags...),
    78  		Category:  "CONSOLE COMMANDS",
    79  		Description: `
    80  The JavaScript VM exposes a node admin interface as well as the Ðapp
    81  JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console`,
    82  	}
    83  )
    84  
    85  // Quorum
    86  //
    87  // read tls client configuration from command line arguments
    88  //
    89  // only for HTTPS or WSS
    90  func readTLSClientConfig(endpoint string, ctx *cli.Context) (*tls.Config, bool, error) {
    91  	if !strings.HasPrefix(endpoint, "https://") && !strings.HasPrefix(endpoint, "wss://") {
    92  		return nil, false, nil
    93  	}
    94  	hasCustomTls := false
    95  	insecureSkipVerify := ctx.Bool(utils.RPCClientTLSInsecureSkipVerify.Name)
    96  	tlsConfig := &tls.Config{
    97  		InsecureSkipVerify: insecureSkipVerify,
    98  	}
    99  	var certFile, caFile string
   100  	if !insecureSkipVerify {
   101  		var certPem, caPem []byte
   102  		certFile, caFile = ctx.String(utils.RPCClientTLSCert.Name), ctx.String(utils.RPCClientTLSCaCert.Name)
   103  		var err error
   104  		if certFile != "" {
   105  			if certPem, err = ioutil.ReadFile(certFile); err != nil {
   106  				return nil, true, err
   107  			}
   108  		}
   109  		if caFile != "" {
   110  			if caPem, err = ioutil.ReadFile(caFile); err != nil {
   111  				return nil, true, err
   112  			}
   113  		}
   114  		if len(certPem) != 0 || len(caPem) != 0 {
   115  			certPool, err := x509.SystemCertPool()
   116  			if err != nil {
   117  				certPool = x509.NewCertPool()
   118  			}
   119  			if len(certPem) != 0 {
   120  				certPool.AppendCertsFromPEM(certPem)
   121  			}
   122  			if len(caPem) != 0 {
   123  				certPool.AppendCertsFromPEM(caPem)
   124  			}
   125  			tlsConfig.RootCAs = certPool
   126  			hasCustomTls = true
   127  		}
   128  	} else {
   129  		hasCustomTls = true
   130  	}
   131  	cipherSuitesInput := ctx.String(utils.RPCClientTLSCipherSuites.Name)
   132  	cipherSuitesStrings := strings.FieldsFunc(cipherSuitesInput, func(r rune) bool {
   133  		return r == ','
   134  	})
   135  	if len(cipherSuitesStrings) > 0 {
   136  		cipherSuiteList := make(security.CipherSuiteList, len(cipherSuitesStrings))
   137  		for i, s := range cipherSuitesStrings {
   138  			cipherSuiteList[i] = security.CipherSuite(strings.TrimSpace(s))
   139  		}
   140  		cipherSuites, err := cipherSuiteList.ToUint16Array()
   141  		if err != nil {
   142  			return nil, true, err
   143  		}
   144  		tlsConfig.CipherSuites = cipherSuites
   145  		hasCustomTls = true
   146  	}
   147  	if !hasCustomTls {
   148  		return nil, false, nil
   149  	}
   150  	return tlsConfig, hasCustomTls, nil
   151  }
   152  
   153  // localConsole starts a new geth node, attaching a JavaScript console to it at the
   154  // same time.
   155  func localConsole(ctx *cli.Context) error {
   156  	// Create and start the node based on the CLI flags
   157  	prepare(ctx)
   158  	node := makeFullNode(ctx)
   159  	startNode(ctx, node)
   160  	defer node.Close()
   161  
   162  	// Attach to the newly started node and start the JavaScript console
   163  	client, err := node.Attach()
   164  	if err != nil {
   165  		utils.Fatalf("Failed to attach to the inproc geth: %v", err)
   166  	}
   167  	config := console.Config{
   168  		DataDir: utils.MakeDataDir(ctx),
   169  		DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
   170  		Client:  client,
   171  		Preload: utils.MakeConsolePreloads(ctx),
   172  	}
   173  
   174  	console, err := console.New(config)
   175  	if err != nil {
   176  		utils.Fatalf("Failed to start the JavaScript console: %v", err)
   177  	}
   178  	defer console.Stop(false)
   179  
   180  	// If only a short execution was requested, evaluate and return
   181  	if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
   182  		console.Evaluate(script)
   183  		return nil
   184  	}
   185  	// Otherwise print the welcome screen and enter interactive mode
   186  	console.Welcome()
   187  	console.Interactive()
   188  
   189  	return nil
   190  }
   191  
   192  // remoteConsole will connect to a remote geth instance, attaching a JavaScript
   193  // console to it.
   194  func remoteConsole(ctx *cli.Context) error {
   195  	// Attach to a remotely running geth instance and start the JavaScript console
   196  	endpoint := ctx.Args().First()
   197  	if endpoint == "" {
   198  		path := node.DefaultDataDir()
   199  		if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
   200  			path = ctx.GlobalString(utils.DataDirFlag.Name)
   201  		}
   202  		if path != "" {
   203  			if ctx.GlobalBool(utils.TestnetFlag.Name) {
   204  				path = filepath.Join(path, "testnet")
   205  			} else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
   206  				path = filepath.Join(path, "rinkeby")
   207  			}
   208  		}
   209  		endpoint = fmt.Sprintf("%s/geth.ipc", path)
   210  	}
   211  	client, err := dialRPC(endpoint, ctx)
   212  	if err != nil {
   213  		utils.Fatalf("Unable to attach to remote geth: %v", err)
   214  	}
   215  	config := console.Config{
   216  		DataDir: utils.MakeDataDir(ctx),
   217  		DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
   218  		Client:  client,
   219  		Preload: utils.MakeConsolePreloads(ctx),
   220  	}
   221  
   222  	consl, err := console.New(config)
   223  	if err != nil {
   224  		utils.Fatalf("Failed to start the JavaScript console: %v", err)
   225  	}
   226  	defer consl.Stop(false)
   227  
   228  	if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
   229  		consl.Evaluate(script)
   230  		return nil
   231  	}
   232  
   233  	// Otherwise print the welcome screen and enter interactive mode
   234  	consl.Welcome()
   235  	consl.Interactive()
   236  
   237  	return nil
   238  }
   239  
   240  // dialRPC returns a RPC client which connects to the given endpoint.
   241  // The check for empty endpoint implements the defaulting logic
   242  // for "geth attach" and "geth monitor" with no argument.
   243  //
   244  // Quorum: passing the cli context to build security-aware client:
   245  // 1. Custom TLS configuration
   246  // 2. Access Token awareness via rpc.HttpCredentialsProviderFunc
   247  func dialRPC(endpoint string, ctx *cli.Context) (*rpc.Client, error) {
   248  	if endpoint == "" {
   249  		endpoint = node.DefaultIPCEndpoint(clientIdentifier)
   250  	} else if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
   251  		// Backwards compatibility with geth < 1.5 which required
   252  		// these prefixes.
   253  		endpoint = endpoint[4:]
   254  	}
   255  	var (
   256  		client  *rpc.Client
   257  		err     error
   258  		dialCtx = context.Background()
   259  	)
   260  	tlsConfig, hasCustomTls, tlsReadErr := readTLSClientConfig(endpoint, ctx)
   261  	if tlsReadErr != nil {
   262  		return nil, tlsReadErr
   263  	}
   264  	if token := ctx.String(utils.RPCClientToken.Name); token != "" {
   265  		var f rpc.HttpCredentialsProviderFunc = func(ctx context.Context) (string, error) {
   266  			return token, nil
   267  		}
   268  		// it's important that f MUST BE OF TYPE rpc.HttpCredentialsProviderFunc
   269  		dialCtx = context.WithValue(dialCtx, rpc.CtxCredentialsProvider, f)
   270  	}
   271  	if hasCustomTls {
   272  		u, err := url.Parse(endpoint)
   273  		if err != nil {
   274  			return nil, err
   275  		}
   276  		switch u.Scheme {
   277  		case "https":
   278  			customHttpClient := &http.Client{
   279  				Transport: http.DefaultTransport,
   280  			}
   281  			customHttpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig
   282  			client, err = rpc.DialHTTPWithClient(endpoint, customHttpClient)
   283  		case "wss":
   284  			client, err = rpc.DialWebsocketWithCustomTLS(dialCtx, endpoint, "", tlsConfig)
   285  		default:
   286  			log.Warn("unsupported scheme for custom TLS which is only for HTTPS/WSS", "scheme", u.Scheme)
   287  			client, err = rpc.DialContext(dialCtx, endpoint)
   288  		}
   289  	} else {
   290  		client, err = rpc.DialContext(dialCtx, endpoint)
   291  	}
   292  	if f, ok := dialCtx.Value(rpc.CtxCredentialsProvider).(rpc.HttpCredentialsProviderFunc); ok && err == nil {
   293  		client, err = client.WithHTTPCredentials(f)
   294  	}
   295  	return client, err
   296  }
   297  
   298  // ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
   299  // console to it, executes each of the files specified as arguments and tears
   300  // everything down.
   301  func ephemeralConsole(ctx *cli.Context) error {
   302  	// Create and start the node based on the CLI flags
   303  	node := makeFullNode(ctx)
   304  	startNode(ctx, node)
   305  	defer node.Close()
   306  
   307  	// Attach to the newly started node and start the JavaScript console
   308  	client, err := node.Attach()
   309  	if err != nil {
   310  		utils.Fatalf("Failed to attach to the inproc geth: %v", err)
   311  	}
   312  	config := console.Config{
   313  		DataDir: utils.MakeDataDir(ctx),
   314  		DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
   315  		Client:  client,
   316  		Preload: utils.MakeConsolePreloads(ctx),
   317  	}
   318  
   319  	console, err := console.New(config)
   320  	if err != nil {
   321  		utils.Fatalf("Failed to start the JavaScript console: %v", err)
   322  	}
   323  	defer console.Stop(false)
   324  
   325  	// Evaluate each of the specified JavaScript files
   326  	for _, file := range ctx.Args() {
   327  		if err = console.Execute(file); err != nil {
   328  			utils.Fatalf("Failed to execute %s: %v", file, err)
   329  		}
   330  	}
   331  	// Wait for pending callbacks, but stop for Ctrl-C.
   332  	abort := make(chan os.Signal, 1)
   333  	signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM)
   334  
   335  	go func() {
   336  		<-abort
   337  		os.Exit(0)
   338  	}()
   339  	console.Stop(true)
   340  
   341  	return nil
   342  }