Skip to content

Commit

Permalink
Add NetworkState Service (#113)
Browse files Browse the repository at this point in the history
This PR introduces a new feature to fetch the maximum block height
information from an archive node. This information can then be used to
check the synchronization of archive nodes with Mina nodes.

The query below returns `canonicalMaxBlockHeight` and
`pendingMaxBlockHeight`:
```graphql
query maxBlockHeightInfo {
  networkState {
    maxBlockHeight {
      canonicalMaxBlockHeight
      pendingMaxBlockHeight
    }
  }
}
```

closes #111
  • Loading branch information
boray authored Jan 15, 2025
1 parent 44a437a commit c405653
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 15 deletions.
10 changes: 10 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ type BlockInfo {
distanceFromMaxBlockHeight: Int!
}

type MaxBlockHeightInfo {
canonicalMaxBlockHeight: Int!
pendingMaxBlockHeight: Int!
}

type TransactionInfo {
status: String!
hash: String!
Expand Down Expand Up @@ -75,7 +80,12 @@ type ActionOutput {
actionState: ActionStates!
}

type NetworkStateOutput {
maxBlockHeight: MaxBlockHeightInfo
}

type Query {
events(input: EventFilterOptionsInput!): [EventOutput]!
actions(input: ActionFilterOptionsInput!): [ActionOutput]!
networkState: NetworkStateOutput!
}
9 changes: 9 additions & 0 deletions src/blockchain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export type Action = {
data: string[];
};

export type NetworkState = {
maxBlockHeight: MaxBlockHeightInfo;
};

export type MaxBlockHeightInfo = {
canonicalMaxBlockHeight: number;
pendingMaxBlockHeight: number;
};

export type BlockInfo = {
height: number;
stateHash: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import type { EventFilterOptionsInput } from '../../resolvers-types.js';
import type { Actions, Events } from '../../blockchain/types.js';
import type {
Actions,
Events,
NetworkState,
} from '../../blockchain/types.js';

export interface DatabaseAdapter {
getEvents(input: EventFilterOptionsInput, options?: unknown): Promise<Events>;
getActions(
input: EventFilterOptionsInput,
options?: unknown
): Promise<Actions>;
getNetworkState(options?: unknown): Promise<NetworkState>;
}
14 changes: 13 additions & 1 deletion src/db/archive-node-adapter/archive-node-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import postgres from 'postgres';
import type { Actions, Events } from '../../blockchain/types.js';
import type {
Actions,
Events,
NetworkState,
} from '../../blockchain/types.js';
import type { DatabaseAdapter } from './archive-node-adapter.interface.js';
import type {
ActionFilterOptionsInput,
Expand All @@ -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 { NetworkService } from '../../services/network-service/network-service.js';
import { INetworkService } from '../../services/network-service/network-service.interface.js';

export class ArchiveNodeAdapter implements DatabaseAdapter {
/**
Expand All @@ -21,6 +27,7 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
private client: postgres.Sql;
private eventsService: IEventsService;
private actionsService: IActionsService;
private networkService: INetworkService;

constructor(connectionString: string | undefined) {
if (!connectionString)
Expand All @@ -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.networkService = new NetworkService(this.client);
}

async getEvents(
Expand All @@ -46,6 +54,10 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
return this.actionsService.getActions(input, options);
}

async getNetworkState(options: unknown): Promise<NetworkState> {
return this.networkService.getNetworkState(options);
}

async checkSQLSchema() {
let tables;
try {
Expand Down
18 changes: 18 additions & 0 deletions src/db/sql/events-actions/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,24 @@ export function getActionsQuery(
`;
}

export function getNetworkStateQuery(db_client: postgres.Sql) {
return db_client`
WITH max_heights AS (
SELECT
chain_status,
MAX(height) AS max_height
FROM blocks
WHERE chain_status IN ('canonical', 'pending')
GROUP BY chain_status
)
SELECT b.*
FROM blocks b
JOIN max_heights mh
ON b.chain_status = mh.chain_status
AND b.height = mh.max_height;
`;
}

export function checkActionState(db_client: postgres.Sql, actionState: string) {
return db_client`
SELECT field FROM zkapp_field WHERE field = ${actionState}
Expand Down
19 changes: 19 additions & 0 deletions src/resolvers-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ export type BlockInfo = {
timestamp: Scalars['String']['output'];
};

export type NetworkStateOutput = {
__typename?: 'NetworkStateOutput';
maxBlockHeight: MaxBlockHeightInfo
};

export type MaxBlockHeightInfo = {
__typename?: 'MaxBlockHeightInfo';
canonicalMaxBlockHeight: Scalars['Int']['output'];
pendingMaxBlockHeight: Scalars['Int']['output'];
};

export { BlockStatusFilter };

export type EventData = {
Expand Down Expand Up @@ -110,6 +121,7 @@ export type Query = {
__typename?: 'Query';
actions: Array<Maybe<ActionOutput>>;
events: Array<Maybe<EventOutput>>;
networkState: Maybe<NetworkStateOutput>;
};

export type QueryActionsArgs = {
Expand Down Expand Up @@ -242,6 +254,8 @@ export type ResolversTypes = {
ActionOutput: ResolverTypeWrapper<ActionOutput>;
ActionStates: ResolverTypeWrapper<ActionStates>;
BlockInfo: ResolverTypeWrapper<BlockInfo>;
NetworkStateOutput: ResolverTypeWrapper<NetworkStateOutput>;
MaxBlockHeightInfo: ResolverTypeWrapper<MaxBlockHeightInfo>;
BlockStatusFilter: BlockStatusFilter;
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
EventData: ResolverTypeWrapper<EventData>;
Expand Down Expand Up @@ -438,6 +452,11 @@ export type QueryResolvers<
ContextType,
RequireFields<QueryEventsArgs, 'input'>
>;
networkState?: Resolver<
Maybe<ResolversTypes['NetworkStateOutput']>,
ParentType,
ContextType
>;
};

export type TransactionInfoResolvers<
Expand Down
10 changes: 10 additions & 0 deletions src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ const resolvers: Resolvers = {
tracingState: new TracingState(graphQLSpan),
});
},

networkState: async (_, __, context) => {
const graphQLSpan = setSpanNameFromGraphQLContext(
context,
'networkState.graphql'
);
return context.db_client.getNetworkState({
tracingState: new TracingState(graphQLSpan),
});
},
},
};

Expand Down
5 changes: 5 additions & 0 deletions src/services/network-service/network-service.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { NetworkState } from '../../blockchain/types.js';

export interface INetworkService {
getNetworkState(options: unknown): Promise<NetworkState>;
}
53 changes: 53 additions & 0 deletions src/services/network-service/network-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type postgres from 'postgres';
import { NetworkState } from '../../blockchain/types.js';
import { getNetworkStateQuery } from '../../db/sql/events-actions/queries.js';

import { INetworkService } from './network-service.interface.js';
import {
TracingState,
extractTraceStateFromOptions,
} from '../../tracing/tracer.js';

export { NetworkService };

class NetworkService implements INetworkService {
private readonly client: postgres.Sql;

constructor(client: postgres.Sql) {
this.client = client;
}

async getNetworkState(options: unknown): Promise<NetworkState> {
const tracingState = extractTraceStateFromOptions(options);
return (await this.getNetworkStateData({ tracingState })) ?? [];
}

async getNetworkStateData({
tracingState,
}: {
tracingState: TracingState;
}): Promise<NetworkState> {
const sqlSpan = tracingState.startSpan('networkState.SQL');
const rows = await this.executeNetworkStateQuery();
sqlSpan.end();

const processingSpan = tracingState.startSpan('networkState.processing');
const maxBlockHeightInfo = {
canonicalMaxBlockHeight: Number(
rows.filter((row) => row.chain_status === 'canonical')[0].height
),
pendingMaxBlockHeight: Number(
rows.filter((row) => row.chain_status === 'pending')[0].height
),
};
const networkState = {
maxBlockHeight: maxBlockHeightInfo
}
processingSpan.end();
return networkState;
}

private async executeNetworkStateQuery() {
return getNetworkStateQuery(this.client);
}
}
Loading

0 comments on commit c405653

Please sign in to comment.