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.

536 lines
14 KiB

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