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.Num, nil, auctionVars)
120 } else {
121 slot = a.newSlotAPI(bid.SlotNum, currentBlock.Num, &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.Num)
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.Num, 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.Num, 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}