@o1-labs/mina-archive-node-graphql 0.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +92 -268
- package/build/src/blockchain/utils.js.map +1 -1
- package/build/src/config.js +86 -0
- package/build/src/config.js.map +1 -0
- package/build/src/db/archive-node-adapter/archive-node-adapter.js +29 -2
- package/build/src/db/archive-node-adapter/archive-node-adapter.js.map +1 -1
- package/build/src/db/archive-node-adapter/postgres-options.js +35 -0
- package/build/src/db/archive-node-adapter/postgres-options.js.map +1 -0
- package/build/src/db/sql/best-chain.js +5 -0
- package/build/src/db/sql/best-chain.js.map +1 -0
- package/build/src/db/sql/events-actions/queries.js +102 -21
- package/build/src/db/sql/events-actions/queries.js.map +1 -1
- package/build/src/db/sql/verification-key-updates/queries.js +100 -0
- package/build/src/db/sql/verification-key-updates/queries.js.map +1 -0
- package/build/src/db/sql/zkapp-commands/queries.js +363 -0
- package/build/src/db/sql/zkapp-commands/queries.js.map +1 -0
- package/build/src/db/sql/zkapp-commands/types.js +2 -0
- package/build/src/db/sql/zkapp-commands/types.js.map +1 -0
- package/build/src/errors/error.js +4 -1
- package/build/src/errors/error.js.map +1 -1
- package/build/src/index.js +44 -7
- package/build/src/index.js.map +1 -1
- package/build/src/resolvers.js +42 -23
- package/build/src/resolvers.js.map +1 -1
- package/build/src/schema-version.js +2 -0
- package/build/src/schema-version.js.map +1 -0
- package/build/src/server/cors.js +41 -0
- package/build/src/server/cors.js.map +1 -0
- package/build/src/server/graceful-shutdown.js +42 -0
- package/build/src/server/graceful-shutdown.js.map +1 -0
- package/build/src/server/graphql-armor.js +39 -0
- package/build/src/server/graphql-armor.js.map +1 -0
- package/build/src/server/logger.js +45 -0
- package/build/src/server/logger.js.map +1 -0
- package/build/src/server/metrics.js +72 -0
- package/build/src/server/metrics.js.map +1 -0
- package/build/src/server/plugins.js +31 -15
- package/build/src/server/plugins.js.map +1 -1
- package/build/src/server/rate-limit.js +143 -0
- package/build/src/server/rate-limit.js.map +1 -0
- package/build/src/server/readiness.js +45 -0
- package/build/src/server/readiness.js.map +1 -0
- package/build/src/server/request-logging.js +58 -0
- package/build/src/server/request-logging.js.map +1 -0
- package/build/src/server/server.js +38 -13
- package/build/src/server/server.js.map +1 -1
- package/build/src/services/actions-service/actions-service.js +26 -15
- package/build/src/services/actions-service/actions-service.js.map +1 -1
- package/build/src/services/blocks-service/blocks-service.js +10 -9
- package/build/src/services/blocks-service/blocks-service.js.map +1 -1
- package/build/src/services/network-service/network-service.js +17 -9
- package/build/src/services/network-service/network-service.js.map +1 -1
- package/build/src/services/verification-key-updates-service/verification-key-updates-service.interface.js +2 -0
- package/build/src/services/verification-key-updates-service/verification-key-updates-service.interface.js.map +1 -0
- package/build/src/services/verification-key-updates-service/verification-key-updates-service.js +41 -0
- package/build/src/services/verification-key-updates-service/verification-key-updates-service.js.map +1 -0
- package/build/src/services/zkapp-commands-service/zkapp-commands-service.interface.js +2 -0
- package/build/src/services/zkapp-commands-service/zkapp-commands-service.interface.js.map +1 -0
- package/build/src/services/zkapp-commands-service/zkapp-commands-service.js +115 -0
- package/build/src/services/zkapp-commands-service/zkapp-commands-service.js.map +1 -0
- package/build/src/tracing/jaeger-tracing.js +2 -1
- package/build/src/tracing/jaeger-tracing.js.map +1 -1
- package/build/tests/devnet-dump/action-state-invariants.js +165 -0
- package/build/tests/devnet-dump/action-state-invariants.js.map +1 -0
- package/build/tests/devnet-dump/devnet-dump.test.js +24 -10
- package/build/tests/devnet-dump/devnet-dump.test.js.map +1 -1
- package/build/tests/devnet-dump/setup.js +63 -17
- package/build/tests/devnet-dump/setup.js.map +1 -1
- package/build/tests/integration/action-state-ordering.test.js +139 -0
- package/build/tests/integration/action-state-ordering.test.js.map +1 -0
- package/build/tests/integration/action-state-setup.js +48 -0
- package/build/tests/integration/action-state-setup.js.map +1 -0
- package/build/tests/integration/consensus-vs-chain.test.js +81 -0
- package/build/tests/integration/consensus-vs-chain.test.js.map +1 -0
- package/build/tests/integration/integration.test.js +166 -2
- package/build/tests/integration/integration.test.js.map +1 -1
- package/build/tests/integration/postgres-timeout.test.js +92 -0
- package/build/tests/integration/postgres-timeout.test.js.map +1 -0
- package/build/tests/integration/setup.js +58 -1
- package/build/tests/integration/setup.js.map +1 -1
- package/build/tests/integration/verification-key-setup.js +49 -0
- package/build/tests/integration/verification-key-setup.js.map +1 -0
- package/build/tests/integration/verification-key-updates.test.js +159 -0
- package/build/tests/integration/verification-key-updates.test.js.map +1 -0
- package/build/tests/live-api/events.test.js +11 -7
- package/build/tests/live-api/events.test.js.map +1 -1
- package/build/tests/live-api/networkState.test.js +1 -0
- package/build/tests/live-api/networkState.test.js.map +1 -1
- package/build/tests/live-network/live-network.test.js +1 -0
- package/build/tests/live-network/live-network.test.js.map +1 -1
- package/build/tests/resolvers.test.js +65 -37
- package/build/tests/resolvers.test.js.map +1 -1
- package/build/tests/services/verification-key-updates-service/verification-key-updates-service.test.js +35 -0
- package/build/tests/services/verification-key-updates-service/verification-key-updates-service.test.js.map +1 -0
- package/build/tests/services/zkapp-commands-service/zkapp-commands-service.test.js +159 -0
- package/build/tests/services/zkapp-commands-service/zkapp-commands-service.test.js.map +1 -0
- package/build/tests/test-helpers.js +15 -0
- package/build/tests/test-helpers.js.map +1 -1
- package/build/tests/unit/best-chain-anchor.test.js +70 -0
- package/build/tests/unit/best-chain-anchor.test.js.map +1 -0
- package/build/tests/unit/config.test.js +106 -0
- package/build/tests/unit/config.test.js.map +1 -0
- package/build/tests/unit/cors.test.js +95 -0
- package/build/tests/unit/cors.test.js.map +1 -0
- package/build/tests/unit/error-masking.test.js +119 -0
- package/build/tests/unit/error-masking.test.js.map +1 -0
- package/build/tests/unit/graceful-shutdown.test.js +99 -0
- package/build/tests/unit/graceful-shutdown.test.js.map +1 -0
- package/build/tests/unit/graphql-armor.test.js +121 -0
- package/build/tests/unit/graphql-armor.test.js.map +1 -0
- package/build/tests/unit/logging.test.js +120 -0
- package/build/tests/unit/logging.test.js.map +1 -0
- package/build/tests/unit/metrics.test.js +114 -0
- package/build/tests/unit/metrics.test.js.map +1 -0
- package/build/tests/unit/network-service.test.js +66 -0
- package/build/tests/unit/network-service.test.js.map +1 -0
- package/build/tests/unit/postgres-options.test.js +56 -0
- package/build/tests/unit/postgres-options.test.js.map +1 -0
- package/build/tests/unit/rate-limit.test.js +271 -0
- package/build/tests/unit/rate-limit.test.js.map +1 -0
- package/build/tests/unit/readiness.test.js +75 -0
- package/build/tests/unit/readiness.test.js.map +1 -0
- package/build/tests/unit/schema-nullability.test.js +40 -0
- package/build/tests/unit/schema-nullability.test.js.map +1 -0
- package/build/tests/unit/schema-version.test.js +48 -0
- package/build/tests/unit/schema-version.test.js.map +1 -0
- package/build/tests/unit/yoga-http-contract.test.js +30 -0
- package/build/tests/unit/yoga-http-contract.test.js.map +1 -0
- package/build/tests/unit/zkapp-commands-gating.test.js +38 -0
- package/build/tests/unit/zkapp-commands-gating.test.js.map +1 -0
- package/build/zkapp/utils.js +2 -0
- package/build/zkapp/utils.js.map +1 -1
- package/package.json +33 -14
- package/schema.graphql +126 -8
- package/src/blockchain/types.ts +65 -1
- package/src/blockchain/utils.ts +26 -2
- package/src/config.ts +133 -0
- package/src/db/archive-node-adapter/archive-node-adapter.interface.ts +26 -3
- package/src/db/archive-node-adapter/archive-node-adapter.ts +65 -2
- package/src/db/archive-node-adapter/postgres-options.ts +90 -0
- package/src/db/sql/best-chain.ts +34 -0
- package/src/db/sql/events-actions/queries.ts +168 -24
- package/src/db/sql/verification-key-updates/queries.ts +132 -0
- package/src/db/sql/zkapp-commands/queries.ts +406 -0
- package/src/db/sql/zkapp-commands/types.ts +43 -0
- package/src/envionment.d.ts +20 -0
- package/src/errors/error.ts +17 -1
- package/src/index.ts +64 -7
- package/src/resolvers-types.ts +311 -24
- package/src/resolvers.ts +70 -34
- package/src/schema-version.ts +24 -0
- package/src/server/cors.ts +97 -0
- package/src/server/graceful-shutdown.ts +76 -0
- package/src/server/graphql-armor.ts +78 -0
- package/src/server/logger.ts +72 -0
- package/src/server/metrics.ts +104 -0
- package/src/server/plugins.ts +53 -19
- package/src/server/rate-limit.ts +274 -0
- package/src/server/readiness.ts +78 -0
- package/src/server/request-logging.ts +83 -0
- package/src/server/server.ts +70 -15
- package/src/services/actions-service/actions-service.ts +87 -24
- package/src/services/blocks-service/blocks-service.ts +16 -14
- package/src/services/network-service/network-service.ts +30 -12
- package/src/services/verification-key-updates-service/verification-key-updates-service.interface.ts +9 -0
- package/src/services/verification-key-updates-service/verification-key-updates-service.ts +72 -0
- package/src/services/zkapp-commands-service/zkapp-commands-service.interface.ts +9 -0
- package/src/services/zkapp-commands-service/zkapp-commands-service.ts +181 -0
- package/src/tracing/jaeger-tracing.ts +2 -1
- package/build/tests/consensus/run-compare.js +0 -40
- package/build/tests/consensus/run-compare.js.map +0 -1
- package/build/tests/consensus/select-consensus-precomputed.js +0 -80
- package/build/tests/consensus/select-consensus-precomputed.js.map +0 -1
- package/build/tests/consensus/types.js +0 -15
- package/build/tests/consensus/types.js.map +0 -1
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
Events,
|
|
5
5
|
NetworkState,
|
|
6
6
|
Blocks,
|
|
7
|
+
VerificationKeyUpdates,
|
|
8
|
+
ZkappCommands,
|
|
7
9
|
} from '../../blockchain/types.js';
|
|
8
10
|
import type { DatabaseAdapter } from './archive-node-adapter.interface.js';
|
|
9
11
|
import type {
|
|
@@ -11,16 +13,29 @@ import type {
|
|
|
11
13
|
EventFilterOptionsInput,
|
|
12
14
|
BlockQueryInput,
|
|
13
15
|
BlockSortByInput,
|
|
16
|
+
VerificationKeyUpdateFilterInput,
|
|
17
|
+
ZkappCommandFilterOptionsInput,
|
|
14
18
|
} from '../../resolvers-types.js';
|
|
15
19
|
import { getTables, USED_TABLES } from '../../db/sql/events-actions/queries.js';
|
|
20
|
+
import { buildPostgresOptions } from './postgres-options.js';
|
|
16
21
|
import { EventsService } from '../../services/events-service/events-service.js';
|
|
17
22
|
import { IEventsService } from '../../services/events-service/events-service.interface.js';
|
|
18
23
|
import { ActionsService } from '../../services/actions-service/actions-service.js';
|
|
19
24
|
import { IActionsService } from '../../services/actions-service/actions-service.interface.js';
|
|
25
|
+
import { ZkappCommandsService } from '../../services/zkapp-commands-service/zkapp-commands-service.js';
|
|
26
|
+
import { IZkappCommandsService } from '../../services/zkapp-commands-service/zkapp-commands-service.interface.js';
|
|
20
27
|
import { NetworkService } from '../../services/network-service/network-service.js';
|
|
21
28
|
import { INetworkService } from '../../services/network-service/network-service.interface.js';
|
|
22
29
|
import { BlocksService } from '../../services/blocks-service/blocks-service.js';
|
|
23
30
|
import { IBlocksService } from '../../services/blocks-service/blocks-service.interface.js';
|
|
31
|
+
import { VerificationKeyUpdatesService } from '../../services/verification-key-updates-service/verification-key-updates-service.js';
|
|
32
|
+
import { IVerificationKeyUpdatesService } from '../../services/verification-key-updates-service/verification-key-updates-service.interface.js';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Connect deadline for the readiness pinger, in seconds. Kept short so a probe
|
|
36
|
+
* fails fast rather than hanging for the driver's 30s default.
|
|
37
|
+
*/
|
|
38
|
+
const PING_CONNECT_TIMEOUT_S = 2;
|
|
24
39
|
|
|
25
40
|
export class ArchiveNodeAdapter implements DatabaseAdapter {
|
|
26
41
|
/**
|
|
@@ -30,21 +45,38 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
|
|
|
30
45
|
* `postgres.Sql` instance across the adapter is safe.
|
|
31
46
|
*/
|
|
32
47
|
private client: postgres.Sql;
|
|
48
|
+
/**
|
|
49
|
+
* Dedicated single-connection client for readiness pings. postgres.js queues
|
|
50
|
+
* onto busy connections, so using the main pool can make a merely-busy DB look
|
|
51
|
+
* unreachable while long GraphQL queries are running.
|
|
52
|
+
*/
|
|
53
|
+
private pingClient: postgres.Sql;
|
|
33
54
|
private eventsService: IEventsService;
|
|
34
55
|
private actionsService: IActionsService;
|
|
56
|
+
private zkappCommandsService: IZkappCommandsService;
|
|
35
57
|
private networkService: INetworkService;
|
|
36
58
|
private blocksService: IBlocksService;
|
|
59
|
+
private verificationKeyUpdatesService: IVerificationKeyUpdatesService;
|
|
37
60
|
|
|
38
61
|
constructor(connectionString: string | undefined) {
|
|
39
62
|
if (!connectionString)
|
|
40
63
|
throw new Error(
|
|
41
64
|
'Missing Postgres Connection String. Please provide a valid connection string in the environment variables or in your configuration file to connect to the Postgres database.'
|
|
42
65
|
);
|
|
43
|
-
this.client = postgres(connectionString);
|
|
66
|
+
this.client = postgres(connectionString, buildPostgresOptions());
|
|
67
|
+
this.pingClient = postgres(connectionString, {
|
|
68
|
+
max: 1,
|
|
69
|
+
idle_timeout: 60,
|
|
70
|
+
connect_timeout: PING_CONNECT_TIMEOUT_S,
|
|
71
|
+
});
|
|
44
72
|
this.eventsService = new EventsService(this.client);
|
|
45
73
|
this.actionsService = new ActionsService(this.client);
|
|
74
|
+
this.zkappCommandsService = new ZkappCommandsService(this.client);
|
|
46
75
|
this.networkService = new NetworkService(this.client);
|
|
47
76
|
this.blocksService = new BlocksService(this.client);
|
|
77
|
+
this.verificationKeyUpdatesService = new VerificationKeyUpdatesService(
|
|
78
|
+
this.client
|
|
79
|
+
);
|
|
48
80
|
}
|
|
49
81
|
|
|
50
82
|
async getEvents(
|
|
@@ -61,10 +93,27 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
|
|
|
61
93
|
return this.actionsService.getActions(input, options);
|
|
62
94
|
}
|
|
63
95
|
|
|
96
|
+
async getZkappCommands(
|
|
97
|
+
input: ZkappCommandFilterOptionsInput,
|
|
98
|
+
options: unknown
|
|
99
|
+
): Promise<ZkappCommands> {
|
|
100
|
+
return this.zkappCommandsService.getZkappCommands(input, options);
|
|
101
|
+
}
|
|
102
|
+
|
|
64
103
|
async getNetworkState(options: unknown): Promise<NetworkState> {
|
|
65
104
|
return this.networkService.getNetworkState(options);
|
|
66
105
|
}
|
|
67
106
|
|
|
107
|
+
async getVerificationKeyUpdates(
|
|
108
|
+
input: VerificationKeyUpdateFilterInput,
|
|
109
|
+
options: unknown
|
|
110
|
+
): Promise<VerificationKeyUpdates> {
|
|
111
|
+
return this.verificationKeyUpdatesService.getVerificationKeyUpdates(
|
|
112
|
+
input,
|
|
113
|
+
options
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
68
117
|
async getBlocks(
|
|
69
118
|
query: BlockQueryInput | null | undefined,
|
|
70
119
|
limit: number | null | undefined,
|
|
@@ -95,7 +144,21 @@ export class ArchiveNodeAdapter implements DatabaseAdapter {
|
|
|
95
144
|
}
|
|
96
145
|
}
|
|
97
146
|
|
|
147
|
+
async ping(): Promise<boolean> {
|
|
148
|
+
try {
|
|
149
|
+
await this.pingClient`SELECT 1`;
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.warn(
|
|
153
|
+
`[readiness] database ping failed: ${
|
|
154
|
+
error instanceof Error ? error.message : String(error)
|
|
155
|
+
}`
|
|
156
|
+
);
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
98
161
|
async close() {
|
|
99
|
-
|
|
162
|
+
await Promise.all([this.client.end(), this.pingClient.end()]);
|
|
100
163
|
}
|
|
101
164
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type postgres from 'postgres';
|
|
2
|
+
|
|
3
|
+
export { buildPostgresOptions, resolvePoolConfig, POOL_DEFAULTS };
|
|
4
|
+
export type { PostgresPoolConfig };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Connection-pool and timeout configuration for the archive-node Postgres
|
|
8
|
+
* client. Without these limits a single expensive query can hold a connection
|
|
9
|
+
* open indefinitely, exhausting the pool and cascading into an outage. The
|
|
10
|
+
* defaults are deliberately conservative so the server is safe to expose
|
|
11
|
+
* publicly out of the box, and every value is tunable via the environment.
|
|
12
|
+
*/
|
|
13
|
+
interface PostgresPoolConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Maximum pooled connections in total, not per host: a multi-host `PG_CONN`
|
|
16
|
+
* fails over to the first reachable host rather than fanning out, so the pool
|
|
17
|
+
* only ever points at one host at a time.
|
|
18
|
+
*/
|
|
19
|
+
max: number;
|
|
20
|
+
/** Seconds a connection may sit idle before it is closed. */
|
|
21
|
+
idleTimeout: number;
|
|
22
|
+
/** Seconds to wait for a new connection before giving up. */
|
|
23
|
+
connectTimeout: number;
|
|
24
|
+
/**
|
|
25
|
+
* Server-side `statement_timeout` in milliseconds. A query running longer
|
|
26
|
+
* than this is cancelled by Postgres. `0` disables the timeout.
|
|
27
|
+
*/
|
|
28
|
+
statementTimeout: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const POOL_DEFAULTS: PostgresPoolConfig = {
|
|
32
|
+
max: 10,
|
|
33
|
+
idleTimeout: 30,
|
|
34
|
+
connectTimeout: 30,
|
|
35
|
+
statementTimeout: 15_000,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parse a non-negative integer from an env value, falling back to `fallback`
|
|
40
|
+
* when the value is missing or malformed. We fall back rather than throw so a
|
|
41
|
+
* stray typo never silently disables a safety limit (e.g. `max` becoming 0).
|
|
42
|
+
*/
|
|
43
|
+
function intFromEnv(value: string | undefined, fallback: number): number {
|
|
44
|
+
if (value === undefined || value.trim() === '') return fallback;
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isInteger(parsed) || parsed < 0) return fallback;
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type EnvSource = Record<string, string | undefined>;
|
|
51
|
+
|
|
52
|
+
function resolvePoolConfig(env: EnvSource = process.env): PostgresPoolConfig {
|
|
53
|
+
return {
|
|
54
|
+
// `max` must be at least 1 — a pool of 0 connections can never serve a query.
|
|
55
|
+
max: Math.max(1, intFromEnv(env.PG_MAX_CONNECTIONS, POOL_DEFAULTS.max)),
|
|
56
|
+
idleTimeout: intFromEnv(env.PG_IDLE_TIMEOUT, POOL_DEFAULTS.idleTimeout),
|
|
57
|
+
connectTimeout: intFromEnv(
|
|
58
|
+
env.PG_CONNECT_TIMEOUT,
|
|
59
|
+
POOL_DEFAULTS.connectTimeout
|
|
60
|
+
),
|
|
61
|
+
statementTimeout: intFromEnv(
|
|
62
|
+
env.PG_STATEMENT_TIMEOUT,
|
|
63
|
+
POOL_DEFAULTS.statementTimeout
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build the options object passed to `postgres()`. `statement_timeout` is sent
|
|
70
|
+
* as a startup connection parameter so it applies to every query on every
|
|
71
|
+
* connection in the pool.
|
|
72
|
+
*/
|
|
73
|
+
function buildPostgresOptions(
|
|
74
|
+
env: EnvSource = process.env
|
|
75
|
+
): postgres.Options<Record<string, never>> {
|
|
76
|
+
const config = resolvePoolConfig(env);
|
|
77
|
+
return {
|
|
78
|
+
max: config.max,
|
|
79
|
+
idle_timeout: config.idleTimeout,
|
|
80
|
+
connect_timeout: config.connectTimeout,
|
|
81
|
+
connection: {
|
|
82
|
+
// Sent as a startup connection parameter, so it applies to every query.
|
|
83
|
+
// Stringified deliberately: postgres.js drops falsy startup parameters,
|
|
84
|
+
// so numeric 0 would be omitted instead of explicitly disabling it.
|
|
85
|
+
// Its type narrows this specific key to number, but the startup-packet
|
|
86
|
+
// implementation serialises strings and the index signature allows them.
|
|
87
|
+
statement_timeout: String(config.statementTimeout) as unknown as number,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type postgres from 'postgres';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The height of the best chain's tip — the anchor every "walk back from the tip"
|
|
5
|
+
* recursive CTE starts from.
|
|
6
|
+
*
|
|
7
|
+
* This is deliberately NOT `(SELECT MAX(height) FROM blocks)`. The archive keeps every
|
|
8
|
+
* block it ever saw, so after a hard fork the abandoned chain's blocks stay in the table
|
|
9
|
+
* ABOVE the new chain's tip, marked `orphaned`. A global max then lands on a dead block:
|
|
10
|
+
* with the `chain_status = 'pending'` guard the anchor matches nothing and the walk
|
|
11
|
+
* silently degrades to canonical-only rows; without the guard the walk follows the dead
|
|
12
|
+
* chain. Either way the live tip and everything the network produced since the fork is
|
|
13
|
+
* invisible to `inBestChain`, events, actions and action-state resolution.
|
|
14
|
+
*
|
|
15
|
+
* Seen on mainnet at the Mesa upgrade (2026-09-03): the old chain reached height 548187
|
|
16
|
+
* before stopping, the fork block was 548147, and for the whole first hour
|
|
17
|
+
* `blocks(inBestChain: true)` returned nothing above 548147 while `networkState`
|
|
18
|
+
* correctly reported a pending tip of 548164 — the two disagreed because only one of
|
|
19
|
+
* them excluded orphans.
|
|
20
|
+
*
|
|
21
|
+
* Restricting the max to non-orphaned blocks makes the anchor agree with
|
|
22
|
+
* `networkState.pendingMaxBlockHeight` (which is computed per `chain_status`), and it is
|
|
23
|
+
* a no-op in normal operation, where the highest block is always the pending tip.
|
|
24
|
+
*/
|
|
25
|
+
export const BEST_CHAIN_TIP_HEIGHT_SQL =
|
|
26
|
+
"(SELECT MAX(height) FROM blocks WHERE chain_status <> 'orphaned')";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* {@link BEST_CHAIN_TIP_HEIGHT_SQL} as a fragment for tagged-template queries. The text is
|
|
30
|
+
* a constant with no user input, which is what makes `unsafe` safe here.
|
|
31
|
+
*/
|
|
32
|
+
export function bestChainTipHeight(db_client: postgres.Sql) {
|
|
33
|
+
return db_client.unsafe(BEST_CHAIN_TIP_HEIGHT_SQL);
|
|
34
|
+
}
|
|
@@ -2,6 +2,7 @@ import type postgres from 'postgres';
|
|
|
2
2
|
import { ArchiveNodeDatabaseRow } from './types.js';
|
|
3
3
|
import { BlockStatusFilter } from '../../../blockchain/types.js';
|
|
4
4
|
import { BLOCK_RANGE_SIZE } from '../../../server/server.js';
|
|
5
|
+
import { bestChainTipHeight } from '../best-chain.js';
|
|
5
6
|
|
|
6
7
|
function fullChainCTE(db_client: postgres.Sql, from?: string, to?: string) {
|
|
7
8
|
let toAsNum = to ? Number(to) : undefined;
|
|
@@ -20,20 +21,27 @@ function fullChainCTE(db_client: postgres.Sql, from?: string, to?: string) {
|
|
|
20
21
|
FROM
|
|
21
22
|
blocks b
|
|
22
23
|
WHERE
|
|
23
|
-
height = (
|
|
24
|
+
height = ${bestChainTipHeight(db_client)}
|
|
24
25
|
)
|
|
25
26
|
UNION ALL
|
|
26
27
|
SELECT
|
|
27
28
|
b.id, b.state_hash, b.parent_hash, b.parent_id, b.height, b.global_slot_since_genesis, b.global_slot_since_hard_fork, b.timestamp, b.chain_status, b.ledger_hash, b.last_vrf_output
|
|
28
29
|
FROM
|
|
29
30
|
blocks b
|
|
31
|
+
-- The walk terminates because genesis has parent_id = NULL, so the
|
|
32
|
+
-- id = parent_id join can never match there. No block is its own parent
|
|
33
|
+
-- (parent_id is a FK to blocks.id; a self-reference would be a cycle), so
|
|
34
|
+
-- no explicit id <> parent_id guard is needed. A schema/hard-fork change
|
|
35
|
+
-- that ever self-references a block would loop here — see the matching
|
|
36
|
+
-- note in getZkappsWithPendingEventsQuery.
|
|
30
37
|
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
|
|
31
|
-
AND pending_chain.id <> pending_chain.parent_id
|
|
32
38
|
AND pending_chain.chain_status <> 'canonical'
|
|
33
|
-
),
|
|
39
|
+
),
|
|
34
40
|
full_chain AS (
|
|
35
41
|
SELECT
|
|
36
|
-
DISTINCT id, state_hash, parent_id, parent_hash, height, global_slot_since_genesis, global_slot_since_hard_fork, timestamp, chain_status, ledger_hash, (
|
|
42
|
+
DISTINCT id, state_hash, parent_id, parent_hash, height, global_slot_since_genesis, global_slot_since_hard_fork, timestamp, chain_status, ledger_hash, ${bestChainTipHeight(
|
|
43
|
+
db_client
|
|
44
|
+
)} - height AS distance_from_max_block_height, last_vrf_output
|
|
37
45
|
FROM
|
|
38
46
|
(
|
|
39
47
|
SELECT
|
|
@@ -63,10 +71,9 @@ function fullChainCTE(db_client: postgres.Sql, from?: string, to?: string) {
|
|
|
63
71
|
// If no params ar provided, then we query the last BLOCK_RANGE_SIZE blocks
|
|
64
72
|
fromAsNum
|
|
65
73
|
? db_client`AND b.height >= ${fromAsNum} AND b.height < ${toAsNum!}`
|
|
66
|
-
: db_client`AND b.height >= (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
) - ${BLOCK_RANGE_SIZE}`
|
|
74
|
+
: db_client`AND b.height >= ${bestChainTipHeight(
|
|
75
|
+
db_client
|
|
76
|
+
)} - ${BLOCK_RANGE_SIZE}`
|
|
70
77
|
}
|
|
71
78
|
) AS full_chain
|
|
72
79
|
)
|
|
@@ -256,8 +263,8 @@ function emittedActionsCTE(db_client: postgres.Sql) {
|
|
|
256
263
|
|
|
257
264
|
function emittedActionStateCTE(
|
|
258
265
|
db_client: postgres.Sql,
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
fromActionStateHeight?: string,
|
|
267
|
+
endActionStateHeight?: string
|
|
261
268
|
) {
|
|
262
269
|
return db_client`
|
|
263
270
|
emitted_action_state AS (
|
|
@@ -308,14 +315,28 @@ function emittedActionStateCTE(
|
|
|
308
315
|
INNER JOIN zkapp_field zkf4 ON zkf4.id = zks.element4
|
|
309
316
|
WHERE
|
|
310
317
|
1 = 1
|
|
318
|
+
-- Filter on the position of the checkpoint ON THE CHAIN, resolved by
|
|
319
|
+
-- resolveActionStateBoundary() before this query runs.
|
|
320
|
+
--
|
|
321
|
+
-- This used to compare zkf0.id against the id of the requested action state.
|
|
322
|
+
-- zkapp_field is the interning table for every field value in the archive,
|
|
323
|
+
-- and its id records when a value was FIRST WRITTEN, not where it sits on
|
|
324
|
+
-- the chain. Any archive filled out of chain order — a bulk import, a
|
|
325
|
+
-- hard-fork migration, a bootstrap — breaks that assumption, and the filter
|
|
326
|
+
-- then silently dropped real actions or returned actions from before the
|
|
327
|
+
-- checkpoint. See tests/integration/action-state-ordering.test.ts.
|
|
328
|
+
--
|
|
329
|
+
-- Greater-or-equal keeps the checkpoint's own block in the answer. o1js
|
|
330
|
+
-- removes it in createActionsList by comparing actionStateOne with the
|
|
331
|
+
-- requested value, so excluding it here would change what clients receive.
|
|
311
332
|
${
|
|
312
|
-
|
|
313
|
-
? db_client`AND
|
|
333
|
+
fromActionStateHeight
|
|
334
|
+
? db_client`AND emitted_actions.height >= ${fromActionStateHeight}`
|
|
314
335
|
: db_client``
|
|
315
336
|
}
|
|
316
337
|
${
|
|
317
|
-
|
|
318
|
-
? db_client`AND
|
|
338
|
+
endActionStateHeight
|
|
339
|
+
? db_client`AND emitted_actions.height <= ${endActionStateHeight}`
|
|
319
340
|
: db_client``
|
|
320
341
|
}
|
|
321
342
|
)`;
|
|
@@ -378,17 +399,21 @@ export function getActionsQuery(
|
|
|
378
399
|
status: BlockStatusFilter,
|
|
379
400
|
to?: string,
|
|
380
401
|
from?: string,
|
|
381
|
-
|
|
382
|
-
|
|
402
|
+
fromActionStateHeight?: string,
|
|
403
|
+
endActionStateHeight?: string
|
|
383
404
|
) {
|
|
384
405
|
return db_client<ArchiveNodeDatabaseRow[]>`
|
|
385
|
-
WITH
|
|
406
|
+
WITH
|
|
386
407
|
${fullChainCTE(db_client, from, to)},
|
|
387
408
|
${accountIdentifierCTE(db_client, address, tokenId)},
|
|
388
409
|
${blocksAccessedCTE(db_client, status)},
|
|
389
410
|
${emittedZkAppCommandsCTE(db_client)},
|
|
390
411
|
${emittedActionsCTE(db_client)},
|
|
391
|
-
${emittedActionStateCTE(
|
|
412
|
+
${emittedActionStateCTE(
|
|
413
|
+
db_client,
|
|
414
|
+
fromActionStateHeight,
|
|
415
|
+
endActionStateHeight
|
|
416
|
+
)}
|
|
392
417
|
SELECT *
|
|
393
418
|
FROM emitted_action_state
|
|
394
419
|
`;
|
|
@@ -412,9 +437,115 @@ JOIN max_heights mh
|
|
|
412
437
|
`;
|
|
413
438
|
}
|
|
414
439
|
|
|
415
|
-
export
|
|
416
|
-
|
|
417
|
-
|
|
440
|
+
export type ActionStateBoundaryRow = {
|
|
441
|
+
boundary_height: string | null;
|
|
442
|
+
window_start: string | null;
|
|
443
|
+
/**
|
|
444
|
+
* true — the account still held the requested action state when the window
|
|
445
|
+
* began, so nothing was missed before it.
|
|
446
|
+
* false — the state had already moved on, so actions are missing.
|
|
447
|
+
* null — the account was never accessed below the window, which means the
|
|
448
|
+
* checkpoint itself is inside it.
|
|
449
|
+
*/
|
|
450
|
+
state_entering_window: boolean | null;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Resolves an action state to its position on the chain, for one account.
|
|
455
|
+
*
|
|
456
|
+
* This replaces the old `checkActionState`, which only asked whether the value
|
|
457
|
+
* existed anywhere in `zkapp_field`. That accepted any field value in the whole
|
|
458
|
+
* database — action data, app state, another account's action state — so the
|
|
459
|
+
* server answered with data that had nothing to do with the request instead of
|
|
460
|
+
* raising an error.
|
|
461
|
+
*
|
|
462
|
+
* The lookup is deliberately NOT limited to the queried block range. A checkpoint
|
|
463
|
+
* is a position, not data: resolving it does not need its block to be inside the
|
|
464
|
+
* window. Restricting it to the window would make an out-of-range checkpoint
|
|
465
|
+
* resolve to nothing, and `height >= NULL` selects no rows — the server would
|
|
466
|
+
* then answer with an empty list, which a reducer reads as "no actions to fold".
|
|
467
|
+
*
|
|
468
|
+
* Returns one row:
|
|
469
|
+
*
|
|
470
|
+
* boundary_height the lowest block height at which this account's action
|
|
471
|
+
* state was the requested value, over the canonical chain
|
|
472
|
+
* plus the current pending chain. NULL means the value is
|
|
473
|
+
* not an action state of this account at all.
|
|
474
|
+
* window_start the first block height this query covers, derived the
|
|
475
|
+
* same way as fullChainCTE.
|
|
476
|
+
* state_entering_window this account's action state at its last accessed block
|
|
477
|
+
* below window_start, or NULL if it was never accessed
|
|
478
|
+
* below it. If this still equals the requested value, no
|
|
479
|
+
* action was missed between the checkpoint and the start
|
|
480
|
+
* of the window, so the answer is complete even though the
|
|
481
|
+
* checkpoint itself lies outside it.
|
|
482
|
+
*/
|
|
483
|
+
export function resolveActionStateBoundary(
|
|
484
|
+
db_client: postgres.Sql,
|
|
485
|
+
address: string,
|
|
486
|
+
tokenId: string,
|
|
487
|
+
actionState: string,
|
|
488
|
+
from?: string,
|
|
489
|
+
to?: string
|
|
490
|
+
) {
|
|
491
|
+
const fromAsNum = from ? Number(from) : null;
|
|
492
|
+
const toAsNum = to ? Number(to) : null;
|
|
493
|
+
|
|
494
|
+
return db_client<ActionStateBoundaryRow[]>`
|
|
495
|
+
WITH RECURSIVE pending_chain AS (
|
|
496
|
+
(
|
|
497
|
+
SELECT id, parent_id, chain_status
|
|
498
|
+
FROM blocks
|
|
499
|
+
WHERE height = ${bestChainTipHeight(db_client)}
|
|
500
|
+
)
|
|
501
|
+
UNION ALL
|
|
502
|
+
SELECT b.id, b.parent_id, b.chain_status
|
|
503
|
+
FROM blocks b
|
|
504
|
+
-- Same invariant as fullChainCTE: genesis has parent_id = NULL, and no
|
|
505
|
+
-- block is its own parent, so an explicit id <> parent_id guard never
|
|
506
|
+
-- excludes a row.
|
|
507
|
+
INNER JOIN pending_chain ON b.id = pending_chain.parent_id
|
|
508
|
+
AND pending_chain.chain_status <> 'canonical'
|
|
509
|
+
),
|
|
510
|
+
bounds AS (
|
|
511
|
+
SELECT ${
|
|
512
|
+
fromAsNum !== null
|
|
513
|
+
? db_client`${fromAsNum}::bigint`
|
|
514
|
+
: toAsNum !== null
|
|
515
|
+
? db_client`${toAsNum - BLOCK_RANGE_SIZE}::bigint`
|
|
516
|
+
: db_client`${bestChainTipHeight(
|
|
517
|
+
db_client
|
|
518
|
+
)} - ${BLOCK_RANGE_SIZE}::bigint`
|
|
519
|
+
} AS window_start
|
|
520
|
+
),
|
|
521
|
+
target AS (
|
|
522
|
+
SELECT id FROM zkapp_field WHERE field = ${actionState}
|
|
523
|
+
),
|
|
524
|
+
account_action_states AS (
|
|
525
|
+
SELECT b.height, zks.element0 AS state_field_id
|
|
526
|
+
FROM accounts_accessed aa
|
|
527
|
+
INNER JOIN blocks b ON b.id = aa.block_id
|
|
528
|
+
INNER JOIN zkapp_accounts zkacc ON zkacc.id = aa.zkapp_id
|
|
529
|
+
INNER JOIN zkapp_action_states zks ON zks.id = zkacc.action_state_id
|
|
530
|
+
WHERE aa.account_identifier_id = (
|
|
531
|
+
SELECT ai.id FROM account_identifiers ai
|
|
532
|
+
WHERE ai.public_key_id = (SELECT id FROM public_keys WHERE value = ${address})
|
|
533
|
+
AND ai.token_id = (SELECT id FROM tokens WHERE value = ${tokenId})
|
|
534
|
+
)
|
|
535
|
+
AND (b.chain_status = 'canonical' OR b.id IN (SELECT id FROM pending_chain))
|
|
536
|
+
)
|
|
537
|
+
SELECT
|
|
538
|
+
(
|
|
539
|
+
SELECT min(height) FROM account_action_states
|
|
540
|
+
WHERE state_field_id = (SELECT id FROM target)
|
|
541
|
+
) AS boundary_height,
|
|
542
|
+
(SELECT window_start FROM bounds) AS window_start,
|
|
543
|
+
(
|
|
544
|
+
SELECT state_field_id FROM account_action_states
|
|
545
|
+
WHERE height < (SELECT window_start FROM bounds)
|
|
546
|
+
ORDER BY height DESC
|
|
547
|
+
LIMIT 1
|
|
548
|
+
) = (SELECT id FROM target) AS state_entering_window
|
|
418
549
|
`;
|
|
419
550
|
}
|
|
420
551
|
|
|
@@ -427,16 +558,27 @@ export function getTables(db_client: postgres.Sql) {
|
|
|
427
558
|
export const USED_TABLES = [
|
|
428
559
|
'blocks',
|
|
429
560
|
'account_identifiers',
|
|
561
|
+
'public_keys',
|
|
562
|
+
'tokens',
|
|
430
563
|
'accounts_accessed',
|
|
431
564
|
'blocks_zkapp_commands',
|
|
565
|
+
'public_keys',
|
|
566
|
+
'tokens',
|
|
432
567
|
'zkapp_commands',
|
|
568
|
+
'zkapp_fee_payer_body',
|
|
433
569
|
'zkapp_account_update',
|
|
434
570
|
'zkapp_account_update_body',
|
|
571
|
+
'zkapp_updates',
|
|
435
572
|
'zkapp_events',
|
|
436
573
|
'zkapp_field_array',
|
|
437
574
|
'zkapp_field',
|
|
575
|
+
'zkapp_account_precondition',
|
|
576
|
+
'zkapp_network_precondition',
|
|
577
|
+
'zkapp_global_slot_bounds',
|
|
578
|
+
'zkapp_states_nullable',
|
|
438
579
|
'zkapp_verification_key_hashes',
|
|
439
580
|
'zkapp_verification_keys',
|
|
581
|
+
'zkapp_updates',
|
|
440
582
|
'zkapp_accounts',
|
|
441
583
|
'zkapp_action_states',
|
|
442
584
|
] as const;
|
|
@@ -447,17 +589,19 @@ export function getZkappsWithPendingEventsQuery(db_client: postgres.Sql) {
|
|
|
447
589
|
-- start at the tip
|
|
448
590
|
SELECT id, parent_id, chain_status
|
|
449
591
|
FROM blocks
|
|
450
|
-
WHERE height = (
|
|
592
|
+
WHERE height = ${bestChainTipHeight(db_client)}
|
|
451
593
|
|
|
452
594
|
UNION ALL
|
|
453
595
|
|
|
454
596
|
-- walk back until we re-join the canonical branch
|
|
597
|
+
-- The id <> parent_id guard was dropped here for the same reason as in
|
|
598
|
+
-- fullChainCTE: no block is its own parent, so it never excludes a row, and
|
|
599
|
+
-- the walk terminates on genesis's NULL parent_id regardless.
|
|
455
600
|
SELECT b.id, b.parent_id, b.chain_status
|
|
456
601
|
FROM blocks b
|
|
457
602
|
JOIN pending_chain pc
|
|
458
603
|
ON b.id = pc.parent_id
|
|
459
|
-
WHERE pc.
|
|
460
|
-
AND pc.chain_status <> 'canonical'
|
|
604
|
+
WHERE pc.chain_status <> 'canonical'
|
|
461
605
|
)
|
|
462
606
|
SELECT DISTINCT
|
|
463
607
|
pk.value AS public_key
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type postgres from 'postgres';
|
|
2
|
+
import { BlockStatusFilter } from '../../../blockchain/types.js';
|
|
3
|
+
|
|
4
|
+
export type VerificationKeyUpdateDatabaseRow = {
|
|
5
|
+
account_update_id: number;
|
|
6
|
+
address: string;
|
|
7
|
+
token_id: string;
|
|
8
|
+
verification_key_hash: string;
|
|
9
|
+
state_hash: string;
|
|
10
|
+
parent_hash: string;
|
|
11
|
+
height: string;
|
|
12
|
+
global_slot_since_genesis: string;
|
|
13
|
+
global_slot_since_hard_fork: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
chain_status: string;
|
|
16
|
+
ledger_hash: string;
|
|
17
|
+
distance_from_max_block_height: string;
|
|
18
|
+
last_vrf_output: string;
|
|
19
|
+
status: string;
|
|
20
|
+
hash: string;
|
|
21
|
+
memo: string;
|
|
22
|
+
authorization_kind: string;
|
|
23
|
+
sequence_number: number;
|
|
24
|
+
zkapp_account_updates_ids: number[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function getVerificationKeyUpdatesQuery(
|
|
28
|
+
client: postgres.Sql,
|
|
29
|
+
verificationKeyHash: string,
|
|
30
|
+
from: number,
|
|
31
|
+
to: number,
|
|
32
|
+
status: BlockStatusFilter
|
|
33
|
+
) {
|
|
34
|
+
const statusClause =
|
|
35
|
+
status === BlockStatusFilter.all
|
|
36
|
+
? client`AND b.chain_status <> 'orphaned'`
|
|
37
|
+
: client`AND b.chain_status = ${status.toLowerCase()}`;
|
|
38
|
+
|
|
39
|
+
return client<VerificationKeyUpdateDatabaseRow[]>`
|
|
40
|
+
WITH RECURSIVE pending_chain AS (
|
|
41
|
+
SELECT id, parent_id
|
|
42
|
+
FROM blocks
|
|
43
|
+
WHERE chain_status = 'pending'
|
|
44
|
+
AND height = (
|
|
45
|
+
SELECT MAX(height) FROM blocks WHERE chain_status = 'pending'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
UNION ALL
|
|
49
|
+
|
|
50
|
+
SELECT parent.id, parent.parent_id
|
|
51
|
+
FROM blocks parent
|
|
52
|
+
INNER JOIN pending_chain child ON parent.id = child.parent_id
|
|
53
|
+
WHERE parent.chain_status <> 'canonical'
|
|
54
|
+
AND child.id <> child.parent_id
|
|
55
|
+
),
|
|
56
|
+
-- Resolve the requested hash to the set of zkapp_updates rows that SET
|
|
57
|
+
-- that verification key, before touching any block. The set is small — one
|
|
58
|
+
-- row per distinct update that ever wrote this key — and MATERIALIZED
|
|
59
|
+
-- keeps the planner from inlining it back into the join tree, where it
|
|
60
|
+
-- would hash the whole of zkapp_updates and zkapp_account_update_body once
|
|
61
|
+
-- per query. Measured on a 681k-block devnet archive over the maximum
|
|
62
|
+
-- 10 000-block range: 405 ms inlined, 199 ms materialised.
|
|
63
|
+
target_updates AS MATERIALIZED (
|
|
64
|
+
SELECT zu.id
|
|
65
|
+
FROM zkapp_verification_key_hashes vkh
|
|
66
|
+
INNER JOIN zkapp_verification_keys vk ON vk.hash_id = vkh.id
|
|
67
|
+
INNER JOIN zkapp_updates zu ON zu.verification_key_id = vk.id
|
|
68
|
+
WHERE vkh.value = ${verificationKeyHash}
|
|
69
|
+
),
|
|
70
|
+
full_chain AS (
|
|
71
|
+
SELECT b.*
|
|
72
|
+
FROM blocks b
|
|
73
|
+
WHERE b.height >= ${from}
|
|
74
|
+
AND b.height < ${to}
|
|
75
|
+
AND (
|
|
76
|
+
b.chain_status = 'canonical'
|
|
77
|
+
OR b.id IN (SELECT id FROM pending_chain)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
SELECT
|
|
81
|
+
zau.id AS account_update_id,
|
|
82
|
+
pk.value AS address,
|
|
83
|
+
t.value AS token_id,
|
|
84
|
+
${verificationKeyHash}::text AS verification_key_hash,
|
|
85
|
+
b.state_hash,
|
|
86
|
+
b.parent_hash,
|
|
87
|
+
b.height,
|
|
88
|
+
b.global_slot_since_genesis,
|
|
89
|
+
b.global_slot_since_hard_fork,
|
|
90
|
+
b.timestamp,
|
|
91
|
+
b.chain_status,
|
|
92
|
+
b.ledger_hash,
|
|
93
|
+
(SELECT MAX(height) FROM blocks) - b.height
|
|
94
|
+
AS distance_from_max_block_height,
|
|
95
|
+
b.last_vrf_output,
|
|
96
|
+
bzc.status,
|
|
97
|
+
zc.hash,
|
|
98
|
+
zc.memo,
|
|
99
|
+
zaub.authorization_kind,
|
|
100
|
+
bzc.sequence_no AS sequence_number,
|
|
101
|
+
zc.zkapp_account_updates_ids
|
|
102
|
+
FROM full_chain b
|
|
103
|
+
INNER JOIN blocks_zkapp_commands bzc ON bzc.block_id = b.id
|
|
104
|
+
INNER JOIN zkapp_commands zc ON zc.id = bzc.zkapp_command_id
|
|
105
|
+
INNER JOIN LATERAL UNNEST(zc.zkapp_account_updates_ids)
|
|
106
|
+
WITH ORDINALITY AS update_ref(id, position) ON TRUE
|
|
107
|
+
INNER JOIN zkapp_account_update zau ON zau.id = update_ref.id
|
|
108
|
+
-- An account update SETS a verification key through update_id. The other
|
|
109
|
+
-- column that names a verification key on this table,
|
|
110
|
+
-- verification_key_hash_id, is the PRECONDITION: the key the update
|
|
111
|
+
-- requires the account to already have. Filtering on that one would answer
|
|
112
|
+
-- "who called this contract" instead of "who deployed it".
|
|
113
|
+
INNER JOIN zkapp_account_update_body zaub ON zaub.id = zau.body_id
|
|
114
|
+
AND zaub.update_id IN (SELECT id FROM target_updates)
|
|
115
|
+
INNER JOIN account_identifiers ai ON ai.id = zaub.account_identifier_id
|
|
116
|
+
INNER JOIN public_keys pk ON pk.id = ai.public_key_id
|
|
117
|
+
INNER JOIN tokens t ON t.id = ai.token_id
|
|
118
|
+
WHERE bzc.status = 'applied'
|
|
119
|
+
${statusClause}
|
|
120
|
+
-- state_hash is what makes this order total. Two competing tips sit at the
|
|
121
|
+
-- same height, and pending_chain seeds from every block at the maximum
|
|
122
|
+
-- pending height, so both are in the answer. A command carried by both —
|
|
123
|
+
-- the normal case during a reorg; one command was measured in 8 blocks at
|
|
124
|
+
-- a single height on devnet — then produces rows that agree on height,
|
|
125
|
+
-- sequence_no, account-update position and zkapp_account_update.id alike.
|
|
126
|
+
ORDER BY
|
|
127
|
+
b.height ASC,
|
|
128
|
+
b.state_hash ASC,
|
|
129
|
+
bzc.sequence_no ASC,
|
|
130
|
+
update_ref.position ASC
|
|
131
|
+
`;
|
|
132
|
+
}
|