github.com/neatlab/neatio@v1.7.3-0.20220425043230-d903e92fcc75/utilities/console/bridge.go (about) 1 package console 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strings" 8 "time" 9 10 "github.com/neatlab/neatio/chain/accounts/usbwallet" 11 "github.com/neatlab/neatio/chain/log" 12 "github.com/neatlab/neatio/network/rpc" 13 "github.com/robertkrimen/otto" 14 ) 15 16 type bridge struct { 17 client *rpc.Client 18 prompter UserPrompter 19 printer io.Writer 20 } 21 22 func newBridge(client *rpc.Client, prompter UserPrompter, printer io.Writer) *bridge { 23 return &bridge{ 24 client: client, 25 prompter: prompter, 26 printer: printer, 27 } 28 } 29 30 func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) { 31 var ( 32 password string 33 confirm string 34 err error 35 ) 36 switch { 37 38 case len(call.ArgumentList) == 0: 39 if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil { 40 throwJSException(err.Error()) 41 } 42 if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil { 43 throwJSException(err.Error()) 44 } 45 if password != confirm { 46 throwJSException("passphrases don't match!") 47 } 48 49 case len(call.ArgumentList) == 1 && call.Argument(0).IsString(): 50 password, _ = call.Argument(0).ToString() 51 52 default: 53 throwJSException("expected 0 or 1 string argument") 54 } 55 56 ret, err := call.Otto.Call("jeth.newAccount", nil, password) 57 if err != nil { 58 throwJSException(err.Error()) 59 } 60 return ret 61 } 62 63 func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) { 64 65 if !call.Argument(0).IsString() { 66 throwJSException("first argument must be the wallet URL to open") 67 } 68 wallet := call.Argument(0) 69 70 var passwd otto.Value 71 if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { 72 passwd, _ = otto.ToValue("") 73 } else { 74 passwd = call.Argument(1) 75 } 76 77 val, err := call.Otto.Call("jeth.openWallet", nil, wallet, passwd) 78 if err == nil { 79 return val 80 } 81 82 if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) { 83 throwJSException(err.Error()) 84 } 85 86 fmt.Fprintf(b.printer, "Look at the device for number positions\n\n") 87 fmt.Fprintf(b.printer, "7 | 8 | 9\n") 88 fmt.Fprintf(b.printer, "--+---+--\n") 89 fmt.Fprintf(b.printer, "4 | 5 | 6\n") 90 fmt.Fprintf(b.printer, "--+---+--\n") 91 fmt.Fprintf(b.printer, "1 | 2 | 3\n\n") 92 93 if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil { 94 throwJSException(err.Error()) 95 } else { 96 passwd, _ = otto.ToValue(input) 97 } 98 if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil { 99 throwJSException(err.Error()) 100 } 101 return val 102 } 103 104 func (b *bridge) UnlockAccount(call otto.FunctionCall) (response otto.Value) { 105 106 if !call.Argument(0).IsString() { 107 throwJSException("first argument must be the account to unlock") 108 } 109 account := call.Argument(0) 110 111 var passwd otto.Value 112 113 if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { 114 fmt.Fprintf(b.printer, "Unlock account %s\n", account) 115 if input, err := b.prompter.PromptPassword("Passphrase: "); err != nil { 116 throwJSException(err.Error()) 117 } else { 118 passwd, _ = otto.ToValue(input) 119 } 120 } else { 121 if !call.Argument(1).IsString() { 122 throwJSException("password must be a string") 123 } 124 passwd = call.Argument(1) 125 } 126 127 duration := otto.NullValue() 128 if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() { 129 if !call.Argument(2).IsNumber() { 130 throwJSException("unlock duration must be a number") 131 } 132 duration = call.Argument(2) 133 } 134 135 val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration) 136 if err != nil { 137 throwJSException(err.Error()) 138 } 139 return val 140 } 141 142 func (b *bridge) Sign(call otto.FunctionCall) (response otto.Value) { 143 var ( 144 message = call.Argument(0) 145 account = call.Argument(1) 146 passwd = call.Argument(2) 147 ) 148 149 if !message.IsString() { 150 throwJSException("first argument must be the message to sign") 151 } 152 if !account.IsString() { 153 throwJSException("second argument must be the account to sign with") 154 } 155 156 if passwd.IsUndefined() || passwd.IsNull() { 157 fmt.Fprintf(b.printer, "Give password for account %s\n", account) 158 if input, err := b.prompter.PromptPassword("Passphrase: "); err != nil { 159 throwJSException(err.Error()) 160 } else { 161 passwd, _ = otto.ToValue(input) 162 } 163 } 164 if !passwd.IsString() { 165 throwJSException("third argument must be the password to unlock the account") 166 } 167 168 val, err := call.Otto.Call("jeth.sign", nil, message, account, passwd) 169 if err != nil { 170 throwJSException(err.Error()) 171 } 172 return val 173 } 174 175 func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) { 176 if call.Argument(0).IsNumber() { 177 sleep, _ := call.Argument(0).ToInteger() 178 time.Sleep(time.Duration(sleep) * time.Second) 179 return otto.TrueValue() 180 } 181 return throwJSException("usage: sleep(<number of seconds>)") 182 } 183 184 func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) { 185 var ( 186 blocks = int64(0) 187 sleep = int64(9999999999999999) 188 ) 189 190 nArgs := len(call.ArgumentList) 191 if nArgs == 0 { 192 throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])") 193 } 194 if nArgs >= 1 { 195 if call.Argument(0).IsNumber() { 196 blocks, _ = call.Argument(0).ToInteger() 197 } else { 198 throwJSException("expected number as first argument") 199 } 200 } 201 if nArgs >= 2 { 202 if call.Argument(1).IsNumber() { 203 sleep, _ = call.Argument(1).ToInteger() 204 } else { 205 throwJSException("expected number as second argument") 206 } 207 } 208 209 blockNumber := func() int64 { 210 result, err := call.Otto.Run("eth.blockNumber") 211 if err != nil { 212 throwJSException(err.Error()) 213 } 214 block, err := result.ToInteger() 215 if err != nil { 216 throwJSException(err.Error()) 217 } 218 return block 219 } 220 221 targetBlockNr := blockNumber() + blocks 222 deadline := time.Now().Add(time.Duration(sleep) * time.Second) 223 224 for time.Now().Before(deadline) { 225 if blockNumber() >= targetBlockNr { 226 return otto.TrueValue() 227 } 228 time.Sleep(time.Second) 229 } 230 return otto.FalseValue() 231 } 232 233 type jsonrpcCall struct { 234 Id int64 235 Method string 236 Params []interface{} 237 } 238 239 func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { 240 241 JSON, _ := call.Otto.Object("JSON") 242 reqVal, err := JSON.Call("stringify", call.Argument(0)) 243 if err != nil { 244 throwJSException(err.Error()) 245 } 246 var ( 247 rawReq = reqVal.String() 248 dec = json.NewDecoder(strings.NewReader(rawReq)) 249 reqs []jsonrpcCall 250 batch bool 251 ) 252 dec.UseNumber() 253 if rawReq[0] == '[' { 254 batch = true 255 dec.Decode(&reqs) 256 } else { 257 batch = false 258 reqs = make([]jsonrpcCall, 1) 259 dec.Decode(&reqs[0]) 260 } 261 262 resps, _ := call.Otto.Object("new Array()") 263 for _, req := range reqs { 264 resp, _ := call.Otto.Object(`({"jsonrpc":"2.0"})`) 265 resp.Set("id", req.Id) 266 var result json.RawMessage 267 err = b.client.Call(&result, req.Method, req.Params...) 268 269 if err == nil { 270 if result == nil { 271 272 resp.Set("result", otto.NullValue()) 273 } else { 274 resultVal, err := JSON.Call("parse", string(result)) 275 if err != nil { 276 setError(resp, -32603, err.Error(), nil) 277 } else { 278 resp.Set("result", resultVal) 279 } 280 } 281 } else { 282 code := -32603 283 var data interface{} 284 if err, ok := err.(rpc.Error); ok { 285 code = err.ErrorCode() 286 } 287 288 if err, ok := err.(rpc.DataError); ok { 289 data = err.ErrorData() 290 } 291 292 setError(resp, code, err.Error(), data) 293 } 294 resps.Call("push", resp) 295 } 296 297 if batch { 298 response = resps.Value() 299 } else { 300 response, _ = resps.Get("0") 301 } 302 if fn := call.Argument(1); fn.Class() == "Function" { 303 fn.Call(otto.NullValue(), otto.NullValue(), response) 304 return otto.UndefinedValue() 305 } 306 return response 307 } 308 309 func setError(resp *otto.Object, code int, msg string, data interface{}) { 310 311 err := make(map[string]interface{}) 312 err["code"] = code 313 err["message"] = msg 314 if data != nil { 315 err["data"] = data 316 } 317 resp.Set("error", err) 318 } 319 320 func throwJSException(msg interface{}) otto.Value { 321 val, err := otto.ToValue(msg) 322 if err != nil { 323 log.Error("Failed to serialize JavaScript exception", "exception", msg, "err", err) 324 } 325 panic(val) 326 }