bridge.go 14 KB

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