mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 11:26:44 +01:00
Merge pull request #251 from hermeznetwork/feature/api-slots
API add slots endpoints
This commit is contained in:
@@ -62,6 +62,7 @@ func SetAPIEndpoints(
|
|||||||
server.GET("/batches/:batchNum", getBatch)
|
server.GET("/batches/:batchNum", getBatch)
|
||||||
server.GET("/full-batches/:batchNum", getFullBatch)
|
server.GET("/full-batches/:batchNum", getFullBatch)
|
||||||
server.GET("/slots", getSlots)
|
server.GET("/slots", getSlots)
|
||||||
|
server.GET("/slots/:slotNum", getSlot)
|
||||||
server.GET("/bids", getBids)
|
server.GET("/bids", getBids)
|
||||||
server.GET("/next-forgers", getNextForgers)
|
server.GET("/next-forgers", getNextForgers)
|
||||||
server.GET("/state", getState)
|
server.GET("/state", getState)
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ type testCommon struct {
|
|||||||
auths []testAuth
|
auths []testAuth
|
||||||
router *swagger.Router
|
router *swagger.Router
|
||||||
bids []testBid
|
bids []testBid
|
||||||
|
slots []testSlot
|
||||||
|
auctionVars common.AuctionVariables
|
||||||
}
|
}
|
||||||
|
|
||||||
var tc testCommon
|
var tc testCommon
|
||||||
@@ -122,6 +124,8 @@ func TestMain(m *testing.M) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
lastBlockNum := blocks[nBlocks-1].EthBlockNum
|
||||||
|
|
||||||
// Gen tokens and add them to DB
|
// Gen tokens and add them to DB
|
||||||
const nTokens = 10
|
const nTokens = 10
|
||||||
tokens, ethToken := test.GenTokens(nTokens, blocks)
|
tokens, ethToken := test.GenTokens(nTokens, blocks)
|
||||||
@@ -258,12 +262,26 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bids
|
// Bids
|
||||||
const nBids = 10
|
const nBids = 20
|
||||||
bids := test.GenBids(nBids, blocks, coords)
|
bids := test.GenBids(nBids, blocks, coords)
|
||||||
err = hdb.AddBids(bids)
|
err = hdb.AddBids(bids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
testBids := genTestBids(blocks, coordinators, bids)
|
||||||
|
|
||||||
|
// Vars
|
||||||
|
auctionVars := common.AuctionVariables{
|
||||||
|
BootCoordinator: ethCommon.HexToAddress("0x1111111111111111111111111111111111111111"),
|
||||||
|
ClosedAuctionSlots: uint16(2),
|
||||||
|
OpenAuctionSlots: uint16(5),
|
||||||
|
}
|
||||||
|
err = hdb.AddAuctionVars(&auctionVars)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nSlots = 20
|
||||||
|
|
||||||
// Set testCommon
|
// Set testCommon
|
||||||
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
|
usrTxs, allTxs := genTestTxs(sortedTxs, usrIdxs, accs, tokensUSD, blocks)
|
||||||
@@ -287,7 +305,9 @@ func TestMain(m *testing.M) {
|
|||||||
poolTxsToReceive: poolTxsToReceive,
|
poolTxsToReceive: poolTxsToReceive,
|
||||||
auths: genTestAuths(test.GenAuths(5)),
|
auths: genTestAuths(test.GenAuths(5)),
|
||||||
router: router,
|
router: router,
|
||||||
bids: genTestBids(blocks, coordinators, bids),
|
bids: testBids,
|
||||||
|
slots: genTestSlots(nSlots, lastBlockNum, testBids, auctionVars),
|
||||||
|
auctionVars: auctionVars,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fake server
|
// Fake server
|
||||||
@@ -317,15 +337,18 @@ func doGoodReqPaginated(
|
|||||||
iterStruct db.Paginationer,
|
iterStruct db.Paginationer,
|
||||||
appendIter func(res interface{}),
|
appendIter func(res interface{}),
|
||||||
) error {
|
) error {
|
||||||
next := 0
|
next := -1
|
||||||
for {
|
for {
|
||||||
// Call API to get this iteration items
|
// Call API to get this iteration items
|
||||||
iterPath := path
|
iterPath := path
|
||||||
if next == 0 && order == historydb.OrderDesc {
|
if next == -1 && order == historydb.OrderDesc {
|
||||||
// Fetch first item in reverse order
|
// Fetch first item in reverse order
|
||||||
iterPath += "99999"
|
iterPath += "99999"
|
||||||
} else {
|
} else {
|
||||||
// Fetch from next item or 0 if it's ascending order
|
// Fetch from next item or 0 if it's ascending order
|
||||||
|
if next == -1 {
|
||||||
|
next = 0
|
||||||
|
}
|
||||||
iterPath += strconv.Itoa(next)
|
iterPath += strconv.Itoa(next)
|
||||||
}
|
}
|
||||||
if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
|
if err := doGoodReq("GET", iterPath+"&order="+order, nil, iterStruct); err != nil {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func TestGetBids(t *testing.T) {
|
|||||||
// slotNum, in reverse order
|
// slotNum, in reverse order
|
||||||
fetchedBids = []testBid{}
|
fetchedBids = []testBid{}
|
||||||
path = fmt.Sprintf("%s?slotNum=%d&limit=%d&fromItem=", endpoint, slotNum, limit)
|
path = fmt.Sprintf("%s?slotNum=%d&limit=%d&fromItem=", endpoint, slotNum, limit)
|
||||||
err = doGoodReqPaginated(path, historydb.OrderAsc, &testBidsResponse{}, appendIter)
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testBidsResponse{}, appendIter)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
flippedBids := []testBid{}
|
flippedBids := []testBid{}
|
||||||
for i := len(slotNumBids) - 1; i >= 0; i-- {
|
for i := len(slotNumBids) - 1; i >= 0; i-- {
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ const (
|
|||||||
|
|
||||||
// 2^32 -1
|
// 2^32 -1
|
||||||
maxUint32 = 4294967295
|
maxUint32 = 4294967295
|
||||||
|
|
||||||
|
// 2^64 /2 -1
|
||||||
|
maxInt64 = 9223372036854775807
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -36,10 +39,6 @@ func getAccount(c *gin.Context) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSlots(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNextForgers(c *gin.Context) {
|
func getNextForgers(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ func parseQueryUint(name string, dflt *uint, min, max uint, c querier) (*uint, e
|
|||||||
return stringToUint(str, name, dflt, min, max)
|
return stringToUint(str, name, dflt, min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseQueryInt64(name string, dflt *int64, min, max int64, c querier) (*int64, error) { //nolint:SA4009 res may be not overwriten
|
||||||
|
str := c.Query(name)
|
||||||
|
return stringToInt64(str, name, dflt, min, max)
|
||||||
|
}
|
||||||
|
|
||||||
func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
|
func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolint:SA4009 res may be not overwriten
|
||||||
str := c.Query(name)
|
str := c.Query(name)
|
||||||
if str == "" {
|
if str == "" {
|
||||||
@@ -69,7 +74,7 @@ func parseQueryBool(name string, dflt *bool, c querier) (*bool, error) { //nolin
|
|||||||
*res = false
|
*res = false
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("Inavlid %s. Must be eithe true or false", name)
|
return nil, fmt.Errorf("Invalid %s. Must be eithe true or false", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
|
func parseQueryHezEthAddr(c querier) (*ethCommon.Address, error) {
|
||||||
@@ -198,8 +203,8 @@ func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) {
|
|||||||
return tokensIDs, symbols, nameStr, nil
|
return tokensIDs, symbols, nameStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBidFilters(c querier) (*uint, *ethCommon.Address, error) {
|
func parseBidFilters(c querier) (*int64, *ethCommon.Address, error) {
|
||||||
slotNum, err := parseQueryUint("slotNum", nil, 0, maxUint32, c)
|
slotNum, err := parseQueryInt64("slotNum", nil, 0, maxInt64, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -210,6 +215,26 @@ func parseBidFilters(c querier) (*uint, *ethCommon.Address, error) {
|
|||||||
return slotNum, bidderAddr, nil
|
return slotNum, bidderAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSlotFilters(c querier) (*int64, *int64, *ethCommon.Address, *bool, error) {
|
||||||
|
minSlotNum, err := parseQueryInt64("minSlotNum", nil, 0, maxInt64, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
maxSlotNum, err := parseQueryInt64("maxSlotNum", nil, 0, maxInt64, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
wonByEthereumAddress, err := parseQueryEthAddr("wonByEthereumAddress", c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
finishedAuction, err := parseQueryBool("finishedAuction", nil, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, nil, err
|
||||||
|
}
|
||||||
|
return minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Param parsers
|
// Param parsers
|
||||||
|
|
||||||
type paramer interface {
|
type paramer interface {
|
||||||
@@ -240,6 +265,11 @@ func parseParamUint(name string, dflt *uint, min, max uint, c paramer) (*uint, e
|
|||||||
return stringToUint(str, name, dflt, min, max)
|
return stringToUint(str, name, dflt, min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseParamInt64(name string, dflt *int64, min, max int64, c paramer) (*int64, error) { //nolint:SA4009 res may be not overwriten
|
||||||
|
str := c.Param(name)
|
||||||
|
return stringToInt64(str, name, dflt, min, max)
|
||||||
|
}
|
||||||
|
|
||||||
func stringToIdx(idxStr, name string) (*common.Idx, error) {
|
func stringToIdx(idxStr, name string) (*common.Idx, error) {
|
||||||
if idxStr == "" {
|
if idxStr == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -261,7 +291,7 @@ func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error
|
|||||||
resInt, err := strconv.Atoi(uintStr)
|
resInt, err := strconv.Atoi(uintStr)
|
||||||
if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
|
if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Inavlid %s. Must be an integer within the range [%d, %d]",
|
"Invalid %s. Must be an integer within the range [%d, %d]",
|
||||||
name, min, max)
|
name, min, max)
|
||||||
}
|
}
|
||||||
res := uint(resInt)
|
res := uint(resInt)
|
||||||
@@ -270,6 +300,20 @@ func stringToUint(uintStr, name string, dflt *uint, min, max uint) (*uint, error
|
|||||||
return dflt, nil
|
return dflt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stringToInt64(uintStr, name string, dflt *int64, min, max int64) (*int64, error) {
|
||||||
|
if uintStr != "" {
|
||||||
|
resInt, err := strconv.Atoi(uintStr)
|
||||||
|
if err != nil || resInt < 0 || resInt < int(min) || resInt > int(max) {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Invalid %s. Must be an integer within the range [%d, %d]",
|
||||||
|
name, min, max)
|
||||||
|
}
|
||||||
|
res := int64(resInt)
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
return dflt, nil
|
||||||
|
}
|
||||||
|
|
||||||
func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
|
func hezStringToEthAddr(addrStr, name string) (*ethCommon.Address, error) {
|
||||||
if addrStr == "" {
|
if addrStr == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|||||||
346
api/slots.go
Normal file
346
api/slots.go
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db"
|
||||||
|
"github.com/hermeznetwork/hermez-node/db/historydb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SlotAPI is a repesentation of a slot information
|
||||||
|
type SlotAPI struct {
|
||||||
|
ItemID int `json:"itemId"`
|
||||||
|
SlotNum int64 `json:"slotNum"`
|
||||||
|
FirstBlock int64 `json:"firstBlock"`
|
||||||
|
LastBlock int64 `json:"lastBlock"`
|
||||||
|
OpenAuction bool `json:"openAuction"`
|
||||||
|
WinnerBid *historydb.BidAPI `json:"winnerBid"`
|
||||||
|
TotalItems int `json:"-"`
|
||||||
|
FirstItem int `json:"-"`
|
||||||
|
LastItem int `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFirstLastBlock(slotNum int64) (int64, int64) {
|
||||||
|
genesisBlock := cg.AuctionConstants.GenesisBlockNum
|
||||||
|
blocksPerSlot := int64(cg.AuctionConstants.BlocksPerSlot)
|
||||||
|
firstBlock := slotNum*blocksPerSlot + genesisBlock
|
||||||
|
lastBlock := (slotNum+1)*blocksPerSlot + genesisBlock - 1
|
||||||
|
return firstBlock, lastBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentSlot(currentBlock int64) int64 {
|
||||||
|
genesisBlock := cg.AuctionConstants.GenesisBlockNum
|
||||||
|
blocksPerSlot := int64(cg.AuctionConstants.BlocksPerSlot)
|
||||||
|
currentSlot := (currentBlock - genesisBlock) / blocksPerSlot
|
||||||
|
return currentSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOpenAuction(currentBlock, slotNum int64, auctionVars common.AuctionVariables) bool {
|
||||||
|
currentSlot := getCurrentSlot(currentBlock)
|
||||||
|
closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots)
|
||||||
|
openAuctionSlots := int64(auctionVars.OpenAuctionSlots)
|
||||||
|
if slotNum > closedAuctionSlots && slotNum <= (closedAuctionSlots+openAuctionSlots) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPagination(totalItems int, minSlotNum, maxSlotNum *int64) *db.Pagination {
|
||||||
|
// itemID is slotNum
|
||||||
|
firstItem := *minSlotNum
|
||||||
|
lastItem := *maxSlotNum
|
||||||
|
pagination := &db.Pagination{
|
||||||
|
TotalItems: int(totalItems),
|
||||||
|
FirstItem: int(firstItem),
|
||||||
|
LastItem: int(lastItem),
|
||||||
|
}
|
||||||
|
return pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSlotAPI(slotNum, currentBlockNum int64, bid *historydb.BidAPI, auctionVars *common.AuctionVariables) SlotAPI {
|
||||||
|
firstBlock, lastBlock := getFirstLastBlock(slotNum)
|
||||||
|
openAuction := isOpenAuction(currentBlockNum, slotNum, *auctionVars)
|
||||||
|
slot := SlotAPI{
|
||||||
|
ItemID: int(slotNum),
|
||||||
|
SlotNum: slotNum,
|
||||||
|
FirstBlock: firstBlock,
|
||||||
|
LastBlock: lastBlock,
|
||||||
|
OpenAuction: openAuction,
|
||||||
|
WinnerBid: bid,
|
||||||
|
}
|
||||||
|
return slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSlotsAPIFromWinnerBids(fromItem *uint, order string, bids []historydb.BidAPI, currentBlockNum int64, auctionVars *common.AuctionVariables) (slots []SlotAPI) {
|
||||||
|
for i := range bids {
|
||||||
|
slotNum := bids[i].SlotNum
|
||||||
|
slot := newSlotAPI(slotNum, currentBlockNum, &bids[i], auctionVars)
|
||||||
|
if order == historydb.OrderAsc {
|
||||||
|
if slot.ItemID >= int(*fromItem) {
|
||||||
|
slots = append(slots, slot)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if slot.ItemID <= int(*fromItem) {
|
||||||
|
slots = append(slots, slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slots
|
||||||
|
}
|
||||||
|
|
||||||
|
func addEmptySlot(slots []SlotAPI, slotNum int64, currentBlockNum int64, auctionVars *common.AuctionVariables, fromItem *uint, order string) ([]SlotAPI, error) {
|
||||||
|
emptySlot := newSlotAPI(slotNum, currentBlockNum, nil, auctionVars)
|
||||||
|
if order == historydb.OrderAsc {
|
||||||
|
if emptySlot.ItemID >= int(*fromItem) {
|
||||||
|
slots = append(slots, emptySlot)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if emptySlot.ItemID <= int(*fromItem) {
|
||||||
|
slots = append([]SlotAPI{emptySlot}, slots...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSlot(c *gin.Context) {
|
||||||
|
slotNumUint, err := parseParamUint("slotNum", nil, 0, maxUint32, c)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentBlock, err := h.GetLastBlock()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auctionVars, err := h.GetAuctionVars()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slotNum := int64(*slotNumUint)
|
||||||
|
bid, err := h.GetBestBidAPI(&slotNum)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var slot SlotAPI
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
slot = newSlotAPI(slotNum, currentBlock.EthBlockNum, nil, auctionVars)
|
||||||
|
} else {
|
||||||
|
slot = newSlotAPI(bid.SlotNum, currentBlock.EthBlockNum, &bid, auctionVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON response
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLim, maxLim
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLimitsWithAddr(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) {
|
||||||
|
var minLim, maxLim int64
|
||||||
|
if fromItem != nil {
|
||||||
|
if order == historydb.OrderAsc {
|
||||||
|
maxLim = *maxSlotNum
|
||||||
|
if int64(*fromItem) > *minSlotNum {
|
||||||
|
minLim = int64(*fromItem)
|
||||||
|
} else {
|
||||||
|
minLim = *minSlotNum
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minLim = *minSlotNum
|
||||||
|
if int64(*fromItem) < *maxSlotNum {
|
||||||
|
maxLim = int64(*fromItem)
|
||||||
|
} else {
|
||||||
|
maxLim = *maxSlotNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLim, maxLim
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSlots(c *gin.Context) {
|
||||||
|
var slots []SlotAPI
|
||||||
|
minSlotNumDflt := int64(0)
|
||||||
|
|
||||||
|
// Get filters
|
||||||
|
minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, err := parseSlotFilters(c)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
fromItem, order, limit, err := parsePagination(c)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlock, err := h.GetLastBlock()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auctionVars, err := h.GetAuctionVars()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check filters
|
||||||
|
if maxSlotNum == nil && finishedAuction == nil {
|
||||||
|
retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
|
||||||
|
return
|
||||||
|
} else if finishedAuction != nil {
|
||||||
|
if maxSlotNum == nil && !*finishedAuction {
|
||||||
|
retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
|
||||||
|
return
|
||||||
|
} else if *finishedAuction {
|
||||||
|
currentBlock, err := h.GetLastBlock()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentSlot := getCurrentSlot(currentBlock.EthBlockNum)
|
||||||
|
auctionVars, err := h.GetAuctionVars()
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots)
|
||||||
|
if maxSlotNum == nil {
|
||||||
|
maxSlotNum = &closedAuctionSlots
|
||||||
|
} else if closedAuctionSlots < *maxSlotNum {
|
||||||
|
maxSlotNum = &closedAuctionSlots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if maxSlotNum != nil && minSlotNum != nil {
|
||||||
|
if *minSlotNum > *maxSlotNum {
|
||||||
|
retBadReq(errors.New("It is necessary to add valid filter (minSlotNum <= maxSlotNum)"), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if minSlotNum == nil {
|
||||||
|
minSlotNum = &minSlotNumDflt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get bids and pagination according to filters
|
||||||
|
var slotMinLim, slotMaxLim int64
|
||||||
|
var bids []historydb.BidAPI
|
||||||
|
var pag *db.Pagination
|
||||||
|
totalItems := 0
|
||||||
|
if wonByEthereumAddress == nil {
|
||||||
|
slotMinLim, slotMaxLim = getLimits(minSlotNum, maxSlotNum, fromItem, limit, order)
|
||||||
|
// Get best bids in range maxSlotNum - minSlotNum
|
||||||
|
bids, _, err = h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, nil, order)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
totalItems = int(*maxSlotNum) - int(*minSlotNum) + 1
|
||||||
|
} else {
|
||||||
|
slotMinLim, slotMaxLim = getLimitsWithAddr(minSlotNum, maxSlotNum, fromItem, limit, order)
|
||||||
|
bids, pag, err = h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
retSQLErr(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(bids) > 0 {
|
||||||
|
totalItems = pag.TotalItems
|
||||||
|
*maxSlotNum = int64(pag.LastItem)
|
||||||
|
*minSlotNum = int64(pag.FirstItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the slot information with previous bids
|
||||||
|
var slotsBids []SlotAPI
|
||||||
|
if len(bids) > 0 {
|
||||||
|
slotsBids = newSlotsAPIFromWinnerBids(fromItem, order, bids, currentBlock.EthBlockNum, auctionVars)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the other slots
|
||||||
|
if wonByEthereumAddress == nil {
|
||||||
|
// Build hte information of the slots with bids or not
|
||||||
|
for i := slotMinLim; i <= slotMaxLim; i++ {
|
||||||
|
found := false
|
||||||
|
for j := range slotsBids {
|
||||||
|
if slotsBids[j].SlotNum == i {
|
||||||
|
found = true
|
||||||
|
if order == historydb.OrderAsc {
|
||||||
|
if slotsBids[j].ItemID >= int(*fromItem) {
|
||||||
|
slots = append(slots, slotsBids[j])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if slotsBids[j].ItemID <= int(*fromItem) {
|
||||||
|
slots = append([]SlotAPI{slotsBids[j]}, slots...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
slots, err = addEmptySlot(slots, i, currentBlock.EthBlockNum, auctionVars, fromItem, order)
|
||||||
|
if err != nil {
|
||||||
|
retBadReq(err, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(slotsBids) > 0 {
|
||||||
|
slots = slotsBids
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(slots) == 0 {
|
||||||
|
retSQLErr(sql.ErrNoRows, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build succesfull response
|
||||||
|
type slotsResponse struct {
|
||||||
|
Slots []SlotAPI `json:"slots"`
|
||||||
|
Pagination *db.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, &slotsResponse{
|
||||||
|
Slots: slots,
|
||||||
|
Pagination: getPagination(totalItems, minSlotNum, maxSlotNum),
|
||||||
|
})
|
||||||
|
}
|
||||||
305
api/slots_test.go
Normal file
305
api/slots_test.go
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"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"`
|
||||||
|
SlotNum int64 `json:"slotNum"`
|
||||||
|
FirstBlock int64 `json:"firstBlock"`
|
||||||
|
LastBlock int64 `json:"lastBlock"`
|
||||||
|
OpenAuction bool `json:"openAuction"`
|
||||||
|
WinnerBid *testBid `json:"winnerBid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSlotsResponse struct {
|
||||||
|
Slots []testSlot `json:"slots"`
|
||||||
|
Pagination *db.Pagination `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSlotsResponse) GetPagination() *db.Pagination {
|
||||||
|
if t.Slots[0].ItemID < t.Slots[len(t.Slots)-1].ItemID {
|
||||||
|
t.Pagination.FirstReturnedItem = int(t.Slots[0].ItemID)
|
||||||
|
t.Pagination.LastReturnedItem = int(t.Slots[len(t.Slots)-1].ItemID)
|
||||||
|
} else {
|
||||||
|
t.Pagination.LastReturnedItem = int(t.Slots[0].ItemID)
|
||||||
|
t.Pagination.FirstReturnedItem = int(t.Slots[len(t.Slots)-1].ItemID)
|
||||||
|
}
|
||||||
|
return t.Pagination
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSlotsResponse) Len() int {
|
||||||
|
return len(t.Slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTestSlots(nSlots int, lastBlockNum int64, bids []testBid, auctionVars common.AuctionVariables) []testSlot {
|
||||||
|
tSlots := []testSlot{}
|
||||||
|
bestBids := make(map[int64]testBid)
|
||||||
|
// It's assumed that bids for each slot will be received in increasing order
|
||||||
|
for i := range bids {
|
||||||
|
bestBids[bids[i].SlotNum] = bids[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := int64(0); i < int64(nSlots); i++ {
|
||||||
|
bid, ok := bestBids[i]
|
||||||
|
firstBlock, lastBlock := getFirstLastBlock(int64(i))
|
||||||
|
tSlot := testSlot{
|
||||||
|
SlotNum: int64(i),
|
||||||
|
FirstBlock: firstBlock,
|
||||||
|
LastBlock: lastBlock,
|
||||||
|
OpenAuction: isOpenAuction(lastBlockNum, int64(i), auctionVars),
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
tSlot.WinnerBid = &bid
|
||||||
|
}
|
||||||
|
tSlots = append(tSlots, tSlot)
|
||||||
|
}
|
||||||
|
return tSlots
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEmptyTestSlot(slotNum int64) testSlot {
|
||||||
|
firstBlock, lastBlock := getFirstLastBlock(slotNum)
|
||||||
|
slot := testSlot{
|
||||||
|
SlotNum: slotNum,
|
||||||
|
FirstBlock: firstBlock,
|
||||||
|
LastBlock: lastBlock,
|
||||||
|
OpenAuction: false,
|
||||||
|
WinnerBid: nil,
|
||||||
|
}
|
||||||
|
return slot
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSlot(t *testing.T) {
|
||||||
|
endpoint := apiURL + "slots/"
|
||||||
|
for _, slot := range tc.slots {
|
||||||
|
fetchedSlot := testSlot{}
|
||||||
|
assert.NoError(
|
||||||
|
t, doGoodReq(
|
||||||
|
"GET",
|
||||||
|
endpoint+strconv.Itoa(int(slot.SlotNum)),
|
||||||
|
nil, &fetchedSlot,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertSlot(t, slot, fetchedSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot with WinnerBid == nil
|
||||||
|
slotNum := int64(15)
|
||||||
|
fetchedSlot := testSlot{}
|
||||||
|
assert.NoError(
|
||||||
|
t, doGoodReq(
|
||||||
|
"GET",
|
||||||
|
endpoint+strconv.Itoa(int(slotNum)),
|
||||||
|
nil, &fetchedSlot,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
emptySlot := getEmptyTestSlot(slotNum)
|
||||||
|
assertSlot(t, emptySlot, fetchedSlot)
|
||||||
|
|
||||||
|
// Invalid slotNum
|
||||||
|
path := endpoint + strconv.Itoa(-2)
|
||||||
|
err := doBadReq("GET", path, nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetSlots(t *testing.T) {
|
||||||
|
endpoint := apiURL + "slots"
|
||||||
|
fetchedSlots := []testSlot{}
|
||||||
|
appendIter := func(intr interface{}) {
|
||||||
|
for i := 0; i < len(intr.(*testSlotsResponse).Slots); i++ {
|
||||||
|
tmp, err := copystructure.Copy(intr.(*testSlotsResponse).Slots[i])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fetchedSlots = append(fetchedSlots, tmp.(testSlot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All slots with maxSlotNum filter
|
||||||
|
maxSlotNum := tc.slots[len(tc.slots)-1].SlotNum + 5
|
||||||
|
limit := 1
|
||||||
|
path := fmt.Sprintf("%s?maxSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, limit)
|
||||||
|
err := doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
allSlots := tc.slots
|
||||||
|
for i := tc.slots[len(tc.slots)-1].SlotNum; i < maxSlotNum; i++ {
|
||||||
|
emptySlot := getEmptyTestSlot(i + 1)
|
||||||
|
allSlots = append(allSlots, emptySlot)
|
||||||
|
}
|
||||||
|
assertSlots(t, allSlots, fetchedSlots)
|
||||||
|
|
||||||
|
// All slots with maxSlotNum filter, in reverse order
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
limit = 3
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
flippedAllSlots := []testSlot{}
|
||||||
|
for i := len(allSlots) - 1; i >= 0; i-- {
|
||||||
|
flippedAllSlots = append(flippedAllSlots, allSlots[i])
|
||||||
|
}
|
||||||
|
assertSlots(t, flippedAllSlots, fetchedSlots)
|
||||||
|
|
||||||
|
// maxSlotNum & wonByEthereumAddress
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
limit = 1
|
||||||
|
bidderAddr := tc.coordinators[2].Bidder
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, bidderAddr.String(), limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
bidderAddressSlots := []testSlot{}
|
||||||
|
for i := 0; i < len(tc.slots); i++ {
|
||||||
|
if tc.slots[i].WinnerBid != nil {
|
||||||
|
if tc.slots[i].WinnerBid.Bidder == bidderAddr {
|
||||||
|
bidderAddressSlots = append(bidderAddressSlots, tc.slots[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, bidderAddressSlots, fetchedSlots)
|
||||||
|
|
||||||
|
// maxSlotNum & wonByEthereumAddress, in reverse order
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
limit = 1
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, bidderAddr.String(), limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
flippedBidderAddressSlots := []testSlot{}
|
||||||
|
for i := len(bidderAddressSlots) - 1; i >= 0; i-- {
|
||||||
|
flippedBidderAddressSlots = append(flippedBidderAddressSlots, bidderAddressSlots[i])
|
||||||
|
}
|
||||||
|
assertSlots(t, flippedBidderAddressSlots, fetchedSlots)
|
||||||
|
|
||||||
|
// finishedAuction
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
limit = 15
|
||||||
|
path = fmt.Sprintf("%s?finishedAuction=%t&limit=%d&fromItem=", endpoint, true, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
currentSlot := getCurrentSlot(tc.blocks[len(tc.blocks)-1].EthBlockNum)
|
||||||
|
finishedAuctionSlots := []testSlot{}
|
||||||
|
for i := 0; i < len(tc.slots); i++ {
|
||||||
|
finishAuction := currentSlot + int64(tc.auctionVars.ClosedAuctionSlots)
|
||||||
|
if tc.slots[i].SlotNum <= finishAuction {
|
||||||
|
finishedAuctionSlots = append(finishedAuctionSlots, tc.slots[i])
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, finishedAuctionSlots, fetchedSlots)
|
||||||
|
|
||||||
|
//minSlot + maxSlot
|
||||||
|
limit = 10
|
||||||
|
minSlotNum := tc.slots[3].SlotNum
|
||||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum - 1
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
minMaxBatchNumSlots := []testSlot{}
|
||||||
|
for i := 0; i < len(tc.slots); i++ {
|
||||||
|
if tc.slots[i].SlotNum >= minSlotNum && tc.slots[i].SlotNum <= maxSlotNum {
|
||||||
|
minMaxBatchNumSlots = append(minMaxBatchNumSlots, tc.slots[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, minMaxBatchNumSlots, fetchedSlots)
|
||||||
|
|
||||||
|
//minSlot + maxSlot
|
||||||
|
limit = 15
|
||||||
|
minSlotNum = tc.slots[0].SlotNum
|
||||||
|
maxSlotNum = tc.slots[0].SlotNum
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
minMaxBatchNumSlots = []testSlot{}
|
||||||
|
for i := 0; i < len(tc.slots); i++ {
|
||||||
|
if tc.slots[i].SlotNum >= minSlotNum && tc.slots[i].SlotNum <= maxSlotNum {
|
||||||
|
minMaxBatchNumSlots = append(minMaxBatchNumSlots, tc.slots[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, minMaxBatchNumSlots, fetchedSlots)
|
||||||
|
|
||||||
|
// Only empty Slots
|
||||||
|
limit = 2
|
||||||
|
minSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 1
|
||||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 5
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderAsc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
emptySlots := []testSlot{}
|
||||||
|
for i := 0; i < len(allSlots); i++ {
|
||||||
|
if allSlots[i].SlotNum >= minSlotNum && allSlots[i].SlotNum <= maxSlotNum {
|
||||||
|
emptySlots = append(emptySlots, allSlots[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, emptySlots, fetchedSlots)
|
||||||
|
|
||||||
|
// Only empty Slots, in reverse order
|
||||||
|
limit = 4
|
||||||
|
minSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 1
|
||||||
|
maxSlotNum = tc.slots[len(tc.slots)-1].SlotNum + 5
|
||||||
|
fetchedSlots = []testSlot{}
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit)
|
||||||
|
err = doGoodReqPaginated(path, historydb.OrderDesc, &testSlotsResponse{}, appendIter)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
flippedEmptySlots := []testSlot{}
|
||||||
|
for i := 0; i < len(flippedAllSlots); i++ {
|
||||||
|
if flippedAllSlots[i].SlotNum >= minSlotNum && flippedAllSlots[i].SlotNum <= maxSlotNum {
|
||||||
|
flippedEmptySlots = append(flippedEmptySlots, flippedAllSlots[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertSlots(t, flippedEmptySlots, fetchedSlots)
|
||||||
|
|
||||||
|
// 400
|
||||||
|
// No filters
|
||||||
|
path = fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
|
||||||
|
err = doBadReq("GET", path, nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Invalid maxSlotNum
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d", endpoint, -2)
|
||||||
|
err = doBadReq("GET", path, nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Invalid wonByEthereumAddress
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s", endpoint, maxSlotNum, "0xG0000001")
|
||||||
|
err = doBadReq("GET", path, nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// Invalid minSlotNum / maxSlotNum (minSlotNum > maxSlotNum)
|
||||||
|
maxSlotNum = tc.slots[1].SlotNum
|
||||||
|
minSlotNum = tc.slots[4].SlotNum
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&minSlotNum=%d&limit=%d&fromItem=", endpoint, maxSlotNum, minSlotNum, limit)
|
||||||
|
err = doBadReq("GET", path, nil, 400)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// 404
|
||||||
|
maxSlotNum = tc.slots[1].SlotNum
|
||||||
|
path = fmt.Sprintf("%s?maxSlotNum=%d&wonByEthereumAddress=%s&limit=%d&fromItem=", endpoint, maxSlotNum, tc.coordinators[3].Bidder.String(), limit)
|
||||||
|
err = doBadReq("GET", path, nil, 404)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSlots(t *testing.T, expected, actual []testSlot) {
|
||||||
|
assert.Equal(t, len(expected), len(actual))
|
||||||
|
for i := 0; i < len(expected); i++ {
|
||||||
|
assertSlot(t, expected[i], actual[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSlot(t *testing.T, expected, actual testSlot) {
|
||||||
|
if actual.WinnerBid != nil {
|
||||||
|
assert.Equal(t, expected.WinnerBid.Timestamp.Unix(), actual.WinnerBid.Timestamp.Unix())
|
||||||
|
expected.WinnerBid.Timestamp = actual.WinnerBid.Timestamp
|
||||||
|
actual.WinnerBid.ItemID = expected.WinnerBid.ItemID
|
||||||
|
}
|
||||||
|
actual.ItemID = expected.ItemID
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
@@ -774,19 +774,19 @@ paths:
|
|||||||
- name: minSlotNum
|
- name: minSlotNum
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: Only include batches with `slotNum < minSlotNum`.
|
description: Only include slots with `slotNum >= minSlotNum`. By default, `minSlotNum = 0`.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/SlotNum'
|
$ref: '#/components/schemas/SlotNum'
|
||||||
- name: maxSlothNum
|
- name: maxSlothNum
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: Only include batches with `slotNum > maxSlotNum`.
|
description: Only include slots with `slotNum <= maxSlotNum`.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/SlotNum'
|
$ref: '#/components/schemas/SlotNum'
|
||||||
- name: wonByEthereumAddress
|
- name: wonByEthereumAddress
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
description: Only include slots won by a coordinator whose `forgerAddr == wonByEthereumAddress`.
|
description: Only include slots won by a coordinator whose `bidderAddr == wonByEthereumAddress`.
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/EthereumAddress'
|
$ref: '#/components/schemas/EthereumAddress'
|
||||||
- name: finishedAuction
|
- name: finishedAuction
|
||||||
@@ -2171,6 +2171,8 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
description: Slot information.
|
description: Slot information.
|
||||||
properties:
|
properties:
|
||||||
|
itemId:
|
||||||
|
$ref: '#/components/schemas/ItemId'
|
||||||
slotNum:
|
slotNum:
|
||||||
$ref: '#/components/schemas/SlotNum'
|
$ref: '#/components/schemas/SlotNum'
|
||||||
firstBlock:
|
firstBlock:
|
||||||
@@ -2183,25 +2185,67 @@ components:
|
|||||||
- $ref: '#/components/schemas/EthBlockNum'
|
- $ref: '#/components/schemas/EthBlockNum'
|
||||||
- description: Block in which the slot ended or will end
|
- description: Block in which the slot ended or will end
|
||||||
- example: 4475934
|
- example: 4475934
|
||||||
closedAuction:
|
openAuction:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether the auction of the slot has finished or not.
|
description: Whether the auction of the slot is open or not.
|
||||||
winner:
|
winnerBid:
|
||||||
allOf:
|
type: object
|
||||||
- $ref: '#/components/schemas/Coordinator'
|
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: Coordinator who won the auction. Only applicable if the auction is closed.
|
nullable: true
|
||||||
- nullable: true
|
properties:
|
||||||
- example: null
|
itemId:
|
||||||
|
$ref: '#/components/schemas/ItemId'
|
||||||
|
bidderAddr:
|
||||||
|
$ref: '#/components/schemas/EthereumAddress'
|
||||||
|
forgerAddr:
|
||||||
|
$ref: '#/components/schemas/EthereumAddress'
|
||||||
|
slotNum:
|
||||||
|
$ref: '#/components/schemas/SlotNum'
|
||||||
|
URL:
|
||||||
|
$ref: '#/components/schemas/URL'
|
||||||
|
bidValue:
|
||||||
|
type: string
|
||||||
|
description: BigInt is an integer encoded as a string for numbers that are very large.
|
||||||
|
example: "8708856933496328593"
|
||||||
|
pattern: "^\\d+$"
|
||||||
|
ethereumBlockNum:
|
||||||
|
$ref: '#/components/schemas/EthBlockNum'
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
additionalProperties: false
|
||||||
|
require:
|
||||||
|
- bidderAddr
|
||||||
|
- forgerAddr
|
||||||
|
- slotNum
|
||||||
|
- URL
|
||||||
|
- bidValue
|
||||||
|
- ethereumBlockNum
|
||||||
|
- timestamp
|
||||||
|
additionalProperties: false
|
||||||
|
require:
|
||||||
|
- slotNum
|
||||||
|
- firstBlock
|
||||||
|
- lastBlock
|
||||||
|
- openAuction
|
||||||
|
- winner
|
||||||
|
- winnerBid
|
||||||
Slots:
|
Slots:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
nextForgers:
|
slots:
|
||||||
type: array
|
type: array
|
||||||
description: List of slots.
|
description: List of slots.
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Slot'
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Slot'
|
||||||
|
- description: Last synchronized Etherum block.
|
||||||
pagination:
|
pagination:
|
||||||
$ref: '#/components/schemas/PaginationInfo'
|
$ref: '#/components/schemas/PaginationInfo'
|
||||||
|
additionalProperties: false
|
||||||
|
require:
|
||||||
|
- slots
|
||||||
|
- pagination
|
||||||
NextForger:
|
NextForger:
|
||||||
type: object
|
type: object
|
||||||
description: Coordinator information along with the scheduled forging period
|
description: Coordinator information along with the scheduled forging period
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
ethCommon "github.com/ethereum/go-ethereum/common"
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/hermeznetwork/hermez-node/common"
|
"github.com/hermeznetwork/hermez-node/common"
|
||||||
"github.com/hermeznetwork/hermez-node/db"
|
"github.com/hermeznetwork/hermez-node/db"
|
||||||
"github.com/hermeznetwork/hermez-node/log"
|
|
||||||
"github.com/iden3/go-iden3-crypto/babyjub"
|
"github.com/iden3/go-iden3-crypto/babyjub"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
@@ -257,7 +256,7 @@ func (hdb *HistoryDB) GetBatchesAPI(
|
|||||||
}
|
}
|
||||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||||
query = hdb.db.Rebind(queryStr)
|
query = hdb.db.Rebind(queryStr)
|
||||||
log.Debug(query)
|
// log.Debug(query)
|
||||||
batchPtrs := []*BatchAPI{}
|
batchPtrs := []*BatchAPI{}
|
||||||
if err := meddler.QueryAll(hdb.db, &batchPtrs, query, args...); err != nil {
|
if err := meddler.QueryAll(hdb.db, &batchPtrs, query, args...); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -355,8 +354,67 @@ func (hdb *HistoryDB) GetAllBids() ([]common.Bid, error) {
|
|||||||
return db.SlicePtrsToSlice(bids).([]common.Bid), err
|
return db.SlicePtrsToSlice(bids).([]common.Bid), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBestBidAPI returns the best bid in specific slot by slotNum
|
||||||
|
func (hdb *HistoryDB) GetBestBidAPI(slotNum *int64) (BidAPI, error) {
|
||||||
|
bid := &BidAPI{}
|
||||||
|
err := meddler.QueryRow(
|
||||||
|
hdb.db, bid, `SELECT bid.*, block.timestamp, coordinator.forger_addr, coordinator.url
|
||||||
|
FROM bid INNER JOIN block ON bid.eth_block_num = block.eth_block_num
|
||||||
|
INNER JOIN coordinator ON bid.bidder_addr = coordinator.bidder_addr
|
||||||
|
WHERE slot_num = $1 ORDER BY item_id DESC LIMIT 1;`, slotNum,
|
||||||
|
)
|
||||||
|
return *bid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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 (
|
||||||
|
SELECT slot_num, MAX(item_id) as maxitem
|
||||||
|
FROM bid GROUP BY slot_num
|
||||||
|
)
|
||||||
|
AS x INNER JOIN bid AS b ON b.item_id = x.maxitem
|
||||||
|
INNER JOIN block ON b.eth_block_num = block.eth_block_num
|
||||||
|
INNER JOIN coordinator ON b.bidder_addr = coordinator.bidder_addr
|
||||||
|
WHERE (b.slot_num >= ? AND b.slot_num <= ?)`
|
||||||
|
args = append(args, minSlotNum)
|
||||||
|
args = append(args, maxSlotNum)
|
||||||
|
// Apply filters
|
||||||
|
if bidderAddr != nil {
|
||||||
|
queryStr += " AND b.bidder_addr = ? "
|
||||||
|
args = append(args, bidderAddr)
|
||||||
|
}
|
||||||
|
queryStr += " ORDER BY b.slot_num "
|
||||||
|
if order == OrderAsc {
|
||||||
|
queryStr += "ASC "
|
||||||
|
} else {
|
||||||
|
queryStr += "DESC "
|
||||||
|
}
|
||||||
|
if limit != nil {
|
||||||
|
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||||
|
}
|
||||||
|
query = hdb.db.Rebind(queryStr)
|
||||||
|
bidPtrs := []*BidAPI{}
|
||||||
|
if err := meddler.QueryAll(hdb.db, &bidPtrs, query, args...); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// log.Debug(query)
|
||||||
|
bids := db.SlicePtrsToSlice(bidPtrs).([]BidAPI)
|
||||||
|
if len(bids) == 0 {
|
||||||
|
return nil, nil, sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return bids, &db.Pagination{
|
||||||
|
TotalItems: bids[0].TotalItems,
|
||||||
|
FirstItem: bids[0].FirstItem,
|
||||||
|
LastItem: bids[0].LastItem,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBidsAPI return the bids applying the given filters
|
// GetBidsAPI return the bids applying the given filters
|
||||||
func (hdb *HistoryDB) GetBidsAPI(slotNum *uint, 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, *db.Pagination, error) {
|
||||||
var query string
|
var query string
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
queryStr := `SELECT bid.*, block.timestamp, coordinator.forger_addr, coordinator.url,
|
queryStr := `SELECT bid.*, block.timestamp, coordinator.forger_addr, coordinator.url,
|
||||||
@@ -863,7 +921,7 @@ func (hdb *HistoryDB) GetHistoryTxs(
|
|||||||
}
|
}
|
||||||
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
|
||||||
query = hdb.db.Rebind(queryStr)
|
query = hdb.db.Rebind(queryStr)
|
||||||
log.Debug(query)
|
// log.Debug(query)
|
||||||
txsPtrs := []*TxAPI{}
|
txsPtrs := []*TxAPI{}
|
||||||
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -1320,3 +1378,17 @@ func (hdb *HistoryDB) GetCoordinatorsAPI(fromItem, limit *uint, order string) ([
|
|||||||
LastItem: coordinators[0].LastItem,
|
LastItem: coordinators[0].LastItem,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAuctionVars insert auction vars into the DB
|
||||||
|
func (hdb *HistoryDB) AddAuctionVars(auctionVars *common.AuctionVariables) error {
|
||||||
|
return meddler.Insert(hdb.db, "auction_vars", auctionVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuctionVars returns auction variables
|
||||||
|
func (hdb *HistoryDB) GetAuctionVars() (*common.AuctionVariables, error) {
|
||||||
|
auctionVars := &common.AuctionVariables{}
|
||||||
|
err := meddler.QueryRow(
|
||||||
|
hdb.db, auctionVars, `SELECT * FROM auction_vars;`,
|
||||||
|
)
|
||||||
|
return auctionVars, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -329,9 +329,15 @@ func GenCoordinators(nCoords int, blocks []common.Block) []common.Coordinator {
|
|||||||
// GenBids generates bids. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol.
|
// GenBids generates bids. WARNING: This is meant for DB/API testing, and may not be fully consistent with the protocol.
|
||||||
func GenBids(nBids int, blocks []common.Block, coords []common.Coordinator) []common.Bid {
|
func GenBids(nBids int, blocks []common.Block, coords []common.Coordinator) []common.Bid {
|
||||||
bids := []common.Bid{}
|
bids := []common.Bid{}
|
||||||
for i := 0; i < nBids; i++ {
|
for i := 0; i < nBids*2; i = i + 2 { //nolint:gomnd
|
||||||
|
var slotNum int64
|
||||||
|
if i < nBids {
|
||||||
|
slotNum = int64(i)
|
||||||
|
} else {
|
||||||
|
slotNum = int64(i - nBids)
|
||||||
|
}
|
||||||
bids = append(bids, common.Bid{
|
bids = append(bids, common.Bid{
|
||||||
SlotNum: int64(i),
|
SlotNum: slotNum,
|
||||||
BidValue: big.NewInt(int64(i)),
|
BidValue: big.NewInt(int64(i)),
|
||||||
EthBlockNum: blocks[i%len(blocks)].EthBlockNum,
|
EthBlockNum: blocks[i%len(blocks)].EthBlockNum,
|
||||||
Bidder: coords[i%len(blocks)].Bidder,
|
Bidder: coords[i%len(blocks)].Bidder,
|
||||||
|
|||||||
Reference in New Issue
Block a user