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