diff --git a/schema.graphql b/schema.graphql index 28187a1..8feab78 100644 --- a/schema.graphql +++ b/schema.graphql @@ -46,6 +46,11 @@ type BlockInfo { distanceFromMaxBlockHeight: Int! } +type MaxBlockHeightInfo { + canonicalMaxBlockHeight: Int! + pendingMaxBlockHeight: Int! +} + type TransactionInfo { status: String! hash: String! @@ -78,4 +83,5 @@ type ActionOutput { type Query { events(input: EventFilterOptionsInput!): [EventOutput]! actions(input: ActionFilterOptionsInput!): [ActionOutput]! + maxBlockHeightInfo: MaxBlockHeightInfo! } diff --git a/src/blockchain/types.ts b/src/blockchain/types.ts index 6f3baa0..42e4938 100644 --- a/src/blockchain/types.ts +++ b/src/blockchain/types.ts @@ -24,6 +24,11 @@ export type Action = { data: string[]; }; +export type MaxBlockHeightInfo = { + canonicalMaxBlockHeight: number; + pendingMaxBlockHeight: number; +}; + export type BlockInfo = { height: number; stateHash: string; diff --git a/src/db/archive-node-adapter/archive-node-adapter.interface.ts b/src/db/archive-node-adapter/archive-node-adapter.interface.ts index 78b11bf..af83054 100644 --- a/src/db/archive-node-adapter/archive-node-adapter.interface.ts +++ b/src/db/archive-node-adapter/archive-node-adapter.interface.ts @@ -1,5 +1,9 @@ import type { EventFilterOptionsInput } from '../../resolvers-types.js'; -import type { Actions, Events } from '../../blockchain/types.js'; +import type { + Actions, + Events, + MaxBlockHeightInfo, +} from '../../blockchain/types.js'; export interface DatabaseAdapter { getEvents(input: EventFilterOptionsInput, options?: unknown): Promise; @@ -7,4 +11,5 @@ export interface DatabaseAdapter { input: EventFilterOptionsInput, options?: unknown ): Promise; + getMaxBlockHeightInfo(options?: unknown): Promise; } diff --git a/src/db/archive-node-adapter/archive-node-adapter.ts b/src/db/archive-node-adapter/archive-node-adapter.ts index 6de9011..8daa7d1 100644 --- a/src/db/archive-node-adapter/archive-node-adapter.ts +++ b/src/db/archive-node-adapter/archive-node-adapter.ts @@ -1,5 +1,9 @@ import postgres from 'postgres'; -import type { Actions, Events } from '../../blockchain/types.js'; +import type { + Actions, + Events, + MaxBlockHeightInfo, +} from '../../blockchain/types.js'; import type { DatabaseAdapter } from './archive-node-adapter.interface.js'; import type { ActionFilterOptionsInput, @@ -10,6 +14,8 @@ import { EventsService } from '../../services/events-service/events-service.js'; import { IEventsService } from '../../services/events-service/events-service.interface.js'; import { ActionsService } from '../../services/actions-service/actions-service.js'; import { IActionsService } from '../../services/actions-service/actions-service.interface.js'; +import { BlockService } from '../../services/blocks-service/block-service.js'; +import { IBlockService } from '../../services/blocks-service/block-service.interface.js'; export class ArchiveNodeAdapter implements DatabaseAdapter { /** @@ -21,6 +27,7 @@ export class ArchiveNodeAdapter implements DatabaseAdapter { private client: postgres.Sql; private eventsService: IEventsService; private actionsService: IActionsService; + private blockService: IBlockService; constructor(connectionString: string | undefined) { if (!connectionString) @@ -30,6 +37,7 @@ export class ArchiveNodeAdapter implements DatabaseAdapter { this.client = postgres(connectionString); this.eventsService = new EventsService(this.client); this.actionsService = new ActionsService(this.client); + this.blockService = new BlockService(this.client); } async getEvents( @@ -46,6 +54,10 @@ export class ArchiveNodeAdapter implements DatabaseAdapter { return this.actionsService.getActions(input, options); } + async getMaxBlockHeightInfo(options: unknown): Promise { + return this.blockService.maxBlockHeightInfo(options); + } + async checkSQLSchema() { let tables; try { diff --git a/src/db/sql/events-actions/queries.ts b/src/db/sql/events-actions/queries.ts index 0de23c4..4df195e 100644 --- a/src/db/sql/events-actions/queries.ts +++ b/src/db/sql/events-actions/queries.ts @@ -371,6 +371,24 @@ export function getActionsQuery( `; } +export function getBlockQuery(db_client: postgres.Sql) { + return db_client` + WITH max_height AS ( + SELECT MAX(height) AS max_height + FROM blocks + ) + SELECT * + FROM blocks + WHERE height = (SELECT max_height FROM max_height) + AND chain_status = 'canonical' + UNION + SELECT * + FROM blocks + WHERE height = (SELECT max_height FROM max_height) + AND chain_status = 'pending' + `; +} + export function checkActionState(db_client: postgres.Sql, actionState: string) { return db_client` SELECT field FROM zkapp_field WHERE field = ${actionState} diff --git a/src/resolvers-types.ts b/src/resolvers-types.ts index 5c8f266..ed356a5 100644 --- a/src/resolvers-types.ts +++ b/src/resolvers-types.ts @@ -83,6 +83,12 @@ export type BlockInfo = { timestamp: Scalars['String']['output']; }; +export type MaxBlockHeightInfo = { + __typename?: 'MaxBlockHeightInfo'; + canonicalMaxBlockHeight: Scalars['Int']['output']; + pendingMaxBlockHeight: Scalars['Int']['output']; +}; + export { BlockStatusFilter }; export type EventData = { @@ -110,6 +116,7 @@ export type Query = { __typename?: 'Query'; actions: Array>; events: Array>; + block: Maybe; }; export type QueryActionsArgs = { @@ -242,6 +249,7 @@ export type ResolversTypes = { ActionOutput: ResolverTypeWrapper; ActionStates: ResolverTypeWrapper; BlockInfo: ResolverTypeWrapper; + MaxBlockHeightInfo: ResolverTypeWrapper; BlockStatusFilter: BlockStatusFilter; Boolean: ResolverTypeWrapper; EventData: ResolverTypeWrapper; @@ -438,6 +446,11 @@ export type QueryResolvers< ContextType, RequireFields >; + block?: Resolver< + Maybe, + ParentType, + ContextType + >; }; export type TransactionInfoResolvers< diff --git a/src/resolvers.ts b/src/resolvers.ts index e0fb300..ed26202 100644 --- a/src/resolvers.ts +++ b/src/resolvers.ts @@ -32,6 +32,16 @@ const resolvers: Resolvers = { tracingState: new TracingState(graphQLSpan), }); }, + + block: async (_, {}, context) => { + const graphQLSpan = setSpanNameFromGraphQLContext( + context, + 'block.graphql' + ); + return context.db_client.getMaxBlockHeightInfo({ + tracingState: new TracingState(graphQLSpan), + }); + }, }, }; diff --git a/src/services/blocks-service/block-service.interface.ts b/src/services/blocks-service/block-service.interface.ts new file mode 100644 index 0000000..31e7633 --- /dev/null +++ b/src/services/blocks-service/block-service.interface.ts @@ -0,0 +1,5 @@ +import { MaxBlockHeightInfo } from '../../blockchain/types.js'; + +export interface IBlockService { + maxBlockHeightInfo(options: unknown): Promise; +} diff --git a/src/services/blocks-service/block-service.ts b/src/services/blocks-service/block-service.ts new file mode 100644 index 0000000..dce79fb --- /dev/null +++ b/src/services/blocks-service/block-service.ts @@ -0,0 +1,50 @@ +import type postgres from 'postgres'; +import { MaxBlockHeightInfo } from '../../blockchain/types.js'; +import { getBlockQuery } from '../../db/sql/events-actions/queries.js'; + +import { IBlockService } from './block-service.interface.js'; +import { + TracingState, + extractTraceStateFromOptions, +} from '../../tracing/tracer.js'; + +export { BlockService }; + +class BlockService implements IBlockService { + private readonly client: postgres.Sql; + + constructor(client: postgres.Sql) { + this.client = client; + } + + async maxBlockHeightInfo(options: unknown): Promise { + const tracingState = extractTraceStateFromOptions(options); + return (await this.getMaxBlockHeightInfo({ tracingState })) ?? []; + } + + async getMaxBlockHeightInfo({ + tracingState, + }: { + tracingState: TracingState; + }): Promise { + const sqlSpan = tracingState.startSpan('block.SQL'); + const rows = await this.executeBlockQuery(); + sqlSpan.end(); + + const processingSpan = tracingState.startSpan('block.processing'); + const blockData = { + canonicalMaxBlockHeight: Number( + rows.filter((row) => row.chain_status === 'canonical')[0] + ), + pendingMaxBlockHeight: Number( + rows.filter((row) => row.chain_status === 'pending')[0] + ), + }; + processingSpan.end(); + return blockData; + } + + private async executeBlockQuery() { + return getBlockQuery(this.client); + } +} diff --git a/zkapp/utils.ts b/zkapp/utils.ts index 4c48afd..5dc2e86 100644 --- a/zkapp/utils.ts +++ b/zkapp/utils.ts @@ -97,7 +97,7 @@ async function updateContractState( await zkApp.update(Field(4)); } ); - transaction.sign([senderKey]).prove(); + await transaction.sign([senderKey]).prove(); await sendTransaction(transaction); }