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"
12 "math/big"
13 "net/http"
14 "os"
15 "sort"
16 "strconv"
17 "testing"
18 "time"
19
20 ethCommon "github.com/ethereum/go-ethereum/common"
21 swagger "github.com/getkin/kin-openapi/openapi3filter"
22 "github.com/gin-gonic/gin"
23 "github.com/hermeznetwork/hermez-node/common"
24 "github.com/hermeznetwork/hermez-node/db"
25 "github.com/hermeznetwork/hermez-node/db/historydb"
26 "github.com/hermeznetwork/hermez-node/db/l2db"
27 "github.com/hermeznetwork/hermez-node/db/statedb"
28 "github.com/hermeznetwork/hermez-node/eth"
29 "github.com/hermeznetwork/hermez-node/log"
30 "github.com/hermeznetwork/hermez-node/test"
31 "github.com/iden3/go-iden3-crypto/babyjub"
32 "github.com/mitchellh/copystructure"
33 "github.com/stretchr/testify/assert"
34 "github.com/stretchr/testify/require"
35)
36
37const apiPort = ":4010"
38const apiURL = "http://localhost" + apiPort + "/"
39
40type testCommon struct {
41 blocks []common.Block
42 tokens []historydb.TokenWithUSD
43 batches []testBatch
44 coordinators []coordinatorAPI
45 usrAddr string
46 usrBjj string
47 accs []common.Account
48 usrTxs []historyTxAPI
49 allTxs []historyTxAPI
50 exits []exitAPI
51 usrExits []exitAPI
52 poolTxsToSend []receivedPoolTx
53 poolTxsToReceive []sendPoolTx
54 auths []accountCreationAuthAPI
55 router *swagger.Router
56}
57
58// TxSortFields represents the fields needed to sort L1 and L2 transactions
59type txSortFields struct {
60 BatchNum *common.BatchNum
61 Position int
62}
63
64// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
65type txSortFielder interface {
66 SortFields() txSortFields
67 L1() *common.L1Tx
68 L2() *common.L2Tx
69}
70
71// TxsSort array of TxSortFielder
72type txsSort []txSortFielder
73
74func (t txsSort) Len() int { return len(t) }
75func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
76func (t txsSort) Less(i, j int) bool {
77 // i not forged yet
78 isf := t[i].SortFields()
79 jsf := t[j].SortFields()
80 if isf.BatchNum == nil {
81 if jsf.BatchNum != nil { // j is already forged
82 return false
83 }
84 // Both aren't forged, is i in a smaller position?
85 return isf.Position < jsf.Position
86 }
87 // i is forged
88 if jsf.BatchNum == nil {
89 return false // j is not forged
90 }
91 // Both are forged
92 if *isf.BatchNum == *jsf.BatchNum {
93 // At the same batch, is i in a smaller position?
94 return isf.Position < jsf.Position
95 }
96 // At different batches, is i in a smaller batch?
97 return *isf.BatchNum < *jsf.BatchNum
98}
99
100type wrappedL1 common.L1Tx
101
102// SortFields implements TxSortFielder
103func (tx *wrappedL1) SortFields() txSortFields {
104 return txSortFields{
105 BatchNum: tx.BatchNum,
106 Position: tx.Position,
107 }
108}
109
110// L1 implements TxSortFielder
111func (tx *wrappedL1) L1() *common.L1Tx {
112 l1tx := common.L1Tx(*tx)
113 return &l1tx
114}
115
116// L2 implements TxSortFielder
117func (tx *wrappedL1) L2() *common.L2Tx { return nil }
118
119type wrappedL2 common.L2Tx
120
121// SortFields implements TxSortFielder
122func (tx *wrappedL2) SortFields() txSortFields {
123 return txSortFields{
124 BatchNum: &tx.BatchNum,
125 Position: tx.Position,
126 }
127}
128
129// L1 implements TxSortFielder
130func (tx *wrappedL2) L1() *common.L1Tx { return nil }
131
132// L2 implements TxSortFielder
133func (tx *wrappedL2) L2() *common.L2Tx {
134 l2tx := common.L2Tx(*tx)
135 return &l2tx
136}
137
138var tc testCommon
139var config configAPI
140
141func TestMain(m *testing.M) {
142 // Init swagger
143 router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
144 // Init DBs
145 // HistoryDB
146 pass := os.Getenv("POSTGRES_PASS")
147 database, err := db.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
148 if err != nil {
149 panic(err)
150 }
151 hdb := historydb.NewHistoryDB(database)
152 err = hdb.Reorg(-1)
153 if err != nil {
154 panic(err)
155 }
156 // StateDB
157 dir, err := ioutil.TempDir("", "tmpdb")
158 if err != nil {
159 panic(err)
160 }
161 defer func() {
162 if err := os.RemoveAll(dir); err != nil {
163 panic(err)
164 }
165 }()
166 sdb, err := statedb.NewStateDB(dir, statedb.TypeTxSelector, 0)
167 if err != nil {
168 panic(err)
169 }
170 // L2DB
171 l2DB := l2db.NewL2DB(database, 10, 100, 24*time.Hour)
172 test.CleanL2DB(l2DB.DB())
173
174 config.RollupConstants.ExchangeMultiplier = eth.RollupConstExchangeMultiplier
175 config.RollupConstants.ExitIdx = eth.RollupConstExitIDx
176 config.RollupConstants.ReservedIdx = eth.RollupConstReservedIDx
177 config.RollupConstants.LimitLoadAmount, _ = new(big.Int).SetString("340282366920938463463374607431768211456", 10)
178 config.RollupConstants.LimitL2TransferAmount, _ = new(big.Int).SetString("6277101735386680763835789423207666416102355444464034512896", 10)
179 config.RollupConstants.LimitTokens = eth.RollupConstLimitTokens
180 config.RollupConstants.L1CoordinatorTotalBytes = eth.RollupConstL1CoordinatorTotalBytes
181 config.RollupConstants.L1UserTotalBytes = eth.RollupConstL1UserTotalBytes
182 config.RollupConstants.MaxL1UserTx = eth.RollupConstMaxL1UserTx
183 config.RollupConstants.MaxL1Tx = eth.RollupConstMaxL1Tx
184 config.RollupConstants.InputSHAConstantBytes = eth.RollupConstInputSHAConstantBytes
185 config.RollupConstants.NumBuckets = eth.RollupConstNumBuckets
186 config.RollupConstants.MaxWithdrawalDelay = eth.RollupConstMaxWithdrawalDelay
187 var rollupPublicConstants eth.RollupPublicConstants
188 rollupPublicConstants.AbsoluteMaxL1L2BatchTimeout = 240
189 rollupPublicConstants.HermezAuctionContract = ethCommon.HexToAddress("0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e")
190 rollupPublicConstants.HermezGovernanceDAOAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
191 rollupPublicConstants.SafetyAddress = ethCommon.HexToAddress("0xE5904695748fe4A84b40b3fc79De2277660BD1D3")
192 rollupPublicConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
193 rollupPublicConstants.WithdrawDelayerContract = ethCommon.HexToAddress("0xD6C850aeBFDC46D7F4c207e445cC0d6B0919BDBe")
194 var verifier eth.RollupVerifierStruct
195 verifier.MaxTx = 512
196 verifier.NLevels = 32
197 rollupPublicConstants.Verifiers = append(rollupPublicConstants.Verifiers, verifier)
198
199 var auctionConstants eth.AuctionConstants
200 auctionConstants.BlocksPerSlot = 40
201 auctionConstants.GenesisBlockNum = 100
202 auctionConstants.GovernanceAddress = ethCommon.HexToAddress("0xeAD9C93b79Ae7C1591b1FB5323BD777E86e150d4")
203 auctionConstants.InitialMinimalBidding, _ = new(big.Int).SetString("10000000000000000000", 10)
204 auctionConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
205 auctionConstants.TokenHEZ = ethCommon.HexToAddress("0xf784709d2317D872237C4bC22f867d1BAe2913AB")
206
207 var wdelayerConstants eth.WDelayerConstants
208 wdelayerConstants.HermezRollup = ethCommon.HexToAddress("0xEa960515F8b4C237730F028cBAcF0a28E7F45dE0")
209 wdelayerConstants.MaxEmergencyModeTime = uint64(1000000)
210 wdelayerConstants.MaxWithdrawalDelay = uint64(10000000)
211
212 config.RollupConstants.PublicConstants = rollupPublicConstants
213 config.AuctionConstants = auctionConstants
214 config.WDelayerConstants = wdelayerConstants
215
216 // Init API
217 api := gin.Default()
218 if err := SetAPIEndpoints(
219 true,
220 true,
221 api,
222 hdb,
223 sdb,
224 l2DB,
225 &config,
226 ); err != nil {
227 panic(err)
228 }
229 // Start server
230 server := &http.Server{Addr: apiPort, Handler: api}
231 go func() {
232 if err := server.ListenAndServe(); err != nil &&
233 err != http.ErrServerClosed {
234 panic(err)
235 }
236 }()
237
238 // Populate DBs
239 // Clean DB
240 err = h.Reorg(0)
241 if err != nil {
242 panic(err)
243 }
244 // Gen blocks and add them to DB
245 const nBlocks = 5
246 blocks := test.GenBlocks(1, nBlocks+1)
247 err = h.AddBlocks(blocks)
248 if err != nil {
249 panic(err)
250 }
251 // Gen tokens and add them to DB
252 const nTokens = 10
253 tokens := test.GenTokens(nTokens, blocks)
254 err = h.AddTokens(tokens)
255 if err != nil {
256 panic(err)
257 }
258 // Set token value
259 tokensUSD := []historydb.TokenWithUSD{}
260 for i, tkn := range tokens {
261 token := historydb.TokenWithUSD{
262 TokenID: tkn.TokenID,
263 EthBlockNum: tkn.EthBlockNum,
264 EthAddr: tkn.EthAddr,
265 Name: tkn.Name,
266 Symbol: tkn.Symbol,
267 Decimals: tkn.Decimals,
268 }
269 // Set value of 50% of the tokens
270 if i%2 != 0 {
271 value := float64(i) * 1.234567
272 now := time.Now().UTC()
273 token.USD = &value
274 token.USDUpdate = &now
275 err = h.UpdateTokenValue(token.Symbol, value)
276 if err != nil {
277 panic(err)
278 }
279 }
280 tokensUSD = append(tokensUSD, token)
281 }
282 // Gen batches and add them to DB
283 const nBatches = 10
284 batches := test.GenBatches(nBatches, blocks)
285 err = h.AddBatches(batches)
286 if err != nil {
287 panic(err)
288 }
289 // Gen accounts and add them to HistoryDB and StateDB
290 const totalAccounts = 40
291 const userAccounts = 4
292 usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
293 privK := babyjub.NewRandPrivKey()
294 usrBjj := privK.Public()
295 accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
296 err = h.AddAccounts(accs)
297 if err != nil {
298 panic(err)
299 }
300 for i := 0; i < len(accs); i++ {
301 if _, err := s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
302 panic(err)
303 }
304 }
305 // Gen exits and add them to DB
306 const totalExits = 40
307 exits := test.GenExitTree(totalExits, batches, accs)
308 err = h.AddExitTree(exits)
309 if err != nil {
310 panic(err)
311 }
312 // Gen L1Txs and add them to DB
313 const totalL1Txs = 40
314 const userL1Txs = 4
315 usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
316 // Gen L2Txs and add them to DB
317 const totalL2Txs = 20
318 const userL2Txs = 4
319 usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
320 // Order txs
321 sortedTxs := []txSortFielder{}
322 for i := 0; i < len(usrL1Txs); i++ {
323 wL1 := wrappedL1(usrL1Txs[i])
324 sortedTxs = append(sortedTxs, &wL1)
325 }
326 for i := 0; i < len(othrL1Txs); i++ {
327 wL1 := wrappedL1(othrL1Txs[i])
328 sortedTxs = append(sortedTxs, &wL1)
329 }
330 for i := 0; i < len(usrL2Txs); i++ {
331 wL2 := wrappedL2(usrL2Txs[i])
332 sortedTxs = append(sortedTxs, &wL2)
333 }
334 for i := 0; i < len(othrL2Txs); i++ {
335 wL2 := wrappedL2(othrL2Txs[i])
336 sortedTxs = append(sortedTxs, &wL2)
337 }
338 sort.Sort(txsSort(sortedTxs))
339 // Add txs to DB and prepare them for test commons
340 usrTxs := []historyTxAPI{}
341 allTxs := []historyTxAPI{}
342 getTimestamp := func(blockNum int64) time.Time {
343 for i := 0; i < len(blocks); i++ {
344 if blocks[i].EthBlockNum == blockNum {
345 return blocks[i].Timestamp
346 }
347 }
348 panic("timesamp not found")
349 }
350 getToken := func(id common.TokenID) historydb.TokenWithUSD {
351 for i := 0; i < len(tokensUSD); i++ {
352 if tokensUSD[i].TokenID == id {
353 return tokensUSD[i]
354 }
355 }
356 panic("token not found")
357 }
358 getTokenByIdx := func(idx common.Idx) historydb.TokenWithUSD {
359 for _, acc := range accs {
360 if idx == acc.Idx {
361 return getToken(acc.TokenID)
362 }
363 }
364 panic("token not found")
365 }
366 usrIdxs := []string{}
367 for _, acc := range accs {
368 if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
369 for _, token := range tokens {
370 if token.TokenID == acc.TokenID {
371 usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
372 }
373 }
374 }
375 }
376 isUsrTx := func(tx historyTxAPI) bool {
377 for _, idx := range usrIdxs {
378 if tx.FromIdx != nil && *tx.FromIdx == idx {
379 return true
380 }
381 if tx.ToIdx == idx {
382 return true
383 }
384 }
385 return false
386 }
387 for _, genericTx := range sortedTxs {
388 l1 := genericTx.L1()
389 l2 := genericTx.L2()
390 if l1 != nil {
391 // Add L1 tx to DB
392 err = h.AddL1Txs([]common.L1Tx{*l1})
393 if err != nil {
394 panic(err)
395 }
396 // L1Tx ==> historyTxAPI
397 token := getToken(l1.TokenID)
398 tx := historyTxAPI{
399 IsL1: "L1",
400 TxID: l1.TxID,
401 Type: l1.Type,
402 Position: l1.Position,
403 ToIdx: idxToHez(l1.ToIdx, token.Symbol),
404 Amount: l1.Amount.String(),
405 BatchNum: l1.BatchNum,
406 Timestamp: getTimestamp(l1.EthBlockNum),
407 L1Info: &l1Info{
408 ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
409 UserOrigin: l1.UserOrigin,
410 FromEthAddr: ethAddrToHez(l1.FromEthAddr),
411 FromBJJ: bjjToString(l1.FromBJJ),
412 LoadAmount: l1.LoadAmount.String(),
413 EthBlockNum: l1.EthBlockNum,
414 },
415 Token: token,
416 }
417 if l1.FromIdx != 0 {
418 idxStr := idxToHez(l1.FromIdx, token.Symbol)
419 tx.FromIdx = &idxStr
420 }
421 if token.USD != nil {
422 af := new(big.Float).SetInt(l1.Amount)
423 amountFloat, _ := af.Float64()
424 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
425 tx.HistoricUSD = &usd
426 laf := new(big.Float).SetInt(l1.LoadAmount)
427 loadAmountFloat, _ := laf.Float64()
428 loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
429 tx.L1Info.HistoricLoadAmountUSD = &loadUSD
430 }
431 allTxs = append(allTxs, tx)
432 if isUsrTx(tx) {
433 usrTxs = append(usrTxs, tx)
434 }
435 } else {
436 // Add L2 tx to DB
437 err = h.AddL2Txs([]common.L2Tx{*l2})
438 if err != nil {
439 panic(err)
440 }
441 // L2Tx ==> historyTxAPI
442 var tokenID common.TokenID
443 found := false
444 for _, acc := range accs {
445 if acc.Idx == l2.FromIdx {
446 found = true
447 tokenID = acc.TokenID
448 break
449 }
450 }
451 if !found {
452 panic("tokenID not found")
453 }
454 token := getToken(tokenID)
455 tx := historyTxAPI{
456 IsL1: "L2",
457 TxID: l2.TxID,
458 Type: l2.Type,
459 Position: l2.Position,
460 ToIdx: idxToHez(l2.ToIdx, token.Symbol),
461 Amount: l2.Amount.String(),
462 BatchNum: &l2.BatchNum,
463 Timestamp: getTimestamp(l2.EthBlockNum),
464 L2Info: &l2Info{
465 Nonce: l2.Nonce,
466 Fee: l2.Fee,
467 },
468 Token: token,
469 }
470 if l2.FromIdx != 0 {
471 idxStr := idxToHez(l2.FromIdx, token.Symbol)
472 tx.FromIdx = &idxStr
473 }
474 if token.USD != nil {
475 af := new(big.Float).SetInt(l2.Amount)
476 amountFloat, _ := af.Float64()
477 usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
478 tx.HistoricUSD = &usd
479 feeUSD := usd * l2.Fee.Percentage()
480 tx.HistoricUSD = &usd
481 tx.L2Info.HistoricFeeUSD = &feeUSD
482 }
483 allTxs = append(allTxs, tx)
484 if isUsrTx(tx) {
485 usrTxs = append(usrTxs, tx)
486 }
487 }
488 }
489 // Transform exits to API
490 exitsToAPIExits := func(exits []common.ExitInfo, accs []common.Account, tokens []common.Token) []exitAPI {
491 historyExits := []historydb.HistoryExit{}
492 for _, exit := range exits {
493 token := getTokenByIdx(exit.AccountIdx)
494 historyExits = append(historyExits, historydb.HistoryExit{
495 BatchNum: exit.BatchNum,
496 AccountIdx: exit.AccountIdx,
497 MerkleProof: exit.MerkleProof,
498 Balance: exit.Balance,
499 InstantWithdrawn: exit.InstantWithdrawn,
500 DelayedWithdrawRequest: exit.DelayedWithdrawRequest,
501 DelayedWithdrawn: exit.DelayedWithdrawn,
502 TokenID: token.TokenID,
503 TokenEthBlockNum: token.EthBlockNum,
504 TokenEthAddr: token.EthAddr,
505 TokenName: token.Name,
506 TokenSymbol: token.Symbol,
507 TokenDecimals: token.Decimals,
508 TokenUSD: token.USD,
509 TokenUSDUpdate: token.USDUpdate,
510 })
511 }
512 return historyExitsToAPI(historyExits)
513 }
514 apiExits := exitsToAPIExits(exits, accs, tokens)
515 // sort.Sort(apiExits)
516 usrExits := []exitAPI{}
517 for _, exit := range apiExits {
518 for _, idx := range usrIdxs {
519 if idx == exit.AccountIdx {
520 usrExits = append(usrExits, exit)
521 }
522 }
523 }
524 // Prepare pool Txs
525 // Generate common.PoolL2Tx
526 // WARNING: this should be replaced once transakcio is ready
527 poolTxs := []common.PoolL2Tx{}
528 amount := new(big.Int)
529 amount, ok := amount.SetString("100000000000000", 10)
530 if !ok {
531 panic("bad amount")
532 }
533 poolTx := common.PoolL2Tx{
534 FromIdx: accs[0].Idx,
535 ToIdx: accs[1].Idx,
536 Amount: amount,
537 TokenID: accs[0].TokenID,
538 Nonce: 6,
539 }
540 if _, err := common.NewPoolL2Tx(&poolTx); err != nil {
541 panic(err)
542 }
543 h, err := poolTx.HashToSign()
544 if err != nil {
545 panic(err)
546 }
547 poolTx.Signature = privK.SignPoseidon(h).Compress()
548 poolTxs = append(poolTxs, poolTx)
549 // Transform to API formats
550 poolTxsToSend := []receivedPoolTx{}
551 poolTxsToReceive := []sendPoolTx{}
552 for _, poolTx := range poolTxs {
553 // common.PoolL2Tx ==> receivedPoolTx
554 token := getToken(poolTx.TokenID)
555 genSendTx := receivedPoolTx{
556 TxID: poolTx.TxID,
557 Type: poolTx.Type,
558 TokenID: poolTx.TokenID,
559 FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
560 Amount: poolTx.Amount.String(),
561 Fee: poolTx.Fee,
562 Nonce: poolTx.Nonce,
563 Signature: poolTx.Signature,
564 RqFee: &poolTx.RqFee,
565 RqNonce: &poolTx.RqNonce,
566 }
567 // common.PoolL2Tx ==> receivedPoolTx
568 genReceiveTx := sendPoolTx{
569 TxID: poolTx.TxID,
570 Type: poolTx.Type,
571 FromIdx: idxToHez(poolTx.FromIdx, token.Symbol),
572 Amount: poolTx.Amount.String(),
573 Fee: poolTx.Fee,
574 Nonce: poolTx.Nonce,
575 State: poolTx.State,
576 Signature: poolTx.Signature,
577 Timestamp: poolTx.Timestamp,
578 // BatchNum: poolTx.BatchNum,
579 RqFee: &poolTx.RqFee,
580 RqNonce: &poolTx.RqNonce,
581 Token: token,
582 }
583 if poolTx.ToIdx != 0 {
584 toIdx := idxToHez(poolTx.ToIdx, token.Symbol)
585 genSendTx.ToIdx = &toIdx
586 genReceiveTx.ToIdx = &toIdx
587 }
588 if poolTx.ToEthAddr != common.EmptyAddr {
589 toEth := ethAddrToHez(poolTx.ToEthAddr)
590 genSendTx.ToEthAddr = &toEth
591 genReceiveTx.ToEthAddr = &toEth
592 }
593 if poolTx.ToBJJ != nil {
594 toBJJ := bjjToString(poolTx.ToBJJ)
595 genSendTx.ToBJJ = &toBJJ
596 genReceiveTx.ToBJJ = &toBJJ
597 }
598 if poolTx.RqFromIdx != 0 {
599 rqFromIdx := idxToHez(poolTx.RqFromIdx, token.Symbol)
600 genSendTx.RqFromIdx = &rqFromIdx
601 genReceiveTx.RqFromIdx = &rqFromIdx
602 genSendTx.RqTokenID = &token.TokenID
603 genReceiveTx.RqTokenID = &token.TokenID
604 rqAmount := poolTx.RqAmount.String()
605 genSendTx.RqAmount = &rqAmount
606 genReceiveTx.RqAmount = &rqAmount
607
608 if poolTx.RqToIdx != 0 {
609 rqToIdx := idxToHez(poolTx.RqToIdx, token.Symbol)
610 genSendTx.RqToIdx = &rqToIdx
611 genReceiveTx.RqToIdx = &rqToIdx
612 }
613 if poolTx.RqToEthAddr != common.EmptyAddr {
614 rqToEth := ethAddrToHez(poolTx.RqToEthAddr)
615 genSendTx.RqToEthAddr = &rqToEth
616 genReceiveTx.RqToEthAddr = &rqToEth
617 }
618 if poolTx.RqToBJJ != nil {
619 rqToBJJ := bjjToString(poolTx.RqToBJJ)
620 genSendTx.RqToBJJ = &rqToBJJ
621 genReceiveTx.RqToBJJ = &rqToBJJ
622 }
623 }
624 poolTxsToSend = append(poolTxsToSend, genSendTx)
625 poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
626 }
627 // Coordinators
628 const nCoords = 10
629 coords := test.GenCoordinators(nCoords, blocks)
630 err = hdb.AddCoordinators(coords)
631 if err != nil {
632 panic(err)
633 }
634 fromItem := uint(0)
635 limit := uint(99999)
636 coordinators, _, err := hdb.GetCoordinators(&fromItem, &limit, historydb.OrderAsc)
637 apiCoordinators := coordinatorsToAPI(coordinators)
638 if err != nil {
639 panic(err)
640 }
641 // Account creation auth
642 const nAuths = 5
643 auths := test.GenAuths(nAuths)
644 // Transform auths to API format
645 apiAuths := []accountCreationAuthAPI{}
646 for _, auth := range auths {
647 apiAuth := accountCreationAuthToAPI(auth)
648 apiAuths = append(apiAuths, *apiAuth)
649 }
650 // Set testCommon
651 tc = testCommon{
652 blocks: blocks,
653 tokens: tokensUSD,
654 batches: genTestBatches(blocks, batches),
655 coordinators: apiCoordinators,
656 usrAddr: ethAddrToHez(usrAddr),
657 usrBjj: bjjToString(usrBjj),
658 accs: accs,
659 usrTxs: usrTxs,
660 allTxs: allTxs,
661 exits: apiExits,
662 usrExits: usrExits,
663 poolTxsToSend: poolTxsToSend,
664 poolTxsToReceive: poolTxsToReceive,
665 auths: apiAuths,
666 router: router,
667 }
668 // Fake server
669 if os.Getenv("FAKE_SERVER") == "yes" {
670 for {
671 log.Info("Running fake server until ^C is received")
672 time.Sleep(10 * time.Second)
673 }
674 }
675 // Run tests
676 result := m.Run()
677 // Stop server
678 if err := server.Shutdown(context.Background()); err != nil {
679 panic(err)
680 }
681 if err := database.Close(); err != nil {
682 panic(err)
683 }
684 if err := os.RemoveAll(dir); err != nil {
685 panic(err)
686 }
687 os.Exit(result)
688}
689
690func TestGetHistoryTxs(t *testing.T) {
691 endpoint := apiURL + "transactions-history"
692 fetchedTxs := []historyTxAPI{}
693 appendIter := func(intr interface{}) {
694 for i := 0; i < len(intr.(*historyTxsAPI).Txs); i++ {
695 tmp, err := copystructure.Copy(intr.(*historyTxsAPI).Txs[i])
696 if err != nil {
697 panic(err)
698 }
699 fetchedTxs = append(fetchedTxs, tmp.(historyTxAPI))
700 }
701 }
702 // Get all (no filters)
703 limit := 8
704 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
705 err := doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
706 assert.NoError(t, err)
707 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
708 // Uncomment once tx generation for tests is fixed
709 // // Get by ethAddr
710 // fetchedTxs = []historyTxAPI{}
711 // limit = 7
712 // path = fmt.Sprintf(
713 // "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
714 // endpoint, tc.usrAddr, limit,
715 // )
716 // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
717 // assert.NoError(t, err)
718 // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
719 // // Get by bjj
720 // fetchedTxs = []historyTxAPI{}
721 // limit = 6
722 // path = fmt.Sprintf(
723 // "%s?BJJ=%s&limit=%d&fromItem=",
724 // endpoint, tc.usrBjj, limit,
725 // )
726 // err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
727 // assert.NoError(t, err)
728 // assertHistoryTxAPIs(t, tc.usrTxs, fetchedTxs)
729 // Get by tokenID
730 fetchedTxs = []historyTxAPI{}
731 limit = 5
732 tokenID := tc.allTxs[0].Token.TokenID
733 path = fmt.Sprintf(
734 "%s?tokenId=%d&limit=%d&fromItem=",
735 endpoint, tokenID, limit,
736 )
737 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
738 assert.NoError(t, err)
739 tokenIDTxs := []historyTxAPI{}
740 for i := 0; i < len(tc.allTxs); i++ {
741 if tc.allTxs[i].Token.TokenID == tokenID {
742 tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
743 }
744 }
745 assertHistoryTxAPIs(t, tokenIDTxs, fetchedTxs)
746 // idx
747 fetchedTxs = []historyTxAPI{}
748 limit = 4
749 idx := tc.allTxs[0].ToIdx
750 path = fmt.Sprintf(
751 "%s?accountIndex=%s&limit=%d&fromItem=",
752 endpoint, idx, limit,
753 )
754 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
755 assert.NoError(t, err)
756 idxTxs := []historyTxAPI{}
757 for i := 0; i < len(tc.allTxs); i++ {
758 if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
759 tc.allTxs[i].ToIdx[6:] == idx[6:] {
760 idxTxs = append(idxTxs, tc.allTxs[i])
761 }
762 }
763 assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
764 // batchNum
765 fetchedTxs = []historyTxAPI{}
766 limit = 3
767 batchNum := tc.allTxs[0].BatchNum
768 path = fmt.Sprintf(
769 "%s?batchNum=%d&limit=%d&fromItem=",
770 endpoint, *batchNum, limit,
771 )
772 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
773 assert.NoError(t, err)
774 batchNumTxs := []historyTxAPI{}
775 for i := 0; i < len(tc.allTxs); i++ {
776 if tc.allTxs[i].BatchNum != nil &&
777 *tc.allTxs[i].BatchNum == *batchNum {
778 batchNumTxs = append(batchNumTxs, tc.allTxs[i])
779 }
780 }
781 assertHistoryTxAPIs(t, batchNumTxs, fetchedTxs)
782 // type
783 txTypes := []common.TxType{
784 // Uncomment once test gen is fixed
785 // common.TxTypeExit,
786 // common.TxTypeTransfer,
787 // common.TxTypeDeposit,
788 common.TxTypeCreateAccountDeposit,
789 // common.TxTypeCreateAccountDepositTransfer,
790 // common.TxTypeDepositTransfer,
791 common.TxTypeForceTransfer,
792 // common.TxTypeForceExit,
793 // common.TxTypeTransferToEthAddr,
794 // common.TxTypeTransferToBJJ,
795 }
796 for _, txType := range txTypes {
797 fetchedTxs = []historyTxAPI{}
798 limit = 2
799 path = fmt.Sprintf(
800 "%s?type=%s&limit=%d&fromItem=",
801 endpoint, txType, limit,
802 )
803 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
804 assert.NoError(t, err)
805 txTypeTxs := []historyTxAPI{}
806 for i := 0; i < len(tc.allTxs); i++ {
807 if tc.allTxs[i].Type == txType {
808 txTypeTxs = append(txTypeTxs, tc.allTxs[i])
809 }
810 }
811 assertHistoryTxAPIs(t, txTypeTxs, fetchedTxs)
812 }
813 // Multiple filters
814 fetchedTxs = []historyTxAPI{}
815 limit = 1
816 path = fmt.Sprintf(
817 "%s?batchNum=%d&tokenId=%d&limit=%d&fromItem=",
818 endpoint, *batchNum, tokenID, limit,
819 )
820 err = doGoodReqPaginated(path, historydb.OrderAsc, &historyTxsAPI{}, appendIter)
821 assert.NoError(t, err)
822 mixedTxs := []historyTxAPI{}
823 for i := 0; i < len(tc.allTxs); i++ {
824 if tc.allTxs[i].BatchNum != nil {
825 if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
826 mixedTxs = append(mixedTxs, tc.allTxs[i])
827 }
828 }
829 }
830 assertHistoryTxAPIs(t, mixedTxs, fetchedTxs)
831 // All, in reverse order
832 fetchedTxs = []historyTxAPI{}
833 limit = 5
834 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
835 err = doGoodReqPaginated(path, historydb.OrderDesc, &historyTxsAPI{}, appendIter)
836 assert.NoError(t, err)
837 flipedTxs := []historyTxAPI{}
838 for i := 0; i < len(tc.allTxs); i++ {
839 flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
840 }
841 assertHistoryTxAPIs(t, flipedTxs, fetchedTxs)
842 // 400
843 path = fmt.Sprintf(
844 "%s?accountIndex=%s&hermezEthereumAddress=%s",
845 endpoint, idx, tc.usrAddr,
846 )
847 err = doBadReq("GET", path, nil, 400)
848 assert.NoError(t, err)
849 path = fmt.Sprintf("%s?tokenId=X", endpoint)
850 err = doBadReq("GET", path, nil, 400)
851 assert.NoError(t, err)
852 // 404
853 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
854 err = doBadReq("GET", path, nil, 404)
855 assert.NoError(t, err)
856 path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
857 err = doBadReq("GET", path, nil, 404)
858 assert.NoError(t, err)
859}
860
861func TestGetHistoryTx(t *testing.T) {
862 // Get all txs by their ID
863 endpoint := apiURL + "transactions-history/"
864 fetchedTxs := []historyTxAPI{}
865 for _, tx := range tc.allTxs {
866 fetchedTx := historyTxAPI{}
867 assert.NoError(t, doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx))
868 fetchedTxs = append(fetchedTxs, fetchedTx)
869 }
870 assertHistoryTxAPIs(t, tc.allTxs, fetchedTxs)
871 // 400
872 err := doBadReq("GET", endpoint+"0x001", nil, 400)
873 assert.NoError(t, err)
874 // 404
875 err = doBadReq("GET", endpoint+"0x00000000000001e240004700", nil, 404)
876 assert.NoError(t, err)
877}
878
879func assertHistoryTxAPIs(t *testing.T, expected, actual []historyTxAPI) {
880 require.Equal(t, len(expected), len(actual))
881 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
882 actual[i].ItemID = 0
883 assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
884 expected[i].Timestamp = actual[i].Timestamp
885 if expected[i].Token.USDUpdate == nil {
886 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
887 } else {
888 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
889 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
890 }
891 test.AssertUSD(t, expected[i].HistoricUSD, actual[i].HistoricUSD)
892 if expected[i].L2Info != nil {
893 test.AssertUSD(t, expected[i].L2Info.HistoricFeeUSD, actual[i].L2Info.HistoricFeeUSD)
894 } else {
895 test.AssertUSD(t, expected[i].L1Info.HistoricLoadAmountUSD, actual[i].L1Info.HistoricLoadAmountUSD)
896 }
897 assert.Equal(t, expected[i], actual[i])
898 }
899}
900
901func TestGetExits(t *testing.T) {
902 endpoint := apiURL + "exits"
903 fetchedExits := []exitAPI{}
904 appendIter := func(intr interface{}) {
905 for i := 0; i < len(intr.(*exitsAPI).Exits); i++ {
906 tmp, err := copystructure.Copy(intr.(*exitsAPI).Exits[i])
907 if err != nil {
908 panic(err)
909 }
910 fetchedExits = append(fetchedExits, tmp.(exitAPI))
911 }
912 }
913 // Get all (no filters)
914 limit := 8
915 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
916 err := doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
917 assert.NoError(t, err)
918 assertExitAPIs(t, tc.exits, fetchedExits)
919
920 // Get by ethAddr
921 fetchedExits = []exitAPI{}
922 limit = 7
923 path = fmt.Sprintf(
924 "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
925 endpoint, tc.usrAddr, limit,
926 )
927 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
928 assert.NoError(t, err)
929 assertExitAPIs(t, tc.usrExits, fetchedExits)
930 // Get by bjj
931 fetchedExits = []exitAPI{}
932 limit = 6
933 path = fmt.Sprintf(
934 "%s?BJJ=%s&limit=%d&fromItem=",
935 endpoint, tc.usrBjj, limit,
936 )
937 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
938 assert.NoError(t, err)
939 assertExitAPIs(t, tc.usrExits, fetchedExits)
940 // Get by tokenID
941 fetchedExits = []exitAPI{}
942 limit = 5
943 tokenID := tc.exits[0].Token.TokenID
944 path = fmt.Sprintf(
945 "%s?tokenId=%d&limit=%d&fromItem=",
946 endpoint, tokenID, limit,
947 )
948 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
949 assert.NoError(t, err)
950 tokenIDExits := []exitAPI{}
951 for i := 0; i < len(tc.exits); i++ {
952 if tc.exits[i].Token.TokenID == tokenID {
953 tokenIDExits = append(tokenIDExits, tc.exits[i])
954 }
955 }
956 assertExitAPIs(t, tokenIDExits, fetchedExits)
957 // idx
958 fetchedExits = []exitAPI{}
959 limit = 4
960 idx := tc.exits[0].AccountIdx
961 path = fmt.Sprintf(
962 "%s?accountIndex=%s&limit=%d&fromItem=",
963 endpoint, idx, limit,
964 )
965 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
966 assert.NoError(t, err)
967 idxExits := []exitAPI{}
968 for i := 0; i < len(tc.exits); i++ {
969 if tc.exits[i].AccountIdx[6:] == idx[6:] {
970 idxExits = append(idxExits, tc.exits[i])
971 }
972 }
973 assertExitAPIs(t, idxExits, fetchedExits)
974 // batchNum
975 fetchedExits = []exitAPI{}
976 limit = 3
977 batchNum := tc.exits[0].BatchNum
978 path = fmt.Sprintf(
979 "%s?batchNum=%d&limit=%d&fromItem=",
980 endpoint, batchNum, limit,
981 )
982 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
983 assert.NoError(t, err)
984 batchNumExits := []exitAPI{}
985 for i := 0; i < len(tc.exits); i++ {
986 if tc.exits[i].BatchNum == batchNum {
987 batchNumExits = append(batchNumExits, tc.exits[i])
988 }
989 }
990 assertExitAPIs(t, batchNumExits, fetchedExits)
991 // Multiple filters
992 fetchedExits = []exitAPI{}
993 limit = 1
994 path = fmt.Sprintf(
995 "%s?batchNum=%d&tokeId=%d&limit=%d&fromItem=",
996 endpoint, batchNum, tokenID, limit,
997 )
998 err = doGoodReqPaginated(path, historydb.OrderAsc, &exitsAPI{}, appendIter)
999 assert.NoError(t, err)
1000 mixedExits := []exitAPI{}
1001 flipedExits := []exitAPI{}
1002 for i := 0; i < len(tc.exits); i++ {
1003 if tc.exits[i].BatchNum == batchNum && tc.exits[i].Token.TokenID == tokenID {
1004 mixedExits = append(mixedExits, tc.exits[i])
1005 }
1006 flipedExits = append(flipedExits, tc.exits[len(tc.exits)-1-i])
1007 }
1008 assertExitAPIs(t, mixedExits, fetchedExits)
1009 // All, in reverse order
1010 fetchedExits = []exitAPI{}
1011 limit = 5
1012 path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
1013 err = doGoodReqPaginated(path, historydb.OrderDesc, &exitsAPI{}, appendIter)
1014 assert.NoError(t, err)
1015 assertExitAPIs(t, flipedExits, fetchedExits)
1016 // 400
1017 path = fmt.Sprintf(
1018 "%s?accountIndex=%s&hermezEthereumAddress=%s",
1019 endpoint, idx, tc.usrAddr,
1020 )
1021 err = doBadReq("GET", path, nil, 400)
1022 assert.NoError(t, err)
1023 path = fmt.Sprintf("%s?tokenId=X", endpoint)
1024 err = doBadReq("GET", path, nil, 400)
1025 assert.NoError(t, err)
1026 // 404
1027 path = fmt.Sprintf("%s?batchNum=999999", endpoint)
1028 err = doBadReq("GET", path, nil, 404)
1029 assert.NoError(t, err)
1030 path = fmt.Sprintf("%s?limit=1000&fromItem=999999", endpoint)
1031 err = doBadReq("GET", path, nil, 404)
1032 assert.NoError(t, err)
1033}
1034
1035func TestGetExit(t *testing.T) {
1036 // Get all txs by their ID
1037 endpoint := apiURL + "exits/"
1038 fetchedExits := []exitAPI{}
1039 for _, exit := range tc.exits {
1040 fetchedExit := exitAPI{}
1041 assert.NoError(
1042 t, doGoodReq(
1043 "GET",
1044 fmt.Sprintf("%s%d/%s", endpoint, exit.BatchNum, exit.AccountIdx),
1045 nil, &fetchedExit,
1046 ),
1047 )
1048 fetchedExits = append(fetchedExits, fetchedExit)
1049 }
1050 assertExitAPIs(t, tc.exits, fetchedExits)
1051 // 400
1052 err := doBadReq("GET", endpoint+"1/haz:BOOM:1", nil, 400)
1053 assert.NoError(t, err)
1054 err = doBadReq("GET", endpoint+"-1/hez:BOOM:1", nil, 400)
1055 assert.NoError(t, err)
1056 // 404
1057 err = doBadReq("GET", endpoint+"494/hez:XXX:1", nil, 404)
1058 assert.NoError(t, err)
1059}
1060
1061func assertExitAPIs(t *testing.T, expected, actual []exitAPI) {
1062 require.Equal(t, len(expected), len(actual))
1063 for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
1064 actual[i].ItemID = 0
1065 if expected[i].Token.USDUpdate == nil {
1066 assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
1067 } else {
1068 assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
1069 expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
1070 }
1071 assert.Equal(t, expected[i], actual[i])
1072 }
1073}
1074func TestGetConfig(t *testing.T) {
1075 endpoint := apiURL + "config"
1076 var configTest configAPI
1077 assert.NoError(t, doGoodReq("GET", endpoint, nil, &configTest))
1078 assert.Equal(t, config, configTest)
1079 assert.Equal(t, cg, &configTest)
1080}
1081
1082func TestPoolTxs(t *testing.T) {
1083 // POST
1084 endpoint := apiURL + "transactions-pool"
1085 fetchedTxID := common.TxID{}
1086 for _, tx := range tc.poolTxsToSend {
1087 jsonTxBytes, err := json.Marshal(tx)
1088 assert.NoError(t, err)
1089 jsonTxReader := bytes.NewReader(jsonTxBytes)
1090 assert.NoError(
1091 t, doGoodReq(
1092 "POST",
1093 endpoint,
1094 jsonTxReader, &fetchedTxID,
1095 ),
1096 )
1097 assert.Equal(t, tx.TxID, fetchedTxID)
1098 }
1099 // 400
1100 // Wrong signature
1101 badTx := tc.poolTxsToSend[0]
1102 badTx.FromIdx = "hez:foo:1000"
1103 jsonTxBytes, err := json.Marshal(badTx)
1104 assert.NoError(t, err)
1105 jsonTxReader := bytes.NewReader(jsonTxBytes)
1106 err = doBadReq("POST", endpoint, jsonTxReader, 400)
1107 assert.NoError(t, err)
1108 // Wrong to
1109 badTx = tc.poolTxsToSend[0]
1110 ethAddr := "hez:0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
1111 badTx.ToEthAddr = ðAddr
1112 badTx.ToIdx = nil
1113 jsonTxBytes, err = json.Marshal(badTx)
1114 assert.NoError(t, err)
1115 jsonTxReader = bytes.NewReader(jsonTxBytes)
1116 err = doBadReq("POST", endpoint, jsonTxReader, 400)
1117 assert.NoError(t, err)
1118 // Wrong rq
1119 badTx = tc.poolTxsToSend[0]
1120 rqFromIdx := "hez:foo:30"
1121 badTx.RqFromIdx = &rqFromIdx
1122 jsonTxBytes, err = json.Marshal(badTx)
1123 assert.NoError(t, err)
1124 jsonTxReader = bytes.NewReader(jsonTxBytes)
1125 err = doBadReq("POST", endpoint, jsonTxReader, 400)
1126 assert.NoError(t, err)
1127 // GET
1128 endpoint += "/"
1129 for _, tx := range tc.poolTxsToReceive {
1130 fetchedTx := sendPoolTx{}
1131 assert.NoError(
1132 t, doGoodReq(
1133 "GET",
1134 endpoint+tx.TxID.String(),
1135 nil, &fetchedTx,
1136 ),
1137 )
1138 assertPoolTx(t, tx, fetchedTx)
1139 }
1140 // 400
1141 err = doBadReq("GET", endpoint+"0xG20000000156660000000090", nil, 400)
1142 assert.NoError(t, err)
1143 // 404
1144 err = doBadReq("GET", endpoint+"0x020000000156660000000090", nil, 404)
1145 assert.NoError(t, err)
1146}
1147
1148func assertPoolTx(t *testing.T, expected, actual sendPoolTx) {
1149 // state should be pending
1150 assert.Equal(t, common.PoolL2TxStatePending, actual.State)
1151 expected.State = actual.State
1152 // timestamp should be very close to now
1153 assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
1154 expected.Timestamp = actual.Timestamp
1155 // token timestamp
1156 if expected.Token.USDUpdate == nil {
1157 assert.Equal(t, expected.Token.USDUpdate, actual.Token.USDUpdate)
1158 } else {
1159 assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())
1160 expected.Token.USDUpdate = actual.Token.USDUpdate
1161 }
1162 assert.Equal(t, expected, actual)
1163}
1164
1165func TestGetCoordinators(t *testing.T) {
1166 endpoint := apiURL + "coordinators"
1167 fetchedCoordinators := []coordinatorAPI{}
1168
1169 appendIter := func(intr interface{}) {
1170 for i := 0; i < len(intr.(*coordinatorsAPI).Coordinators); i++ {
1171 tmp, err := copystructure.Copy(intr.(*coordinatorsAPI).Coordinators[i])
1172 if err != nil {
1173 panic(err)
1174 }
1175 fetchedCoordinators = append(fetchedCoordinators, tmp.(coordinatorAPI))
1176 }
1177 }
1178
1179 limit := 5
1180
1181 path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
1182 err := doGoodReqPaginated(path, historydb.OrderAsc, &coordinatorsAPI{}, appendIter)
1183 assert.NoError(t, err)
1184 assert.Equal(t, tc.coordinators, fetchedCoordinators)
1185
1186 // Reverse Order
1187 reversedCoordinators := []coordinatorAPI{}
1188 appendIter = func(intr interface{}) {
1189 for i := 0; i < len(intr.(*coordinatorsAPI).Coordinators); i++ {
1190 tmp, err := copystructure.Copy(intr.(*coordinatorsAPI).Coordinators[i])
1191 if err != nil {
1192 panic(err)
1193 }
1194 reversedCoordinators = append(reversedCoordinators, tmp.(coordinatorAPI))
1195 }
1196 }
1197 err = doGoodReqPaginated(path, historydb.OrderDesc, &coordinatorsAPI{}, appendIter)
1198 assert.NoError(t, err)
1199 for i := 0; i < len(fetchedCoordinators); i++ {
1200 assert.Equal(t, reversedCoordinators[i], fetchedCoordinators[len(fetchedCoordinators)-1-i])
1201 }
1202
1203 // Test GetCoordinator
1204 path = fmt.Sprintf("%s/%s", endpoint, fetchedCoordinators[2].Forger.String())
1205 coordinator := coordinatorAPI{}
1206 assert.NoError(t, doGoodReq("GET", path, nil, &coordinator))
1207 assert.Equal(t, fetchedCoordinators[2], coordinator)
1208 // 400
1209 path = fmt.Sprintf("%s/0x001", endpoint)
1210 err = doBadReq("GET", path, nil, 400)
1211 assert.NoError(t, err)
1212 // 404
1213 path = fmt.Sprintf("%s/0xaa942cfcd25ad4d90a62358b0dd84f33b398262a", endpoint)
1214 err = doBadReq("GET", path, nil, 404)
1215 assert.NoError(t, err)
1216}
1217func TestAccountCreationAuth(t *testing.T) {
1218 // POST
1219 endpoint := apiURL + "account-creation-authorization"
1220 for _, auth := range tc.auths {
1221 jsonAuthBytes, err := json.Marshal(auth)
1222 assert.NoError(t, err)
1223 jsonAuthReader := bytes.NewReader(jsonAuthBytes)
1224 fmt.Println(string(jsonAuthBytes))
1225 assert.NoError(
1226 t, doGoodReq(
1227 "POST",
1228 endpoint,
1229 jsonAuthReader, nil,
1230 ),
1231 )
1232 }
1233 // GET
1234 endpoint += "/"
1235 for _, auth := range tc.auths {
1236 fetchedAuth := accountCreationAuthAPI{}
1237 assert.NoError(
1238 t, doGoodReq(
1239 "GET",
1240 endpoint+auth.EthAddr,
1241 nil, &fetchedAuth,
1242 ),
1243 )
1244 assertAuth(t, auth, fetchedAuth)
1245 }
1246 // POST
1247 // 400
1248 // Wrong addr
1249 badAuth := tc.auths[0]
1250 badAuth.EthAddr = ethAddrToHez(ethCommon.BigToAddress(big.NewInt(1)))
1251 jsonAuthBytes, err := json.Marshal(badAuth)
1252 assert.NoError(t, err)
1253 jsonAuthReader := bytes.NewReader(jsonAuthBytes)
1254 err = doBadReq("POST", endpoint, jsonAuthReader, 400)
1255 assert.NoError(t, err)
1256 // Wrong signature
1257 badAuth = tc.auths[0]
1258 badAuth.Signature = badAuth.Signature[:len(badAuth.Signature)-1]
1259 badAuth.Signature += "F"
1260 jsonAuthBytes, err = json.Marshal(badAuth)
1261 assert.NoError(t, err)
1262 jsonAuthReader = bytes.NewReader(jsonAuthBytes)
1263 err = doBadReq("POST", endpoint, jsonAuthReader, 400)
1264 assert.NoError(t, err)
1265 // GET
1266 // 400
1267 err = doBadReq("GET", endpoint+"hez:0xFooBar", nil, 400)
1268 assert.NoError(t, err)
1269 // 404
1270 err = doBadReq("GET", endpoint+"hez:0x0000000000000000000000000000000000000001", nil, 404)
1271 assert.NoError(t, err)
1272}
1273
1274func assertAuth(t *testing.T, expected, actual accountCreationAuthAPI) {
1275 // timestamp should be very close to now
1276 assert.Less(t, time.Now().UTC().Unix()-3, actual.Timestamp.Unix())
1277 expected.Timestamp = actual.Timestamp
1278 assert.Equal(t, expected, actual)
1279}
1280
1281func doGoodReqPaginated(
1282 path, order string,
1283 iterStruct db.Paginationer,
1284 appendIter func(res interface{}),
1285) error {
1286 next := 0
1287 for {
1288 // Call API to get this iteration items
1289 iterPath := path
1290 if next == 0 && order == historydb.OrderDesc {
1291 // Fetch first item in reverse order
1292 iterPath += "99999"
1293 } else {
1294 // Fetch from next item or 0 if it's ascending order
1295 iterPath += strconv.Itoa(next)
1296 }
1297 if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
1298 return err
1299 }
1300 appendIter(iterStruct)
1301 // Keep iterating?
1302 pag := iterStruct.GetPagination()
1303 if order == historydb.OrderAsc {
1304 if pag.LastReturnedItem == pag.LastItem { // No
1305 break
1306 } else { // Yes
1307 next = pag.LastReturnedItem + 1
1308 }
1309 } else {
1310 if pag.FirstReturnedItem == pag.FirstItem { // No
1311 break
1312 } else { // Yes
1313 next = pag.FirstReturnedItem - 1
1314 }
1315 }
1316 }
1317 return nil
1318}
1319
1320func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{}) error {
1321 ctx := context.Background()
1322 client := &http.Client{}
1323 httpReq, err := http.NewRequest(method, path, reqBody)
1324 if err != nil {
1325 return err
1326 }
1327 if reqBody != nil {
1328 httpReq.Header.Add("Content-Type", "application/json")
1329 }
1330 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1331 if err != nil {
1332 return err
1333 }
1334 // Validate request against swagger spec
1335 requestValidationInput := &swagger.RequestValidationInput{
1336 Request: httpReq,
1337 PathParams: pathParams,
1338 Route: route,
1339 }
1340 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1341 return err
1342 }
1343 // Do API call
1344 resp, err := client.Do(httpReq)
1345 if err != nil {
1346 return err
1347 }
1348 if resp.Body == nil && returnStruct != nil {
1349 return errors.New("Nil body")
1350 }
1351 //nolint
1352 defer resp.Body.Close()
1353 body, err := ioutil.ReadAll(resp.Body)
1354 if err != nil {
1355 return err
1356 }
1357 if resp.StatusCode != 200 {
1358 return fmt.Errorf("%d response. Body: %s", resp.StatusCode, string(body))
1359 }
1360 if returnStruct == nil {
1361 return nil
1362 }
1363 // Unmarshal body into return struct
1364 if err := json.Unmarshal(body, returnStruct); err != nil {
1365 log.Error("invalid json: " + string(body))
1366 return err
1367 }
1368 // Validate response against swagger spec
1369 responseValidationInput := &swagger.ResponseValidationInput{
1370 RequestValidationInput: requestValidationInput,
1371 Status: resp.StatusCode,
1372 Header: resp.Header,
1373 }
1374 responseValidationInput = responseValidationInput.SetBodyBytes(body)
1375 return swagger.ValidateResponse(ctx, responseValidationInput)
1376}
1377
1378func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int) error {
1379 ctx := context.Background()
1380 client := &http.Client{}
1381 httpReq, _ := http.NewRequest(method, path, reqBody)
1382 route, pathParams, err := tc.router.FindRoute(httpReq.Method, httpReq.URL)
1383 if err != nil {
1384 return err
1385 }
1386 // Validate request against swagger spec
1387 requestValidationInput := &swagger.RequestValidationInput{
1388 Request: httpReq,
1389 PathParams: pathParams,
1390 Route: route,
1391 }
1392 if err := swagger.ValidateRequest(ctx, requestValidationInput); err != nil {
1393 if expectedResponseCode != 400 {
1394 return err
1395 }
1396 log.Warn("The request does not match the API spec")
1397 }
1398 // Do API call
1399 resp, err := client.Do(httpReq)
1400 if err != nil {
1401 return err
1402 }
1403 if resp.Body == nil {
1404 return errors.New("Nil body")
1405 }
1406 //nolint
1407 defer resp.Body.Close()
1408 body, err := ioutil.ReadAll(resp.Body)
1409 if err != nil {
1410 return err
1411 }
1412 if resp.StatusCode != expectedResponseCode {
1413 return fmt.Errorf("Unexpected response code: %d. Body: %s", resp.StatusCode, string(body))
1414 }
1415 // Validate response against swagger spec
1416 responseValidationInput := &swagger.ResponseValidationInput{
1417 RequestValidationInput: requestValidationInput,
1418 Status: resp.StatusCode,
1419 Header: resp.Header,
1420 }
1421 responseValidationInput = responseValidationInput.SetBodyBytes(body)
1422 return swagger.ValidateResponse(ctx, responseValidationInput)
1423}