You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

658 lines
17 KiB

3 years ago
3 years ago
  1. package api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "math/big"
  10. "net/http"
  11. "os"
  12. "strconv"
  13. "testing"
  14. "time"
  15. ethCommon "github.com/ethereum/go-ethereum/common"
  16. swagger "github.com/getkin/kin-openapi/openapi3filter"
  17. "github.com/gin-gonic/gin"
  18. "github.com/hermeznetwork/hermez-node/common"
  19. "github.com/hermeznetwork/hermez-node/db"
  20. "github.com/hermeznetwork/hermez-node/db/historydb"
  21. "github.com/hermeznetwork/hermez-node/db/l2db"
  22. "github.com/hermeznetwork/hermez-node/db/statedb"
  23. "github.com/hermeznetwork/hermez-node/log"
  24. "github.com/hermeznetwork/hermez-node/test"
  25. "github.com/hermeznetwork/hermez-node/test/til"
  26. )
  27. // Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
  28. // when testing paginated endpoints.
  29. type Pendinger interface {
  30. GetPending() (pendingItems, lastItemID uint64)
  31. Len() int
  32. New() Pendinger
  33. }
  34. const apiPort = ":4010"
  35. const apiURL = "http://localhost" + apiPort + "/"
  36. var SetBlockchain = `
  37. Type: Blockchain
  38. AddToken(1)
  39. AddToken(2)
  40. AddToken(3)
  41. AddToken(4)
  42. AddToken(5)
  43. AddToken(6)
  44. AddToken(7)
  45. AddToken(8)
  46. > block
  47. // Coordinator accounts, Idxs: 256, 257
  48. CreateAccountCoordinator(0) Coord
  49. CreateAccountCoordinator(1) Coord
  50. // close Block:0, Batch:0
  51. > batch
  52. CreateAccountDeposit(0) A: 111111111
  53. CreateAccountDeposit(1) C: 222222222
  54. CreateAccountCoordinator(0) C
  55. // close Block:0, Batch:1
  56. > batchL1
  57. // Expected balances:
  58. // Coord(0): 0, Coord(1): 0
  59. // C(0): 0
  60. CreateAccountDeposit(1) A: 333333333
  61. // close Block:0, Batch:2
  62. > batchL1
  63. // close Block:0, Batch:3
  64. > batchL1
  65. CreateAccountDepositTransfer(0) B-A: 444444444, 1234444444 // to_eth_addr is NULL
  66. // close Block:0, Batch:4
  67. > batchL1
  68. CreateAccountDeposit(0) D: 555555555
  69. // close Block:0, Batch:5
  70. > batchL1
  71. CreateAccountCoordinator(1) B
  72. Transfer(1) A-B: 111111 (2) // to_eth_addr is NULL
  73. Transfer(0) B-C: 222222 (3)
  74. // close Block:0, Batch:6
  75. > batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2}
  76. Deposit(0) C: 666666666
  77. DepositTransfer(0) C-D: 777777777, 123777777 // to_eth_addr is NULL
  78. Transfer(0) A-B: 333333 (111)
  79. Transfer(0) C-A: 444444 (222)
  80. Transfer(1) B-C: 555555 (123)
  81. Exit(0) A: 666666 (44)
  82. ForceTransfer(0) D-B: 777777 // to_eth_addr is NULL
  83. ForceExit(0) B: 888888
  84. // close Block:0, Batch:7
  85. > batchL1
  86. > block
  87. Transfer(0) D-A: 999999 (77)
  88. Transfer(0) B-D: 123123 (55)
  89. // close Block:1, Batch:0
  90. > batchL1
  91. CreateAccountCoordinator(0) F
  92. CreateAccountCoordinator(0) G
  93. CreateAccountCoordinator(0) H
  94. CreateAccountCoordinator(0) I
  95. CreateAccountCoordinator(0) J
  96. CreateAccountCoordinator(0) K
  97. CreateAccountCoordinator(0) L
  98. CreateAccountCoordinator(0) M
  99. CreateAccountCoordinator(0) N
  100. CreateAccountCoordinator(0) O
  101. CreateAccountCoordinator(0) P
  102. CreateAccountCoordinator(5) G
  103. CreateAccountCoordinator(5) H
  104. CreateAccountCoordinator(5) I
  105. CreateAccountCoordinator(5) J
  106. CreateAccountCoordinator(5) K
  107. CreateAccountCoordinator(5) L
  108. CreateAccountCoordinator(5) M
  109. CreateAccountCoordinator(5) N
  110. CreateAccountCoordinator(5) O
  111. CreateAccountCoordinator(5) P
  112. CreateAccountCoordinator(2) G
  113. CreateAccountCoordinator(2) H
  114. CreateAccountCoordinator(2) I
  115. CreateAccountCoordinator(2) J
  116. CreateAccountCoordinator(2) K
  117. CreateAccountCoordinator(2) L
  118. CreateAccountCoordinator(2) M
  119. CreateAccountCoordinator(2) N
  120. CreateAccountCoordinator(2) O
  121. CreateAccountCoordinator(2) P
  122. > batch
  123. > block
  124. > batch
  125. > block
  126. > batch
  127. > block
  128. `
  129. type testCommon struct {
  130. blocks []common.Block
  131. tokens []historydb.TokenWithUSD
  132. batches []testBatch
  133. fullBatches []testFullBatch
  134. coordinators []historydb.CoordinatorAPI
  135. accounts []testAccount
  136. txs []testTx
  137. exits []testExit
  138. poolTxsToSend []testPoolTxSend
  139. poolTxsToReceive []testPoolTxReceive
  140. auths []testAuth
  141. router *swagger.Router
  142. bids []testBid
  143. slots []testSlot
  144. auctionVars common.AuctionVariables
  145. rollupVars common.RollupVariables
  146. wdelayerVars common.WDelayerVariables
  147. }
  148. var tc testCommon
  149. var config configAPI
  150. var api *API
  151. // TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
  152. // emulating the task of the synchronizer in order to have data to be returned
  153. // by the API endpoints that will be tested
  154. func TestMain(m *testing.M) {
  155. // Initializations
  156. // Swagger
  157. router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
  158. // HistoryDB
  159. pass := os.Getenv("POSTGRES_PASS")
  160. database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
  161. if err != nil {
  162. panic(err)
  163. }
  164. hdb := historydb.NewHistoryDB(database)
  165. if err != nil {
  166. panic(err)
  167. }
  168. // StateDB
  169. dir, err := ioutil.TempDir("", "tmpdb")
  170. if err != nil {
  171. panic(err)
  172. }
  173. defer func() {
  174. if err := os.RemoveAll(dir); err != nil {
  175. panic(err)
  176. }
  177. }()
  178. sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
  179. if err != nil {
  180. panic(err)
  181. }
  182. // L2DB
  183. l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
  184. test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
  185. // Config (smart contract constants)
  186. config = getConfigTest()
  187. // API
  188. apiGin := gin.Default()
  189. api, err = NewAPI(
  190. true,
  191. true,
  192. apiGin,
  193. hdb,
  194. sdb,
  195. l2DB,
  196. &config,
  197. )
  198. if err != nil {
  199. panic(err)
  200. }
  201. // Start server
  202. server := &http.Server{Addr: apiPort, Handler: apiGin}
  203. go func() {
  204. if err := server.ListenAndServe(); err != nil &&
  205. err != http.ErrServerClosed {
  206. panic(err)
  207. }
  208. }()
  209. // Reset DB
  210. test.WipeDB(api.h.DB())
  211. // Genratre blockchain data with til
  212. tcc := til.NewContext(common.RollupConstMaxL1UserTx)
  213. tilCfgExtra := til.ConfigExtra{
  214. BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
  215. CoordUser: "Coord",
  216. }
  217. blocksData, err := tcc.GenerateBlocks(SetBlockchain)
  218. if err != nil {
  219. panic(err)
  220. }
  221. err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
  222. if err != nil {
  223. panic(err)
  224. }
  225. AddAditionalInformation(blocksData)
  226. // Generate L2 Txs with til
  227. commonPoolTxs, err := tcc.GeneratePoolL2Txs(til.SetPoolL2MinimumFlow0)
  228. if err != nil {
  229. panic(err)
  230. }
  231. // Extract til generated data, and add it to HistoryDB
  232. var commonBlocks []common.Block
  233. var commonBatches []common.Batch
  234. var commonAccounts []common.Account
  235. var commonExitTree []common.ExitInfo
  236. var commonL1Txs []common.L1Tx
  237. var commonL2Txs []common.L2Tx
  238. // Add ETH token at the beginning of the array
  239. testTokens := []historydb.TokenWithUSD{}
  240. ethUSD := float64(500)
  241. ethNow := time.Now()
  242. testTokens = append(testTokens, historydb.TokenWithUSD{
  243. TokenID: test.EthToken.TokenID,
  244. EthBlockNum: test.EthToken.EthBlockNum,
  245. EthAddr: test.EthToken.EthAddr,
  246. Name: test.EthToken.Name,
  247. Symbol: test.EthToken.Symbol,
  248. Decimals: test.EthToken.Decimals,
  249. USD: &ethUSD,
  250. USDUpdate: &ethNow,
  251. })
  252. err = api.h.UpdateTokenValue(test.EthToken.Symbol, ethUSD)
  253. if err != nil {
  254. panic(err)
  255. }
  256. for _, block := range blocksData {
  257. // Insert block into HistoryDB
  258. if err := api.h.AddBlockSCData(&block); err != nil { //nolint:gosec block is used as read only in the function
  259. panic(err)
  260. }
  261. // Extract data
  262. commonBlocks = append(commonBlocks, block.Block)
  263. for i, tkn := range block.Rollup.AddedTokens {
  264. token := historydb.TokenWithUSD{
  265. TokenID: tkn.TokenID,
  266. EthBlockNum: tkn.EthBlockNum,
  267. EthAddr: tkn.EthAddr,
  268. Name: tkn.Name,
  269. Symbol: tkn.Symbol,
  270. Decimals: tkn.Decimals,
  271. }
  272. value := float64(i + 423)
  273. now := time.Now().UTC()
  274. token.USD = &value
  275. token.USDUpdate = &now
  276. // Set value in DB
  277. err = api.h.UpdateTokenValue(token.Symbol, value)
  278. if err != nil {
  279. panic(err)
  280. }
  281. testTokens = append(testTokens, token)
  282. }
  283. // Set USD value for tokens in DB
  284. commonL1Txs = append(commonL1Txs, block.Rollup.L1UserTxs...)
  285. for _, batch := range block.Rollup.Batches {
  286. commonL2Txs = append(commonL2Txs, batch.L2Txs...)
  287. commonAccounts = append(commonAccounts, batch.CreatedAccounts...)
  288. commonBatches = append(commonBatches, batch.Batch)
  289. commonExitTree = append(commonExitTree, batch.ExitTree...)
  290. commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...)
  291. }
  292. }
  293. // lastBlockNum2 := blocksData[len(blocksData)-1].Block.EthBlockNum
  294. // Add accounts to StateDB
  295. for i := 0; i < len(commonAccounts); i++ {
  296. if _, err := api.s.CreateAccount(commonAccounts[i].Idx, &commonAccounts[i]); err != nil {
  297. panic(err)
  298. }
  299. }
  300. // Generate Coordinators and add them to HistoryDB
  301. const nCoords = 10
  302. commonCoords := test.GenCoordinators(nCoords, commonBlocks)
  303. if err := api.h.AddCoordinators(commonCoords); err != nil {
  304. panic(err)
  305. }
  306. // Generate Bids and add them to HistoryDB
  307. const nBids = 20
  308. commonBids := test.GenBids(nBids, commonBlocks, commonCoords)
  309. if err = api.h.AddBids(commonBids); err != nil {
  310. panic(err)
  311. }
  312. // Generate SC vars and add them to HistoryDB (if needed)
  313. var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)}
  314. auctionVars := common.AuctionVariables{
  315. EthBlockNum: int64(2),
  316. DonationAddress: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  317. DefaultSlotSetBid: defaultSlotSetBid,
  318. Outbidding: uint16(1),
  319. SlotDeadline: uint8(20),
  320. BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  321. ClosedAuctionSlots: uint16(2),
  322. OpenAuctionSlots: uint16(5),
  323. }
  324. rollupVars := common.RollupVariables{
  325. EthBlockNum: int64(3),
  326. FeeAddToken: big.NewInt(100),
  327. ForgeL1L2BatchTimeout: int64(44),
  328. WithdrawalDelay: uint64(3000),
  329. }
  330. wdelayerVars := common.WDelayerVariables{
  331. WithdrawalDelay: uint64(3000),
  332. }
  333. err = api.h.AddAuctionVars(&auctionVars)
  334. if err != nil {
  335. panic(err)
  336. }
  337. // Generate test data, as expected to be received/sended from/to the API
  338. testCoords := genTestCoordinators(commonCoords)
  339. testBids := genTestBids(commonBlocks, testCoords, commonBids)
  340. testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
  341. testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
  342. testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
  343. poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
  344. tc = testCommon{
  345. blocks: commonBlocks,
  346. tokens: testTokens,
  347. batches: testBatches,
  348. fullBatches: testFullBatches,
  349. coordinators: testCoords,
  350. accounts: genTestAccounts(commonAccounts, testTokens),
  351. txs: testTxs,
  352. exits: testExits,
  353. poolTxsToSend: poolTxsToSend,
  354. poolTxsToReceive: poolTxsToReceive,
  355. auths: genTestAuths(test.GenAuths(5)),
  356. router: router,
  357. bids: testBids,
  358. slots: api.genTestSlots(
  359. 20,
  360. commonBlocks[len(commonBlocks)-1].EthBlockNum,
  361. testBids,
  362. auctionVars,
  363. ),
  364. auctionVars: auctionVars,
  365. rollupVars: rollupVars,
  366. wdelayerVars: wdelayerVars,
  367. }
  368. // Fake server
  369. if os.Getenv("FAKE_SERVER") == "yes" {
  370. for {
  371. log.Info("Running fake server at " + apiURL + " until ^C is received")
  372. time.Sleep(30 * time.Second)
  373. }
  374. }
  375. // Run tests
  376. result := m.Run()
  377. // Stop server
  378. if err := server.Shutdown(context.Background()); err != nil {
  379. panic(err)
  380. }
  381. if err := database.Close(); err != nil {
  382. panic(err)
  383. }
  384. if err := os.RemoveAll(dir); err != nil {
  385. panic(err)
  386. }
  387. os.Exit(result)
  388. }
  389. func doGoodReqPaginated(
  390. path, order string,
  391. iterStruct Pendinger,
  392. appendIter func(res interface{}),
  393. ) error {
  394. var next uint64
  395. firstIte := true
  396. expectedTotal := 0
  397. totalReceived := 0
  398. for {
  399. // Calculate fromItem
  400. iterPath := path
  401. if firstIte {
  402. if order == historydb.OrderDesc {
  403. // Fetch first item in reverse order
  404. iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
  405. } else {
  406. iterPath += "0"
  407. }
  408. } else {
  409. iterPath += strconv.Itoa(int(next))
  410. }
  411. // Call API to get this iteration items
  412. iterStruct = iterStruct.New()
  413. if err := doGoodReq(
  414. "GET", iterPath+"&order="+order, nil,
  415. iterStruct,
  416. ); err != nil {
  417. return err
  418. }
  419. appendIter(iterStruct)
  420. // Keep iterating?
  421. remaining, lastID := iterStruct.GetPending()
  422. if remaining == 0 {
  423. break
  424. }
  425. if order == historydb.OrderDesc {
  426. next = lastID - 1
  427. } else {
  428. next = lastID + 1
  429. }
  430. // Check that the expected amount of items is consistent across iterations
  431. totalReceived += iterStruct.Len()
  432. if firstIte {
  433. firstIte = false
  434. expectedTotal = totalReceived + int(remaining)
  435. }
  436. if expectedTotal != totalReceived+int(remaining) {
  437. panic(fmt.Sprintf(
  438. "pagination error, totalReceived + remaining should be %d, but is %d",
  439. expectedTotal, totalReceived+int(remaining),
  440. ))
  441. }
  442. }
  443. return nil
  444. }
  445. func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
  446. ctx := context.Background()
  447. client := &http.Client{}
  448. httpReq, err := http.NewRequest(method, path, reqBody)
  449. if err != nil {
  450. return err
  451. }
  452. if reqBody != nil {
  453. httpReq.Header.Add("Content-Type", "application/json")
  454. }
  455. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  456. if err != nil {
  457. return err
  458. }
  459. // Validate request against swagger spec
  460. requestValidationInput := &swagger.RequestValidationInput{
  461. Request: httpReq,
  462. PathParams: pathParams,
  463. Route: route,
  464. }
  465. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  466. return err
  467. }
  468. // Do API call
  469. resp, err := client.Do(httpReq)
  470. if err != nil {
  471. return err
  472. }
  473. if resp.Body == nil && returnStruct != nil {
  474. return errors.New("Nil body")
  475. }
  476. //nolint
  477. defer resp.Body.Close()
  478. body, err := ioutil.ReadAll(resp.Body)
  479. if err != nil {
  480. return err
  481. }
  482. if resp.StatusCode != 200 {
  483. return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
  484. }
  485. if returnStruct == nil {
  486. return nil
  487. }
  488. // Unmarshal body into return struct
  489. if err := json.Unmarshal(body, returnStruct); err != nil {
  490. log.Error("invalid json: " + string(body))
  491. log.Error(err)
  492. return err
  493. }
  494. // log.Info(string(body))
  495. // Validate response against swagger spec
  496. responseValidationInput := &swagger.ResponseValidationInput{
  497. RequestValidationInput: requestValidationInput,
  498. Status: resp.StatusCode,
  499. Header: resp.Header,
  500. }
  501. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  502. return swagger.ValidateResponse(ctx, responseValidationInput)
  503. }
  504. func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
  505. ctx := context.Background()
  506. client := &http.Client{}
  507. httpReq, _ := http.NewRequest(method, path, reqBody)
  508. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  509. if err != nil {
  510. return err
  511. }
  512. // Validate request against swagger spec
  513. requestValidationInput := &swagger.RequestValidationInput{
  514. Request: httpReq,
  515. PathParams: pathParams,
  516. Route: route,
  517. }
  518. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  519. if expectedResponseCode != 400 {
  520. return err
  521. }
  522. log.Warn("The request does not match the API spec")
  523. }
  524. // Do API call
  525. resp, err := client.Do(httpReq)
  526. if err != nil {
  527. return err
  528. }
  529. if resp.Body == nil {
  530. return errors.New("Nil body")
  531. }
  532. //nolint
  533. defer resp.Body.Close()
  534. body, err := ioutil.ReadAll(resp.Body)
  535. if err != nil {
  536. return err
  537. }
  538. if resp.StatusCode != expectedResponseCode {
  539. return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
  540. }
  541. // Validate response against swagger spec
  542. responseValidationInput := &swagger.ResponseValidationInput{
  543. RequestValidationInput: requestValidationInput,
  544. Status: resp.StatusCode,
  545. Header: resp.Header,
  546. }
  547. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  548. return swagger.ValidateResponse(ctx, responseValidationInput)
  549. }
  550. // test helpers
  551. func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
  552. for i := 0; i < len(blocks); i++ {
  553. if blocks[i].EthBlockNum == blockNum {
  554. return blocks[i].Timestamp
  555. }
  556. }
  557. panic("timesamp not found")
  558. }
  559. func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
  560. for i := 0; i < len(tokens); i++ {
  561. if tokens[i].TokenID == id {
  562. return tokens[i]
  563. }
  564. }
  565. panic("token not found")
  566. }
  567. func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
  568. for _, acc := range accs {
  569. if idx == acc.Idx {
  570. return getTokenByID(acc.TokenID, tokens)
  571. }
  572. }
  573. panic("token not found")
  574. }
  575. func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
  576. for _, acc := range accs {
  577. if acc.Idx == idx {
  578. return &acc
  579. }
  580. }
  581. panic("account not found")
  582. }
  583. func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
  584. for _, b := range blocks {
  585. if b.EthBlockNum == ethBlockNum {
  586. return b
  587. }
  588. }
  589. panic("block not found")
  590. }
  591. func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
  592. for _, c := range coordinators {
  593. if c.Bidder == bidder {
  594. return c
  595. }
  596. }
  597. panic("coordinator not found")
  598. }