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