github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/console/bridge.go (about) 1 // Copyright 2015 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 "time" 24 25 "github.com/ethereumproject/go-ethereum/logger" 26 "github.com/ethereumproject/go-ethereum/logger/glog" 27 "github.com/ethereumproject/go-ethereum/rpc" 28 "github.com/robertkrimen/otto" 29 ) 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 39 // newBridge creates a new JavaScript wrapper around an RPC client. 40 func newBridge(client rpc.Client, prompter UserPrompter, printer io.Writer) *bridge { 41 return &bridge{ 42 client: client, 43 prompter: prompter, 44 printer: printer, 45 } 46 } 47 48 // NewAccount is a wrapper around the personal.newAccount RPC method that uses a 49 // non-echoing password prompt to aquire the passphrase and executes the original 50 // RPC method (saved in jeth.newAccount) with it to actually execute the RPC call. 51 func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) { 52 var ( 53 password string 54 confirm string 55 err error 56 ) 57 switch { 58 // No password was specified, prompt the user for it 59 case len(call.ArgumentList) == 0: 60 if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil { 61 throwJSException(err.Error()) 62 } 63 if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil { 64 throwJSException(err.Error()) 65 } 66 if password != confirm { 67 throwJSException("passphrases don't match!") 68 } 69 70 // A single string password was specified, use that 71 case len(call.ArgumentList) == 1 && call.Argument(0).IsString(): 72 password, _ = call.Argument(0).ToString() 73 74 // Otherwise fail with some error 75 default: 76 throwJSException("expected 0 or 1 string argument") 77 } 78 // Password aquired, execute the call and return 79 ret, err := call.Otto.Call("jeth.newAccount", nil, password) 80 if err != nil { 81 throwJSException(err.Error()) 82 } 83 return ret 84 } 85 86 // UnlockAccount is a wrapper around the personal.unlockAccount RPC method that 87 // uses a non-echoing password prompt to aquire the passphrase and executes the 88 // original RPC method (saved in jeth.unlockAccount) with it to actually execute 89 // the RPC call. 90 func (b *bridge) UnlockAccount(call otto.FunctionCall) (response otto.Value) { 91 // Make sure we have an account specified to unlock 92 if !call.Argument(0).IsString() { 93 throwJSException("first argument must be the account to unlock") 94 } 95 account := call.Argument(0) 96 97 // If password is not given or is the null value, prompt the user for it 98 var passwd otto.Value 99 100 if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { 101 fmt.Fprintf(b.printer, "Unlock account %s\n", account) 102 if input, err := b.prompter.PromptPassword("Passphrase: "); err != nil { 103 throwJSException(err.Error()) 104 } else { 105 passwd, _ = otto.ToValue(input) 106 } 107 } else { 108 if !call.Argument(1).IsString() { 109 throwJSException("password must be a string") 110 } 111 passwd = call.Argument(1) 112 } 113 // Third argument is the duration how long the account must be unlocked. 114 duration := otto.NullValue() 115 if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() { 116 if !call.Argument(2).IsNumber() { 117 throwJSException("unlock duration must be a number") 118 } 119 duration = call.Argument(2) 120 } 121 // Send the request to the backend and return 122 val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration) 123 if err != nil { 124 throwJSException(err.Error()) 125 } 126 return val 127 } 128 129 // Sleep will block the console for the specified number of seconds. 130 func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) { 131 if call.Argument(0).IsNumber() { 132 sleep, _ := call.Argument(0).ToInteger() 133 time.Sleep(time.Duration(sleep) * time.Second) 134 return otto.TrueValue() 135 } 136 return throwJSException("usage: sleep(<number of seconds>)") 137 } 138 139 // SleepBlocks will block the console for a specified number of new blocks optionally 140 // until the given timeout is reached. 141 func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) { 142 var ( 143 blocks = int64(0) 144 sleep = int64(9999999999999999) // indefinitely 145 ) 146 // Parse the input parameters for the sleep 147 nArgs := len(call.ArgumentList) 148 if nArgs == 0 { 149 throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])") 150 } 151 if nArgs >= 1 { 152 if call.Argument(0).IsNumber() { 153 blocks, _ = call.Argument(0).ToInteger() 154 } else { 155 throwJSException("expected number as first argument") 156 } 157 } 158 if nArgs >= 2 { 159 if call.Argument(1).IsNumber() { 160 sleep, _ = call.Argument(1).ToInteger() 161 } else { 162 throwJSException("expected number as second argument") 163 } 164 } 165 // go through the console, this will allow web3 to call the appropriate 166 // callbacks if a delayed response or notification is received. 167 blockNumber := func() int64 { 168 result, err := call.Otto.Run("eth.blockNumber") 169 if err != nil { 170 throwJSException(err.Error()) 171 } 172 block, err := result.ToInteger() 173 if err != nil { 174 throwJSException(err.Error()) 175 } 176 return block 177 } 178 // Poll the current block number until either it ot a timeout is reached 179 targetBlockNr := blockNumber() + blocks 180 deadline := time.Now().Add(time.Duration(sleep) * time.Second) 181 182 for time.Now().Before(deadline) { 183 if blockNumber() >= targetBlockNr { 184 return otto.TrueValue() 185 } 186 time.Sleep(time.Second) 187 } 188 return otto.FalseValue() 189 } 190 191 // Send will serialize the first argument, send it to the node and returns the response. 192 func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { 193 // Ensure that we've got a batch request (array) or a single request (object) 194 arg := call.Argument(0).Object() 195 if arg == nil || (arg.Class() != "Array" && arg.Class() != "Object") { 196 throwJSException("request must be an object or array") 197 } 198 // Convert the otto VM arguments to Go values 199 data, err := call.Otto.Call("JSON.stringify", nil, arg) 200 if err != nil { 201 throwJSException(err.Error()) 202 } 203 reqjson, err := data.ToString() 204 if err != nil { 205 throwJSException(err.Error()) 206 } 207 208 var ( 209 reqs []rpc.JSONRequest 210 batch = true 211 ) 212 if err = json.Unmarshal([]byte(reqjson), &reqs); err != nil { 213 // single request? 214 reqs = make([]rpc.JSONRequest, 1) 215 if err = json.Unmarshal([]byte(reqjson), &reqs[0]); err != nil { 216 throwJSException("invalid request") 217 } 218 batch = false 219 } 220 // Iteratively execute the requests 221 call.Otto.Set("response_len", len(reqs)) 222 call.Otto.Run("var ret_response = new Array(response_len);") 223 224 for i, req := range reqs { 225 // Execute the RPC request and parse the reply 226 if err = b.client.Send(&req); err != nil { 227 return newErrorResponse(call, -32603, err.Error(), req.Id) 228 } 229 result := make(map[string]interface{}) 230 if err = b.client.Recv(&result); err != nil { 231 return newErrorResponse(call, -32603, err.Error(), req.Id) 232 } 233 // Feed the reply back into the JavaScript runtime environment 234 id, _ := result["id"] 235 jsonver, _ := result["jsonrpc"] 236 237 call.Otto.Set("ret_id", id) 238 call.Otto.Set("ret_jsonrpc", jsonver) 239 call.Otto.Set("response_idx", i) 240 241 if res, ok := result["result"]; ok { 242 payload, _ := json.Marshal(res) 243 call.Otto.Set("ret_result", string(payload)) 244 response, err = call.Otto.Run(` 245 ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) }; 246 `) 247 continue 248 } 249 if res, ok := result["error"]; ok { 250 payload, _ := json.Marshal(res) 251 call.Otto.Set("ret_result", string(payload)) 252 response, err = call.Otto.Run(` 253 ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) }; 254 `) 255 continue 256 } 257 return newErrorResponse(call, -32603, fmt.Sprintf("Invalid response"), new(int64)) 258 } 259 // Convert single requests back from batch ones 260 if !batch { 261 call.Otto.Run("ret_response = ret_response[0];") 262 } 263 // Execute any registered callbacks 264 if call.Argument(1).IsObject() { 265 call.Otto.Set("callback", call.Argument(1)) 266 call.Otto.Run(` 267 if (Object.prototype.toString.call(callback) == '[object Function]') { 268 callback(null, ret_response); 269 } 270 `) 271 } 272 return 273 } 274 275 // throwJSException panics on an otto.Value. The Otto VM will recover from the 276 // Go panic and throw msg as a JavaScript error. 277 func throwJSException(msg interface{}) otto.Value { 278 val, err := otto.ToValue(msg) 279 if err != nil { 280 glog.V(logger.Error).Infof("Failed to serialize JavaScript exception %v: %v", msg, err) 281 } 282 panic(val) 283 } 284 285 // newErrorResponse creates a JSON RPC error response for a specific request id, 286 // containing the specified error code and error message. Beside returning the 287 // error to the caller, it also sets the ret_error and ret_response JavaScript 288 // variables. 289 func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { 290 // Bundle the error into a JSON RPC call response 291 res := rpc.JSONResponse{ 292 Version: rpc.JSONRPCVersion, 293 Id: id, 294 Error: &rpc.JSONError{ 295 Code: code, 296 Message: msg, 297 }, 298 } 299 // Serialize the error response into JavaScript variables 300 errObj, err := json.Marshal(res.Error) 301 if err != nil { 302 glog.V(logger.Error).Infof("Failed to serialize JSON RPC error: %v", err) 303 } 304 resObj, err := json.Marshal(res) 305 if err != nil { 306 glog.V(logger.Error).Infof("Failed to serialize JSON RPC error response: %v", err) 307 } 308 309 if _, err = call.Otto.Run("ret_error = " + string(errObj)); err != nil { 310 glog.V(logger.Error).Infof("Failed to set `ret_error` to the occurred error: %v", err) 311 } 312 resVal, err := call.Otto.Run("ret_response = " + string(resObj)) 313 if err != nil { 314 glog.V(logger.Error).Infof("Failed to set `ret_response` to the JSON RPC response: %v", err) 315 } 316 return resVal 317 }