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