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.

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