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