Browse Source

Integrate til into api

feature/sql-semaphore1
Arnau B 4 years ago
parent
commit
ce1218e28a
19 changed files with 575 additions and 758 deletions
  1. +2
    -0
      api/account_test.go
  2. +136
    -352
      api/api_test.go
  3. +2
    -1
      api/batch.go
  4. +14
    -12
      api/batch_test.go
  5. +3
    -1
      api/bids_test.go
  6. +42
    -30
      api/coordinator_test.go
  7. +31
    -22
      api/exits_test.go
  8. +1
    -1
      api/slots.go
  9. +3
    -1
      api/slots_test.go
  10. +12
    -7
      api/state.go
  11. +13
    -11
      api/state_test.go
  12. +13
    -6
      api/swagger.yml
  13. +2
    -0
      api/token_test.go
  14. +232
    -258
      api/txshistory_test.go
  15. +6
    -28
      api/txspool_test.go
  16. +3
    -3
      apitypes/apitypes.go
  17. +44
    -15
      db/historydb/historydb.go
  18. +15
    -10
      db/migrations/0001.sql
  19. +1
    -0
      go.sum

+ 2
- 0
api/account_test.go

@ -36,6 +36,8 @@ func (t testAccountsResponse) GetPending() (pendingItems, lastItemID uint64) {
func (t *testAccountsResponse) Len() int { return len(t.Accounts) } func (t *testAccountsResponse) Len() int { return len(t.Accounts) }
func (t testAccountsResponse) New() Pendinger { return &testAccountsResponse{} }
func genTestAccounts(accounts []common.Account, tokens []historydb.TokenWithUSD) []testAccount { func genTestAccounts(accounts []common.Account, tokens []historydb.TokenWithUSD) []testAccount {
tAccounts := []testAccount{} tAccounts := []testAccount{}
for x, account := range accounts { for x, account := range accounts {

+ 136
- 352
api/api_test.go

@ -10,7 +10,6 @@ import (
"math/big" "math/big"
"net/http" "net/http"
"os" "os"
"sort"
"strconv" "strconv"
"testing" "testing"
"time" "time"
@ -26,7 +25,6 @@ import (
"github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/hermez-node/log"
"github.com/hermeznetwork/hermez-node/test" "github.com/hermeznetwork/hermez-node/test"
"github.com/hermeznetwork/hermez-node/test/til" "github.com/hermeznetwork/hermez-node/test/til"
"github.com/iden3/go-iden3-crypto/babyjub"
) )
// Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem // Pendinger is an interface that allows getting last returned item ID and PendingItems to be used for building fromItem
@ -34,6 +32,7 @@ import (
type Pendinger interface { type Pendinger interface {
GetPending() (pendingItems, lastItemID uint64) GetPending() (pendingItems, lastItemID uint64)
Len() int Len() int
New() Pendinger
} }
const apiPort = ":4010" const apiPort = ":4010"
@ -46,6 +45,11 @@ var SetBlockchain = `
AddToken(2) AddToken(2)
AddToken(3) AddToken(3)
AddToken(4) AddToken(4)
AddToken(5)
AddToken(6)
AddToken(7)
AddToken(8)
> block
// Coordinator accounts, Idxs: 256, 257 // Coordinator accounts, Idxs: 256, 257
CreateAccountCoordinator(0) Coord CreateAccountCoordinator(0) Coord
@ -54,8 +58,8 @@ var SetBlockchain = `
// close Block:0, Batch:0 // close Block:0, Batch:0
> batch > batch
CreateAccountDeposit(0) A: 500
CreateAccountDeposit(1) C: 0
CreateAccountDeposit(0) A: 111111111
CreateAccountDeposit(1) C: 222222222
CreateAccountCoordinator(0) C CreateAccountCoordinator(0) C
// close Block:0, Batch:1 // close Block:0, Batch:1
@ -64,7 +68,7 @@ var SetBlockchain = `
// Coord(0): 0, Coord(1): 0 // Coord(0): 0, Coord(1): 0
// C(0): 0 // C(0): 0
CreateAccountDeposit(1) A: 500
CreateAccountDeposit(1) A: 333333333
// close Block:0, Batch:2 // close Block:0, Batch:2
> batchL1 > batchL1
@ -72,45 +76,40 @@ var SetBlockchain = `
// close Block:0, Batch:3 // close Block:0, Batch:3
> batchL1 > batchL1
CreateAccountDepositTransfer(0) B-A: 500, 100
CreateAccountDepositTransfer(0) B-A: 444444444, 1234444444 // to_eth_addr is NULL
// close Block:0, Batch:4 // close Block:0, Batch:4
> batchL1 > batchL1
CreateAccountDeposit(0) D: 800
CreateAccountDeposit(0) D: 555555555
// close Block:0, Batch:5 // close Block:0, Batch:5
> batchL1 > batchL1
CreateAccountCoordinator(1) B CreateAccountCoordinator(1) B
Transfer(1) A-B: 200 (126)
Transfer(0) B-C: 100 (126)
Transfer(1) A-B: 111111 (2) // to_eth_addr is NULL
Transfer(0) B-C: 222222 (3)
// close Block:0, Batch:6 // close Block:0, Batch:6
> batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2} > batchL1 // forge L1User{1}, forge L1Coord{2}, forge L2{2}
Deposit(0) C: 500
DepositTransfer(0) C-D: 400, 100
Deposit(0) C: 666666666
DepositTransfer(0) C-D: 777777777, 123777777 // to_eth_addr is NULL
Transfer(0) A-B: 100 (126)
Transfer(0) C-A: 50 (126)
Transfer(1) B-C: 100 (126)
Exit(0) A: 100 (126)
Transfer(0) A-B: 333333 (111)
Transfer(0) C-A: 444444 (222)
Transfer(1) B-C: 555555 (123)
Exit(0) A: 666666 (44)
ForceTransfer(0) D-B: 200
ForceExit(0) B: 100
ForceTransfer(0) D-B: 777777 // to_eth_addr is NULL
ForceExit(0) B: 888888
// close Block:0, Batch:7 // close Block:0, Batch:7
> batchL1 > batchL1
> block > block
AddToken(5)
AddToken(6)
AddToken(7)
AddToken(8)
Transfer(0) D-A: 300 (126)
Transfer(0) B-D: 100 (126)
Transfer(0) D-A: 999999 (77)
Transfer(0) B-D: 123123 (55)
// close Block:1, Batch:0 // close Block:1, Batch:0
> batchL1 > batchL1
@ -166,13 +165,8 @@ type testCommon struct {
fullBatches []testFullBatch fullBatches []testFullBatch
coordinators []historydb.CoordinatorAPI coordinators []historydb.CoordinatorAPI
accounts []testAccount accounts []testAccount
usrAddr string
usrBjj string
accs []common.Account
usrTxs []testTx
allTxs []testTx
txs []testTx
exits []testExit exits []testExit
usrExits []testExit
poolTxsToSend []testPoolTxSend poolTxsToSend []testPoolTxSend
poolTxsToReceive []testPoolTxReceive poolTxsToReceive []testPoolTxReceive
auths []testAuth auths []testAuth
@ -192,16 +186,6 @@ var api *API
// emulating the task of the synchronizer in order to have data to be returned // emulating the task of the synchronizer in order to have data to be returned
// by the API endpoints that will be tested // by the API endpoints that will be tested
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
/*
til update considerations:
1. Two instructions sets should be enough (one for L2 another for historydb)
2. FillBlocksExtra function must be used, there is a coment on top of the function that explains which data is setted
3. Some data will not be generated by til nor FillBlocksExtra, test.GenXXX will still be required to cover this cases
4. Most of the historydb inserts should be replaced with nBlocks calls to AddBlockSCData
5. When defining til instructions, there is no need to have 100s of entries for each table, but it's interesting to
cover all different cases (for instance all tx types)
*/
// Initializations // Initializations
// Swagger // Swagger
router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml") router := swagger.NewRouter().WithSwaggerFromFile("./swagger.yml")
@ -260,171 +244,117 @@ func TestMain(m *testing.M) {
} }
}() }()
// Fill HistoryDB and StateDB with fake data
// Gen blocks and add them to DB
const nBlocks = 5
// TODO: UPDATE with til
blocks := test.GenBlocks(1, nBlocks+1)
err = api.h.AddBlocks(blocks)
// Reset DB
test.WipeDB(api.h.DB())
// Genratre blockchain data with til
tcc := til.NewContext(common.RollupConstMaxL1UserTx)
tilCfgExtra := til.ConfigExtra{
BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
CoordUser: "Coord",
}
blocksData, err := tcc.GenerateBlocks(SetBlockchain)
if err != nil {
panic(err)
}
err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
if err != nil {
panic(err)
}
AddAditionalInformation(blocksData)
// Generate L2 Txs with til
commonPoolTxs, err := tcc.GeneratePoolL2Txs(til.SetPoolL2MinimumFlow0)
if err != nil { if err != nil {
panic(err) panic(err)
} }
lastBlockNum := blocks[nBlocks-1].EthBlockNum
// Gen tokens and add them to DB
const nTokens = 10
// TODO: UPDATE with til
tokens, ethToken := test.GenTokens(nTokens, blocks)
err = api.h.AddTokens(tokens)
// Extract til generated data, and add it to HistoryDB
var commonBlocks []common.Block
var commonBatches []common.Batch
var commonAccounts []common.Account
var commonExitTree []common.ExitInfo
var commonL1Txs []common.L1Tx
var commonL2Txs []common.L2Tx
// Add ETH token at the beginning of the array
testTokens := []historydb.TokenWithUSD{}
ethUSD := float64(500)
ethNow := time.Now()
testTokens = append(testTokens, historydb.TokenWithUSD{
TokenID: test.EthToken.TokenID,
EthBlockNum: test.EthToken.EthBlockNum,
EthAddr: test.EthToken.EthAddr,
Name: test.EthToken.Name,
Symbol: test.EthToken.Symbol,
Decimals: test.EthToken.Decimals,
USD: &ethUSD,
USDUpdate: &ethNow,
})
err = api.h.UpdateTokenValue(test.EthToken.Symbol, ethUSD)
if err != nil { if err != nil {
panic(err) panic(err)
} }
tokens = append([]common.Token{ethToken}, tokens...)
// Set token value
tokensUSD := []historydb.TokenWithUSD{}
for i, tkn := range tokens {
token := historydb.TokenWithUSD{
TokenID: tkn.TokenID,
EthBlockNum: tkn.EthBlockNum,
EthAddr: tkn.EthAddr,
Name: tkn.Name,
Symbol: tkn.Symbol,
Decimals: tkn.Decimals,
for _, block := range blocksData {
// Insert block into HistoryDB
if err := api.h.AddBlockSCData(&block); err != nil { //nolint:gosec block is used as read only in the function
panic(err)
} }
// Set value of 50% of the tokens
if i%2 != 0 {
value := float64(i) * 1.234567
// Extract data
commonBlocks = append(commonBlocks, block.Block)
for i, tkn := range block.Rollup.AddedTokens {
token := historydb.TokenWithUSD{
TokenID: tkn.TokenID,
EthBlockNum: tkn.EthBlockNum,
EthAddr: tkn.EthAddr,
Name: tkn.Name,
Symbol: tkn.Symbol,
Decimals: tkn.Decimals,
}
value := float64(i + 423)
now := time.Now().UTC() now := time.Now().UTC()
token.USD = &value token.USD = &value
token.USDUpdate = &now token.USDUpdate = &now
// Set value in DB
err = api.h.UpdateTokenValue(token.Symbol, value) err = api.h.UpdateTokenValue(token.Symbol, value)
if err != nil { if err != nil {
panic(err) panic(err)
} }
testTokens = append(testTokens, token)
} }
tokensUSD = append(tokensUSD, token)
}
// Gen batches and add them to DB
const nBatches = 10
// TODO: UPDATE with til
batches := test.GenBatches(nBatches, blocks)
err = api.h.AddBatches(batches)
if err != nil {
panic(err)
}
// Gen accounts and add them to HistoryDB and StateDB
const totalAccounts = 40
const userAccounts = 4
usrAddr := ethCommon.BigToAddress(big.NewInt(4896847))
privK := babyjub.NewRandPrivKey()
usrBjj := privK.Public()
// TODO: UPDATE with til
accs := test.GenAccounts(totalAccounts, userAccounts, tokens, &usrAddr, usrBjj, batches)
err = api.h.AddAccounts(accs)
if err != nil {
panic(err)
}
// api.s.CreateAccount called in new part with til
/* for i := 0; i < len(accs); i++ {
if _, err := api.s.CreateAccount(accs[i].Idx, &accs[i]); err != nil {
panic(err)
}
} */
// helper to vinculate user related resources
usrIdxs := []string{}
for _, acc := range accs {
if acc.EthAddr == usrAddr || acc.PublicKey == usrBjj {
for _, token := range tokens {
if token.TokenID == acc.TokenID {
usrIdxs = append(usrIdxs, idxToHez(acc.Idx, token.Symbol))
}
}
// Set USD value for tokens in DB
commonL1Txs = append(commonL1Txs, block.Rollup.L1UserTxs...)
for _, batch := range block.Rollup.Batches {
commonL2Txs = append(commonL2Txs, batch.L2Txs...)
commonAccounts = append(commonAccounts, batch.CreatedAccounts...)
commonBatches = append(commonBatches, batch.Batch)
commonExitTree = append(commonExitTree, batch.ExitTree...)
commonL1Txs = append(commonL1Txs, batch.L1CoordinatorTxs...)
} }
} }
// Gen exits and add them to DB
const totalExits = 40
// TODO: UPDATE with til
exits := test.GenExitTree(totalExits, batches, accs, blocks)
err = api.h.AddExitTree(exits)
if err != nil {
panic(err)
}
// L1 and L2 txs need to be sorted in a combined way
// Gen L1Txs
const totalL1Txs = 40
const userL1Txs = 4
// TODO: UPDATE with til
usrL1Txs, othrL1Txs := test.GenL1Txs(256, totalL1Txs, userL1Txs, &usrAddr, accs, tokens, blocks, batches)
// Gen L2Txs
const totalL2Txs = 20
const userL2Txs = 4
// TODO: UPDATE with til
usrL2Txs, othrL2Txs := test.GenL2Txs(256+totalL1Txs, totalL2Txs, userL2Txs, &usrAddr, accs, tokens, blocks, batches)
// Sort txs
sortedTxs := []txSortFielder{}
for i := 0; i < len(usrL1Txs); i++ {
wL1 := wrappedL1(usrL1Txs[i])
sortedTxs = append(sortedTxs, &wL1)
}
for i := 0; i < len(othrL1Txs); i++ {
wL1 := wrappedL1(othrL1Txs[i])
sortedTxs = append(sortedTxs, &wL1)
}
for i := 0; i < len(usrL2Txs); i++ {
wL2 := wrappedL2(usrL2Txs[i])
sortedTxs = append(sortedTxs, &wL2)
}
for i := 0; i < len(othrL2Txs); i++ {
wL2 := wrappedL2(othrL2Txs[i])
sortedTxs = append(sortedTxs, &wL2)
}
sort.Sort(txsSort(sortedTxs))
// Store txs to DB
for _, genericTx := range sortedTxs {
l1 := genericTx.L1()
l2 := genericTx.L2()
if l1 != nil {
err = api.h.AddL1Txs([]common.L1Tx{*l1})
if err != nil {
panic(err)
}
} else if l2 != nil {
err = api.h.AddL2Txs([]common.L2Tx{*l2})
if err != nil {
panic(err)
}
} else {
panic("should be l1 or l2")
// lastBlockNum2 := blocksData[len(blocksData)-1].Block.EthBlockNum
// Add accounts to StateDB
for i := 0; i < len(commonAccounts); i++ {
if _, err := api.s.CreateAccount(commonAccounts[i].Idx, &commonAccounts[i]); err != nil {
panic(err)
} }
} }
// Coordinators
// Generate Coordinators and add them to HistoryDB
const nCoords = 10 const nCoords = 10
coords := test.GenCoordinators(nCoords, blocks)
err = api.h.AddCoordinators(coords)
if err != nil {
panic(err)
}
fromItem := uint(0)
limit := uint(99999)
coordinators, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
if err != nil {
commonCoords := test.GenCoordinators(nCoords, commonBlocks)
if err := api.h.AddCoordinators(commonCoords); err != nil {
panic(err) panic(err)
} }
// Bids
// Generate Bids and add them to HistoryDB
const nBids = 20 const nBids = 20
bids := test.GenBids(nBids, blocks, coords)
err = api.h.AddBids(bids)
if err != nil {
commonBids := test.GenBids(nBids, commonBlocks, commonCoords)
if err = api.h.AddBids(commonBids); err != nil {
panic(err) panic(err)
} }
testBids := genTestBids(blocks, coordinators, bids)
// Vars
// Generate SC vars and add them to HistoryDB (if needed)
var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)} var defaultSlotSetBid [6]*big.Int = [6]*big.Int{big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10), big.NewInt(10)}
auctionVars := common.AuctionVariables{ auctionVars := common.AuctionVariables{
EthBlockNum: int64(2), EthBlockNum: int64(2),
@ -453,189 +383,37 @@ func TestMain(m *testing.M) {
panic(err) panic(err)
} }
const nSlots = 20
// Set testCommon
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
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.
testBatches, fullBatches := genTestBatches(blocks, batches, allTxs)
/* usrExits, allExits*/ _, _ = genTestExits(exits, tokensUSD, accs, usrIdxs)
// NEW WITH TIL
// Reset DB
test.WipeDB(api.h.DB())
tcc := til.NewContext(common.RollupConstMaxL1UserTx)
tilCfgExtra := til.ConfigExtra{
BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"),
CoordUser: "A",
}
blocksData, err := tcc.GenerateBlocks(SetBlockchain)
if err != nil {
panic(err)
}
err = tcc.FillBlocksExtra(blocksData, &tilCfgExtra)
if err != nil {
panic(err)
}
// poolL2Txs, err := tcc.GeneratePoolL2Txs(til.SetPoolL2MinimumFlow0)
var blocksTc []common.Block
var batchesTc []common.Batch
var tokensTc []common.Token
var accountsTc []common.Account
var exitTreeTc []common.ExitInfo
var allL1TxsTc []common.L1Tx
var allL2TxsTc []common.L2Tx
AddAditionalInformation(blocksData)
for _, block := range blocksData {
// Insert block
err := api.h.AddBlockSCData(&block)
if err != nil {
panic(err)
}
blocksTc = append(blocksTc, block.Block)
tokensTc = append(tokensTc, block.Rollup.AddedTokens...)
allL1TxsTc = append(allL1TxsTc, block.Rollup.L1UserTxs...)
for _, batch := range block.Rollup.Batches {
allL2TxsTc = append(allL2TxsTc, batch.L2Txs...)
accountsTc = append(accountsTc, batch.CreatedAccounts...)
batchesTc = append(batchesTc, batch.Batch)
exitTreeTc = append(exitTreeTc, batch.ExitTree...)
allL1TxsTc = append(allL1TxsTc, batch.L1CoordinatorTxs...)
}
}
// lastBlockNum2 := blocksData[len(blocksData)-1].Block.EthBlockNum
tokensTc = append([]common.Token{ethToken}, tokensTc...)
tokensUSDTc := []historydb.TokenWithUSD{}
for i, tkn := range tokensTc {
token := historydb.TokenWithUSD{
TokenID: tkn.TokenID,
EthBlockNum: tkn.EthBlockNum,
EthAddr: tkn.EthAddr,
Name: tkn.Name,
Symbol: tkn.Symbol,
Decimals: tkn.Decimals,
}
// Set value of 50% of the tokens
if i%2 != 0 {
value := float64(i) * 1.234567
now := time.Now().UTC()
token.USD = &value
token.USDUpdate = &now
err = api.h.UpdateTokenValue(token.Symbol, value)
if err != nil {
panic(err)
}
}
tokensUSDTc = append(tokensUSDTc, token)
}
for i := 0; i < len(accountsTc); i++ {
if _, err := api.s.CreateAccount(accountsTc[i].Idx, &accountsTc[i]); err != nil {
panic(err)
}
}
usrIdxsTc := []string{}
for _, acc := range accountsTc {
for _, token := range tokensTc {
if token.TokenID == acc.TokenID {
usrIdxsTc = append(usrIdxsTc, idxToHez(acc.Idx, token.Symbol))
}
}
}
// Sort txs
sortedTxsTc := []txSortFielder{}
for i := 0; i < len(allL1TxsTc); i++ {
wL1 := wrappedL1(allL1TxsTc[i])
sortedTxsTc = append(sortedTxsTc, &wL1)
}
for i := 0; i < len(allL2TxsTc); i++ {
wL2 := wrappedL2(allL2TxsTc[i])
sortedTxsTc = append(sortedTxsTc, &wL2)
}
sort.Sort(txsSort(sortedTxsTc))
// Coordinators
const nCoordsTc = 10
coordsTc := test.GenCoordinators(nCoordsTc, blocksTc)
err = api.h.AddCoordinators(coordsTc)
if err != nil {
panic(err)
}
coordinatorsTc, _, err := api.h.GetCoordinatorsAPI(&fromItem, &limit, historydb.OrderAsc)
if err != nil {
panic(err)
}
// Bids
const nBidsTc = 20
bidsTc := test.GenBids(nBidsTc, blocksTc, coordsTc)
err = api.h.AddBids(bidsTc)
if err != nil {
panic(err)
}
testBidsTc := genTestBids(blocksTc, coordinatorsTc, bidsTc)
usrExitsTc, allExitsTc := genTestExits(exitTreeTc, tokensUSDTc, accountsTc, usrIdxsTc)
_, allTxsTc := genTestTxs(sortedTxsTc, usrIdxsTc, accountsTc, tokensUSDTc, blocksTc)
fmt.Println(allTxsTc)
// allTxsTc == allTxs
// testBatchesTc, fullBatchesTc := genTestBatches(blocksTc, batchesTc, allTxsTc)
// Generate test data, as expected to be received/sended from/to the API
testCoords := genTestCoordinators(commonCoords)
testBids := genTestBids(commonBlocks, testCoords, commonBids)
testExits := genTestExits(commonExitTree, testTokens, commonAccounts)
testTxs := genTestTxs(commonL1Txs, commonL2Txs, commonAccounts, testTokens, commonBlocks)
testBatches, testFullBatches := genTestBatches(commonBlocks, commonBatches, testTxs)
poolTxsToSend, poolTxsToReceive := genTestPoolTxs(commonPoolTxs, testTokens, commonAccounts)
tc = testCommon{ tc = testCommon{
blocks: blocksTc,
tokens: tokensUSDTc,
blocks: commonBlocks,
tokens: testTokens,
batches: testBatches, batches: testBatches,
fullBatches: fullBatches,
coordinators: coordinatorsTc,
accounts: genTestAccounts(accountsTc, tokensUSDTc),
usrAddr: ethAddrToHez(usrAddr),
usrBjj: bjjToString(usrBjj),
accs: accountsTc,
usrTxs: usrTxs,
allTxs: allTxs,
exits: allExitsTc,
usrExits: usrExitsTc,
poolTxsToSend: poolTxsToSend,
poolTxsToReceive: poolTxsToReceive,
auths: genTestAuths(test.GenAuths(5)),
router: router,
bids: testBidsTc,
slots: api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
auctionVars: auctionVars,
rollupVars: rollupVars,
wdelayerVars: wdelayerVars,
}
/* tc = testCommon{
blocks: blocks,
tokens: tokensUSD,
batches: testBatches,
fullBatches: fullBatches,
coordinators: coordinators,
accounts: genTestAccounts(accs, tokensUSD),
usrAddr: ethAddrToHez(usrAddr),
usrBjj: bjjToString(usrBjj),
accs: accs,
usrTxs: usrTxs,
allTxs: allTxs,
exits: allExits,
usrExits: usrExits,
fullBatches: testFullBatches,
coordinators: testCoords,
accounts: genTestAccounts(commonAccounts, testTokens),
txs: testTxs,
exits: testExits,
poolTxsToSend: poolTxsToSend, poolTxsToSend: poolTxsToSend,
poolTxsToReceive: poolTxsToReceive, poolTxsToReceive: poolTxsToReceive,
auths: genTestAuths(test.GenAuths(5)), auths: genTestAuths(test.GenAuths(5)),
router: router, router: router,
bids: testBids, bids: testBids,
slots: api.genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
auctionVars: auctionVars,
rollupVars: rollupVars,
wdelayerVars: wdelayerVars,
} */
slots: api.genTestSlots(
20,
commonBlocks[len(commonBlocks)-1].EthBlockNum,
testBids,
auctionVars,
),
auctionVars: auctionVars,
rollupVars: rollupVars,
wdelayerVars: wdelayerVars,
}
// Fake server // Fake server
if os.Getenv("FAKE_SERVER") == "yes" { if os.Getenv("FAKE_SERVER") == "yes" {
@ -682,7 +460,11 @@ func doGoodReqPaginated(
iterPath += strconv.Itoa(int(next)) iterPath += strconv.Itoa(int(next))
} }
// Call API to get this iteration items // Call API to get this iteration items
if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
iterStruct = iterStruct.New()
if err := doGoodReq(
"GET", iterPath+"&order="+order, nil,
iterStruct,
); err != nil {
return err return err
} }
appendIter(iterStruct) appendIter(iterStruct)
@ -758,8 +540,10 @@ func doGoodReq(method, path string, reqBody io.Reader, returnStruct interface{})
// Unmarshal body into return struct // Unmarshal body into return struct
if err := json.Unmarshal(body, returnStruct); err != nil { if err := json.Unmarshal(body, returnStruct); err != nil {
log.Error("invalid json: " + string(body)) log.Error("invalid json: " + string(body))
log.Error(err)
return err return err
} }
// log.Info(string(body))
// Validate response against swagger spec // Validate response against swagger spec
responseValidationInput := &swagger.ResponseValidationInput{ responseValidationInput := &swagger.ResponseValidationInput{
RequestValidationInput: requestValidationInput, RequestValidationInput: requestValidationInput,

+ 2
- 1
api/batch.go

@ -1,6 +1,7 @@
package api package api
import ( import (
"database/sql"
"errors" "errors"
"net/http" "net/http"
@ -109,7 +110,7 @@ func (a *API) getFullBatch(c *gin.Context) {
txs, _, err := a.h.GetHistoryTxs( txs, _, err := a.h.GetHistoryTxs(
nil, nil, nil, nil, batchNum, nil, nil, &maxTxsPerBatch, historydb.OrderAsc, nil, nil, nil, nil, batchNum, nil, nil, &maxTxsPerBatch, historydb.OrderAsc,
) )
if err != nil {
if err != nil && err != sql.ErrNoRows {
retSQLErr(err, c) retSQLErr(err, c)
return return
} }

+ 14
- 12
api/batch_test.go

@ -43,6 +43,8 @@ func (t testBatchesResponse) Len() int {
return len(t.Batches) return len(t.Batches)
} }
func (t testBatchesResponse) New() Pendinger { return &testBatchesResponse{} }
type testFullBatch struct { type testFullBatch struct {
Batch testBatch `json:"batch"` Batch testBatch `json:"batch"`
Txs []testTx `json:"transactions"` Txs []testTx `json:"transactions"`
@ -54,11 +56,11 @@ func genTestBatches(
txs []testTx, txs []testTx,
) ([]testBatch, []testFullBatch) { ) ([]testBatch, []testFullBatch) {
tBatches := []testBatch{} tBatches := []testBatch{}
for _, cBatch := range cBatches {
for i := 0; i < len(cBatches); i++ {
block := common.Block{} block := common.Block{}
found := false found := false
for _, b := range blocks { for _, b := range blocks {
if b.EthBlockNum == cBatch.EthBlockNum {
if b.EthBlockNum == cBatches[i].EthBlockNum {
block = b block = b
found = true found = true
break break
@ -68,22 +70,22 @@ func genTestBatches(
panic("block not found") panic("block not found")
} }
collectedFees := make(map[common.TokenID]string) collectedFees := make(map[common.TokenID]string)
for k, v := range cBatch.CollectedFees {
for k, v := range cBatches[i].CollectedFees {
collectedFees[k] = v.String() collectedFees[k] = v.String()
} }
tBatch := testBatch{ tBatch := testBatch{
BatchNum: cBatch.BatchNum,
EthBlockNum: cBatch.EthBlockNum,
BatchNum: cBatches[i].BatchNum,
EthBlockNum: cBatches[i].EthBlockNum,
EthBlockHash: block.Hash, EthBlockHash: block.Hash,
Timestamp: block.Timestamp, Timestamp: block.Timestamp,
ForgerAddr: cBatch.ForgerAddr,
ForgerAddr: cBatches[i].ForgerAddr,
CollectedFees: collectedFees, CollectedFees: collectedFees,
TotalFeesUSD: cBatch.TotalFeesUSD,
StateRoot: cBatch.StateRoot.String(),
NumAccounts: cBatch.NumAccounts,
ExitRoot: cBatch.ExitRoot.String(),
ForgeL1TxsNum: cBatch.ForgeL1TxsNum,
SlotNum: cBatch.SlotNum,
TotalFeesUSD: cBatches[i].TotalFeesUSD,
StateRoot: cBatches[i].StateRoot.String(),
NumAccounts: cBatches[i].NumAccounts,
ExitRoot: cBatches[i].ExitRoot.String(),
ForgeL1TxsNum: cBatches[i].ForgeL1TxsNum,
SlotNum: cBatches[i].SlotNum,
} }
tBatches = append(tBatches, tBatch) tBatches = append(tBatches, tBatch)
} }

+ 3
- 1
api/bids_test.go

@ -38,6 +38,8 @@ func (t testBidsResponse) Len() int {
return len(t.Bids) return len(t.Bids)
} }
func (t testBidsResponse) New() Pendinger { return &testBidsResponse{} }
func genTestBids(blocks []common.Block, coordinators []historydb.CoordinatorAPI, bids []common.Bid) []testBid { func genTestBids(blocks []common.Block, coordinators []historydb.CoordinatorAPI, bids []common.Bid) []testBid {
tBids := []testBid{} tBids := []testBid{}
for _, bid := range bids { for _, bid := range bids {
@ -113,7 +115,7 @@ func TestGetBids(t *testing.T) {
// Mixed filters // Mixed filters
fetchedBids = []testBid{} fetchedBids = []testBid{}
bidderAddress = tc.bids[1].Bidder bidderAddress = tc.bids[1].Bidder
slotNum = tc.bids[5].SlotNum
slotNum = tc.bids[1].SlotNum
path = fmt.Sprintf("%s?bidderAddr=%s&slotNum=%d&limit=%d&fromItem=", endpoint, bidderAddress.String(), slotNum, limit) path = fmt.Sprintf("%s?bidderAddr=%s&slotNum=%d&limit=%d&fromItem=", endpoint, bidderAddress.String(), slotNum, limit)
err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)

+ 42
- 30
api/coordinator_test.go

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure" "github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -22,10 +23,24 @@ func (t testCoordinatorsResponse) GetPending() (pendingItems, lastItemID uint64)
func (t *testCoordinatorsResponse) Len() int { return len(t.Coordinators) } func (t *testCoordinatorsResponse) Len() int { return len(t.Coordinators) }
func (t testCoordinatorsResponse) New() Pendinger { return &testCoordinatorsResponse{} }
func genTestCoordinators(coordinators []common.Coordinator) []historydb.CoordinatorAPI {
testCoords := []historydb.CoordinatorAPI{}
for i := 0; i < len(coordinators); i++ {
testCoords = append(testCoords, historydb.CoordinatorAPI{
Bidder: coordinators[i].Bidder,
Forger: coordinators[i].Forger,
EthBlockNum: coordinators[i].EthBlockNum,
URL: coordinators[i].URL,
})
}
return testCoords
}
func TestGetCoordinators(t *testing.T) { func TestGetCoordinators(t *testing.T) {
endpoint := apiURL + "coordinators" endpoint := apiURL + "coordinators"
fetchedCoordinators := []historydb.CoordinatorAPI{} fetchedCoordinators := []historydb.CoordinatorAPI{}
appendIter := func(intr interface{}) { appendIter := func(intr interface{}) {
for i := 0; i < len(intr.(*testCoordinatorsResponse).Coordinators); i++ { for i := 0; i < len(intr.(*testCoordinatorsResponse).Coordinators); i++ {
tmp, err := copystructure.Copy(intr.(*testCoordinatorsResponse).Coordinators[i]) tmp, err := copystructure.Copy(intr.(*testCoordinatorsResponse).Coordinators[i])
@ -36,45 +51,30 @@ func TestGetCoordinators(t *testing.T) {
} }
} }
// All
limit := 5 limit := 5
path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
err := doGoodReqPaginated(path, historydb.OrderAsc, &testCoordinatorsResponse{}, appendIter) err := doGoodReqPaginated(path, historydb.OrderAsc, &testCoordinatorsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
for i := 0; i < len(fetchedCoordinators); i++ {
assert.Equal(t, tc.coordinators[i].ItemID, fetchedCoordinators[i].ItemID)
assert.Equal(t, tc.coordinators[i].Bidder, fetchedCoordinators[i].Bidder)
assert.Equal(t, tc.coordinators[i].Forger, fetchedCoordinators[i].Forger)
assert.Equal(t, tc.coordinators[i].EthBlockNum, fetchedCoordinators[i].EthBlockNum)
assert.Equal(t, tc.coordinators[i].URL, fetchedCoordinators[i].URL)
}
assertCoordinators(t, tc.coordinators, fetchedCoordinators)
// Reverse Order
reversedCoordinators := []historydb.CoordinatorAPI{}
appendIter = func(intr interface{}) {
for i := 0; i < len(intr.(*testCoordinatorsResponse).Coordinators); i++ {
tmp, err := copystructure.Copy(intr.(*testCoordinatorsResponse).Coordinators[i])
if err != nil {
panic(err)
}
reversedCoordinators = append(reversedCoordinators, tmp.(historydb.CoordinatorAPI))
}
}
// All in reverse order
fetchedCoordinators = []historydb.CoordinatorAPI{}
err = doGoodReqPaginated(path, historydb.OrderDesc, &testCoordinatorsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderDesc, &testCoordinatorsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
for i := 0; i < len(fetchedCoordinators); i++ {
assert.Equal(t, reversedCoordinators[i].ItemID, fetchedCoordinators[len(fetchedCoordinators)-1-i].ItemID)
assert.Equal(t, reversedCoordinators[i].Bidder, fetchedCoordinators[len(fetchedCoordinators)-1-i].Bidder)
assert.Equal(t, reversedCoordinators[i].Forger, fetchedCoordinators[len(fetchedCoordinators)-1-i].Forger)
assert.Equal(t, reversedCoordinators[i].EthBlockNum, fetchedCoordinators[len(fetchedCoordinators)-1-i].EthBlockNum)
assert.Equal(t, reversedCoordinators[i].URL, fetchedCoordinators[len(fetchedCoordinators)-1-i].URL)
reversedCoordinators := []historydb.CoordinatorAPI{}
for i := 0; i < len(tc.coordinators); i++ {
reversedCoordinators = append(reversedCoordinators, tc.coordinators[len(tc.coordinators)-1-i])
} }
assertCoordinators(t, reversedCoordinators, fetchedCoordinators)
// Test GetCoordinator // Test GetCoordinator
path = fmt.Sprintf("%s/%s", endpoint, fetchedCoordinators[2].Forger.String())
coordinator := historydb.CoordinatorAPI{}
assert.NoError(t, doGoodReq("GET", path, nil, &coordinator))
assert.Equal(t, fetchedCoordinators[2], coordinator)
for _, coord := range tc.coordinators {
path = fmt.Sprintf("%s/%s", endpoint, coord.Forger.String())
fetchedCoordinator := historydb.CoordinatorAPI{}
assert.NoError(t, doGoodReq("GET", path, nil, &fetchedCoordinator))
assertCoordinator(t, coord, fetchedCoordinator)
}
// 400 // 400
path = fmt.Sprintf("%s/0x001", endpoint) path = fmt.Sprintf("%s/0x001", endpoint)
@ -85,3 +85,15 @@ func TestGetCoordinators(t *testing.T) {
err = doBadReq("GET", path, nil, 404) err = doBadReq("GET", path, nil, 404)
assert.NoError(t, err) assert.NoError(t, err)
} }
func assertCoordinator(t *testing.T, expected, actual historydb.CoordinatorAPI) {
actual.ItemID = 0
assert.Equal(t, expected, actual)
}
func assertCoordinators(t *testing.T, expected, actual []historydb.CoordinatorAPI) {
assert.Equal(t, len(expected), len(actual))
for i := 0; i < len(expected); i++ {
assertCoordinator(t, expected[i], actual[i])
}
}

+ 31
- 22
api/exits_test.go

@ -45,6 +45,8 @@ func (t testExitsResponse) GetPending() (pendingItems, lastItemID uint64) {
return pendingItems, lastItemID return pendingItems, lastItemID
} }
func (t testExitsResponse) New() Pendinger { return &testExitsResponse{} }
func (t *testExitsResponse) Len() int { func (t *testExitsResponse) Len() int {
return len(t.Exits) return len(t.Exits)
} }
@ -53,9 +55,8 @@ func genTestExits(
commonExits []common.ExitInfo, commonExits []common.ExitInfo,
tokens []historydb.TokenWithUSD, tokens []historydb.TokenWithUSD,
accs []common.Account, accs []common.Account,
usrIdxs []string,
) (usrExits, allExits []testExit) {
allExits = []testExit{}
) []testExit {
allExits := []testExit{}
for _, exit := range commonExits { for _, exit := range commonExits {
token := getTokenByIdx(exit.AccountIdx, tokens, accs) token := getTokenByIdx(exit.AccountIdx, tokens, accs)
siblings := []string{} siblings := []string{}
@ -82,16 +83,7 @@ func genTestExits(
Token: token, Token: token,
}) })
} }
usrExits = []testExit{}
for _, exit := range allExits {
for _, idx := range usrIdxs {
if idx == exit.AccountIdx {
usrExits = append(usrExits, exit)
break
}
}
}
return usrExits, allExits
return allExits
} }
func TestGetExits(t *testing.T) { func TestGetExits(t *testing.T) {
@ -116,21 +108,38 @@ func TestGetExits(t *testing.T) {
// Get by ethAddr // Get by ethAddr
fetchedExits = []testExit{} fetchedExits = []testExit{}
limit = 7 limit = 7
account := tc.accounts[3]
var account testAccount
for _, tx := range tc.txs {
found := false
if tx.Type == common.TxTypeExit {
for i := 0; i < len(tc.accounts); i++ {
if tx.FromIdx != nil && string(tc.accounts[i].Idx) == *tx.FromIdx {
account = tc.accounts[i]
break
}
}
}
if found {
break
}
}
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?hermezEthereumAddress=%s&limit=%d&fromItem=", "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
endpoint, account.EthAddr, limit, endpoint, account.EthAddr, limit,
) )
err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
accountIdx := fetchedExits[0].AccountIdx
var hermezEthExits []testExit
for i := range tc.usrExits {
if tc.usrExits[i].AccountIdx == accountIdx {
hermezEthExits = append(hermezEthExits, tc.usrExits[i])
var accountExits []testExit
for i := range tc.exits {
for _, acc := range tc.accounts {
if string(acc.Idx) == tc.exits[i].AccountIdx {
if acc.EthAddr == account.EthAddr {
accountExits = append(accountExits, tc.exits[i])
}
}
} }
} }
assertExitAPIs(t, hermezEthExits, fetchedExits)
assertExitAPIs(t, accountExits, fetchedExits)
// Get by bjj // Get by bjj
fetchedExits = []testExit{} fetchedExits = []testExit{}
limit = 6 limit = 6
@ -140,7 +149,7 @@ func TestGetExits(t *testing.T) {
) )
err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testExitsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
assertExitAPIs(t, hermezEthExits, fetchedExits)
assertExitAPIs(t, accountExits, fetchedExits)
// Get by tokenID // Get by tokenID
fetchedExits = []testExit{} fetchedExits = []testExit{}
limit = 5 limit = 5
@ -236,7 +245,7 @@ func TestGetExits(t *testing.T) {
// 400 // 400
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?accountIndex=%s&hermezEthereumAddress=%s", "%s?accountIndex=%s&hermezEthereumAddress=%s",
endpoint, idx, tc.usrAddr,
endpoint, idx, account.EthAddr,
) )
err = doBadReq("GET", path, nil, 400) err = doBadReq("GET", path, nil, 400)
assert.NoError(t, err) assert.NoError(t, err)

+ 1
- 1
api/slots.go

@ -17,7 +17,7 @@ type SlotAPI struct {
FirstBlock int64 `json:"firstBlock"` FirstBlock int64 `json:"firstBlock"`
LastBlock int64 `json:"lastBlock"` LastBlock int64 `json:"lastBlock"`
OpenAuction bool `json:"openAuction"` OpenAuction bool `json:"openAuction"`
WinnerBid *historydb.BidAPI `json:"winnerBid"`
WinnerBid *historydb.BidAPI `json:"bestBid"`
TotalItems uint64 `json:"-"` TotalItems uint64 `json:"-"`
FirstItem uint64 `json:"-"` FirstItem uint64 `json:"-"`
LastItem uint64 `json:"-"` LastItem uint64 `json:"-"`

+ 3
- 1
api/slots_test.go

@ -17,7 +17,7 @@ type testSlot struct {
FirstBlock int64 `json:"firstBlock"` FirstBlock int64 `json:"firstBlock"`
LastBlock int64 `json:"lastBlock"` LastBlock int64 `json:"lastBlock"`
OpenAuction bool `json:"openAuction"` OpenAuction bool `json:"openAuction"`
WinnerBid *testBid `json:"winnerBid"`
WinnerBid *testBid `json:"bestBid"`
} }
type testSlotsResponse struct { type testSlotsResponse struct {
@ -35,6 +35,8 @@ func (t testSlotsResponse) Len() int {
return len(t.Slots) return len(t.Slots)
} }
func (t testSlotsResponse) New() Pendinger { return &testSlotsResponse{} }
func (a *API) genTestSlots(nSlots int, lastBlockNum int64, bids []testBid, auctionVars common.AuctionVariables) []testSlot { func (a *API) genTestSlots(nSlots int, lastBlockNum int64, bids []testBid, auctionVars common.AuctionVariables) []testSlot {
tSlots := []testSlot{} tSlots := []testSlot{}
bestBids := make(map[int64]testBid) bestBids := make(map[int64]testBid)

+ 12
- 7
api/state.go

@ -12,10 +12,11 @@ import (
// Network define status of the network // Network define status of the network
type Network struct { type Network struct {
LastBlock int64 `json:"lastBlock"`
LastBatch historydb.BatchAPI `json:"lastBatch"`
CurrentSlot int64 `json:"currentSlot"`
NextForgers []NextForger `json:"nextForgers"`
LastEthBlock int64 `json:"lastEthereumBlock"`
LastSyncBlock int64 `json:"lastSynchedBlock"`
LastBatch historydb.BatchAPI `json:"lastBatch"`
CurrentSlot int64 `json:"currentSlot"`
NextForgers []NextForger `json:"nextForgers"`
} }
// NextForger is a representation of the information of a coordinator and the period will forge // NextForger is a representation of the information of a coordinator and the period will forge
@ -65,8 +66,12 @@ func (a *API) SetAuctionVariables(auctionVariables common.AuctionVariables) {
// Network // Network
// UpdateNetworkInfo update Status.Network information // UpdateNetworkInfo update Status.Network information
func (a *API) UpdateNetworkInfo(lastBlock common.Block, lastBatchNum common.BatchNum, currentSlot int64) error {
a.status.Network.LastBlock = lastBlock.EthBlockNum
func (a *API) UpdateNetworkInfo(
lastEthBlock, lastSyncBlock common.Block,
lastBatchNum common.BatchNum, currentSlot int64,
) error {
a.status.Network.LastSyncBlock = lastSyncBlock.EthBlockNum
a.status.Network.LastEthBlock = lastEthBlock.EthBlockNum
lastBatch, err := a.h.GetBatchAPI(lastBatchNum) lastBatch, err := a.h.GetBatchAPI(lastBatchNum)
if err != nil { if err != nil {
return err return err
@ -74,7 +79,7 @@ func (a *API) UpdateNetworkInfo(lastBlock common.Block, lastBatchNum common.Batc
a.status.Network.LastBatch = *lastBatch a.status.Network.LastBatch = *lastBatch
a.status.Network.CurrentSlot = currentSlot a.status.Network.CurrentSlot = currentSlot
lastClosedSlot := currentSlot + int64(a.status.Auction.ClosedAuctionSlots) lastClosedSlot := currentSlot + int64(a.status.Auction.ClosedAuctionSlots)
nextForgers, err := a.GetNextForgers(lastBlock, currentSlot, lastClosedSlot)
nextForgers, err := a.GetNextForgers(lastSyncBlock, currentSlot, lastClosedSlot)
if err != nil { if err != nil {
return err return err
} }

+ 13
- 11
api/state_test.go

@ -21,10 +21,11 @@ type testStatus struct {
} }
type testNetwork struct { type testNetwork struct {
LastBlock int64 `json:"lastBlock"`
LastBatch testBatch `json:"lastBatch"`
CurrentSlot int64 `json:"currentSlot"`
NextForgers []NextForger `json:"nextForgers"`
LastEthBlock int64 `json:"lastEthereumBlock"`
LastSyncBlock int64 `json:"lastSynchedBlock"`
LastBatch testBatch `json:"lastBatch"`
CurrentSlot int64 `json:"currentSlot"`
NextForgers []NextForger `json:"nextForgers"`
} }
func TestSetRollupVariables(t *testing.T) { func TestSetRollupVariables(t *testing.T) {
@ -80,16 +81,16 @@ func TestNextForgers(t *testing.T) {
func TestUpdateNetworkInfo(t *testing.T) { func TestUpdateNetworkInfo(t *testing.T) {
status := &Network{} status := &Network{}
assert.Equal(t, status.LastBlock, api.status.Network.LastBlock)
assert.Equal(t, status.LastSyncBlock, api.status.Network.LastSyncBlock)
assert.Equal(t, status.LastBatch.BatchNum, api.status.Network.LastBatch.BatchNum) assert.Equal(t, status.LastBatch.BatchNum, api.status.Network.LastBatch.BatchNum)
assert.Equal(t, status.CurrentSlot, api.status.Network.CurrentSlot) assert.Equal(t, status.CurrentSlot, api.status.Network.CurrentSlot)
assert.Equal(t, status.NextForgers, api.status.Network.NextForgers) assert.Equal(t, status.NextForgers, api.status.Network.NextForgers)
lastBlock := tc.blocks[3] lastBlock := tc.blocks[3]
lastBatchNum := common.BatchNum(3) lastBatchNum := common.BatchNum(3)
currentSlotNum := int64(1) currentSlotNum := int64(1)
err := api.UpdateNetworkInfo(lastBlock, lastBatchNum, currentSlotNum)
err := api.UpdateNetworkInfo(lastBlock, lastBlock, lastBatchNum, currentSlotNum)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, lastBlock.EthBlockNum, api.status.Network.LastBlock)
assert.Equal(t, lastBlock.EthBlockNum, api.status.Network.LastSyncBlock)
assert.Equal(t, lastBatchNum, api.status.Network.LastBatch.BatchNum) assert.Equal(t, lastBatchNum, api.status.Network.LastBatch.BatchNum)
assert.Equal(t, currentSlotNum, api.status.Network.CurrentSlot) assert.Equal(t, currentSlotNum, api.status.Network.CurrentSlot)
assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(api.status.Network.NextForgers)) assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(api.status.Network.NextForgers))
@ -101,7 +102,7 @@ func TestUpdateMetrics(t *testing.T) {
lastBlock := tc.blocks[3] lastBlock := tc.blocks[3]
lastBatchNum := common.BatchNum(3) lastBatchNum := common.BatchNum(3)
currentSlotNum := int64(1) currentSlotNum := int64(1)
err := api.UpdateNetworkInfo(lastBlock, lastBatchNum, currentSlotNum)
err := api.UpdateNetworkInfo(lastBlock, lastBlock, lastBatchNum, currentSlotNum)
assert.NoError(t, err) assert.NoError(t, err)
err = api.UpdateMetrics() err = api.UpdateMetrics()
@ -124,14 +125,14 @@ func TestUpdateRecommendedFee(t *testing.T) {
api.status.RecommendedFee.ExistingAccount*createAccountInternalExtraFeePercentage) api.status.RecommendedFee.ExistingAccount*createAccountInternalExtraFeePercentage)
} }
func TestGetStatus(t *testing.T) {
func TestGetState(t *testing.T) {
lastBlock := tc.blocks[3] lastBlock := tc.blocks[3]
lastBatchNum := common.BatchNum(3) lastBatchNum := common.BatchNum(3)
currentSlotNum := int64(1) currentSlotNum := int64(1)
api.SetRollupVariables(tc.rollupVars) api.SetRollupVariables(tc.rollupVars)
api.SetWDelayerVariables(tc.wdelayerVars) api.SetWDelayerVariables(tc.wdelayerVars)
api.SetAuctionVariables(tc.auctionVars) api.SetAuctionVariables(tc.auctionVars)
err := api.UpdateNetworkInfo(lastBlock, lastBatchNum, currentSlotNum)
err := api.UpdateNetworkInfo(lastBlock, lastBlock, lastBatchNum, currentSlotNum)
assert.NoError(t, err) assert.NoError(t, err)
err = api.UpdateMetrics() err = api.UpdateMetrics()
assert.NoError(t, err) assert.NoError(t, err)
@ -145,7 +146,8 @@ func TestGetStatus(t *testing.T) {
assert.Equal(t, tc.rollupVars, status.Rollup) assert.Equal(t, tc.rollupVars, status.Rollup)
assert.Equal(t, tc.auctionVars, status.Auction) assert.Equal(t, tc.auctionVars, status.Auction)
assert.Equal(t, tc.wdelayerVars, status.WithdrawalDelayer) assert.Equal(t, tc.wdelayerVars, status.WithdrawalDelayer)
assert.Equal(t, lastBlock.EthBlockNum, status.Network.LastBlock)
assert.Equal(t, lastBlock.EthBlockNum, status.Network.LastEthBlock)
assert.Equal(t, lastBlock.EthBlockNum, status.Network.LastSyncBlock)
assert.Equal(t, lastBatchNum, status.Network.LastBatch.BatchNum) assert.Equal(t, lastBatchNum, status.Network.LastBatch.BatchNum)
assert.Equal(t, currentSlotNum, status.Network.CurrentSlot) assert.Equal(t, currentSlotNum, status.Network.CurrentSlot)
assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(status.Network.NextForgers)) assert.Equal(t, int(api.status.Auction.ClosedAuctionSlots)+1, len(status.Network.NextForgers))

+ 13
- 6
api/swagger.yml

@ -1857,6 +1857,7 @@ components:
description: List of forged transactions in the batch description: List of forged transactions in the batch
items: items:
$ref: '#/components/schemas/HistoryTransaction' $ref: '#/components/schemas/HistoryTransaction'
nullable: true
additionalProperties: false additionalProperties: false
required: required:
- batch - batch
@ -2205,9 +2206,9 @@ components:
openAuction: openAuction:
type: boolean type: boolean
description: Whether the auction of the slot is open or not. description: Whether the auction of the slot is open or not.
winnerBid:
bestBid:
type: object type: object
description: The winning bid of the auction. If openAuction == true, is the current winner. If the auction is closed because it has already been finalized, the bid is the final winner. If the winnerBid is null, it is because no coordinator has bid for that slot.
description: The best bid of the auction. If the bestBid is null, it is because no coordinator has bid for that slot.
nullable: true nullable: true
properties: properties:
itemId: itemId:
@ -2245,7 +2246,7 @@ components:
- firstBlock - firstBlock
- lastBlock - lastBlock
- openAuction - openAuction
- winnerBid
- bestBid
Slots: Slots:
type: object type: object
properties: properties:
@ -2328,10 +2329,15 @@ components:
type: object type: object
description: Gobal statistics of the network description: Gobal statistics of the network
properties: properties:
lastBlock:
lastEthereumBlock:
allOf:
- $ref: '#/components/schemas/EthBlockNum'
- description: Current Etherum block. Note that this is the actual last block of Ethereum, not the last synchronized block by the node.
- example: 3457437
lastSynchedBlock:
allOf: allOf:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
- description: Last synchronized Etherum block.
- description: Last synchronized Etherum block. Compare with lastEthereumBlock to check the synchronization status of the node.
- example: 3457437 - example: 3457437
lastBatch: lastBatch:
$ref: '#/components/schemas/Batch' $ref: '#/components/schemas/Batch'
@ -2344,7 +2350,8 @@ components:
$ref: '#/components/schemas/NextForgers' $ref: '#/components/schemas/NextForgers'
additionalProperties: false additionalProperties: false
required: required:
- lastBlock
- lastEthereumBlock
- lastSynchedBlock
- lastBatch - lastBatch
- currentSlot - currentSlot
- nextForgers - nextForgers

+ 2
- 0
api/token_test.go

@ -26,6 +26,8 @@ func (t *testTokensResponse) Len() int {
return len(t.Tokens) return len(t.Tokens)
} }
func (t testTokensResponse) New() Pendinger { return &testTokensResponse{} }
func TestGetToken(t *testing.T) { func TestGetToken(t *testing.T) {
// Get all txs by their ID // Get all txs by their ID
endpoint := apiURL + "tokens/" endpoint := apiURL + "tokens/"

+ 232
- 258
api/txshistory_test.go

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"sort"
"testing" "testing"
"time" "time"
@ -51,43 +52,14 @@ type testTx struct {
Token historydb.TokenWithUSD `json:"token"` Token historydb.TokenWithUSD `json:"token"`
} }
type testTxsResponse struct {
Txs []testTx `json:"transactions"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testTxsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Txs[len(t.Txs)-1].ItemID
return pendingItems, lastItemID
}
func (t testTxsResponse) Len() int {
return len(t.Txs)
}
// TxSortFields represents the fields needed to sort L1 and L2 transactions
type txSortFields struct {
BatchNum *common.BatchNum
Position int
}
// TxSortFielder is a interface that allows sorting L1 and L2 transactions in a combined way
type txSortFielder interface {
SortFields() txSortFields
L1() *common.L1Tx
L2() *common.L2Tx
}
// TxsSort array of TxSortFielder
type txsSort []txSortFielder
type txsSort []testTx
func (t txsSort) Len() int { return len(t) } func (t txsSort) Len() int { return len(t) }
func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t txsSort) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t txsSort) Less(i, j int) bool { func (t txsSort) Less(i, j int) bool {
// i not forged yet // i not forged yet
isf := t[i].SortFields()
jsf := t[j].SortFields()
isf := t[i]
jsf := t[j]
if isf.BatchNum == nil { if isf.BatchNum == nil {
if jsf.BatchNum != nil { // j is already forged if jsf.BatchNum != nil { // j is already forged
return false return false
@ -108,189 +80,166 @@ func (t txsSort) Less(i, j int) bool {
return *isf.BatchNum < *jsf.BatchNum return *isf.BatchNum < *jsf.BatchNum
} }
type wrappedL1 common.L1Tx
// SortFields implements TxSortFielder
func (tx *wrappedL1) SortFields() txSortFields {
return txSortFields{
BatchNum: tx.BatchNum,
Position: tx.Position,
}
type testTxsResponse struct {
Txs []testTx `json:"transactions"`
PendingItems uint64 `json:"pendingItems"`
} }
// L1 implements TxSortFielder
func (tx *wrappedL1) L1() *common.L1Tx {
l1tx := common.L1Tx(*tx)
return &l1tx
func (t testTxsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Txs[len(t.Txs)-1].ItemID
return pendingItems, lastItemID
} }
// L2 implements TxSortFielder
func (tx *wrappedL1) L2() *common.L2Tx { return nil }
type wrappedL2 common.L2Tx
// SortFields implements TxSortFielder
func (tx *wrappedL2) SortFields() txSortFields {
return txSortFields{
BatchNum: &tx.BatchNum,
Position: tx.Position,
}
func (t testTxsResponse) Len() int {
return len(t.Txs)
} }
// L1 implements TxSortFielder
func (tx *wrappedL2) L1() *common.L1Tx { return nil }
// L2 implements TxSortFielder
func (tx *wrappedL2) L2() *common.L2Tx {
l2tx := common.L2Tx(*tx)
return &l2tx
}
func (t testTxsResponse) New() Pendinger { return &testTxsResponse{} }
func genTestTxs( func genTestTxs(
genericTxs []txSortFielder,
usrIdxs []string,
l1s []common.L1Tx,
l2s []common.L2Tx,
accs []common.Account, accs []common.Account,
tokens []historydb.TokenWithUSD, tokens []historydb.TokenWithUSD,
blocks []common.Block, blocks []common.Block,
) (usrTxs []testTx, allTxs []testTx) {
usrTxs = []testTx{}
allTxs = []testTx{}
isUsrTx := func(tx testTx) bool {
for _, idx := range usrIdxs {
if tx.FromIdx != nil && *tx.FromIdx == idx {
return true
}
if tx.ToIdx == idx {
return true
}
) []testTx {
txs := []testTx{}
// common.L1Tx ==> testTx
for _, l1 := range l1s {
token := getTokenByID(l1.TokenID, tokens)
// l1.FromEthAddr and l1.FromBJJ can't be nil
fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
tx := testTx{
IsL1: "L1",
TxID: l1.TxID,
Type: l1.Type,
Position: l1.Position,
FromEthAddr: &fromEthAddr,
FromBJJ: &fromBJJ,
ToIdx: idxToHez(l1.ToIdx, token.Symbol),
Amount: l1.Amount.String(),
BatchNum: l1.BatchNum,
Timestamp: getTimestamp(l1.EthBlockNum, blocks),
L1Info: &testL1Info{
ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
UserOrigin: l1.UserOrigin,
LoadAmount: l1.LoadAmount.String(),
EthBlockNum: l1.EthBlockNum,
},
Token: token,
} }
return false
}
for _, genericTx := range genericTxs {
l1 := genericTx.L1()
l2 := genericTx.L2()
if l1 != nil { // L1Tx to testTx
token := getTokenByID(l1.TokenID, tokens)
// l1.FromEthAddr and l1.FromBJJ can't be nil
fromEthAddr := string(apitypes.NewHezEthAddr(l1.FromEthAddr))
fromBJJ := string(apitypes.NewHezBJJ(l1.FromBJJ))
tx := testTx{
IsL1: "L1",
TxID: l1.TxID,
Type: l1.Type,
Position: l1.Position,
FromEthAddr: &fromEthAddr,
FromBJJ: &fromBJJ,
ToIdx: idxToHez(l1.ToIdx, token.Symbol),
Amount: l1.Amount.String(),
BatchNum: l1.BatchNum,
Timestamp: getTimestamp(l1.EthBlockNum, blocks),
L1Info: &testL1Info{
ToForgeL1TxsNum: l1.ToForgeL1TxsNum,
UserOrigin: l1.UserOrigin,
LoadAmount: l1.LoadAmount.String(),
EthBlockNum: l1.EthBlockNum,
},
Token: token,
}
// If FromIdx is not nil
if l1.FromIdx != 0 {
idxStr := idxToHez(l1.FromIdx, token.Symbol)
tx.FromIdx = &idxStr
}
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l1.ToIdx >= common.UserThreshold {
// find account
for _, acc := range accs {
if l1.ToIdx == acc.Idx {
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.ToEthAddr = &toEthAddr
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.ToBJJ = &toBJJ
break
}
// set BatchNum for user txs
if tx.L1Info.ToForgeL1TxsNum != nil {
// WARNING: this is an asumption, and the test input data can brake it easily
bn := common.BatchNum(*tx.L1Info.ToForgeL1TxsNum + 2)
tx.BatchNum = &bn
}
// If FromIdx is not nil
idxStr := idxToHez(l1.FromIdx, token.Symbol)
tx.FromIdx = &idxStr
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l1.ToIdx >= common.UserThreshold {
// find account
for _, acc := range accs {
if l1.ToIdx == acc.Idx {
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.ToEthAddr = &toEthAddr
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.ToBJJ = &toBJJ
break
} }
} }
// If the token has USD value setted
if token.USD != nil {
af := new(big.Float).SetInt(l1.Amount)
amountFloat, _ := af.Float64()
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
}
// If the token has USD value setted
if token.USD != nil {
af := new(big.Float).SetInt(l1.Amount)
amountFloat, _ := af.Float64()
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
if usd != 0 {
tx.HistoricUSD = &usd tx.HistoricUSD = &usd
laf := new(big.Float).SetInt(l1.LoadAmount)
loadAmountFloat, _ := laf.Float64()
loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
tx.L1Info.HistoricLoadAmountUSD = &loadUSD
}
allTxs = append(allTxs, tx)
if isUsrTx(tx) {
usrTxs = append(usrTxs, tx)
} }
} else { // L2Tx to testTx
token := getTokenByIdx(l2.FromIdx, tokens, accs)
// l1.FromIdx can't be nil
fromIdx := idxToHez(l2.FromIdx, token.Symbol)
tx := testTx{
IsL1: "L2",
TxID: l2.TxID,
Type: l2.Type,
Position: l2.Position,
ToIdx: idxToHez(l2.ToIdx, token.Symbol),
FromIdx: &fromIdx,
Amount: l2.Amount.String(),
BatchNum: &l2.BatchNum,
Timestamp: getTimestamp(l2.EthBlockNum, blocks),
L2Info: &testL2Info{
Nonce: l2.Nonce,
Fee: l2.Fee,
},
Token: token,
laf := new(big.Float).SetInt(l1.LoadAmount)
loadAmountFloat, _ := laf.Float64()
loadUSD := *token.USD * loadAmountFloat / math.Pow(10, float64(token.Decimals))
if loadAmountFloat != 0 {
tx.L1Info.HistoricLoadAmountUSD = &loadUSD
} }
// If FromIdx is not nil
if l2.FromIdx != 0 {
idxStr := idxToHez(l2.FromIdx, token.Symbol)
tx.FromIdx = &idxStr
}
txs = append(txs, tx)
}
// common.L2Tx ==> testTx
for i := 0; i < len(l2s); i++ {
token := getTokenByIdx(l2s[i].FromIdx, tokens, accs)
// l1.FromIdx can't be nil
fromIdx := idxToHez(l2s[i].FromIdx, token.Symbol)
tx := testTx{
IsL1: "L2",
TxID: l2s[i].TxID,
Type: l2s[i].Type,
Position: l2s[i].Position,
ToIdx: idxToHez(l2s[i].ToIdx, token.Symbol),
FromIdx: &fromIdx,
Amount: l2s[i].Amount.String(),
BatchNum: &l2s[i].BatchNum,
Timestamp: getTimestamp(l2s[i].EthBlockNum, blocks),
L2Info: &testL2Info{
Nonce: l2s[i].Nonce,
Fee: l2s[i].Fee,
},
Token: token,
}
// If FromIdx is not nil
if l2s[i].FromIdx != 0 {
idxStr := idxToHez(l2s[i].FromIdx, token.Symbol)
tx.FromIdx = &idxStr
}
// Set FromEthAddr and FromBJJ (FromIdx it's always >255)
for _, acc := range accs {
if l2s[i].FromIdx == acc.Idx {
fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.FromEthAddr = &fromEthAddr
fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.FromBJJ = &fromBJJ
break
} }
// Set FromEthAddr and FromBJJ (FromIdx it's always >255)
}
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l2s[i].ToIdx >= common.UserThreshold {
// find account
for _, acc := range accs { for _, acc := range accs {
if l2.ToIdx == acc.Idx {
fromEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.FromEthAddr = &fromEthAddr
fromBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.FromBJJ = &fromBJJ
if l2s[i].ToIdx == acc.Idx {
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.ToEthAddr = &toEthAddr
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.ToBJJ = &toBJJ
break break
} }
} }
// If tx has a normal ToIdx (>255), set FromEthAddr and FromBJJ
if l2.ToIdx >= common.UserThreshold {
// find account
for _, acc := range accs {
if l2.ToIdx == acc.Idx {
toEthAddr := string(apitypes.NewHezEthAddr(acc.EthAddr))
tx.ToEthAddr = &toEthAddr
toBJJ := string(apitypes.NewHezBJJ(acc.PublicKey))
tx.ToBJJ = &toBJJ
break
}
}
}
// If the token has USD value setted
if token.USD != nil {
af := new(big.Float).SetInt(l2.Amount)
amountFloat, _ := af.Float64()
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
tx.HistoricUSD = &usd
feeUSD := usd * l2.Fee.Percentage()
}
// If the token has USD value setted
if token.USD != nil {
af := new(big.Float).SetInt(l2s[i].Amount)
amountFloat, _ := af.Float64()
usd := *token.USD * amountFloat / math.Pow(10, float64(token.Decimals))
if usd != 0 {
tx.HistoricUSD = &usd tx.HistoricUSD = &usd
tx.L2Info.HistoricFeeUSD = &feeUSD
}
allTxs = append(allTxs, tx)
if isUsrTx(tx) {
usrTxs = append(usrTxs, tx)
feeUSD := usd * l2s[i].Fee.Percentage()
if feeUSD != 0 {
tx.L2Info.HistoricFeeUSD = &feeUSD
}
} }
} }
txs = append(txs, tx)
} }
return usrTxs, allTxs
// Sort txs
sortedTxs := txsSort(txs)
sort.Sort(sortedTxs)
return []testTx(sortedTxs)
} }
func TestGetHistoryTxs(t *testing.T) { func TestGetHistoryTxs(t *testing.T) {
@ -310,32 +259,44 @@ func TestGetHistoryTxs(t *testing.T) {
path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit) path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err := doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
assertTxs(t, tc.allTxs, fetchedTxs)
// Uncomment once tx generation for tests is fixed
// // Get by ethAddr
// fetchedTxs = []testTx{}
// limit = 7
// path = fmt.Sprintf(
// "%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
// endpoint, tc.usrAddr, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// assertTxs(t, tc.usrTxs, fetchedTxs)
// // Get by bjj
// fetchedTxs = []testTx{}
// limit = 6
// path = fmt.Sprintf(
// "%s?BJJ=%s&limit=%d&fromItem=",
// endpoint, tc.usrBjj, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// assertTxs(t, tc.usrTxs, fetchedTxs)
assertTxs(t, tc.txs, fetchedTxs)
// Get by ethAddr
account := tc.accounts[2]
fetchedTxs = []testTx{}
limit = 7
path = fmt.Sprintf(
"%s?hermezEthereumAddress=%s&limit=%d&fromItem=",
endpoint, account.EthAddr, limit,
)
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err)
accountTxs := []testTx{}
for i := 0; i < len(tc.txs); i++ {
tx := tc.txs[i]
if (tx.FromIdx != nil && *tx.FromIdx == string(account.Idx)) ||
tx.ToIdx == string(account.Idx) ||
(tx.FromEthAddr != nil && *tx.FromEthAddr == string(account.EthAddr)) ||
(tx.ToEthAddr != nil && *tx.ToEthAddr == string(account.EthAddr)) ||
(tx.FromBJJ != nil && *tx.FromBJJ == string(account.PublicKey)) ||
(tx.ToBJJ != nil && *tx.ToBJJ == string(account.PublicKey)) {
accountTxs = append(accountTxs, tx)
}
}
assertTxs(t, accountTxs, fetchedTxs)
// Get by bjj
fetchedTxs = []testTx{}
limit = 6
path = fmt.Sprintf(
"%s?BJJ=%s&limit=%d&fromItem=",
endpoint, account.PublicKey, limit,
)
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err)
assertTxs(t, accountTxs, fetchedTxs)
// Get by tokenID // Get by tokenID
fetchedTxs = []testTx{} fetchedTxs = []testTx{}
limit = 5 limit = 5
tokenID := tc.allTxs[0].Token.TokenID
tokenID := tc.txs[0].Token.TokenID
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?tokenId=%d&limit=%d&fromItem=", "%s?tokenId=%d&limit=%d&fromItem=",
endpoint, tokenID, limit, endpoint, tokenID, limit,
@ -343,34 +304,46 @@ func TestGetHistoryTxs(t *testing.T) {
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
tokenIDTxs := []testTx{} tokenIDTxs := []testTx{}
for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].Token.TokenID == tokenID {
tokenIDTxs = append(tokenIDTxs, tc.allTxs[i])
for i := 0; i < len(tc.txs); i++ {
if tc.txs[i].Token.TokenID == tokenID {
tokenIDTxs = append(tokenIDTxs, tc.txs[i])
} }
} }
assertTxs(t, tokenIDTxs, fetchedTxs) assertTxs(t, tokenIDTxs, fetchedTxs)
// // idx
// fetchedTxs = []testTx{}
// limit = 4
idx := tc.allTxs[0].ToIdx
// path = fmt.Sprintf(
// "%s?accountIndex=%s&limit=%d&fromItem=",
// endpoint, idx, limit,
// )
// err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
// assert.NoError(t, err)
// idxTxs := []testTx{}
// for i := 0; i < len(tc.allTxs); i++ {
// if (tc.allTxs[i].FromIdx != nil && (*tc.allTxs[i].FromIdx)[6:] == idx[6:]) ||
// tc.allTxs[i].ToIdx[6:] == idx[6:] {
// idxTxs = append(idxTxs, tc.allTxs[i])
// }
// }
// assertHistoryTxAPIs(t, idxTxs, fetchedTxs)
// idx
fetchedTxs = []testTx{}
limit = 4
idxStr := tc.txs[0].ToIdx
idx, err := stringToIdx(idxStr, "")
assert.NoError(t, err)
path = fmt.Sprintf(
"%s?accountIndex=%s&limit=%d&fromItem=",
endpoint, idxStr, limit,
)
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err)
idxTxs := []testTx{}
for i := 0; i < len(tc.txs); i++ {
var fromIdx *common.Idx
if tc.txs[i].FromIdx != nil {
fromIdx, err = stringToIdx(*tc.txs[i].FromIdx, "")
assert.NoError(t, err)
if *fromIdx == *idx {
idxTxs = append(idxTxs, tc.txs[i])
continue
}
}
toIdx, err := stringToIdx((tc.txs[i].ToIdx), "")
assert.NoError(t, err)
if *toIdx == *idx {
idxTxs = append(idxTxs, tc.txs[i])
}
}
assertTxs(t, idxTxs, fetchedTxs)
// batchNum // batchNum
fetchedTxs = []testTx{} fetchedTxs = []testTx{}
limit = 3 limit = 3
batchNum := tc.allTxs[0].BatchNum
batchNum := tc.txs[0].BatchNum
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?batchNum=%d&limit=%d&fromItem=", "%s?batchNum=%d&limit=%d&fromItem=",
endpoint, *batchNum, limit, endpoint, *batchNum, limit,
@ -378,26 +351,24 @@ func TestGetHistoryTxs(t *testing.T) {
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
batchNumTxs := []testTx{} batchNumTxs := []testTx{}
for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].BatchNum != nil &&
*tc.allTxs[i].BatchNum == *batchNum {
batchNumTxs = append(batchNumTxs, tc.allTxs[i])
for i := 0; i < len(tc.txs); i++ {
if tc.txs[i].BatchNum != nil &&
*tc.txs[i].BatchNum == *batchNum {
batchNumTxs = append(batchNumTxs, tc.txs[i])
} }
} }
assertTxs(t, batchNumTxs, fetchedTxs) assertTxs(t, batchNumTxs, fetchedTxs)
// type // type
txTypes := []common.TxType{ txTypes := []common.TxType{
// Uncomment once test gen is fixed // Uncomment once test gen is fixed
// common.TxTypeExit,
// common.TxTypeTransfer,
// common.TxTypeDeposit,
common.TxTypeExit,
common.TxTypeTransfer,
common.TxTypeDeposit,
common.TxTypeCreateAccountDeposit, common.TxTypeCreateAccountDeposit,
// common.TxTypeCreateAccountDepositTransfer,
// common.TxTypeDepositTransfer,
common.TxTypeCreateAccountDepositTransfer,
common.TxTypeDepositTransfer,
common.TxTypeForceTransfer, common.TxTypeForceTransfer,
// common.TxTypeForceExit,
// common.TxTypeTransferToEthAddr,
// common.TxTypeTransferToBJJ,
common.TxTypeForceExit,
} }
for _, txType := range txTypes { for _, txType := range txTypes {
fetchedTxs = []testTx{} fetchedTxs = []testTx{}
@ -409,9 +380,9 @@ func TestGetHistoryTxs(t *testing.T) {
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
txTypeTxs := []testTx{} txTypeTxs := []testTx{}
for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].Type == txType {
txTypeTxs = append(txTypeTxs, tc.allTxs[i])
for i := 0; i < len(tc.txs); i++ {
if tc.txs[i].Type == txType {
txTypeTxs = append(txTypeTxs, tc.txs[i])
} }
} }
assertTxs(t, txTypeTxs, fetchedTxs) assertTxs(t, txTypeTxs, fetchedTxs)
@ -426,10 +397,10 @@ func TestGetHistoryTxs(t *testing.T) {
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
mixedTxs := []testTx{} mixedTxs := []testTx{}
for i := 0; i < len(tc.allTxs); i++ {
if tc.allTxs[i].BatchNum != nil {
if *tc.allTxs[i].BatchNum == *batchNum && tc.allTxs[i].Token.TokenID == tokenID {
mixedTxs = append(mixedTxs, tc.allTxs[i])
for i := 0; i < len(tc.txs); i++ {
if tc.txs[i].BatchNum != nil {
if *tc.txs[i].BatchNum == *batchNum && tc.txs[i].Token.TokenID == tokenID {
mixedTxs = append(mixedTxs, tc.txs[i])
} }
} }
} }
@ -441,14 +412,14 @@ func TestGetHistoryTxs(t *testing.T) {
err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderDesc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
flipedTxs := []testTx{} flipedTxs := []testTx{}
for i := 0; i < len(tc.allTxs); i++ {
flipedTxs = append(flipedTxs, tc.allTxs[len(tc.allTxs)-1-i])
for i := 0; i < len(tc.txs); i++ {
flipedTxs = append(flipedTxs, tc.txs[len(tc.txs)-1-i])
} }
assertTxs(t, flipedTxs, fetchedTxs) assertTxs(t, flipedTxs, fetchedTxs)
// 400 // 400
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?accountIndex=%s&hermezEthereumAddress=%s", "%s?accountIndex=%s&hermezEthereumAddress=%s",
endpoint, idx, tc.usrAddr,
endpoint, idx, account.EthAddr,
) )
err = doBadReq("GET", path, nil, 400) err = doBadReq("GET", path, nil, 400)
assert.NoError(t, err) assert.NoError(t, err)
@ -468,13 +439,13 @@ func TestGetHistoryTx(t *testing.T) {
// Get all txs by their ID // Get all txs by their ID
endpoint := apiURL + "transactions-history/" endpoint := apiURL + "transactions-history/"
fetchedTxs := []testTx{} fetchedTxs := []testTx{}
for _, tx := range tc.allTxs {
for _, tx := range tc.txs {
fetchedTx := testTx{} fetchedTx := testTx{}
err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx) err := doGoodReq("GET", endpoint+tx.TxID.String(), nil, &fetchedTx)
assert.NoError(t, err) assert.NoError(t, err)
fetchedTxs = append(fetchedTxs, fetchedTx) fetchedTxs = append(fetchedTxs, fetchedTx)
} }
assertTxs(t, tc.allTxs, fetchedTxs)
assertTxs(t, tc.txs, fetchedTxs)
// 400 // 400
err := doBadReq("GET", endpoint+"0x001", nil, 400) err := doBadReq("GET", endpoint+"0x001", nil, 400)
assert.NoError(t, err) assert.NoError(t, err)
@ -486,12 +457,15 @@ func TestGetHistoryTx(t *testing.T) {
func assertTxs(t *testing.T, expected, actual []testTx) { func assertTxs(t *testing.T, expected, actual []testTx) {
require.Equal(t, len(expected), len(actual)) require.Equal(t, len(expected), len(actual))
for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop for i := 0; i < len(actual); i++ { //nolint len(actual) won't change within the loop
assert.Equal(t, expected[i].BatchNum, actual[i].BatchNum)
assert.Equal(t, expected[i].Position, actual[i].Position)
actual[i].ItemID = 0 actual[i].ItemID = 0
actual[i].Token.ItemID = 0 actual[i].Token.ItemID = 0
assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix()) assert.Equal(t, expected[i].Timestamp.Unix(), actual[i].Timestamp.Unix())
expected[i].Timestamp = actual[i].Timestamp expected[i].Timestamp = actual[i].Timestamp
if expected[i].Token.USDUpdate == nil { if expected[i].Token.USDUpdate == nil {
assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate) assert.Equal(t, expected[i].Token.USDUpdate, actual[i].Token.USDUpdate)
expected[i].Token.USDUpdate = actual[i].Token.USDUpdate
} else { } else {
assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix()) assert.Equal(t, expected[i].Token.USDUpdate.Unix(), actual[i].Token.USDUpdate.Unix())
expected[i].Token.USDUpdate = actual[i].Token.USDUpdate expected[i].Token.USDUpdate = actual[i].Token.USDUpdate

+ 6
- 28
api/txspool_test.go

@ -3,7 +3,6 @@ package api
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"math/big"
"testing" "testing"
"time" "time"
@ -66,32 +65,11 @@ type testPoolTxSend struct {
RqNonce *common.Nonce `json:"requestNonce"` RqNonce *common.Nonce `json:"requestNonce"`
} }
func genTestPoolTx(accs []common.Account, privKs []babyjub.PrivateKey, tokens []historydb.TokenWithUSD) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) {
// Generate common.PoolL2Tx
// WARNING: this should be replaced once til is ready
poolTxs := []common.PoolL2Tx{}
amount := new(big.Int)
amount, ok := amount.SetString("100000000000000", 10)
if !ok {
panic("bad amount")
}
poolTx := common.PoolL2Tx{
FromIdx: accs[0].Idx,
ToIdx: accs[1].Idx,
Amount: amount,
TokenID: accs[0].TokenID,
Nonce: 6,
}
if _, err := common.NewPoolL2Tx(&poolTx); err != nil {
panic(err)
}
h, err := poolTx.HashToSign()
if err != nil {
panic(err)
}
poolTx.Signature = privKs[0].SignPoseidon(h).Compress()
poolTxs = append(poolTxs, poolTx)
// Transform to API formats
func genTestPoolTxs(
poolTxs []common.PoolL2Tx,
tokens []historydb.TokenWithUSD,
accs []common.Account,
) (poolTxsToSend []testPoolTxSend, poolTxsToReceive []testPoolTxReceive) {
poolTxsToSend = []testPoolTxSend{} poolTxsToSend = []testPoolTxSend{}
poolTxsToReceive = []testPoolTxReceive{} poolTxsToReceive = []testPoolTxReceive{}
for _, poolTx := range poolTxs { for _, poolTx := range poolTxs {
@ -125,7 +103,7 @@ func genTestPoolTx(accs []common.Account, privKs []babyjub.PrivateKey, tokens []
RqNonce: &poolTx.RqNonce, RqNonce: &poolTx.RqNonce,
Token: token, Token: token,
} }
fromAcc := getAccountByIdx(poolTx.ToIdx, accs)
fromAcc := getAccountByIdx(poolTx.FromIdx, accs)
fromAddr := ethAddrToHez(fromAcc.EthAddr) fromAddr := ethAddrToHez(fromAcc.EthAddr)
genReceiveTx.FromEthAddr = &fromAddr genReceiveTx.FromEthAddr = &fromAddr
fromBjj := bjjToString(fromAcc.PublicKey) fromBjj := bjjToString(fromAcc.PublicKey)

+ 3
- 3
apitypes/apitypes.go

@ -95,12 +95,12 @@ func (c *CollectedFees) UnmarshalJSON(text []byte) error {
if err := json.Unmarshal(text, &bigIntMap); err != nil { if err := json.Unmarshal(text, &bigIntMap); err != nil {
return err return err
} }
bStrMap := make(map[common.TokenID]BigIntStr)
*c = CollectedFees(make(map[common.TokenID]BigIntStr))
for k, v := range bigIntMap { for k, v := range bigIntMap {
bStr := NewBigIntStr(v) bStr := NewBigIntStr(v)
bStrMap[k] = *bStr
(CollectedFees(*c)[k]) = *bStr
} }
*c = CollectedFees(bStrMap)
// *c = CollectedFees(bStrMap)
return nil return nil
} }

+ 44
- 15
db/historydb/historydb.go

@ -894,17 +894,13 @@ func (hdb *HistoryDB) GetHistoryTxs(
nextIsAnd := false nextIsAnd := false
// ethAddr filter // ethAddr filter
if ethAddr != nil { if ethAddr != nil {
queryStr = `WITH acc AS
(select idx from account where eth_addr = ?) ` + queryStr
queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
queryStr += "WHERE (tx.from_eth_addr = ? OR tx.to_eth_addr = ?) "
nextIsAnd = true nextIsAnd = true
args = append(args, ethAddr)
args = append(args, ethAddr, ethAddr)
} else if bjj != nil { // bjj filter } else if bjj != nil { // bjj filter
queryStr = `WITH acc AS
(select idx from account where bjj = ?) ` + queryStr
queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
queryStr += "WHERE (tx.from_bjj = ? OR tx.to_bjj = ?) "
nextIsAnd = true nextIsAnd = true
args = append(args, bjj)
args = append(args, bjj, bjj)
} }
// tokenID filter // tokenID filter
if tokenID != nil { if tokenID != nil {
@ -1302,10 +1298,30 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
} }
} }
// Add l1 Txs
if len(blockData.Rollup.L1UserTxs) > 0 {
if err := hdb.addL1Txs(txn, blockData.Rollup.L1UserTxs); err != nil {
return err
// Prepare user L1 txs to be added.
// They must be added before the batch that will forge them (which can be in the same block)
// and after the account that will be sent to (also can be in the same block).
// Note: insert order is not relevant since item_id will be updated by a DB trigger when
// the batch that forges those txs is inserted
userL1s := make(map[common.BatchNum][]common.L1Tx)
for i := range blockData.Rollup.L1UserTxs {
batchThatForgesIsInTheBlock := false
for _, batch := range blockData.Rollup.Batches {
if batch.Batch.ForgeL1TxsNum != nil &&
*batch.Batch.ForgeL1TxsNum == *blockData.Rollup.L1UserTxs[i].ToForgeL1TxsNum {
// Tx is forged in this block. It's guaranteed that:
// * the first batch of the block won't forge user L1 txs that have been added in this block
// * batch nums are sequential therefore it's safe to add the tx at batch.BatchNum -1
batchThatForgesIsInTheBlock = true
addAtBatchNum := batch.Batch.BatchNum - 1
userL1s[addAtBatchNum] = append(userL1s[addAtBatchNum], blockData.Rollup.L1UserTxs[i])
break
}
}
if !batchThatForgesIsInTheBlock {
// User artificial batchNum 0 to add txs that are not forge in this block
// after all the accounts of the block have been added
userL1s[0] = append(userL1s[0], blockData.Rollup.L1UserTxs[i])
} }
} }
@ -1318,6 +1334,13 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
return err return err
} }
// Add accounts
if len(batch.CreatedAccounts) > 0 {
if err := hdb.addAccounts(txn, batch.CreatedAccounts); err != nil {
return err
}
}
// Add forged l1 coordinator Txs // Add forged l1 coordinator Txs
if len(batch.L1CoordinatorTxs) > 0 { if len(batch.L1CoordinatorTxs) > 0 {
if err := hdb.addL1Txs(txn, batch.L1CoordinatorTxs); err != nil { if err := hdb.addL1Txs(txn, batch.L1CoordinatorTxs); err != nil {
@ -1332,9 +1355,9 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
} }
} }
// Add accounts
if len(batch.CreatedAccounts) > 0 {
if err := hdb.addAccounts(txn, batch.CreatedAccounts); err != nil {
// Add user L1 txs that will be forged in next batch
if userlL1s, ok := userL1s[batch.Batch.BatchNum]; ok {
if err := hdb.addL1Txs(txn, userlL1s); err != nil {
return err return err
} }
} }
@ -1346,6 +1369,12 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
} }
} }
} }
// Add user L1 txs that won't be forged in this block
if userL1sNotForgedInThisBlock, ok := userL1s[0]; ok {
if err := hdb.addL1Txs(txn, userL1sNotForgedInThisBlock); err != nil {
return err
}
}
if blockData.Rollup.Vars != nil { if blockData.Rollup.Vars != nil {
if err := hdb.setRollupVars(txn, blockData.Rollup.Vars); err != nil { if err := hdb.setRollupVars(txn, blockData.Rollup.Vars); err != nil {
return err return err

+ 15
- 10
db/migrations/0001.sql

@ -448,7 +448,7 @@ DECLARE
_tx_timestamp TIMESTAMP; _tx_timestamp TIMESTAMP;
BEGIN BEGIN
IF NEW.is_l1 THEN IF NEW.is_l1 THEN
-- Validate
-- Validate L1 Tx
IF NEW.user_origin IS NULL OR IF NEW.user_origin IS NULL OR
NEW.from_eth_addr IS NULL OR NEW.from_eth_addr IS NULL OR
NEW.from_bjj IS NULL OR NEW.from_bjj IS NULL OR
@ -458,7 +458,7 @@ BEGIN
RAISE EXCEPTION 'Invalid L1 tx: %', NEW; RAISE EXCEPTION 'Invalid L1 tx: %', NEW;
END IF; END IF;
ELSE ELSE
-- Validate
-- Validate L2 Tx
IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN IF NEW.batch_num IS NULL OR NEW.nonce IS NULL THEN
RAISE EXCEPTION 'Invalid L2 tx: %', NEW; RAISE EXCEPTION 'Invalid L2 tx: %', NEW;
END IF; END IF;
@ -474,17 +474,22 @@ BEGIN
-- Set USD related -- Set USD related
SELECT INTO _value, _usd_update, _tx_timestamp SELECT INTO _value, _usd_update, _tx_timestamp
usd / POWER(10, decimals), usd_update, timestamp FROM token INNER JOIN block on token.eth_block_num = block.eth_block_num WHERE token_id = NEW.token_id; usd / POWER(10, decimals), usd_update, timestamp FROM token INNER JOIN block on token.eth_block_num = block.eth_block_num WHERE token_id = NEW.token_id;
IF _tx_timestamp - interval '24 hours' < _usd_update AND _tx_timestamp + interval '24 hours' > _usd_update THEN
NEW."amount_usd" = (SELECT _value * NEW.amount_f);
IF NOT NEW.is_l1 THEN
NEW."fee_usd" = (SELECT NEW."amount_usd" * fee_percentage(NEW.fee::NUMERIC));
ELSE
NEW."load_amount_usd" = (SELECT _value * NEW.load_amount_f);
IF _usd_update - interval '24 hours' < _usd_update AND _usd_update + interval '24 hours' > _usd_update THEN
IF _value > 0.0 THEN
IF NEW."amount_f" > 0.0 THEN
NEW."amount_usd" = (SELECT _value * NEW."amount_f");
IF NOT NEW."is_l1" AND NEW."fee" > 0 THEN
NEW."fee_usd" = (SELECT NEW."amount_usd" * fee_percentage(NEW.fee::NUMERIC));
END IF;
END IF;
IF NEW."is_l1" AND NEW."load_amount_f" > 0.0 THEN
NEW."load_amount_usd" = (SELECT _value * NEW.load_amount_f);
END IF;
END IF; END IF;
END IF; END IF;
-- Set to_{eth_addr,bjj} -- Set to_{eth_addr,bjj}
IF NEW.to_idx > 255 THEN
SELECT INTO NEW."to_eth_addr", NEW."to_bjj" eth_addr, bjj FROM account WHERE idx = NEW.to_idx;
IF NEW.>"to_idx" > 255 THEN
SELECT INTO NEW."to_eth_addr", NEW."to_bjj" eth_addr, bjj FROM account WHERE idx = NEW.>"to_idx";
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;

+ 1
- 0
go.sum

@ -174,6 +174,7 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x
github.com/getkin/kin-openapi v0.22.0 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bRkz4s= github.com/getkin/kin-openapi v0.22.0 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bRkz4s=
github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw= github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/getkin/kin-openapi v0.23.0 h1:RKtVNKk8kxcTIWEswgZ3Olvn1RxWOJ0zz8cP3d9aHIA= github.com/getkin/kin-openapi v0.23.0 h1:RKtVNKk8kxcTIWEswgZ3Olvn1RxWOJ0zz8cP3d9aHIA=
github.com/getkin/kin-openapi v0.29.0 h1:YAJ8s2UFkhAfJ/bpDcBiUPD4spfVa1ur0FqhBuubaRw=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs=

Loading…
Cancel
Save