github.com/ethereum/go-ethereum@v1.16.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 "io" 23 "reflect" 24 "strings" 25 "time" 26 27 "github.com/dop251/goja" 28 "github.com/ethereum/go-ethereum/common/hexutil" 29 "github.com/ethereum/go-ethereum/console/prompt" 30 "github.com/ethereum/go-ethereum/internal/jsre" 31 "github.com/ethereum/go-ethereum/rpc" 32 ) 33 34 // bridge is a collection of JavaScript utility methods to bride the .js runtime 35 // environment and the Go RPC connection backing the remote method calls. 36 type bridge struct { 37 client *rpc.Client // RPC client to execute Ethereum requests through 38 prompter prompt.UserPrompter // Input prompter to allow interactive user feedback 39 printer io.Writer // Output writer to serialize any display strings to 40 } 41 42 // newBridge creates a new JavaScript wrapper around an RPC client. 43 func newBridge(client *rpc.Client, prompter prompt.UserPrompter, printer io.Writer) *bridge { 44 return &bridge{ 45 client: client, 46 prompter: prompter, 47 printer: printer, 48 } 49 } 50 51 // Sleep will block the console for the specified number of seconds. 52 func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) { 53 if nArgs := len(call.Arguments); nArgs < 1 { 54 return nil, errors.New("usage: sleep(<number of seconds>)") 55 } 56 sleepObj := call.Argument(0) 57 if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) { 58 return nil, errors.New("usage: sleep(<number of seconds>)") 59 } 60 sleep := sleepObj.ToFloat() 61 time.Sleep(time.Duration(sleep * float64(time.Second))) 62 return call.VM.ToValue(true), nil 63 } 64 65 // SleepBlocks will block the console for a specified number of new blocks optionally 66 // until the given timeout is reached. 67 func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { 68 // Parse the input parameters for the sleep. 69 var ( 70 blocks = int64(0) 71 sleep = int64(9999999999999999) // indefinitely 72 ) 73 nArgs := len(call.Arguments) 74 if nArgs == 0 { 75 return nil, errors.New("usage: sleepBlocks(<n blocks>[, max sleep in seconds])") 76 } 77 if nArgs >= 1 { 78 if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) { 79 return nil, errors.New("expected number as first argument") 80 } 81 blocks = call.Argument(0).ToInteger() 82 } 83 if nArgs >= 2 { 84 if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) { 85 return nil, errors.New("expected number as second argument") 86 } 87 sleep = call.Argument(1).ToInteger() 88 } 89 90 // Poll the current block number until either it or a timeout is reached. 91 deadline := time.Now().Add(time.Duration(sleep) * time.Second) 92 var lastNumber hexutil.Uint64 93 if err := b.client.Call(&lastNumber, "eth_blockNumber"); err != nil { 94 return nil, err 95 } 96 for time.Now().Before(deadline) { 97 var number hexutil.Uint64 98 if err := b.client.Call(&number, "eth_blockNumber"); err != nil { 99 return nil, err 100 } 101 if number != lastNumber { 102 lastNumber = number 103 blocks-- 104 } 105 if blocks <= 0 { 106 break 107 } 108 time.Sleep(time.Second) 109 } 110 return call.VM.ToValue(true), nil 111 } 112 113 type jsonrpcCall struct { 114 ID int64 115 Method string 116 Params []interface{} 117 } 118 119 // Send implements the web3 provider "send" method. 120 func (b *bridge) Send(call jsre.Call) (goja.Value, error) { 121 // Remarshal the request into a Go value. 122 reqVal, err := call.Argument(0).ToObject(call.VM).MarshalJSON() 123 if err != nil { 124 return nil, err 125 } 126 127 var ( 128 rawReq = string(reqVal) 129 dec = json.NewDecoder(strings.NewReader(rawReq)) 130 reqs []jsonrpcCall 131 batch bool 132 ) 133 dec.UseNumber() // avoid float64s 134 if rawReq[0] == '[' { 135 batch = true 136 dec.Decode(&reqs) 137 } else { 138 batch = false 139 reqs = make([]jsonrpcCall, 1) 140 dec.Decode(&reqs[0]) 141 } 142 143 // Execute the requests. 144 var resps []*goja.Object 145 for _, req := range reqs { 146 resp := call.VM.NewObject() 147 resp.Set("jsonrpc", "2.0") 148 resp.Set("id", req.ID) 149 150 var result json.RawMessage 151 if err = b.client.Call(&result, req.Method, req.Params...); err == nil { 152 if result == nil { 153 // Special case null because it is decoded as an empty 154 // raw message for some reason. 155 resp.Set("result", goja.Null()) 156 } else { 157 JSON := call.VM.Get("JSON").ToObject(call.VM) 158 parse, callable := goja.AssertFunction(JSON.Get("parse")) 159 if !callable { 160 return nil, errors.New("JSON.parse is not a function") 161 } 162 resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) 163 if err != nil { 164 setError(resp, -32603, err.Error(), nil) 165 } else { 166 resp.Set("result", resultVal) 167 } 168 } 169 } else { 170 code := -32603 171 var data interface{} 172 if err, ok := err.(rpc.Error); ok { 173 code = err.ErrorCode() 174 } 175 if err, ok := err.(rpc.DataError); ok { 176 data = err.ErrorData() 177 } 178 setError(resp, code, err.Error(), data) 179 } 180 resps = append(resps, resp) 181 } 182 // Return the responses either to the callback (if supplied) 183 // or directly as the return value. 184 var result goja.Value 185 if batch { 186 result = call.VM.ToValue(resps) 187 } else { 188 result = resps[0] 189 } 190 if fn, isFunc := goja.AssertFunction(call.Argument(1)); isFunc { 191 fn(goja.Null(), goja.Null(), result) 192 return goja.Undefined(), nil 193 } 194 return result, nil 195 } 196 197 func setError(resp *goja.Object, code int, msg string, data interface{}) { 198 err := make(map[string]interface{}) 199 err["code"] = code 200 err["message"] = msg 201 if data != nil { 202 err["data"] = data 203 } 204 resp.Set("error", err) 205 } 206 207 // isNumber returns true if input value is a JS number. 208 func isNumber(v goja.Value) bool { 209 k := v.ExportType().Kind() 210 return k >= reflect.Int && k <= reflect.Float64 211 } 212 213 func getObject(vm *goja.Runtime, name string) *goja.Object { 214 v := vm.Get(name) 215 if v == nil { 216 return nil 217 } 218 return v.ToObject(vm) 219 }