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