浏览代码

reify joystream library

Joystream Stats 3 年之前
当前提交
5b72c51293
共有 4 个文件被更改,包括 509 次插入0 次删除
  1. 281 0
      api.ts
  2. 41 0
      index.ts
  3. 60 0
      media.ts
  4. 127 0
      rewards.ts

+ 281 - 0
api.ts

@@ -0,0 +1,281 @@
+import { ApiPromise } from "@polkadot/api";
+import { Option, u32, Vec } from "@polkadot/types";
+import type { Codec, Observable } from "@polkadot/types/types";
+import {
+  AccountId,
+  Balance,
+  BalanceOf,
+  BlockNumber,
+  EraIndex,
+  EventRecord,
+  Hash,
+  Moment,
+} from "@polkadot/types/interfaces";
+import { SignedBlock } from "@polkadot/types/interfaces/runtime";
+
+import { PostId, ThreadId } from "@joystream/types/common";
+import { CategoryId } from "@joystream/types/forum";
+import { ElectionStake, SealedVote, Seats } from "@joystream/types/council";
+import { Mint, MintId } from "@joystream/types/mint";
+import { MemberId, Membership } from "@joystream/types/members";
+import { WorkerId } from "@joystream/types/working-group";
+import { Stake, StakeId } from "@joystream/types/stake";
+import {
+  RewardRelationship,
+  RewardRelationshipId,
+} from "@joystream/types/recurring-rewards";
+
+import { Entity, EntityId } from "@joystream/types/content-directory";
+import { ContentId, DataObject } from "@joystream/types/media";
+
+import { SpendingParams } from "@joystream/types/proposals";
+import { ProposalId, WorkerOf } from "@joystream/types/augment-codec/all";
+import { ProposalDetails, ProposalOf } from "@joystream/types/augment/types";
+
+// blocks
+export const getBlock = (api: ApiPromise, hash: Hash): Promise<SignedBlock> =>
+  api.rpc.chain.getBlock(hash);
+
+export const getBlockHash = (api: ApiPromise, block: number): Promise<Hash> =>
+  api.rpc.chain.getBlockHash(block);
+
+export const getTimestamp = (api: ApiPromise, hash: Hash): Promise<Moment> =>
+  api.query.timestamp.now.at(hash);
+
+export const getIssuance = (api: ApiPromise, hash: Hash): Promise<Balance> =>
+  api.query.balances.totalIssuance.at(hash);
+
+export const getEvents = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Vec<EventRecord>> => api.query.system.events.at(hash);
+
+export const getEra = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Option<EraIndex>> => api.query.staking.currentEra.at(hash);
+
+export const getEraStake = async (
+  api: ApiPromise,
+  hash: Hash,
+  era: EraIndex
+): Promise<number> =>
+  (await api.query.staking.erasTotalStake.at(hash, era)).toNumber();
+
+// council
+export const getCouncil = (api: ApiPromise, hash: Hash): Promise<Seats> =>
+  api.query.council.activeCouncil.at(hash);
+
+export const getCouncilRound = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.councilElection.round.at(hash)) as u32).toNumber();
+
+export const getCouncilSize = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.councilElection.councilSize.at(hash)) as u32).toNumber();
+
+export const getCouncilApplicants = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Vec<AccountId>> => api.query.councilElection.applicants.at(hash);
+
+export const getCouncilApplicantStakes = (
+  api: ApiPromise,
+  hash: Hash,
+  applicant: AccountId
+): Promise<ElectionStake> =>
+  api.query.councilElection.applicantStakes.at(hash, applicant);
+
+export const getCouncilCommitments = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Vec<Hash>> => api.query.councilElection.commitments.at(hash);
+
+export const getCouncilPayoutInterval = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Option<BlockNumber>> => api.query.council.payoutInterval.at(hash);
+
+export const getCouncilPayout = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<BalanceOf> => api.query.council.amountPerPayout.at(hash);
+
+const periods = [
+  "announcingPeriod",
+  "votingPeriod",
+  "revealingPeriod",
+  "newTermDuration",
+];
+export const getCouncilPeriods = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number>[] =>
+  periods.map(async (period: string) =>
+    ((await api.query.councilElection[period].at(
+      hash
+    )) as BlockNumber).toNumber()
+  );
+
+// working groups
+export const getNextWorker = async (
+  api: ApiPromise,
+  group: string,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query[group].nextWorkerId.at(hash)) as WorkerId).toNumber();
+
+export const getWorker = (
+  api: ApiPromise,
+  group: string,
+  hash: Hash,
+  id: number
+): Promise<WorkerOf> => api.query[group].workerById.at(hash, id);
+
+export const getWorkers = (
+  api: ApiPromise,
+  group: string,
+  hash: Hash
+): Promise<number> => api.query[group].activeWorkerCount.at(hash);
+
+export const getStake = async (
+  api: ApiPromise,
+  id: StakeId | number
+): Promise<Stake> => (await api.query.stake.stakes(id)) as Stake;
+
+export const getWorkerReward = (
+  api: ApiPromise,
+  hash: Hash,
+  id: RewardRelationshipId | number
+): Promise<RewardRelationship> =>
+  api.query.recurringRewards.rewardRelationships.at(hash, id);
+
+// mints
+export const getCouncilMint = (api: ApiPromise, hash: Hash): Promise<MintId> =>
+  api.query.council.councilMint.at(hash);
+
+export const getGroupMint = (
+  api: ApiPromise,
+  group: string,
+  hash: Hash
+): Promise<MintId> => api.query[group].mint.at(hash);
+
+export const getMintsCreated = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> => parseInt(await api.query.minting.mintsCreated.at(hash));
+
+export const getMint = (
+  api: ApiPromise,
+  hash: Hash,
+  id: MintId | number
+): Promise<Mint> => api.query.minting.mints.at(hash, id);
+
+// members
+export const getNextMember = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.members.nextMemberId.at(hash)) as MemberId).toNumber();
+
+export const getMember = (
+  api: ApiPromise,
+  hash: Hash,
+  id: MemberId
+): Promise<Membership> => api.query.members.membershipById.at(hash, id);
+
+// forum
+export const getNextPost = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.forum.nextPostId.at(hash)) as PostId).toNumber();
+
+export const getNextThread = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.forum.nextThreadId.at(hash)) as ThreadId).toNumber();
+
+export const getNextCategory = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.forum.nextCategoryId.at(hash)) as CategoryId).toNumber();
+
+// proposals
+export const getProposalCount = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.proposalsEngine.proposalCount.at(hash)) as u32).toNumber();
+
+export const getProposalInfo = async (
+  api: ApiPromise,
+  id: ProposalId
+): Promise<ProposalOf> =>
+  (await api.query.proposalsEngine.proposals(id)) as ProposalOf;
+
+export const getProposalDetails = async (
+  api: ApiPromise,
+  id: ProposalId
+): Promise<ProposalDetails> =>
+  (await api.query.proposalsCodex.proposalDetailsByProposalId(
+    id
+  )) as ProposalDetails;
+
+// validators
+export const getValidatorCount = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.staking.validatorCount.at(hash)) as u32).toNumber();
+
+export const getValidators = (
+  api: ApiPromise,
+  hash: Hash
+): Promise<Option<Vec<AccountId>>> =>
+  api.query.staking.snapshotValidators.at(hash);
+
+// media
+export const getNextEntity = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> =>
+  ((await api.query.contentDirectory.nextEntityId.at(
+    hash
+  )) as EntityId).toNumber();
+
+export const getNextChannel = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> => api.query.content.nextChannelId.at(hash);
+
+export const getNextVideo = async (
+  api: ApiPromise,
+  hash: Hash
+): Promise<number> => api.query.content.nextVideoId.at(hash);
+
+export const getEntity = (
+  api: ApiPromise,
+  hash: Hash,
+  id: number
+): Promise<Entity> => api.query.contentDirectory.entityById.at(hash, id);
+
+export const getDataObjects = async (
+  api: ApiPromise
+): Promise<Map<ContentId, DataObject>> =>
+  ((await api.query.dataDirectory.dataByContentId.entries()) as unknown) as Map<
+    ContentId,
+    DataObject
+  >;
+
+export const getDataObject = async (
+  api: ApiPromise,
+  id: ContentId
+): Promise<Option<DataObject>> =>
+  (await api.query.dataDirectory.dataByContentId(id)) as Option<DataObject>;

+ 41 - 0
index.ts

@@ -0,0 +1,41 @@
+import { CacheEvent } from "../types";
+import { Mint, MintId } from "@joystream/types/mint";
+import { Moment } from "@polkadot/types/interfaces";
+
+export const getPercent = (value1: number, value2: number): number => {
+  if (value1 == 0) return value2 > 0 ? Infinity : 0;
+  return Number(((value2 * 100) / value1 - 100).toFixed(2));
+};
+
+export const momentToString = (moment: Moment) =>
+  new Date(moment.toNumber()).toLocaleDateString("en-US");
+
+export const getTotalMinted = (mint: Mint) =>
+  Number(mint.getField("total_minted").toString());
+
+export const eventStats = (blockEventsCache: Map<number, CacheEvent[]>) => {
+  let sections: {
+    [key: string]: { [key: string]: [{ key: number; data: any }] };
+  } = {};
+  for (let [key, blockEvents] of blockEventsCache) {
+    blockEvents.map(({ section, method, data }) => {
+      if (!sections[section]) sections[section] = {};
+      if (sections[section][method])
+        sections[section][method].push({ key, data });
+      else sections[section][method] = [{ key, data }];
+      if (section === "system" && method === "ExtrinsicSuccess") return;
+      if (section === "imOnline" && method === "HeartbeatReceived") return;
+      if (section === "imOnline" && method === "AllGood") return;
+      if (section === "utility" && method === "BatchCompleted") return;
+      if (section === "grandpa" && method === "NewAuthorities") return;
+      if (section === "session" && method === "NewSession") return;
+      //console.log(section, method, data);
+    });
+  }
+  console.log(`Events:`);
+  Object.keys(sections).map((section: string) =>
+    Object.keys(sections[section]).map((method: string) =>
+      console.log(` ${section}.${method}: ${sections[section][method].length}`)
+    )
+  );
+};

+ 60 - 0
media.ts

@@ -0,0 +1,60 @@
+const getEntities = async (blockHash: Hash): Promise<Map<number, Entity>> => {
+  let nrEntities: number = await getNextEntity(this.api, blockHash);
+  let entities = new Map<number, Entity>();
+  for (let i = 0; i < nrEntities; ++i) {
+    const entity: Entity = await getEntity(this.api, blockHash, i);
+    entities.set(i, entity);
+  }
+  return entities;
+};
+
+export const computeUsedSpaceInMbs = async (
+  contentIds: Vec<ContentId>
+): Promise<number> => {
+  let space = 0;
+  for (let contentId of contentIds) {
+    const dataObject: Option<DataObject> = await getDataObject(
+      this.api,
+      contentId
+    );
+    space += dataObject.unwrap().size_in_bytes.toNumber();
+  }
+  return space / 1024 / 1024;
+};
+
+export const parseVideos = async (
+  entities: Map<number, Entity>
+): Promise<Media[]> => {
+  let videos: Media[] = [];
+  for (let [key, entity] of entities) {
+    if (entity.class_id.toNumber() != VIDEO_CLASS_iD || entity.values.isEmpty)
+      continue;
+    let values = Array.from(entity.getField("values").entries());
+    if (values.length < 2 || values[2].length < 1) continue;
+
+    let title = values[2][1].getValue().toString();
+    videos.push(new Media(key, title));
+  }
+
+  return videos;
+};
+
+const parseChannels = async (
+  entities: Map<number, Entity>
+): Promise<Channel[]> => {
+  let channels: Channel[] = [];
+
+  for (let [key, entity] of entities) {
+    if (
+      entity.class_id.toNumber() != CHANNEL_CLASS_iD ||
+      entity.values.isEmpty
+    ) {
+      continue;
+    }
+    let values = Array.from(entity.getField("values").entries());
+
+    let title = values[0][1].getValue().toString();
+    channels.push(new Channel(key, title));
+  }
+  return channels;
+};

+ 127 - 0
rewards.ts

@@ -0,0 +1,127 @@
+import { ApiPromise } from "@polkadot/api";
+import { Option, Vec } from "@polkadot/types";
+import { AccountId, Balance } from "@polkadot/types/interfaces";
+import { Hash } from "@polkadot/types/interfaces";
+import { Mint, MintId } from "@joystream/types/mint";
+import { Stake } from "@joystream/types/stake";
+import { WorkerOf } from "@joystream/types/augment-codec/all";
+import { Bounty, CacheEvent, MintStatistics, WorkerReward } from "../types";
+import {
+  RewardRelationship,
+  RewardRelationshipId,
+} from "@joystream/types/recurring-rewards";
+
+import { getPercent, getTotalMinted, momentToString } from "./";
+import {
+  getBlock,
+  getBlockHash,
+  getMint,
+  getNextWorker,
+  getWorker,
+  getWorkerReward,
+  getStake,
+  getValidators,
+  getValidatorCount,
+} from "./api";
+
+export const filterMethods = {
+  getBurnedTokens: ({ section, method }: CacheEvent) =>
+    section === "balances" && method === "Transfer",
+  newValidatorsRewards: ({ section, method }: CacheEvent) =>
+    section === "staking" && method === "Reward",
+};
+
+export const getWorkerRewards = async (
+  api: ApiPromise,
+  group: string,
+  hash: Hash
+): Promise<WorkerReward[]> => {
+  let workers = Array<WorkerReward>();
+  const nextWorkerId = await getNextWorker(api, group, hash);
+
+  for (let id = 0; id < nextWorkerId; ++id) {
+    const worker: WorkerOf = await getWorker(api, group, hash, id);
+
+    // TODO workers fired before the end will be missed out
+    if (!worker.is_active) continue;
+    let stake: Stake, reward: RewardRelationship;
+
+    if (worker.role_stake_profile.isSome) {
+      const roleStakeProfile = worker.role_stake_profile.unwrap();
+      stake = await getStake(api, roleStakeProfile.stake_id);
+    }
+
+    if (worker.reward_relationship.isSome) {
+      // TODO changing salaries are not reflected
+      const rewardId: RewardRelationshipId = worker.reward_relationship.unwrap();
+      reward = await getWorkerReward(api, hash, rewardId);
+    }
+    workers.push({ id, stake, reward });
+  }
+  return workers;
+};
+
+export const getBurnedTokens = (
+  burnAddress: string,
+  blocks: [number, CacheEvent[]][]
+): number => {
+  let tokensBurned = 0;
+  blocks.forEach(([key, transfers]) =>
+    transfers.forEach((transfer) => {
+      let receiver = transfer.data[1] as AccountId;
+      let amount = transfer.data[2] as Balance;
+      if (receiver.toString() === burnAddress) tokensBurned = Number(amount);
+    })
+  );
+  return tokensBurned;
+};
+
+export const getMintInfo = async (
+  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;
+};
+
+export const getValidatorsRewards = (
+  blocks: [number, CacheEvent[]][]
+): number => {
+  let newValidatorRewards = 0;
+  blocks.forEach(([key, validatorRewards]) =>
+    validatorRewards.forEach(
+      (reward: CacheEvent) => (newValidatorRewards += Number(reward.data[1]))
+    )
+  );
+  return newValidatorRewards;
+};
+
+export const getActiveValidators = async (
+  api: ApiPromise,
+  hash: Hash,
+  searchPreviousBlocks: boolean = false
+): Promise<AccountId[]> => {
+  const block = await getBlock(api, hash);
+  let currentBlockNr = block.block.header.number.toNumber();
+  let activeValidators: AccountId[];
+  do {
+    const hash: Hash = await getBlockHash(api, currentBlockNr);
+    const validators: Option<Vec<AccountId>> = await getValidators(api, hash);
+    if (!validators.isEmpty) {
+      let max = await getValidatorCount(api, hash);
+      activeValidators = Array.from(validators.unwrap()).slice(0, max);
+    }
+
+    if (searchPreviousBlocks) --currentBlockNr;
+    else ++currentBlockNr;
+  } while (activeValidators == undefined);
+  return activeValidators;
+};