StatisticsCollector.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import {ApiPromise, WsProvider} from "@polkadot/api";
  2. import {types} from '@joystream/types'
  3. import { HeaderExtended } from '@polkadot/api-derive/type/types';
  4. import {
  5. AccountId,
  6. EraIndex,
  7. EventRecord,
  8. Hash,
  9. Moment
  10. } from "@polkadot/types/interfaces";
  11. import {
  12. CacheEvent,
  13. Statistics,
  14. } from "./types";
  15. import {Option, u32, Vec} from "@polkadot/types";
  16. import {RewardRelationship, RewardRelationshipId} from "@joystream/types/recurring-rewards";
  17. const fsSync = require('fs');
  18. const fs = fsSync.promises;
  19. const PROVIDER_URL = process.env.PROVIDER_URL; //"wss://rome-rpc-endpoint.joystream.org:9944";
  20. const CACHE_FOLDER = "cache";
  21. export class StatisticsCollector {
  22. private api?: ApiPromise;
  23. private blocksEventsCache: Map<number, CacheEvent[]>;
  24. private statistics: Statistics;
  25. constructor() {
  26. this.blocksEventsCache = new Map<number, CacheEvent[]>();
  27. this.statistics = new Statistics();
  28. }
  29. async getStatistics(startBlock: number, endBlock: number): Promise<Statistics> {
  30. this.api = await StatisticsCollector.connectApi();
  31. let startHash = (await this.api.rpc.chain.getBlockHash(startBlock)) as Hash;
  32. let endHash = (await this.api.rpc.chain.getBlockHash(endBlock)) as Hash;
  33. this.statistics.startBlock = startBlock;
  34. this.statistics.endBlock = endBlock;
  35. this.statistics.newBlocks = endBlock - startBlock;
  36. await this.buildBlocksEventCache(startBlock, endBlock);
  37. await this.fillBasicInfo(startHash, endHash);
  38. console.log("1");
  39. await this.fillValidatorInfo(startHash, endHash);
  40. console.log("2");
  41. await this.api.disconnect();
  42. return this.statistics;
  43. }
  44. async fillBasicInfo(startHash: Hash, endHash: Hash) {
  45. let startDate = (await this.api.query.timestamp.now.at(startHash)) as Moment;
  46. let endDate = (await this.api.query.timestamp.now.at(endHash)) as Moment;
  47. this.statistics.dateStart = new Date(startDate.toNumber()).toLocaleDateString("en-US");
  48. this.statistics.dateEnd = new Date(endDate.toNumber()).toLocaleDateString("en-US");
  49. }
  50. async computeReward(roundNrBlocks: number, rewardRelationshipIds: RewardRelationshipId[], hash: Hash) {
  51. let recurringRewards = await Promise.all(rewardRelationshipIds.map(async (rewardRelationshipId) => {
  52. return await this.api.query.recurringRewards.rewardRelationships.at(hash, rewardRelationshipId) as RewardRelationship;
  53. }));
  54. let rewardPerBlock = 0;
  55. for (let recurringReward of recurringRewards) {
  56. const amount = recurringReward.amount_per_payout.toNumber();
  57. const payoutInterval = recurringReward.payout_interval.unwrapOr(null);
  58. if (amount && payoutInterval) {
  59. rewardPerBlock += amount / payoutInterval;
  60. }
  61. }
  62. return rewardPerBlock * roundNrBlocks;
  63. }
  64. async fillValidatorInfo(startHash: Hash, endHash: Hash) {
  65. let startTimestamp = await this.api.query.timestamp.now.at(startHash) as unknown as Moment;
  66. let endTimestamp = await this.api.query.timestamp.now.at(endHash) as unknown as Moment;
  67. let avgBlockProduction = (((endTimestamp.toNumber() - startTimestamp.toNumber())
  68. / 1000) / this.statistics.newBlocks);
  69. this.statistics.avgBlockProduction = Number(avgBlockProduction.toFixed(2));
  70. let maxStartValidators = (await this.api.query.staking.validatorCount.at(startHash) as u32).toNumber();
  71. let startValidators = await this.findActiveValidators(startHash, false);
  72. this.statistics.startValidators = startValidators.length + " / " + maxStartValidators;
  73. let maxEndValidators = (await this.api.query.staking.validatorCount.at(endHash) as u32).toNumber();
  74. let endValidators = await this.findActiveValidators(endHash, true);
  75. this.statistics.endValidators = endValidators.length + " / " + maxEndValidators;
  76. let aaa = (await this.api.query.staking.erasStakers.at(startHash) as u32).toNumber();
  77. let bbb = (await this.api.query.staking.erasStakers.at(endHash) as u32).toNumber();
  78. const startEra = await this.api.query.staking.currentEra.at(startHash) as Option<EraIndex>;
  79. this.statistics.startValidatorsStake = (await this.api.query.staking.erasTotalStake.at(startHash, startEra.unwrap())).toNumber();
  80. const endEra = await this.api.query.staking.currentEra.at(endHash) as Option<EraIndex>;
  81. this.statistics.endValidatorsStake = (await this.api.query.staking.erasTotalStake.at(endHash, endEra.unwrap())).toNumber();
  82. }
  83. async findActiveValidators(hash: Hash, searchPreviousBlocks: boolean): Promise<AccountId[]> {
  84. const block = await this.api.rpc.chain.getBlock(hash);
  85. let currentBlockNr = block.block.header.number.toNumber();
  86. let activeValidators;
  87. do {
  88. let currentHash = (await this.api.rpc.chain.getBlockHash(currentBlockNr)) as Hash;
  89. let allValidators = await this.api.query.staking.snapshotValidators.at(currentHash) as Option<Vec<AccountId>>;
  90. if (!allValidators.isEmpty) {
  91. let max = (await this.api.query.staking.validatorCount.at(currentHash)).toNumber();
  92. activeValidators = Array.from(allValidators.unwrap()).slice(0, max);
  93. }
  94. if (searchPreviousBlocks) {
  95. --currentBlockNr;
  96. } else {
  97. ++currentBlockNr;
  98. }
  99. } while (activeValidators == undefined);
  100. return activeValidators;
  101. }
  102. async buildBlocksEventCache(startBlock: number, endBlock: number) {
  103. let cacheFile = CACHE_FOLDER + '/' + startBlock + '-' + endBlock + '.json';
  104. let exists = await fs.access(cacheFile, fsSync.constants.R_OK).then(() => true)
  105. .catch(() => false);
  106. // let exists = false;
  107. if (!exists) {
  108. console.log('Building events cache...');
  109. for (let i = startBlock; i < endBlock; ++i) {
  110. process.stdout.write('\rCaching block: ' + i + ' until ' + endBlock);
  111. const blockHash: Hash = await this.api.rpc.chain.getBlockHash(i);
  112. // const signedBlock = await this.api.rpc.chain.getBlock(blockHash);
  113. const era = await this.api.query.staking.activeEra.at(blockHash)
  114. console.log(`Era=${era}`);
  115. const extendedHeader = await this.api.derive.chain.getHeader(blockHash) as HeaderExtended;
  116. console.log(`#${extendedHeader.number}: ${extendedHeader.author}`);
  117. let eventRecord = await this.api.query.system.events.at(blockHash) as Vec<EventRecord>;
  118. let cacheEvents = new Array<CacheEvent>();
  119. for (let event of eventRecord) {
  120. cacheEvents.push(new CacheEvent(event.event.section, event.event.method, event.event.data));
  121. }
  122. this.blocksEventsCache.set(i, cacheEvents);
  123. }
  124. console.log('\nFinish events cache...');
  125. await fs.writeFile(cacheFile, JSON.stringify(Array.from(this.blocksEventsCache.entries()), null, 2));
  126. } else {
  127. console.log('Cache file found, loading it...');
  128. let fileData = await fs.readFile(cacheFile);
  129. this.blocksEventsCache = new Map(JSON.parse(fileData));
  130. console.log('Cache file loaded...');
  131. }
  132. }
  133. static async connectApi(): Promise<ApiPromise> {
  134. // const provider = new WsProvider('wss://testnet.joystream.org:9944');
  135. const provider = new WsProvider(PROVIDER_URL);
  136. // Create the API and wait until ready
  137. return await ApiPromise.create({provider, types});
  138. }
  139. }