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.

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