import { ApiPromise } from '@polkadot/api' ; import { Option , Vec } from '@polkadot/types' ; import '@polkadot/api-augment/substrate' ; import { BlockHash , EventRecord , LastRuntimeUpgradeInfo , RuntimeVersion , SignedBlock , } from '@polkadot/types/interfaces' ; import { SpecVersionRange , SubqlBlockFilter , SubqlCallFilter , SubqlEventFilter , SubstrateBlock , SubstrateEvent , SubstrateExtrinsic , } from '@subql/types' ; import { last , merge , range } from 'lodash' ; import { BlockContent } from '../indexer/types' ; import { getLogger } from './logger' ; const logger = getLogger ( 'fetch' ) ; export function wrapBlock ( signedBlock , events , specVersion ? ) { return merge ( signedBlock , { timestamp : getTimestamp ( signedBlock ) , specVersion : specVersion , events , } ) ; } function getTimestamp ( { block : { extrinsics } } ) { for ( const e of extrinsics ) { const { method : { method , section } , } = e ; if ( section === 'timestamp' && method === 'set' ) { const date = new Date ( e . args [ 0 ] . toJSON ( ) as number ) ; if ( isNaN ( date . getTime ( ) ) ) { throw new Error ( 'timestamp args type wrong' ) ; } return date ; } } } export function wrapExtrinsics ( wrappedBlock , allEvents ) { return wrappedBlock . block . extrinsics . map ( ( extrinsic , idx ) => { const events = filterExtrinsicEvents ( idx , allEvents ) ; return { idx , extrinsic , block : wrappedBlock , events , success : getExtrinsicSuccess ( events ) , } ; } ) ; } function getExtrinsicSuccess ( events ) { return ( events . findIndex ( ( evt ) => evt . event . method === 'ExtrinsicSuccess' ) > - 1 ) ; } function filterExtrinsicEvents ( extrinsicIdx , events ) { return events . filter ( ( { phase } ) => phase . isApplyExtrinsic && phase . asApplyExtrinsic . eqn ( extrinsicIdx ) ) ; } export function wrapEvents ( extrinsics , events , block ) { return events . reduce ( ( acc , event , idx ) => { const { phase } = event ; const wrappedEvent = merge ( event , { idx , block } ) ; if ( phase . isApplyExtrinsic ) { wrappedEvent . extrinsic = extrinsics [ phase . asApplyExtrinsic . toNumber ( ) ] ; } acc . push ( wrappedEvent ) ; return acc ; } , [ ] as SubstrateEvent [ ] ) ; } function checkSpecRange ( specVersionRange , specVersion ) { const [ lowerBond , upperBond ] = specVersionRange ; return ( ( lowerBond === undefined || lowerBond === null || specVersion >= lowerBond ) && ( upperBond === undefined || upperBond === null || specVersion <= upperBond ) ) ; } export function filterBlock ( block , filter ? ) { if ( ! filter ) return block ; return filter . specVersion === undefined || block . specVersion === undefined || checkSpecRange ( filter . specVersion , block . specVersion ) ? block : undefined ; } export function filterExtrinsics ( extrinsics , filterOrFilters ) { if ( ! filterOrFilters || ( filterOrFilters instanceof Array && filterOrFilters . length === 0 ) ) { return extrinsics ; } const filters = filterOrFilters instanceof Array ? filterOrFilters : [ filterOrFilters ] ; return extrinsics . filter ( ( { block , extrinsic , success } ) => filters . find ( ( filter ) => ( filter . specVersion === undefined || block . specVersion === undefined || checkSpecRange ( filter . specVersion , block . specVersion ) ) && ( filter . module === undefined || extrinsic . method . section === filter . module ) && ( filter . method === undefined || extrinsic . method . method === filter . method ) && ( filter . success === undefined || success === filter . success ) ) ) ; } export function filterEvents ( events , filterOrFilters ? ) { if ( ! filterOrFilters || ( filterOrFilters instanceof Array && filterOrFilters . length === 0 ) ) { return events ; } const filters = filterOrFilters instanceof Array ? filterOrFilters : [ filterOrFilters ] ; return events . filter ( ( { block , event } ) => filters . find ( ( filter ) => ( filter . specVersion === undefined || block . specVersion === undefined || checkSpecRange ( filter . specVersion , block . specVersion ) ) && ( filter . module ? event . section === filter . module : true ) && ( filter . method ? event . method === filter . method : true ) ) ) ; } export async function prefetchMetadata ( api , hash ) { await api . getBlockRegistry ( hash ) ; } param api param startHeight param endHeight param overallSpecVer export async function fetchBlocks ( api , startHeight , endHeight , overallSpecVer ? ) { const blocks = await fetchBlocksRange ( api , startHeight , endHeight ) ; const blockHashs = blocks . map ( ( b ) => b . block . header . hash ) ; const parentBlockHashs = blocks . map ( ( b ) => b . block . header . parentHash ) ; const [ blockEvents , runtimeVersions ] = await Promise . all ( [ fetchEventsRange ( api , blockHashs ) , overallSpecVer ? undefined : fetchRuntimeVersionRange ( api , parentBlockHashs ) , ] ) ; return blocks . map ( ( block , idx ) => { const events = blockEvents [ idx ] ; const parentSpecVersion = overallSpecVer ? overallSpecVer : runtimeVersions [ idx ] . specVersion . toNumber ( ) ; const wrappedBlock = wrapBlock ( block , events . toArray ( ) , parentSpecVersion ) ; const wrappedExtrinsics = wrapExtrinsics ( wrappedBlock , events ) ; const wrappedEvents = wrapEvents ( wrappedExtrinsics , events , wrappedBlock ) ; return { block : wrappedBlock , extrinsics : wrappedExtrinsics , events : wrappedEvents , } ; } ) ; } export async function fetchBlocksViaRangeQuery ( api , startHeight , endHeight ) { const blocks = await fetchBlocksRange ( api , startHeight , endHeight ) ; const firstBlockHash = blocks [ 0 ] . block . header . hash ; const endBlockHash = last ( blocks ) . block . header . hash ; const [ blockEvents , runtimeUpgrades ] = await Promise . all ( [ api . query . system . events . range ( [ firstBlockHash , endBlockHash ] ) , api . query . system . lastRuntimeUpgrade . range ( [ firstBlockHash , endBlockHash ] ) , ] ) ; let lastEvents ; let lastRuntimeUpgrade ; return blocks . map ( ( block , idx ) => { const [ , events = lastEvents ] = blockEvents [ idx ] ?? [ ] ; const [ , runtimeUpgrade = lastRuntimeUpgrade ] = runtimeUpgrades [ idx ] ?? [ ] ; lastEvents = events ; lastRuntimeUpgrade = runtimeUpgrade ; const wrappedBlock = wrapBlock ( block , events , runtimeUpgrade . unwrap ( ) ?. specVersion . toNumber ( ) ) ; const wrappedExtrinsics = wrapExtrinsics ( wrappedBlock , events ) ; const wrappedEvents = wrapEvents ( wrappedExtrinsics , events , wrappedBlock ) ; return { block : wrappedBlock , extrinsics : wrappedExtrinsics , events : wrappedEvents , } ; } ) ; } async function getBlockByHeight ( api , height ) { const blockHash = await api . rpc . chain . getBlockHash ( height ) . catch ( ( e ) => { logger . error ( `failed to fetch BlockHash ${ height }` ) ; throw e ; } ) ; return api . rpc . chain . getBlock ( blockHash ) . catch ( ( e ) => { logger . error ( `failed to fetch Block ${ blockHash }` ) ; throw e ; } ) ; } export async function fetchBlocksRange ( api , startHeight , endHeight ) { return Promise . all ( range ( startHeight , endHeight + 1 ) . map ( async ( height ) => getBlockByHeight ( api , height ) ) ) ; } export async function fetchBlocksArray ( api , blockArray ) { return Promise . all ( blockArray . map ( async ( height ) => getBlockByHeight ( api , height ) ) ) ; } export async function fetchEventsRange ( api , hashs ) { return Promise . all ( hashs . map ( ( hash ) => api . query . system . events . at ( hash ) . catch ( ( e ) => { logger . error ( `failed to fetch events at block ${ hash }` ) ; throw e ; } ) ) ) ; } export async function fetchRuntimeVersionRange ( api , hashs ) { return Promise . all ( hashs . map ( ( hash ) => api . rpc . state . getRuntimeVersion ( hash ) . catch ( ( e ) => { logger . error ( `failed to fetch RuntimeVersion at block ${ hash }` ) ; throw e ; } ) ) ) ; } export async function fetchBlocksBatches ( api , blockArray , overallSpecVer ? ) { const blocks = await fetchBlocksArray ( api , blockArray ) ; const blockHashs = blocks . map ( ( b ) => b . block . header . hash ) ; const parentBlockHashs = blocks . map ( ( b ) => b . block . header . parentHash ) ; const [ blockEvents , runtimeVersions ] = await Promise . all ( [ fetchEventsRange ( api , blockHashs ) , overallSpecVer ? undefined : fetchRuntimeVersionRange ( api , parentBlockHashs ) , ] ) ; return blocks . map ( ( block , idx ) => { const events = blockEvents [ idx ] ; const parentSpecVersion = overallSpecVer ? overallSpecVer : runtimeVersions [ idx ] . specVersion . toNumber ( ) ; const wrappedBlock = wrapBlock ( block , events . toArray ( ) , parentSpecVersion ) ; const wrappedExtrinsics = wrapExtrinsics ( wrappedBlock , events ) ; const wrappedEvents = wrapEvents ( wrappedExtrinsics , events , wrappedBlock ) ; return { block : wrappedBlock , extrinsics : wrappedExtrinsics , events : wrappedEvents , } ; } ) ; }