Skip to content

Commit

Permalink
fix: OperationRequiresNodeInCurrentEra -> OperationRequiresSyncedNode
Browse files Browse the repository at this point in the history
This fix replaces the use of the last configured major version to determine readiness
during initialisation with Ogmios server network sync information and specific guards
on DB values.
  • Loading branch information
rhyslbw committed Aug 9, 2021
1 parent f6fa791 commit 6165965
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 95 deletions.
49 changes: 25 additions & 24 deletions packages/api-cardano-db-hasura/src/CardanoNodeClient.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { AssetSupply, Transaction } from './graphql_types'
import pRetry from 'p-retry'
import util, { errors, ModuleState } from '@cardano-graphql/util'
import util, { DataFetcher, errors, ModuleState } from '@cardano-graphql/util'
import {
ConnectionConfig,
createStateQueryClient,
createTxSubmissionClient,
Schema,
getOgmiosHealth,
OgmiosHealth,
// Schema,
StateQueryClient,
TxSubmissionClient
} from '@cardano-ogmios/client'
Expand All @@ -20,9 +22,10 @@ export class CardanoNodeClient {
private stateQueryClient: StateQueryClient
private txSubmissionClient: TxSubmissionClient
private state: ModuleState
private serverHealthFetcher: DataFetcher<OgmiosHealth>

constructor (
readonly lastConfiguredMajorVersion: number,
readonly lastConfiguredMajorVersion: number, // Todo: Depreciate
private logger: Logger = dummyLogger
) {
this.state = null
Expand All @@ -38,23 +41,27 @@ export class CardanoNodeClient {
return slotNo
}

public async getProtocolParams (): Promise<Schema.ProtocolParametersShelley> {
if (this.state !== 'initialized') {
throw new errors.ModuleIsNotInitialized(MODULE_NAME, 'getProtocolParams')
}
if (!(await this.isInCurrentEra())) {
throw new errors.OperationRequiresNodeInCurrentEra('getProtocolParams')
}
const protocolParams = await this.stateQueryClient.currentProtocolParameters()
this.logger.debug({ module: MODULE_NAME, protocolParams }, 'getProtocolParams')
return protocolParams
}
// Todo: Include in Graph
// public async getProtocolParams (): Promise<Schema.ProtocolParametersShelley> {
// if (this.state !== 'initialized') {
// throw new errors.ModuleIsNotInitialized(MODULE_NAME, 'getProtocolParams')
// }
// const protocolParams = await this.stateQueryClient.currentProtocolParameters()
// this.logger.debug({ module: MODULE_NAME, protocolParams }, 'getProtocolParams')
// return protocolParams
// }

public async initialize (ogmiosConnectionConfig?: ConnectionConfig) {
if (this.state !== null) return
this.state = 'initializing'
this.logger.info({ module: MODULE_NAME }, 'Initializing. This can take a few minutes...')
this.serverHealthFetcher = new DataFetcher(
'ServerHealth',
() => getOgmiosHealth(ogmiosConnectionConfig),
30000, this.logger
)
await pRetry(async () => {
await this.serverHealthFetcher.initialize()
const options = ogmiosConnectionConfig ? { connection: ogmiosConnectionConfig } : {}
this.stateQueryClient = await createStateQueryClient(
this.logger.error,
Expand Down Expand Up @@ -82,18 +89,9 @@ export class CardanoNodeClient {
this.logger.info({ module: MODULE_NAME }, 'Initialized')
}

private async isInCurrentEra () {
const { protocolVersion } = await this.getProtocolParams()
this.logger.debug({
module: MODULE_NAME,
currentProtocolVersion: protocolVersion,
lastConfiguredMajorVersion: this.lastConfiguredMajorVersion
}, 'Comparing current protocol params with last known major version from cardano-node config')
return protocolVersion.major >= this.lastConfiguredMajorVersion
}

public async shutdown (): Promise<void> {
await Promise.all([
this.serverHealthFetcher.shutdown,
this.stateQueryClient.release,
this.txSubmissionClient.shutdown
])
Expand All @@ -103,6 +101,9 @@ export class CardanoNodeClient {
if (this.state !== 'initialized') {
throw new errors.ModuleIsNotInitialized(MODULE_NAME, 'submitTransaction')
}
if (this.serverHealthFetcher.value.networkSynchronization < 0.95) {
throw new errors.OperationRequiresSyncedNode('submitTransaction')
}
await this.txSubmissionClient.submitTx(transaction)
const hash = getHashOfSignedTransaction(transaction)
this.logger.info({ module: MODULE_NAME, hash }, 'submitTransaction')
Expand Down
70 changes: 19 additions & 51 deletions packages/api-cardano-db-hasura/src/HasuraClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,22 @@ import {

export type AdaPotsToCalculateSupply = { circulating: AssetSupply['circulating'], reserves: AdaPots['reserves']}

const notInCurrentEraMessage = 'currentEpoch is only available when close to the chain tip. This is expected during the initial chain-sync.'
const epochInformationNotYetAvailable = 'Epoch information not yet available. This is expected during the initial chain-sync.'

const withHexPrefix = (value: string) => `\\x${value !== undefined ? value : ''}`

export class HasuraClient {
private client: GraphQLClient
private applyingSchemaAndMetadata: boolean
public adaPotsToCalculateSupplyFetcher: DataFetcher<AdaPotsToCalculateSupply>
public currentProtocolVersionFetcher: DataFetcher<ShelleyProtocolParams['protocolVersion']>
private state: ModuleState
public schema: GraphQLSchema

constructor (
readonly hasuraCliPath: string,
readonly hasuraUri: string,
pollingInterval: number,
readonly lastConfiguredMajorVersion: number,
readonly lastConfiguredMajorVersion: number, // Todo: Depreciate
private logger: Logger = dummyLogger
) {
this.state = null
Expand All @@ -55,28 +54,15 @@ export class HasuraClient {
try {
return this.getAdaPotsToCalculateSupply()
} catch (error) {
if (error.message !== notInCurrentEraMessage) {
if (error.message !== epochInformationNotYetAvailable) {
throw error
}
this.logger.debug({ err: error })
this.logger.trace({ err: error })
}
},
pollingInterval,
this.logger
)
this.currentProtocolVersionFetcher = new DataFetcher<ShelleyProtocolParams['protocolVersion']>(
'ProtocolParams',
async () => {
const currentProtocolVersion = await this.getCurrentProtocolVersion()
this.logger.debug(
{ module: 'HasuraClient', currentProtocolVersion },
'currentProtocolVersionFetcher'
)
return currentProtocolVersion
},
1000 * 60,
this.logger
)
this.client = new GraphQLClient(
`${this.hasuraUri}/v1/graphql`,
{
Expand All @@ -90,11 +76,9 @@ export class HasuraClient {
private async getAdaPotsToCalculateSupply (): Promise<AdaPotsToCalculateSupply> {
const result = await this.client.request(
gql`query {
cardano {
currentEpoch {
adaPots {
reserves
}
epochs (limit: 1, order_by: { number: desc }) {
adaPots {
reserves
}
}
rewards_aggregate {
Expand All @@ -121,22 +105,22 @@ export class HasuraClient {
}`
)
const {
cardano,
epochs,
rewards_aggregate: rewardsAggregate,
utxos_aggregate: utxosAggregate,
withdrawals_aggregate: withdrawalsAggregate
} = result
if (cardano[0]?.currentEpoch === null) {
this.logger.debug({ module: 'HasuraClient' }, notInCurrentEraMessage)
throw new Error(notInCurrentEraMessage)
if (epochs.length === 0 && epochs[0].adaPots === null) {
this.logger.debug({ module: 'HasuraClient' }, epochInformationNotYetAvailable)
throw new Error(epochInformationNotYetAvailable)
}
const rewards = new BigNumber(rewardsAggregate.aggregate.sum.amount)
const utxos = new BigNumber(utxosAggregate.aggregate.sum.value)
const withdrawals = new BigNumber(withdrawalsAggregate.aggregate.sum.amount)
const withdrawableRewards = rewards.minus(withdrawals)
return {
circulating: utxos.plus(withdrawableRewards).toString(),
reserves: cardano[0]?.currentEpoch.adaPots.reserves
reserves: epochs[0]?.adaPots.reserves
}
}

Expand Down Expand Up @@ -174,35 +158,31 @@ export class HasuraClient {
await pRetry(async () => {
const result = await this.client.request(
gql`query {
cardano {
currentEpoch {
number
}
epochs (limit: 1, order_by: { number: desc }) {
number
}
}`
)
if (result.cardano[0]?.currentEpoch === null) {
this.logger.debug({ module: 'HasuraClient' }, notInCurrentEraMessage)
throw new Error(notInCurrentEraMessage)
if (result.epochs.length === 0) {
this.logger.debug({ module: 'HasuraClient' }, epochInformationNotYetAvailable)
throw new Error(epochInformationNotYetAvailable)
}
}, {
factor: 1.05,
retries: 100,
onFailedAttempt: util.onFailedAttemptFor(
'Detecting sync state being in current era',
'Detecting DB sync state has reached minimum progress',
this.logger
)
})
this.logger.debug({ module: 'HasuraClient' }, 'DB is in current era')
await this.currentProtocolVersionFetcher.initialize()
this.logger.debug({ module: 'HasuraClient' }, 'DB sync state has reached minimum progress')
await this.adaPotsToCalculateSupplyFetcher.initialize()
this.state = 'initialized'
this.logger.info({ module: 'HasuraClient' }, 'Initialized')
}

public async shutdown () {
await this.adaPotsToCalculateSupplyFetcher.shutdown()
await this.currentProtocolVersionFetcher.shutdown()
}

public async applySchemaAndMetadata (): Promise<void> {
Expand Down Expand Up @@ -538,18 +518,6 @@ export class HasuraClient {
return result.assets
}

public async isInCurrentEra () {
const protocolVersion = this.currentProtocolVersionFetcher.value
this.logger.debug({
module: 'CardanoNodeClient',
currentProtocolVersion: protocolVersion,
lastConfiguredMajorVersion: protocolVersion.major
},
'Comparing current protocol params with last known major version from cardano-node config'
)
return protocolVersion.major >= this.lastConfiguredMajorVersion
}

public async addAssetMetadata (asset: AssetMetadataAndHash) {
this.logger.info(
{ module: 'HasuraClient', assetId: asset.assetId },
Expand Down
10 changes: 0 additions & 10 deletions packages/api-cardano-db-hasura/src/executableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,6 @@ export async function buildSchema (
cardanoNodeClient: CardanoNodeClient,
customFieldsComplexity: FieldsComplexityMapping = defaultComplexity
) {
const throwIfNotInCurrentEra = async (queryName: string) => {
if (!(await hasuraClient.isInCurrentEra())) {
throw new ApolloError(
`${queryName} results are only available when close to the network tip. This is expected during the initial chain-sync.`
)
}
}
const getComplexityExtension = (operation: string, queryName: string) => {
if (operation in customFieldsComplexity) {
const operationMapping = customFieldsComplexity[
Expand Down Expand Up @@ -82,7 +75,6 @@ export async function buildSchema (
Mutation: {
submitTransaction: {
resolve: async (_root, args) => {
await throwIfNotInCurrentEra('submitTransaction')
try {
const hash = await cardanoNodeClient.submitTransaction(
args.transaction
Expand Down Expand Up @@ -152,7 +144,6 @@ export async function buildSchema (
},
ada: {
resolve: async () => {
await throwIfNotInCurrentEra('ada')
const adaPots = hasuraClient.adaPotsToCalculateSupplyFetcher.value
if (adaPots === undefined) {
return new ApolloError(
Expand Down Expand Up @@ -330,7 +321,6 @@ export async function buildSchema (
},
paymentAddresses: {
resolve: async (_root, args) => {
await throwIfNotInCurrentEra('addressSummary')
return args.addresses.map(async (address) => {
return { address }
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('stakePools', () => {
})
const { stakePools_aggregate } = result.data
expect(parseInt(stakePools_aggregate.aggregate.count)).toBeGreaterThan(900)
expect(parseInt(stakePools_aggregate.aggregate.count)).toBeLessThan(4000)
expect(parseInt(stakePools_aggregate.aggregate.count)).toBeLessThan(5000)
})

it('can return aggregated data on active stake pools', async () => {
Expand Down
8 changes: 0 additions & 8 deletions packages/util/src/errors/OperationRequiresNodeInCurrentEra.ts

This file was deleted.

8 changes: 8 additions & 0 deletions packages/util/src/errors/OperationRequiresSyncedNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { CustomError } from 'ts-custom-error'

export class OperationRequiresSyncedNode extends CustomError {
public constructor (operation: string) {
super()
this.message = `Cannot ${operation} until cardano-node is close to the network tip`
}
}
2 changes: 1 addition & 1 deletion packages/util/src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './HostDoesNotExist'
export * from './MissingConfig'
export * from './ModuleIsNotInitialized'
export * from './OperationRequiresNodeInCurrentEra'
export * from './OperationRequiresSyncedNode'

0 comments on commit 6165965

Please sign in to comment.