index.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { addBlock } from './joystream'
  2. import { connectUpstream } from './joystream/ws'
  3. import express from 'express'
  4. import cors from 'cors'
  5. import ascii from './ascii'
  6. import db from './db'
  7. import moment from 'moment'
  8. import { QueryOptionsWithType, QueryTypes, Sequelize } from 'sequelize'
  9. import {
  10. validatorStats,
  11. countTotalBlocksProduced,
  12. findBlockByTime,
  13. findFirstAuthoredBlock,
  14. findLastAuthoredBlock,
  15. IValidatorReport,
  16. ITotalCount,
  17. ITotalBlockCount,
  18. IValidatorEraStats,
  19. pageSize} from './db/native_queries'
  20. import { Header } from './types'
  21. import {
  22. Block,
  23. StartBlock
  24. } from './db/models'
  25. const PORT: number = process.env.PORT ? +process.env.PORT : 3500
  26. const app = express()
  27. app.listen(PORT, () =>
  28. console.log(`[Express] Listening on port ${PORT}`, ascii)
  29. )
  30. ;(async () => {
  31. const api = await connectUpstream()
  32. let lastHeader: Header = { number: 0, timestamp: 0, author: '' }
  33. let firstProcessedBlockLogged = false
  34. let highId = 0
  35. Block.max('id').then(
  36. (highestProcessedBlock: number) => {
  37. highId = highestProcessedBlock === undefined || isNaN(highestProcessedBlock) ? 0 : highestProcessedBlock
  38. StartBlock.destroy({where: {}})
  39. api.rpc.chain.subscribeNewHeads(async (header: Header) => {
  40. const id = +header.number
  41. if (id === +lastHeader.number)
  42. return console.debug(
  43. `[Joystream] Skipping duplicate block ${id} (TODO handleFork)`
  44. )
  45. lastHeader = header
  46. await addBlock(api, header)
  47. if(!firstProcessedBlockLogged) {
  48. StartBlock.create({block: id})
  49. console.log(`[Joystream] Subscribed to new blocks starting from ${id}`)
  50. firstProcessedBlockLogged = true
  51. }
  52. })
  53. }
  54. )
  55. })()
  56. app.use(cors())
  57. app.use(express.json())
  58. app.use(express.urlencoded({ extended: true }))
  59. const corsOptions = {
  60. origin: process.env.ALOWED_ORIGIN || 'http://localhost:3000',
  61. optionsSuccessStatus: 200
  62. }
  63. const ADDRESS_LENGTH = 48
  64. const opts = {type: QueryTypes.SELECT, plain: true} as QueryOptionsWithType<QueryTypes.SELECT> & { plain: true }
  65. app.get('/validator-report', cors(corsOptions), async (req: any, res: any, next: any) => {
  66. try {
  67. const address = (req.query.addr && req.query.addr.length == ADDRESS_LENGTH) ? req.query.addr : ''
  68. const page = !isNaN(req.query.page) ? req.query.page : 1
  69. const startBlock = !isNaN(req.query.start_block) ? +req.query.start_block : -1
  70. const endBlock = !isNaN(req.query.end_block) ? +req.query.end_block : -1
  71. console.log(`Start block = ${startBlock}, end block = ${endBlock}`)
  72. if(startBlock > 0 && endBlock > 0 && endBlock > startBlock) {
  73. return res.json(await fetchReportPage(
  74. validatorStats(address, startBlock, endBlock, null, null, page),
  75. validatorStats(address, startBlock, endBlock, null, null, page, true),
  76. countTotalBlocksProduced(address, startBlock, endBlock),
  77. findFirstAuthoredBlock(startBlock, endBlock, address),
  78. findLastAuthoredBlock(startBlock, endBlock, address)
  79. ))
  80. } else {
  81. const startTime = moment.utc(req.query.start_time, 'YYYY-MM-DD').startOf('d')
  82. const endTime = moment.utc(req.query.end_time, 'YYYY-MM-DD').endOf('d')
  83. console.log(`Start time: [${startTime}]-[${endTime}]`)
  84. if(endTime.isAfter(startTime)) {
  85. return res.json(await fetchReportPage(
  86. validatorStats(address, -1, -1, startTime, endTime, page),
  87. validatorStats(address, -1, -1, startTime, endTime, page, true),
  88. countTotalBlocksProduced(address, -1, -1, startTime, endTime),
  89. findBlockByTime(startTime),
  90. findBlockByTime(endTime)
  91. ))
  92. } else {
  93. return res.json(await fetchReportPage(
  94. validatorStats(address, -1, -1, null, null, page),
  95. validatorStats(address, -1, -1, null, null, page, true),
  96. countTotalBlocksProduced(address),
  97. findFirstAuthoredBlock(startBlock, endBlock, address),
  98. findLastAuthoredBlock(startBlock, endBlock, address)
  99. ))
  100. }
  101. }
  102. } catch (err) {
  103. console.log(err)
  104. return res.json({})
  105. }
  106. })
  107. const fetchReportPage = async (
  108. validatorStatsSql: string,
  109. validatorStatsCountSql: string,
  110. totalBlocksSql: string,
  111. firstBlockSql: string,
  112. lastBlockSql: string,
  113. ): Promise<IValidatorReport> => {
  114. const dbBlockStart = (await db.query<any>(firstBlockSql, opts)) // TODO <Block> instead of <any> produces an object with no get() function
  115. const dbBlockEnd = (await db.query<any>(lastBlockSql, opts))
  116. const dbCount = (await db.query<ITotalCount>(validatorStatsCountSql, opts))
  117. const blockCount = (await db.query<ITotalBlockCount>(totalBlocksSql, opts))
  118. return db.query<IValidatorEraStats>(validatorStatsSql, {type: QueryTypes.SELECT}).then((stats: IValidatorEraStats[]) => {
  119. const validationReport: IValidatorReport = {
  120. pageSize: pageSize,
  121. totalCount: dbCount.totalCount,
  122. startBlock: dbBlockStart?.id,
  123. endBlock: dbBlockEnd?.id,
  124. startTime: dbBlockStart?.timestamp,
  125. endTime: dbBlockEnd?.timestamp,
  126. startEra: dbBlockStart?.eraId,
  127. endEra: dbBlockEnd?.eraId,
  128. totalBlocks: blockCount.totalBlocks | 0,
  129. report: stats
  130. }
  131. return validationReport
  132. })
  133. }