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.

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