github.com/ethereum/go-ethereum@v1.14.3/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/ethereum/go-ethereum/accounts/scwallet"
    30  	"github.com/ethereum/go-ethereum/accounts/usbwallet"
    31  	"github.com/ethereum/go-ethereum/common/hexutil"
    32  	"github.com/ethereum/go-ethereum/console/prompt"
    33  	"github.com/ethereum/go-ethereum/internal/jsre"
    34  	"github.com/ethereum/go-ethereum/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, "eth_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, "eth_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  }