arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/api_test.go
api/api_test.goRaw
  1package api
  2
  3import (
  4	"bytes"
  5	"context"
  6	"encoding/json"
  7	"errors"
  8	"fmt"
  9	"io"
 10	"io/ioutil"
 11	"math/big"
 12	"net/http"
 13	"os"
 14	"sort"
 15	"strconv"
 16	"testing"
 17	"time"
 18
 19	ethCommon "github.com/ethereum/go-ethereum/common"
 20	swagger "github.com/getkin/kin-openapi/openapi3filter"
 21	"github.com/gin-gonic/gin"
 22	"github.com/hermeznetwork/hermez-node/common"
 23	"github.com/hermeznetwork/hermez-node/db"
 24	"github.com/hermeznetwork/hermez-node/db/historydb"
 25	"github.com/hermeznetwork/hermez-node/db/l2db"
 26	"github.com/hermeznetwork/hermez-node/db/statedb"
 27	"github.com/hermeznetwork/hermez-node/eth"
 28	"github.com/hermeznetwork/hermez-node/log"
 29	"github.com/hermeznetwork/hermez-node/test"
 30	"github.com/iden3/go-iden3-crypto/babyjub"
 31	"github.com/mitchellh/copystructure"
 32	"github.com/stretchr/testify/assert"
 33	"github.com/stretchr/testify/require"
 34)
 35
 36const apiPort = ":4010"
 37const apiURL = "http://localhost" + apiPort + "/"
 38
 39type testCommon struct {
 40	blocks           []common.Block
 41	tokens           []historydb.TokenWithUSD
 42	batches          []testBatch
 43	fullBatches      []testFullBatch
 44	coordinators     []historydb.CoordinatorAPI
 45	usrAddr          string
 46	usrBjj           string
 47	accs             []common.Account
 48	usrTxs           []testTx
 49	allTxs           []testTx
 50	exits            []exitAPI
 51	usrExits         []exitAPI
 52	poolTxsToSend    []testPoolTxSend
 53	poolTxsToReceive []testPoolTxReceive
 54	auths            []accountCreationAuthAPI
 55	router           *swagger.Router
 56	bids             []testBid
 57}
 58
 59var tc testCommon
 60var config configAPI
 61
 62// TestMain initializes the API server, and fill HistoryDB and StateDB with fake data,
 63// emulating the task of the synchronizer in order to have data to be returned
 64// by the API endpoints that will be tested
 65func TestMain(m *testing.M) {
 66	// Initializations
 67	// Swagger
 68	router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
 69	// HistoryDB
 70	pass := os.Getenv("POSTGRES_PASS")
 71	database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
 72	if err != nil {
 73		panic(err)
 74	}
 75	hdb := historydb.NewHistoryDB(database)
 76	err = hdb.Reorg(-1)
 77	if err != nil {
 78		panic(err)
 79	}
 80	// StateDB
 81	dir, err := ioutil.TempDir("", "tmpdb")
 82	if err != nil {
 83		panic(err)
 84	}
 85	defer func() {
 86		if err := os.RemoveAll(dir); err != nil {
 87			panic(err)
 88		}
 89	}()
 90	sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
 91	if err != nil {
 92		panic(err)
 93	}
 94	// L2DB
 95	l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
 96	test.CleanL2DB(l2DB.DB())
 97	// Config (smart contract constants)
 98	config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
 99	config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
100	config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
101	config.RollupConstants.LimitLoadAmount, _ = new(big.Int).SetString("340282366920938463463374607431768211456", 10)
102	config.RollupConstants.LimitL2TransferAmount, _ = new(big.Int).SetString("6277101735386680763835789423207666416102355444464034512896", 10)
103	config.RollupConstants.LimitTokens = eth.RollupConstLimitTokens
104	config.RollupConstants.L1CoordinatorTotalBytes = eth.RollupConstL1CoordinatorTotalBytes
105	config.RollupConstants.L1UserTotalBytes = eth.RollupConstL1UserTotalBytes
106	config.RollupConstants.MaxL1UserTx = eth.RollupConstMaxL1UserTx
107	config.RollupConstants.MaxL1Tx = eth.RollupConstMaxL1Tx
108	config.RollupConstants.InputSHAConstantBytes = eth.RollupConstInputSHAConstantBytes
109	config.RollupConstants.NumBuckets = eth.RollupConstNumBuckets
110	config.RollupConstants.MaxWithdrawalDelay = eth.RollupConstMaxWithdrawalDelay
111	var rollupPublicConstants eth.RollupPublicConstants
112	rollupPublicConstants.AbsoluteMaxL1L2BatchTimeout = 240
113	rollupPublicConstants.HermezAuctionContract = ethCommon.HexToAddress("0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e")
114	rollupPublicConstants.HermezGovernanceDAOAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
115	rollupPublicConstants.SafetyAddress = ethCommon.HexToAddress("0xE5904695748fe4A84b40b3fc79De2277660BD1D3")
116	rollupPublicConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
117	rollupPublicConstants.WithdrawDelayerContract = ethCommon.HexToAddress("0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe")
118	var verifier eth.RollupVerifierStruct
119	verifier.MaxTx = 512
120	verifier.NLevels = 32
121	rollupPublicConstants.Verifiers = append(rollupPublicConstants.Verifiers, verifier)
122
123	var auctionConstants eth.AuctionConstants
124	auctionConstants.BlocksPerSlot = 40
125	auctionConstants.GenesisBlockNum = 100
126	auctionConstants.GovernanceAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
127	auctionConstants.InitialMinimalBidding, _ = new(big.Int).SetString("10000000000000000000", 10)
128	auctionConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
129	auctionConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
130
131	var wdelayerConstants eth.WDelayerConstants
132	wdelayerConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
133	wdelayerConstants.MaxEmergencyModeTime = uint64(1000000)
134	wdelayerConstants.MaxWithdrawalDelay = uint64(10000000)
135
136	config.RollupConstants.PublicConstants = rollupPublicConstants
137	config.AuctionConstants = auctionConstants
138	config.WDelayerConstants = wdelayerConstants
139
140	// API
141	api := gin.Default()
142	if err := SetAPIEndpoints(
143		true,
144		true,
145		api,
146		hdb,
147		sdb,
148		l2DB,
149		&config,
150	); err != nil {
151		panic(err)
152	}
153	// Start server
154	server := &http.Server{Addr: apiPort, Handler: api}
155	go func() {
156		if err := server.ListenAndServe(); err != nil &&
157			err != http.ErrServerClosed {
158			panic(err)
159		}
160	}()
161
162	// Fill HistoryDB and StateDB with fake data
163	// Clean DB
164	err = h.Reorg(0)
165	if err != nil {
166		panic(err)
167	}
168	// Gen blocks and add them to DB
169	const nBlocks = 5
170	blocks := test.GenBlocks(1, nBlocks+1)
171	err = h.AddBlocks(blocks)
172	if err != nil {
173		panic(err)
174	}
175	// Gen tokens and add them to DB
176	const nTokens = 10
177	tokens := test.GenTokens(nTokens, blocks)
178	err = h.AddTokens(tokens)
179	if err != nil {
180		panic(err)
181	}
182	// Set token value
183	tokensUSD := []historydb.TokenWithUSD{}
184	for i, tkn := range tokens {
185		token := historydb.TokenWithUSD{
186			TokenID:     tkn.TokenID,
187			EthBlockNum: tkn.EthBlockNum,
188			EthAddr:     tkn.EthAddr,
189			Name:        tkn.Name,
190			Symbol:      tkn.Symbol,
191			Decimals:    tkn.Decimals,
192		}
193		// Set value of 50% of the tokens
194		if i%2 != 0 {
195			value := float64(i) * 1.234567
196			now := time.Now().UTC()
197			token.USD = &value
198			token.USDUpdate = &now
199			err = h.UpdateTokenValue(token.Symbol, value)
200			if err != nil {
201				panic(err)
202			}
203		}
204		tokensUSD = append(tokensUSD, token)
205	}
206	// Gen batches and add them to DB
207	const nBatches = 10
208	batches := test.GenBatches(nBatches, blocks)
209	err = h.AddBatches(batches)
210	if err != nil {
211		panic(err)
212	}
213	// Gen accounts and add them to HistoryDB and StateDB
214	const totalAccounts = 40
215	const userAccounts = 4
216	usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
217	privK := babyjub.NewRandPrivKey()
218	usrBjj := privK.Public()
219	accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
220	err = h.AddAccounts(accs)
221	if err != nil {
222		panic(err)
223	}
224	for i := 0; i < len(accs); i++ {
225		if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
226			panic(err)
227		}
228	}
229	// helper to vinculate user related resources
230	usrIdxs := []string{}
231	for _, acc := range accs {
232		if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
233			for _, token := range tokens {
234				if token.TokenID == acc.TokenID {
235					usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
236				}
237			}
238		}
239	}
240	// Gen exits and add them to DB
241	const totalExits = 40
242	exits := test.GenExitTree(totalExits, batches, accs)
243	err = h.AddExitTree(exits)
244	if err != nil {
245		panic(err)
246	}
247
248	// L1 and L2 txs need to be sorted in a combined way
249	// Gen L1Txs
250	const totalL1Txs = 40
251	const userL1Txs = 4
252	usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
253	// Gen L2Txs
254	const totalL2Txs = 20
255	const userL2Txs = 4
256	usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
257	// Sort txs
258	sortedTxs := []txSortFielder{}
259	for i := 0; i < len(usrL1Txs); i++ {
260		wL1 := wrappedL1(usrL1Txs[i])
261		sortedTxs = append(sortedTxs, &wL1)
262	}
263	for i := 0; i < len(othrL1Txs); i++ {
264		wL1 := wrappedL1(othrL1Txs[i])
265		sortedTxs = append(sortedTxs, &wL1)
266	}
267	for i := 0; i < len(usrL2Txs); i++ {
268		wL2 := wrappedL2(usrL2Txs[i])
269		sortedTxs = append(sortedTxs, &wL2)
270	}
271	for i := 0; i < len(othrL2Txs); i++ {
272		wL2 := wrappedL2(othrL2Txs[i])
273		sortedTxs = append(sortedTxs, &wL2)
274	}
275	sort.Sort(txsSort(sortedTxs))
276	// Store txs to DB
277	for _, genericTx := range sortedTxs {
278		l1 := genericTx.L1()
279		l2 := genericTx.L2()
280		if l1 != nil {
281			err = h.AddL1Txs([]common.L1Tx{*l1})
282			if err != nil {
283				panic(err)
284			}
285		} else if l2 != nil {
286			err = h.AddL2Txs([]common.L2Tx{*l2})
287			if err != nil {
288				panic(err)
289			}
290		} else {
291			panic("should be l1 or l2")
292		}
293	}
294
295	// Transform exits to API
296	exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
297		historyExits := []historydb.HistoryExit{}
298		for _, exit := range exits {
299			token := getTokenByIdx(exit.AccountIdx, tokensUSD, accs)
300			historyExits = append(historyExits, historydb.HistoryExit{
301				BatchNum:               exit.BatchNum,
302				AccountIdx:             exit.AccountIdx,
303				MerkleProof:            exit.MerkleProof,
304				Balance:                exit.Balance,
305				InstantWithdrawn:       exit.InstantWithdrawn,
306				DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
307				DelayedWithdrawn:       exit.DelayedWithdrawn,
308				TokenID:                token.TokenID,
309				TokenEthBlockNum:       token.EthBlockNum,
310				TokenEthAddr:           token.EthAddr,
311				TokenName:              token.Name,
312				TokenSymbol:            token.Symbol,
313				TokenDecimals:          token.Decimals,
314				TokenUSD:               token.USD,
315				TokenUSDUpdate:         token.USDUpdate,
316			})
317		}
318		return historyExitsToAPI(historyExits)
319	}
320	apiExits := exitsToAPIExits(exits, accs, tokens)
321	// sort.Sort(apiExits)
322	usrExits := []exitAPI{}
323	for _, exit := range apiExits {
324		for _, idx := range usrIdxs {
325			if idx == exit.AccountIdx {
326				usrExits = append(usrExits, exit)
327			}
328		}
329	}
330
331	// Coordinators
332	const nCoords = 10
333	coords := test.GenCoordinators(nCoords, blocks)
334	err = hdb.AddCoordinators(coords)
335	if err != nil {
336		panic(err)
337	}
338	fromItem := uint(0)
339	limit := uint(99999)
340	coordinators, _, err := hdb.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
341	if err != nil {
342		panic(err)
343	}
344	// Account creation auth
345	const nAuths = 5
346	auths := test.GenAuths(nAuths)
347	// Transform auths to API format
348	apiAuths := []accountCreationAuthAPI{}
349	for _, auth := range auths {
350		apiAuth := accountCreationAuthToAPI(auth)
351		apiAuths = append(apiAuths, *apiAuth)
352	}
353
354	// Bids
355	const nBids = 10
356	bids := test.GenBids(nBids, blocks, coords)
357	err = hdb.AddBids(bids)
358	if err != nil {
359		panic(err)
360	}
361
362	// Set testCommon
363	usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
364	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.
365	testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
366	tc = testCommon{
367		blocks:           blocks,
368		tokens:           tokensUSD,
369		batches:          testBatches,
370		fullBatches:      fullBatches,
371		coordinators:     coordinators,
372		usrAddr:          ethAddrToHez(usrAddr),
373		usrBjj:           bjjToString(usrBjj),
374		accs:             accs,
375		usrTxs:           usrTxs,
376		allTxs:           allTxs,
377		exits:            apiExits,
378		usrExits:         usrExits,
379		poolTxsToSend:    poolTxsToSend,
380		poolTxsToReceive: poolTxsToReceive,
381		auths:            apiAuths,
382		router:           router,
383		bids:             genTestBids(blocks, coordinators, bids),
384	}
385
386	// Fake server
387	if os.Getenv("FAKE_SERVER") == "yes" {
388		for {
389			log.Info("Running fake server at " + apiURL + " until ^C is received")
390			time.Sleep(30 * time.Second)
391		}
392	}
393	// Run tests
394	result := m.Run()
395	// Stop server
396	if err := server.Shutdown(context.Background()); err != nil {
397		panic(err)
398	}
399	if err := database.Close(); err != nil {
400		panic(err)
401	}
402	if err := os.RemoveAll(dir); err != nil {
403		panic(err)
404	}
405	os.Exit(result)
406}
407
408func TestGetExits(t *testing.T) {
409	endpoint := apiURL + "exits"
410	fetchedExits := []exitAPI{}
411	appendIter := func(intr interface{}) {
412		for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
413			tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
414			if err != nil {
415				panic(err)
416			}
417			fetchedExits = append(fetchedExits, tmp.(exitAPI))
418		}
419	}
420	// Get all (no filters)
421	limit := 8
422	path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
423	err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
424	assert.NoError(t, err)
425	assertExitAPIs(t, tc.exits, fetchedExits)
426
427	// Get by ethAddr
428	fetchedExits = []exitAPI{}
429	limit = 7
430	path = fmt.Sprintf(
431		"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
432		endpoint, tc.usrAddr, limit,
433	)
434	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
435	assert.NoError(t, err)
436	assertExitAPIs(t, tc.usrExits, fetchedExits)
437	// Get by bjj
438	fetchedExits = []exitAPI{}
439	limit = 6
440	path = fmt.Sprintf(
441		"%s?BJJ=%s&limit=%d&fromItem=",
442		endpoint, tc.usrBjj, limit,
443	)
444	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
445	assert.NoError(t, err)
446	assertExitAPIs(t, tc.usrExits, fetchedExits)
447	// Get by tokenID
448	fetchedExits = []exitAPI{}
449	limit = 5
450	tokenID := tc.exits[0].Token.TokenID
451	path = fmt.Sprintf(
452		"%s?tokenId=%d&limit=%d&fromItem=",
453		endpoint, tokenID, limit,
454	)
455	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
456	assert.NoError(t, err)
457	tokenIDExits := []exitAPI{}
458	for i := 0; i < len(tc.exits); i++ {
459		if tc.exits[i].Token.TokenID == tokenID {
460			tokenIDExits = append(tokenIDExits, tc.exits[i])
461		}
462	}
463	assertExitAPIs(t, tokenIDExits, fetchedExits)
464	// idx
465	fetchedExits = []exitAPI{}
466	limit = 4
467	idx := tc.exits[0].AccountIdx
468	path = fmt.Sprintf(
469		"%s?accountIndex=%s&limit=%d&fromItem=",
470		endpoint, idx, limit,
471	)
472	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
473	assert.NoError(t, err)
474	idxExits := []exitAPI{}
475	for i := 0; i < len(tc.exits); i++ {
476		if tc.exits[i].AccountIdx[6:] == idx[6:] {
477			idxExits = append(idxExits, tc.exits[i])
478		}
479	}
480	assertExitAPIs(t, idxExits, fetchedExits)
481	// batchNum
482	fetchedExits = []exitAPI{}
483	limit = 3
484	batchNum := tc.exits[0].BatchNum
485	path = fmt.Sprintf(
486		"%s?batchNum=%d&limit=%d&fromItem=",
487		endpoint, batchNum, limit,
488	)
489	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
490	assert.NoError(t, err)
491	batchNumExits := []exitAPI{}
492	for i := 0; i < len(tc.exits); i++ {
493		if tc.exits[i].BatchNum == batchNum {
494			batchNumExits = append(batchNumExits, tc.exits[i])
495		}
496	}
497	assertExitAPIs(t, batchNumExits, fetchedExits)
498	// Multiple filters
499	fetchedExits = []exitAPI{}
500	limit = 1
501	path = fmt.Sprintf(
502		"%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
503		endpoint, batchNum, tokenID, limit,
504	)
505	err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
506	assert.NoError(t, err)
507	mixedExits := []exitAPI{}
508	flipedExits := []exitAPI{}
509	for i := 0; i < len(tc.exits); i++ {
510		if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
511			mixedExits = append(mixedExits, tc.exits[i])
512		}
513		flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
514	}
515	assertExitAPIs(t, mixedExits, fetchedExits)
516	// All, in reverse order
517	fetchedExits = []exitAPI{}
518	limit = 5
519	path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
520	err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
521	assert.NoError(t, err)
522	assertExitAPIs(t, flipedExits, fetchedExits)
523	// 400
524	path = fmt.Sprintf(
525		"%s?accountIndex=%s&hermezEthereumAddress=%s",
526		endpoint, idx, tc.usrAddr,
527	)
528	err = doBadReq("GET", path, nil, 400)
529	assert.NoError(t, err)
530	path = fmt.Sprintf("%s?tokenId=X", endpoint)
531	err = doBadReq("GET", path, nil, 400)
532	assert.NoError(t, err)
533	// 404
534	path = fmt.Sprintf("%s?batchNum=999999", endpoint)
535	err = doBadReq("GET", path, nil, 404)
536	assert.NoError(t, err)
537	path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
538	err = doBadReq("GET", path, nil, 404)
539	assert.NoError(t, err)
540}
541
542func TestGetExit(t *testing.T) {
543	// Get all txs by their ID
544	endpoint := apiURL + "exits/"
545	fetchedExits := []exitAPI{}
546	for _, exit := range tc.exits {
547		fetchedExit := exitAPI{}
548		assert.NoError(
549			t, doGoodReq(
550				"GET",
551				fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
552				nil, &fetchedExit,
553			),
554		)
555		fetchedExits = append(fetchedExits, fetchedExit)
556	}
557	assertExitAPIs(t, tc.exits, fetchedExits)
558	// 400
559	err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
560	assert.NoError(t, err)
561	err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
562	assert.NoError(t, err)
563	// 404
564	err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
565	assert.NoError(t, err)
566}
567
568func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
569	require.Equal(t, len(expected), len(actual))
570	for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
571		actual[i].ItemID = 0
572		if expected[i].Token.USDUpdate == nil {
573			assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
574		} else {
575			assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
576			expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
577		}
578		assert.Equal(t, expected[i], actual[i])
579	}
580}
581func TestGetConfig(t *testing.T) {
582	endpoint := apiURL + "config"
583	var configTest configAPI
584	assert.NoError(t, doGoodReq("GET", endpoint, nil, &configTest))
585	assert.Equal(t, config, configTest)
586	assert.Equal(t, cg, &configTest)
587}
588
589func TestAccountCreationAuth(t *testing.T) {
590	// POST
591	endpoint := apiURL + "account-creation-authorization"
592	for _, auth := range tc.auths {
593		jsonAuthBytes, err := json.Marshal(auth)
594		assert.NoError(t, err)
595		jsonAuthReader := bytes.NewReader(jsonAuthBytes)
596		fmt.Println(string(jsonAuthBytes))
597		assert.NoError(
598			t, doGoodReq(
599				"POST",
600				endpoint,
601				jsonAuthReader, nil,
602			),
603		)
604	}
605	// GET
606	endpoint += "/"
607	for _, auth := range tc.auths {
608		fetchedAuth := accountCreationAuthAPI{}
609		assert.NoError(
610			t, doGoodReq(
611				"GET",
612				endpoint+auth.EthAddr,
613				nil, &fetchedAuth,
614			),
615		)
616		assertAuth(t, auth, fetchedAuth)
617	}
618	// POST
619	// 400
620	// Wrong addr
621	badAuth := tc.auths[0]
622	badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
623	jsonAuthBytes, err := json.Marshal(badAuth)
624	assert.NoError(t, err)
625	jsonAuthReader := bytes.NewReader(jsonAuthBytes)
626	err = doBadReq("POST", endpoint, jsonAuthReader, 400)
627	assert.NoError(t, err)
628	// Wrong signature
629	badAuth = tc.auths[0]
630	badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
631	badAuth.Signature += "F"
632	jsonAuthBytes, err = json.Marshal(badAuth)
633	assert.NoError(t, err)
634	jsonAuthReader = bytes.NewReader(jsonAuthBytes)
635	err = doBadReq("POST", endpoint, jsonAuthReader, 400)
636	assert.NoError(t, err)
637	// GET
638	// 400
639	err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
640	assert.NoError(t, err)
641	// 404
642	err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
643	assert.NoError(t, err)
644}
645
646func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
647	// timestamp should be very close to now
648	assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
649	expected.Timestamp = actual.Timestamp
650	assert.Equal(t, expected, actual)
651}
652
653func doGoodReqPaginated(
654	path, order string,
655	iterStruct db.Paginationer,
656	appendIter func(res interface{}),
657) error {
658	next := 0
659	for {
660		// Call API to get this iteration items
661		iterPath := path
662		if next == 0 && order == historydb.OrderDesc {
663			// Fetch first item in reverse order
664			iterPath += "99999"
665		} else {
666			// Fetch from next item or 0 if it's ascending order
667			iterPath += strconv.Itoa(next)
668		}
669		if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
670			return err
671		}
672		appendIter(iterStruct)
673		// Keep iterating?
674		pag := iterStruct.GetPagination()
675		if order == historydb.OrderAsc {
676			if pag.LastReturnedItem == pag.LastItem { // No
677				break
678			} else { // Yes
679				next = pag.LastReturnedItem + 1
680			}
681		} else {
682			if pag.FirstReturnedItem == pag.FirstItem { // No
683				break
684			} else { // Yes
685				next = pag.FirstReturnedItem - 1
686			}
687		}
688	}
689	return nil
690}
691
692func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
693	ctx := context.Background()
694	client := &http.Client{}
695	httpReq, err := http.NewRequest(method, path, reqBody)
696	if err != nil {
697		return err
698	}
699	if reqBody != nil {
700		httpReq.Header.Add("Content-Type", "application/json")
701	}
702	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
703	if err != nil {
704		return err
705	}
706	// Validate request against swagger spec
707	requestValidationInput := &swagger.RequestValidationInput{
708		Request:    httpReq,
709		PathParams: pathParams,
710		Route:      route,
711	}
712	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
713		return err
714	}
715	// Do API call
716	resp, err := client.Do(httpReq)
717	if err != nil {
718		return err
719	}
720	if resp.Body == nil && returnStruct != nil {
721		return errors.New("Nil body")
722	}
723	//nolint
724	defer resp.Body.Close()
725	body, err := ioutil.ReadAll(resp.Body)
726	if err != nil {
727		return err
728	}
729	if resp.StatusCode != 200 {
730		return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
731	}
732	if returnStruct == nil {
733		return nil
734	}
735	// Unmarshal body into return struct
736	if err := json.Unmarshal(body, returnStruct); err != nil {
737		log.Error("invalid json: " + string(body))
738		return err
739	}
740	// Validate response against swagger spec
741	responseValidationInput := &swagger.ResponseValidationInput{
742		RequestValidationInput: requestValidationInput,
743		Status:                 resp.StatusCode,
744		Header:                 resp.Header,
745	}
746	responseValidationInput = responseValidationInput.SetBodyBytes(body)
747	return swagger.ValidateResponse(ctx, responseValidationInput)
748}
749
750func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
751	ctx := context.Background()
752	client := &http.Client{}
753	httpReq, _ := http.NewRequest(method, path, reqBody)
754	route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
755	if err != nil {
756		return err
757	}
758	// Validate request against swagger spec
759	requestValidationInput := &swagger.RequestValidationInput{
760		Request:    httpReq,
761		PathParams: pathParams,
762		Route:      route,
763	}
764	if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
765		if expectedResponseCode != 400 {
766			return err
767		}
768		log.Warn("The request does not match the API spec")
769	}
770	// Do API call
771	resp, err := client.Do(httpReq)
772	if err != nil {
773		return err
774	}
775	if resp.Body == nil {
776		return errors.New("Nil body")
777	}
778	//nolint
779	defer resp.Body.Close()
780	body, err := ioutil.ReadAll(resp.Body)
781	if err != nil {
782		return err
783	}
784	if resp.StatusCode != expectedResponseCode {
785		return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
786	}
787	// Validate response against swagger spec
788	responseValidationInput := &swagger.ResponseValidationInput{
789		RequestValidationInput: requestValidationInput,
790		Status:                 resp.StatusCode,
791		Header:                 resp.Header,
792	}
793	responseValidationInput = responseValidationInput.SetBodyBytes(body)
794	return swagger.ValidateResponse(ctx, responseValidationInput)
795}
796
797// test helpers
798
799func getTimestamp(blockNum int64, blocks []common.Block) time.Time {
800	for i := 0; i < len(blocks); i++ {
801		if blocks[i].EthBlockNum == blockNum {
802			return blocks[i].Timestamp
803		}
804	}
805	panic("timesamp not found")
806}
807
808func getTokenByID(id common.TokenID, tokens []historydb.TokenWithUSD) historydb.TokenWithUSD {
809	for i := 0; i < len(tokens); i++ {
810		if tokens[i].TokenID == id {
811			return tokens[i]
812		}
813	}
814	panic("token not found")
815}
816
817func getTokenByIdx(idx common.Idx, tokens []historydb.TokenWithUSD, accs []common.Account) historydb.TokenWithUSD {
818	for _, acc := range accs {
819		if idx == acc.Idx {
820			return getTokenByID(acc.TokenID, tokens)
821		}
822	}
823	panic("token not found")
824}
825
826func getAccountByIdx(idx common.Idx, accs []common.Account) *common.Account {
827	for _, acc := range accs {
828		if acc.Idx == idx {
829			return &acc
830		}
831	}
832	panic("account not found")
833}
834
835func getBlockByNum(ethBlockNum int64, blocks []common.Block) common.Block {
836	for _, b := range blocks {
837		if b.EthBlockNum == ethBlockNum {
838			return b
839		}
840	}
841	panic("block not found")
842}
843
844func getCoordinatorByBidder(bidder ethCommon.Address, coordinators []historydb.CoordinatorAPI) historydb.CoordinatorAPI {
845	for _, c := range coordinators {
846		if c.Bidder == bidder {
847			return c
848		}
849	}
850	panic("coordinator not found")
851}