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