github.com/Inphi/go-ethereum@v1.9.7/console/bridge.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package console 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "io" 23 "strings" 24 "time" 25 26 "github.com/ethereum/go-ethereum/accounts/scwallet" 27 "github.com/ethereum/go-ethereum/accounts/usbwallet" 28 "github.com/ethereum/go-ethereum/log" 29 "github.com/ethereum/go-ethereum/rpc" 30 "github.com/robertkrimen/otto" 31 ) 32 33 // bridge is a collection of JavaScript utility methods to bride the .js runtime 34 // environment and the Go RPC connection backing the remote method calls. 35 type bridge struct { 36 client *rpc.Client // RPC client to execute Ethereum requests through 37 prompter UserPrompter // Input prompter to allow interactive user feedback 38 printer io.Writer // Output writer to serialize any display strings to 39 } 40 41 // newBridge creates a new JavaScript wrapper around an RPC client. 42 func newBridge(client *rpc.Client, prompter UserPrompter, printer io.Writer) *bridge { 43 return &bridge{ 44 client: client, 45 prompter: prompter, 46 printer: printer, 47 } 48 } 49 50 // NewAccount is a wrapper around the personal.newAccount RPC method that uses a 51 // non-echoing password prompt to acquire the passphrase and executes the original 52 // RPC method (saved in jeth.newAccount) with it to actually execute the RPC call. 53 func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) { 54 var ( 55 password string 56 confirm string 57 err error 58 ) 59 switch { 60 // No password was specified, prompt the user for it 61 case len(call.ArgumentList) == 0: 62 if password, err = b.prompter.PromptPassword("Password: "); err != nil { 63 throwJSException(err.Error()) 64 } 65 if confirm, err = b.prompter.PromptPassword("Repeat password: "); err != nil { 66 throwJSException(err.Error()) 67 } 68 if password != confirm { 69 throwJSException("passwords don't match!") 70 } 71 72 // A single string password was specified, use that 73 case len(call.ArgumentList) == 1 && call.Argument(0).IsString(): 74 password, _ = call.Argument(0).ToString() 75 76 // Otherwise fail with some error 77 default: 78 throwJSException("expected 0 or 1 string argument") 79 } 80 // Password acquired, execute the call and return 81 ret, err := call.Otto.Call("jeth.newAccount", nil, password) 82 if err != nil { 83 throwJSException(err.Error()) 84 } 85 return ret 86 } 87 88 // OpenWallet is a wrapper around personal.openWallet which can interpret and 89 // react to certain error messages, such as the Trezor PIN matrix request. 90 func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) { 91 // Make sure we have a wallet specified to open 92 if !call.Argument(0).IsString() { 93 throwJSException("first argument must be the wallet URL to open") 94 } 95 wallet := call.Argument(0) 96 97 var passwd otto.Value 98 if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { 99 passwd, _ = otto.ToValue("") 100 } else { 101 passwd = call.Argument(1) 102 } 103 // Open the wallet and return if successful in itself 104 val, err := call.Otto.Call("jeth.openWallet", nil, wallet, passwd) 105 if err == nil { 106 return val 107 } 108 109 // Wallet open failed, report error unless it's a PIN or PUK entry 110 switch { 111 case strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()): 112 val, err = b.readPinAndReopenWallet(call) 113 if err == nil { 114 return val 115 } 116 val, err = b.readPassphraseAndReopenWallet(call) 117 if err != nil { 118 throwJSException(err.Error()) 119 } 120 121 case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()): 122 // PUK input requested, fetch from the user and call open again 123 if input, err := b.prompter.PromptPassword("Please enter the pairing password: "); err != nil { 124 throwJSException(err.Error()) 125 } else { 126 passwd, _ = otto.ToValue(input) 127 } 128 if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { 129 if !strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()) { 130 throwJSException(err.Error()) 131 } else { 132 // PIN input requested, fetch from the user and call open again 133 if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil { 134 throwJSException(err.Error()) 135 } else { 136 passwd, _ = otto.ToValue(input) 137 } 138 if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { 139 throwJSException(err.Error()) 140 } 141 } 142 } 143 144 case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()): 145 // PIN unblock requested, fetch PUK and new PIN from the user 146 var pukpin string 147 if input, err := b.prompter.PromptPassword("Please enter current PUK: "); err != nil { 148 throwJSException(err.Error()) 149 } else { 150 pukpin = input 151 } 152 if input, err := b.prompter.PromptPassword("Please enter new PIN: "); err != nil { 153 throwJSException(err.Error()) 154 } else { 155 pukpin += input 156 } 157 passwd, _ = otto.ToValue(pukpin) 158 if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { 159 throwJSException(err.Error()) 160 } 161 162 case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()): 163 // PIN input requested, fetch from the user and call open again 164 if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil { 165 throwJSException(err.Error()) 166 } else { 167 passwd, _ = otto.ToValue(input) 168 } 169 if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { 170 throwJSException(err.Error()) 171 } 172 173 default: 174 // Unknown error occurred, drop to the user 175 throwJSException(err.Error()) 176 } 177 return val 178 } 179 180 func (b *bridge) readPassphraseAndReopenWallet(call otto.FunctionCall) (otto.Value, error) { 181 var passwd otto.Value 182 wallet := call.Argument(0) 183 if input, err := b.prompter.PromptPassword("Please enter your password: "); err != nil { 184 throwJSException(err.Error()) 185 } else { 186 passwd, _ = otto.ToValue(input) 187 } 188 return call.Otto.Call("jeth.openWallet", nil, wallet, passwd) 189 } 190 191 func (b *bridge) readPinAndReopenWallet(call otto.FunctionCall) (otto.Value, error) { 192 var passwd otto.Value 193 wallet := call.Argument(0) 194 // Trezor PIN matrix input requested, display the matrix to the user and fetch the data 195 fmt.Fprintf(b.printer, "Look at the device for number positions\n\n") 196 fmt.Fprintf(b.printer, "7 | 8 | 9\n") 197 fmt.Fprintf(b.printer, "--+---+--\n") 198 fmt.Fprintf(b.printer, "4 | 5 | 6\n") 199 fmt.Fprintf(b.printer, "--+---+--\n") 200 fmt.Fprintf(b.printer, "1 | 2 | 3\n\n") 201 202 if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil { 203 throwJSException(err.Error()) 204 } else { 205 passwd, _ = otto.ToValue(input) 206 } 207 return call.Otto.Call("jeth.openWallet", nil, wallet, passwd) 208 } 209 210 // UnlockAccount is a wrapper around the personal.unlockAccount RPC method that 211 // uses a non-echoing password prompt to acquire the passphrase and executes the 212 // original RPC method (saved in jeth.unlockAccount) with it to actually execute 213 // the RPC call. 214 func (b *bridge) UnlockAccount(call otto.FunctionCall) (response otto.Value) { 215 // Make sure we have an account specified to unlock 216 if !call.Argument(0).IsString() { 217 throwJSException("first argument must be the account to unlock") 218 } 219 account := call.Argument(0) 220 221 // If password is not given or is the null value, prompt the user for it 222 var passwd otto.Value 223 224 if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { 225 fmt.Fprintf(b.printer, "Unlock account %s\n", account) 226 if input, err := b.prompter.PromptPassword("Password: "); err != nil { 227 throwJSException(err.Error()) 228 } else { 229 passwd, _ = otto.ToValue(input) 230 } 231 } else { 232 if !call.Argument(1).IsString() { 233 throwJSException("password must be a string") 234 } 235 passwd = call.Argument(1) 236 } 237 // Third argument is the duration how long the account must be unlocked. 238 duration := otto.NullValue() 239 if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() { 240 if !call.Argument(2).IsNumber() { 241 throwJSException("unlock duration must be a number") 242 } 243 duration = call.Argument(2) 244 } 245 // Send the request to the backend and return 246 val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration) 247 if err != nil { 248 throwJSException(err.Error()) 249 } 250 return val 251 } 252 253 // Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password 254 // prompt to acquire the passphrase and executes the original RPC method (saved in 255 // jeth.sign) with it to actually execute the RPC call. 256 func (b *bridge) Sign(call otto.FunctionCall) (response otto.Value) { 257 var ( 258 message = call.Argument(0) 259 account = call.Argument(1) 260 passwd = call.Argument(2) 261 ) 262 263 if !message.IsString() { 264 throwJSException("first argument must be the message to sign") 265 } 266 if !account.IsString() { 267 throwJSException("second argument must be the account to sign with") 268 } 269 270 // if the password is not given or null ask the user and ensure password is a string 271 if passwd.IsUndefined() || passwd.IsNull() { 272 fmt.Fprintf(b.printer, "Give password for account %s\n", account) 273 if input, err := b.prompter.PromptPassword("Password: "); err != nil { 274 throwJSException(err.Error()) 275 } else { 276 passwd, _ = otto.ToValue(input) 277 } 278 } 279 if !passwd.IsString() { 280 throwJSException("third argument must be the password to unlock the account") 281 } 282 283 // Send the request to the backend and return 284 val, err := call.Otto.Call("jeth.sign", nil, message, account, passwd) 285 if err != nil { 286 throwJSException(err.Error()) 287 } 288 return val 289 } 290 291 // Sleep will block the console for the specified number of seconds. 292 func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) { 293 if call.Argument(0).IsNumber() { 294 sleep, _ := call.Argument(0).ToInteger() 295 time.Sleep(time.Duration(sleep) * time.Second) 296 return otto.TrueValue() 297 } 298 return throwJSException("usage: sleep(<number of seconds>)") 299 } 300 301 // SleepBlocks will block the console for a specified number of new blocks optionally 302 // until the given timeout is reached. 303 func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) { 304 var ( 305 blocks = int64(0) 306 sleep = int64(9999999999999999) // indefinitely 307 ) 308 // Parse the input parameters for the sleep 309 nArgs := len(call.ArgumentList) 310 if nArgs == 0 { 311 throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])") 312 } 313 if nArgs >= 1 { 314 if call.Argument(0).IsNumber() { 315 blocks, _ = call.Argument(0).ToInteger() 316 } else { 317 throwJSException("expected number as first argument") 318 } 319 } 320 if nArgs >= 2 { 321 if call.Argument(1).IsNumber() { 322 sleep, _ = call.Argument(1).ToInteger() 323 } else { 324 throwJSException("expected number as second argument") 325 } 326 } 327 // go through the console, this will allow web3 to call the appropriate 328 // callbacks if a delayed response or notification is received. 329 blockNumber := func() int64 { 330 result, err := call.Otto.Run("eth.blockNumber") 331 if err != nil { 332 throwJSException(err.Error()) 333 } 334 block, err := result.ToInteger() 335 if err != nil { 336 throwJSException(err.Error()) 337 } 338 return block 339 } 340 // Poll the current block number until either it ot a timeout is reached 341 targetBlockNr := blockNumber() + blocks 342 deadline := time.Now().Add(time.Duration(sleep) * time.Second) 343 344 for time.Now().Before(deadline) { 345 if blockNumber() >= targetBlockNr { 346 return otto.TrueValue() 347 } 348 time.Sleep(time.Second) 349 } 350 return otto.FalseValue() 351 } 352 353 type jsonrpcCall struct { 354 ID int64 355 Method string 356 Params []interface{} 357 } 358 359 // Send implements the web3 provider "send" method. 360 func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { 361 // Remarshal the request into a Go value. 362 JSON, _ := call.Otto.Object("JSON") 363 reqVal, err := JSON.Call("stringify", call.Argument(0)) 364 if err != nil { 365 throwJSException(err.Error()) 366 } 367 var ( 368 rawReq = reqVal.String() 369 dec = json.NewDecoder(strings.NewReader(rawReq)) 370 reqs []jsonrpcCall 371 batch bool 372 ) 373 dec.UseNumber() // avoid float64s 374 if rawReq[0] == '[' { 375 batch = true 376 dec.Decode(&reqs) 377 } else { 378 batch = false 379 reqs = make([]jsonrpcCall, 1) 380 dec.Decode(&reqs[0]) 381 } 382 383 // Execute the requests. 384 resps, _ := call.Otto.Object("new Array()") 385 for _, req := range reqs { 386 resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`) 387 resp.Set("id", req.ID) 388 var result json.RawMessage 389 err = b.client.Call(&result, req.Method, req.Params...) 390 switch err := err.(type) { 391 case nil: 392 if result == nil { 393 // Special case null because it is decoded as an empty 394 // raw message for some reason. 395 resp.Set("result", otto.NullValue()) 396 } else { 397 resultVal, err := JSON.Call("parse", string(result)) 398 if err != nil { 399 setError(resp, -32603, err.Error()) 400 } else { 401 resp.Set("result", resultVal) 402 } 403 } 404 case rpc.Error: 405 setError(resp, err.ErrorCode(), err.Error()) 406 default: 407 setError(resp, -32603, err.Error()) 408 } 409 resps.Call("push", resp) 410 } 411 412 // Return the responses either to the callback (if supplied) 413 // or directly as the return value. 414 if batch { 415 response = resps.Value() 416 } else { 417 response, _ = resps.Get("0") 418 } 419 if fn := call.Argument(1); fn.Class() == "Function" { 420 fn.Call(otto.NullValue(), otto.NullValue(), response) 421 return otto.UndefinedValue() 422 } 423 return response 424 } 425 426 func setError(resp *otto.Object, code int, msg string) { 427 resp.Set("error", map[string]interface{}{"code": code, "message": msg}) 428 } 429 430 // throwJSException panics on an otto.Value. The Otto VM will recover from the 431 // Go panic and throw msg as a JavaScript error. 432 func throwJSException(msg interface{}) otto.Value { 433 val, err := otto.ToValue(msg) 434 if err != nil { 435 log.Error("Failed to serialize JavaScript exception", "exception", msg, "err", err) 436 } 437 panic(val) 438 }