arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/slots.go
api/slots.goRaw
  1package api
  2
  3import (
  4	"database/sql"
  5	"errors"
  6	"net/http"
  7
  8	"github.com/gin-gonic/gin"
  9	"github.com/hermeznetwork/hermez-node/common"
 10	"github.com/hermeznetwork/hermez-node/db/historydb"
 11)
 12
 13// SlotAPI is a repesentation of a slot information
 14type SlotAPI struct {
 15	ItemID      uint64            `json:"itemId"`
 16	SlotNum     int64             `json:"slotNum"`
 17	FirstBlock  int64             `json:"firstBlock"`
 18	LastBlock   int64             `json:"lastBlock"`
 19	OpenAuction bool              `json:"openAuction"`
 20	WinnerBid   *historydb.BidAPI `json:"winnerBid"`
 21	TotalItems  uint64            `json:"-"`
 22	FirstItem   uint64            `json:"-"`
 23	LastItem    uint64            `json:"-"`
 24}
 25
 26func (a *API) getFirstLastBlock(slotNum int64) (int64, int64) {
 27	genesisBlock := a.cg.AuctionConstants.GenesisBlockNum
 28	blocksPerSlot := int64(a.cg.AuctionConstants.BlocksPerSlot)
 29	firstBlock := slotNum*blocksPerSlot + genesisBlock
 30	lastBlock := (slotNum+1)*blocksPerSlot + genesisBlock - 1
 31	return firstBlock, lastBlock
 32}
 33
 34func (a *API) getCurrentSlot(currentBlock int64) int64 {
 35	genesisBlock := a.cg.AuctionConstants.GenesisBlockNum
 36	blocksPerSlot := int64(a.cg.AuctionConstants.BlocksPerSlot)
 37	currentSlot := (currentBlock - genesisBlock) / blocksPerSlot
 38	return currentSlot
 39}
 40
 41func (a *API) isOpenAuction(currentBlock, slotNum int64, auctionVars common.AuctionVariables) bool {
 42	currentSlot := a.getCurrentSlot(currentBlock)
 43	closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots)
 44	openAuctionSlots := int64(auctionVars.OpenAuctionSlots)
 45	if slotNum > closedAuctionSlots && slotNum <= (closedAuctionSlots+openAuctionSlots) {
 46		return true
 47	}
 48	return false
 49}
 50
 51func (a *API) newSlotAPI(slotNum, currentBlockNum int64, bid *historydb.BidAPI, auctionVars *common.AuctionVariables) SlotAPI {
 52	firstBlock, lastBlock := a.getFirstLastBlock(slotNum)
 53	openAuction := a.isOpenAuction(currentBlockNum, slotNum, *auctionVars)
 54	slot := SlotAPI{
 55		ItemID:      uint64(slotNum),
 56		SlotNum:     slotNum,
 57		FirstBlock:  firstBlock,
 58		LastBlock:   lastBlock,
 59		OpenAuction: openAuction,
 60		WinnerBid:   bid,
 61	}
 62	return slot
 63}
 64
 65func (a *API) newSlotsAPIFromWinnerBids(fromItem *uint, order string, bids []historydb.BidAPI, currentBlockNum int64, auctionVars *common.AuctionVariables) (slots []SlotAPI) {
 66	for i := range bids {
 67		slotNum := bids[i].SlotNum
 68		slot := a.newSlotAPI(slotNum, currentBlockNum, &bids[i], auctionVars)
 69		if order == historydb.OrderAsc {
 70			if slot.ItemID >= uint64(*fromItem) {
 71				slots = append(slots, slot)
 72			}
 73		} else {
 74			if slot.ItemID <= uint64(*fromItem) {
 75				slots = append(slots, slot)
 76			}
 77		}
 78	}
 79	return slots
 80}
 81
 82func (a *API) addEmptySlot(slots []SlotAPI, slotNum int64, currentBlockNum int64, auctionVars *common.AuctionVariables, fromItem *uint, order string) ([]SlotAPI, error) {
 83	emptySlot := a.newSlotAPI(slotNum, currentBlockNum, nil, auctionVars)
 84	if order == historydb.OrderAsc {
 85		if emptySlot.ItemID >= uint64(*fromItem) {
 86			slots = append(slots, emptySlot)
 87		}
 88	} else {
 89		if emptySlot.ItemID <= uint64(*fromItem) {
 90			slots = append([]SlotAPI{emptySlot}, slots...)
 91		}
 92	}
 93	return slots, nil
 94}
 95
 96func (a *API) getSlot(c *gin.Context) {
 97	slotNumUint, err := parseParamUint("slotNum", nil, 0, maxUint32, c)
 98	if err != nil {
 99		retBadReq(err, c)
100		return
101	}
102	currentBlock, err := a.h.GetLastBlock()
103	if err != nil {
104		retBadReq(err, c)
105		return
106	}
107	auctionVars, err := a.h.GetAuctionVars()
108	if err != nil {
109		retBadReq(err, c)
110		return
111	}
112
113	slotNum := int64(*slotNumUint)
114	bid, err := a.h.GetBestBidAPI(&slotNum)
115	if err != nil && err != sql.ErrNoRows {
116		retSQLErr(err, c)
117		return
118	}
119
120	var slot SlotAPI
121	if err == sql.ErrNoRows {
122		slot = a.newSlotAPI(slotNum, currentBlock.EthBlockNum, nil, auctionVars)
123	} else {
124		slot = a.newSlotAPI(bid.SlotNum, currentBlock.EthBlockNum, &bid, auctionVars)
125	}
126
127	// JSON response
128	c.JSON(http.StatusOK, slot)
129}
130
131func getLimits(
132	minSlotNum, maxSlotNum int64, fromItem, limit *uint, order string,
133) (minLimit, maxLimit int64, pendingItems uint64) {
134	if order == historydb.OrderAsc {
135		if fromItem != nil && int64(*fromItem) > minSlotNum {
136			minLimit = int64(*fromItem)
137		} else {
138			minLimit = minSlotNum
139		}
140		if limit != nil && (minLimit+int64(*limit-1)) < maxSlotNum {
141			maxLimit = minLimit + int64(*limit-1)
142		} else {
143			maxLimit = maxSlotNum
144		}
145		pendingItems = uint64(maxSlotNum - maxLimit)
146	} else {
147		if fromItem != nil && int64(*fromItem) < maxSlotNum {
148			maxLimit = int64(*fromItem)
149		} else {
150			maxLimit = maxSlotNum
151		}
152		if limit != nil && (maxLimit-int64(*limit-1)) < minSlotNum {
153			minLimit = minSlotNum
154		} else {
155			minLimit = maxLimit - int64(*limit-1)
156		}
157		pendingItems = uint64(-(minSlotNum - minLimit))
158	}
159	return minLimit, maxLimit, pendingItems
160}
161
162func getLimitsWithAddr(minSlotNum, maxSlotNum *int64, fromItem, limit *uint, order string) (int64, int64) {
163	var minLim, maxLim int64
164	if fromItem != nil {
165		if order == historydb.OrderAsc {
166			maxLim = *maxSlotNum
167			if int64(*fromItem) > *minSlotNum {
168				minLim = int64(*fromItem)
169			} else {
170				minLim = *minSlotNum
171			}
172		} else {
173			minLim = *minSlotNum
174			if int64(*fromItem) < *maxSlotNum {
175				maxLim = int64(*fromItem)
176			} else {
177				maxLim = *maxSlotNum
178			}
179		}
180	}
181	return minLim, maxLim
182}
183
184func (a *API) getSlots(c *gin.Context) {
185	var slots []SlotAPI
186	minSlotNumDflt := int64(0)
187
188	// Get filters
189	minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, err := parseSlotFilters(c)
190	if err != nil {
191		retBadReq(err, c)
192		return
193	}
194
195	// Pagination
196	fromItem, order, limit, err := parsePagination(c)
197	if err != nil {
198		retBadReq(err, c)
199		return
200	}
201
202	currentBlock, err := a.h.GetLastBlock()
203	if err != nil {
204		retBadReq(err, c)
205		return
206	}
207	auctionVars, err := a.h.GetAuctionVars()
208	if err != nil {
209		retBadReq(err, c)
210		return
211	}
212
213	// Check filters
214	if maxSlotNum == nil && finishedAuction == nil {
215		retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
216		return
217	} else if finishedAuction != nil {
218		if maxSlotNum == nil && !*finishedAuction {
219			retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
220			return
221		} else if *finishedAuction {
222			currentBlock, err := a.h.GetLastBlock()
223			if err != nil {
224				retBadReq(err, c)
225				return
226			}
227			currentSlot := a.getCurrentSlot(currentBlock.EthBlockNum)
228			auctionVars, err := a.h.GetAuctionVars()
229			if err != nil {
230				retBadReq(err, c)
231				return
232			}
233			closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots)
234			if maxSlotNum == nil {
235				maxSlotNum = &closedAuctionSlots
236			} else if closedAuctionSlots < *maxSlotNum {
237				maxSlotNum = &closedAuctionSlots
238			}
239		}
240	} else if maxSlotNum != nil && minSlotNum != nil {
241		if *minSlotNum > *maxSlotNum {
242			retBadReq(errors.New("It is necessary to add valid filter (minSlotNum <= maxSlotNum)"), c)
243			return
244		}
245	}
246	if minSlotNum == nil {
247		minSlotNum = &minSlotNumDflt
248	}
249
250	// Get bids and pagination according to filters
251	var slotMinLim, slotMaxLim int64
252	var bids []historydb.BidAPI
253	var pendingItems uint64
254	if wonByEthereumAddress == nil {
255		slotMinLim, slotMaxLim, pendingItems = getLimits(*minSlotNum, *maxSlotNum, fromItem, limit, order)
256		// Get best bids in range maxSlotNum - minSlotNum
257		bids, _, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, nil, order)
258		if err != nil && err != sql.ErrNoRows {
259			retSQLErr(err, c)
260			return
261		}
262	} else {
263		slotMinLim, slotMaxLim = getLimitsWithAddr(minSlotNum, maxSlotNum, fromItem, limit, order)
264		bids, pendingItems, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order)
265		if err != nil && err != sql.ErrNoRows {
266			retSQLErr(err, c)
267			return
268		}
269	}
270
271	// Build the slot information with previous bids
272	var slotsBids []SlotAPI
273	if len(bids) > 0 {
274		slotsBids = a.newSlotsAPIFromWinnerBids(fromItem, order, bids, currentBlock.EthBlockNum, auctionVars)
275		if err != nil {
276			retBadReq(err, c)
277			return
278		}
279	}
280
281	// Build the other slots
282	if wonByEthereumAddress == nil {
283		// Build hte information of the slots with bids or not
284		for i := slotMinLim; i <= slotMaxLim; i++ {
285			found := false
286			for j := range slotsBids {
287				if slotsBids[j].SlotNum == i {
288					found = true
289					if order == historydb.OrderAsc {
290						if slotsBids[j].ItemID >= uint64(*fromItem) {
291							slots = append(slots, slotsBids[j])
292						}
293					} else {
294						if slotsBids[j].ItemID <= uint64(*fromItem) {
295							slots = append([]SlotAPI{slotsBids[j]}, slots...)
296						}
297					}
298					break
299				}
300			}
301			if !found {
302				slots, err = a.addEmptySlot(slots, i, currentBlock.EthBlockNum, auctionVars, fromItem, order)
303				if err != nil {
304					retBadReq(err, c)
305					return
306				}
307			}
308		}
309	} else if len(slotsBids) > 0 {
310		slots = slotsBids
311	}
312
313	if len(slots) == 0 {
314		retSQLErr(sql.ErrNoRows, c)
315		return
316	}
317
318	// Build succesfull response
319	type slotsResponse struct {
320		Slots        []SlotAPI `json:"slots"`
321		PendingItems uint64    `json:"pendingItems"`
322	}
323	c.JSON(http.StatusOK, &slotsResponse{
324		Slots:        slots,
325		PendingItems: pendingItems,
326	})
327}