Browse Source

Refactor api pagination

feature/sql-semaphore1
Arnau B 3 years ago
parent
commit
d2e9196fba
20 changed files with 257 additions and 359 deletions
  1. +5
    -6
      api/account.go
  2. +10
    -16
      api/account_test.go
  3. +41
    -23
      api/api_test.go
  4. +5
    -6
      api/batch.go
  5. +6
    -12
      api/batch_test.go
  6. +5
    -6
      api/bids.go
  7. +6
    -12
      api/bids_test.go
  8. +3
    -4
      api/coordinator.go
  9. +5
    -11
      api/coordinator_test.go
  10. +5
    -6
      api/exits.go
  11. +6
    -12
      api/exits_test.go
  12. +33
    -52
      api/slots.go
  13. +7
    -13
      api/slots_test.go
  14. +32
    -42
      api/swagger.yml
  15. +5
    -6
      api/token.go
  16. +6
    -12
      api/token_test.go
  17. +5
    -6
      api/txshistory.go
  18. +6
    -12
      api/txshistory_test.go
  19. +66
    -87
      db/historydb/historydb.go
  20. +0
    -15
      db/utils.go

+ 5
- 6
api/account.go

@ -5,7 +5,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/apitypes"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -49,7 +48,7 @@ func (a *API) getAccounts(c *gin.Context) {
}
// Fetch Accounts from historyDB
apiAccounts, pagination, err := a.h.GetAccountsAPI(tokenIDs, addr, bjj, fromItem, limit, order)
apiAccounts, pendingItems, err := a.h.GetAccountsAPI(tokenIDs, addr, bjj, fromItem, limit, order)
if err != nil {
retSQLErr(err, c)
return
@ -72,11 +71,11 @@ func (a *API) getAccounts(c *gin.Context) {
// Build succesfull response
type accountResponse struct {
Accounts []historydb.AccountAPI `json:"accounts"`
Pagination *db.Pagination `json:"pagination"`
Accounts []historydb.AccountAPI `json:"accounts"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &accountResponse{
Accounts: apiAccounts,
Pagination: pagination,
Accounts: apiAccounts,
PendingItems: pendingItems,
})
}

+ 10
- 16
api/account_test.go

@ -7,7 +7,6 @@ import (
"github.com/hermeznetwork/hermez-node/apitypes"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -25,10 +24,18 @@ type testAccount struct {
}
type testAccountsResponse struct {
Accounts []testAccount `json:"accounts"`
Pagination *db.Pagination `json:"pagination"`
Accounts []testAccount `json:"accounts"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testAccountsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Accounts[len(t.Accounts)-1].ItemID
return pendingItems, lastItemID
}
func (t *testAccountsResponse) Len() int { return len(t.Accounts) }
func genTestAccounts(accounts []common.Account, tokens []historydb.TokenWithUSD) []testAccount {
tAccounts := []testAccount{}
for x, account := range accounts {
@ -47,19 +54,6 @@ func genTestAccounts(accounts []common.Account, tokens []historydb.TokenWithUSD)
return tAccounts
}
func (t *testAccountsResponse) GetPagination() *db.Pagination {
if t.Accounts[0].ItemID < t.Accounts[len(t.Accounts)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Accounts[0].ItemID
t.Pagination.LastReturnedItem = t.Accounts[len(t.Accounts)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Accounts[0].ItemID
t.Pagination.FirstReturnedItem = t.Accounts[len(t.Accounts)-1].ItemID
}
return t.Pagination
}
func (t *testAccountsResponse) Len() int { return len(t.Accounts) }
func TestGetAccounts(t *testing.T) {
endpoint := apiURL + "accounts"
fetchedAccounts := []testAccount{}

+ 41
- 23
api/api_test.go

@ -28,6 +28,13 @@ import (
"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
// when testing paginated endpoints.
type Pendinger interface {
GetPending() (pendingItems, lastItemID uint64)
Len() int
}
const apiPort = ":4010"
const apiURL = "http://localhost" + apiPort + "/"
@ -339,41 +346,52 @@ func TestMain(m *testing.M) {
func doGoodReqPaginated(
path, order string,
iterStruct db.Paginationer,
iterStruct Pendinger,
appendIter func(res interface{}),
) error {
next := -1
var next uint64
firstIte := true
expectedTotal := 0
totalReceived := 0
for {
// Call API to get this iteration items
// Calculate fromItem
iterPath := path
if next == -1 && order == historydb.OrderDesc {
// Fetch first item in reverse order
iterPath += "99999"
} else {
// Fetch from next item or 0 if it's ascending order
if next == -1 {
next = 0
if firstIte {
if order == historydb.OrderDesc {
// Fetch first item in reverse order
iterPath += "99999" // Asumption that for testing there won't be any itemID > 99999
} else {
iterPath += "0"
}
iterPath += strconv.Itoa(next)
} else {
iterPath += strconv.Itoa(int(next))
}
// Call API to get this iteration items
if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
return err
}
appendIter(iterStruct)
// Keep iterating?
pag := iterStruct.GetPagination()
if order == historydb.OrderAsc {
if pag.LastReturnedItem == pag.LastItem { // No
break
} else { // Yes
next = int(pag.LastReturnedItem + 1)
}
remaining, lastID := iterStruct.GetPending()
if remaining == 0 {
break
}
if order == historydb.OrderDesc {
next = lastID - 1
} else {
if pag.FirstReturnedItem == pag.FirstItem { // No
break
} else { // Yes
next = int(pag.FirstReturnedItem - 1)
}
next = lastID + 1
}
// Check that the expected amount of items is consistent across iterations
totalReceived += iterStruct.Len()
if firstIte {
firstIte = false
expectedTotal = totalReceived + int(remaining)
}
if expectedTotal != totalReceived+int(remaining) {
panic(fmt.Sprintf(
"pagination error, totalReceived + remaining should be %d, but is %d",
expectedTotal, totalReceived+int(remaining),
))
}
}
return nil

+ 5
- 6
api/batch.go

@ -6,7 +6,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -43,7 +42,7 @@ func (a *API) getBatches(c *gin.Context) {
return
}
// Fetch batches from historyDB
batches, pagination, err := a.h.GetBatchesAPI(
batches, pendingItems, err := a.h.GetBatchesAPI(
minBatchNum, maxBatchNum, slotNum, forgerAddr, fromItem, limit, order,
)
if err != nil {
@ -53,12 +52,12 @@ func (a *API) getBatches(c *gin.Context) {
// Build succesfull response
type batchesResponse struct {
Batches []historydb.BatchAPI `json:"batches"`
Pagination *db.Pagination `json:"pagination"`
Batches []historydb.BatchAPI `json:"batches"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &batchesResponse{
Batches: batches,
Pagination: pagination,
Batches: batches,
PendingItems: pendingItems,
})
}

+ 6
- 12
api/batch_test.go

@ -8,7 +8,6 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -30,19 +29,14 @@ type testBatch struct {
SlotNum int64 `json:"slotNum"`
}
type testBatchesResponse struct {
Batches []testBatch `json:"batches"`
Pagination *db.Pagination `json:"pagination"`
Batches []testBatch `json:"batches"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testBatchesResponse) GetPagination() *db.Pagination {
if t.Batches[0].ItemID < t.Batches[len(t.Batches)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Batches[0].ItemID
t.Pagination.LastReturnedItem = t.Batches[len(t.Batches)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Batches[0].ItemID
t.Pagination.FirstReturnedItem = t.Batches[len(t.Batches)-1].ItemID
}
return t.Pagination
func (t testBatchesResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Batches[len(t.Batches)-1].ItemID
return pendingItems, lastItemID
}
func (t testBatchesResponse) Len() int {

+ 5
- 6
api/bids.go

@ -5,7 +5,6 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -26,7 +25,7 @@ func (a *API) getBids(c *gin.Context) {
return
}
bids, pagination, err := a.h.GetBidsAPI(
bids, pendingItems, err := a.h.GetBidsAPI(
slotNum, bidderAddr, fromItem, limit, order,
)
@ -37,11 +36,11 @@ func (a *API) getBids(c *gin.Context) {
// Build succesfull response
type bidsResponse struct {
Bids []historydb.BidAPI `json:"bids"`
Pagination *db.Pagination `json:"pagination"`
Bids []historydb.BidAPI `json:"bids"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &bidsResponse{
Bids: bids,
Pagination: pagination,
Bids: bids,
PendingItems: pendingItems,
})
}

+ 6
- 12
api/bids_test.go

@ -7,7 +7,6 @@ import (
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -25,19 +24,14 @@ type testBid struct {
}
type testBidsResponse struct {
Bids []testBid `json:"bids"`
Pagination *db.Pagination `json:"pagination"`
Bids []testBid `json:"bids"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testBidsResponse) GetPagination() *db.Pagination {
if t.Bids[0].ItemID < t.Bids[len(t.Bids)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Bids[0].ItemID
t.Pagination.LastReturnedItem = t.Bids[len(t.Bids)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Bids[0].ItemID
t.Pagination.FirstReturnedItem = t.Bids[len(t.Bids)-1].ItemID
}
return t.Pagination
func (t testBidsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Bids[len(t.Bids)-1].ItemID
return pendingItems, lastItemID
}
func (t testBidsResponse) Len() int {

+ 3
- 4
api/coordinator.go

@ -4,7 +4,6 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -39,7 +38,7 @@ func (a *API) getCoordinators(c *gin.Context) {
}
// Fetch coordinators from historyDB
coordinators, pagination, err := a.h.GetCoordinatorsAPI(fromItem, limit, order)
coordinators, pendingItems, err := a.h.GetCoordinatorsAPI(fromItem, limit, order)
if err != nil {
retSQLErr(err, c)
return
@ -48,10 +47,10 @@ func (a *API) getCoordinators(c *gin.Context) {
// Build succesfull response
type coordinatorsResponse struct {
Coordinators []historydb.CoordinatorAPI `json:"coordinators"`
Pagination *db.Pagination `json:"pagination"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &coordinatorsResponse{
Coordinators: coordinators,
Pagination: pagination,
PendingItems: pendingItems,
})
}

+ 5
- 11
api/coordinator_test.go

@ -4,7 +4,6 @@ import (
"fmt"
"testing"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -12,18 +11,13 @@ import (
type testCoordinatorsResponse struct {
Coordinators []historydb.CoordinatorAPI `json:"coordinators"`
Pagination *db.Pagination `json:"pagination"`
PendingItems uint64 `json:"pendingItems"`
}
func (t *testCoordinatorsResponse) GetPagination() *db.Pagination {
if t.Coordinators[0].ItemID < t.Coordinators[len(t.Coordinators)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Coordinators[0].ItemID
t.Pagination.LastReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Coordinators[0].ItemID
t.Pagination.FirstReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
}
return t.Pagination
func (t testCoordinatorsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Coordinators[len(t.Coordinators)-1].ItemID
return pendingItems, lastItemID
}
func (t *testCoordinatorsResponse) Len() int { return len(t.Coordinators) }

+ 5
- 6
api/exits.go

@ -4,7 +4,6 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -36,7 +35,7 @@ func (a *API) getExits(c *gin.Context) {
}
// Fetch exits from historyDB
exits, pagination, err := a.h.GetExitsAPI(
exits, pendingItems, err := a.h.GetExitsAPI(
addr, bjj, tokenID, idx, batchNum, onlyPendingWithdraws, fromItem, limit, order,
)
if err != nil {
@ -46,12 +45,12 @@ func (a *API) getExits(c *gin.Context) {
// Build succesfull response
type exitsResponse struct {
Exits []historydb.ExitAPI `json:"exits"`
Pagination *db.Pagination `json:"pagination"`
Exits []historydb.ExitAPI `json:"exits"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &exitsResponse{
Exits: exits,
Pagination: pagination,
Exits: exits,
PendingItems: pendingItems,
})
}

+ 6
- 12
api/exits_test.go

@ -5,7 +5,6 @@ import (
"testing"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -36,19 +35,14 @@ type testExit struct {
}
type testExitsResponse struct {
Exits []testExit `json:"exits"`
Pagination *db.Pagination `json:"pagination"`
Exits []testExit `json:"exits"`
PendingItems uint64 `json:"pendingItems"`
}
func (t *testExitsResponse) GetPagination() *db.Pagination {
if t.Exits[0].ItemID < t.Exits[len(t.Exits)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Exits[0].ItemID
t.Pagination.LastReturnedItem = t.Exits[len(t.Exits)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Exits[0].ItemID
t.Pagination.FirstReturnedItem = t.Exits[len(t.Exits)-1].ItemID
}
return t.Pagination
func (t testExitsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Exits[len(t.Exits)-1].ItemID
return pendingItems, lastItemID
}
func (t *testExitsResponse) Len() int {

+ 33
- 52
api/slots.go

@ -7,7 +7,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -49,18 +48,6 @@ func (a *API) isOpenAuction(currentBlock, slotNum int64, auctionVars common.Auct
return false
}
func getPagination(totalItems uint64, minSlotNum, maxSlotNum *int64) *db.Pagination {
// itemID is slotNum
firstItem := *minSlotNum
lastItem := *maxSlotNum
pagination := &db.Pagination{
TotalItems: totalItems,
FirstItem: uint64(firstItem),
LastItem: uint64(lastItem),
}
return pagination
}
func (a *API) newSlotAPI(slotNum, currentBlockNum int64, bid *historydb.BidAPI, auctionVars *common.AuctionVariables) SlotAPI {
firstBlock, lastBlock := a.getFirstLastBlock(slotNum)
openAuction := a.isOpenAuction(currentBlockNum, slotNum, *auctionVars)
@ -141,34 +128,35 @@ func (a *API) getSlot(c *gin.Context) {
c.JSON(http.StatusOK, slot)
}
func getLimits(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) {
var minLim, maxLim int64
if fromItem != nil {
if order == historydb.OrderAsc {
if int64(*fromItem) > *minSlotNum {
minLim = int64(*fromItem)
} else {
minLim = *minSlotNum
}
if (minLim + int64(*limit-1)) < *maxSlotNum {
maxLim = minLim + int64(*limit-1)
} else {
maxLim = *maxSlotNum
}
func getLimits(
minSlotNum, maxSlotNum int64, fromItem, limit *uint, order string,
) (minLimit, maxLimit int64, pendingItems uint64) {
if order == historydb.OrderAsc {
if fromItem != nil && int64(*fromItem) > minSlotNum {
minLimit = int64(*fromItem)
} else {
if int64(*fromItem) < *maxSlotNum {
maxLim = int64(*fromItem)
} else {
maxLim = *maxSlotNum
}
if (maxLim - int64(*limit-1)) < *minSlotNum {
minLim = *minSlotNum
} else {
minLim = maxLim - int64(*limit-1)
}
minLimit = minSlotNum
}
if limit != nil && (minLimit+int64(*limit-1)) < maxSlotNum {
maxLimit = minLimit + int64(*limit-1)
} else {
maxLimit = maxSlotNum
}
pendingItems = uint64(maxSlotNum - maxLimit)
} else {
if fromItem != nil && int64(*fromItem) < maxSlotNum {
maxLimit = int64(*fromItem)
} else {
maxLimit = maxSlotNum
}
if limit != nil && (maxLimit-int64(*limit-1)) < minSlotNum {
minLimit = minSlotNum
} else {
minLimit = maxLimit - int64(*limit-1)
}
pendingItems = uint64(-(minSlotNum - minLimit))
}
return minLim, maxLim
return minLimit, maxLimit, pendingItems
}
func getLimitsWithAddr(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) {
@ -262,29 +250,22 @@ func (a *API) getSlots(c *gin.Context) {
// Get bids and pagination according to filters
var slotMinLim, slotMaxLim int64
var bids []historydb.BidAPI
var pag *db.Pagination
totalItems := uint64(0)
var pendingItems uint64
if wonByEthereumAddress == nil {
slotMinLim, slotMaxLim = getLimits(minSlotNum, maxSlotNum, fromItem, limit, order)
slotMinLim, slotMaxLim, pendingItems = getLimits(*minSlotNum, *maxSlotNum, fromItem, limit, order)
// Get best bids in range maxSlotNum - minSlotNum
bids, _, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, nil, order)
if err != nil && err != sql.ErrNoRows {
retSQLErr(err, c)
return
}
totalItems = uint64(*maxSlotNum) - uint64(*minSlotNum) + 1
} else {
slotMinLim, slotMaxLim = getLimitsWithAddr(minSlotNum, maxSlotNum, fromItem, limit, order)
bids, pag, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order)
bids, pendingItems, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order)
if err != nil && err != sql.ErrNoRows {
retSQLErr(err, c)
return
}
if len(bids) > 0 {
totalItems = uint64(pag.TotalItems)
*maxSlotNum = int64(pag.LastItem)
*minSlotNum = int64(pag.FirstItem)
}
}
// Build the slot information with previous bids
@ -336,11 +317,11 @@ func (a *API) getSlots(c *gin.Context) {
// Build succesfull response
type slotsResponse struct {
Slots []SlotAPI `json:"slots"`
Pagination *db.Pagination `json:"pagination"`
Slots []SlotAPI `json:"slots"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &slotsResponse{
Slots: slots,
Pagination: getPagination(totalItems, minSlotNum, maxSlotNum),
Slots: slots,
PendingItems: pendingItems,
})
}

+ 7
- 13
api/slots_test.go

@ -6,14 +6,13 @@ import (
"testing"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
)
type testSlot struct {
ItemID int `json:"itemId"`
ItemID uint64 `json:"itemId"`
SlotNum int64 `json:"slotNum"`
FirstBlock int64 `json:"firstBlock"`
LastBlock int64 `json:"lastBlock"`
@ -22,19 +21,14 @@ type testSlot struct {
}
type testSlotsResponse struct {
Slots []testSlot `json:"slots"`
Pagination *db.Pagination `json:"pagination"`
Slots []testSlot `json:"slots"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testSlotsResponse) GetPagination() *db.Pagination {
if t.Slots[0].ItemID < t.Slots[len(t.Slots)-1].ItemID {
t.Pagination.FirstReturnedItem = uint64(t.Slots[0].ItemID)
t.Pagination.LastReturnedItem = uint64(t.Slots[len(t.Slots)-1].ItemID)
} else {
t.Pagination.LastReturnedItem = uint64(t.Slots[0].ItemID)
t.Pagination.FirstReturnedItem = uint64(t.Slots[len(t.Slots)-1].ItemID)
}
return t.Pagination
func (t testSlotsResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Slots[len(t.Slots)-1].ItemID
return pendingItems, lastItemID
}
func (t testSlotsResponse) Len() int {

+ 32
- 42
api/swagger.yml

@ -17,7 +17,9 @@ info:
* `order`: all pginated items are ordered chronologicaly. However the specific fields to guarantee this order depend on each endpoint. For this purpose, `itemId` is used (itemId follows ascending chronological order except for unforged L1 user transactions). If the parameter is not provided, ascending order will be used by default.
* `limit`: maximum amount of items to include in each response. Default is 20, maximum 2049.
Responses for those endpoint will always include a `pagination` object. This object includes the total amount of items that the endpoint will return at a given time with the given filters. Apart from that, it also includes the `itemId` of the last and first items that will be returned (not in a single response but within the total items). These two properties can be used to know when to stop querying.
Responses for those endpoint will always include a `pendingItems` property. This property includes the amount of items that are not fetched yet. his can be used to:
* Calculate the amount of items that match the filters: `totalItems = length(alreadyFetchedItems) + pendingItems`
* Know when all items have been fetched: `if pendingItems == 0 {/* all items have been fetched */}`
#### Reorgs and safetyness
@ -1739,11 +1741,11 @@ components:
description: List of history transactions.
items:
$ref: '#/components/schemas/HistoryTransaction'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
required:
- transactions
- pagination
- pendingItems
additionalProperties: false
EthBlockNum:
type: integer
@ -1877,12 +1879,12 @@ components:
description: List of batches.
items:
$ref: '#/components/schemas/Batch'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
additionalProperties: false
required:
- batches
- pagination
- pendingItems
Coordinator:
type: object
properties:
@ -1914,12 +1916,12 @@ components:
description: List of coordinators.
items:
$ref: '#/components/schemas/Coordinator'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
additionalProperties: false
required:
- coordinators
- pagination
- pendingItems
Bid:
type: object
description: Tokens placed in an auction by a coordinator to gain the right to forge batches during a specific slot.
@ -1958,12 +1960,12 @@ components:
description: List of bids.
items:
$ref: '#/components/schemas/Bid'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
additionalProperties: false
require:
- bids
- pagination
- pendingItems
RecommendedFee:
type: object
description: Fee that the coordinator recommends per transaction in USD.
@ -2042,8 +2044,8 @@ components:
description: List of tokens.
items:
$ref: '#/components/schemas/Token'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
Exit:
type: object
description: Exit tree leaf. It Contains the necessary information to perform a withdrawal.
@ -2135,11 +2137,11 @@ components:
description: List of exits.
items:
$ref: '#/components/schemas/Exit'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
required:
- exits
- pagination
- pendingItems
additionalProperties: false
Account:
type: object
@ -2176,12 +2178,12 @@ components:
description: List of accounts.
items:
$ref: '#/components/schemas/Account'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
additionalProperties: false
required:
- accounts
- pagination
- pendingItems
Slot:
type: object
description: Slot information.
@ -2255,12 +2257,12 @@ components:
allOf:
- $ref: '#/components/schemas/Slot'
- description: Last synchronized Etherum block.
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
additionalProperties: false
require:
- slots
- pagination
- pendingItems
NextForger:
type: object
description: Coordinator information along with the scheduled forging period
@ -2289,8 +2291,8 @@ components:
description: List of next coordinators to forge.
items:
$ref: '#/components/schemas/NextForger'
pagination:
$ref: '#/components/schemas/PaginationInfo'
pendingItems:
$ref: '#/components/schemas/PendingItems'
State:
type: object
description: Gobal variables of the network
@ -2517,22 +2519,10 @@ components:
- auction
- withdrawalDelayer
- recomendedFee
PaginationInfo:
type: object
description: Give pagination information
properties:
totalItems:
type: integer
description: Amount of items that the endpoint can return given the filters and the current state of the database.
example: 2048
firstItem:
type: integer
description: The smallest itemId that the endpoint will return with the given filters.
example: 50
lastItem:
type: integer
description: The greatest itemId that the endpoint will return with the given filters.
example: 2130
PendingItems:
type: integer
description: Amount of items that will be returned in subsequent calls to the endpoint, as long as they are done with same filters. When the value is 0 it means that all items have been sent.
example: 15
Config:
type: object
description: Configuration parameters of the different smart contracts that power the Hermez network.

+ 5
- 6
api/token.go

@ -6,7 +6,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -46,7 +45,7 @@ func (a *API) getTokens(c *gin.Context) {
return
}
// Fetch exits from historyDB
tokens, pagination, err := a.h.GetTokens(
tokens, pendingItems, err := a.h.GetTokens(
tokenIDs, symbols, name, fromItem, limit, order,
)
if err != nil {
@ -56,11 +55,11 @@ func (a *API) getTokens(c *gin.Context) {
// Build succesfull response
type tokensResponse struct {
Tokens []historydb.TokenWithUSD `json:"tokens"`
Pagination *db.Pagination `json:"pagination"`
Tokens []historydb.TokenWithUSD `json:"tokens"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &tokensResponse{
Tokens: tokens,
Pagination: pagination,
Tokens: tokens,
PendingItems: pendingItems,
})
}

+ 6
- 12
api/token_test.go

@ -5,7 +5,6 @@ import (
"strconv"
"testing"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/mitchellh/copystructure"
"github.com/stretchr/testify/assert"
@ -13,19 +12,14 @@ import (
)
type testTokensResponse struct {
Tokens []historydb.TokenWithUSD `json:"tokens"`
Pagination *db.Pagination `json:"pagination"`
Tokens []historydb.TokenWithUSD `json:"tokens"`
PendingItems uint64 `json:"pendingItems"`
}
func (t *testTokensResponse) GetPagination() *db.Pagination {
if t.Tokens[0].ItemID < t.Tokens[len(t.Tokens)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Tokens[0].ItemID
t.Pagination.LastReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Tokens[0].ItemID
t.Pagination.FirstReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
}
return t.Pagination
func (t testTokensResponse) GetPending() (pendingItems, lastItemID uint64) {
pendingItems = t.PendingItems
lastItemID = t.Tokens[len(t.Tokens)-1].ItemID
return pendingItems, lastItemID
}
func (t *testTokensResponse) Len() int {

+ 5
- 6
api/txshistory.go

@ -4,7 +4,6 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
)
@ -35,7 +34,7 @@ func (a *API) getHistoryTxs(c *gin.Context) {
}
// Fetch txs from historyDB
txs, pagination, err := a.h.GetHistoryTxs(
txs, pendingItems, err := a.h.GetHistoryTxs(
addr, bjj, tokenID, idx, batchNum, txType, fromItem, limit, order,
)
if err != nil {
@ -45,12 +44,12 @@ func (a *API) getHistoryTxs(c *gin.Context) {
// Build succesfull response
type txsResponse struct {
Txs []historydb.TxAPI `json:"transactions"`
Pagination *db.Pagination `json:"pagination"`
Txs []historydb.TxAPI `json:"transactions"`
PendingItems uint64 `json:"pendingItems"`
}
c.JSON(http.StatusOK, &txsResponse{
Txs: txs,
Pagination: pagination,
Txs: txs,
PendingItems: pendingItems,
})
}

+ 6
- 12
api/txshistory_test.go

@ -9,7 +9,6 @@ import (
"github.com/hermeznetwork/hermez-node/apitypes"
"github.com/hermeznetwork/hermez-node/common"
"github.com/hermeznetwork/hermez-node/db"
"github.com/hermeznetwork/hermez-node/db/historydb"
"github.com/hermeznetwork/hermez-node/test"
"github.com/mitchellh/copystructure"
@ -53,19 +52,14 @@ type testTx struct {
}
type testTxsResponse struct {
Txs []testTx `json:"transactions"`
Pagination *db.Pagination `json:"pagination"`
Txs []testTx `json:"transactions"`
PendingItems uint64 `json:"pendingItems"`
}
func (t testTxsResponse) GetPagination() *db.Pagination {
if t.Txs[0].ItemID < t.Txs[len(t.Txs)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Txs[0].ItemID
t.Pagination.LastReturnedItem = t.Txs[len(t.Txs)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Txs[0].ItemID
t.Pagination.FirstReturnedItem = t.Txs[len(t.Txs)-1].ItemID
}
return t.Pagination
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 {

+ 66
- 87
db/historydb/historydb.go

@ -181,12 +181,11 @@ func (hdb *HistoryDB) GetBatchesAPI(
minBatchNum, maxBatchNum, slotNum *uint,
forgerAddr *ethCommon.Address,
fromItem, limit *uint, order string,
) ([]BatchAPI, *db.Pagination, error) {
) ([]BatchAPI, uint64, error) {
var query string
var args []interface{}
queryStr := `SELECT batch.*, block.timestamp, block.hash,
count(*) OVER() AS total_items, MIN(batch.item_id) OVER() AS first_item,
MAX(batch.item_id) OVER() AS last_item
count(*) OVER() AS total_items
FROM batch INNER JOIN block ON batch.eth_block_num = block.eth_block_num `
// Apply filters
nextIsAnd := false
@ -259,17 +258,13 @@ func (hdb *HistoryDB) GetBatchesAPI(
// log.Debug(query)
batchPtrs := []*BatchAPI{}
if err := meddler.QueryAll(hdb.db, &batchPtrs, query, args...); err != nil {
return nil, nil, err
return nil, 0, err
}
batches := db.SlicePtrsToSlice(batchPtrs).([]BatchAPI)
if len(batches) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return batches, &db.Pagination{
TotalItems: batches[0].TotalItems,
FirstItem: batches[0].FirstItem,
LastItem: batches[0].LastItem,
}, nil
return batches, batches[0].TotalItems - uint64(len(batches)), nil
}
// GetAllBatches retrieve all batches from the DB
@ -367,12 +362,15 @@ func (hdb *HistoryDB) GetBestBidAPI(slotNum *int64) (BidAPI, error) {
}
// GetBestBidsAPI returns the best bid in specific slot by slotNum
func (hdb *HistoryDB) GetBestBidsAPI(minSlotNum, maxSlotNum *int64, bidderAddr *ethCommon.Address, limit *uint, order string) ([]BidAPI, *db.Pagination, error) {
func (hdb *HistoryDB) GetBestBidsAPI(
minSlotNum, maxSlotNum *int64,
bidderAddr *ethCommon.Address,
limit *uint, order string,
) ([]BidAPI, uint64, error) {
var query string
var args []interface{}
queryStr := `SELECT b.*, block.timestamp, coordinator.forger_addr, coordinator.url,
COUNT(*) OVER() AS total_items, MIN(b.slot_num) OVER() AS first_item,
MAX(b.slot_num) OVER() AS last_item FROM (
COUNT(*) OVER() AS total_items FROM (
SELECT slot_num, MAX(item_id) as maxitem
FROM bid GROUP BY slot_num
)
@ -399,28 +397,26 @@ func (hdb *HistoryDB) GetBestBidsAPI(minSlotNum, maxSlotNum *int64, bidderAddr *
query = hdb.db.Rebind(queryStr)
bidPtrs := []*BidAPI{}
if err := meddler.QueryAll(hdb.db, &bidPtrs, query, args...); err != nil {
return nil, nil, err
return nil, 0, err
}
// log.Debug(query)
bids := db.SlicePtrsToSlice(bidPtrs).([]BidAPI)
if len(bids) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return bids, &db.Pagination{
TotalItems: bids[0].TotalItems,
FirstItem: bids[0].FirstItem,
LastItem: bids[0].LastItem,
}, nil
return bids, bids[0].TotalItems - uint64(len(bids)), nil
}
// GetBidsAPI return the bids applying the given filters
func (hdb *HistoryDB) GetBidsAPI(slotNum *int64, forgerAddr *ethCommon.Address, fromItem, limit *uint, order string) ([]BidAPI, *db.Pagination, error) {
func (hdb *HistoryDB) GetBidsAPI(
slotNum *int64, forgerAddr *ethCommon.Address,
fromItem, limit *uint, order string,
) ([]BidAPI, uint64, error) {
var query string
var args []interface{}
queryStr := `SELECT bid.*, block.timestamp, coordinator.forger_addr, coordinator.url,
COUNT(*) OVER() AS total_items, MIN(bid.item_id) OVER() AS first_item,
MAX(bid.item_id) OVER() AS last_item FROM bid
INNER JOIN block ON bid.eth_block_num = block.eth_block_num
COUNT(*) OVER() AS total_items
FROM bid INNER JOIN block ON bid.eth_block_num = block.eth_block_num
INNER JOIN coordinator ON bid.bidder_addr = coordinator.bidder_addr `
// Apply filters
nextIsAnd := false
@ -469,21 +465,17 @@ func (hdb *HistoryDB) GetBidsAPI(slotNum *int64, forgerAddr *ethCommon.Address,
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
query, argsQ, err := sqlx.In(queryStr, args...)
if err != nil {
return nil, nil, err
return nil, 0, err
}
query = hdb.db.Rebind(query)
bids := []*BidAPI{}
if err := meddler.QueryAll(hdb.db, &bids, query, argsQ...); err != nil {
return nil, nil, err
return nil, 0, err
}
if len(bids) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return db.SlicePtrsToSlice(bids).([]BidAPI), &db.Pagination{
TotalItems: bids[0].TotalItems,
FirstItem: bids[0].FirstItem,
LastItem: bids[0].LastItem,
}, nil
return db.SlicePtrsToSlice(bids).([]BidAPI), bids[0].TotalItems - uint64(len(bids)), nil
}
// AddCoordinators insert Coordinators into the DB
@ -562,10 +554,13 @@ func (hdb *HistoryDB) GetAllTokens() ([]TokenWithUSD, error) {
}
// GetTokens returns a list of tokens from the DB
func (hdb *HistoryDB) GetTokens(ids []common.TokenID, symbols []string, name string, fromItem, limit *uint, order string) ([]TokenWithUSD, *db.Pagination, error) {
func (hdb *HistoryDB) GetTokens(
ids []common.TokenID, symbols []string, name string, fromItem,
limit *uint, order string,
) ([]TokenWithUSD, uint64, error) {
var query string
var args []interface{}
queryStr := `SELECT * , COUNT(*) OVER() AS total_items, MIN(token.item_id) OVER() AS first_item, MAX(token.item_id) OVER() AS last_item FROM token `
queryStr := `SELECT * , COUNT(*) OVER() AS total_items FROM token `
// Apply filters
nextIsAnd := false
if len(ids) > 0 {
@ -616,21 +611,17 @@ func (hdb *HistoryDB) GetTokens(ids []common.TokenID, symbols []string, name str
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
query, argsQ, err := sqlx.In(queryStr, args...)
if err != nil {
return nil, nil, err
return nil, 0, err
}
query = hdb.db.Rebind(query)
tokens := []*TokenWithUSD{}
if err := meddler.QueryAll(hdb.db, &tokens, query, argsQ...); err != nil {
return nil, nil, err
return nil, 0, err
}
if len(tokens) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return db.SlicePtrsToSlice(tokens).([]TokenWithUSD), &db.Pagination{
TotalItems: tokens[0].TotalItems,
FirstItem: tokens[0].FirstItem,
LastItem: tokens[0].LastItem,
}, nil
return db.SlicePtrsToSlice(tokens).([]TokenWithUSD), uint64(len(tokens)) - tokens[0].TotalItems, nil
}
// GetTokenSymbols returns all the token symbols from the DB
@ -813,9 +804,9 @@ func (hdb *HistoryDB) GetHistoryTxs(
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
fromItem, limit *uint, order string,
) ([]TxAPI, *db.Pagination, error) {
) ([]TxAPI, uint64, error) {
if ethAddr != nil && bjj != nil {
return nil, nil, errors.New("ethAddr and bjj are incompatible")
return nil, 0, errors.New("ethAddr and bjj are incompatible")
}
var query string
var args []interface{}
@ -827,8 +818,7 @@ func (hdb *HistoryDB) GetHistoryTxs(
tx.load_amount, tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
token.usd_update, block.timestamp, count(*) OVER() AS total_items,
MIN(tx.item_id) OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item
token.usd_update, block.timestamp, count(*) OVER() AS total_items
FROM tx INNER JOIN token ON tx.token_id = token.token_id
INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
// Apply filters
@ -924,17 +914,13 @@ func (hdb *HistoryDB) GetHistoryTxs(
// log.Debug(query)
txsPtrs := []*TxAPI{}
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
return nil, nil, err
return nil, 0, err
}
txs := db.SlicePtrsToSlice(txsPtrs).([]TxAPI)
if len(txs) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return txs, &db.Pagination{
TotalItems: txs[0].TotalItems,
FirstItem: txs[0].FirstItem,
LastItem: txs[0].LastItem,
}, nil
return txs, txs[0].TotalItems - uint64(len(txs)), nil
}
// GetAllExits returns all exit from the DB
@ -972,9 +958,9 @@ func (hdb *HistoryDB) GetExitsAPI(
ethAddr *ethCommon.Address, bjj *babyjub.PublicKey, tokenID *common.TokenID,
idx *common.Idx, batchNum *uint, onlyPendingWithdraws *bool,
fromItem, limit *uint, order string,
) ([]ExitAPI, *db.Pagination, error) {
) ([]ExitAPI, uint64, error) {
if ethAddr != nil && bjj != nil {
return nil, nil, errors.New("ethAddr and bjj are incompatible")
return nil, 0, errors.New("ethAddr and bjj are incompatible")
}
var query string
var args []interface{}
@ -984,8 +970,7 @@ func (hdb *HistoryDB) GetExitsAPI(
exit_tree.delayed_withdraw_request, exit_tree.delayed_withdrawn,
token.token_id, token.item_id AS token_item_id,
token.eth_block_num AS token_block, token.eth_addr, token.name, token.symbol,
token.decimals, token.usd, token.usd_update, COUNT(*) OVER() AS total_items,
MIN(exit_tree.item_id) OVER() AS first_item, MAX(exit_tree.item_id) OVER() AS last_item
token.decimals, token.usd, token.usd_update, COUNT(*) OVER() AS total_items
FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx
INNER JOIN token ON account.token_id = token.token_id `
// Apply filters
@ -1071,16 +1056,12 @@ func (hdb *HistoryDB) GetExitsAPI(
// log.Debug(query)
exits := []*ExitAPI{}
if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
return nil, nil, err
return nil, 0, err
}
if len(exits) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return db.SlicePtrsToSlice(exits).([]ExitAPI), &db.Pagination{
TotalItems: exits[0].TotalItems,
FirstItem: exits[0].FirstItem,
LastItem: exits[0].LastItem,
}, nil
return db.SlicePtrsToSlice(exits).([]ExitAPI), exits[0].TotalItems - uint64(len(exits)), nil
}
// // GetTx returns a tx from the DB
@ -1337,11 +1318,11 @@ func (hdb *HistoryDB) GetCoordinatorAPI(bidderAddr ethCommon.Address) (*Coordina
}
// GetCoordinatorsAPI returns a list of coordinators from the DB and pagination info
func (hdb *HistoryDB) GetCoordinatorsAPI(fromItem, limit *uint, order string) ([]CoordinatorAPI, *db.Pagination, error) {
func (hdb *HistoryDB) GetCoordinatorsAPI(fromItem, limit *uint, order string) ([]CoordinatorAPI, uint64, error) {
var query string
var args []interface{}
queryStr := `SELECT coordinator.*,
COUNT(*) OVER() AS total_items, MIN(coordinator.item_id) OVER() AS first_item, MAX(coordinator.item_id) OVER() AS last_item
COUNT(*) OVER() AS total_items
FROM coordinator `
// Apply filters
if fromItem != nil {
@ -1365,16 +1346,13 @@ func (hdb *HistoryDB) GetCoordinatorsAPI(fromItem, limit *uint, order string) ([
coordinators := []*CoordinatorAPI{}
if err := meddler.QueryAll(hdb.db, &coordinators, query, args...); err != nil {
return nil, nil, err
return nil, 0, err
}
if len(coordinators) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return db.SlicePtrsToSlice(coordinators).([]CoordinatorAPI), &db.Pagination{
TotalItems: coordinators[0].TotalItems,
FirstItem: coordinators[0].FirstItem,
LastItem: coordinators[0].LastItem,
}, nil
return db.SlicePtrsToSlice(coordinators).([]CoordinatorAPI),
coordinators[0].TotalItems - uint64(len(coordinators)), nil
}
// AddAuctionVars insert auction vars into the DB
@ -1394,7 +1372,8 @@ func (hdb *HistoryDB) GetAuctionVars() (*common.AuctionVariables, error) {
// GetAccountAPI returns an account by its index
func (hdb *HistoryDB) GetAccountAPI(idx common.Idx) (*AccountAPI, error) {
account := &AccountAPI{}
err := meddler.QueryRow(hdb.db, account, `SELECT account.item_id, hez_idx(account.idx, token.symbol) as idx, account.batch_num, account.bjj, account.eth_addr,
err := meddler.QueryRow(hdb.db, account, `SELECT account.item_id, hez_idx(account.idx,
token.symbol) as idx, account.batch_num, account.bjj, account.eth_addr,
token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
token.eth_addr as token_eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
FROM account INNER JOIN token ON account.token_id = token.token_id WHERE idx = $1;`, idx)
@ -1407,16 +1386,19 @@ func (hdb *HistoryDB) GetAccountAPI(idx common.Idx) (*AccountAPI, error) {
}
// GetAccountsAPI returns a list of accounts from the DB and pagination info
func (hdb *HistoryDB) GetAccountsAPI(tokenIDs []common.TokenID, ethAddr *ethCommon.Address, bjj *babyjub.PublicKey, fromItem, limit *uint, order string) ([]AccountAPI, *db.Pagination, error) {
func (hdb *HistoryDB) GetAccountsAPI(
tokenIDs []common.TokenID, ethAddr *ethCommon.Address,
bjj *babyjub.PublicKey, fromItem, limit *uint, order string,
) ([]AccountAPI, uint64, error) {
if ethAddr != nil && bjj != nil {
return nil, nil, errors.New("ethAddr and bjj are incompatible")
return nil, 0, errors.New("ethAddr and bjj are incompatible")
}
var query string
var args []interface{}
queryStr := `SELECT account.item_id, hez_idx(account.idx, token.symbol) as idx, account.batch_num, account.bjj, account.eth_addr,
token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
queryStr := `SELECT account.item_id, hez_idx(account.idx, token.symbol) as idx, account.batch_num,
account.bjj, account.eth_addr, token.token_id, token.item_id AS token_item_id, token.eth_block_num AS token_block,
token.eth_addr as token_eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update,
COUNT(*) OVER() AS total_items, MIN(account.item_id) OVER() AS first_item, MAX(account.item_id) OVER() AS last_item
COUNT(*) OVER() AS total_items
FROM account INNER JOIN token ON account.token_id = token.token_id `
// Apply filters
nextIsAnd := false
@ -1464,21 +1446,18 @@ func (hdb *HistoryDB) GetAccountsAPI(tokenIDs []common.TokenID, ethAddr *ethComm
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
query, argsQ, err := sqlx.In(queryStr, args...)
if err != nil {
return nil, nil, err
return nil, 0, err
}
query = hdb.db.Rebind(query)
accounts := []*AccountAPI{}
if err := meddler.QueryAll(hdb.db, &accounts, query, argsQ...); err != nil {
return nil, nil, err
return nil, 0, err
}
if len(accounts) == 0 {
return nil, nil, sql.ErrNoRows
return nil, 0, sql.ErrNoRows
}
return db.SlicePtrsToSlice(accounts).([]AccountAPI), &db.Pagination{
TotalItems: accounts[0].TotalItems,
FirstItem: accounts[0].FirstItem,
LastItem: accounts[0].LastItem,
}, nil
return db.SlicePtrsToSlice(accounts).([]AccountAPI),
accounts[0].TotalItems - uint64(len(accounts)), nil
}

+ 0
- 15
db/utils.go

@ -179,21 +179,6 @@ func SlicePtrsToSlice(slice interface{}) interface{} {
return res.Interface()
}
// Pagination give information on the items of a query
type Pagination struct {
TotalItems uint64 `json:"totalItems"`
FirstItem uint64 `json:"firstItem"`
LastItem uint64 `json:"lastItem"`
FirstReturnedItem uint64 `json:"-"`
LastReturnedItem uint64 `json:"-"`
}
// Paginationer is an interface that allows getting pagination info on any struct
type Paginationer interface {
GetPagination() *Pagination
Len() int
}
// Rollback an sql transaction, and log the error if it's not nil
func Rollback(txn *sql.Tx) {
err := txn.Rollback()

Loading…
Cancel
Save