| 
									
										
										
										
											2016-11-09 02:01:56 +01:00
										 |  |  | // Copyright 2016 The go-ethereum Authors | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // This file is part of the go-ethereum library. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Lesser General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The go-ethereum library 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 Lesser General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Lesser General Public License | 
					
						
							|  |  |  | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package console | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"os/signal" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"regexp" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2018-02-20 14:33:34 +02:00
										 |  |  | 	"syscall" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	"github.com/dop251/goja" | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | 	"github.com/ethereum/go-ethereum/console/prompt" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	"github.com/ethereum/go-ethereum/internal/jsre" | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	"github.com/ethereum/go-ethereum/internal/jsre/deps" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	"github.com/ethereum/go-ethereum/internal/web3ext" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/rpc" | 
					
						
							| 
									
										
										
										
											2016-06-08 13:12:15 +03:00
										 |  |  | 	"github.com/mattn/go-colorable" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	"github.com/peterh/liner" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							| 
									
										
										
										
											2020-07-02 10:00:18 +02:00
										 |  |  | 	// u: unlock, s: signXX, sendXX, n: newAccount, i: importXX | 
					
						
							|  |  |  | 	passwordRegexp = regexp.MustCompile(`personal.[nusi]`) | 
					
						
							| 
									
										
										
										
											2017-01-06 16:44:20 +01:00
										 |  |  | 	onlyWhitespace = regexp.MustCompile(`^\s*$`) | 
					
						
							|  |  |  | 	exit           = regexp.MustCompile(`^\s*exit\s*;*\s*$`) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HistoryFile is the file within the data directory to store input scrollback. | 
					
						
							|  |  |  | const HistoryFile = "history" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // DefaultPrompt is the default prompt line prefix to use for user input querying. | 
					
						
							|  |  |  | const DefaultPrompt = "> " | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-11-24 10:20:01 +01:00
										 |  |  | // Config is the collection of configurations to fine tune the behavior of the | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // JavaScript console. | 
					
						
							|  |  |  | type Config struct { | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | 	DataDir  string              // Data directory to store the console history at | 
					
						
							|  |  |  | 	DocRoot  string              // Filesystem path from where to load JavaScript files from | 
					
						
							|  |  |  | 	Client   *rpc.Client         // RPC client to execute Ethereum requests through | 
					
						
							|  |  |  | 	Prompt   string              // Input prompt prefix string (defaults to DefaultPrompt) | 
					
						
							|  |  |  | 	Prompter prompt.UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter) | 
					
						
							|  |  |  | 	Printer  io.Writer           // Output writer to serialize any display strings to (defaults to os.Stdout) | 
					
						
							|  |  |  | 	Preload  []string            // Absolute paths to JavaScript files to preload | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-14 16:35:20 +08:00
										 |  |  | // Console is a JavaScript interpreted runtime environment. It is a fully fledged | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // JavaScript console attached to a running node via an external or in-process RPC | 
					
						
							|  |  |  | // client. | 
					
						
							|  |  |  | type Console struct { | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | 	client   *rpc.Client         // RPC client to execute Ethereum requests through | 
					
						
							|  |  |  | 	jsre     *jsre.JSRE          // JavaScript runtime environment running the interpreter | 
					
						
							|  |  |  | 	prompt   string              // Input prompt prefix string | 
					
						
							|  |  |  | 	prompter prompt.UserPrompter // Input prompter to allow interactive user feedback | 
					
						
							|  |  |  | 	histPath string              // Absolute path to the console scrollback history | 
					
						
							|  |  |  | 	history  []string            // Scroll history maintained by the console | 
					
						
							|  |  |  | 	printer  io.Writer           // Output writer to serialize any display strings to | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-05-31 04:59:08 -07:00
										 |  |  | // New initializes a JavaScript interpreted runtime environment and sets defaults | 
					
						
							|  |  |  | // with the config struct. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | func New(config Config) (*Console, error) { | 
					
						
							|  |  |  | 	// Handle unset config values gracefully | 
					
						
							|  |  |  | 	if config.Prompter == nil { | 
					
						
							| 
									
										
										
										
											2020-05-19 10:44:46 +02:00
										 |  |  | 		config.Prompter = prompt.Stdin | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if config.Prompt == "" { | 
					
						
							|  |  |  | 		config.Prompt = DefaultPrompt | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if config.Printer == nil { | 
					
						
							| 
									
										
										
										
											2016-06-08 13:12:15 +03:00
										 |  |  | 		config.Printer = colorable.NewColorableStdout() | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	// Initialize the console and return | 
					
						
							|  |  |  | 	console := &Console{ | 
					
						
							|  |  |  | 		client:   config.Client, | 
					
						
							|  |  |  | 		jsre:     jsre.New(config.DocRoot, config.Printer), | 
					
						
							|  |  |  | 		prompt:   config.Prompt, | 
					
						
							|  |  |  | 		prompter: config.Prompter, | 
					
						
							|  |  |  | 		printer:  config.Printer, | 
					
						
							|  |  |  | 		histPath: filepath.Join(config.DataDir, HistoryFile), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-12-19 13:21:03 +01:00
										 |  |  | 	if err := os.MkdirAll(config.DataDir, 0700); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	if err := console.init(config.Preload); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return console, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // init retrieves the available APIs from the remote RPC provider and initializes | 
					
						
							|  |  |  | // the console's JavaScript namespaces based on the exposed modules. | 
					
						
							|  |  |  | func (c *Console) init(preload []string) error { | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	c.initConsoleObject() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Initialize the JavaScript <-> Go RPC bridge. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	bridge := newBridge(c.client, c.prompter, c.printer) | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	if err := c.initWeb3(bridge); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := c.initExtensions(); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Add bridge overrides for web3.js functionality. | 
					
						
							|  |  |  | 	c.jsre.Do(func(vm *goja.Runtime) { | 
					
						
							|  |  |  | 		c.initAdmin(vm, bridge) | 
					
						
							|  |  |  | 		c.initPersonal(vm, bridge) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Preload JavaScript files. | 
					
						
							|  |  |  | 	for _, path := range preload { | 
					
						
							|  |  |  | 		if err := c.jsre.Exec(path); err != nil { | 
					
						
							|  |  |  | 			failure := err.Error() | 
					
						
							|  |  |  | 			if gojaErr, ok := err.(*goja.Exception); ok { | 
					
						
							|  |  |  | 				failure = gojaErr.String() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return fmt.Errorf("%s: %v", path, failure) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	// Configure the input prompter for history and tab completion. | 
					
						
							|  |  |  | 	if c.prompter != nil { | 
					
						
							|  |  |  | 		if content, err := ioutil.ReadFile(c.histPath); err != nil { | 
					
						
							|  |  |  | 			c.prompter.SetHistory(nil) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			c.history = strings.Split(string(content), "\n") | 
					
						
							|  |  |  | 			c.prompter.SetHistory(c.history) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		c.prompter.SetWordCompleter(c.AutoCompleteInput) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | func (c *Console) initConsoleObject() { | 
					
						
							|  |  |  | 	c.jsre.Do(func(vm *goja.Runtime) { | 
					
						
							|  |  |  | 		console := vm.NewObject() | 
					
						
							|  |  |  | 		console.Set("log", c.consoleOutput) | 
					
						
							|  |  |  | 		console.Set("error", c.consoleOutput) | 
					
						
							|  |  |  | 		vm.Set("console", console) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | func (c *Console) initWeb3(bridge *bridge) error { | 
					
						
							|  |  |  | 	bnJS := string(deps.MustAsset("bignumber.js")) | 
					
						
							|  |  |  | 	web3JS := string(deps.MustAsset("web3.js")) | 
					
						
							|  |  |  | 	if err := c.jsre.Compile("bignumber.js", bnJS); err != nil { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		return fmt.Errorf("bignumber.js: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	if err := c.jsre.Compile("web3.js", web3JS); err != nil { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		return fmt.Errorf("web3.js: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("web3 require: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	var err error | 
					
						
							|  |  |  | 	c.jsre.Do(func(vm *goja.Runtime) { | 
					
						
							|  |  |  | 		transport := vm.NewObject() | 
					
						
							|  |  |  | 		transport.Set("send", jsre.MakeCallback(vm, bridge.Send)) | 
					
						
							|  |  |  | 		transport.Set("sendAsync", jsre.MakeCallback(vm, bridge.Send)) | 
					
						
							|  |  |  | 		vm.Set("_consoleWeb3Transport", transport) | 
					
						
							|  |  |  | 		_, err = vm.RunString("var web3 = new Web3(_consoleWeb3Transport)") | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // initExtensions loads and registers web3.js extensions. | 
					
						
							|  |  |  | func (c *Console) initExtensions() error { | 
					
						
							|  |  |  | 	// Compute aliases from server-provided modules. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	apis, err := c.client.SupportedModules() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("api modules: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	aliases := map[string]struct{}{"eth": {}, "personal": {}} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	for api := range apis { | 
					
						
							|  |  |  | 		if api == "web3" { | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 		aliases[api] = struct{}{} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		if file, ok := web3ext.Modules[api]; ok { | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 			if err = c.jsre.Compile(api+".js", file); err != nil { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 				return fmt.Errorf("%s.js: %v", api, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	// Apply aliases. | 
					
						
							|  |  |  | 	c.jsre.Do(func(vm *goja.Runtime) { | 
					
						
							|  |  |  | 		web3 := getObject(vm, "web3") | 
					
						
							|  |  |  | 		for name := range aliases { | 
					
						
							|  |  |  | 			if v := web3.Get(name); v != nil { | 
					
						
							|  |  |  | 				vm.Set(name, v) | 
					
						
							| 
									
										
										
										
											2016-05-11 17:28:29 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // initAdmin creates additional admin APIs implemented by the bridge. | 
					
						
							|  |  |  | func (c *Console) initAdmin(vm *goja.Runtime, bridge *bridge) { | 
					
						
							|  |  |  | 	if admin := getObject(vm, "admin"); admin != nil { | 
					
						
							|  |  |  | 		admin.Set("sleepBlocks", jsre.MakeCallback(vm, bridge.SleepBlocks)) | 
					
						
							|  |  |  | 		admin.Set("sleep", jsre.MakeCallback(vm, bridge.Sleep)) | 
					
						
							|  |  |  | 		admin.Set("clearHistory", c.clearHistory) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // initPersonal redirects account-related API methods through the bridge. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If the console is in interactive mode and the 'personal' API is available, override | 
					
						
							|  |  |  | // the openWallet, unlockAccount, newAccount and sign methods since these require user | 
					
						
							|  |  |  | // interaction. The original web3 callbacks are stored in 'jeth'. These will be called | 
					
						
							|  |  |  | // by the bridge after the prompt and send the original web3 request to the backend. | 
					
						
							|  |  |  | func (c *Console) initPersonal(vm *goja.Runtime, bridge *bridge) { | 
					
						
							|  |  |  | 	personal := getObject(vm, "personal") | 
					
						
							|  |  |  | 	if personal == nil || c.prompter == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	jeth := vm.NewObject() | 
					
						
							|  |  |  | 	vm.Set("jeth", jeth) | 
					
						
							|  |  |  | 	jeth.Set("openWallet", personal.Get("openWallet")) | 
					
						
							|  |  |  | 	jeth.Set("unlockAccount", personal.Get("unlockAccount")) | 
					
						
							|  |  |  | 	jeth.Set("newAccount", personal.Get("newAccount")) | 
					
						
							|  |  |  | 	jeth.Set("sign", personal.Get("sign")) | 
					
						
							|  |  |  | 	personal.Set("openWallet", jsre.MakeCallback(vm, bridge.OpenWallet)) | 
					
						
							|  |  |  | 	personal.Set("unlockAccount", jsre.MakeCallback(vm, bridge.UnlockAccount)) | 
					
						
							|  |  |  | 	personal.Set("newAccount", jsre.MakeCallback(vm, bridge.NewAccount)) | 
					
						
							|  |  |  | 	personal.Set("sign", jsre.MakeCallback(vm, bridge.Sign)) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-08 06:14:14 -08:00
										 |  |  | func (c *Console) clearHistory() { | 
					
						
							|  |  |  | 	c.history = nil | 
					
						
							|  |  |  | 	c.prompter.ClearHistory() | 
					
						
							|  |  |  | 	if err := os.Remove(c.histPath); err != nil { | 
					
						
							|  |  |  | 		fmt.Fprintln(c.printer, "can't delete history file:", err) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		fmt.Fprintln(c.printer, "history file deleted") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // consoleOutput is an override for the console.log and console.error methods to | 
					
						
							|  |  |  | // stream the output into the configured output stream instead of stdout. | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | func (c *Console) consoleOutput(call goja.FunctionCall) goja.Value { | 
					
						
							| 
									
										
										
										
											2019-02-14 14:59:54 -08:00
										 |  |  | 	var output []string | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	for _, argument := range call.Arguments { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		output = append(output, fmt.Sprintf("%v", argument)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	fmt.Fprintln(c.printer, strings.Join(output, " ")) | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	return goja.Null() | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // AutoCompleteInput is a pre-assembled word completer to be used by the user | 
					
						
							|  |  |  | // input prompter to provide hints to the user about the methods available. | 
					
						
							|  |  |  | func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) { | 
					
						
							|  |  |  | 	// No completions can be provided for empty inputs | 
					
						
							|  |  |  | 	if len(line) == 0 || pos == 0 { | 
					
						
							|  |  |  | 		return "", nil, "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Chunck data to relevant part for autocompletion | 
					
						
							|  |  |  | 	// E.g. in case of nested lines eth.getBalance(eth.coinb<tab><tab> | 
					
						
							| 
									
										
										
										
											2017-01-09 11:16:06 +01:00
										 |  |  | 	start := pos - 1 | 
					
						
							|  |  |  | 	for ; start > 0; start-- { | 
					
						
							| 
									
										
										
										
											2017-11-24 10:20:01 +01:00
										 |  |  | 		// Skip all methods and namespaces (i.e. including the dot) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Handle web3 in a special way (i.e. other numbers aren't auto completed) | 
					
						
							|  |  |  | 		if start >= 3 && line[start-3:start] == "web3" { | 
					
						
							|  |  |  | 			start -= 3 | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// We've hit an unexpected character, autocomplete form here | 
					
						
							|  |  |  | 		start++ | 
					
						
							|  |  |  | 		break | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Welcome show summary of current Geth instance and some metadata about the | 
					
						
							|  |  |  | // console's available modules. | 
					
						
							|  |  |  | func (c *Console) Welcome() { | 
					
						
							| 
									
										
										
										
											2019-04-02 13:59:39 +03:00
										 |  |  | 	message := "Welcome to the Geth JavaScript console!\n\n" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	// Print some generic Geth metadata | 
					
						
							| 
									
										
										
										
											2019-04-02 13:59:39 +03:00
										 |  |  | 	if res, err := c.jsre.Run(` | 
					
						
							|  |  |  | 		var message = "instance: " + web3.version.node + "\n"; | 
					
						
							|  |  |  | 		try { | 
					
						
							|  |  |  | 			message += "coinbase: " + eth.coinbase + "\n"; | 
					
						
							|  |  |  | 		} catch (err) {} | 
					
						
							|  |  |  | 		message += "at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")\n"; | 
					
						
							|  |  |  | 		try { | 
					
						
							|  |  |  | 			message += " datadir: " + admin.datadir + "\n"; | 
					
						
							|  |  |  | 		} catch (err) {} | 
					
						
							|  |  |  | 		message | 
					
						
							|  |  |  | 	`); err == nil { | 
					
						
							|  |  |  | 		message += res.String() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	// List all the supported modules for the user to call | 
					
						
							|  |  |  | 	if apis, err := c.client.SupportedModules(); err == nil { | 
					
						
							|  |  |  | 		modules := make([]string, 0, len(apis)) | 
					
						
							|  |  |  | 		for api, version := range apis { | 
					
						
							|  |  |  | 			modules = append(modules, fmt.Sprintf("%s:%s", api, version)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		sort.Strings(modules) | 
					
						
							| 
									
										
										
										
											2019-04-02 13:59:39 +03:00
										 |  |  | 		message += " modules: " + strings.Join(modules, " ") + "\n" | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-20 10:56:51 +02:00
										 |  |  | 	message += "\nTo exit, press ctrl-d" | 
					
						
							| 
									
										
										
										
											2019-04-02 13:59:39 +03:00
										 |  |  | 	fmt.Fprintln(c.printer, message) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Evaluate executes code and pretty prints the result to the specified output | 
					
						
							|  |  |  | // stream. | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | func (c *Console) Evaluate(statement string) { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if r := recover(); r != nil { | 
					
						
							|  |  |  | 			fmt.Fprintf(c.printer, "[native] error: %v\n", r) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							| 
									
										
										
										
											2020-01-27 11:50:48 +01:00
										 |  |  | 	c.jsre.Evaluate(statement, c.printer) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Interactive starts an interactive user session, where input is propted from | 
					
						
							|  |  |  | // the configured user prompter. | 
					
						
							|  |  |  | func (c *Console) Interactive() { | 
					
						
							|  |  |  | 	var ( | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 		prompt      = c.prompt             // the current prompt line (used for multi-line inputs) | 
					
						
							|  |  |  | 		indents     = 0                    // the current number of input indents (used for multi-line inputs) | 
					
						
							|  |  |  | 		input       = ""                   // the current user input | 
					
						
							|  |  |  | 		inputLine   = make(chan string, 1) // receives user input | 
					
						
							|  |  |  | 		inputErr    = make(chan error, 1)  // receives liner errors | 
					
						
							|  |  |  | 		requestLine = make(chan string)    // requests a line of input | 
					
						
							|  |  |  | 		interrupt   = make(chan os.Signal, 1) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 	// Monitor Ctrl-C. While liner does turn on the relevant terminal mode bits to avoid | 
					
						
							|  |  |  | 	// the signal, a signal can still be received for unsupported terminals. Unfortunately | 
					
						
							|  |  |  | 	// there is no way to cancel the line reader when this happens. The readLines | 
					
						
							|  |  |  | 	// goroutine will be leaked in this case. | 
					
						
							|  |  |  | 	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) | 
					
						
							|  |  |  | 	defer signal.Stop(interrupt) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The line reader runs in a separate goroutine. | 
					
						
							|  |  |  | 	go c.readLines(inputLine, inputErr, requestLine) | 
					
						
							|  |  |  | 	defer close(requestLine) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 	for { | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 		// Send the next prompt, triggering an input read. | 
					
						
							|  |  |  | 		requestLine <- prompt | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		select { | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 		case <-interrupt: | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 			fmt.Fprintln(c.printer, "caught interrupt, exiting") | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 		case err := <-inputErr: | 
					
						
							| 
									
										
										
										
											2020-10-20 10:56:51 +02:00
										 |  |  | 			if err == liner.ErrPromptAborted { | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 				// When prompting for multi-line input, the first Ctrl-C resets | 
					
						
							|  |  |  | 				// the multi-line state. | 
					
						
							|  |  |  | 				prompt, indents, input = c.prompt, 0, "" | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case line := <-inputLine: | 
					
						
							|  |  |  | 			// User input was returned by the prompter, handle special cases. | 
					
						
							|  |  |  | 			if indents <= 0 && exit.MatchString(line) { | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if onlyWhitespace.MatchString(line) { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 			// Append the line to the input and check for multi-line interpretation. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 			input += line + "\n" | 
					
						
							| 
									
										
										
										
											2016-06-08 12:17:38 +02:00
										 |  |  | 			indents = countIndents(input) | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 			if indents <= 0 { | 
					
						
							|  |  |  | 				prompt = c.prompt | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2016-06-08 12:17:38 +02:00
										 |  |  | 				prompt = strings.Repeat(".", indents*3) + " " | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | 			// If all the needed lines are present, save the command and run it. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 			if indents <= 0 { | 
					
						
							| 
									
										
										
										
											2016-05-30 17:30:17 +03:00
										 |  |  | 				if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { | 
					
						
							|  |  |  | 					if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { | 
					
						
							|  |  |  | 						c.history = append(c.history, command) | 
					
						
							|  |  |  | 						if c.prompter != nil { | 
					
						
							|  |  |  | 							c.prompter.AppendHistory(command) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				c.Evaluate(input) | 
					
						
							|  |  |  | 				input = "" | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-04 02:07:22 +08:00
										 |  |  | // readLines runs in its own goroutine, prompting for input. | 
					
						
							|  |  |  | func (c *Console) readLines(input chan<- string, errc chan<- error, prompt <-chan string) { | 
					
						
							|  |  |  | 	for p := range prompt { | 
					
						
							|  |  |  | 		line, err := c.prompter.PromptInput(p) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			errc <- err | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			input <- line | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-08 12:17:38 +02:00
										 |  |  | // countIndents returns the number of identations for the given input. | 
					
						
							|  |  |  | // In case of invalid input such as var a = } the result can be negative. | 
					
						
							|  |  |  | func countIndents(input string) int { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		indents     = 0 | 
					
						
							|  |  |  | 		inString    = false | 
					
						
							|  |  |  | 		strOpenChar = ' '   // keep track of the string open char to allow var str = "I'm ...."; | 
					
						
							|  |  |  | 		charEscaped = false // keep track if the previous char was the '\' char, allow var str = "abc\"def"; | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, c := range input { | 
					
						
							|  |  |  | 		switch c { | 
					
						
							|  |  |  | 		case '\\': | 
					
						
							|  |  |  | 			// indicate next char as escaped when in string and previous char isn't escaping this backslash | 
					
						
							|  |  |  | 			if !charEscaped && inString { | 
					
						
							|  |  |  | 				charEscaped = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case '\'', '"': | 
					
						
							|  |  |  | 			if inString && !charEscaped && strOpenChar == c { // end string | 
					
						
							|  |  |  | 				inString = false | 
					
						
							|  |  |  | 			} else if !inString && !charEscaped { // begin string | 
					
						
							|  |  |  | 				inString = true | 
					
						
							|  |  |  | 				strOpenChar = c | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			charEscaped = false | 
					
						
							|  |  |  | 		case '{', '(': | 
					
						
							|  |  |  | 			if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting | 
					
						
							|  |  |  | 				indents++ | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			charEscaped = false | 
					
						
							|  |  |  | 		case '}', ')': | 
					
						
							|  |  |  | 			if !inString { | 
					
						
							|  |  |  | 				indents-- | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			charEscaped = false | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			charEscaped = false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return indents | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | // Execute runs the JavaScript file specified as the argument. | 
					
						
							|  |  |  | func (c *Console) Execute(path string) error { | 
					
						
							|  |  |  | 	return c.jsre.Exec(path) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-19 13:21:03 +01:00
										 |  |  | // Stop cleans up the console and terminates the runtime environment. | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | func (c *Console) Stop(graceful bool) error { | 
					
						
							| 
									
										
										
										
											2016-05-30 17:30:17 +03:00
										 |  |  | 	if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if err := os.Chmod(c.histPath, 0600); err != nil { // Force 0600, even if it was different previously | 
					
						
							| 
									
										
										
										
											2016-05-06 12:40:23 +03:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	c.jsre.Stop(graceful) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |