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