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