github.com/aquanetwork/aquachain@v1.7.8/opt/console/console.go (about)

     1  // Copyright 2016 The aquachain Authors
     2  // This file is part of the aquachain library.
     3  //
     4  // The aquachain library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser 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  // The aquachain library 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 Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the aquachain library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package console
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/signal"
    25  	"path/filepath"
    26  	"regexp"
    27  	"sort"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"github.com/mattn/go-colorable"
    32  	"github.com/peterh/liner"
    33  	"github.com/robertkrimen/otto"
    34  	"gitlab.com/aquachain/aquachain/internal/jsre"
    35  	"gitlab.com/aquachain/aquachain/internal/web3ext"
    36  	"gitlab.com/aquachain/aquachain/rpc"
    37  )
    38  
    39  var (
    40  	passwordRegexp = regexp.MustCompile(`personal.[nus]`)
    41  	onlyWhitespace = regexp.MustCompile(`^\s*$`)
    42  	exit           = regexp.MustCompile(`^\s*exit\s*;*\s*$`)
    43  	help           = regexp.MustCompile(`^\s*help\s*;*\s*$`)
    44  	sendline       = regexp.MustCompile(`^\s*send\s*;*\s*$`)
    45  )
    46  
    47  // HistoryFile is the file within the data directory to store input scrollback.
    48  const HistoryFile = "history"
    49  
    50  // DefaultPrompt is the default prompt line prefix to use for user input querying.
    51  const DefaultPrompt = "AQUA> "
    52  
    53  const helpText = `
    54  Web links:
    55  
    56  	Explorer: https://aquachain.github.io/explorer/
    57  	Wiki: http://github.com/aquanetwork/aquachain/wiki/Basics
    58  	Chat: https://t.me/AquaCrypto
    59  
    60  Common AQUA commands::
    61  
    62  	New address:              personal.newAccount()
    63  	Import private key:       personal.importRawKey('the private key')
    64  	Start solo mining (cpu):  miner.start()
    65  	Get balance:              aqua.balance(aqua.coinbase)
    66  	Get all balances:         balance()
    67  	Send transaction:         send
    68  	List accounts:            aqua.accounts
    69  	Show Transaction:         aqua.getTransaction('the tx hash')
    70  	Show Block #1000:         aqua.getBlock('1000')
    71  	Show Latest:              aqua.getBlock('latest')
    72  
    73  In this javascript console, you can define variables and load script.
    74  
    75  	loadScript('filename.js')
    76  	block = aqua.getBlock
    77  	myBlock = block('0x92cd50f36edddd9347ec37ef93206135518acd4115941f6287ea00407f186e15')
    78  	tx = aqua.getTransaction('0x23eabf63f8da796e2e68cd2ae602c1b5a9cb8f9946ad9d87a9561924e3d20db8')
    79  	web3.fromWei(tx.value)
    80  
    81  Press TAB to autocomplete commands
    82  `
    83  
    84  const logo = `                              _           _
    85    __ _  __ _ _   _  __ _  ___| |__   __ _(_)_ __
    86   / _ '|/ _' | | | |/ _' |/ __| '_ \ / _' | | '_ \
    87  | (_| | (_| | |_| | (_| | (__| | | | (_| | | | | |
    88   \__,_|\__, |\__,_|\__,_|\___|_| |_|\__,_|_|_| |_|
    89            |_|` + "\nUpdate Often! https://gitlab.com/aquachain/aquachain\n\n"
    90  
    91  // Config is the collection of configurations to fine tune the behavior of the
    92  // JavaScript console.
    93  type Config struct {
    94  	DataDir  string       // Data directory to store the console history at
    95  	DocRoot  string       // Filesystem path from where to load JavaScript files from
    96  	Client   *rpc.Client  // RPC client to execute AquaChain requests through
    97  	Prompt   string       // Input prompt prefix string (defaults to DefaultPrompt)
    98  	Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
    99  	Printer  io.Writer    // Output writer to serialize any display strings to (defaults to os.Stdout)
   100  	Preload  []string     // Absolute paths to JavaScript files to preload
   101  }
   102  
   103  // Console is a JavaScript interpreted runtime environment. It is a fully fleged
   104  // JavaScript console attached to a running node via an external or in-process RPC
   105  // client.
   106  type Console struct {
   107  	client   *rpc.Client  // RPC client to execute AquaChain requests through
   108  	jsre     *jsre.JSRE   // JavaScript runtime environment running the interpreter
   109  	prompt   string       // Input prompt prefix string
   110  	prompter UserPrompter // Input prompter to allow interactive user feedback
   111  	histPath string       // Absolute path to the console scrollback history
   112  	history  []string     // Scroll history maintained by the console
   113  	printer  io.Writer    // Output writer to serialize any display strings to
   114  }
   115  
   116  func New(config Config) (*Console, error) {
   117  	// Handle unset config values gracefully
   118  	if config.Prompter == nil {
   119  		config.Prompter = Stdin
   120  	}
   121  	if config.Prompt == "" {
   122  		config.Prompt = DefaultPrompt
   123  	}
   124  	if config.Printer == nil {
   125  		config.Printer = colorable.NewColorableStdout()
   126  	}
   127  	// Initialize the console and return
   128  	console := &Console{
   129  		client:   config.Client,
   130  		jsre:     jsre.New(config.DocRoot, config.Printer),
   131  		prompt:   config.Prompt,
   132  		prompter: config.Prompter,
   133  		printer:  config.Printer,
   134  		histPath: filepath.Join(config.DataDir, HistoryFile),
   135  	}
   136  	if err := os.MkdirAll(config.DataDir, 0700); err != nil {
   137  		return nil, err
   138  	}
   139  	if err := console.init(config.Preload); err != nil {
   140  		return nil, err
   141  	}
   142  	return console, nil
   143  }
   144  
   145  // init retrieves the available APIs from the remote RPC provider and initializes
   146  // the console's JavaScript namespaces based on the exposed modules.
   147  func (c *Console) init(preload []string) error {
   148  	// Initialize the JavaScript <-> Go RPC bridge
   149  	bridge := newBridge(c.client, c.prompter, c.printer)
   150  	c.jsre.Set("jeth", struct{}{})
   151  
   152  	jethObj, _ := c.jsre.Get("jeth")
   153  	jethObj.Object().Set("send", bridge.Send)
   154  	jethObj.Object().Set("sendAsync", bridge.Send)
   155  
   156  	consoleObj, _ := c.jsre.Get("console")
   157  	consoleObj.Object().Set("log", c.consoleOutput)
   158  	consoleObj.Object().Set("error", c.consoleOutput)
   159  
   160  	// Load all the internal utility JavaScript libraries
   161  	if err := c.jsre.Compile("bignumber.js", jsre.BigNumber_JS); err != nil {
   162  		return fmt.Errorf("bignumber.js: %v", err)
   163  	}
   164  	if err := c.jsre.Compile("web3.js", jsre.Web3_JS); err != nil {
   165  		return fmt.Errorf("web3.js: %v", err)
   166  	}
   167  	if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil {
   168  		return fmt.Errorf("web3 require: %v", err)
   169  	}
   170  	if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil {
   171  		return fmt.Errorf("web3 provider: %v", err)
   172  	}
   173  	// Load the supported APIs into the JavaScript runtime environment
   174  	apis, err := c.client.SupportedModules()
   175  	if err != nil {
   176  		return fmt.Errorf("api modules: %v", err)
   177  	}
   178  	flatten := "var aqua = web3.aqua; var personal = web3.personal; "
   179  	for api := range apis {
   180  		if api == "web3" {
   181  			continue // manually mapped or ignore
   182  		}
   183  		if file, ok := web3ext.Modules[api]; ok {
   184  			// Load our extension for the module.
   185  			if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil {
   186  				return fmt.Errorf("%s.js: %v", api, err)
   187  			}
   188  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   189  		} else if obj, err := c.jsre.Run("web3." + api); err == nil && obj.IsObject() {
   190  			// Enable web3.js built-in extension if available.
   191  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   192  		}
   193  	}
   194  	if _, err = c.jsre.Run(flatten); err != nil {
   195  		return fmt.Errorf("namespace flattening: %v", err)
   196  	}
   197  	// Initialize the global name register (disabled for now)
   198  	//c.jsre.Run(`var GlobalRegistrar = aqua.contract(` + registrar.GlobalRegistrarAbi + `);   registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
   199  
   200  	// If the console is in interactive mode, instrument password related methods to query the user
   201  	if c.prompter != nil {
   202  		// Retrieve the account management object to instrument
   203  		personal, err := c.jsre.Get("personal")
   204  		if err != nil {
   205  			return err
   206  		}
   207  		// Override the openWallet, unlockAccount, newAccount and sign methods since
   208  		// these require user interaction. Assign these method in the Console the
   209  		// original web3 callbacks. These will be called by the jeth.* methods after
   210  		// they got the password from the user and send the original web3 request to
   211  		// the backend.
   212  		if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
   213  			if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
   214  				return fmt.Errorf("personal.openWallet: %v", err)
   215  			}
   216  			if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
   217  				return fmt.Errorf("personal.unlockAccount: %v", err)
   218  			}
   219  			if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
   220  				return fmt.Errorf("personal.newAccount: %v", err)
   221  			}
   222  			if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
   223  				return fmt.Errorf("personal.sign: %v", err)
   224  			}
   225  			obj.Set("openWallet", bridge.OpenWallet)
   226  			obj.Set("unlockAccount", bridge.UnlockAccount)
   227  			obj.Set("newAccount", bridge.NewAccount)
   228  			obj.Set("sign", bridge.Sign)
   229  		}
   230  	}
   231  	// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
   232  	admin, err := c.jsre.Get("admin")
   233  	if err != nil {
   234  		return err
   235  	}
   236  	if obj := admin.Object(); obj != nil { // make sure the admin api is enabled over the interface
   237  		obj.Set("sleepBlocks", bridge.SleepBlocks)
   238  		obj.Set("sleep", bridge.Sleep)
   239  		obj.Set("clearHistory", c.clearHistory)
   240  	}
   241  	// Preload any JavaScript files before starting the console
   242  	for _, path := range preload {
   243  		if err := c.jsre.Exec(path); err != nil {
   244  			failure := err.Error()
   245  			if ottoErr, ok := err.(*otto.Error); ok {
   246  				failure = ottoErr.String()
   247  			}
   248  			return fmt.Errorf("%s: %v", path, failure)
   249  		}
   250  	}
   251  	// Configure the console's input prompter for scrollback and tab completion
   252  	if c.prompter != nil {
   253  		if content, err := ioutil.ReadFile(c.histPath); err != nil {
   254  			c.prompter.SetHistory(nil)
   255  		} else {
   256  			c.history = strings.Split(string(content), "\n")
   257  			c.prompter.SetHistory(c.history)
   258  		}
   259  		c.prompter.SetWordCompleter(c.AutoCompleteInput)
   260  	}
   261  	return nil
   262  }
   263  
   264  func (c *Console) clearHistory() {
   265  	c.history = nil
   266  	c.prompter.ClearHistory()
   267  	if err := os.Remove(c.histPath); err != nil {
   268  		fmt.Fprintln(c.printer, "can't delete history file:", err)
   269  	} else {
   270  		fmt.Fprintln(c.printer, "history file deleted")
   271  	}
   272  }
   273  
   274  // consoleOutput is an override for the console.log and console.error methods to
   275  // stream the output into the configured output stream instead of stdout.
   276  func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
   277  	output := []string{}
   278  	for _, argument := range call.ArgumentList {
   279  		output = append(output, fmt.Sprintf("%v", argument))
   280  	}
   281  	fmt.Fprintln(c.printer, strings.Join(output, " "))
   282  	return otto.Value{}
   283  }
   284  
   285  // AutoCompleteInput is a pre-assembled word completer to be used by the user
   286  // input prompter to provide hints to the user about the methods available.
   287  func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) {
   288  	// No completions can be provided for empty inputs
   289  	if len(line) == 0 || pos == 0 {
   290  		return "", c.jsre.CompleteKeywords(""), ""
   291  	}
   292  	// Chunck data to relevant part for autocompletion
   293  	// E.g. in case of nested lines aqua.getBalance(aqua.coinb<tab><tab>
   294  	start := pos - 1
   295  	for ; start > 0; start-- {
   296  		// Skip all methods and namespaces (i.e. including the dot)
   297  		if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') {
   298  			continue
   299  		}
   300  		// Handle web3 in a special way (i.e. other numbers aren't auto completed)
   301  		if start >= 3 && line[start-3:start] == "web3" {
   302  			start -= 3
   303  			continue
   304  		}
   305  		// We've hit an unexpected character, autocomplete form here
   306  		start++
   307  		break
   308  	}
   309  	return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
   310  }
   311  
   312  // Welcome show summary of current AquaChain instance and some metadata about the
   313  // console's available modules.
   314  func (c *Console) Welcome() {
   315  	// friendly balance
   316  	c.jsre.Run(`
   317  function pending() {
   318  			var totalBal = 0;
   319  			for (var acctNum in aqua.accounts) {
   320  								var acct = aqua.accounts[acctNum];
   321  								var acctBal = aqua.balance(acct, 'pending');
   322  								totalBal += parseFloat(acctBal);
   323  								console.log("  aqua.accounts[" + acctNum + "]: \t" + acct + " \tbalance: " + acctBal + " AQUA");
   324  						}
   325  			console.log("Pending balance: " + totalBal + " AQUA");
   326  			return totalBal;
   327  };
   328  function balance() {
   329  			var totalBal = 0;
   330  			for (var acctNum in aqua.accounts) {
   331  								var acct = aqua.accounts[acctNum];
   332  								var acctBal = aqua.balance(acct, 'latest');
   333  								totalBal += parseFloat(acctBal);
   334  								console.log("  aqua.accounts[" + acctNum + "]: \t" + acct + " \tbalance: " + acctBal + " AQUA");
   335  						}
   336  			console.log("  Total balance: " + totalBal + " AQUA");
   337  			return totalBal;
   338  };
   339  	`)
   340  
   341  	// Print some generic AquaChain metadata
   342  	fmt.Fprintf(c.printer, "\nWelcome to the AquaChain JavaScript console!\n")
   343  	fmt.Fprintf(c.printer, logo)
   344  
   345  	c.jsre.Run(`
   346  		console.log("instance: " + web3.version.node);
   347  		console.log("coinbase: " + aqua.coinbase);
   348  		console.log("at block: " + aqua.blockNumber + " (" + new Date(1000 * aqua.getBlock(aqua.blockNumber).timestamp) + ")");
   349  		console.log("    algo: " + aqua.getBlock(aqua.blockNumber).version);
   350  		console.log(" datadir: " + admin.datadir);
   351  	`)
   352  
   353  	// List all the supported modules for the user to call
   354  	if apis, err := c.client.SupportedModules(); err == nil {
   355  		modules := make([]string, 0, len(apis))
   356  		for api, version := range apis {
   357  			if api == "eth" {
   358  				continue
   359  			}
   360  			modules = append(modules, fmt.Sprintf("%s:%s", api, version))
   361  		}
   362  		sort.Strings(modules)
   363  		fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " "))
   364  	}
   365  	fmt.Fprintln(c.printer)
   366  }
   367  
   368  // Evaluate executes code and pretty prints the result to the specified output
   369  // stream.
   370  func (c *Console) Evaluate(statement string) error {
   371  	defer func() {
   372  		if r := recover(); r != nil {
   373  			fmt.Fprintf(c.printer, "[native] error: %v\n", r)
   374  		}
   375  	}()
   376  	return c.jsre.Evaluate(statement, c.printer)
   377  }
   378  
   379  // Interactive starts an interactive user session, where input is propted from
   380  // the configured user prompter.
   381  func (c *Console) Interactive() {
   382  	var (
   383  		prompt    = c.prompt          // Current prompt line (used for multi-line inputs)
   384  		indents   = 0                 // Current number of input indents (used for multi-line inputs)
   385  		input     = ""                // Current user input
   386  		scheduler = make(chan string) // Channel to send the next prompt on and receive the input
   387  	)
   388  	// Start a goroutine to listen for promt requests and send back inputs
   389  	go func() {
   390  		for {
   391  			// Read the next user input
   392  			line, err := c.prompter.PromptInput(<-scheduler)
   393  			if err != nil {
   394  				// In case of an error, either clear the prompt or fail
   395  				if err == liner.ErrPromptAborted { // ctrl-C
   396  					prompt, indents, input = c.prompt, 0, ""
   397  					scheduler <- ""
   398  					continue
   399  				}
   400  				close(scheduler)
   401  				return
   402  			}
   403  			// User input retrieved, send for interpretation and loop
   404  			scheduler <- line
   405  		}
   406  	}()
   407  	// Monitor Ctrl-C too in case the input is empty and we need to bail
   408  	abort := make(chan os.Signal, 1)
   409  	signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM)
   410  
   411  	// Start sending prompts to the user and reading back inputs
   412  	for {
   413  		// Send the next prompt, triggering an input read and process the result
   414  		scheduler <- prompt
   415  		select {
   416  		case <-abort:
   417  			// User forcefully quite the console
   418  			fmt.Fprintln(c.printer, "caught interrupt, exiting")
   419  			return
   420  
   421  		case line, ok := <-scheduler:
   422  			// User input was returned by the prompter, handle special cases
   423  			if !ok || (indents <= 0 && exit.MatchString(line)) {
   424  				return
   425  			}
   426  			if onlyWhitespace.MatchString(line) {
   427  				continue
   428  			}
   429  			if !ok || (indents <= 0 && help.MatchString(line)) {
   430  				fmt.Fprintln(c.printer, helpText)
   431  				continue
   432  			}
   433  
   434  			// command: 'send'
   435  			if sendline.MatchString(line) {
   436  				err := handleSend(c)
   437  				if err != nil {
   438  					fmt.Fprintln(c.printer, "Error:", err)
   439  					continue
   440  				}
   441  				fmt.Fprintln(c.printer, "TX Sent!")
   442  				continue
   443  			}
   444  			// Append the line to the input and check for multi-line interpretation
   445  			input += line + "\n"
   446  
   447  			indents = countIndents(input)
   448  			if indents <= 0 {
   449  				prompt = c.prompt
   450  			} else {
   451  				prompt = strings.Repeat(".", indents*3) + " "
   452  			}
   453  			// If all the needed lines are present, save the command and run
   454  			if indents <= 0 {
   455  				if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) {
   456  					if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] {
   457  						c.history = append(c.history, command)
   458  						if c.prompter != nil {
   459  							c.prompter.AppendHistory(command)
   460  						}
   461  					}
   462  				}
   463  				c.Evaluate(input)
   464  				input = ""
   465  			}
   466  		}
   467  	}
   468  }
   469  
   470  func handleSend(c *Console) error {
   471  
   472  	cont, err := c.prompter.PromptConfirm("You are about to create a transaction from your Aquabase. Right?")
   473  	if err != nil {
   474  		return fmt.Errorf("input error: %v", err)
   475  	}
   476  	if !cont {
   477  		return fmt.Errorf("transaction canceled")
   478  	}
   479  	_, err = c.jsre.Run(`personal.unlockAccount(aqua.coinbase);`)
   480  	if err != nil {
   481  		return fmt.Errorf("error: %v", err)
   482  	}
   483  
   484  	amount, err := c.prompter.PromptInput("How much AQUA to send? For example: 0.1: ")
   485  	if err != nil {
   486  		return fmt.Errorf("input error: %v", err)
   487  	}
   488  
   489  	fmt.Fprintf(c.printer, "Send %q to whom?", amount)
   490  
   491  	destination, err := c.prompter.PromptInput("Where to send? With 0x prefix: ")
   492  	if err != nil {
   493  		return fmt.Errorf("input error: %v", err)
   494  	}
   495  
   496  	cont, err = c.prompter.PromptConfirm(fmt.Sprintf("Send %s to %s?", amount, destination))
   497  	if err != nil {
   498  		return fmt.Errorf("input error: %v", err)
   499  	}
   500  	if !cont {
   501  		return fmt.Errorf("transaction canceled")
   502  	}
   503  
   504  	fmt.Fprintln(c.printer, "Running:\n"+`aqua.sendTransaction({from: aqua.coinbase, to: '`+destination+`', value: web3.toWei(`+amount+`,'aqua')});`)
   505  	cont, err = c.prompter.PromptConfirm(fmt.Sprintf("REALLY Send %s to %s?", amount, destination))
   506  	if err != nil {
   507  		return fmt.Errorf("input error: %v", err)
   508  	}
   509  	if !cont {
   510  		return fmt.Errorf("transaction canceled")
   511  	}
   512  	if !strings.HasPrefix(destination, "0x") && !strings.HasPrefix(destination, "aqua.accounts[") {
   513  		return fmt.Errorf("does not have 0x prefix")
   514  	}
   515  	_, err = c.jsre.Run(`aqua.sendTransaction({from: aqua.coinbase, to: '` + destination + `', value: web3.toWei(` + amount + `,'aqua')});`)
   516  	if err != nil {
   517  		return fmt.Errorf("error: %v", err)
   518  	}
   519  
   520  	return nil
   521  }
   522  
   523  // countIndents returns the number of identations for the given input.
   524  // In case of invalid input such as var a = } the result can be negative.
   525  func countIndents(input string) int {
   526  	var (
   527  		indents     = 0
   528  		inString    = false
   529  		strOpenChar = ' '   // keep track of the string open char to allow var str = "I'm ....";
   530  		charEscaped = false // keep track if the previous char was the '\' char, allow var str = "abc\"def";
   531  	)
   532  
   533  	for _, c := range input {
   534  		switch c {
   535  		case '\\':
   536  			// indicate next char as escaped when in string and previous char isn't escaping this backslash
   537  			if !charEscaped && inString {
   538  				charEscaped = true
   539  			}
   540  		case '\'', '"':
   541  			if inString && !charEscaped && strOpenChar == c { // end string
   542  				inString = false
   543  			} else if !inString && !charEscaped { // begin string
   544  				inString = true
   545  				strOpenChar = c
   546  			}
   547  			charEscaped = false
   548  		case '{', '(':
   549  			if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting
   550  				indents++
   551  			}
   552  			charEscaped = false
   553  		case '}', ')':
   554  			if !inString {
   555  				indents--
   556  			}
   557  			charEscaped = false
   558  		default:
   559  			charEscaped = false
   560  		}
   561  	}
   562  
   563  	return indents
   564  }
   565  
   566  // Execute runs the JavaScript file specified as the argument.
   567  func (c *Console) Execute(path string) error {
   568  	return c.jsre.Exec(path)
   569  }
   570  
   571  // Stop cleans up the console and terminates the runtime environment.
   572  func (c *Console) Stop(graceful bool) error {
   573  	if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
   574  		return err
   575  	}
   576  	if err := os.Chmod(c.histPath, 0600); err != nil { // Force 0600, even if it was different previously
   577  		return err
   578  	}
   579  	c.jsre.Stop(graceful)
   580  	return nil
   581  }