github.com/n1ghtfa1l/go-vnt@v0.6.4-alpha.6/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/robertkrimen/otto" 27 "github.com/vntchain/go-vnt/log" 28 "github.com/vntchain/go-vnt/rpc" 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 VNT 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 acquire the passphrase and executes the original 50 // RPC method (saved in jvnt.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 acquired, execute the call and return 79 ret, err := call.Otto.Call("jvnt.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 acquire the passphrase and executes the 88 // original RPC method (saved in jvnt.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("jvnt.unlockAccount", nil, account, passwd, duration) 123 if err != nil { 124 throwJSException(err.Error()) 125 } 126 return val 127 } 128 129 // Sign is a wrapper around the personal.sign RPC method that uses a non-echoing password 130 // prompt to acquire the passphrase and executes the original RPC method (saved in 131 // jvnt.sign) with it to actually execute the RPC call. 132 func (b *bridge) Sign(call otto.FunctionCall) (response otto.Value) { 133 var ( 134 message = call.Argument(0) 135 account = call.Argument(1) 136 passwd = call.Argument(2) 137 ) 138 139 if !message.IsString() { 140 throwJSException("first argument must be the message to sign") 141 } 142 if !account.IsString() { 143 throwJSException("second argument must be the account to sign with") 144 } 145 146 // if the password is not given or null ask the user and ensure password is a string 147 if passwd.IsUndefined() || passwd.IsNull() { 148 fmt.Fprintf(b.printer, "Give password for account %s\n", account) 149 if input, err := b.prompter.PromptPassword("Passphrase: "); err != nil { 150 throwJSException(err.Error()) 151 } else { 152 passwd, _ = otto.ToValue(input) 153 } 154 } 155 if !passwd.IsString() { 156 throwJSException("third argument must be the password to unlock the account") 157 } 158 159 // Send the request to the backend and return 160 val, err := call.Otto.Call("jvnt.sign", nil, message, account, passwd) 161 if err != nil { 162 throwJSException(err.Error()) 163 } 164 return val 165 } 166 167 // Sleep will block the console for the specified number of seconds. 168 func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) { 169 if call.Argument(0).IsNumber() { 170 sleep, _ := call.Argument(0).ToInteger() 171 time.Sleep(time.Duration(sleep) * time.Second) 172 return otto.TrueValue() 173 } 174 return throwJSException("usage: sleep(<number of seconds>)") 175 } 176 177 // SleepBlocks will block the console for a specified number of new blocks optionally 178 // until the given timeout is reached. 179 func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) { 180 var ( 181 blocks = int64(0) 182 sleep = int64(9999999999999999) // indefinitely 183 ) 184 // Parse the input parameters for the sleep 185 nArgs := len(call.ArgumentList) 186 if nArgs == 0 { 187 throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])") 188 } 189 if nArgs >= 1 { 190 if call.Argument(0).IsNumber() { 191 blocks, _ = call.Argument(0).ToInteger() 192 } else { 193 throwJSException("expected number as first argument") 194 } 195 } 196 if nArgs >= 2 { 197 if call.Argument(1).IsNumber() { 198 sleep, _ = call.Argument(1).ToInteger() 199 } else { 200 throwJSException("expected number as second argument") 201 } 202 } 203 // go through the console, this will allow web3 to call the appropriate 204 // callbacks if a delayed response or notification is received. 205 blockNumber := func() int64 { 206 result, err := call.Otto.Run("core.blockNumber") 207 if err != nil { 208 throwJSException(err.Error()) 209 } 210 block, err := result.ToInteger() 211 if err != nil { 212 throwJSException(err.Error()) 213 } 214 return block 215 } 216 // Poll the current block number until either it ot a timeout is reached 217 targetBlockNr := blockNumber() + blocks 218 deadline := time.Now().Add(time.Duration(sleep) * time.Second) 219 220 for time.Now().Before(deadline) { 221 if blockNumber() >= targetBlockNr { 222 return otto.TrueValue() 223 } 224 time.Sleep(time.Second) 225 } 226 return otto.FalseValue() 227 } 228 229 type jsonrpcCall struct { 230 ID int64 231 Method string 232 Params []interface{} 233 } 234 235 // Send implements the web3 provider "send" method. 236 func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { 237 // Remarshal the request into a Go value. 238 JSON, _ := call.Otto.Object("JSON") 239 reqVal, err := JSON.Call("stringify", call.Argument(0)) 240 if err != nil { 241 throwJSException(err.Error()) 242 } 243 var ( 244 rawReq = reqVal.String() 245 dec = json.NewDecoder(strings.NewReader(rawReq)) 246 reqs []jsonrpcCall 247 batch bool 248 ) 249 dec.UseNumber() // avoid float64s 250 if rawReq[0] == '[' { 251 batch = true 252 dec.Decode(&reqs) 253 } else { 254 batch = false 255 reqs = make([]jsonrpcCall, 1) 256 dec.Decode(&reqs[0]) 257 } 258 259 // Execute the requests. 260 resps, _ := call.Otto.Object("new Array()") 261 for _, req := range reqs { 262 resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`) 263 resp.Set("id", req.ID) 264 var result json.RawMessage 265 err = b.client.Call(&result, req.Method, req.Params...) 266 switch err := err.(type) { 267 case nil: 268 if result == nil { 269 // Special case null because it is decoded as an empty 270 // raw message for some reason. 271 resp.Set("result", otto.NullValue()) 272 } else { 273 resultVal, err := JSON.Call("parse", string(result)) 274 if err != nil { 275 setError(resp, -32603, err.Error()) 276 } else { 277 resp.Set("result", resultVal) 278 } 279 } 280 case rpc.Error: 281 setError(resp, err.ErrorCode(), err.Error()) 282 default: 283 setError(resp, -32603, err.Error()) 284 } 285 resps.Call("push", resp) 286 } 287 288 // Return the responses either to the callback (if supplied) 289 // or directly as the return value. 290 if batch { 291 response = resps.Value() 292 } else { 293 response, _ = resps.Get("0") 294 } 295 if fn := call.Argument(1); fn.Class() == "Function" { 296 fn.Call(otto.NullValue(), otto.NullValue(), response) 297 return otto.UndefinedValue() 298 } 299 return response 300 } 301 302 func setError(resp *otto.Object, code int, msg string) { 303 resp.Set("error", map[string]interface{}{"code": code, "message": msg}) 304 } 305 306 // throwJSException panics on an otto.Value. The Otto VM will recover from the 307 // Go panic and throw msg as a JavaScript error. 308 func throwJSException(msg interface{}) otto.Value { 309 val, err := otto.ToValue(msg) 310 if err != nil { 311 log.Error("Failed to serialize JavaScript exception", "exception", msg, "err", err) 312 } 313 panic(val) 314 }