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