Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NOT MERGE BEFORE #26] Add supply indexing #28

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
9 changes: 9 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Block @entity {
messages: [Message] @derivedFrom(field: "block")
events: [Event] @derivedFrom(field: "block")
balancesOfAccountByDenom: [Balance] @derivedFrom(field: "lastUpdatedBlock")
supplies: [Supply] @derivedFrom(field: "block")
}

type Transaction @entity {
Expand Down Expand Up @@ -120,3 +121,11 @@ type GenesisFile @entity {
id: ID!
raw: String!
}

type Supply @entity {
# coin denom
id: ID!
denom: String! @index
amount: BigInt!
block: Block!
}
64 changes: 64 additions & 0 deletions src/mappings/bank/supply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type {
Coin,
CosmosBlock,
} from "@subql/types-cosmos";
import type { QueryTotalSupplyResponse } from "cosmjs-types/cosmos/bank/v1beta1/query";
import { Supply } from "../../types";
import { stringify } from "../utils";

export const getSupplyId = function(supply: Coin, block: CosmosBlock): string {
return `${supply.denom}@${block.block.id}`;
};

export const getSupplyRecord = function(supply: Coin, block: CosmosBlock): Supply {
return Supply.create({
id: getSupplyId(supply, block),
denom: supply.denom,
amount: BigInt(supply.amount),
blockId: block.block.id,
});
};

export async function queryTotalSupply(): Promise<Coin[]> {
const finalSupply: Coin[] = [];
let paginationKey: Uint8Array | undefined;

try {
// Here we force the use of a private property, breaking typescript limitation, due to the need of call a total supply
// rpc query of cosmosjs that is not exposed on the implemented client by SubQuery team.
// To avoid this, we need to move to implement our own rpc client and also use `unsafe` parameter which I prefer to avoid.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const queryClient = api.forceGetQueryClient();

// Initial call to get the first set of results
const initialResponse: QueryTotalSupplyResponse = await queryClient.bank.totalSupply() as unknown as QueryTotalSupplyResponse;
logger.debug(`[handleTotalSupply]: initialResponse=${stringify(initialResponse, undefined, 2)}`);
finalSupply.push(...initialResponse.supply);
paginationKey = initialResponse.pagination?.nextKey;

// Continue fetching if there is a nextKey
while (paginationKey && paginationKey.length > 0) {
logger.debug(`[handleTotalSupply]: loading more supply pages pagination.nextKey=${JSON.stringify(paginationKey, undefined, 2)}`);
const response = await queryClient.bank.totalSupply(paginationKey);
finalSupply.push(...response.supply);
paginationKey = response.pagination?.nextKey;
}
logger.debug(`[handleTotalSupply]: all_total_supply=${JSON.stringify(finalSupply, undefined, 2)}`);
} catch (error) {
logger.error(`[handleTotalSupply] errored: ${error}`);
}

return finalSupply;
}

export async function _handleSupply(block: CosmosBlock): Promise<void> {
jorgecuesta marked this conversation as resolved.
Show resolved Hide resolved
const totalSupply = await queryTotalSupply();
if (totalSupply.length === 0) {
logger.warn(`[handleSupply]: no total supply found`);
return;
}

logger.info(`[handleSupply]: indexing total supply for ${totalSupply.length} coins`);
await store.bulkCreate("Supply", totalSupply.map(supply => getSupplyRecord(supply, block)));
}
51 changes: 28 additions & 23 deletions src/mappings/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,57 +12,59 @@ import {
isString,
} from "lodash";
import {
Balance,
Block,
Event,
EventAttribute,
GenesisBalance,
GenesisFile as GenesisEntity,
Message,
NativeBalanceChange,
Transaction,
TxStatus,
NativeBalanceChange,
GenesisBalance,
Balance,
GenesisFile as GenesisEntity,
} from "../types";
import { _handleSupply } from "./bank/supply";
import { PREFIX } from "./constants";
import type { Genesis } from "./types/genesis";
import {
attemptHandling,
getBalanceId,
messageId,
primitivesFromMsg,
primitivesFromTx,
stringify,
trackUnprocessed,
unprocessedEventHandler,
getBalanceId,
} from "./utils";

export async function handleGenesis(block: CosmosBlock): Promise<void> {
const genesis: Genesis = require('../../genesis.json');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const genesis: Genesis = require("../../genesis.json");

// IMPORTANT: Return early if this is not the genesis initial height as this is called for block indexed!
if (block.block.header.height !== genesis.initial_height) {
return
return;
}

logger.info(`[handleGenesis] (block.header.height): indexing genesis block ${block.block.header.height}`);

await Promise.all(
[
store.bulkCreate('Account', genesis.app_state.auth.accounts.map(account => {
store.bulkCreate("Account", genesis.app_state.auth.accounts.map(account => {
return {
id: account.address,
chainId: block.block.header.chainId,
}
};
})),
Event.create({
id: "genesis",
type: "genesis",
blockId: block.block.id,
}).save(),
]
)
],
);

type EntityToSave<T> = Omit<T, 'save' |'_name'>;
type EntityToSave<T> = Omit<T, "save" | "_name">;
const nativeBalanceChanges: Array<EntityToSave<NativeBalanceChange>> = [];
const genesisBalances: Array<EntityToSave<GenesisBalance>> = [];
const balances: Array<EntityToSave<Balance>> = [];
Expand All @@ -77,26 +79,26 @@ export async function handleGenesis(block: CosmosBlock): Promise<void> {
const amountByAccountAndDenom: AmountByAccountAndDenom = genesis.app_state.bank.balances.reduce((acc, balance) => {
const amountByDenom: Record<string, bigint> = balance.coins.reduce((acc, coin) => ({
...acc,
[coin.denom]: BigInt(acc[coin.denom] || 0) + BigInt(coin.amount),
}), {} as Record<string, bigint>)
[coin.denom]: BigInt(acc[coin.denom] || 0) + BigInt(coin.amount),
}), {} as Record<string, bigint>);

for (const [denom, amount] of Object.entries(amountByDenom)) {
const id = getBalanceId(balance.address, denom)
const id = getBalanceId(balance.address, denom);
if (acc[id]) {
acc[id].amount += amount
acc[id].amount += amount;
} else {
acc[id] = {
amount,
denom,
accountId: balance.address,
}
};
}
}

return acc
}, {} as AmountByAccountAndDenom)
return acc;
}, {} as AmountByAccountAndDenom);

for (const [id, {accountId, amount, denom}] of Object.entries(amountByAccountAndDenom)) {
for (const [id, { accountId, amount, denom }] of Object.entries(amountByAccountAndDenom)) {
nativeBalanceChanges.push({
id,
balanceOffset: amount.valueOf(),
Expand All @@ -123,9 +125,9 @@ export async function handleGenesis(block: CosmosBlock): Promise<void> {
}

await Promise.all([
store.bulkCreate('GenesisBalance', genesisBalances),
store.bulkCreate('NativeBalanceChange', nativeBalanceChanges),
store.bulkCreate('Balance', balances)
store.bulkCreate("GenesisBalance", genesisBalances),
store.bulkCreate("NativeBalanceChange", nativeBalanceChanges),
store.bulkCreate("Balance", balances),
]);

await GenesisEntity.create({
Expand Down Expand Up @@ -163,6 +165,8 @@ async function _handleBlock(block: CosmosBlock): Promise<void> {
});

await blockEntity.save();

await _handleSupply(block);
jorgecuesta marked this conversation as resolved.
Show resolved Hide resolved
}

async function _handleTransaction(tx: CosmosTransaction): Promise<void> {
Expand Down Expand Up @@ -274,6 +278,7 @@ async function _handleEvent(event: CosmosEvent): Promise<void> {
}
}


// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function _handleBlockError(err: Error, _: CosmosBlock): Promise<void> {
// NB: we won't have persisted any related entities yet.
Expand Down