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}