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}