/*eslint @typescript-eslint/no-use-before-define: ["error", { "variables": false }]*/ import { DeriveStakingAccount, DeriveEraExposure } from '@polkadot/api-derive/staking/types'; import { MyDeriveStakingAccount, WriteCSVRequest, ChainData, Voter } from "./types"; import { Logger } from '@w3f/logger'; import { ApiPromise } from '@polkadot/api'; import { EraRewardPoints } from '@polkadot/types/interfaces'; import { delay, getDisplayName, getErrorMessage } from './utils'; import BN from 'bn.js'; export const gatherChainData = async (request: WriteCSVRequest, logger: Logger): Promise<ChainData> =>{ logger.info(`Data gathering triggered...`) const data = await _handleConnectionRetries(_gatherData,request,logger) logger.info(`Data have been gathered.`) return data } /* eslint-disable @typescript-eslint/no-explicit-any */ const _handleConnectionRetries = async (f: { (request: WriteCSVRequest, logger: Logger): Promise<ChainData> }, request: WriteCSVRequest, logger: Logger): Promise<ChainData> => { let attempts = 0 for(;;){ try { const data = await f(request,logger) return data } catch (error) { logger.error(`Could not process the Data gathering...`); const errorMessage = getErrorMessage(error) if( !errorMessage.includes("Unable to decode using the supplied passphrase") && //there is no way to recover from this ++attempts < 5 ){ logger.warn(`Retrying...`) await delay(5000) //wait x seconds before retrying } else{ process.exit(-1); } } } } /* eslint-enable @typescript-eslint/no-explicit-any */ const _gatherData = async (request: WriteCSVRequest, logger: Logger): Promise<ChainData> =>{ logger.debug(`gathering some data from the chain...`) const {api,apiChunkSize,eraIndex} = request const eraPointsPromise = api.query.staking.erasRewardPoints(eraIndex); const eraExposures = await api.derive.staking.eraExposure(eraIndex) const totalIssuance = await api.query.balances.totalIssuance() const validatorRewardsPreviousEra = (await api.query.staking.erasValidatorReward(eraIndex.sub(new BN(1)))).unwrap(); logger.debug(`nominators...`); console.time('get nominators'); const nominatorStakingPromise = _getNominatorStaking(api,apiChunkSize,logger) const [nominatorStaking,eraPoints] = [await nominatorStakingPromise, await eraPointsPromise] console.timeEnd('get nominators') logger.debug(`validators...`); console.time('get validators') const myValidatorStaking = await _getMyValidatorStaking(api,apiChunkSize,nominatorStaking,eraPoints, eraExposures, logger) console.timeEnd('get validators') logger.debug(`waiting validators...`); console.time('get waiting validators') const myWaitingValidatorStaking = await _getMyWaitingValidatorStaking(api,apiChunkSize,nominatorStaking,eraPoints, eraExposures, logger) console.timeEnd('get waiting validators') return { eraPoints, totalIssuance, validatorRewardsPreviousEra, nominatorStaking, myValidatorStaking, myWaitingValidatorStaking } as ChainData } const _getNominatorStaking = async (api: ApiPromise, apiChunkSize: number, logger: Logger): Promise<DeriveStakingAccount[]> =>{ logger.debug(`getting the nominator entries...`) const nominators = await api.query.staking.nominators.entries(); logger.debug(`got ${nominators.length} entries !!`) const nominatorAddresses = nominators.map(([address]) => ""+address.toHuman()[0]); logger.debug(`the nominator addresses size is ${nominatorAddresses.length}`) //A too big nominators set could make crush the API => Chunk splitting const size = apiChunkSize const nominatorAddressesChucked = [] for (let i = 0; i < nominatorAddresses.length; i += size) { const chunk = nominatorAddresses.slice(i, i + size) nominatorAddressesChucked.push(chunk) } const nominatorsStakings: DeriveStakingAccount[] = [] for (const chunk of nominatorAddressesChucked) { logger.debug(`the handled chunk size is ${chunk.length}`) nominatorsStakings.push(...await api.derive.staking.accounts(chunk)) } return nominatorsStakings } const _getMyValidatorStaking = async (api: ApiPromise, apiChunkSize: number, nominatorsStakings: DeriveStakingAccount[], eraPoints: EraRewardPoints, eraExposures: DeriveEraExposure, logger: Logger): Promise<MyDeriveStakingAccount[]> =>{ const validatorsAddresses = await api.query.session.validators(); logger.debug(`the validator addresses size is ${validatorsAddresses.length}`) //A too big nominators set could make crush the API => Chunk splitting const size = apiChunkSize const validatorsAddressesChucked = [] for (let i = 0; i < validatorsAddresses.length; i += size) { const chunk = validatorsAddresses.slice(i, i + size) validatorsAddressesChucked.push(chunk) } const validatorsStakings: DeriveStakingAccount[] = [] for (const chunk of validatorsAddressesChucked) { logger.debug(`the handled chunk size is ${chunk.length}`) validatorsStakings.push(...await api.derive.staking.accounts(chunk)) } return await _buildMyValidatorStaking(api,validatorsStakings,nominatorsStakings,eraPoints,eraExposures) } const _getMyWaitingValidatorStaking = async (api: ApiPromise, apiChunkSize: number, nominatorsStakings: DeriveStakingAccount[], eraPoints: EraRewardPoints, eraExposures: DeriveEraExposure, logger: Logger): Promise<MyDeriveStakingAccount[]> => { const validatorsAddresses = await _getWaitingValidatorsAccountId(api) logger.debug(`the waiting validator addresses size is ${validatorsAddresses.length}`) //A too big nominators set could make crush the API => Chunk splitting const size = apiChunkSize const validatorsAddressesChucked = [] for (let i = 0; i < validatorsAddresses.length; i += size) { const chunk = validatorsAddresses.slice(i, i + size) validatorsAddressesChucked.push(chunk) } const validatorsStakings: DeriveStakingAccount[] = [] for (const chunk of validatorsAddressesChucked) { logger.debug(`the handled chunk size is ${chunk.length}`) validatorsStakings.push(...await api.derive.staking.accounts(chunk)) } return await _buildMyValidatorStaking(api,validatorsStakings,nominatorsStakings,eraPoints,eraExposures) } const _buildMyValidatorStaking = async (api: ApiPromise, validatorsStakings: DeriveStakingAccount[], nominatorsStakings: DeriveStakingAccount[], eraPoints: EraRewardPoints, eraExposures: DeriveEraExposure): Promise<MyDeriveStakingAccount[]> =>{ const myValidatorStaking = Promise.all ( validatorsStakings.map( async validatorStaking => { const validatorAddress = validatorStaking.accountId const infoPromise = api.derive.accounts.info(validatorAddress); const validatorEraPoints = eraPoints.toJSON()['individual'][validatorAddress.toHuman()] ? eraPoints.toJSON()['individual'][validatorAddress.toHuman()] : 0 const exposure = eraExposures.validators[validatorAddress.toHuman()] ? eraExposures.validators[validatorAddress.toHuman()] : {total:0,own:0,others:[]} const voters: Voter[] = [] for (const staking of nominatorsStakings) { if (staking.nominators.includes(validatorAddress)) { voters.push({address: staking.accountId.toString(), value: staking.stakingLedger.total }) } } const {identity} = await infoPromise return { ...validatorStaking, displayName: getDisplayName(identity), voters: voters, exposure: exposure, eraPoints: validatorEraPoints, } as MyDeriveStakingAccount })) return myValidatorStaking } const _getWaitingValidatorsAccountId = async (api: ApiPromise): Promise<string[]> => { const skStashes = await api.query.staking.validators.keys() const stashes = skStashes.map(sk => sk.args) const active = await api.query.session.validators(); const waiting = stashes.filter((s) => !active.includes(s.toString())); return waiting.map(account => account.toString()) }