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.

559 lines
14 KiB

4 years ago
4 years ago
4 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. "sort"
  13. "strconv"
  14. "testing"
  15. "time"
  16. ethCommon "github.com/ethereum/go-ethereum/common"
  17. swagger "github.com/getkin/kin-openapi/openapi3filter"
  18. "github.com/gin-gonic/gin"
  19. "github.com/hermeznetwork/hermez-node/common"
  20. "github.com/hermeznetwork/hermez-node/db"
  21. "github.com/hermeznetwork/hermez-node/db/historydb"
  22. "github.com/hermeznetwork/hermez-node/db/l2db"
  23. "github.com/hermeznetwork/hermez-node/db/statedb"
  24. "github.com/hermeznetwork/hermez-node/log"
  25. "github.com/hermeznetwork/hermez-node/test"
  26. "github.com/iden3/go-iden3-crypto/babyjub"
  27. )
  28. // Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
  29. // when testing paginated endpoints.
  30. type Pendinger interface {
  31. GetPending() (pendingItems, lastItemID uint64)
  32. Len() int
  33. }
  34. const apiPort = ":4010"
  35. const apiURL = "http://localhost" + apiPort + "/"
  36. type testCommon struct {
  37. blocks []common.Block
  38. tokens []historydb.TokenWithUSD
  39. batches []testBatch
  40. fullBatches []testFullBatch
  41. coordinators []historydb.CoordinatorAPI
  42. accounts []testAccount
  43. usrAddr string
  44. usrBjj string
  45. accs []common.Account
  46. usrTxs []testTx
  47. allTxs []testTx
  48. exits []testExit
  49. usrExits []testExit
  50. poolTxsToSend []testPoolTxSend
  51. poolTxsToReceive []testPoolTxReceive
  52. auths []testAuth
  53. router *swagger.Router
  54. bids []testBid
  55. slots []testSlot
  56. auctionVars common.AuctionVariables
  57. }
  58. var tc testCommon
  59. var config configAPI
  60. var api *API
  61. // TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
  62. // emulating the task of the synchronizer in order to have data to be returned
  63. // by the API endpoints that will be tested
  64. func TestMain(m *testing.M) {
  65. // Initializations
  66. // Swagger
  67. router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
  68. // HistoryDB
  69. pass := os.Getenv("POSTGRES_PASS")
  70. database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
  71. if err != nil {
  72. panic(err)
  73. }
  74. hdb := historydb.NewHistoryDB(database)
  75. if err != nil {
  76. panic(err)
  77. }
  78. // StateDB
  79. dir, err := ioutil.TempDir("", "tmpdb")
  80. if err != nil {
  81. panic(err)
  82. }
  83. defer func() {
  84. if err := os.RemoveAll(dir); err != nil {
  85. panic(err)
  86. }
  87. }()
  88. sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
  89. if err != nil {
  90. panic(err)
  91. }
  92. // L2DB
  93. l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
  94. test.WipeDB(l2DB.DB()) // this will clean HistoryDB and L2DB
  95. // Config (smart contract constants)
  96. config = getConfigTest()
  97. // API
  98. apiGin := gin.Default()
  99. api, err = NewAPI(
  100. true,
  101. true,
  102. apiGin,
  103. hdb,
  104. sdb,
  105. l2DB,
  106. &config,
  107. )
  108. if err != nil {
  109. panic(err)
  110. }
  111. // Start server
  112. server := &http.Server{Addr: apiPort, Handler: apiGin}
  113. go func() {
  114. if err := server.ListenAndServe(); err != nil &&
  115. err != http.ErrServerClosed {
  116. panic(err)
  117. }
  118. }()
  119. // Fill HistoryDB and StateDB with fake data
  120. // Gen blocks and add them to DB
  121. const nBlocks = 5
  122. blocks := test.GenBlocks(1, nBlocks+1)
  123. err = api.h.AddBlocks(blocks)
  124. if err != nil {
  125. panic(err)
  126. }
  127. lastBlockNum := blocks[nBlocks-1].EthBlockNum
  128. // Gen tokens and add them to DB
  129. const nTokens = 10
  130. tokens, ethToken := test.GenTokens(nTokens, blocks)
  131. err = api.h.AddTokens(tokens)
  132. if err != nil {
  133. panic(err)
  134. }
  135. tokens = append([]common.Token{ethToken}, tokens...)
  136. // Set token value
  137. tokensUSD := []historydb.TokenWithUSD{}
  138. for i, tkn := range tokens {
  139. token := historydb.TokenWithUSD{
  140. TokenID: tkn.TokenID,
  141. EthBlockNum: tkn.EthBlockNum,
  142. EthAddr: tkn.EthAddr,
  143. Name: tkn.Name,
  144. Symbol: tkn.Symbol,
  145. Decimals: tkn.Decimals,
  146. }
  147. // Set value of 50% of the tokens
  148. if i%2 != 0 {
  149. value := float64(i) * 1.234567
  150. now := time.Now().UTC()
  151. token.USD = &value
  152. token.USDUpdate = &now
  153. err = api.h.UpdateTokenValue(token.Symbol, value)
  154. if err != nil {
  155. panic(err)
  156. }
  157. }
  158. tokensUSD = append(tokensUSD, token)
  159. }
  160. // Gen batches and add them to DB
  161. const nBatches = 10
  162. batches := test.GenBatches(nBatches, blocks)
  163. err = api.h.AddBatches(batches)
  164. if err != nil {
  165. panic(err)
  166. }
  167. // Gen accounts and add them to HistoryDB and StateDB
  168. const totalAccounts = 40
  169. const userAccounts = 4
  170. usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
  171. privK := babyjub.NewRandPrivKey()
  172. usrBjj := privK.Public()
  173. accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
  174. err = api.h.AddAccounts(accs)
  175. if err != nil {
  176. panic(err)
  177. }
  178. for i := 0; i < len(accs); i++ {
  179. if _, err := api.s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
  180. panic(err)
  181. }
  182. }
  183. // helper to vinculate user related resources
  184. usrIdxs := []string{}
  185. for _, acc := range accs {
  186. if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
  187. for _, token := range tokens {
  188. if token.TokenID == acc.TokenID {
  189. usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
  190. }
  191. }
  192. }
  193. }
  194. // Gen exits and add them to DB
  195. const totalExits = 40
  196. exits := test.GenExitTree(totalExits, batches, accs)
  197. err = api.h.AddExitTree(exits)
  198. if err != nil {
  199. panic(err)
  200. }
  201. // L1 and L2 txs need to be sorted in a combined way
  202. // Gen L1Txs
  203. const totalL1Txs = 40
  204. const userL1Txs = 4
  205. usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
  206. // Gen L2Txs
  207. const totalL2Txs = 20
  208. const userL2Txs = 4
  209. usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
  210. // Sort txs
  211. sortedTxs := []txSortFielder{}
  212. for i := 0; i < len(usrL1Txs); i++ {
  213. wL1 := wrappedL1(usrL1Txs[i])
  214. sortedTxs = append(sortedTxs, &wL1)
  215. }
  216. for i := 0; i < len(othrL1Txs); i++ {
  217. wL1 := wrappedL1(othrL1Txs[i])
  218. sortedTxs = append(sortedTxs, &wL1)
  219. }
  220. for i := 0; i < len(usrL2Txs); i++ {
  221. wL2 := wrappedL2(usrL2Txs[i])
  222. sortedTxs = append(sortedTxs, &wL2)
  223. }
  224. for i := 0; i < len(othrL2Txs); i++ {
  225. wL2 := wrappedL2(othrL2Txs[i])
  226. sortedTxs = append(sortedTxs, &wL2)
  227. }
  228. sort.Sort(txsSort(sortedTxs))
  229. // Store txs to DB
  230. for _, genericTx := range sortedTxs {
  231. l1 := genericTx.L1()
  232. l2 := genericTx.L2()
  233. if l1 != nil {
  234. err = api.h.AddL1Txs([]common.L1Tx{*l1})
  235. if err != nil {
  236. panic(err)
  237. }
  238. } else if l2 != nil {
  239. err = api.h.AddL2Txs([]common.L2Tx{*l2})
  240. if err != nil {
  241. panic(err)
  242. }
  243. } else {
  244. panic("should be l1 or l2")
  245. }
  246. }
  247. // Coordinators
  248. const nCoords = 10
  249. coords := test.GenCoordinators(nCoords, blocks)
  250. err = api.h.AddCoordinators(coords)
  251. if err != nil {
  252. panic(err)
  253. }
  254. fromItem := uint(0)
  255. limit := uint(99999)
  256. coordinators, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
  257. if err != nil {
  258. panic(err)
  259. }
  260. // Bids
  261. const nBids = 20
  262. bids := test.GenBids(nBids, blocks, coords)
  263. err = api.h.AddBids(bids)
  264. if err != nil {
  265. panic(err)
  266. }
  267. testBids := genTestBids(blocks, coordinators, bids)
  268. // Vars
  269. auctionVars := common.AuctionVariables{
  270. BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
  271. ClosedAuctionSlots: uint16(2),
  272. OpenAuctionSlots: uint16(5),
  273. }
  274. err = api.h.AddAuctionVars(&auctionVars)
  275. if err != nil {
  276. panic(err)
  277. }
  278. const nSlots = 20
  279. // Set testCommon
  280. usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
  281. poolTxsToSend, poolTxsToReceive := genTestPoolTx(accs, []babyjub.PrivateKey{privK}, tokensUSD) // NOTE: pool txs are not inserted to the DB here. In the test they will be posted and getted.
  282. testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
  283. usrExits, allExits := genTestExits(exits, tokensUSD, accs, usrIdxs)
  284. tc = testCommon{
  285. blocks: blocks,
  286. tokens: tokensUSD,
  287. batches: testBatches,
  288. fullBatches: fullBatches,
  289. coordinators: coordinators,
  290. accounts: genTestAccounts(accs, tokensUSD),
  291. usrAddr: ethAddrToHez(usrAddr),
  292. usrBjj: bjjToString(usrBjj),
  293. accs: accs,
  294. usrTxs: usrTxs,
  295. allTxs: allTxs,
  296. exits: allExits,
  297. usrExits: usrExits,
  298. poolTxsToSend: poolTxsToSend,
  299. poolTxsToReceive: poolTxsToReceive,
  300. auths: genTestAuths(test.GenAuths(5)),
  301. router: router,
  302. bids: testBids,
  303. slots: api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
  304. auctionVars: auctionVars,
  305. }
  306. // Fake server
  307. if os.Getenv("FAKE_SERVER") == "yes" {
  308. for {
  309. log.Info("Running fake server at " + apiURL + " until ^C is received")
  310. time.Sleep(30 * time.Second)
  311. }
  312. }
  313. // Run tests
  314. result := m.Run()
  315. // Stop server
  316. if err := server.Shutdown(context.Background()); err != nil {
  317. panic(err)
  318. }
  319. if err := database.Close(); err != nil {
  320. panic(err)
  321. }
  322. if err := os.RemoveAll(dir); err != nil {
  323. panic(err)
  324. }
  325. os.Exit(result)
  326. }
  327. func doGoodReqPaginated(
  328. path, order string,
  329. iterStruct Pendinger,
  330. appendIter func(res interface{}),
  331. ) error {
  332. var next uint64
  333. firstIte := true
  334. expectedTotal := 0
  335. totalReceived := 0
  336. for {
  337. // Calculate fromItem
  338. iterPath := path
  339. if firstIte {
  340. if order == historydb.OrderDesc {
  341. // Fetch first item in reverse order
  342. iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
  343. } else {
  344. iterPath += "0"
  345. }
  346. } else {
  347. iterPath += strconv.Itoa(int(next))
  348. }
  349. // Call API to get this iteration items
  350. if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
  351. return err
  352. }
  353. appendIter(iterStruct)
  354. // Keep iterating?
  355. remaining, lastID := iterStruct.GetPending()
  356. if remaining == 0 {
  357. break
  358. }
  359. if order == historydb.OrderDesc {
  360. next = lastID - 1
  361. } else {
  362. next = lastID + 1
  363. }
  364. // Check that the expected amount of items is consistent across iterations
  365. totalReceived += iterStruct.Len()
  366. if firstIte {
  367. firstIte = false
  368. expectedTotal = totalReceived + int(remaining)
  369. }
  370. if expectedTotal != totalReceived+int(remaining) {
  371. panic(fmt.Sprintf(
  372. "pagination error, totalReceived + remaining should be %d, but is %d",
  373. expectedTotal, totalReceived+int(remaining),
  374. ))
  375. }
  376. }
  377. return nil
  378. }
  379. func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
  380. ctx := context.Background()
  381. client := &http.Client{}
  382. httpReq, err := http.NewRequest(method, path, reqBody)
  383. if err != nil {
  384. return err
  385. }
  386. if reqBody != nil {
  387. httpReq.Header.Add("Content-Type", "application/json")
  388. }
  389. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  390. if err != nil {
  391. return err
  392. }
  393. // Validate request against swagger spec
  394. requestValidationInput := &swagger.RequestValidationInput{
  395. Request: httpReq,
  396. PathParams: pathParams,
  397. Route: route,
  398. }
  399. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  400. return err
  401. }
  402. // Do API call
  403. resp, err := client.Do(httpReq)
  404. if err != nil {
  405. return err
  406. }
  407. if resp.Body == nil && returnStruct != nil {
  408. return errors.New("Nil body")
  409. }
  410. //nolint
  411. defer resp.Body.Close()
  412. body, err := ioutil.ReadAll(resp.Body)
  413. if err != nil {
  414. return err
  415. }
  416. if resp.StatusCode != 200 {
  417. return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
  418. }
  419. if returnStruct == nil {
  420. return nil
  421. }
  422. // Unmarshal body into return struct
  423. if err := json.Unmarshal(body, returnStruct); err != nil {
  424. log.Error("invalid json: " + string(body))
  425. return err
  426. }
  427. // Validate response against swagger spec
  428. responseValidationInput := &swagger.ResponseValidationInput{
  429. RequestValidationInput: requestValidationInput,
  430. Status: resp.StatusCode,
  431. Header: resp.Header,
  432. }
  433. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  434. return swagger.ValidateResponse(ctx, responseValidationInput)
  435. }
  436. func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
  437. ctx := context.Background()
  438. client := &http.Client{}
  439. httpReq, _ := http.NewRequest(method, path, reqBody)
  440. route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
  441. if err != nil {
  442. return err
  443. }
  444. // Validate request against swagger spec
  445. requestValidationInput := &swagger.RequestValidationInput{
  446. Request: httpReq,
  447. PathParams: pathParams,
  448. Route: route,
  449. }
  450. if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
  451. if expectedResponseCode != 400 {
  452. return err
  453. }
  454. log.Warn("The request does not match the API spec")
  455. }
  456. // Do API call
  457. resp, err := client.Do(httpReq)
  458. if err != nil {
  459. return err
  460. }
  461. if resp.Body == nil {
  462. return errors.New("Nil body")
  463. }
  464. //nolint
  465. defer resp.Body.Close()
  466. body, err := ioutil.ReadAll(resp.Body)
  467. if err != nil {
  468. return err
  469. }
  470. if resp.StatusCode != expectedResponseCode {
  471. return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
  472. }
  473. // Validate response against swagger spec
  474. responseValidationInput := &swagger.ResponseValidationInput{
  475. RequestValidationInput: requestValidationInput,
  476. Status: resp.StatusCode,
  477. Header: resp.Header,
  478. }
  479. responseValidationInput = responseValidationInput.SetBodyBytes(body)
  480. return swagger.ValidateResponse(ctx, responseValidationInput)
  481. }
  482. // test helpers
  483. func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
  484. for i := 0; i < len(blocks); i++ {
  485. if blocks[i].EthBlockNum == blockNum {
  486. return blocks[i].Timestamp
  487. }
  488. }
  489. panic("timesamp not found")
  490. }
  491. func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
  492. for i := 0; i < len(tokens); i++ {
  493. if tokens[i].TokenID == id {
  494. return tokens[i]
  495. }
  496. }
  497. panic("token not found")
  498. }
  499. func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
  500. for _, acc := range accs {
  501. if idx == acc.Idx {
  502. return getTokenByID(acc.TokenID, tokens)
  503. }
  504. }
  505. panic("token not found")
  506. }
  507. func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
  508. for _, acc := range accs {
  509. if acc.Idx == idx {
  510. return &acc
  511. }
  512. }
  513. panic("account not found")
  514. }
  515. func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
  516. for _, b := range blocks {
  517. if b.EthBlockNum == ethBlockNum {
  518. return b
  519. }
  520. }
  521. panic("block not found")
  522. }
  523. func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
  524. for _, c := range coordinators {
  525. if c.Bidder == bidder {
  526. return c
  527. }
  528. }
  529. panic("coordinator not found")
  530. }