@o1-labs/mina-archive-node-graphql 0.0.6
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/README.md +321 -0
- package/build/benchmark/setup.js +32 -0
- package/build/benchmark/setup.js.map +1 -0
- package/build/src/blockchain/constants.js +2 -0
- package/build/src/blockchain/constants.js.map +1 -0
- package/build/src/blockchain/types.js +7 -0
- package/build/src/blockchain/types.js.map +1 -0
- package/build/src/blockchain/utils.js +39 -0
- package/build/src/blockchain/utils.js.map +1 -0
- package/build/src/consensus/mina-consensus.js +59 -0
- package/build/src/consensus/mina-consensus.js.map +1 -0
- package/build/src/context.js +10 -0
- package/build/src/context.js.map +1 -0
- package/build/src/db/archive-node-adapter/archive-node-adapter.interface.js +2 -0
- package/build/src/db/archive-node-adapter/archive-node-adapter.interface.js.map +1 -0
- package/build/src/db/archive-node-adapter/archive-node-adapter.js +47 -0
- package/build/src/db/archive-node-adapter/archive-node-adapter.js.map +1 -0
- package/build/src/db/sql/events-actions/queries.js +438 -0
- package/build/src/db/sql/events-actions/queries.js.map +1 -0
- package/build/src/db/sql/events-actions/types.js +2 -0
- package/build/src/db/sql/events-actions/types.js.map +1 -0
- package/build/src/errors/error.js +17 -0
- package/build/src/errors/error.js.map +1 -0
- package/build/src/index.js +27 -0
- package/build/src/index.js.map +1 -0
- package/build/src/resolvers-types.js +8 -0
- package/build/src/resolvers-types.js.map +1 -0
- package/build/src/resolvers.js +67 -0
- package/build/src/resolvers.js.map +1 -0
- package/build/src/server/plugins.js +39 -0
- package/build/src/server/plugins.js.map +1 -0
- package/build/src/server/server.js +25 -0
- package/build/src/server/server.js.map +1 -0
- package/build/src/services/actions-service/actions-service.interface.js +2 -0
- package/build/src/services/actions-service/actions-service.interface.js.map +1 -0
- package/build/src/services/actions-service/actions-service.js +94 -0
- package/build/src/services/actions-service/actions-service.js.map +1 -0
- package/build/src/services/blocks-service/blocks-service.interface.js +2 -0
- package/build/src/services/blocks-service/blocks-service.interface.js.map +1 -0
- package/build/src/services/blocks-service/blocks-service.js +263 -0
- package/build/src/services/blocks-service/blocks-service.js.map +1 -0
- package/build/src/services/data-adapters/database-row-adapters.js +116 -0
- package/build/src/services/data-adapters/database-row-adapters.js.map +1 -0
- package/build/src/services/events-service/events-service.interface.js +2 -0
- package/build/src/services/events-service/events-service.interface.js.map +1 -0
- package/build/src/services/events-service/events-service.js +64 -0
- package/build/src/services/events-service/events-service.js.map +1 -0
- package/build/src/services/network-service/network-service.interface.js +2 -0
- package/build/src/services/network-service/network-service.interface.js.map +1 -0
- package/build/src/services/network-service/network-service.js +31 -0
- package/build/src/services/network-service/network-service.js.map +1 -0
- package/build/src/tracing/jaeger-setup.js +39 -0
- package/build/src/tracing/jaeger-setup.js.map +1 -0
- package/build/src/tracing/jaeger-tracing.js +29 -0
- package/build/src/tracing/jaeger-tracing.js.map +1 -0
- package/build/src/tracing/tracer.js +31 -0
- package/build/src/tracing/tracer.js.map +1 -0
- package/build/tests/__mocks__/generate_mock_data.js +13 -0
- package/build/tests/__mocks__/generate_mock_data.js.map +1 -0
- package/build/tests/consensus/run-compare.js +40 -0
- package/build/tests/consensus/run-compare.js.map +1 -0
- package/build/tests/consensus/select-consensus-precomputed.js +80 -0
- package/build/tests/consensus/select-consensus-precomputed.js.map +1 -0
- package/build/tests/consensus/types.js +15 -0
- package/build/tests/consensus/types.js.map +1 -0
- package/build/tests/devnet-dump/devnet-dump.test.js +311 -0
- package/build/tests/devnet-dump/devnet-dump.test.js.map +1 -0
- package/build/tests/devnet-dump/setup.js +58 -0
- package/build/tests/devnet-dump/setup.js.map +1 -0
- package/build/tests/integration/integration.test.js +226 -0
- package/build/tests/integration/integration.test.js.map +1 -0
- package/build/tests/integration/setup.js +82 -0
- package/build/tests/integration/setup.js.map +1 -0
- package/build/tests/live-api/actions.test.js +4 -0
- package/build/tests/live-api/actions.test.js.map +1 -0
- package/build/tests/live-api/blocks.test.js +116 -0
- package/build/tests/live-api/blocks.test.js.map +1 -0
- package/build/tests/live-api/events.test.js +97 -0
- package/build/tests/live-api/events.test.js.map +1 -0
- package/build/tests/live-api/networkState.test.js +27 -0
- package/build/tests/live-api/networkState.test.js.map +1 -0
- package/build/tests/live-network/live-network.test.js +144 -0
- package/build/tests/live-network/live-network.test.js.map +1 -0
- package/build/tests/live-network/setup.js +206 -0
- package/build/tests/live-network/setup.js.map +1 -0
- package/build/tests/resolvers.test.js +475 -0
- package/build/tests/resolvers.test.js.map +1 -0
- package/build/tests/services/actions-service/actions-service.test.js +124 -0
- package/build/tests/services/actions-service/actions-service.test.js.map +1 -0
- package/build/tests/services/blocks-service/blocks-service.test.js +348 -0
- package/build/tests/services/blocks-service/blocks-service.test.js.map +1 -0
- package/build/tests/services/events-service/events-service.test.js +185 -0
- package/build/tests/services/events-service/events-service.test.js.map +1 -0
- package/build/tests/test-helpers.js +11 -0
- package/build/tests/test-helpers.js.map +1 -0
- package/build/tests/unit/blockchain-utils.test.js +106 -0
- package/build/tests/unit/blockchain-utils.test.js.map +1 -0
- package/build/tests/unit/consensus.test.js +154 -0
- package/build/tests/unit/consensus.test.js.map +1 -0
- package/build/tests/unit/error-handling.test.js +52 -0
- package/build/tests/unit/error-handling.test.js.map +1 -0
- package/build/tests/utils.test.js +313 -0
- package/build/tests/utils.test.js.map +1 -0
- package/build/zkapp/contract.js +144 -0
- package/build/zkapp/contract.js.map +1 -0
- package/build/zkapp/utils.js +174 -0
- package/build/zkapp/utils.js.map +1 -0
- package/package.json +85 -0
- package/schema.graphql +221 -0
- package/src/blockchain/constants.ts +2 -0
- package/src/blockchain/types.ts +121 -0
- package/src/blockchain/utils.ts +54 -0
- package/src/consensus/mina-consensus.ts +137 -0
- package/src/context.ts +18 -0
- package/src/db/archive-node-adapter/archive-node-adapter.interface.ts +23 -0
- package/src/db/archive-node-adapter/archive-node-adapter.ts +101 -0
- package/src/db/sql/events-actions/queries.ts +490 -0
- package/src/db/sql/events-actions/types.ts +208 -0
- package/src/envionment.d.ts +18 -0
- package/src/errors/error.ts +20 -0
- package/src/index.ts +31 -0
- package/src/resolvers-types.ts +754 -0
- package/src/resolvers.ts +102 -0
- package/src/server/plugins.ts +56 -0
- package/src/server/server.ts +30 -0
- package/src/services/actions-service/actions-service.interface.ts +9 -0
- package/src/services/actions-service/actions-service.ts +195 -0
- package/src/services/blocks-service/blocks-service.interface.ts +11 -0
- package/src/services/blocks-service/blocks-service.ts +407 -0
- package/src/services/data-adapters/database-row-adapters.ts +261 -0
- package/src/services/events-service/events-service.interface.ts +6 -0
- package/src/services/events-service/events-service.ts +118 -0
- package/src/services/network-service/network-service.interface.ts +5 -0
- package/src/services/network-service/network-service.ts +53 -0
- package/src/tracing/jaeger-setup.ts +65 -0
- package/src/tracing/jaeger-tracing.ts +44 -0
- package/src/tracing/tracer.ts +52 -0
- package/tsconfig.json +104 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import type postgres from 'postgres';
|
|
2
|
+
import {
|
|
3
|
+
Blocks,
|
|
4
|
+
UserCommand,
|
|
5
|
+
ZkAppCommand,
|
|
6
|
+
FeeTransfer,
|
|
7
|
+
} from '../../blockchain/types.js';
|
|
8
|
+
import type {
|
|
9
|
+
BlockQueryInput,
|
|
10
|
+
BlockSortByInput,
|
|
11
|
+
} from '../../resolvers-types.js';
|
|
12
|
+
import { IBlocksService } from './blocks-service.interface.js';
|
|
13
|
+
import {
|
|
14
|
+
TracingState,
|
|
15
|
+
extractTraceStateFromOptions,
|
|
16
|
+
} from '../../tracing/tracer.js';
|
|
17
|
+
import {
|
|
18
|
+
BLOCK_RANGE_SIZE,
|
|
19
|
+
ENABLE_BLOCK_TRANSACTION_DETAILS,
|
|
20
|
+
} from '../../server/server.js';
|
|
21
|
+
|
|
22
|
+
export { BlocksService };
|
|
23
|
+
export type { BlockRow, UserCommandRow, ZkAppCommandRow, FeeTransferRow };
|
|
24
|
+
|
|
25
|
+
// Interface representing the expected row structure from the database
|
|
26
|
+
interface BlockRow {
|
|
27
|
+
id: number;
|
|
28
|
+
height: number;
|
|
29
|
+
creator: string;
|
|
30
|
+
state_hash: string;
|
|
31
|
+
parent_hash: string;
|
|
32
|
+
timestamp: string;
|
|
33
|
+
internal_command_type?: string;
|
|
34
|
+
coinbase_amount?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface UserCommandRow {
|
|
38
|
+
block_id: number;
|
|
39
|
+
hash: string;
|
|
40
|
+
kind: string;
|
|
41
|
+
from: string;
|
|
42
|
+
to: string;
|
|
43
|
+
amount: string;
|
|
44
|
+
fee: string;
|
|
45
|
+
memo: string;
|
|
46
|
+
nonce: number;
|
|
47
|
+
status: string;
|
|
48
|
+
failure_reason: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface ZkAppCommandRow {
|
|
52
|
+
block_id: number;
|
|
53
|
+
hash: string;
|
|
54
|
+
fee_payer: string;
|
|
55
|
+
fee: string;
|
|
56
|
+
memo: string;
|
|
57
|
+
status: string;
|
|
58
|
+
failure_reasons_ids: number[] | string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface FeeTransferRow {
|
|
62
|
+
block_id: number;
|
|
63
|
+
recipient: string;
|
|
64
|
+
fee: string;
|
|
65
|
+
type: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
class BlocksService implements IBlocksService {
|
|
69
|
+
private readonly client: postgres.Sql;
|
|
70
|
+
|
|
71
|
+
constructor(client: postgres.Sql) {
|
|
72
|
+
this.client = client;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getBlocks(
|
|
76
|
+
query: BlockQueryInput | null | undefined,
|
|
77
|
+
limit: number | null | undefined,
|
|
78
|
+
sortBy: BlockSortByInput | null | undefined,
|
|
79
|
+
options: unknown
|
|
80
|
+
): Promise<Blocks> {
|
|
81
|
+
const tracingState = extractTraceStateFromOptions(options);
|
|
82
|
+
return (await this.getBlockData(query, limit, sortBy, { tracingState })) ?? [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getBlockData(
|
|
86
|
+
query: BlockQueryInput | null | undefined,
|
|
87
|
+
limit: number | null | undefined,
|
|
88
|
+
sortBy: BlockSortByInput | null | undefined,
|
|
89
|
+
{ tracingState }: { tracingState: TracingState }
|
|
90
|
+
): Promise<Blocks> {
|
|
91
|
+
const sqlSpan = tracingState.startSpan('blocks.SQL');
|
|
92
|
+
const rows = await this.executeBlocksQuery(query, limit, sortBy);
|
|
93
|
+
sqlSpan.end();
|
|
94
|
+
|
|
95
|
+
const blockRows = rows as unknown as BlockRow[];
|
|
96
|
+
|
|
97
|
+
let userCommandsByBlock = new Map<number, UserCommand[]>();
|
|
98
|
+
let zkappCommandsByBlock = new Map<number, ZkAppCommand[]>();
|
|
99
|
+
let feeTransfersByBlock = new Map<number, FeeTransfer[]>();
|
|
100
|
+
|
|
101
|
+
if (ENABLE_BLOCK_TRANSACTION_DETAILS && blockRows.length > 0) {
|
|
102
|
+
const txSpan = tracingState.startSpan('blocks.transactionDetails.SQL');
|
|
103
|
+
const blockIds = blockRows.map((r) => r.id);
|
|
104
|
+
[userCommandsByBlock, zkappCommandsByBlock, feeTransfersByBlock] =
|
|
105
|
+
await this.fetchTransactionDetails(blockIds);
|
|
106
|
+
txSpan.end();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const processingSpan = tracingState.startSpan('blocks.processing');
|
|
110
|
+
const blocks = this.rowsToBlocks(
|
|
111
|
+
blockRows,
|
|
112
|
+
userCommandsByBlock,
|
|
113
|
+
zkappCommandsByBlock,
|
|
114
|
+
feeTransfersByBlock
|
|
115
|
+
);
|
|
116
|
+
processingSpan.end();
|
|
117
|
+
return blocks;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private async executeBlocksQuery(
|
|
121
|
+
query: BlockQueryInput | null | undefined,
|
|
122
|
+
limit: number | null | undefined,
|
|
123
|
+
sortBy: BlockSortByInput | null | undefined
|
|
124
|
+
) {
|
|
125
|
+
const blockHeightGte = query?.blockHeight_gte;
|
|
126
|
+
const blockHeightLt = query?.blockHeight_lt;
|
|
127
|
+
const dateTimeGte = query?.dateTime_gte;
|
|
128
|
+
const dateTimeLt = query?.dateTime_lt;
|
|
129
|
+
const canonical = query?.canonical;
|
|
130
|
+
const inBestChain = query?.inBestChain;
|
|
131
|
+
const orderBy = sortBy === 'BLOCKHEIGHT_DESC' ? 'DESC' : 'ASC';
|
|
132
|
+
const limitValue = Math.min(limit ?? 200, BLOCK_RANGE_SIZE);
|
|
133
|
+
|
|
134
|
+
// Build the SQL query for blocks with transactions
|
|
135
|
+
// Archive Node uses a junction table blocks_internal_commands
|
|
136
|
+
let sql = `
|
|
137
|
+
SELECT
|
|
138
|
+
b.id,
|
|
139
|
+
b.state_hash,
|
|
140
|
+
b.parent_hash,
|
|
141
|
+
b.height,
|
|
142
|
+
b.timestamp,
|
|
143
|
+
pk.value as creator,
|
|
144
|
+
COALESCE(ic.fee, '0') as coinbase_amount
|
|
145
|
+
FROM blocks b
|
|
146
|
+
INNER JOIN public_keys pk ON b.creator_id = pk.id
|
|
147
|
+
LEFT JOIN (
|
|
148
|
+
SELECT
|
|
149
|
+
bic.block_id,
|
|
150
|
+
ic.fee
|
|
151
|
+
FROM internal_commands ic
|
|
152
|
+
INNER JOIN blocks_internal_commands bic ON ic.id = bic.internal_command_id
|
|
153
|
+
WHERE ic.command_type = 'coinbase'
|
|
154
|
+
) AS ic ON b.id = ic.block_id
|
|
155
|
+
WHERE TRUE
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
const params: (string | number)[] = [];
|
|
159
|
+
let paramIndex = 1;
|
|
160
|
+
|
|
161
|
+
if (canonical === true) {
|
|
162
|
+
sql += ` AND b.chain_status = 'canonical'`;
|
|
163
|
+
} else if (canonical === false) {
|
|
164
|
+
sql += ` AND b.chain_status <> 'canonical'`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (blockHeightGte) {
|
|
168
|
+
sql += ` AND b.height >= $${paramIndex}`;
|
|
169
|
+
params.push(blockHeightGte);
|
|
170
|
+
paramIndex++;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (blockHeightLt) {
|
|
174
|
+
sql += ` AND b.height < $${paramIndex}`;
|
|
175
|
+
params.push(blockHeightLt);
|
|
176
|
+
paramIndex++;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (dateTimeGte) {
|
|
180
|
+
// Convert ISO timestamp to Unix milliseconds
|
|
181
|
+
const timestampMs = new Date(dateTimeGte).getTime().toString();
|
|
182
|
+
sql += ` AND b.timestamp >= $${paramIndex}`;
|
|
183
|
+
params.push(timestampMs);
|
|
184
|
+
paramIndex++;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (dateTimeLt) {
|
|
188
|
+
// Convert ISO timestamp to Unix milliseconds
|
|
189
|
+
const timestampMs = new Date(dateTimeLt).getTime().toString();
|
|
190
|
+
sql += ` AND b.timestamp < $${paramIndex}`;
|
|
191
|
+
params.push(timestampMs);
|
|
192
|
+
paramIndex++;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const best_chain_til_canonical_cte =
|
|
196
|
+
`
|
|
197
|
+
WITH RECURSIVE best_chain_til_canonical AS (
|
|
198
|
+
SELECT
|
|
199
|
+
id,
|
|
200
|
+
parent_id,
|
|
201
|
+
height
|
|
202
|
+
FROM blocks
|
|
203
|
+
WHERE height = (SELECT MAX(height) FROM blocks)
|
|
204
|
+
AND chain_status = 'pending'
|
|
205
|
+
|
|
206
|
+
UNION
|
|
207
|
+
|
|
208
|
+
SELECT
|
|
209
|
+
potential_parent.id,
|
|
210
|
+
potential_parent.parent_id,
|
|
211
|
+
potential_parent.height
|
|
212
|
+
FROM blocks potential_parent
|
|
213
|
+
JOIN best_chain_til_canonical ON potential_parent.id = best_chain_til_canonical.parent_id
|
|
214
|
+
WHERE potential_parent.chain_status <> 'canonical'
|
|
215
|
+
)
|
|
216
|
+
`
|
|
217
|
+
if (inBestChain === true) {
|
|
218
|
+
sql =
|
|
219
|
+
best_chain_til_canonical_cte
|
|
220
|
+
+ sql
|
|
221
|
+
+ ` AND (b.id IN (SELECT id FROM best_chain_til_canonical) OR b.chain_status = 'canonical')`;
|
|
222
|
+
} else if (inBestChain === false) {
|
|
223
|
+
sql =
|
|
224
|
+
best_chain_til_canonical_cte
|
|
225
|
+
+ sql
|
|
226
|
+
+ ` AND (b.id NOT IN (SELECT id FROM best_chain_til_canonical)
|
|
227
|
+
OR b.chain_status = 'orphaned')`;
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
sql += ` ORDER BY b.height ${orderBy}`;
|
|
232
|
+
sql += ` LIMIT $${paramIndex}`;
|
|
233
|
+
params.push(limitValue);
|
|
234
|
+
|
|
235
|
+
return this.client.unsafe(sql, params);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private async fetchTransactionDetails(
|
|
239
|
+
blockIds: number[]
|
|
240
|
+
): Promise<
|
|
241
|
+
[
|
|
242
|
+
Map<number, UserCommand[]>,
|
|
243
|
+
Map<number, ZkAppCommand[]>,
|
|
244
|
+
Map<number, FeeTransfer[]>,
|
|
245
|
+
]
|
|
246
|
+
> {
|
|
247
|
+
const [ucRows, zkRows, ftRows] = await Promise.all([
|
|
248
|
+
this.fetchUserCommands(blockIds),
|
|
249
|
+
this.fetchZkAppCommands(blockIds),
|
|
250
|
+
this.fetchFeeTransfers(blockIds),
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
return [
|
|
254
|
+
this.groupBy(
|
|
255
|
+
ucRows as unknown as UserCommandRow[],
|
|
256
|
+
(r) => r.block_id,
|
|
257
|
+
this.mapUserCommandRow
|
|
258
|
+
),
|
|
259
|
+
this.groupBy(
|
|
260
|
+
zkRows as unknown as ZkAppCommandRow[],
|
|
261
|
+
(r) => r.block_id,
|
|
262
|
+
this.mapZkAppCommandRow
|
|
263
|
+
),
|
|
264
|
+
this.groupBy(
|
|
265
|
+
ftRows as unknown as FeeTransferRow[],
|
|
266
|
+
(r) => r.block_id,
|
|
267
|
+
this.mapFeeTransferRow
|
|
268
|
+
),
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private async fetchUserCommands(blockIds: number[]) {
|
|
273
|
+
const sql = `
|
|
274
|
+
SELECT
|
|
275
|
+
buc.block_id,
|
|
276
|
+
uc.hash,
|
|
277
|
+
uc.command_type AS kind,
|
|
278
|
+
pk_from.value AS "from",
|
|
279
|
+
pk_to.value AS "to",
|
|
280
|
+
COALESCE(uc.amount, '0') AS amount,
|
|
281
|
+
uc.fee,
|
|
282
|
+
uc.memo,
|
|
283
|
+
uc.nonce,
|
|
284
|
+
buc.status,
|
|
285
|
+
buc.failure_reason
|
|
286
|
+
FROM blocks_user_commands buc
|
|
287
|
+
INNER JOIN user_commands uc ON buc.user_command_id = uc.id
|
|
288
|
+
INNER JOIN public_keys pk_from ON uc.source_id = pk_from.id
|
|
289
|
+
INNER JOIN public_keys pk_to ON uc.receiver_id = pk_to.id
|
|
290
|
+
WHERE buc.block_id = ANY($1)
|
|
291
|
+
ORDER BY buc.block_id, buc.sequence_no
|
|
292
|
+
`;
|
|
293
|
+
return this.client.unsafe(sql, [blockIds]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async fetchZkAppCommands(blockIds: number[]) {
|
|
297
|
+
const sql = `
|
|
298
|
+
SELECT
|
|
299
|
+
bzkc.block_id,
|
|
300
|
+
zkc.hash,
|
|
301
|
+
pk_fee.value AS fee_payer,
|
|
302
|
+
fpb.fee,
|
|
303
|
+
zkc.memo,
|
|
304
|
+
bzkc.status,
|
|
305
|
+
bzkc.failure_reasons_ids
|
|
306
|
+
FROM blocks_zkapp_commands bzkc
|
|
307
|
+
INNER JOIN zkapp_commands zkc ON bzkc.zkapp_command_id = zkc.id
|
|
308
|
+
INNER JOIN zkapp_fee_payer_body fpb ON zkc.zkapp_fee_payer_body_id = fpb.id
|
|
309
|
+
INNER JOIN public_keys pk_fee ON fpb.public_key_id = pk_fee.id
|
|
310
|
+
WHERE bzkc.block_id = ANY($1)
|
|
311
|
+
ORDER BY bzkc.block_id, bzkc.sequence_no
|
|
312
|
+
`;
|
|
313
|
+
return this.client.unsafe(sql, [blockIds]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private async fetchFeeTransfers(blockIds: number[]) {
|
|
317
|
+
const sql = `
|
|
318
|
+
SELECT
|
|
319
|
+
bic.block_id,
|
|
320
|
+
pk.value AS recipient,
|
|
321
|
+
ic.fee,
|
|
322
|
+
ic.command_type AS type
|
|
323
|
+
FROM blocks_internal_commands bic
|
|
324
|
+
INNER JOIN internal_commands ic ON bic.internal_command_id = ic.id
|
|
325
|
+
INNER JOIN public_keys pk ON ic.receiver_id = pk.id
|
|
326
|
+
WHERE bic.block_id = ANY($1)
|
|
327
|
+
AND ic.command_type IN ('fee_transfer', 'fee_transfer_via_coinbase')
|
|
328
|
+
ORDER BY bic.block_id, bic.sequence_no
|
|
329
|
+
`;
|
|
330
|
+
return this.client.unsafe(sql, [blockIds]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
groupBy<TRow, TResult>(
|
|
334
|
+
rows: TRow[],
|
|
335
|
+
keyFn: (row: TRow) => number,
|
|
336
|
+
mapFn: (row: TRow) => TResult
|
|
337
|
+
): Map<number, TResult[]> {
|
|
338
|
+
const map = new Map<number, TResult[]>();
|
|
339
|
+
for (const row of rows) {
|
|
340
|
+
const key = keyFn(row);
|
|
341
|
+
let arr = map.get(key);
|
|
342
|
+
if (!arr) {
|
|
343
|
+
arr = [];
|
|
344
|
+
map.set(key, arr);
|
|
345
|
+
}
|
|
346
|
+
arr.push(mapFn(row));
|
|
347
|
+
}
|
|
348
|
+
return map;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
mapUserCommandRow(row: UserCommandRow): UserCommand {
|
|
352
|
+
return {
|
|
353
|
+
hash: row.hash,
|
|
354
|
+
kind: row.kind,
|
|
355
|
+
from: row.from,
|
|
356
|
+
to: row.to,
|
|
357
|
+
amount: row.amount,
|
|
358
|
+
fee: row.fee,
|
|
359
|
+
memo: row.memo,
|
|
360
|
+
nonce: row.nonce,
|
|
361
|
+
status: row.status,
|
|
362
|
+
failureReason: row.failure_reason,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
mapZkAppCommandRow(row: ZkAppCommandRow): ZkAppCommand {
|
|
367
|
+
return {
|
|
368
|
+
hash: row.hash,
|
|
369
|
+
feePayer: row.fee_payer,
|
|
370
|
+
fee: row.fee,
|
|
371
|
+
memo: row.memo,
|
|
372
|
+
status: row.status,
|
|
373
|
+
failureReason: row.failure_reasons_ids
|
|
374
|
+
? String(row.failure_reasons_ids)
|
|
375
|
+
: null,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
mapFeeTransferRow(row: FeeTransferRow): FeeTransfer {
|
|
380
|
+
return {
|
|
381
|
+
recipient: row.recipient,
|
|
382
|
+
fee: row.fee,
|
|
383
|
+
type: row.type,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
rowsToBlocks(
|
|
388
|
+
rows: BlockRow[],
|
|
389
|
+
userCommandsByBlock: Map<number, UserCommand[]> = new Map(),
|
|
390
|
+
zkappCommandsByBlock: Map<number, ZkAppCommand[]> = new Map(),
|
|
391
|
+
feeTransfersByBlock: Map<number, FeeTransfer[]> = new Map()
|
|
392
|
+
): Blocks {
|
|
393
|
+
return rows.map((row) => ({
|
|
394
|
+
blockHeight: row.height,
|
|
395
|
+
creator: row.creator,
|
|
396
|
+
stateHash: row.state_hash,
|
|
397
|
+
parentHash: ENABLE_BLOCK_TRANSACTION_DETAILS ? row.parent_hash : '',
|
|
398
|
+
dateTime: new Date(parseInt(row.timestamp)).toISOString(),
|
|
399
|
+
transactions: {
|
|
400
|
+
coinbase: row.coinbase_amount || '0',
|
|
401
|
+
userCommands: userCommandsByBlock.get(row.id) ?? [],
|
|
402
|
+
zkappCommands: zkappCommandsByBlock.get(row.id) ?? [],
|
|
403
|
+
feeTransfer: feeTransfersByBlock.get(row.id) ?? [],
|
|
404
|
+
},
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Action,
|
|
3
|
+
Event,
|
|
4
|
+
BlocksWithTransactionsMap,
|
|
5
|
+
FieldElementIdWithValueMap,
|
|
6
|
+
BlockInfo,
|
|
7
|
+
} from '../../blockchain/types.js';
|
|
8
|
+
import type { ArchiveNodeDatabaseRow } from '../../db/sql/events-actions/types.js';
|
|
9
|
+
import {
|
|
10
|
+
createTransactionInfo,
|
|
11
|
+
createEvent,
|
|
12
|
+
createAction,
|
|
13
|
+
} from '../../blockchain/utils.js';
|
|
14
|
+
import { filterBestTip } from '../../consensus/mina-consensus.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
partitionBlocks,
|
|
18
|
+
getElementIdFieldValues,
|
|
19
|
+
mapActionOrEvent,
|
|
20
|
+
removeRedundantEmittedFields,
|
|
21
|
+
sortAndFilterBlocks,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Partitions the given rows into a map where the keys are block hashes
|
|
26
|
+
* and the values are maps of transaction hashes to array of rows.
|
|
27
|
+
*
|
|
28
|
+
* For each row, if a block with the same block hash exists in the map,
|
|
29
|
+
* it adds the row to the corresponding transaction in the map.
|
|
30
|
+
* If the block does not exist, it creates a new map entry with the block hash.
|
|
31
|
+
*
|
|
32
|
+
* If the transaction does not exist within a block, it creates a new transaction entry with the transaction hash.
|
|
33
|
+
*
|
|
34
|
+
* @param rows The array of rows to be partitioned.
|
|
35
|
+
* @returns A map where the keys are block hashes and the values are maps of transaction hashes to array of rows.
|
|
36
|
+
*/
|
|
37
|
+
function partitionBlocks(rows: ArchiveNodeDatabaseRow[]) {
|
|
38
|
+
const blocks: BlocksWithTransactionsMap = new Map();
|
|
39
|
+
if (rows.length === 0) return blocks;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < rows.length; i++) {
|
|
42
|
+
const { state_hash: blockHash, hash: transactionHash } = rows[i];
|
|
43
|
+
const blockData = blocks.get(blockHash);
|
|
44
|
+
|
|
45
|
+
if (blockData === undefined) {
|
|
46
|
+
const firstEntry = new Map();
|
|
47
|
+
firstEntry.set(transactionHash, [rows[i]]);
|
|
48
|
+
blocks.set(blockHash, firstEntry);
|
|
49
|
+
} else {
|
|
50
|
+
const blockDataRows = blockData.get(transactionHash);
|
|
51
|
+
if (blockDataRows) {
|
|
52
|
+
blockDataRows.push(rows[i]);
|
|
53
|
+
} else {
|
|
54
|
+
blockData.set(transactionHash, [rows[i]]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return blocks;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extracts 'id' and 'field' properties from each row in the provided array and maps them together.
|
|
63
|
+
*
|
|
64
|
+
* This function iterates over each row in the given array, and for each row, it retrieves
|
|
65
|
+
* the 'id' and 'field' properties. It then stores these values in a Map where the key is
|
|
66
|
+
* the stringified 'id' and the value is the 'field'.
|
|
67
|
+
*
|
|
68
|
+
* @param rows An array of ArchiveNodeDatabaseRow objects to process.
|
|
69
|
+
* @returns A Map where the keys are the stringified 'id' properties from the input rows and
|
|
70
|
+
* the values are the corresponding 'field' properties.
|
|
71
|
+
*/
|
|
72
|
+
function getElementIdFieldValues(rows: ArchiveNodeDatabaseRow[]) {
|
|
73
|
+
const elementIdValues: FieldElementIdWithValueMap = new Map();
|
|
74
|
+
for (let i = 0; i < rows.length; i++) {
|
|
75
|
+
const { field_id, field_value } = rows[i];
|
|
76
|
+
elementIdValues.set(field_id.toString(), field_value);
|
|
77
|
+
}
|
|
78
|
+
return elementIdValues;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createUniqueEventId(
|
|
82
|
+
zkapp_account_update_id: number,
|
|
83
|
+
zkapp_event_array_id: number
|
|
84
|
+
) {
|
|
85
|
+
return [zkapp_account_update_id, zkapp_event_array_id].join(',');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Removes redundant fields from an array of rows based on unique event and account update identifiers.
|
|
90
|
+
*
|
|
91
|
+
* There are redundant fields in the database output because a single action/event can be made up of multiple fields, which means
|
|
92
|
+
* we get rows with the same account update id and event array id but different field values.
|
|
93
|
+
*
|
|
94
|
+
* We remove rows that refer to the same event/action that is emitted, but just keep one row since
|
|
95
|
+
* one row can be used to derive all event/action field values (by it's element_ids).
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* The following rows are returned from the database, which represents one event/action:
|
|
99
|
+
* [
|
|
100
|
+
* { id: 1, field: "1", element_ids: [3, 2, 1], zkapp_event_array_id: 1, zkapp_account_update_id: 1, zkapp_account_updates_ids: [1, 2, 3] },
|
|
101
|
+
* { id: 2, field: "2", element_ids: [3, 2, 1], zkapp_event_array_id: 1, zkapp_account_update_id: 1, zkapp_account_updates_ids: [1, 2, 3] },
|
|
102
|
+
* { id: 3, field: "3", element_ids: [3, 2, 1], zkapp_event_array_id: 1, zkapp_account_update_id: 1, zkapp_account_updates_ids: [1, 2, 3] },
|
|
103
|
+
* ]
|
|
104
|
+
*
|
|
105
|
+
* `id` corresponds to the field id, the `field` corresponds to the field value, and the `element_ids` correspond to the field ids. Since these rows
|
|
106
|
+
* represent a single event/action, they are all redundant because they have the same event array id and account update id.
|
|
107
|
+
*
|
|
108
|
+
* The output of this function will be:
|
|
109
|
+
* [
|
|
110
|
+
* { id: 1, field: "1", element_ids: [3, 2, 1], zkapp_event_array_id: 1, zkapp_account_update_id: 1, zkapp_account_updates_ids: [1, 2, 3] },
|
|
111
|
+
* ]
|
|
112
|
+
*
|
|
113
|
+
*
|
|
114
|
+
* @param archiveNodeRow An array of ArchiveNodeDatabaseRow objects to process.
|
|
115
|
+
* @returns A new array of ArchiveNodeDatabaseRow objects where redundant fields have been removed.
|
|
116
|
+
* @throws {Error} If no matching account update is found for a given account update id and event array id.
|
|
117
|
+
*/
|
|
118
|
+
function removeRedundantEmittedFields(
|
|
119
|
+
archiveNodeRow: ArchiveNodeDatabaseRow[]
|
|
120
|
+
) {
|
|
121
|
+
const inOrderTransactionRows: ArchiveNodeDatabaseRow[][] = [];
|
|
122
|
+
const seenEventOrActionIds = new Set<string>();
|
|
123
|
+
for (let i = 0; i < archiveNodeRow.length; i++) {
|
|
124
|
+
const currentRow = archiveNodeRow[i];
|
|
125
|
+
const {
|
|
126
|
+
event_field_elements_id, // The unique id for the field array in the current row
|
|
127
|
+
event_element_ids, // The list of element ids in the event (list of event_field_elements_id)
|
|
128
|
+
zkapp_account_update_id, // The unique id for the account update that emitted the event/action
|
|
129
|
+
zkapp_account_updates_ids, // List of all account update ids inside the transaction
|
|
130
|
+
} = currentRow;
|
|
131
|
+
|
|
132
|
+
// Create a unique Id consisting of the account update id and event/action id emitted
|
|
133
|
+
// This is used to check if we have already seen this event/action before.
|
|
134
|
+
const uniqueEventId = createUniqueEventId(
|
|
135
|
+
zkapp_account_update_id,
|
|
136
|
+
event_field_elements_id
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (!seenEventOrActionIds.has(uniqueEventId)) {
|
|
140
|
+
// Since multiple events/actions can be emitted in a single account update, we want to put back the event/action
|
|
141
|
+
// in the correct place. To do this, we need to know the index of the event array id in the list of event array ids (these stored in order by the Archive Node)
|
|
142
|
+
const emittedEventOrActionIndexes = findAllIndexes(
|
|
143
|
+
event_element_ids,
|
|
144
|
+
event_field_elements_id
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Since multiple account updates can be emitted in a single transaction, we need to know the index of the account update id in the list of account update ids
|
|
148
|
+
const accountUpdateIndexes = findAllIndexes(
|
|
149
|
+
zkapp_account_updates_ids,
|
|
150
|
+
zkapp_account_update_id
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (accountUpdateIndexes.length === 0) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`No matching account update found for the given account update ID (${zkapp_account_update_id}) and event array ID (${event_field_elements_id}).`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const accountUpdateIdIndex = accountUpdateIndexes[0];
|
|
160
|
+
|
|
161
|
+
// Put the event/action back in the correct place. The specific event/action should go into the index of the account update that emitted it.
|
|
162
|
+
// When we put this all together, the transaction will be in the correct order.
|
|
163
|
+
emittedEventOrActionIndexes.forEach((eventOrActionIndex) => {
|
|
164
|
+
if (inOrderTransactionRows[accountUpdateIdIndex] === undefined) {
|
|
165
|
+
inOrderTransactionRows[accountUpdateIdIndex] = [];
|
|
166
|
+
}
|
|
167
|
+
inOrderTransactionRows[accountUpdateIdIndex][eventOrActionIndex] =
|
|
168
|
+
currentRow;
|
|
169
|
+
});
|
|
170
|
+
seenEventOrActionIds.add(uniqueEventId);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return inOrderTransactionRows.flat();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Maps an array of database rows into an array of Action or Event instances.
|
|
177
|
+
*
|
|
178
|
+
* This function iterates over the given array of rows and for each row, it retrieves the element
|
|
179
|
+
* ids. It then looks up each element id in the provided map of field values and appends the corresponding
|
|
180
|
+
* value to an array, which is used to create a new Action or Event instance, depending on the
|
|
181
|
+
* provided kind.
|
|
182
|
+
*
|
|
183
|
+
* If the kind is 'event', a TransactionInfo object is created from the current row, and this information
|
|
184
|
+
* along with the array of values is used to create a new Event instance. If the kind is 'action',
|
|
185
|
+
* in addition to the TransactionInfo, the account update id from the current row is also used to
|
|
186
|
+
* create a new Action instance. The created instances are appended to the output array.
|
|
187
|
+
*
|
|
188
|
+
* If the element id does not exist in the provided map, the function simply continues with the next id.
|
|
189
|
+
*
|
|
190
|
+
* @param kind The type of instances to create. Can be either 'action' or 'event'.
|
|
191
|
+
* @param rows An array of ArchiveNodeDatabaseRow objects to map into Action or Event instances.
|
|
192
|
+
* @param elementIdFieldValues A map of element ids to their corresponding field values.
|
|
193
|
+
* @returns An array of Action or Event instances.
|
|
194
|
+
*/
|
|
195
|
+
function mapActionOrEvent(
|
|
196
|
+
kind: 'action' | 'event',
|
|
197
|
+
rows: ArchiveNodeDatabaseRow[],
|
|
198
|
+
elementIdFieldValues: FieldElementIdWithValueMap
|
|
199
|
+
) {
|
|
200
|
+
const data: (Event | Action)[] = [];
|
|
201
|
+
for (let i = 0; i < rows.length; i++) {
|
|
202
|
+
const { zkapp_account_update_id, event_field_element_ids } = rows[i];
|
|
203
|
+
const transactionInfo = createTransactionInfo(rows[i]);
|
|
204
|
+
const elementIdToFieldValues = getFieldValuesFromElementIds(
|
|
205
|
+
event_field_element_ids,
|
|
206
|
+
elementIdFieldValues
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
if (kind === 'event') {
|
|
210
|
+
const event = createEvent(
|
|
211
|
+
zkapp_account_update_id.toString(),
|
|
212
|
+
elementIdToFieldValues,
|
|
213
|
+
transactionInfo
|
|
214
|
+
);
|
|
215
|
+
data.push(event);
|
|
216
|
+
} else {
|
|
217
|
+
const { zkapp_account_update_id } = rows[i];
|
|
218
|
+
const action = createAction(
|
|
219
|
+
zkapp_account_update_id.toString(),
|
|
220
|
+
elementIdToFieldValues,
|
|
221
|
+
transactionInfo
|
|
222
|
+
);
|
|
223
|
+
data.push(action);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return data;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function getFieldValuesFromElementIds(
|
|
230
|
+
element_ids: number[],
|
|
231
|
+
elementIdFieldValues: FieldElementIdWithValueMap
|
|
232
|
+
) {
|
|
233
|
+
const elementIdToFieldValues = [];
|
|
234
|
+
for (const elementId of element_ids) {
|
|
235
|
+
const elementIdFieldValue = elementIdFieldValues.get(elementId.toString());
|
|
236
|
+
if (elementIdFieldValue === undefined) continue;
|
|
237
|
+
elementIdToFieldValues.push(elementIdFieldValue);
|
|
238
|
+
}
|
|
239
|
+
return elementIdToFieldValues;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function sortAndFilterBlocks<T extends { blockInfo: BlockInfo }>(data: T[]) {
|
|
243
|
+
data.sort((a, b) => {
|
|
244
|
+
if (a.blockInfo.height < b.blockInfo.height) return -1;
|
|
245
|
+
if (a.blockInfo.height > b.blockInfo.height) return 1;
|
|
246
|
+
if (a.blockInfo.timestamp < b.blockInfo.timestamp) return -1;
|
|
247
|
+
if (a.blockInfo.timestamp > b.blockInfo.timestamp) return 1;
|
|
248
|
+
return 0;
|
|
249
|
+
});
|
|
250
|
+
filterBestTip(data);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function findAllIndexes<T>(arr: T[], target: T): number[] {
|
|
254
|
+
const indexes: number[] = [];
|
|
255
|
+
arr.forEach((element, index) => {
|
|
256
|
+
if (element === target) {
|
|
257
|
+
indexes.push(index);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return indexes;
|
|
261
|
+
}
|