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.

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