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 }