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 int `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 int `json:"-"`
23 FirstItem int `json:"-"`
24 LastItem int `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 int, minSlotNum, maxSlotNum *int64) *db.Pagination {
53 // itemID is slotNum
54 firstItem := *minSlotNum
55 lastItem := *maxSlotNum
56 pagination := &db.Pagination{
57 TotalItems: int(totalItems),
58 FirstItem: int(firstItem),
59 LastItem: int(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: int(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 >= int(*fromItem) {
84 slots = append(slots, slot)
85 }
86 } else {
87 if slot.ItemID <= int(*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 >= int(*fromItem) {
99 slots = append(slots, emptySlot)
100 }
101 } else {
102 if emptySlot.ItemID <= int(*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 := 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 = int(*maxSlotNum) - int(*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 = 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 >= int(*fromItem) {
310 slots = append(slots, slotsBids[j])
311 }
312 } else {
313 if slotsBids[j].ItemID <= int(*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}