rules_test.go 17 KB

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