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