Browse Source

Add more fields to the report

Ricardo Maltez 4 years ago
parent
commit
05a17351dd

+ 1 - 1
council/report-generator/.gitignore

@@ -2,4 +2,4 @@
 lib/*
 node_modules
 yarn.lock
-report.md
+./report.md

+ 26 - 30
council/report-generator/report-template.md

@@ -30,18 +30,16 @@ This is a report which explains the current state of the Joystream network in nu
 ### 2.1 Token generation breakdown
 | Property                    | This Session | All Sessions | % Change |
 |-----------------------------|--------------|--------------|----------|
-| Total Tokens Minted         |              |              |          |
-| Validator Role              |              |              |          |
-| Storage Role                |              |              |          |
+| Total Tokens Minted         |{totalMinted}            |              |          |
+| Validator Role              |{newValidatorReward}|              |          |
+| Storage Role                |{newStorageProviderReward}              |              |          |
 | Council Role                |              |              |          |
 
 ### 2.2 Mints 
-| Property                    | This Session | All Sessions | % Change |
-|-----------------------------|--------------|--------------|----------|
-| Council Mint Capacity        |              |              |          |
-| Council Mint Total Minted       |              |              |          |
-| Curator Mint Capacity |              |              |          |
-| Curator Mint Total Minted |              |              |          |
+| Property                  | This Session | All Sessions | % Change |
+|---------------------------|--------------|--------------|----------|
+| Council Mint Total Minted |{newCouncilMinted}|              |          |
+| Curator Mint Total Minted |{newCuratorMinted}|              |          |
 
 ## 3.0 Council
 * Council session #: {councilRound}
@@ -50,23 +48,23 @@ This is a report which explains the current state of the Joystream network in nu
 ### 3.1 Elections
 | Property                    | This Session | All Sessions | % Change |
 |-----------------------------|--------------|--------------|----------|
-| Total Applicants            |{councilApplicants}|{councilAvgApplicants}||
-| Total Applicant Stake       |{councilApplicants}|              |          |
-| Total Votes                 |                   |              |          |
-| Avg Votes per Applicant     |                   |              |          |
+| Total Applicants            |{electionApplicants}      |{electionAvgApplicants}||
+| Total Applicant Stake       |{electionApplicantsStakes}|              |          |
+| Total Votes                 |{electionVotes}           |              |          |
+| Avg Votes per Applicant     |{avgVotePerApplicant}     |              |          |
 
 ### 3.2 Proposals
 | Proposal Type                           | # of proposals during this session | Total number of proposal type |
 |-----------------------------------------|------------------------------------|-------------------------------|
-| Text                                    |                                    |                               |
-| Runtime Upgrade                         |                                    |                               |
-| Set Election Parameters                 |                                    |                               |
-| Spending                                |                                    |                               |
-| Set Lead                                |                                    |                               |
-| Set Content Working Group Mint Capacity |                                    |                               |
-| Evict Storage Provider                  |                                    |                               |
-| Set Validator Count                     |                                    |                               |
-| Set Storage Role Parameters             |                                    |                               |
+| Text                                    | {newTextProposals}                              |                               |
+| Runtime Upgrade                         | {newRuntimeUpgradeProposal}                    |                               |
+| Set Election Parameters                 | {newSetElectionParametersProposal}              |                               |
+| Spending                                | {newSpendingProposal}                           |                               |
+| Set Lead                                | {newSetLeadProposal}                            |                               |
+| Set Content Working Group Mint Capacity | {newSetContentWorkingGroupMintCapacityProposal} |                               |
+| Evict Storage Provider                  | {newEvictStorageProviderProposal}               |                               |
+| Set Validator Count                     | {newSetValidatorCountProposal}                  |                               |
+| Set Storage Role Parameters             | {newSetStorageRoleParametersProposal}           |                               |
 * Average time for proposal vote success:
 * Average overall time for proposal vote success:
 
@@ -74,10 +72,10 @@ This is a report which explains the current state of the Joystream network in nu
 ### 4.1 Validator Information
 | Property                    | This Session | All Sessions | % Change |
 |-----------------------------|--------------|--------------|----------|
-| Number of validators        | {nrValidators}             |              |          |
+| Number of validators        | {avgValidators}             |              |          |
 | Validator total stake       |              |              |          |
 | Average stake per validator |              |              |          |
-| Tokens generated by validator role |              |              |          |
+| Tokens generated by validator role |{newValidatorReward}            |              |          |
 
 ### 4.2 Storage Role
 | Property                | This Session | All Sessions | % Change |
@@ -91,8 +89,6 @@ This is a report which explains the current state of the Joystream network in nu
 ### 4.3 Curator Role
 | Property                | This Session | All Sessions | % Change |
 |-------------------------|--------------|--------------|----------|
-| Mint Capacity |              |              |          |
-| Mint Tokens Generated (Total)    |              |              |          |
 | Curator roles filled     |              |              |          |
 
 
@@ -100,10 +96,10 @@ This is a report which explains the current state of the Joystream network in nu
 ### 5.1 Media & Uploads
 | Property                | This Session | All Sessions | % Change |
 |-------------------------|--------------|--------------|----------|
-| Number of uploads       |      {newMedia}        |              |          |
-| Size of content         |   {totalNewUsedSpace}           |              |          |
-| Average size of content |   {avgNewContentSize}           |              |          |
-| Number of channels      |  {newChannels}        | {nrTotalChannels}             |          |
+| Number of uploads       | {newMedia}       |{totalMedia} | {percNewMedia} |
+| Size of content         | {newUsedSpace} | {totalUsedSpace} | {percNewUsedSpace} |
+| Average size of content | {avgNewSizePerContent} |  {totalAvgSizePerContent} | {percAvgSizePerContent}|
+| Number of channels      | {newChannels} | {totalChannels} | {percNewChannels} |
 | Avg. uploads per channel      |              |              |          |
 
 ### 5.2 Forum Activity

+ 73 - 9
council/report-generator/src/StatisticsData.ts

@@ -2,9 +2,13 @@ export class StatisticsData {
     councilRound: number;
     councilMembers: number;
 
-    councilApplicants: number;
-    councilAvgApplicants: number;
-    perCouncilApplicants: number;
+    electionApplicants: number;
+    electionAvgApplicants: number = 0;
+    perElectionApplicants: number;
+
+    electionApplicantsStakes: number;
+    electionVotes: number;
+    avgVotePerApplicant: number;
 
     dateStart: string;
     dateEnd: string;
@@ -21,11 +25,13 @@ export class StatisticsData {
     totalMembers: number;
     percNewMembers: number;
 
-
-
     newBlocks: number;
     avgBlockProduction: number;
-    nrValidators: number;
+
+    avgValidators: number;
+    newValidatorReward: number;
+
+    newStorageProviderReward: number;
 
     newThreads: number;
     totalThreads: number;
@@ -38,17 +44,75 @@ export class StatisticsData {
     newCategories: number;
 
     newProposals: number;
+
+    newChannels: number;
+    totalChannels: number;
+    percNewChannels: number;
+
     newMedia: number;
+    totalMedia: number;
+    percNewMedia: number;
+
     deletedMedia: number;
     createdMints: number;
     totalMinted: number;
     totalMintCapacityIncrease: number;
-    // totalBurned: number;
-    totalNewUsedSpace: number;
+
+    newCouncilMinted: number;
+    newCuratorMinted: number;
+
+    newTokensBurn: number;
+
+    newUsedSpace: number;
+    totalUsedSpace: number;
+    percNewUsedSpace: number;
+
+    avgNewSizePerContent: number;
+    totalAvgSizePerContent: number;
+    percAvgSizePerContent: number;
+
     newStakes: number;
     totalNewStakeValue: number;
 
+    newTextProposals: number = 0;
+    newRuntimeUpgradeProposal: number = 0;
+    newSetElectionParametersProposal: number = 0;
+    newSpendingProposal: number = 0;
+    newSetLeadProposal:number = 0;
+    newSetContentWorkingGroupMintCapacityProposal: number = 0;
+    newEvictStorageProviderProposal: number = 0;
+    newSetValidatorCountProposal: number = 0;
+    newSetStorageRoleParametersProposal: number = 0;
+
+
     constructor() {
     }
 
-}
+}
+
+export class ValidatorReward{
+    sharedReward: number;
+    remainingReward: number;
+    validators: number;
+    slotStake: number;
+    blockNumber: number;
+}
+
+export class Exchange{
+    sender: string;
+    amount: number;
+    fees: number;
+    blockNumber: number;
+}
+
+export enum ProposalTypes {
+    Text= "Text",
+    RuntimeUpgrade = "RuntimeUpgrade",
+    SetElectionParameters = "SetElectionParameters",
+    Spending = "Spending",
+    SetLead = "SetLead",
+    SetContentWorkingGroupMintCapacity = "SetContentWorkingGroupMintCapacity",
+    EvictStorageProvider = "EvictStorageProvider",
+    SetValidatorCount = "SetValidatorCount",
+    SetStorageRoleParameters = "SetStorageRoleParameters",
+}

+ 246 - 39
council/report-generator/src/statistics.ts

@@ -1,9 +1,9 @@
 import {rpc} from "@polkadot/types/interfaces/definitions";
 import {ApiPromise, WsProvider} from "@polkadot/api";
 import {registerJoystreamTypes} from '@joystream/types';
-import {AccountId, Balance, Hash, Moment} from "@polkadot/types/interfaces";
-import {ClassId, ClassPropertyValue, Entity} from "@joystream/types/lib/versioned-store";
-import {Mint} from "@joystream/types/lib/mint";
+import {AccountId, Balance, BlockNumber, EventRecord, Hash, Moment} from "@polkadot/types/interfaces";
+import {ClassId, ClassPropertyValue, Entity, EntityId} from "@joystream/types/lib/versioned-store";
+import {Mint, MintId} from "@joystream/types/lib/mint";
 import {Option, u32, Vec} from "@polkadot/types";
 import {ContentId, DataObject} from "@joystream/types/lib/media";
 import {Stake, StakeId} from "@joystream/types/lib/stake";
@@ -11,12 +11,18 @@ import Linkage from "@polkadot/types/codec/Linkage";
 import {CategoryId, PostId, ThreadId} from "@joystream/types/lib/forum";
 import {MemberId} from "@joystream/types/lib/members";
 import number from "@polkadot/util/is/number";
-import {StatisticsData} from "./StatisticsData";
-import {Seats} from "@joystream/types/lib/proposals";
+import {Exchange, ProposalTypes, StatisticsData, ValidatorReward} from "./StatisticsData";
+import {ProposalDetails, Seats} from "@joystream/types/lib/proposals";
+import {ApplicationStage} from "@joystream/types/lib/hiring";
+import {Stake as ApplicantStake} from "@joystream/types/lib";
+import {RoleParameters} from "@joystream/types/lib/roles";
+import {ChannelId, CuratorId} from "@joystream/types/lib/content-working-group";
 
 const BURN_ADDRESS = '5D5PhZQNJzcJXVBxwJxZcsutjKPqUPydrvpu6HeiBfMaeKQu';
 
 const FIRST_COUNCIL_BLOCK = 908796;
+const COUNCIL_ROUND_OFFSET = 5;
+
 
 class Media {
 
@@ -27,27 +33,32 @@ class Media {
 export class StatisticsCollector {
     static async getStatistics(startBlock: number, endBlock: number): Promise<StatisticsData> {
 
-        // Initialise the provider to connect to the local node
-        // const provider = new WsProvider('ws://127.0.0.1:9944');
-
-        const provider = new WsProvider('ws://127.0.0.1:9944');
-        // register types before creating the api
-        registerJoystreamTypes();
-
-        // Create the API and wait until ready
-        const api = await ApiPromise.create({provider});
+        const api = await this.connectApi();
 
         let startHash = await api.rpc.chain.getBlockHash(startBlock);
         let endHash = await api.rpc.chain.getBlockHash(endBlock);
 
         let statistics = new StatisticsData();
 
-        statistics.councilRound = (await api.query.councilElection.round.at(startHash) as u32).toNumber() - 5;
+        statistics.councilRound = (await api.query.councilElection.round.at(startHash) as u32).toNumber() - COUNCIL_ROUND_OFFSET;
         let seats = await api.query.council.activeCouncil.at(startHash) as Seats;
 
         statistics.councilMembers = seats.length;
-        let applicants = await api.query.councilElection.applicants() as Vec<AccountId>;
-        statistics.councilApplicants = applicants.length;
+        let applicants = await api.query.councilElection.applicants.at(startHash) as Vec<AccountId>;
+        statistics.electionApplicants = applicants.length;
+
+        statistics.electionApplicantsStakes = 0;
+        for (let applicant of applicants) {
+            let applicantStakes = await api.query.councilElection.applicantStakes.at(startHash, applicant) as unknown as ApplicantStake;
+            statistics.electionApplicantsStakes += applicantStakes.new.toNumber();
+        }
+        statistics.electionVotes = seats.map((seat) => seat.backers.length).reduce((a, b) => a + b);
+
+        if (statistics.electionVotes) {
+            statistics.avgVotePerApplicant = statistics.electionVotes / statistics.electionApplicants;
+        } else {
+            statistics.avgVotePerApplicant = 0;
+        }
 
         let startDate = await api.query.timestamp.now.at(startHash) as Moment;
         let endDate = await api.query.timestamp.now.at(endHash) as Moment;
@@ -90,13 +101,7 @@ export class StatisticsCollector {
         //
         // statistics.totalBurned = endBurnedTokens.toNumber() - startBurnedTokens.toNumber();
 
-        let startDataObjects = await api.query.dataDirectory.knownContentIds.at(startHash) as Vec<ContentId>;
-        let startUsedSpace = await this.computeUsedSpaceInBytes(api, startDataObjects);
-
-        let endDataObjects = await api.query.dataDirectory.knownContentIds.at(endHash) as Vec<ContentId>;
-        let endUsedSpace = await this.computeUsedSpaceInBytes(api, endDataObjects);
 
-        statistics.totalNewUsedSpace = endUsedSpace - startUsedSpace;
 
         let startNrMints = parseInt((await api.query.minting.mintsCreated.at(startHash)).toString());
         let endNrMints = parseInt((await api.query.minting.mintsCreated.at(endHash)).toString());
@@ -104,23 +109,72 @@ export class StatisticsCollector {
         statistics.createdMints = endNrMints - startNrMints;
 
         for (let i = 0; i < startNrMints; ++i) {
-            let startMintResult = await api.query.minting.mints.at(startHash, i) as unknown as [2];
-            let startMint = startMintResult[0] as unknown as Mint;
+            let startMintResult = await api.query.minting.mints.at(startHash, i) as unknown as [Mint, Linkage<MintId>];
+            let startMint = startMintResult[0];
 
-            let endMintResult = await api.query.minting.mints.at(endHash, i) as unknown as [2];
-            let endMint = endMintResult[0] as unknown as Mint;
+            let endMintResult = await api.query.minting.mints.at(endHash, i) as unknown as [Mint, Linkage<MintId>];
+            let endMint = endMintResult[0];
 
             statistics.totalMinted = parseInt(endMint.getField('total_minted').toString()) - parseInt(startMint.getField('total_minted').toString());
             statistics.totalMintCapacityIncrease = parseInt(endMint.getField('capacity').toString()) - -parseInt(startMint.getField('capacity').toString());
         }
 
-        // let startMedias = await this.getMedia(api, startHash);
-        // let endMedias = await this.getMedia(api, endHash);
-        //
-        // let newMedia = endMedias.filter((endMedia) => {
-        //     return !startMedias.some((startMedia) => startMedia.id == endMedia.id);
-        // });
-        // statistics.newMedia = newMedia.length;
+        for (let i = startNrMints; i < endNrMints; ++i) {
+            let endMintResult = await api.query.minting.mints.at(endHash, i) as unknown as [Mint, Linkage<MintId>];
+            let endMint = endMintResult[0] as Mint;
+            statistics.totalMinted = parseInt(endMint.getField('total_minted').toString());
+        }
+
+        let councilMint = await api.query.council.councilMint.at(endHash) as Option<MintId>;
+        let curatorMint = await api.query.contentWorkingGroup.mint.at(endHash) as MintId;
+
+        let startCouncilMintResult = await api.query.minting.mints.at(startHash, councilMint.unwrap()) as unknown as [Mint, Linkage<MintId>];
+        let startCouncilMint = startCouncilMintResult[0] as unknown as Mint;
+
+        let endCouncilMintResult = await api.query.minting.mints.at(endHash, councilMint.unwrap()) as unknown as [Mint, Linkage<MintId>];
+        let endCouncilMint = endCouncilMintResult[0] as unknown as Mint;
+
+        let startCuratorMintResult = await api.query.minting.mints.at(startHash, curatorMint.toNumber()) as unknown as [Mint, Linkage<MintId>];
+        let startCuratorMint = startCuratorMintResult[0] as unknown as Mint;
+
+        let endCuratorMintResult = await api.query.minting.mints.at(endHash, curatorMint.toNumber()) as unknown as [Mint, Linkage<MintId>];
+        let endCuratorMint = endCuratorMintResult[0] as unknown as Mint;
+
+        statistics.newCouncilMinted = parseInt(endCouncilMint.getField('total_minted').toString()) - parseInt(startCouncilMint.getField('total_minted').toString());
+        statistics.newCuratorMinted = parseInt(endCuratorMint.getField('total_minted').toString()) - parseInt(startCuratorMint.getField('total_minted').toString());
+
+        let startNrChannels = (await api.query.contentWorkingGroup.nextChannelId.at(startHash) as ChannelId).toNumber() - 1;
+        let endNrChannels = (await api.query.contentWorkingGroup.nextChannelId.at(endHash) as ChannelId).toNumber() - 1;
+
+        statistics.newChannels = endNrChannels - startNrChannels;
+        statistics.totalChannels = endNrChannels;
+        statistics.percNewChannels = this.convertToPercentage(statistics.newChannels, statistics.totalChannels);
+
+        let startMedias = await this.getMedia(api, startHash);
+        let endMedias = await this.getMedia(api, endHash);
+
+        let newMedia = endMedias.filter((endMedia) => {
+            return !startMedias.some((startMedia) => startMedia.id == endMedia.id);
+        });
+
+        statistics.newMedia = newMedia.length;
+        statistics.totalMedia = endMedias.length;
+        statistics.percNewMedia = this.convertToPercentage(statistics.newMedia, statistics.totalMedia);
+
+        let startDataObjects = await api.query.dataDirectory.knownContentIds.at(startHash) as Vec<ContentId>;
+        let startUsedSpace = await this.computeUsedSpaceInBytes(api, startDataObjects);
+
+        let endDataObjects = await api.query.dataDirectory.knownContentIds.at(endHash) as Vec<ContentId>;
+        let endUsedSpace = await this.computeUsedSpaceInBytes(api, endDataObjects);
+
+        statistics.newUsedSpace = endUsedSpace - startUsedSpace;
+        statistics.totalUsedSpace = endUsedSpace;
+        statistics.percNewUsedSpace = this.convertToPercentage(statistics.newUsedSpace, statistics.totalUsedSpace);
+
+        statistics.avgNewSizePerContent = Number((statistics.newUsedSpace / statistics.newMedia).toFixed(2));
+        statistics.totalAvgSizePerContent = Number((statistics.totalUsedSpace / statistics.totalMedia).toFixed(2));
+        statistics.percAvgSizePerContent = this.convertToPercentage(statistics.avgNewSizePerContent, statistics.totalAvgSizePerContent);
+
         //
         // for (let startMedia of startMedias) {
         //     let deleted = !endMedias.some((endMedia) => {
@@ -153,22 +207,166 @@ export class StatisticsCollector {
         let endCategoryId = await api.query.forum.nextCategoryId.at(endHash) as unknown as CategoryId;
         statistics.newCategories = endCategoryId.toNumber() - startCategoryId.toNumber();
 
-
         let startNrProposals = await api.query.proposalsEngine.proposalCount.at(startHash) as unknown as u32;
         let endNrProposals = await api.query.proposalsEngine.proposalCount.at(endHash) as unknown as u32;
         statistics.newProposals = endNrProposals.toNumber() - startNrProposals.toNumber();
 
+        for (let i = startNrProposals.toNumber(); i < endNrProposals.toNumber(); ++i) {
+            let proposalNumber = i - 1;
+            let proposalDetails = await api.query.proposalsCodex.proposalDetailsByProposalId.at(endHash, proposalNumber) as ProposalDetails;
+            switch (proposalDetails.type) {
+                case ProposalTypes.Text:
+                    ++statistics.newTextProposals;
+                    break;
+
+                case ProposalTypes.RuntimeUpgrade:
+                    ++statistics.newRuntimeUpgradeProposal;
+                    break;
+
+                case ProposalTypes.SetElectionParameters:
+                    ++statistics.newSetElectionParametersProposal;
+                    break;
+
+                case ProposalTypes.Spending:
+                    ++statistics.newSpendingProposal;
+                    break;
+
+                case ProposalTypes.SetLead:
+                    ++statistics.newSetLeadProposal;
+                    break;
+
+                case ProposalTypes.SetContentWorkingGroupMintCapacity:
+                    ++statistics.newSetContentWorkingGroupMintCapacityProposal;
+                    break;
+
+                case ProposalTypes.EvictStorageProvider:
+                    ++statistics.newEvictStorageProviderProposal;
+                    break;
+
+                case ProposalTypes.SetValidatorCount:
+                    ++statistics.newSetValidatorCountProposal;
+                    break;
+
+                case ProposalTypes.SetStorageRoleParameters:
+                    ++statistics.newSetStorageRoleParametersProposal;
+                    break;
+            }
+        }
+
+        let validatorRewards: ValidatorReward[] = [];
+        let exchangesCollection: Exchange[] = [];
+        let promises = [];
+
+        console.time('extractValidatorsRewards');
+        for (let i = startBlock; i < endBlock; ++i) {
+            let promise = (async () => {
+                const blockHash: Hash = await api.rpc.chain.getBlockHash(i);
+                const events = await api.query.system.events.at(blockHash) as Vec<EventRecord>;
+                let rewards = await this.extractValidatorsRewards(api, i, events);
+                if (rewards.length) {
+                    validatorRewards = validatorRewards.concat(rewards);
+                }
+                let exchanges = this.extractExchanges(i, events);
+                if (exchanges.length) {
+                    exchangesCollection = exchangesCollection.concat(exchanges);
+                }
+
+            })();
+            promises.push(promise);
+        }
+        await Promise.all(promises);
+        console.timeEnd('extractValidatorsRewards');
 
+        statistics.newValidatorReward = validatorRewards.map((validatorReward) => validatorReward.sharedReward).reduce((a, b) => a + b);
+        let avgValidators = validatorRewards.map((validatorReward) => validatorReward.validators).reduce((a, b) => a + b) / validatorRewards.length;
+        statistics.avgValidators = Number(avgValidators.toFixed(2));
+
+        statistics.newTokensBurn = exchangesCollection.map((exchange) => exchange.amount).reduce((a, b) => a + b);
+
+        statistics.newStorageProviderReward = await this.computeStorageRewards(api, startBlock, endBlock);
+
+        api.disconnect();
         return statistics;
     }
 
-   static convertToPercentage(value: number, totalValue: number): number{
+
+    static async extractValidatorsRewards(api: ApiPromise, blockNumber: number, events: Vec<EventRecord>): Promise<ValidatorReward[]> {
+        let valRewards = [];
+        // const api = await this.connectApi();
+        for (let {event} of events) {
+            if (event.section === 'staking' && event.method === 'Reward') {
+                const sharedReward = event.data[0] as Balance;
+                const remainingReward = event.data[1] as Balance;
+                const oldHash: Hash = await api.rpc.chain.getBlockHash(blockNumber - 1)
+                const slotStake = await api.query.staking.slotStake.at(oldHash) as Balance;
+                const validatorInfo = await api.query.staking.currentElected.at(oldHash) as AccountId;
+                const valReward = new ValidatorReward();
+                valReward.sharedReward = sharedReward.toNumber();
+
+
+                valReward.remainingReward = remainingReward.toNumber();
+                valReward.validators = validatorInfo.length;
+                valReward.slotStake = slotStake.toNumber();
+                // date: new Date(timestamp.toNumber()),
+                valReward.blockNumber = blockNumber;
+                // session: session.toNumber(),
+                // era: era.toNumber()
+
+                valRewards.push(valReward)
+            }
+        }
+        return valRewards;
+    }
+
+    static async computeStorageRewards(api: ApiPromise, startBlock: number, endBlock: number): Promise<number> {
+        let estimateOfStorageReward = 0
+        for (let blockHeight = startBlock; blockHeight < endBlock; blockHeight += 600) {
+            const blockHash: Hash = await api.rpc.chain.getBlockHash(blockHeight);
+            const storageProviders = (await api.query.actors.actorAccountIds.at(blockHash) as Vec<AccountId>).length;
+            const storageParameters = (await api.query.actors.parameters.at(blockHash, "StorageProvider") as Option<RoleParameters>).unwrap();
+            const reward = storageParameters.reward.toNumber();
+            const rewardPeriod = storageParameters.reward_period.toNumber();
+            estimateOfStorageReward += storageProviders * reward * rewardPeriod / 600;
+        }
+        return estimateOfStorageReward;
+    }
+
+    static extractExchanges(blockNumber: number, events: Vec<EventRecord>): Exchange[] {
+        let exchanges = [];
+        for (let {event} of events) {
+            if (event.section === 'balances' && event.method === 'Transfer') {
+                const recipient = event.data[1] as AccountId;
+                if (recipient.toString() === BURN_ADDRESS) {
+                    // For all events of "Transfer" type with matching recipient...
+                    const sender = event.data[0] as AccountId;
+                    const amountJOY = event.data[2] as Balance;
+                    const feesJOY = event.data[3] as Balance;
+                    //const memo = await api.query.memo.memo.at(blockHash, sender) as Text;
+                    let exchange = new Exchange();
+
+                    exchange.sender = sender.toString();
+                    // recipient: recipient.toString(),
+                    exchange.amount = amountJOY.toNumber();
+                    exchange.fees = feesJOY.toNumber();
+                    // date: new Date(timestamp.toNumber()),
+                    exchange.blockNumber = blockNumber;
+                    // session: session.toNumber(),
+                    // era: era.toNumber()
+
+                    exchanges.push(exchange)
+                }
+            }
+        }
+        return exchanges;
+    }
+
+    static convertToPercentage(value: number, totalValue: number):
+        number {
         return Number((value / totalValue * 100).toFixed(2));
     }
 
 
     static async computeUsedSpaceInBytes(api: ApiPromise, contentIds: Vec<ContentId>) {
-
         let space = 0;
         for (let contentId of contentIds) {
             let dataObject = await api.query.dataDirectory.dataObjectByContentId(contentId) as Option<DataObject>;
@@ -179,12 +377,11 @@ export class StatisticsCollector {
     }
 
     static async getMedia(api: ApiPromise, blockHash: Hash) {
-        let nrEntities = parseInt((await api.query.versionedStore.nextEntityId()).toString());
+        let nrEntities = (await api.query.versionedStore.nextEntityId.at(blockHash) as EntityId).toNumber();
 
         let medias: Media[] = [];
         for (let i = 0; i < nrEntities; ++i) {
             let entity = await api.query.versionedStore.entityById.at(blockHash, i) as unknown as Entity;
-            // console.log(entity);
 
             if (entity.class_id.toNumber() != 7) {
                 continue;
@@ -197,6 +394,16 @@ export class StatisticsCollector {
         }
         return medias;
     }
+
+    static async connectApi(): Promise<ApiPromise> {
+        const provider = new WsProvider('ws://127.0.0.1:9944');
+        // register types before creating the api
+        registerJoystreamTypes();
+
+        // Create the API and wait until ready
+        return await ApiPromise.create({provider});
+    }
+
 }