github.com/dreamer-zq/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  }