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