github.com/quinndk/ethereum_read@v0.0.0-20181211143958-29c55eec3237/go-ethereum-master_read/signer/rules/rules_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU 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 // go-ethereum 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 General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 // 17 package rules 18 19 import ( 20 "fmt" 21 "math/big" 22 "strings" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/accounts" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/common/hexutil" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/internal/ethapi" 30 "github.com/ethereum/go-ethereum/signer/core" 31 "github.com/ethereum/go-ethereum/signer/storage" 32 ) 33 34 const JS = ` 35 /** 36 This is an example implementation of a Javascript rule file. 37 38 When the signer receives a request over the external API, the corresponding method is evaluated. 39 Three things can happen: 40 41 1. The method returns "Approve". This means the operation is permitted. 42 2. The method returns "Reject". This means the operation is rejected. 43 3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means 44 that the operation will continue to manual processing, via the regular UI method chosen by the user. 45 46 [*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not 47 only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all 48 accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject"). 49 50 **/ 51 52 function ApproveListing(request){ 53 console.log("In js approve listing"); 54 console.log(request.accounts[3].Address) 55 console.log(request.meta.Remote) 56 return "Approve" 57 } 58 59 function ApproveTx(request){ 60 console.log("test"); 61 console.log("from"); 62 return "Reject"; 63 } 64 65 function test(thing){ 66 console.log(thing.String()) 67 } 68 69 ` 70 71 func mixAddr(a string) (*common.MixedcaseAddress, error) { 72 return common.NewMixedcaseAddressFromString(a) 73 } 74 75 type alwaysDenyUI struct{} 76 77 func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) { 78 } 79 80 func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 81 return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil 82 } 83 84 func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 85 return core.SignDataResponse{Approved: false, Password: ""}, nil 86 } 87 88 func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 89 return core.ExportResponse{Approved: false}, nil 90 } 91 92 func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 93 return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil 94 } 95 96 func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 97 return core.ListResponse{Accounts: nil}, nil 98 } 99 100 func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 101 return core.NewAccountResponse{Approved: false, Password: ""}, nil 102 } 103 104 func (alwaysDenyUI) ShowError(message string) { 105 panic("implement me") 106 } 107 108 func (alwaysDenyUI) ShowInfo(message string) { 109 panic("implement me") 110 } 111 112 func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { 113 panic("implement me") 114 } 115 116 func initRuleEngine(js string) (*rulesetUI, error) { 117 r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) 118 if err != nil { 119 return nil, fmt.Errorf("failed to create js engine: %v", err) 120 } 121 if err = r.Init(js); err != nil { 122 return nil, fmt.Errorf("failed to load bootstrap js: %v", err) 123 } 124 return r, nil 125 } 126 127 func TestListRequest(t *testing.T) { 128 accs := make([]core.Account, 5) 129 130 for i := range accs { 131 addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i) 132 acc := core.Account{ 133 Address: common.BytesToAddress(common.Hex2Bytes(addr)), 134 URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)}, 135 } 136 accs[i] = acc 137 } 138 139 js := `function ApproveListing(){ return "Approve" }` 140 141 r, err := initRuleEngine(js) 142 if err != nil { 143 t.Errorf("Couldn't create evaluator %v", err) 144 return 145 } 146 resp, err := r.ApproveListing(&core.ListRequest{ 147 Accounts: accs, 148 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 149 }) 150 if len(resp.Accounts) != len(accs) { 151 t.Errorf("Expected check to resolve to 'Approve'") 152 } 153 } 154 155 func TestSignTxRequest(t *testing.T) { 156 157 js := ` 158 function ApproveTx(r){ 159 console.log("transaction.from", r.transaction.from); 160 console.log("transaction.to", r.transaction.to); 161 console.log("transaction.value", r.transaction.value); 162 console.log("transaction.nonce", r.transaction.nonce); 163 if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} 164 if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} 165 }` 166 167 r, err := initRuleEngine(js) 168 if err != nil { 169 t.Errorf("Couldn't create evaluator %v", err) 170 return 171 } 172 to, err := mixAddr("000000000000000000000000000000000000dead") 173 if err != nil { 174 t.Error(err) 175 return 176 } 177 from, err := mixAddr("0000000000000000000000000000000000001337") 178 179 if err != nil { 180 t.Error(err) 181 return 182 } 183 fmt.Printf("to %v", to.Address().String()) 184 resp, err := r.ApproveTx(&core.SignTxRequest{ 185 Transaction: core.SendTxArgs{ 186 From: *from, 187 To: to}, 188 Callinfo: nil, 189 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 190 }) 191 if err != nil { 192 t.Errorf("Unexpected error %v", err) 193 } 194 if !resp.Approved { 195 t.Errorf("Expected check to resolve to 'Approve'") 196 } 197 } 198 199 type dummyUI struct { 200 calls []string 201 } 202 203 func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 204 d.calls = append(d.calls, "ApproveTx") 205 return core.SignTxResponse{}, core.ErrRequestDenied 206 } 207 208 func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 209 d.calls = append(d.calls, "ApproveSignData") 210 return core.SignDataResponse{}, core.ErrRequestDenied 211 } 212 213 func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 214 d.calls = append(d.calls, "ApproveExport") 215 return core.ExportResponse{}, core.ErrRequestDenied 216 } 217 218 func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 219 d.calls = append(d.calls, "ApproveImport") 220 return core.ImportResponse{}, core.ErrRequestDenied 221 } 222 223 func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 224 d.calls = append(d.calls, "ApproveListing") 225 return core.ListResponse{}, core.ErrRequestDenied 226 } 227 228 func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 229 d.calls = append(d.calls, "ApproveNewAccount") 230 return core.NewAccountResponse{}, core.ErrRequestDenied 231 } 232 233 func (d *dummyUI) ShowError(message string) { 234 d.calls = append(d.calls, "ShowError") 235 } 236 237 func (d *dummyUI) ShowInfo(message string) { 238 d.calls = append(d.calls, "ShowInfo") 239 } 240 241 func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { 242 d.calls = append(d.calls, "OnApprovedTx") 243 } 244 func (d *dummyUI) OnSignerStartup(info core.StartupInfo) { 245 } 246 247 //TestForwarding tests that the rule-engine correctly dispatches requests to the next caller 248 func TestForwarding(t *testing.T) { 249 250 js := "" 251 ui := &dummyUI{make([]string, 0)} 252 jsBackend := storage.NewEphemeralStorage() 253 credBackend := storage.NewEphemeralStorage() 254 r, err := NewRuleEvaluator(ui, jsBackend, credBackend) 255 if err != nil { 256 t.Fatalf("Failed to create js engine: %v", err) 257 } 258 if err = r.Init(js); err != nil { 259 t.Fatalf("Failed to load bootstrap js: %v", err) 260 } 261 r.ApproveSignData(nil) 262 r.ApproveTx(nil) 263 r.ApproveImport(nil) 264 r.ApproveNewAccount(nil) 265 r.ApproveListing(nil) 266 r.ApproveExport(nil) 267 r.ShowError("test") 268 r.ShowInfo("test") 269 270 //This one is not forwarded 271 r.OnApprovedTx(ethapi.SignTransactionResult{}) 272 273 expCalls := 8 274 if len(ui.calls) != expCalls { 275 276 t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ",")) 277 278 } 279 280 } 281 282 func TestMissingFunc(t *testing.T) { 283 r, err := initRuleEngine(JS) 284 if err != nil { 285 t.Errorf("Couldn't create evaluator %v", err) 286 return 287 } 288 289 _, err = r.execute("MissingMethod", "test") 290 291 if err == nil { 292 t.Error("Expected error") 293 } 294 295 approved, err := r.checkApproval("MissingMethod", nil, nil) 296 if err == nil { 297 t.Errorf("Expected missing method to yield error'") 298 } 299 if approved { 300 t.Errorf("Expected missing method to cause non-approval") 301 } 302 fmt.Printf("Err %v", err) 303 304 } 305 func TestStorage(t *testing.T) { 306 307 js := ` 308 function testStorage(){ 309 storage.Put("mykey", "myvalue") 310 a = storage.Get("mykey") 311 312 storage.Put("mykey", ["a", "list"]) // Should result in "a,list" 313 a += storage.Get("mykey") 314 315 316 storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]" 317 a += storage.Get("mykey") 318 319 320 storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}' 321 a += storage.Get("mykey") 322 323 a += storage.Get("missingkey") //Missing keys should result in empty string 324 storage.Put("","missing key==noop") // Can't store with 0-length key 325 a += storage.Get("") // Should result in '' 326 327 var b = new BigNumber(2) 328 var c = new BigNumber(16)//"0xf0",16) 329 var d = b.plus(c) 330 console.log(d) 331 return a 332 } 333 ` 334 r, err := initRuleEngine(js) 335 if err != nil { 336 t.Errorf("Couldn't create evaluator %v", err) 337 return 338 } 339 340 v, err := r.execute("testStorage", nil) 341 342 if err != nil { 343 t.Errorf("Unexpected error %v", err) 344 } 345 346 retval, err := v.ToString() 347 348 if err != nil { 349 t.Errorf("Unexpected error %v", err) 350 } 351 exp := `myvaluea,list[object Object]{"an":"object"}` 352 if retval != exp { 353 t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval) 354 } 355 fmt.Printf("Err %v", err) 356 357 } 358 359 const ExampleTxWindow = ` 360 function big(str){ 361 if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} 362 return new BigNumber(str) 363 } 364 365 // Time window: 1 week 366 var window = 1000* 3600*24*7; 367 368 // Limit : 1 ether 369 var limit = new BigNumber("1e18"); 370 371 function isLimitOk(transaction){ 372 var value = big(transaction.value) 373 // Start of our window function 374 var windowstart = new Date().getTime() - window; 375 376 var txs = []; 377 var stored = storage.Get('txs'); 378 379 if(stored != ""){ 380 txs = JSON.parse(stored) 381 } 382 // First, remove all that have passed out of the time-window 383 var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); 384 console.log(txs, newtxs.length); 385 386 // Secondly, aggregate the current sum 387 sum = new BigNumber(0) 388 389 sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); 390 console.log("ApproveTx > Sum so far", sum); 391 console.log("ApproveTx > Requested", value.toNumber()); 392 393 // Would we exceed weekly limit ? 394 return sum.plus(value).lt(limit) 395 396 } 397 function ApproveTx(r){ 398 console.log(r) 399 console.log(typeof(r)) 400 if (isLimitOk(r.transaction)){ 401 return "Approve" 402 } 403 return "Nope" 404 } 405 406 /** 407 * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter 408 * 'response_str' contains the return value that will be sent to the external caller. 409 * The return value from this method is ignore - the reason for having this callback is to allow the 410 * ruleset to keep track of approved transactions. 411 * 412 * When implementing rate-limited rules, this callback should be used. 413 * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user 414 * then accepts the transaction, this method will be called. 415 * 416 * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. 417 */ 418 function OnApprovedTx(resp){ 419 var value = big(resp.tx.value) 420 var txs = [] 421 // Load stored transactions 422 var stored = storage.Get('txs'); 423 if(stored != ""){ 424 txs = JSON.parse(stored) 425 } 426 // Add this to the storage 427 txs.push({tstamp: new Date().getTime(), value: value}); 428 storage.Put("txs", JSON.stringify(txs)); 429 } 430 431 ` 432 433 func dummyTx(value hexutil.Big) *core.SignTxRequest { 434 435 to, _ := mixAddr("000000000000000000000000000000000000dead") 436 from, _ := mixAddr("000000000000000000000000000000000000dead") 437 n := hexutil.Uint64(3) 438 gas := hexutil.Uint64(21000) 439 gasPrice := hexutil.Big(*big.NewInt(2000000)) 440 441 return &core.SignTxRequest{ 442 Transaction: core.SendTxArgs{ 443 From: *from, 444 To: to, 445 Value: value, 446 Nonce: n, 447 GasPrice: gasPrice, 448 Gas: gas, 449 }, 450 Callinfo: []core.ValidationInfo{ 451 {Typ: "Warning", Message: "All your base are bellong to us"}, 452 }, 453 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 454 } 455 } 456 func dummyTxWithV(value uint64) *core.SignTxRequest { 457 458 v := big.NewInt(0).SetUint64(value) 459 h := hexutil.Big(*v) 460 return dummyTx(h) 461 } 462 func dummySigned(value *big.Int) *types.Transaction { 463 to := common.HexToAddress("000000000000000000000000000000000000dead") 464 gas := uint64(21000) 465 gasPrice := big.NewInt(2000000) 466 data := make([]byte, 0) 467 return types.NewTransaction(3, to, value, gas, gasPrice, data) 468 469 } 470 func TestLimitWindow(t *testing.T) { 471 472 r, err := initRuleEngine(ExampleTxWindow) 473 if err != nil { 474 t.Errorf("Couldn't create evaluator %v", err) 475 return 476 } 477 478 // 0.3 ether: 429D069189E0000 wei 479 v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000")) 480 h := hexutil.Big(*v) 481 // The first three should succeed 482 for i := 0; i < 3; i++ { 483 unsigned := dummyTx(h) 484 resp, err := r.ApproveTx(unsigned) 485 if err != nil { 486 t.Errorf("Unexpected error %v", err) 487 } 488 if !resp.Approved { 489 t.Errorf("Expected check to resolve to 'Approve'") 490 } 491 // Create a dummy signed transaction 492 493 response := ethapi.SignTransactionResult{ 494 Tx: dummySigned(v), 495 Raw: common.Hex2Bytes("deadbeef"), 496 } 497 r.OnApprovedTx(response) 498 } 499 // Fourth should fail 500 resp, err := r.ApproveTx(dummyTx(h)) 501 if resp.Approved { 502 t.Errorf("Expected check to resolve to 'Reject'") 503 } 504 505 } 506 507 // dontCallMe is used as a next-handler that does not want to be called - it invokes test failure 508 type dontCallMe struct { 509 t *testing.T 510 } 511 512 func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { 513 } 514 515 func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { 516 d.t.Fatalf("Did not expect next-handler to be called") 517 return core.SignTxResponse{}, core.ErrRequestDenied 518 } 519 520 func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { 521 d.t.Fatalf("Did not expect next-handler to be called") 522 return core.SignDataResponse{}, core.ErrRequestDenied 523 } 524 525 func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { 526 d.t.Fatalf("Did not expect next-handler to be called") 527 return core.ExportResponse{}, core.ErrRequestDenied 528 } 529 530 func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { 531 d.t.Fatalf("Did not expect next-handler to be called") 532 return core.ImportResponse{}, core.ErrRequestDenied 533 } 534 535 func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { 536 d.t.Fatalf("Did not expect next-handler to be called") 537 return core.ListResponse{}, core.ErrRequestDenied 538 } 539 540 func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { 541 d.t.Fatalf("Did not expect next-handler to be called") 542 return core.NewAccountResponse{}, core.ErrRequestDenied 543 } 544 545 func (d *dontCallMe) ShowError(message string) { 546 d.t.Fatalf("Did not expect next-handler to be called") 547 } 548 549 func (d *dontCallMe) ShowInfo(message string) { 550 d.t.Fatalf("Did not expect next-handler to be called") 551 } 552 553 func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) { 554 d.t.Fatalf("Did not expect next-handler to be called") 555 } 556 557 //TestContextIsCleared tests that the rule-engine does not retain variables over several requests. 558 // if it does, that would be bad since developers may rely on that to store data, 559 // instead of using the disk-based data storage 560 func TestContextIsCleared(t *testing.T) { 561 562 js := ` 563 function ApproveTx(){ 564 if (typeof foobar == 'undefined') { 565 foobar = "Approve" 566 } 567 console.log(foobar) 568 if (foobar == "Approve"){ 569 foobar = "Reject" 570 }else{ 571 foobar = "Approve" 572 } 573 return foobar 574 } 575 ` 576 ui := &dontCallMe{t} 577 r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) 578 if err != nil { 579 t.Fatalf("Failed to create js engine: %v", err) 580 } 581 if err = r.Init(js); err != nil { 582 t.Fatalf("Failed to load bootstrap js: %v", err) 583 } 584 tx := dummyTxWithV(0) 585 r1, err := r.ApproveTx(tx) 586 r2, err := r.ApproveTx(tx) 587 if r1.Approved != r2.Approved { 588 t.Errorf("Expected execution context to be cleared between executions") 589 } 590 } 591 592 func TestSignData(t *testing.T) { 593 594 js := `function ApproveListing(){ 595 return "Approve" 596 } 597 function ApproveSignData(r){ 598 if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") 599 { 600 if(r.message.indexOf("bazonk") >= 0){ 601 return "Approve" 602 } 603 return "Reject" 604 } 605 // Otherwise goes to manual processing 606 }` 607 r, err := initRuleEngine(js) 608 if err != nil { 609 t.Errorf("Couldn't create evaluator %v", err) 610 return 611 } 612 message := []byte("baz bazonk foo") 613 hash, msg := core.SignHash(message) 614 raw := hexutil.Bytes(message) 615 addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") 616 617 fmt.Printf("address %v %v\n", addr.String(), addr.Original()) 618 resp, err := r.ApproveSignData(&core.SignDataRequest{ 619 Address: *addr, 620 Message: msg, 621 Hash: hash, 622 Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, 623 Rawdata: raw, 624 }) 625 if err != nil { 626 t.Fatalf("Unexpected error %v", err) 627 } 628 if !resp.Approved { 629 t.Fatalf("Expected approved") 630 } 631 }