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