github.com/yinchengtsinghua/golang-Eos-dpos-Ethereum@v0.0.0-20190121132951-92cc4225ed8e/console/console.go (about)

     1  
     2  //此源码被清华学神尹成大魔王专业翻译分析并修改
     3  //尹成QQ77025077
     4  //尹成微信18510341407
     5  //尹成所在QQ群721929980
     6  //尹成邮箱 yinc13@mails.tsinghua.edu.cn
     7  //尹成毕业于清华大学,微软区块链领域全球最有价值专家
     8  //https://mvp.microsoft.com/zh-cn/PublicProfile/4033620
     9  //版权所有2016 Go Ethereum作者
    10  //此文件是Go以太坊库的一部分。
    11  //
    12  //Go-Ethereum库是免费软件:您可以重新分发它和/或修改
    13  //根据GNU发布的较低通用公共许可证的条款
    14  //自由软件基金会,或者许可证的第3版,或者
    15  //(由您选择)任何更高版本。
    16  //
    17  //Go以太坊图书馆的发行目的是希望它会有用,
    18  //但没有任何保证;甚至没有
    19  //适销性或特定用途的适用性。见
    20  //GNU较低的通用公共许可证,了解更多详细信息。
    21  //
    22  //你应该收到一份GNU较低级别的公共许可证副本
    23  //以及Go以太坊图书馆。如果没有,请参见<http://www.gnu.org/licenses/>。
    24  
    25  package console
    26  
    27  import (
    28  	"fmt"
    29  	"io"
    30  	"io/ioutil"
    31  	"os"
    32  	"os/signal"
    33  	"path/filepath"
    34  	"regexp"
    35  	"sort"
    36  	"strings"
    37  	"syscall"
    38  
    39  	"github.com/ethereum/go-ethereum/internal/jsre"
    40  	"github.com/ethereum/go-ethereum/internal/web3ext"
    41  	"github.com/ethereum/go-ethereum/rpc"
    42  	"github.com/mattn/go-colorable"
    43  	"github.com/peterh/liner"
    44  	"github.com/robertkrimen/otto"
    45  )
    46  
    47  var (
    48  	passwordRegexp = regexp.MustCompile(`personal.[nus]`)
    49  	onlyWhitespace = regexp.MustCompile(`^\s*$`)
    50  	exit           = regexp.MustCompile(`^\s*exit\s*;*\s*$`)
    51  )
    52  
    53  //HistoryFile是数据目录中用于存储输入回滚的文件。
    54  const HistoryFile = "history"
    55  
    56  //默认提示是用于用户输入查询的默认提示行前缀。
    57  const DefaultPrompt = "> "
    58  
    59  //config是配置的集合,用于微调
    60  //
    61  type Config struct {
    62  DataDir  string       //存储控制台历史记录的数据目录
    63  DocRoot  string       //从何处加载javascript文件的文件系统路径
    64  Client   *rpc.Client  //通过RPC客户端执行以太坊请求
    65  Prompt   string       //输入提示前缀字符串(默认为默认提示)
    66  Prompter UserPrompter //输入提示以允许交互式用户反馈(默认为TerminalPrompter)
    67  Printer  io.Writer    //要序列化任何显示字符串的输出编写器(默认为os.stdout)
    68  Preload  []string     //要预加载的javascript文件的绝对路径
    69  }
    70  
    71  //控制台是一个JavaScript解释的运行时环境。它是一个成熟的
    72  //通过外部或进程内RPC连接到运行节点的javascript控制台
    73  //客户端。
    74  type Console struct {
    75  client   *rpc.Client  //通过RPC客户端执行以太坊请求
    76  jsre     *jsre.JSRE   //运行解释器的javascript运行时环境
    77  prompt   string       //输入提示前缀字符串
    78  prompter UserPrompter //输入提示以允许交互式用户反馈
    79  histPath string       //控制台回滚历史记录的绝对路径
    80  history  []string     //控制台维护的滚动历史记录
    81  printer  io.Writer    //要将任何显示字符串序列化到的输出编写器
    82  }
    83  
    84  //new初始化javascript解释的运行时环境并设置默认值
    85  //使用config结构。
    86  func New(config Config) (*Console, error) {
    87  //优雅地处理未设置的配置值
    88  	if config.Prompter == nil {
    89  		config.Prompter = Stdin
    90  	}
    91  	if config.Prompt == "" {
    92  		config.Prompt = DefaultPrompt
    93  	}
    94  	if config.Printer == nil {
    95  		config.Printer = colorable.NewColorableStdout()
    96  	}
    97  //初始化控制台并返回
    98  	console := &Console{
    99  		client:   config.Client,
   100  		jsre:     jsre.New(config.DocRoot, config.Printer),
   101  		prompt:   config.Prompt,
   102  		prompter: config.Prompter,
   103  		printer:  config.Printer,
   104  		histPath: filepath.Join(config.DataDir, HistoryFile),
   105  	}
   106  	if err := os.MkdirAll(config.DataDir, 0700); err != nil {
   107  		return nil, err
   108  	}
   109  	if err := console.init(config.Preload); err != nil {
   110  		return nil, err
   111  	}
   112  	return console, nil
   113  }
   114  
   115  //init从远程RPC提供程序检索可用的API并初始化
   116  //控制台的javascript名称空间基于公开的模块。
   117  func (c *Console) init(preload []string) error {
   118  //初始化javascript<->go-rpc桥
   119  	bridge := newBridge(c.client, c.prompter, c.printer)
   120  	c.jsre.Set("jeth", struct{}{})
   121  
   122  	jethObj, _ := c.jsre.Get("jeth")
   123  	jethObj.Object().Set("send", bridge.Send)
   124  	jethObj.Object().Set("sendAsync", bridge.Send)
   125  
   126  	consoleObj, _ := c.jsre.Get("console")
   127  	consoleObj.Object().Set("log", c.consoleOutput)
   128  	consoleObj.Object().Set("error", c.consoleOutput)
   129  
   130  //加载所有内部实用程序javascript库
   131  	if err := c.jsre.Compile("bignumber.js", jsre.BigNumber_JS); err != nil {
   132  		return fmt.Errorf("bignumber.js: %v", err)
   133  	}
   134  	if err := c.jsre.Compile("web3.js", jsre.Web3_JS); err != nil {
   135  		return fmt.Errorf("web3.js: %v", err)
   136  	}
   137  	if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil {
   138  		return fmt.Errorf("web3 require: %v", err)
   139  	}
   140  	if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil {
   141  		return fmt.Errorf("web3 provider: %v", err)
   142  	}
   143  //将支持的API加载到JavaScript运行时环境中
   144  	apis, err := c.client.SupportedModules()
   145  	if err != nil {
   146  		return fmt.Errorf("api modules: %v", err)
   147  	}
   148  	flatten := "var eth = web3.eth; var personal = web3.personal; "
   149  	for api := range apis {
   150  		if api == "web3" {
   151  continue //手动映射或忽略
   152  		}
   153  		if file, ok := web3ext.Modules[api]; ok {
   154  //加载模块的扩展。
   155  			if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil {
   156  				return fmt.Errorf("%s.js: %v", api, err)
   157  			}
   158  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   159  		} else if obj, err := c.jsre.Run("web3." + api); err == nil && obj.IsObject() {
   160  //启用web3.js内置扩展(如果可用)。
   161  			flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
   162  		}
   163  	}
   164  	if _, err = c.jsre.Run(flatten); err != nil {
   165  		return fmt.Errorf("namespace flattening: %v", err)
   166  	}
   167  //初始化全局名称寄存器(暂时禁用)
   168  //c.jsre.run(`var globalregistrar=eth.contract(`+registrar.globalregistrabi+`);registrar=globalregistrar.at(`+registrar.globalregistraradr+`);`)
   169  
   170  //
   171  	if c.prompter != nil {
   172  //检索要检测的帐户管理对象
   173  		personal, err := c.jsre.Get("personal")
   174  		if err != nil {
   175  			return err
   176  		}
   177  //重写openwallet、unlockaccount、newaccount和sign方法,因为
   178  //这些需要用户交互。在控制台中分配这些方法
   179  //原始Web3回调。这些将由jeth.*方法在
   180  //他们从用户那里获得了密码,并将原始Web3请求发送到
   181  //后端。
   182  if obj := personal.Object(); obj != nil { //确保通过接口启用个人API
   183  			if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
   184  				return fmt.Errorf("personal.openWallet: %v", err)
   185  			}
   186  			if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
   187  				return fmt.Errorf("personal.unlockAccount: %v", err)
   188  			}
   189  			if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
   190  				return fmt.Errorf("personal.newAccount: %v", err)
   191  			}
   192  			if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
   193  				return fmt.Errorf("personal.sign: %v", err)
   194  			}
   195  			obj.Set("openWallet", bridge.OpenWallet)
   196  			obj.Set("unlockAccount", bridge.UnlockAccount)
   197  			obj.Set("newAccount", bridge.NewAccount)
   198  			obj.Set("sign", bridge.Sign)
   199  		}
   200  	}
   201  //admin.sleep和admin.sleepBlocks由控制台提供,而不是由rpc层提供。
   202  	admin, err := c.jsre.Get("admin")
   203  	if err != nil {
   204  		return err
   205  	}
   206  if obj := admin.Object(); obj != nil { //确保通过接口启用管理API
   207  		obj.Set("sleepBlocks", bridge.SleepBlocks)
   208  		obj.Set("sleep", bridge.Sleep)
   209  		obj.Set("clearHistory", c.clearHistory)
   210  	}
   211  //在启动控制台之前预加载任何javascript文件
   212  	for _, path := range preload {
   213  		if err := c.jsre.Exec(path); err != nil {
   214  			failure := err.Error()
   215  			if ottoErr, ok := err.(*otto.Error); ok {
   216  				failure = ottoErr.String()
   217  			}
   218  			return fmt.Errorf("%s: %v", path, failure)
   219  		}
   220  	}
   221  //配置控制台的输入Prompter以完成回滚和选项卡。
   222  	if c.prompter != nil {
   223  		if content, err := ioutil.ReadFile(c.histPath); err != nil {
   224  			c.prompter.SetHistory(nil)
   225  		} else {
   226  			c.history = strings.Split(string(content), "\n")
   227  			c.prompter.SetHistory(c.history)
   228  		}
   229  		c.prompter.SetWordCompleter(c.AutoCompleteInput)
   230  	}
   231  	return nil
   232  }
   233  
   234  func (c *Console) clearHistory() {
   235  	c.history = nil
   236  	c.prompter.ClearHistory()
   237  	if err := os.Remove(c.histPath); err != nil {
   238  		fmt.Fprintln(c.printer, "can't delete history file:", err)
   239  	} else {
   240  		fmt.Fprintln(c.printer, "history file deleted")
   241  	}
   242  }
   243  
   244  //consoleoutput是console.log和console.error方法的重写,用于
   245  //将输出流传输到配置的输出流,而不是stdout。
   246  func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
   247  	output := []string{}
   248  	for _, argument := range call.ArgumentList {
   249  		output = append(output, fmt.Sprintf("%v", argument))
   250  	}
   251  	fmt.Fprintln(c.printer, strings.Join(output, " "))
   252  	return otto.Value{}
   253  }
   254  
   255  //autocompleteinput是一个预先组装好的单词完成器,供用户使用。
   256  //输入prompter以向用户提供有关可用方法的提示。
   257  func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) {
   258  //不能为空输入提供完成
   259  	if len(line) == 0 || pos == 0 {
   260  		return "", nil, ""
   261  	}
   262  //将数据分块到相关部分进行自动完成
   263  //例如,如果是嵌套行eth.getbalance(eth.coinb<tab><tab>
   264  	start := pos - 1
   265  	for ; start > 0; start-- {
   266  //跳过所有方法和名称空间(即包括点)
   267  		if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') {
   268  			continue
   269  		}
   270  //以特殊方式处理Web3(即其他数字不会自动完成)
   271  		if start >= 3 && line[start-3:start] == "web3" {
   272  			start -= 3
   273  			continue
   274  		}
   275  //我们碰到了一个意想不到的字符,这里是自动完成表单
   276  		start++
   277  		break
   278  	}
   279  	return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
   280  }
   281  
   282  //欢迎显示当前geth实例的摘要和有关
   283  //控制台的可用模块。
   284  func (c *Console) Welcome() {
   285  //打印一些通用的geth元数据
   286  	fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n")
   287  	c.jsre.Run(`
   288  		console.log("instance: " + web3.version.node);
   289  		console.log("coinbase: " + eth.coinbase);
   290  		console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")");
   291  		console.log(" datadir: " + admin.datadir);
   292  	`)
   293  //列出用户可以调用的所有支持模块
   294  	if apis, err := c.client.SupportedModules(); err == nil {
   295  		modules := make([]string, 0, len(apis))
   296  		for api, version := range apis {
   297  			modules = append(modules, fmt.Sprintf("%s:%s", api, version))
   298  		}
   299  		sort.Strings(modules)
   300  		fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " "))
   301  	}
   302  	fmt.Fprintln(c.printer)
   303  }
   304  
   305  //evaluate执行代码并将结果漂亮地打印到指定的输出
   306  //溪流。
   307  func (c *Console) Evaluate(statement string) error {
   308  	defer func() {
   309  		if r := recover(); r != nil {
   310  			fmt.Fprintf(c.printer, "[native] error: %v\n", r)
   311  		}
   312  	}()
   313  	return c.jsre.Evaluate(statement, c.printer)
   314  }
   315  
   316  //Interactive启动一个交互式用户会话,在该会话中输入来自
   317  //
   318  func (c *Console) Interactive() {
   319  	var (
   320  prompt    = c.prompt          //当前提示行(用于多行输入)
   321  indents   = 0                 //当前输入缩进数(用于多行输入)
   322  input     = ""                //当前用户输入
   323  scheduler = make(chan string) //发送下一个提示并接收输入的通道
   324  	)
   325  //启动Goroutine以侦听提示请求并返回输入
   326  	go func() {
   327  		for {
   328  //读取下一个用户输入
   329  			line, err := c.prompter.PromptInput(<-scheduler)
   330  			if err != nil {
   331  //如果出现错误,请清除提示或失败。
   332  if err == liner.ErrPromptAborted { //Ctrl—C
   333  					prompt, indents, input = c.prompt, 0, ""
   334  					scheduler <- ""
   335  					continue
   336  				}
   337  				close(scheduler)
   338  				return
   339  			}
   340  //检索用户输入、发送解释和循环
   341  			scheduler <- line
   342  		}
   343  	}()
   344  //如果输入为空,我们需要退出时也监视ctrl-c
   345  	abort := make(chan os.Signal, 1)
   346  	signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM)
   347  
   348  //开始向用户发送提示并读取输入
   349  	for {
   350  //发送下一个提示,触发输入读取并处理结果
   351  		scheduler <- prompt
   352  		select {
   353  		case <-abort:
   354  //用户强制相当于控制台
   355  			fmt.Fprintln(c.printer, "caught interrupt, exiting")
   356  			return
   357  
   358  		case line, ok := <-scheduler:
   359  //提示器返回用户输入,处理特殊情况
   360  			if !ok || (indents <= 0 && exit.MatchString(line)) {
   361  				return
   362  			}
   363  			if onlyWhitespace.MatchString(line) {
   364  				continue
   365  			}
   366  //将行附加到输入并检查多行解释
   367  			input += line + "\n"
   368  
   369  			indents = countIndents(input)
   370  			if indents <= 0 {
   371  				prompt = c.prompt
   372  			} else {
   373  				prompt = strings.Repeat(".", indents*3) + " "
   374  			}
   375  //如果所有需要的行都存在,请保存命令并运行
   376  			if indents <= 0 {
   377  				if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) {
   378  					if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] {
   379  						c.history = append(c.history, command)
   380  						if c.prompter != nil {
   381  							c.prompter.AppendHistory(command)
   382  						}
   383  					}
   384  				}
   385  				c.Evaluate(input)
   386  				input = ""
   387  			}
   388  		}
   389  	}
   390  }
   391  
   392  //countindents返回给定输入的标识数。
   393  //如果输入无效,例如var a=,结果可以是负数。
   394  func countIndents(input string) int {
   395  	var (
   396  		indents     = 0
   397  		inString    = false
   398  strOpenChar = ' '   //跟踪字符串open char以允许var str=“i'm…..”;
   399  charEscaped = false //如果前一个字符是'\'字符,则允许var str=“abc\”def;
   400  	)
   401  
   402  	for _, c := range input {
   403  		switch c {
   404  		case '\\':
   405  //在字符串中指示下一个字符为转义字符,而上一个字符不转义此反斜杠
   406  			if !charEscaped && inString {
   407  				charEscaped = true
   408  			}
   409  		case '\'', '"':
   410  if inString && !charEscaped && strOpenChar == c { //结束字符串
   411  				inString = false
   412  } else if !inString && !charEscaped { //开始字符串
   413  				inString = true
   414  				strOpenChar = c
   415  			}
   416  			charEscaped = false
   417  		case '{', '(':
   418  if !inString { //在字符串中忽略括号,允许var str=“a”;而不缩进
   419  				indents++
   420  			}
   421  			charEscaped = false
   422  		case '}', ')':
   423  			if !inString {
   424  				indents--
   425  			}
   426  			charEscaped = false
   427  		default:
   428  			charEscaped = false
   429  		}
   430  	}
   431  
   432  	return indents
   433  }
   434  
   435  //执行运行指定为参数的javascript文件。
   436  func (c *Console) Execute(path string) error {
   437  	return c.jsre.Exec(path)
   438  }
   439  
   440  //停止清理控制台并终止运行时环境。
   441  func (c *Console) Stop(graceful bool) error {
   442  	if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
   443  		return err
   444  	}
   445  if err := os.Chmod(c.histPath, 0600); err != nil { //强制0600,即使以前不同
   446  		return err
   447  	}
   448  	c.jsre.Stop(graceful)
   449  	return nil
   450  }