You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

324 lines
8.7 KiB

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