123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886 |
- import { ApiPromise } from "@polkadot/api";
- // types
- import {
- AccountId,
- Balance,
- BalanceOf,
- BlockNumber,
- EventRecord,
- Hash,
- } from "@polkadot/types/interfaces";
- import { Config, MintStatistics, Statistics, WorkersInfo } from "./types/tokenomics";
- import {
- CacheEvent,
- Bounty,
- WorkerReward,
- SpendingProposal,
- StatusData,
- } from "./lib/types";
- import { Option, u32, Vec } from "@polkadot/types";
- import { ElectionStake, SealedVote, Seats } from "@joystream/types/council";
- import { Mint, MintId } from "@joystream/types/mint";
- import { ContentId, DataObject } from "@joystream/types/media";
- import { CategoryId } from "@joystream/types/forum";
- import { MemberId, Membership } from "@joystream/types/members";
- import {
- Proposal,
- ProposalId,
- SpendingParams,
- } from "@joystream/types/proposals";
- import {
- RewardRelationship,
- RewardRelationshipId,
- } from "@joystream/types/recurring-rewards";
- import { Stake } from "@joystream/types/stake";
- import { Worker, WorkerId } from "@joystream/types/working-group";
- import { ProposalDetails, ProposalOf } from "@joystream/types/augment/types";
- import * as constants from "constants";
- import axios from "axios";
- // lib
- import { eventStats, getPercent, getTotalMinted, momentToString } from "./lib";
- import {
- connectApi,
- getBlock,
- getBlockHash,
- getHead,
- getTimestamp,
- getIssuance,
- getEra,
- getEraStake,
- getEvents,
- getCouncil,
- getCouncilRound,
- getCouncilSize,
- getCouncilApplicants,
- getCouncilApplicantStakes,
- getCouncilCommitments,
- getCouncilPayoutInterval,
- getCouncilPayout,
- getCouncilElectionDurations,
- getNextWorker,
- getWorkers,
- getWorkerReward,
- getStake,
- getCouncilMint,
- getMintsCreated,
- getMint,
- getGroupMint,
- getNextMember,
- getMember,
- getNextPost,
- getNextThread,
- getNextCategory,
- getProposalCount,
- getProposalInfo,
- getProposalDetails,
- getValidatorCount,
- getValidators,
- getNextEntity,
- getNextChannel,
- getNextVideo,
- getEntity,
- getDataObject,
- getDataObjects,
- } from "./lib/api";
- import {
- filterMethods,
- getWorkerRewards,
- getWorkerRow,
- getBurnedTokens,
- getFinalizedSpendingProposals,
- getActiveValidators,
- getValidatorsRewards,
- } from "./lib/rewards";
- const fsSync = require("fs");
- const fs = fsSync.promises;
- const parse = require("csv-parse/lib/sync");
- export class StatisticsCollector {
- private api?: ApiPromise;
- private blocksEventsCache: Map<number, CacheEvent[]>;
- private statistics: Statistics;
- constructor() {
- this.blocksEventsCache = new Map<number, CacheEvent[]>();
- this.statistics = new Statistics();
- }
- saveStats(data: any) {
- Object.keys(data).map((key: string) => (this.statistics[key] = data[key]));
- }
- filterCache(
- filterEvent: (event: CacheEvent) => boolean
- ): [number, CacheEvent[]][] {
- const blocks: [number, CacheEvent[]][] = [];
- for (let block of this.blocksEventsCache) {
- const [key, events] = block;
- const filtered = events.filter((event) => filterEvent(event));
- if (filtered.length) blocks.push([key, filtered]);
- }
- return blocks;
- }
- async getStats(
- startBlock: number,
- endBlock: number,
- config: Config
- ): Promise<Statistics> {
- const { cacheDir, providerUrl, statusUrl } = config;
- this.api = await connectApi(providerUrl);
- const aboveHead = endBlock - Number(await getHead(this.api));
- if (aboveHead > 0) {
- console.log(`End Block is above our Head, wait ${aboveHead} blocks.`);
- return this.statistics;
- }
- let startHash: Hash = await getBlockHash(this.api, startBlock);
- let endHash: Hash = await getBlockHash(this.api, endBlock);
- let dateStart = momentToString(await getTimestamp(this.api, startHash));
- let dateEnd = momentToString(await getTimestamp(this.api, endHash));
- this.saveStats({
- dateStart,
- dateEnd,
- startBlock,
- endBlock,
- newBlocks: endBlock - startBlock,
- percNewBlocks: getPercent(startBlock, endBlock),
- });
- // run long running tasks in parallel first
- await Promise.all([
- this.buildBlocksEventCache(startBlock, endBlock, cacheDir).then(() =>
- this.fillStats(startBlock, endBlock, startHash, endHash, config)
- ),
- this.getFiatEvents(startBlock, endBlock, statusUrl),
- this.fillMediaUploadInfo(startHash, endHash),
- ]);
- this.api.disconnect();
- return this.statistics;
- }
- fillStats(
- startBlock: number,
- endBlock: number,
- startHash: Hash,
- endHash: Hash,
- config: Config
- ): Promise<void[]> {
- eventStats(this.blocksEventsCache); // print event stats
- return Promise.all([
- this.fillTokenInfo(startBlock, endBlock, startHash, endHash, config),
- this.fillMintsInfo(startHash, endHash),
- this.fillCouncilInfo(startHash, endHash, config.councilRoundOffset),
- this.fillCouncilElectionInfo(startBlock),
- this.fillValidatorInfo(startHash, endHash),
- this.fillStorageProviderInfo(startBlock, endBlock, startHash, endHash),
- this.fillCuratorInfo(startHash, endHash),
- this.fillOperationsInfo(startBlock, endBlock, startHash, endHash),
- this.fillMembershipInfo(startHash, endHash),
- this.fillForumInfo(startHash, endHash),
- ]);
- }
- async getApprovedBounties(file: string): Promise<Bounty[]> {
- try {
- await fs.access(file, constants.R_OK);
- } catch {
- console.warn("File with spending proposal categories not found: ${file}");
- }
- const fileContent = await fs.readFile(file);
- const proposals = parse(fileContent).slice(1);
- console.log(`Loaded ${proposals.length} proposals.`);
- return proposals
- .filter(
- (line: string[]) =>
- line[0] === "Antioch" &&
- line[3] === "Approved" &&
- line[8] === "Bounties"
- )
- .map((bounty: string[]) => {
- return new Bounty(
- bounty[0],
- Number(bounty[1]),
- bounty[2],
- bounty[3],
- Number(bounty[4]),
- Number(bounty[5])
- );
- });
- }
- fillSudoSetBalance() {
- let balancesSetByRoot = 0;
- this.filterCache(filterMethods.sudoSetBalance).map(([block, events]) =>
- events.forEach(({ data }) => {
- balancesSetByRoot += Number(data[1]);
- })
- );
- this.saveStats({ balancesSetByRoot });
- }
- async fillTokenInfo(
- startBlock: number,
- endBlock: number,
- startHash: Hash,
- endHash: Hash,
- config: Config
- ): Promise<void> {
- const { burnAddress } = config;
- const proposalsFile = config.repoDir + config.spendingCategoriesFile;
- const startIssuance = (await getIssuance(this.api, startHash)).toNumber();
- const endIssuance = (await getIssuance(this.api, endHash)).toNumber();
- const burnEvents = this.filterCache(filterMethods.getBurnedTokens);
- this.saveStats({
- startIssuance,
- endIssuance,
- newIssuance: endIssuance - startIssuance,
- percNewIssuance: getPercent(startIssuance, endIssuance),
- newTokensBurn: await getBurnedTokens(burnAddress, burnEvents),
- });
- this.fillSudoSetBalance();
- // bounties
- const bounties = await this.getApprovedBounties(proposalsFile);
- const blocks = this.filterCache(filterMethods.finalizedSpendingProposals);
- const spendingProposals: SpendingProposal[] =
- await getFinalizedSpendingProposals(this.api, blocks);
- let bountiesTotalPaid = 0;
- for (let bounty of bounties) {
- const bountySpendingProposal = spendingProposals.find(
- (spendingProposal) => spendingProposal.id == bounty.proposalId
- );
- if (bountySpendingProposal)
- bountiesTotalPaid += bountySpendingProposal.amount;
- }
- if (!bountiesTotalPaid) {
- console.warn(
- `No bounties in selected period. Need to update ${proposalsFile}?\nLooking for spending proposals titled "bounty":`
- );
- for (const { title, amount } of spendingProposals) {
- if (!title.toLowerCase().includes("bounty")) continue;
- bountiesTotalPaid += amount;
- console.log(` - ${title}: ${amount}`);
- }
- }
- this.saveStats({ bountiesTotalPaid });
- const spendingProposalsTotal = spendingProposals.reduce(
- (n, p) => n + p.amount,
- 0
- );
- const newCouncilRewards = await this.computeCouncilReward(
- endBlock - startBlock,
- endHash
- );
- const newCuratorInfo = await this.computeWorkingGroupReward(
- startHash,
- endHash,
- "contentDirectory"
- );
- this.saveStats({
- spendingProposalsTotal,
- newCouncilRewards: newCouncilRewards.toFixed(2),
- newCuratorRewards: newCuratorInfo.rewards.toFixed(2),
- });
- }
- async getMintInfo(
- api: ApiPromise,
- mintId: MintId,
- startHash: Hash,
- endHash: Hash
- ): Promise<MintStatistics> {
- const startMint: Mint = await getMint(api, startHash, mintId);
- const endMint: Mint = await getMint(api, endHash, mintId);
- let stats = new MintStatistics();
- stats.startMinted = getTotalMinted(startMint);
- stats.endMinted = getTotalMinted(endMint);
- stats.diffMinted = stats.endMinted - stats.startMinted;
- stats.percMinted = getPercent(stats.startMinted, stats.endMinted);
- return stats;
- }
- async computeCouncilReward(
- roundNrBlocks: number,
- endHash: Hash
- ): Promise<number> {
- const payoutInterval = Number(
- (
- (await getCouncilPayoutInterval(
- this.api,
- endHash
- )) as Option<BlockNumber>
- ).unwrapOr(0)
- );
- const amountPerPayout = (
- (await getCouncilPayout(this.api, endHash)) as BalanceOf
- ).toNumber();
- const [
- announcingPeriod,
- votingPeriod,
- revealingPeriod,
- termDuration,
- ]: number[] = await getCouncilElectionDurations(this.api, endHash);
- const nrCouncilMembers = ((await getCouncil(this.api, endHash)) as Seats)
- .length;
- const totalCouncilRewardsPerBlock =
- amountPerPayout && payoutInterval
- ? (amountPerPayout * nrCouncilMembers) / payoutInterval
- : 0;
- const councilTermDurationRatio =
- termDuration /
- (termDuration + votingPeriod + revealingPeriod + announcingPeriod);
- const avgCouncilRewardPerBlock =
- councilTermDurationRatio * totalCouncilRewardsPerBlock;
- return avgCouncilRewardPerBlock * roundNrBlocks;
- }
- // Summarize stakes and rewards at start and end
- async computeWorkingGroupReward(
- startHash: Hash,
- endHash: Hash,
- workingGroup: string
- ): Promise<WorkersInfo> {
- const group = workingGroup + "WorkingGroup";
- let info = new WorkersInfo();
- // stakes at start
- const workersStart: WorkerReward[] = await getWorkerRewards(
- this.api,
- group,
- startHash
- );
- workersStart.forEach(({ stake }) => {
- if (stake) info.startStake += stake.value.toNumber();
- });
- // stakes at end
- const workersEnd: WorkerReward[] = await getWorkerRewards(
- this.api,
- group,
- endHash
- );
- let workers = ``;
- workersEnd.forEach(async (worker) => {
- if (worker.stake) info.endStake += worker.stake.value.toNumber();
- if (!worker.reward) return;
- let earnedBefore = 0;
- const hired = workersStart.find((w) => w.id === worker.id);
- if (hired) earnedBefore = hired.reward.total_reward_received.toNumber();
- workers += getWorkerRow(worker, earnedBefore);
- });
- const groupTag =
- workingGroup === `storage`
- ? `storageProviders`
- : workingGroup === `contentDirectory`
- ? `curators`
- : workingGroup === `operations`
- ? `operations`
- : ``;
- if (workers.length) {
- const header = `| # | Member | Status | tJOY / Block | M tJOY Term | M tJOY total |\n|--|--|--|--|--|--|\n`;
- this.saveStats({ [groupTag]: header + workers });
- } else this.saveStats({ [groupTag]: `` });
- const mintId = await getGroupMint(this.api, group);
- const mintStart: Mint = await getMint(this.api, startHash, mintId);
- const mintEnd: Mint = await getMint(this.api, endHash, mintId);
- const totalMinted = (m: Mint) => Number(m.total_minted);
- info.rewards = totalMinted(mintEnd) - totalMinted(mintStart);
- info.endNrOfWorkers = workersEnd.length;
- return info;
- }
- async computeGroupMintStats(
- [label, tag]: string[],
- startHash: Hash,
- endHash: Hash
- ) {
- const group = label + "WorkingGroup";
- const mint = await getGroupMint(this.api, group);
- const info = await this.getMintInfo(this.api, mint, startHash, endHash);
- let stats: { [key: string]: number } = {};
- stats[`start${tag}Minted`] = info.startMinted;
- stats[`end${tag}Minted`] = info.endMinted;
- stats[`new${tag}Minted`] = info.diffMinted;
- stats[`perc${tag}Minted`] = info.percMinted;
- this.saveStats(stats);
- }
- async fillMintsInfo(startHash: Hash, endHash: Hash): Promise<void> {
- const startNrMints = await getMintsCreated(this.api, startHash);
- const endNrMints = await getMintsCreated(this.api, endHash);
- const newMints = endNrMints - startNrMints;
- // calcuate sum of all mints
- let totalMinted = 0;
- let totalMintCapacityIncrease = 0;
- // summarize old mints
- for (let i = 0; i < startNrMints; ++i) {
- const startMint: Mint = await getMint(this.api, startHash, i);
- const endMint: Mint = await getMint(this.api, endHash, i);
- const startMintTotal = getTotalMinted(startMint);
- const endMintTotal = getTotalMinted(endMint);
- totalMinted += endMintTotal - startMintTotal;
- totalMintCapacityIncrease +=
- parseInt(endMint.getField("capacity").toString()) -
- parseInt(startMint.getField("capacity").toString());
- }
- // summarize new mints
- for (let i = startNrMints; i < endNrMints; ++i) {
- const endMint: Mint = await getMint(this.api, endHash, i);
- if (endMint) totalMinted += getTotalMinted(endMint);
- }
- this.saveStats({ newMints, totalMinted, totalMintCapacityIncrease });
- // council
- const councilInfo = await this.getMintInfo(
- this.api,
- await getCouncilMint(this.api, endHash),
- startHash,
- endHash
- );
- this.saveStats({
- startCouncilMinted: councilInfo.startMinted,
- endCouncilMinted: councilInfo.endMinted,
- newCouncilMinted: councilInfo.diffMinted,
- percNewCouncilMinted: councilInfo.percMinted,
- });
- // working groups
- const groups = [
- ["contentDirectory", "Curator"],
- ["storage", "Storage"],
- ["operations", "Operations"],
- ].forEach((group) => this.computeGroupMintStats(group, startHash, endHash));
- }
- async fillCouncilInfo(
- startHash: Hash,
- endHash: Hash,
- councilRoundOffset: number
- ): Promise<void> {
- const round = await getCouncilRound(this.api, startHash);
- const startNrProposals = await getProposalCount(this.api, startHash);
- const endNrProposals = await getProposalCount(this.api, endHash);
- let approvedProposals = new Set();
- for (let [key, blockEvents] of this.blocksEventsCache) {
- for (let event of blockEvents) {
- if (
- event.section == "proposalsEngine" &&
- event.method == "ProposalStatusUpdated"
- ) {
- let statusUpdateData = event.data[1] as any;
- let finalizeData = statusUpdateData.finalized as any;
- if (finalizeData && finalizeData.proposalStatus.approved) {
- approvedProposals.add(Number(event.data[0]));
- }
- }
- }
- }
- this.saveStats({
- councilRound: round - councilRoundOffset,
- councilMembers: await getCouncilSize(this.api, startHash),
- newProposals: endNrProposals - startNrProposals,
- newApprovedProposals: approvedProposals.size,
- });
- }
- async fillCouncilElectionInfo(startBlock: number): Promise<void> {
- let startBlockHash = await getBlockHash(this.api, startBlock);
- let events: Vec<EventRecord> = await getEvents(this.api, startBlockHash);
- let isStartBlockFirstCouncilBlock = events.some(
- ({ event }) =>
- event.section == "councilElection" && event.method == "CouncilElected"
- );
- if (!isStartBlockFirstCouncilBlock)
- return console.warn(
- "Note: The given start block is not the first block of the council round so council election information will be empty"
- );
- let lastBlockHash = await getBlockHash(this.api, startBlock - 1);
- let applicants: Vec<AccountId> = await getCouncilApplicants(
- this.api,
- lastBlockHash
- );
- let electionApplicantsStakes = 0;
- for (let applicant of applicants) {
- const applicantStakes: ElectionStake = await getCouncilApplicantStakes(
- this.api,
- lastBlockHash,
- applicant
- );
- electionApplicantsStakes += applicantStakes.new.toNumber();
- }
- // let seats = await getCouncil(this.api,startBlockHash) as Seats;
- //TODO: Find a more accurate way of getting the votes
- const votes: Vec<Hash> = await getCouncilCommitments(
- this.api,
- lastBlockHash
- );
- this.saveStats({
- electionApplicants: applicants.length,
- electionApplicantsStakes,
- electionVotes: votes.length,
- });
- }
- async fillValidatorInfo(startHash: Hash, endHash: Hash): Promise<void> {
- const startTimestamp: number = await getTimestamp(this.api, startHash);
- const endTimestamp: number = await getTimestamp(this.api, endHash);
- const blocks = this.statistics.newBlocks;
- const avgBlockProduction = (endTimestamp - startTimestamp) / 1000 / blocks;
- const maxStartValidators = await getValidatorCount(this.api, startHash);
- const startValidators = await getActiveValidators(this.api, startHash);
- const maxEndValidators = await getValidatorCount(this.api, endHash);
- const endValidators = await getActiveValidators(this.api, endHash, true);
- const startEra: number = await getEra(this.api, startHash);
- const endEra: number = await getEra(this.api, endHash);
- const startStake = await getEraStake(this.api, startHash, startEra);
- const endStake = await getEraStake(this.api, endHash, endEra);
- this.saveStats({
- avgBlockProduction: Number(avgBlockProduction.toFixed(2)),
- startValidators: startValidators.length + " / " + maxStartValidators,
- endValidators: endValidators.length + " / " + maxEndValidators,
- percValidators: getPercent(startValidators.length, endValidators.length),
- startValidatorsStake: startStake,
- endValidatorsStake: endStake,
- percNewValidatorsStake: getPercent(startStake, endStake),
- newValidatorRewards: await getValidatorsRewards(
- this.filterCache(filterMethods.newValidatorsRewards)
- ),
- });
- }
- async fillStorageProviderInfo(
- startBlock: number,
- endBlock: number,
- startHash: Hash,
- endHash: Hash
- ): Promise<void> {
- let storageProvidersRewards = await this.computeWorkingGroupReward(
- startHash,
- endHash,
- "storage"
- );
- const newStorageProviderReward = Number(
- storageProvidersRewards.rewards.toFixed(2)
- );
- const startStorageProvidersStake = storageProvidersRewards.startStake;
- const endStorageProvidersStake = storageProvidersRewards.endStake;
- const group = "storageWorkingGroup";
- const startStorageProviders = await getWorkers(this.api, group, startHash);
- const endStorageProviders = await getWorkers(this.api, group, endHash);
- this.saveStats({
- newStorageProviderReward,
- startStorageProvidersStake,
- endStorageProvidersStake,
- percNewStorageProviderStake: getPercent(
- startStorageProvidersStake,
- endStorageProvidersStake
- ),
- startStorageProviders,
- endStorageProviders,
- percNewStorageProviders: getPercent(
- startStorageProviders,
- endStorageProviders
- ),
- });
- }
- async fillCuratorInfo(startHash: Hash, endHash: Hash): Promise<void> {
- const group = "contentDirectoryWorkingGroup";
- const startCurators = await getWorkers(this.api, group, startHash);
- const endCurators = await getWorkers(this.api, group, endHash);
- this.saveStats({
- startCurators,
- endCurators,
- percNewCurators: getPercent(startCurators, endCurators),
- });
- }
- async fillOperationsInfo(
- startBlock: number,
- endBlock: number,
- startHash: Hash,
- endHash: Hash
- ): Promise<void> {
- const operationsRewards = await this.computeWorkingGroupReward(
- startHash,
- endHash,
- "operations"
- );
- const newOperationsReward = operationsRewards.rewards.toFixed(2);
- const startOperationsStake = operationsRewards.startStake;
- const endOperationsStake = operationsRewards.endStake;
- const group = "operationsWorkingGroup";
- const startWorkers = await getWorkers(this.api, group, startHash);
- const endWorkers = await getWorkers(this.api, group, endHash);
- this.saveStats({
- newOperationsReward: Number(newOperationsReward),
- startOperationsWorkers: startWorkers,
- endOperationsWorkers: endWorkers,
- percNewOperationsWorkers: getPercent(startWorkers, endWorkers),
- startOperationsStake,
- endOperationsStake,
- percNewOperationstake: getPercent(
- startOperationsStake,
- endOperationsStake
- ),
- });
- }
- async fillMembershipInfo(startHash: Hash, endHash: Hash): Promise<void> {
- const startMembers = await getNextMember(this.api, startHash);
- const endMembers = await getNextMember(this.api, endHash);
- this.saveStats({
- startMembers,
- endMembers,
- newMembers: endMembers - startMembers,
- percNewMembers: getPercent(startMembers, endMembers),
- });
- }
- async fillMediaUploadInfo(startHash: Hash, endHash: Hash): Promise<void> {
- console.log(`Collecting Media stats`);
- const startMedia = Number(await getNextVideo(this.api, startHash));
- const endMedia = Number(await getNextVideo(this.api, endHash));
- const startChannels = Number(await getNextChannel(this.api, startHash));
- const endChannels = Number(await getNextChannel(this.api, endHash));
- // count size
- let startUsedSpace = 0;
- let endUsedSpace = 0;
- const startBlock = await getBlock(this.api, startHash);
- const endBlock = await getBlock(this.api, endHash);
- getDataObjects(this.api).then((dataObjects: Map<ContentId, DataObject>) => {
- for (let [key, dataObject] of dataObjects) {
- const added = dataObject.added_at.block.toNumber();
- const start = startBlock.block.header.number.toNumber();
- const end = endBlock.block.header.number.toNumber();
- if (added < start)
- startUsedSpace += dataObject.size_in_bytes.toNumber() / 1024 / 1024;
- if (added < end)
- endUsedSpace += dataObject.size_in_bytes.toNumber() / 1024 / 1024;
- }
- if (!startUsedSpace || !endUsedSpace)
- console.log(`space start, end`, startUsedSpace, endUsedSpace);
- this.saveStats({
- startMedia,
- endMedia,
- percNewMedia: getPercent(startMedia, endMedia),
- startChannels,
- endChannels,
- percNewChannels: getPercent(startChannels, endChannels),
- startUsedSpace: Number(startUsedSpace.toFixed(2)),
- endUsedSpace: Number(endUsedSpace.toFixed(2)),
- percNewUsedSpace: getPercent(startUsedSpace, endUsedSpace),
- });
- });
- }
- async fillForumInfo(startHash: Hash, endHash: Hash): Promise<void> {
- const startPosts = await getNextPost(this.api, startHash);
- const endPosts = await getNextPost(this.api, endHash);
- const startThreads = await getNextThread(this.api, startHash);
- const endThreads = await getNextThread(this.api, endHash);
- const startCategories = await getNextCategory(this.api, startHash);
- const endCategories = await getNextCategory(this.api, endHash);
- this.saveStats({
- startPosts,
- endPosts,
- newPosts: endPosts - startPosts,
- percNewPosts: getPercent(startPosts, endPosts),
- startThreads,
- endThreads,
- newThreads: endThreads - startThreads,
- percNewThreads: getPercent(startThreads, endThreads),
- startCategories,
- endCategories,
- newCategories: endCategories - startCategories,
- perNewCategories: getPercent(startCategories, endCategories),
- });
- }
- async getFiatEvents(
- startBlockHeight: number,
- endBlockHeight: number,
- statusUrl: string
- ) {
- let sumerGenesis = new Date("2021-04-07T18:20:54.000Z");
- console.log("Fetching fiat events....");
- await axios.get(statusUrl).then(({ data }) => {
- const { burns, exchanges, dollarPoolChanges } = data as StatusData;
- console.log("# Exchanges");
- let filteredExchanges = exchanges.filter(
- (exchange) =>
- exchange.blockHeight >= startBlockHeight &&
- exchange.blockHeight <= endBlockHeight &&
- new Date(exchange.date) > sumerGenesis
- );
- for (let filteredExchange of filteredExchanges) {
- console.log(
- `Block: ${filteredExchange.blockHeight}, USD: ${filteredExchange.amountUSD}`
- );
- }
- let filteredBurns = burns.filter(
- (burn: any) =>
- burn.blockHeight >= startBlockHeight &&
- burn.blockHeight <= endBlockHeight &&
- new Date(burn.date) > sumerGenesis
- );
- if (filteredBurns.length) {
- console.log("# Burns");
- filteredBurns.forEach(({ blockHeight, amount }) =>
- console.log(`Block: ${blockHeight}, tJOY: ${amount}`)
- );
- }
- console.log("# Dollar Pool Changes");
- const allDollarPoolChanges = dollarPoolChanges.filter(
- (dollarPoolChange: any) =>
- dollarPoolChange.blockHeight >= startBlockHeight &&
- dollarPoolChange.blockHeight <= endBlockHeight &&
- new Date(dollarPoolChange.blockTime) > sumerGenesis
- );
- const filteredDollarPoolChanges = dollarPoolChanges.filter(
- (dollarPoolChange: any) =>
- dollarPoolChange.blockHeight >= startBlockHeight &&
- dollarPoolChange.blockHeight <= endBlockHeight &&
- dollarPoolChange.change > 0 &&
- new Date(dollarPoolChange.blockTime) > sumerGenesis
- );
- let dollarPoolRefills = ``;
- if (filteredDollarPoolChanges.length > 0) {
- dollarPoolRefills =
- "| Refill, USD | Reason | Block # |\n|---------------------|--------------|--------------|\n";
- filteredDollarPoolChanges.forEach(({ blockHeight, change, reason }) => {
- console.log(
- `Block: ${blockHeight}, USD: ${change}, Reason: ${reason}`
- );
- dollarPoolRefills += `| ${change} | ${reason} | ${blockHeight} |\n`;
- });
- }
- // calculate inflation
- let startTermExchangeRate = 0;
- let endTermExchangeRate = 0;
- if (filteredExchanges.length) {
- const lastExchangeEvent =
- filteredExchanges[filteredExchanges.length - 1];
- startTermExchangeRate = filteredExchanges[0].price * 1000000;
- endTermExchangeRate = lastExchangeEvent.price * 1000000;
- } else {
- startTermExchangeRate =
- filteredDollarPoolChanges[0].rateAfter * 1000000;
- const lastEvent =
- filteredDollarPoolChanges[filteredDollarPoolChanges.length - 1];
- endTermExchangeRate = lastEvent.rateAfter * 1000000;
- }
- let inflationPct = getPercent(endTermExchangeRate, startTermExchangeRate);
- console.log(
- "# USD / 1M tJOY Rate\n",
- `@ Term start (block #${startBlockHeight}: ${startTermExchangeRate}\n`,
- `@ Term end (block #${endBlockHeight}: ${endTermExchangeRate}\n`,
- `Inflation: ${inflationPct}`
- );
- const startDollarPool =
- allDollarPoolChanges[0].change > 0
- ? allDollarPoolChanges[0].valueAfter - allDollarPoolChanges[0].change
- : allDollarPoolChanges[0].valueAfter;
- const endDollarEvent =
- allDollarPoolChanges[allDollarPoolChanges.length - 1];
- const endDollarPool = endDollarEvent.valueAfter;
- const dollarPoolPctChange = getPercent(startDollarPool, endDollarPool);
- this.saveStats({
- startTermExchangeRate: startTermExchangeRate.toFixed(2),
- endTermExchangeRate: endTermExchangeRate.toFixed(2),
- inflationPct,
- startDollarPool: startDollarPool.toFixed(2),
- endDollarPool: endDollarPool.toFixed(2),
- dollarPoolPctChange,
- dollarPoolRefills,
- });
- });
- }
- async buildBlocksEventCache(
- startBlock: number,
- endBlock: number,
- cacheDir: string
- ): Promise<void> {
- const cacheFile = `${cacheDir}/${startBlock}-${endBlock}.json`;
- const exists = await fs
- .access(cacheFile, fsSync.constants.R_OK)
- .then(() => true)
- .catch(() => false);
- if (!exists) {
- console.log("Building events cache...");
- let blocksEvents = new Map<number, CacheEvent[]>();
- for (let i = startBlock; i < endBlock; ++i) {
- process.stdout.write("\rCaching block: " + i + " until " + endBlock);
- const blockHash: Hash = await getBlockHash(this.api, i);
- let eventRecord: EventRecord[] = [];
- try {
- eventRecord = await getEvents(this.api, blockHash);
- } catch (e) {
- console.warn(`Failed to get events.`, e);
- }
- let cacheEvents = new Array<CacheEvent>();
- for (let { event } of eventRecord) {
- if (!event) {
- console.warn(`empty event record`);
- continue;
- }
- cacheEvents.push(
- new CacheEvent(event.section, event.method, event.data)
- );
- }
- blocksEvents.set(i, cacheEvents);
- }
- console.log("\nFinish events cache...");
- const json = JSON.stringify(Array.from(blocksEvents.entries()), null, 2);
- fsSync.writeFileSync(cacheFile, json);
- this.blocksEventsCache = new Map(JSON.parse(json));
- } else {
- console.log("Cache file found, loading it...");
- let fileData = await fs.readFile(cacheFile);
- this.blocksEventsCache = new Map(JSON.parse(fileData));
- }
- }
- }
|