Skip to content

Commit c405653

Browse files
authored
Add NetworkState Service (#113)
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
1 parent 44a437a commit c405653

File tree

11 files changed

+258
-15
lines changed

11 files changed

+258
-15
lines changed

schema.graphql

+10
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ type BlockInfo {
4646
distanceFromMaxBlockHeight: Int!
4747
}
4848

49+
type MaxBlockHeightInfo {
50+
canonicalMaxBlockHeight: Int!
51+
pendingMaxBlockHeight: Int!
52+
}
53+
4954
type TransactionInfo {
5055
status: String!
5156
hash: String!
@@ -75,7 +80,12 @@ type ActionOutput {
7580
actionState: ActionStates!
7681
}
7782

83+
type NetworkStateOutput {
84+
maxBlockHeight: MaxBlockHeightInfo
85+
}
86+
7887
type Query {
7988
events(input: EventFilterOptionsInput!): [EventOutput]!
8089
actions(input: ActionFilterOptionsInput!): [ActionOutput]!
90+
networkState: NetworkStateOutput!
8191
}

src/blockchain/types.ts

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ export type Action = {
2424
data: string[];
2525
};
2626

27+
export type NetworkState = {
28+
maxBlockHeight: MaxBlockHeightInfo;
29+
};
30+
31+
export type MaxBlockHeightInfo = {
32+
canonicalMaxBlockHeight: number;
33+
pendingMaxBlockHeight: number;
34+
};
35+
2736
export type BlockInfo = {
2837
height: number;
2938
stateHash: string;
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import type { EventFilterOptionsInput } from '../../resolvers-types.js';
2-
import type { Actions, Events } from '../../blockchain/types.js';
2+
import type {
3+
Actions,
4+
Events,
5+
NetworkState,
6+
} from '../../blockchain/types.js';
37

48
export interface DatabaseAdapter {
59
getEvents(input: EventFilterOptionsInput, options?: unknown): Promise<Events>;
610
getActions(
711
input: EventFilterOptionsInput,
812
options?: unknown
913
): Promise<Actions>;
14+
getNetworkState(options?: unknown): Promise<NetworkState>;
1015
}

src/db/archive-node-adapter/archive-node-adapter.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import postgres from 'postgres';
2-
import type { Actions, Events } from '../../blockchain/types.js';
2+
import type {
3+
Actions,
4+
Events,
5+
NetworkState,
6+
} from '../../blockchain/types.js';
37
import type { DatabaseAdapter } from './archive-node-adapter.interface.js';
48
import type {
59
ActionFilterOptionsInput,
@@ -10,6 +14,8 @@ import { EventsService } from '../../services/events-service/events-service.js';
1014
import { IEventsService } from '../../services/events-service/events-service.interface.js';
1115
import { ActionsService } from '../../services/actions-service/actions-service.js';
1216
import { IActionsService } from '../../services/actions-service/actions-service.interface.js';
17+
import { NetworkService } from '../../services/network-service/network-service.js';
18+
import { INetworkService } from '../../services/network-service/network-service.interface.js';
1319

1420
export class ArchiveNodeAdapter implements DatabaseAdapter {
1521
/**
@@ -21,6 +27,7 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
2127
private client: postgres.Sql;
2228
private eventsService: IEventsService;
2329
private actionsService: IActionsService;
30+
private networkService: INetworkService;
2431

2532
constructor(connectionString: string | undefined) {
2633
if (!connectionString)
@@ -30,6 +37,7 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
3037
this.client = postgres(connectionString);
3138
this.eventsService = new EventsService(this.client);
3239
this.actionsService = new ActionsService(this.client);
40+
this.networkService = new NetworkService(this.client);
3341
}
3442

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

57+
async getNetworkState(options: unknown): Promise<NetworkState> {
58+
return this.networkService.getNetworkState(options);
59+
}
60+
4961
async checkSQLSchema() {
5062
let tables;
5163
try {

src/db/sql/events-actions/queries.ts

+18
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,24 @@ export function getActionsQuery(
371371
`;
372372
}
373373

374+
export function getNetworkStateQuery(db_client: postgres.Sql) {
375+
return db_client`
376+
WITH max_heights AS (
377+
SELECT
378+
chain_status,
379+
MAX(height) AS max_height
380+
FROM blocks
381+
WHERE chain_status IN ('canonical', 'pending')
382+
GROUP BY chain_status
383+
)
384+
SELECT b.*
385+
FROM blocks b
386+
JOIN max_heights mh
387+
ON b.chain_status = mh.chain_status
388+
AND b.height = mh.max_height;
389+
`;
390+
}
391+
374392
export function checkActionState(db_client: postgres.Sql, actionState: string) {
375393
return db_client`
376394
SELECT field FROM zkapp_field WHERE field = ${actionState}

src/resolvers-types.ts

+19
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ export type BlockInfo = {
8383
timestamp: Scalars['String']['output'];
8484
};
8585

86+
export type NetworkStateOutput = {
87+
__typename?: 'NetworkStateOutput';
88+
maxBlockHeight: MaxBlockHeightInfo
89+
};
90+
91+
export type MaxBlockHeightInfo = {
92+
__typename?: 'MaxBlockHeightInfo';
93+
canonicalMaxBlockHeight: Scalars['Int']['output'];
94+
pendingMaxBlockHeight: Scalars['Int']['output'];
95+
};
96+
8697
export { BlockStatusFilter };
8798

8899
export type EventData = {
@@ -110,6 +121,7 @@ export type Query = {
110121
__typename?: 'Query';
111122
actions: Array<Maybe<ActionOutput>>;
112123
events: Array<Maybe<EventOutput>>;
124+
networkState: Maybe<NetworkStateOutput>;
113125
};
114126

115127
export type QueryActionsArgs = {
@@ -242,6 +254,8 @@ export type ResolversTypes = {
242254
ActionOutput: ResolverTypeWrapper<ActionOutput>;
243255
ActionStates: ResolverTypeWrapper<ActionStates>;
244256
BlockInfo: ResolverTypeWrapper<BlockInfo>;
257+
NetworkStateOutput: ResolverTypeWrapper<NetworkStateOutput>;
258+
MaxBlockHeightInfo: ResolverTypeWrapper<MaxBlockHeightInfo>;
245259
BlockStatusFilter: BlockStatusFilter;
246260
Boolean: ResolverTypeWrapper<Scalars['Boolean']['output']>;
247261
EventData: ResolverTypeWrapper<EventData>;
@@ -438,6 +452,11 @@ export type QueryResolvers<
438452
ContextType,
439453
RequireFields<QueryEventsArgs, 'input'>
440454
>;
455+
networkState?: Resolver<
456+
Maybe<ResolversTypes['NetworkStateOutput']>,
457+
ParentType,
458+
ContextType
459+
>;
441460
};
442461

443462
export type TransactionInfoResolvers<

src/resolvers.ts

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ const resolvers: Resolvers = {
3232
tracingState: new TracingState(graphQLSpan),
3333
});
3434
},
35+
36+
networkState: async (_, __, context) => {
37+
const graphQLSpan = setSpanNameFromGraphQLContext(
38+
context,
39+
'networkState.graphql'
40+
);
41+
return context.db_client.getNetworkState({
42+
tracingState: new TracingState(graphQLSpan),
43+
});
44+
},
3545
},
3646
};
3747

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { NetworkState } from '../../blockchain/types.js';
2+
3+
export interface INetworkService {
4+
getNetworkState(options: unknown): Promise<NetworkState>;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type postgres from 'postgres';
2+
import { NetworkState } from '../../blockchain/types.js';
3+
import { getNetworkStateQuery } from '../../db/sql/events-actions/queries.js';
4+
5+
import { INetworkService } from './network-service.interface.js';
6+
import {
7+
TracingState,
8+
extractTraceStateFromOptions,
9+
} from '../../tracing/tracer.js';
10+
11+
export { NetworkService };
12+
13+
class NetworkService implements INetworkService {
14+
private readonly client: postgres.Sql;
15+
16+
constructor(client: postgres.Sql) {
17+
this.client = client;
18+
}
19+
20+
async getNetworkState(options: unknown): Promise<NetworkState> {
21+
const tracingState = extractTraceStateFromOptions(options);
22+
return (await this.getNetworkStateData({ tracingState })) ?? [];
23+
}
24+
25+
async getNetworkStateData({
26+
tracingState,
27+
}: {
28+
tracingState: TracingState;
29+
}): Promise<NetworkState> {
30+
const sqlSpan = tracingState.startSpan('networkState.SQL');
31+
const rows = await this.executeNetworkStateQuery();
32+
sqlSpan.end();
33+
34+
const processingSpan = tracingState.startSpan('networkState.processing');
35+
const maxBlockHeightInfo = {
36+
canonicalMaxBlockHeight: Number(
37+
rows.filter((row) => row.chain_status === 'canonical')[0].height
38+
),
39+
pendingMaxBlockHeight: Number(
40+
rows.filter((row) => row.chain_status === 'pending')[0].height
41+
),
42+
};
43+
const networkState = {
44+
maxBlockHeight: maxBlockHeightInfo
45+
}
46+
processingSpan.end();
47+
return networkState;
48+
}
49+
50+
private async executeNetworkStateQuery() {
51+
return getNetworkStateQuery(this.client);
52+
}
53+
}

0 commit comments

Comments
 (0)