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