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}