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 slot.ItemID >= uint64(*fromItem) {
69 slots = append(slots, slot)
70 }
71 } else {
72 if 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 emptySlot.ItemID >= uint64(*fromItem) {
84 slots = append(slots, emptySlot)
85 }
86 } else {
87 if 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.GetLastBlock()
101 if err != nil {
102 retBadReq(err, c)
103 return
104 }
105 auctionVars, err := a.h.GetAuctionVars()
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 }
179 return minLim, maxLim
180}
181
182func (a *API) getSlots(c *gin.Context) {
183 var slots []SlotAPI
184 minSlotNumDflt := int64(0)
185
186 // Get filters
187 minSlotNum, maxSlotNum, wonByEthereumAddress, finishedAuction, err := parseSlotFilters(c)
188 if err != nil {
189 retBadReq(err, c)
190 return
191 }
192
193 // Pagination
194 fromItem, order, limit, err := parsePagination(c)
195 if err != nil {
196 retBadReq(err, c)
197 return
198 }
199
200 currentBlock, err := a.h.GetLastBlock()
201 if err != nil {
202 retBadReq(err, c)
203 return
204 }
205 auctionVars, err := a.h.GetAuctionVars()
206 if err != nil {
207 retBadReq(err, c)
208 return
209 }
210
211 // Check filters
212 if maxSlotNum == nil && finishedAuction == nil {
213 retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
214 return
215 } else if finishedAuction != nil {
216 if maxSlotNum == nil && !*finishedAuction {
217 retBadReq(errors.New("It is necessary to add maxSlotNum filter"), c)
218 return
219 } else if *finishedAuction {
220 currentBlock, err := a.h.GetLastBlock()
221 if err != nil {
222 retBadReq(err, c)
223 return
224 }
225 currentSlot := a.getCurrentSlot(currentBlock.Num)
226 auctionVars, err := a.h.GetAuctionVars()
227 if err != nil {
228 retBadReq(err, c)
229 return
230 }
231 closedAuctionSlots := currentSlot + int64(auctionVars.ClosedAuctionSlots)
232 if maxSlotNum == nil {
233 maxSlotNum = &closedAuctionSlots
234 } else if closedAuctionSlots < *maxSlotNum {
235 maxSlotNum = &closedAuctionSlots
236 }
237 }
238 } else if maxSlotNum != nil && minSlotNum != nil {
239 if *minSlotNum > *maxSlotNum {
240 retBadReq(errors.New("It is necessary to add valid filter (minSlotNum <= maxSlotNum)"), c)
241 return
242 }
243 }
244 if minSlotNum == nil {
245 minSlotNum = &minSlotNumDflt
246 }
247
248 // Get bids and pagination according to filters
249 var slotMinLim, slotMaxLim int64
250 var bids []historydb.BidAPI
251 var pendingItems uint64
252 if wonByEthereumAddress == nil {
253 slotMinLim, slotMaxLim, pendingItems = getLimits(*minSlotNum, *maxSlotNum, fromItem, limit, order)
254 // Get best bids in range maxSlotNum - minSlotNum
255 bids, _, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, nil, order)
256 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
257 retSQLErr(err, c)
258 return
259 }
260 } else {
261 slotMinLim, slotMaxLim = getLimitsWithAddr(minSlotNum, maxSlotNum, fromItem, limit, order)
262 bids, pendingItems, err = a.h.GetBestBidsAPI(&slotMinLim, &slotMaxLim, wonByEthereumAddress, limit, order)
263 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
264 retSQLErr(err, c)
265 return
266 }
267 }
268
269 // Build the slot information with previous bids
270 var slotsBids []SlotAPI
271 if len(bids) > 0 {
272 slotsBids = a.newSlotsAPIFromWinnerBids(fromItem, order, bids, currentBlock.Num, auctionVars)
273 if err != nil {
274 retBadReq(err, c)
275 return
276 }
277 }
278
279 // Build the other slots
280 if wonByEthereumAddress == nil {
281 // Build hte information of the slots with bids or not
282 for i := slotMinLim; i <= slotMaxLim; i++ {
283 found := false
284 for j := range slotsBids {
285 if slotsBids[j].SlotNum == i {
286 found = true
287 if order == historydb.OrderAsc {
288 if slotsBids[j].ItemID >= uint64(*fromItem) {
289 slots = append(slots, slotsBids[j])
290 }
291 } else {
292 if slotsBids[j].ItemID <= uint64(*fromItem) {
293 slots = append([]SlotAPI{slotsBids[j]}, slots...)
294 }
295 }
296 break
297 }
298 }
299 if !found {
300 slots, err = a.addEmptySlot(slots, i, currentBlock.Num, auctionVars, fromItem, order)
301 if err != nil {
302 retBadReq(err, c)
303 return
304 }
305 }
306 }
307 } else if len(slotsBids) > 0 {
308 slots = slotsBids
309 }
310
311 if len(slots) == 0 {
312 retSQLErr(sql.ErrNoRows, c)
313 return
314 }
315
316 // Build succesfull response
317 type slotsResponse struct {
318 Slots []SlotAPI `json:"slots"`
319 PendingItems uint64 `json:"pendingItems"`
320 }
321 c.JSON(http.StatusOK, &slotsResponse{
322 Slots: slots,
323 PendingItems: pendingItems,
324 })
325}