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