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.

541 lines
14 KiB

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