rules_test.go 17 KB

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