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