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