univo 0.3.3 → 0.4.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/dist/cli.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +441 -384
- package/dist/proxy.d.mts +20 -0
- package/dist/proxy.d.ts +20 -0
- package/dist/proxy.mjs +90 -0
- package/dist/realtime.d.mts +2 -3
- package/dist/realtime.d.ts +2 -3
- package/dist/realtime.mjs +20 -3
- package/dist/rpc.d.mts +3 -326
- package/dist/rpc.d.ts +3 -326
- package/dist/shared/univo.C0flyVee.mjs +21 -0
- package/dist/shared/univo.CEA4vY0E.d.ts +354 -0
- package/dist/shared/univo.DYd1g40o.d.mts +354 -0
- package/dist/shared/{univo.BCKcG-WP.mjs → univo.mlthZw0m.mjs} +2 -6
- package/dist/transport.d.mts +1 -39
- package/dist/transport.d.ts +1 -39
- package/dist/transport.mjs +215 -4
- package/package.json +5 -1
- package/dist/shared/univo.DObJAnN6.mjs +0 -238
package/dist/index.mjs
CHANGED
|
@@ -1,100 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { c as createLogger,
|
|
1
|
+
import { hexToNumber, numberToHex, hexToBytes, keccak256, toRlp, hexToBigInt, formatTransaction, serializeTransaction, numberToBytes, isHex, concatBytes } from 'viem';
|
|
2
|
+
import { local } from './transport.mjs';
|
|
3
|
+
import { c as createLogger, r as retry, v as version, n as normalizeHex, d as decompress, i as isHexEqual, a as compress, b as iife, e as decoder } from './shared/univo.mlthZw0m.mjs';
|
|
4
4
|
import { AdapterError } from './metadata/adapters.mjs';
|
|
5
|
+
import { c as createException, a as catchException } from './shared/univo.C0flyVee.mjs';
|
|
5
6
|
import 'ws';
|
|
6
7
|
import 'partysocket';
|
|
7
8
|
|
|
8
|
-
function createServer(opts) {
|
|
9
|
-
const log = createLogger({ quiet: opts.quiet, prefix: "[server]" });
|
|
10
|
-
const http = async (req) => {
|
|
11
|
-
let body_buffer;
|
|
12
|
-
try {
|
|
13
|
-
body_buffer = await req.arrayBuffer();
|
|
14
|
-
} catch {
|
|
15
|
-
return Response.json(
|
|
16
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid request body" } },
|
|
17
|
-
//
|
|
18
|
-
{ status: 400 }
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
const authorization = req.headers.get("Authorization");
|
|
22
|
-
const authenticated = authorization === `Bearer ${opts.signingKey}`;
|
|
23
|
-
let body_string;
|
|
24
|
-
if (req.headers.get("Content-Encoding") === "gzip") {
|
|
25
|
-
if (!authorization) {
|
|
26
|
-
return Response.json(
|
|
27
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "No bearer token provided" } },
|
|
28
|
-
//
|
|
29
|
-
{ status: 400 }
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
if (!authenticated) {
|
|
33
|
-
return Response.json(
|
|
34
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Invalid bearer token" } },
|
|
35
|
-
//
|
|
36
|
-
{ status: 400 }
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
body_string = await decompress(body_buffer);
|
|
40
|
-
} else {
|
|
41
|
-
body_string = decoder.decode(body_buffer);
|
|
42
|
-
}
|
|
43
|
-
let json;
|
|
44
|
-
try {
|
|
45
|
-
json = JSON.parse(body_string);
|
|
46
|
-
} catch {
|
|
47
|
-
return Response.json(
|
|
48
|
-
{ jsonrpc: "2.0", id: null, error: { code: 0, message: "Malformed JSON request body" } },
|
|
49
|
-
//
|
|
50
|
-
{ status: 400 }
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
if (json.method.startsWith("private_")) {
|
|
54
|
-
if (!authorization) {
|
|
55
|
-
return Response.json(
|
|
56
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "No bearer token provided" } },
|
|
57
|
-
//
|
|
58
|
-
{ status: 400 }
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
if (authorization !== `Bearer ${opts.signingKey}`) {
|
|
62
|
-
return Response.json(
|
|
63
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Invalid bearer token" } },
|
|
64
|
-
//
|
|
65
|
-
{ status: 400 }
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
const result = await opts.transport.request({ method: json.method, params: json.params });
|
|
71
|
-
return Response.json({ jsonrpc: "2.0", id: json.id, result });
|
|
72
|
-
} catch (error) {
|
|
73
|
-
const message = getException(error);
|
|
74
|
-
if (message) {
|
|
75
|
-
log.error(message);
|
|
76
|
-
return Response.json(
|
|
77
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message } },
|
|
78
|
-
//
|
|
79
|
-
{ status: 400 }
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
if (error instanceof Error) {
|
|
83
|
-
log.error(error.message);
|
|
84
|
-
}
|
|
85
|
-
return Response.json(
|
|
86
|
-
{ jsonrpc: "2.0", id: json.id, error: { code: 0, message: "Internal server error" } },
|
|
87
|
-
//
|
|
88
|
-
{ status: 500 }
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
return {
|
|
93
|
-
http
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const SUPPORTED_TRANSACTION_TYPES = /* @__PURE__ */ new Set([0n, 1n, 2n, 3n, 4n]);
|
|
98
9
|
function matchFilter(block, filter) {
|
|
99
10
|
if (!chainValid(block, filter)) return false;
|
|
100
11
|
if (!toBlockValid(block, filter)) return false;
|
|
@@ -104,16 +15,16 @@ function matchFilter(block, filter) {
|
|
|
104
15
|
return true;
|
|
105
16
|
}
|
|
106
17
|
const chainValid = (block, filter) => {
|
|
107
|
-
if (
|
|
18
|
+
if (hexToNumber(block.eth_chainId) === filter.chain) return true;
|
|
108
19
|
return false;
|
|
109
20
|
};
|
|
110
21
|
const fromBlockValid = (block, filter) => {
|
|
111
|
-
if (
|
|
22
|
+
if (hexToNumber(block.eth_getBlockByNumber.number) >= filter.fromBlock) return true;
|
|
112
23
|
return false;
|
|
113
24
|
};
|
|
114
25
|
const toBlockValid = (block, filter) => {
|
|
115
26
|
if (filter.toBlock === void 0) return true;
|
|
116
|
-
if (
|
|
27
|
+
if (hexToNumber(block.eth_getBlockByNumber.number) <= filter.toBlock) return true;
|
|
117
28
|
return false;
|
|
118
29
|
};
|
|
119
30
|
const includesLogAddress = (block, filter) => {
|
|
@@ -134,18 +45,45 @@ const includesLogEvent = (block, filter) => {
|
|
|
134
45
|
}
|
|
135
46
|
return false;
|
|
136
47
|
};
|
|
48
|
+
function defineEvent(event) {
|
|
49
|
+
return event;
|
|
50
|
+
}
|
|
51
|
+
function defineAction(action) {
|
|
52
|
+
return action;
|
|
53
|
+
}
|
|
137
54
|
function defineIndexer(opts) {
|
|
138
55
|
const log = createLogger({ quiet: opts.quiet ?? false, prefix: "[indexer]" });
|
|
139
|
-
const allEvents = [];
|
|
56
|
+
const allEvents = [...opts.events];
|
|
140
57
|
const eventsGroupedByStorageMap = /* @__PURE__ */ new Map();
|
|
141
|
-
const
|
|
58
|
+
for (const event of allEvents) {
|
|
59
|
+
if (!/^[A-Za-z0-9_-]+$/.test(event.id)) {
|
|
60
|
+
throw new Error(`Invalid event id \`${event.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
61
|
+
}
|
|
62
|
+
const group = eventsGroupedByStorageMap.get(event.storage) ?? [];
|
|
63
|
+
group.push(event);
|
|
64
|
+
eventsGroupedByStorageMap.set(event.storage, group);
|
|
65
|
+
}
|
|
66
|
+
const allActions = [...opts.actions];
|
|
67
|
+
const actionIds = /* @__PURE__ */ new Set();
|
|
68
|
+
for (const action of allActions) {
|
|
69
|
+
if (!/^[A-Za-z0-9_-]+$/.test(action.id)) {
|
|
70
|
+
throw new Error(`Invalid action id \`${action.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
71
|
+
}
|
|
72
|
+
if (actionIds.has(action.id)) {
|
|
73
|
+
throw new Error(`Duplicate action id \`${action.id}\`.`);
|
|
74
|
+
}
|
|
75
|
+
actionIds.add(action.id);
|
|
76
|
+
}
|
|
142
77
|
async function getBlockFromChain(head) {
|
|
143
78
|
return await retry(() => retry_getBlockFromChain(head), 2);
|
|
144
79
|
}
|
|
145
80
|
async function retry_getBlockFromChain(head) {
|
|
146
81
|
const block = await opts.getBlock({ chain: head.chain, number: head.number });
|
|
147
|
-
if (block === null) {
|
|
148
|
-
throw new Error("
|
|
82
|
+
if (block.eth_getBlockByNumber === null) {
|
|
83
|
+
throw new Error("eth_getBlockByNumber returned null response");
|
|
84
|
+
}
|
|
85
|
+
if (block.eth_getBlockReceipts === null) {
|
|
86
|
+
throw new Error("eth_getBlockReceipts returned null response");
|
|
149
87
|
}
|
|
150
88
|
if (typeof head.hash === "string") {
|
|
151
89
|
if (!isHexEqual(head.hash, block.eth_getBlockByNumber.hash)) {
|
|
@@ -157,6 +95,7 @@ function defineIndexer(opts) {
|
|
|
157
95
|
throw new Error("Block returned unexpected parent hash");
|
|
158
96
|
}
|
|
159
97
|
}
|
|
98
|
+
verifyBlockHash(block);
|
|
160
99
|
verifyBlockHashes(block);
|
|
161
100
|
verifyLogIndicies(block);
|
|
162
101
|
verifyReceiptsRoot(block);
|
|
@@ -165,6 +104,48 @@ function defineIndexer(opts) {
|
|
|
165
104
|
verifyTransactionGasUsage(block);
|
|
166
105
|
return block;
|
|
167
106
|
}
|
|
107
|
+
function verifyBlockHash(block) {
|
|
108
|
+
const header = block.eth_getBlockByNumber;
|
|
109
|
+
const fields = [
|
|
110
|
+
hexToBytes(header.parentHash),
|
|
111
|
+
hexToBytes(header.sha3Uncles),
|
|
112
|
+
hexToBytes(header.miner),
|
|
113
|
+
hexToBytes(header.stateRoot),
|
|
114
|
+
hexToBytes(header.transactionsRoot),
|
|
115
|
+
hexToBytes(header.receiptsRoot),
|
|
116
|
+
hexToBytes(header.logsBloom),
|
|
117
|
+
quantityToBytes(header.difficulty),
|
|
118
|
+
quantityToBytes(header.number),
|
|
119
|
+
quantityToBytes(header.gasLimit),
|
|
120
|
+
quantityToBytes(header.gasUsed),
|
|
121
|
+
quantityToBytes(header.timestamp),
|
|
122
|
+
hexToBytes(header.extraData),
|
|
123
|
+
hexToBytes(header.mixHash),
|
|
124
|
+
hexToBytes(header.nonce)
|
|
125
|
+
];
|
|
126
|
+
const extensions = [
|
|
127
|
+
[header.baseFeePerGas, quantityToBytes],
|
|
128
|
+
[header.withdrawalsRoot, hexToBytes],
|
|
129
|
+
[header.blobGasUsed, quantityToBytes],
|
|
130
|
+
[header.excessBlobGas, quantityToBytes],
|
|
131
|
+
[header.parentBeaconBlockRoot, hexToBytes],
|
|
132
|
+
[header.requestsHash, hexToBytes]
|
|
133
|
+
];
|
|
134
|
+
let missingField = false;
|
|
135
|
+
for (const [value, encode] of extensions) {
|
|
136
|
+
if (value == null) {
|
|
137
|
+
missingField = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (missingField) {
|
|
141
|
+
throw new Error("Block returned incomplete block header");
|
|
142
|
+
}
|
|
143
|
+
fields.push(encode(value));
|
|
144
|
+
}
|
|
145
|
+
if (!isHexEqual(header.hash, keccak256(toRlp(fields, "bytes")))) {
|
|
146
|
+
throw new Error("Block returned header that does not match the block hash");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
168
149
|
function verifyBlockHashes(block) {
|
|
169
150
|
const blockHash = block.eth_getBlockByNumber.hash;
|
|
170
151
|
const transactions = block.eth_getBlockByNumber.transactions;
|
|
@@ -177,7 +158,7 @@ function defineIndexer(opts) {
|
|
|
177
158
|
if (!isHexEqual(block.eth_getBlockByNumber.hash, receipt.blockHash)) {
|
|
178
159
|
throw new Error("Block returned receipt with unexpected block hash");
|
|
179
160
|
}
|
|
180
|
-
const transactionIndex =
|
|
161
|
+
const transactionIndex = hexToNumber(receipt.transactionIndex);
|
|
181
162
|
const transaction = transactions[transactionIndex];
|
|
182
163
|
if (transaction === void 0 || !isHexEqual(transaction.hash, receipt.transactionHash)) {
|
|
183
164
|
throw new Error("Block returned receipt with unexpected transaction hash");
|
|
@@ -196,7 +177,7 @@ function defineIndexer(opts) {
|
|
|
196
177
|
let expectedLogIndex = 0n;
|
|
197
178
|
for (const receipt of block.eth_getBlockReceipts) {
|
|
198
179
|
for (const entry of receipt.logs) {
|
|
199
|
-
if (
|
|
180
|
+
if (hexToBigInt(entry.logIndex) !== expectedLogIndex) {
|
|
200
181
|
throw new Error("Block returned non-contiguous log indices");
|
|
201
182
|
}
|
|
202
183
|
expectedLogIndex++;
|
|
@@ -211,17 +192,17 @@ function defineIndexer(opts) {
|
|
|
211
192
|
}
|
|
212
193
|
function calculateTrieRoot(values) {
|
|
213
194
|
if (values.length === 0) {
|
|
214
|
-
return
|
|
195
|
+
return keccak256(toRlp(new Uint8Array(), "bytes"));
|
|
215
196
|
}
|
|
216
197
|
const entries = values.map((value, index) => ({
|
|
217
|
-
key: bytesToNibbles(
|
|
198
|
+
key: bytesToNibbles(toRlp(quantityToBytes(BigInt(index)), "bytes")),
|
|
218
199
|
value
|
|
219
200
|
}));
|
|
220
|
-
return
|
|
201
|
+
return keccak256(toRlp(encodeTrieNode(entries), "bytes"));
|
|
221
202
|
}
|
|
222
203
|
function trieNodeReference(node) {
|
|
223
|
-
const encoded =
|
|
224
|
-
return encoded.length < 32 ? node :
|
|
204
|
+
const encoded = toRlp(node, "bytes");
|
|
205
|
+
return encoded.length < 32 ? node : hexToBytes(keccak256(encoded));
|
|
225
206
|
}
|
|
226
207
|
function encodeTrieNode(entries, depth = 0) {
|
|
227
208
|
if (entries.length === 1) {
|
|
@@ -259,30 +240,31 @@ function defineIndexer(opts) {
|
|
|
259
240
|
return bytes;
|
|
260
241
|
}
|
|
261
242
|
function quantityToBytes(value) {
|
|
262
|
-
const number = typeof value === "bigint" ? value :
|
|
263
|
-
return number === 0n ? new Uint8Array() :
|
|
243
|
+
const number = typeof value === "bigint" ? value : hexToBigInt(value);
|
|
244
|
+
return number === 0n ? new Uint8Array() : numberToBytes(number);
|
|
264
245
|
}
|
|
265
246
|
function bytesToNibbles(value) {
|
|
266
247
|
return Array.from(value).flatMap((byte) => [byte >> 4, byte & 15]);
|
|
267
248
|
}
|
|
268
249
|
function serializeReceipt(receipt) {
|
|
269
|
-
const outcome = receipt.status === void 0 ?
|
|
250
|
+
const outcome = receipt.status === void 0 ? hexToBytes(receipt.root) : quantityToBytes(receipt.status);
|
|
270
251
|
const fields = [
|
|
271
252
|
outcome,
|
|
272
253
|
quantityToBytes(receipt.cumulativeGasUsed),
|
|
273
|
-
|
|
274
|
-
receipt.logs.map((log2) => [
|
|
254
|
+
hexToBytes(receipt.logsBloom),
|
|
255
|
+
receipt.logs.map((log2) => [hexToBytes(log2.address), log2.topics.map(hexToBytes), hexToBytes(log2.data)])
|
|
275
256
|
];
|
|
276
|
-
const encoded =
|
|
277
|
-
if (!
|
|
257
|
+
const encoded = toRlp(fields, "bytes");
|
|
258
|
+
if (!isHex(receipt.type)) {
|
|
278
259
|
throw new Error(`Unsupported receipt type ${receipt.type}`);
|
|
279
260
|
}
|
|
280
|
-
const type =
|
|
281
|
-
return type === 0n ? encoded :
|
|
261
|
+
const type = hexToBigInt(receipt.type);
|
|
262
|
+
return type === 0n ? encoded : concatBytes([Uint8Array.of(Number(type)), encoded]);
|
|
282
263
|
}
|
|
264
|
+
const SUPPORTED_TRANSACTION_TYPES = /* @__PURE__ */ new Set([0n, 1n, 2n, 3n, 4n]);
|
|
283
265
|
function verifyTransactionsRoot(block) {
|
|
284
266
|
const unsupportedTxs = block.eth_getBlockByNumber.transactions.filter((tx) => {
|
|
285
|
-
return !SUPPORTED_TRANSACTION_TYPES.has(
|
|
267
|
+
return !SUPPORTED_TRANSACTION_TYPES.has(hexToBigInt(tx.type));
|
|
286
268
|
});
|
|
287
269
|
for (const tx of unsupportedTxs) {
|
|
288
270
|
log.warn(`Encountered unsupported tx type for ${tx.hash} on ${block.eth_chainId}`);
|
|
@@ -291,20 +273,20 @@ function defineIndexer(opts) {
|
|
|
291
273
|
return log.warn("Unable to verify block `transactionsRoot` because of unsupported tx type");
|
|
292
274
|
}
|
|
293
275
|
const transactions = block.eth_getBlockByNumber.transactions.map((tx) => {
|
|
294
|
-
const { input, ...formatted } =
|
|
276
|
+
const { input, ...formatted } = formatTransaction(tx);
|
|
295
277
|
if (formatted.type === "legacy" && (formatted.v === 27n || formatted.v === 28n)) {
|
|
296
278
|
formatted.chainId = void 0;
|
|
297
279
|
}
|
|
298
280
|
const signature = formatted.type === "legacy" ? { r: formatted.r, s: formatted.s, v: formatted.v } : { r: formatted.r, s: formatted.s, yParity: formatted.yParity };
|
|
299
|
-
const serialized =
|
|
281
|
+
const serialized = serializeTransaction(
|
|
300
282
|
{ ...formatted, data: input },
|
|
301
283
|
//
|
|
302
284
|
signature
|
|
303
285
|
);
|
|
304
|
-
if (!isHexEqual(tx.hash,
|
|
286
|
+
if (!isHexEqual(tx.hash, keccak256(serialized))) {
|
|
305
287
|
throw new Error(`Transaction ${tx.hash} failed serialization check`);
|
|
306
288
|
}
|
|
307
|
-
return
|
|
289
|
+
return hexToBytes(serialized);
|
|
308
290
|
});
|
|
309
291
|
const transactionsRoot = calculateTrieRoot(transactions);
|
|
310
292
|
if (!isHexEqual(block.eth_getBlockByNumber.transactionsRoot, transactionsRoot)) {
|
|
@@ -319,29 +301,29 @@ function defineIndexer(opts) {
|
|
|
319
301
|
}
|
|
320
302
|
for (let index = 0; index < transactions.length; index++) {
|
|
321
303
|
const expectedIndex = BigInt(index);
|
|
322
|
-
if (
|
|
304
|
+
if (hexToBigInt(transactions[index].transactionIndex) !== expectedIndex) {
|
|
323
305
|
throw new Error("Block returned non-contiguous transaction indices");
|
|
324
306
|
}
|
|
325
|
-
if (
|
|
307
|
+
if (hexToBigInt(receipts[index].transactionIndex) !== expectedIndex) {
|
|
326
308
|
throw new Error("Block returned non-contiguous transaction indices");
|
|
327
309
|
}
|
|
328
310
|
for (const entry of receipts[index].logs) {
|
|
329
|
-
if (
|
|
311
|
+
if (hexToBigInt(entry.transactionIndex) !== expectedIndex) {
|
|
330
312
|
throw new Error("Block returned log with unexpected transaction index");
|
|
331
313
|
}
|
|
332
314
|
}
|
|
333
315
|
}
|
|
334
316
|
}
|
|
335
317
|
function verifyTransactionGasUsage(block) {
|
|
336
|
-
const gasLimit =
|
|
337
|
-
const blockGasUsed =
|
|
318
|
+
const gasLimit = hexToBigInt(block.eth_getBlockByNumber.gasLimit);
|
|
319
|
+
const blockGasUsed = hexToBigInt(block.eth_getBlockByNumber.gasUsed);
|
|
338
320
|
if (blockGasUsed > gasLimit) {
|
|
339
321
|
throw new Error("Block returned gas used greater than the block gas limit");
|
|
340
322
|
}
|
|
341
323
|
let cumulativeGasUsed = 0n;
|
|
342
324
|
for (const receipt of block.eth_getBlockReceipts) {
|
|
343
|
-
cumulativeGasUsed +=
|
|
344
|
-
if (
|
|
325
|
+
cumulativeGasUsed += hexToBigInt(receipt.gasUsed);
|
|
326
|
+
if (hexToBigInt(receipt.cumulativeGasUsed) !== cumulativeGasUsed) {
|
|
345
327
|
throw new Error("Block returned inconsistent cumulative gas used");
|
|
346
328
|
}
|
|
347
329
|
if (cumulativeGasUsed > gasLimit) {
|
|
@@ -352,13 +334,17 @@ function defineIndexer(opts) {
|
|
|
352
334
|
throw new Error("Block returned inconsistent gas used");
|
|
353
335
|
}
|
|
354
336
|
}
|
|
337
|
+
const public_getFinalizedHeight = async (chain) => {
|
|
338
|
+
const manifest = await getOrInitManifest(chain);
|
|
339
|
+
return manifest.finalized_block_height;
|
|
340
|
+
};
|
|
355
341
|
async function getOrInitManifest(chain) {
|
|
356
342
|
const path = `manifest/v1/${normalizeHex(chain)}`;
|
|
357
343
|
const manifestRes = await opts.metadataStorage.adapter.get(path);
|
|
358
344
|
let manifest;
|
|
359
345
|
if (manifestRes === null) {
|
|
360
346
|
const block = await getBlockFromChain({ chain, number: "finalized" });
|
|
361
|
-
const chainFinalizedHeight =
|
|
347
|
+
const chainFinalizedHeight = hexToNumber(block.eth_getBlockByNumber.number);
|
|
362
348
|
const newManifest = {
|
|
363
349
|
finalized_block_height: chainFinalizedHeight,
|
|
364
350
|
finalized_block_hash: block.eth_getBlockByNumber.hash,
|
|
@@ -372,39 +358,70 @@ function defineIndexer(opts) {
|
|
|
372
358
|
}
|
|
373
359
|
return manifest;
|
|
374
360
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
361
|
+
const public_writeUnfinalizedHead = async (head) => {
|
|
362
|
+
log.debug("Received unfinalized head...");
|
|
363
|
+
const blocksStart = Date.now();
|
|
364
|
+
const [block, manifest] = await Promise.all([
|
|
365
|
+
getBlockFromChain(head),
|
|
366
|
+
//
|
|
367
|
+
getOrInitManifest(head.chain)
|
|
368
|
+
]);
|
|
369
|
+
log.debug(`Loaded block in ${Date.now() - blocksStart}ms`);
|
|
370
|
+
const indexerFinalizedHeight = manifest.finalized_block_height;
|
|
371
|
+
if (hexToNumber(head.number) <= indexerFinalizedHeight) {
|
|
372
|
+
return log.debug("Receiving finalized head, ignoring...");
|
|
373
|
+
}
|
|
374
|
+
const metadata_start = Date.now();
|
|
375
|
+
const chain = normalizeHex(block.eth_chainId);
|
|
376
|
+
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
377
|
+
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
378
|
+
const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
|
|
379
|
+
const blocksKey = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
380
|
+
const blocksValue = await compress(JSON.stringify(block));
|
|
381
|
+
const conditional = { ifNoneMatch: "*" };
|
|
382
|
+
const result = await opts.metadataStorage.adapter.put(blocksKey, blocksValue, conditional).catch((error) => {
|
|
383
|
+
if (error instanceof AdapterError) {
|
|
384
|
+
if (error.tag === "PreconditionFailed") {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
throw error;
|
|
389
|
+
});
|
|
390
|
+
if (result === null) {
|
|
391
|
+
return log.debug("Block already persisted to wal, ignoring...");
|
|
392
|
+
}
|
|
393
|
+
log.debug(`Persisted block to wal in ${Date.now() - metadata_start}ms`);
|
|
394
|
+
await writeUnfinalizedBlock(block);
|
|
378
395
|
};
|
|
379
396
|
async function writeUnfinalizedBlock(block) {
|
|
380
397
|
const events_start = Date.now();
|
|
381
398
|
const events = eventsGroupedByStorageMap.entries().map(async ([storage, grouped_events]) => {
|
|
382
399
|
const batch = [];
|
|
383
|
-
for (const
|
|
400
|
+
for (const event of grouped_events) {
|
|
384
401
|
try {
|
|
385
|
-
if (!
|
|
386
|
-
log.debug(`Block matches no filters for event ${
|
|
402
|
+
if (!event.filters.some((filter) => matchFilter(block, filter))) {
|
|
403
|
+
log.debug(`Block matches no filters for event ${event.id}`);
|
|
387
404
|
continue;
|
|
388
405
|
}
|
|
389
|
-
const events2 =
|
|
390
|
-
for (const
|
|
391
|
-
batch.push(
|
|
406
|
+
const events2 = event.handler(block);
|
|
407
|
+
for (const event2 of events2) {
|
|
408
|
+
batch.push(event2);
|
|
392
409
|
}
|
|
393
410
|
} catch (error) {
|
|
394
|
-
log.error(`Failed to run your 'handler' for event ${
|
|
411
|
+
log.error(`Failed to run your 'handler' for event ${event.id}`);
|
|
395
412
|
throw error;
|
|
396
413
|
}
|
|
397
414
|
}
|
|
398
415
|
if (batch.length > 0) {
|
|
399
416
|
const start = Date.now();
|
|
400
417
|
await retry(() => storage.upsert(batch), 2).catch((error) => {
|
|
401
|
-
for (const
|
|
402
|
-
log.error(`Failed to run your 'upsert' handler for event ${
|
|
418
|
+
for (const event of grouped_events) {
|
|
419
|
+
log.error(`Failed to run your 'upsert' handler for event ${event.id}`);
|
|
403
420
|
}
|
|
404
421
|
throw error;
|
|
405
422
|
});
|
|
406
|
-
for (const
|
|
407
|
-
log.debug(`Recorded ${batch.length} ${
|
|
423
|
+
for (const event of grouped_events) {
|
|
424
|
+
log.debug(`Recorded ${batch.length} ${event.id} in ${Date.now() - start}ms`);
|
|
408
425
|
}
|
|
409
426
|
}
|
|
410
427
|
});
|
|
@@ -418,84 +435,71 @@ function defineIndexer(opts) {
|
|
|
418
435
|
const commitsValue = JSON.stringify({ hello: "world" });
|
|
419
436
|
await opts.metadataStorage.adapter.put(commitsKey, commitsValue);
|
|
420
437
|
}
|
|
421
|
-
const
|
|
422
|
-
log.debug(
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
|
|
438
|
+
const public_deleteReorganisedHead = async (head) => {
|
|
439
|
+
log.debug(`Received reorganised head ${hexToNumber(head.number)}`);
|
|
440
|
+
const chain = normalizeHex(head.chain);
|
|
441
|
+
const number = normalizeHex(head.number, 16);
|
|
442
|
+
const hash = normalizeHex(head.hash);
|
|
443
|
+
const parentHash = normalizeHex(head.parent_hash);
|
|
444
|
+
const blocksKey = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
445
|
+
const [blocksRes, canonicalBlock] = await Promise.all([
|
|
446
|
+
opts.metadataStorage.adapter.get(blocksKey),
|
|
426
447
|
//
|
|
427
|
-
|
|
448
|
+
getBlockFromChain({ chain: head.chain, number: head.number })
|
|
428
449
|
]);
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if (viem.hexToNumber(head.number) <= indexerFinalizedHeight) {
|
|
432
|
-
return log.debug("Receiving finalized head, ignoring...");
|
|
450
|
+
if (blocksRes === null) {
|
|
451
|
+
return log.debug("Reorganised block never/already processed");
|
|
433
452
|
}
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
|
|
439
|
-
const blocksKey = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
440
|
-
const blocksValue = await compress(JSON.stringify(block));
|
|
441
|
-
const conditional = { ifNoneMatch: "*" };
|
|
442
|
-
const result = await opts.metadataStorage.adapter.put(blocksKey, blocksValue, conditional).catch((error) => {
|
|
443
|
-
if (error instanceof AdapterError) {
|
|
444
|
-
if (error.tag === "PreconditionFailed") {
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
throw error;
|
|
449
|
-
});
|
|
450
|
-
if (result === null) {
|
|
451
|
-
return log.debug("Block already persisted to wal, ignoring...");
|
|
453
|
+
const decompressedBlock = await decompress(blocksRes.body);
|
|
454
|
+
const storedBlock = JSON.parse(decompressedBlock);
|
|
455
|
+
if (isHexEqual(head.hash, canonicalBlock.eth_getBlockByNumber.hash)) {
|
|
456
|
+
throw new Error("Attempted to delete canonical block");
|
|
452
457
|
}
|
|
453
|
-
|
|
454
|
-
await writeUnfinalizedBlock(block);
|
|
458
|
+
await deleteReorganisedBlocksAndWriteCanonicalBlock([storedBlock], canonicalBlock);
|
|
455
459
|
};
|
|
456
460
|
async function deleteReorganisedBlocksAndWriteCanonicalBlock(reorganised, canonical) {
|
|
457
|
-
const deletes = allEvents.map(async (
|
|
461
|
+
const deletes = allEvents.map(async (event) => {
|
|
458
462
|
const batch = [];
|
|
459
463
|
try {
|
|
460
464
|
for (const block of reorganised) {
|
|
461
|
-
const events =
|
|
462
|
-
for (const
|
|
463
|
-
batch.push(
|
|
465
|
+
const events = event.handler(block);
|
|
466
|
+
for (const event2 of events) {
|
|
467
|
+
batch.push(event2);
|
|
464
468
|
}
|
|
465
469
|
}
|
|
466
470
|
} catch (error) {
|
|
467
|
-
log.error(`Failed to run your 'handler' for event ${
|
|
471
|
+
log.error(`Failed to run your 'handler' for event ${event.id}`);
|
|
468
472
|
throw error;
|
|
469
473
|
}
|
|
470
474
|
if (batch.length === 0) {
|
|
471
475
|
return;
|
|
472
476
|
}
|
|
473
|
-
await retry(() =>
|
|
474
|
-
log.error(`Failed to run your 'delete' handler for event ${
|
|
477
|
+
await retry(() => event.storage.delete(batch), 2).catch((error) => {
|
|
478
|
+
log.error(`Failed to run your 'delete' handler for event ${event.id}`);
|
|
475
479
|
throw error;
|
|
476
480
|
});
|
|
477
481
|
});
|
|
478
482
|
await Promise.all(deletes);
|
|
479
483
|
const upserts = eventsGroupedByStorageMap.entries().map(async ([storage, grouped_events]) => {
|
|
480
484
|
const batch = [];
|
|
481
|
-
for (const
|
|
485
|
+
for (const event of grouped_events) {
|
|
482
486
|
try {
|
|
483
|
-
if (!
|
|
487
|
+
if (!event.filters.some((filter) => matchFilter(canonical, filter))) {
|
|
484
488
|
continue;
|
|
485
489
|
}
|
|
486
|
-
const events =
|
|
487
|
-
for (const
|
|
488
|
-
batch.push(
|
|
490
|
+
const events = event.handler(canonical);
|
|
491
|
+
for (const event2 of events) {
|
|
492
|
+
batch.push(event2);
|
|
489
493
|
}
|
|
490
494
|
} catch (error) {
|
|
491
|
-
log.error(`Failed to run your 'handler' for event ${
|
|
495
|
+
log.error(`Failed to run your 'handler' for event ${event.id}`);
|
|
492
496
|
throw error;
|
|
493
497
|
}
|
|
494
498
|
}
|
|
495
499
|
if (batch.length > 0) {
|
|
496
500
|
await retry(() => storage.upsert(batch), 2).catch((error) => {
|
|
497
|
-
for (const
|
|
498
|
-
log.error(`Failed to run your 'upsert' handler for event ${
|
|
501
|
+
for (const event of grouped_events) {
|
|
502
|
+
log.error(`Failed to run your 'upsert' handler for event ${event.id}`);
|
|
499
503
|
}
|
|
500
504
|
throw error;
|
|
501
505
|
});
|
|
@@ -503,55 +507,38 @@ function defineIndexer(opts) {
|
|
|
503
507
|
});
|
|
504
508
|
await Promise.all(upserts);
|
|
505
509
|
}
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const parentHash = normalizeHex(head.parent_hash);
|
|
512
|
-
const blocksKey = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
513
|
-
const [blocksRes, canonicalBlock] = await Promise.all([
|
|
514
|
-
opts.metadataStorage.adapter.get(blocksKey),
|
|
510
|
+
const public_writeFinalizedHeight = async (chain) => {
|
|
511
|
+
const path = `cache/v1/${normalizeHex(chain)}`;
|
|
512
|
+
const [cached, block] = await Promise.all([
|
|
513
|
+
getChainCache(chain),
|
|
514
|
+
getBlockFromChain({ chain, number: "finalized" })
|
|
515
515
|
//
|
|
516
|
-
getBlockFromChain({ chain: head.chain, number: head.number })
|
|
517
516
|
]);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (
|
|
524
|
-
|
|
517
|
+
const cache = {
|
|
518
|
+
chain_finalized_height: hexToNumber(block.eth_getBlockByNumber.number),
|
|
519
|
+
chain_finalized_block_hash: block.eth_getBlockByNumber.hash,
|
|
520
|
+
chain_finalized_parent_hash: block.eth_getBlockByNumber.parentHash
|
|
521
|
+
};
|
|
522
|
+
if (cached.chain_finalized_height >= cache.chain_finalized_height) {
|
|
523
|
+
return;
|
|
525
524
|
}
|
|
526
|
-
await
|
|
525
|
+
await opts.metadataStorage.adapter.put(path, JSON.stringify(cache));
|
|
527
526
|
};
|
|
528
|
-
async function
|
|
529
|
-
|
|
530
|
-
|
|
527
|
+
async function getChainCache(chain) {
|
|
528
|
+
const path = `cache/v1/${normalizeHex(chain)}`;
|
|
529
|
+
const cacheRes = await opts.metadataStorage.adapter.get(path);
|
|
530
|
+
if (cacheRes === null) {
|
|
531
|
+
const block = await getBlockFromChain({ chain, number: "finalized" });
|
|
532
|
+
const cache2 = {
|
|
533
|
+
chain_finalized_height: hexToNumber(block.eth_getBlockByNumber.number),
|
|
534
|
+
chain_finalized_block_hash: block.eth_getBlockByNumber.hash,
|
|
535
|
+
chain_finalized_parent_hash: block.eth_getBlockByNumber.parentHash
|
|
536
|
+
};
|
|
537
|
+
await opts.metadataStorage.adapter.put(path, JSON.stringify(cache2));
|
|
538
|
+
return cache2;
|
|
531
539
|
}
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
535
|
-
const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
|
|
536
|
-
const prefix = `commits/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
537
|
-
const promises = actions.map(async (action2) => {
|
|
538
|
-
const events = action2.event.filters.some((filter) => matchFilter(block, filter)) ? action2.event.handler(block) : [];
|
|
539
|
-
const promises2 = events.map(async (event2) => {
|
|
540
|
-
await retry(() => action2.handler(event2), 2).catch((error) => {
|
|
541
|
-
log.error(`Failed to execute action ${action2.id}`);
|
|
542
|
-
throw error;
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
const results = await Promise.allSettled(promises2);
|
|
546
|
-
const failure = results.find((result) => result.status === "rejected");
|
|
547
|
-
if (failure !== void 0) {
|
|
548
|
-
throw failure.reason;
|
|
549
|
-
}
|
|
550
|
-
const commitsKey = `${prefix}/action/${action2.id}`;
|
|
551
|
-
const commitsValue = JSON.stringify({ hello: "world" });
|
|
552
|
-
await opts.metadataStorage.adapter.put(commitsKey, commitsValue);
|
|
553
|
-
});
|
|
554
|
-
await Promise.all(promises);
|
|
540
|
+
const cache = JSON.parse(decoder.decode(cacheRes.body));
|
|
541
|
+
return cache;
|
|
555
542
|
}
|
|
556
543
|
const public_writeFinalizedHead = async (head) => {
|
|
557
544
|
log.debug("Received finalized head...");
|
|
@@ -559,24 +546,59 @@ function defineIndexer(opts) {
|
|
|
559
546
|
return;
|
|
560
547
|
}
|
|
561
548
|
const blocksStart = Date.now();
|
|
562
|
-
const [
|
|
563
|
-
|
|
549
|
+
const [cache, blockFromMetadata, manifest, heads] = await Promise.all([
|
|
550
|
+
getChainCache(head.chain),
|
|
551
|
+
//
|
|
552
|
+
getBlockFromMetadata(head),
|
|
564
553
|
getOrInitManifest(head.chain),
|
|
565
|
-
|
|
554
|
+
listUnfinalizedHeads(head.chain)
|
|
566
555
|
]);
|
|
567
|
-
|
|
568
|
-
const
|
|
556
|
+
const receivedHeight = hexToNumber(head.number);
|
|
557
|
+
const chainFinalizedHeight = cache.chain_finalized_height;
|
|
569
558
|
const indexerFinalizedHeight = manifest.finalized_block_height;
|
|
570
|
-
const chainFinalizedHeight = viem.hexToNumber(chainFinalizedBlock.eth_getBlockByNumber.number);
|
|
571
559
|
if (receivedHeight <= indexerFinalizedHeight) {
|
|
572
560
|
return log.debug(`Received finalized head (${receivedHeight}) below indexer height (${indexerFinalizedHeight})`);
|
|
573
561
|
}
|
|
574
562
|
if (receivedHeight > chainFinalizedHeight) {
|
|
575
|
-
return log.error(`Received finalized head (${receivedHeight}) that has not finalized (${chainFinalizedHeight})`);
|
|
563
|
+
return log.error(`Received finalized head (${receivedHeight}) that has not actually finalized (${chainFinalizedHeight})`);
|
|
576
564
|
}
|
|
565
|
+
log.debug(`Loaded block in ${Date.now() - blocksStart}ms`);
|
|
566
|
+
const canonical = iife(() => {
|
|
567
|
+
let matchesHead = false;
|
|
568
|
+
let canonicalHead = {
|
|
569
|
+
chain: head.chain,
|
|
570
|
+
hash: cache.chain_finalized_block_hash,
|
|
571
|
+
parent_hash: cache.chain_finalized_parent_hash,
|
|
572
|
+
number: numberToHex(cache.chain_finalized_height)
|
|
573
|
+
};
|
|
574
|
+
const receivedHeight2 = hexToNumber(head.number);
|
|
575
|
+
for (let number2 = cache.chain_finalized_height; number2 > manifest.finalized_block_height; number2--) {
|
|
576
|
+
if (number2 === receivedHeight2) {
|
|
577
|
+
matchesHead = isHexEqual(head.hash, canonicalHead.hash) && isHexEqual(head.parent_hash, canonicalHead.parent_hash);
|
|
578
|
+
if (!matchesHead) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (number2 === manifest.finalized_block_height + 1) {
|
|
583
|
+
return matchesHead && isHexEqual(canonicalHead.parent_hash, manifest.finalized_block_hash);
|
|
584
|
+
}
|
|
585
|
+
const parent = heads.get(`${number2 - 1}/${normalizeHex(canonicalHead.parent_hash)}`);
|
|
586
|
+
if (parent === void 0) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
canonicalHead = parent;
|
|
590
|
+
}
|
|
591
|
+
return false;
|
|
592
|
+
});
|
|
593
|
+
const block = await iife(async () => {
|
|
594
|
+
if (canonical === false || blockFromMetadata === null) {
|
|
595
|
+
return await getBlockFromChain(head);
|
|
596
|
+
}
|
|
597
|
+
return blockFromMetadata;
|
|
598
|
+
});
|
|
577
599
|
const chain = normalizeHex(block.eth_chainId);
|
|
578
|
-
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
579
600
|
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
601
|
+
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
580
602
|
const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
|
|
581
603
|
const finalizedKey = `finalized/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
582
604
|
const finalizedValue = JSON.stringify({ hello: "world" });
|
|
@@ -592,68 +614,62 @@ function defineIndexer(opts) {
|
|
|
592
614
|
}
|
|
593
615
|
await writeFinalizedBlock(block, allActions);
|
|
594
616
|
};
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
async function getBlocksProcessedAndGarbageCollect(chain, finalizedHeight) {
|
|
598
|
-
while (true) {
|
|
599
|
-
const blocksKey = `blocks/v1/${normalizeHex(chain)}/`;
|
|
600
|
-
const blocks = await opts.metadataStorage.adapter.list({ prefix: blocksKey, limit: 1e3 });
|
|
601
|
-
if (blocks.keys.length === 0) {
|
|
602
|
-
return [];
|
|
603
|
-
}
|
|
604
|
-
const garbageCollectionKeys = blocks.keys.filter((key) => {
|
|
605
|
-
const [_, __, ___, number] = key.split("/");
|
|
606
|
-
return viem.hexToNumber(number) <= finalizedHeight;
|
|
607
|
-
});
|
|
608
|
-
if (garbageCollectionKeys.length === 0) {
|
|
609
|
-
return blocks.keys.map((key) => {
|
|
610
|
-
const [_, __, ___, number, hash, parent_hash] = key.split("/");
|
|
611
|
-
return { chain, number, hash, parent_hash };
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
const garbageCollectionPromises = garbageCollectionKeys.map(async (key) => {
|
|
615
|
-
await opts.metadataStorage.adapter.delete(key);
|
|
616
|
-
});
|
|
617
|
-
await Promise.all(garbageCollectionPromises);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
async function getCommitsAndGarbageCollect(chain, finalizedHeight) {
|
|
621
|
-
while (true) {
|
|
622
|
-
const commitsKey = `commits/v1/${normalizeHex(chain)}/`;
|
|
623
|
-
const commits = await opts.metadataStorage.adapter.list({ prefix: commitsKey, limit: 1e3 });
|
|
624
|
-
if (commits.keys.length === 0) {
|
|
625
|
-
return [];
|
|
626
|
-
}
|
|
627
|
-
const garbageCollectionKeys = commits.keys.filter((key) => {
|
|
628
|
-
const [_, __, ___, number] = key.split("/");
|
|
629
|
-
return viem.hexToNumber(number) <= finalizedHeight;
|
|
630
|
-
});
|
|
631
|
-
if (garbageCollectionKeys.length === 0) {
|
|
632
|
-
return commits.keys.map((key) => {
|
|
633
|
-
const [_, __, ___, number, hash, parent_hash, type, id] = key.split("/");
|
|
634
|
-
return { chain, number, hash, parent_hash, type, id };
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
const garbageCollectionPromises = garbageCollectionKeys.map(async (key) => {
|
|
638
|
-
await opts.metadataStorage.adapter.delete(key);
|
|
639
|
-
});
|
|
640
|
-
await Promise.all(garbageCollectionPromises);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
async function getBlockFromMetadataOrChain(head) {
|
|
617
|
+
async function getBlockFromMetadata(head) {
|
|
618
|
+
const hash = normalizeHex(head.hash);
|
|
644
619
|
const chain = normalizeHex(head.chain);
|
|
645
620
|
const number = normalizeHex(head.number, 16);
|
|
646
|
-
const hash = normalizeHex(head.hash);
|
|
647
621
|
const parentHash = normalizeHex(head.parent_hash);
|
|
648
622
|
const prefix = `blocks/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
649
623
|
const object = await opts.metadataStorage.adapter.get(prefix);
|
|
650
|
-
if (object
|
|
651
|
-
|
|
652
|
-
const parsed = JSON.parse(block);
|
|
653
|
-
return parsed;
|
|
624
|
+
if (object === null) {
|
|
625
|
+
return null;
|
|
654
626
|
}
|
|
655
|
-
|
|
627
|
+
const block = await decompress(object.body);
|
|
628
|
+
const parsed = JSON.parse(block);
|
|
629
|
+
return parsed;
|
|
656
630
|
}
|
|
631
|
+
async function listUnfinalizedHeads(chain) {
|
|
632
|
+
const blocks = await opts.metadataStorage.adapter.list({
|
|
633
|
+
prefix: `blocks/v1/${normalizeHex(chain)}/`,
|
|
634
|
+
limit: 1e3
|
|
635
|
+
});
|
|
636
|
+
const heads = /* @__PURE__ */ new Map();
|
|
637
|
+
for (const key of blocks.keys) {
|
|
638
|
+
const [_, __, chain2, number, hash, parent_hash] = key.split("/");
|
|
639
|
+
heads.set(`${hexToNumber(number)}/${normalizeHex(hash)}`, { chain: chain2, number, hash, parent_hash });
|
|
640
|
+
}
|
|
641
|
+
return heads;
|
|
642
|
+
}
|
|
643
|
+
async function writeFinalizedBlock(block, actions) {
|
|
644
|
+
if (actions.length === 0) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const chain = normalizeHex(block.eth_chainId);
|
|
648
|
+
const number = normalizeHex(block.eth_getBlockByNumber.number, 16);
|
|
649
|
+
const hash = normalizeHex(block.eth_getBlockByNumber.hash);
|
|
650
|
+
const parentHash = normalizeHex(block.eth_getBlockByNumber.parentHash);
|
|
651
|
+
const prefix = `commits/v1/${chain}/${number}/${hash}/${parentHash}`;
|
|
652
|
+
const promises = actions.map(async (action) => {
|
|
653
|
+
const events = action.event.filters.some((filter) => matchFilter(block, filter)) ? action.event.handler(block) : [];
|
|
654
|
+
const promises2 = events.map(async (event) => {
|
|
655
|
+
await retry(() => action.handler(event), 2).catch((error) => {
|
|
656
|
+
log.error(`Failed to execute action ${action.id}`);
|
|
657
|
+
throw error;
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
const results = await Promise.allSettled(promises2);
|
|
661
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
662
|
+
if (failure !== void 0) {
|
|
663
|
+
throw failure.reason;
|
|
664
|
+
}
|
|
665
|
+
const commitsKey = `${prefix}/action/${action.id}`;
|
|
666
|
+
const commitsValue = JSON.stringify({ hello: "world" });
|
|
667
|
+
await opts.metadataStorage.adapter.put(commitsKey, commitsValue);
|
|
668
|
+
});
|
|
669
|
+
await Promise.all(promises);
|
|
670
|
+
}
|
|
671
|
+
const FINALIZATION_BATCH_SIZE = 32;
|
|
672
|
+
const LEASE_DURATION_MS = 60 * 1e3;
|
|
657
673
|
const public_finalize = async (chain) => {
|
|
658
674
|
const manifestKey = `manifest/v1/${normalizeHex(chain)}`;
|
|
659
675
|
const [chainFinalizedBlock, manifestGetRes] = await Promise.all([
|
|
@@ -661,7 +677,7 @@ function defineIndexer(opts) {
|
|
|
661
677
|
opts.metadataStorage.adapter.get(manifestKey)
|
|
662
678
|
//
|
|
663
679
|
]);
|
|
664
|
-
const chainFinalizedHeight =
|
|
680
|
+
const chainFinalizedHeight = hexToNumber(chainFinalizedBlock.eth_getBlockByNumber.number);
|
|
665
681
|
if (manifestGetRes === null) {
|
|
666
682
|
log.debug("No manifest file found");
|
|
667
683
|
const manifest2 = {
|
|
@@ -708,7 +724,7 @@ function defineIndexer(opts) {
|
|
|
708
724
|
const nextFinalizedHeight = Math.min(chainFinalizedHeight, indexerFinalizedBlockHeight + FINALIZATION_BATCH_SIZE);
|
|
709
725
|
const finalizationBatchSize = nextFinalizedHeight - indexerFinalizedBlockHeight;
|
|
710
726
|
const [nextFinalizedBlock, blocksProcessed] = await Promise.all([
|
|
711
|
-
getBlockFromChain({ chain, number:
|
|
727
|
+
getBlockFromChain({ chain, number: numberToHex(nextFinalizedHeight) }),
|
|
712
728
|
getBlocksProcessedAndGarbageCollect(chain, indexerFinalizedBlockHeight)
|
|
713
729
|
]);
|
|
714
730
|
const nextFinalizedHead = {
|
|
@@ -718,7 +734,7 @@ function defineIndexer(opts) {
|
|
|
718
734
|
parent_hash: nextFinalizedBlock.eth_getBlockByNumber.parentHash
|
|
719
735
|
};
|
|
720
736
|
const nextFinalizedBlockProcessed = blocksProcessed.some((block) => {
|
|
721
|
-
return isHexEqual(block.hash, nextFinalizedHead.hash) && isHexEqual(block.parent_hash, nextFinalizedHead.parent_hash) &&
|
|
737
|
+
return isHexEqual(block.hash, nextFinalizedHead.hash) && isHexEqual(block.parent_hash, nextFinalizedHead.parent_hash) && hexToNumber(block.number) === hexToNumber(nextFinalizedHead.number);
|
|
722
738
|
});
|
|
723
739
|
if (nextFinalizedBlockProcessed === false) {
|
|
724
740
|
await Promise.all([
|
|
@@ -733,7 +749,7 @@ function defineIndexer(opts) {
|
|
|
733
749
|
for (let index = 1; index < finalizationBatchSize; index++) {
|
|
734
750
|
const number = nextFinalizedHeight - index;
|
|
735
751
|
const blocksProcessedForHeight = blocksProcessed.filter((block) => {
|
|
736
|
-
return
|
|
752
|
+
return hexToNumber(block.number) === number;
|
|
737
753
|
});
|
|
738
754
|
const canonicalHead = blocksProcessedForHeight.find((block) => {
|
|
739
755
|
return isHexEqual(block.hash, parentHash);
|
|
@@ -744,7 +760,7 @@ function defineIndexer(opts) {
|
|
|
744
760
|
continue;
|
|
745
761
|
}
|
|
746
762
|
log.debug("Canonical head never processed, loading from chain...");
|
|
747
|
-
const canonicalBlock = await getBlockFromChain({ chain, number:
|
|
763
|
+
const canonicalBlock = await getBlockFromChain({ chain, number: numberToHex(number) });
|
|
748
764
|
const head = {
|
|
749
765
|
chain,
|
|
750
766
|
hash: canonicalBlock.eth_getBlockByNumber.hash,
|
|
@@ -771,17 +787,17 @@ function defineIndexer(opts) {
|
|
|
771
787
|
const commits = await getCommitsAndGarbageCollect(chain, indexerFinalizedBlockHeight);
|
|
772
788
|
for (const canonicalHead of canonicalHeads) {
|
|
773
789
|
const blocksProcessedForHeight = blocksProcessed.filter((head) => {
|
|
774
|
-
return
|
|
790
|
+
return hexToNumber(canonicalHead.number) === hexToNumber(head.number);
|
|
775
791
|
});
|
|
776
792
|
const processedOnlyCanonicalBlock = blocksProcessedForHeight.every((head) => {
|
|
777
793
|
return isHexEqual(canonicalHead.hash, head.hash) && isHexEqual(canonicalHead.parent_hash, head.parent_hash);
|
|
778
794
|
});
|
|
779
795
|
const eventsCommittedForHeight = commits.some((commit) => {
|
|
780
|
-
return commit.type === void 0 && isHexEqual(canonicalHead.hash, commit.hash) && isHexEqual(canonicalHead.parent_hash, commit.parent_hash) &&
|
|
796
|
+
return commit.type === void 0 && isHexEqual(canonicalHead.hash, commit.hash) && isHexEqual(canonicalHead.parent_hash, commit.parent_hash) && hexToNumber(canonicalHead.number) === hexToNumber(commit.number);
|
|
781
797
|
});
|
|
782
|
-
const actionsWithoutCommit = allActions.filter((
|
|
798
|
+
const actionsWithoutCommit = allActions.filter((action) => {
|
|
783
799
|
const commitExists = commits.some((commit) => {
|
|
784
|
-
return commit.id ===
|
|
800
|
+
return commit.id === action.id && commit.type === "action" && isHexEqual(canonicalHead.hash, commit.hash) && isHexEqual(canonicalHead.parent_hash, commit.parent_hash) && hexToNumber(canonicalHead.number) === hexToNumber(commit.number);
|
|
785
801
|
});
|
|
786
802
|
return !commitExists;
|
|
787
803
|
});
|
|
@@ -837,6 +853,59 @@ function defineIndexer(opts) {
|
|
|
837
853
|
}
|
|
838
854
|
log.debug("Indexer finalized, returning...");
|
|
839
855
|
};
|
|
856
|
+
async function getBlocksProcessedAndGarbageCollect(chain, finalizedHeight) {
|
|
857
|
+
while (true) {
|
|
858
|
+
const blocksKey = `blocks/v1/${normalizeHex(chain)}/`;
|
|
859
|
+
const blocks = await opts.metadataStorage.adapter.list({ prefix: blocksKey, limit: 1e3 });
|
|
860
|
+
if (blocks.keys.length === 0) {
|
|
861
|
+
return [];
|
|
862
|
+
}
|
|
863
|
+
const garbageCollectionKeys = blocks.keys.filter((key) => {
|
|
864
|
+
const [_, __, ___, number] = key.split("/");
|
|
865
|
+
return hexToNumber(number) <= finalizedHeight;
|
|
866
|
+
});
|
|
867
|
+
if (garbageCollectionKeys.length === 0) {
|
|
868
|
+
return blocks.keys.map((key) => {
|
|
869
|
+
const [_, __, ___, number, hash, parent_hash] = key.split("/");
|
|
870
|
+
return { chain, number, hash, parent_hash };
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
const garbageCollectionPromises = garbageCollectionKeys.map(async (key) => {
|
|
874
|
+
await opts.metadataStorage.adapter.delete(key);
|
|
875
|
+
});
|
|
876
|
+
await Promise.all(garbageCollectionPromises);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
async function getCommitsAndGarbageCollect(chain, finalizedHeight) {
|
|
880
|
+
while (true) {
|
|
881
|
+
const commitsKey = `commits/v1/${normalizeHex(chain)}/`;
|
|
882
|
+
const commits = await opts.metadataStorage.adapter.list({ prefix: commitsKey, limit: 1e3 });
|
|
883
|
+
if (commits.keys.length === 0) {
|
|
884
|
+
return [];
|
|
885
|
+
}
|
|
886
|
+
const garbageCollectionKeys = commits.keys.filter((key) => {
|
|
887
|
+
const [_, __, ___, number] = key.split("/");
|
|
888
|
+
return hexToNumber(number) <= finalizedHeight;
|
|
889
|
+
});
|
|
890
|
+
if (garbageCollectionKeys.length === 0) {
|
|
891
|
+
return commits.keys.map((key) => {
|
|
892
|
+
const [_, __, ___, number, hash, parent_hash, type, id] = key.split("/");
|
|
893
|
+
return { chain, number, hash, parent_hash, type, id };
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
const garbageCollectionPromises = garbageCollectionKeys.map(async (key) => {
|
|
897
|
+
await opts.metadataStorage.adapter.delete(key);
|
|
898
|
+
});
|
|
899
|
+
await Promise.all(garbageCollectionPromises);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async function getBlockFromMetadataOrChain(head) {
|
|
903
|
+
const metadata = await getBlockFromMetadata(head);
|
|
904
|
+
if (metadata === null) {
|
|
905
|
+
return await getBlockFromChain(head);
|
|
906
|
+
}
|
|
907
|
+
return metadata;
|
|
908
|
+
}
|
|
840
909
|
const private_getMetadata = async () => {
|
|
841
910
|
return {
|
|
842
911
|
version,
|
|
@@ -844,8 +913,8 @@ function defineIndexer(opts) {
|
|
|
844
913
|
};
|
|
845
914
|
};
|
|
846
915
|
const private_getEvents = async () => {
|
|
847
|
-
return allEvents.map((
|
|
848
|
-
const filters =
|
|
916
|
+
return allEvents.map((event) => {
|
|
917
|
+
const filters = event.filters.map((filter) => {
|
|
849
918
|
return {
|
|
850
919
|
chain: filter.chain,
|
|
851
920
|
event: filter.event,
|
|
@@ -854,7 +923,7 @@ function defineIndexer(opts) {
|
|
|
854
923
|
from_block: filter.fromBlock
|
|
855
924
|
};
|
|
856
925
|
});
|
|
857
|
-
return { id:
|
|
926
|
+
return { id: event.id, filters };
|
|
858
927
|
});
|
|
859
928
|
};
|
|
860
929
|
const ignored = [
|
|
@@ -900,26 +969,40 @@ function defineIndexer(opts) {
|
|
|
900
969
|
};
|
|
901
970
|
const IncompleteBlockError = createException("Received block with missing required property");
|
|
902
971
|
const private_writeEvents = async (params) => {
|
|
903
|
-
if (allEvents.length === 0)
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
972
|
+
if (allEvents.length === 0) {
|
|
973
|
+
return { failures: [] };
|
|
974
|
+
}
|
|
975
|
+
const relevantEvents = allEvents.filter((event) => params.events.includes(event.id));
|
|
976
|
+
if (relevantEvents.length === 0) {
|
|
977
|
+
return { failures: [] };
|
|
978
|
+
}
|
|
979
|
+
if (params.events.length === 0) {
|
|
980
|
+
return { failures: [] };
|
|
981
|
+
}
|
|
907
982
|
const failures = {};
|
|
908
|
-
let
|
|
909
|
-
const
|
|
910
|
-
|
|
983
|
+
let accessedUndefinedKey = false;
|
|
984
|
+
const proxiedBlocks = getWriteEventsProxy(params.blocks, () => {
|
|
985
|
+
accessedUndefinedKey = true;
|
|
911
986
|
throw new Error(IncompleteBlockError);
|
|
912
987
|
});
|
|
913
988
|
const promises = eventsGroupedByStorageMap.entries().map(async ([storage, grouped_events]) => {
|
|
914
989
|
const batch = [];
|
|
915
|
-
for (const
|
|
916
|
-
if (!params.events.includes(
|
|
917
|
-
|
|
990
|
+
for (const event of grouped_events) {
|
|
991
|
+
if (!params.events.includes(event.id)) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
for (const block of proxiedBlocks) {
|
|
918
995
|
try {
|
|
919
|
-
if (!
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
996
|
+
if (!event.filters.some((filter) => matchFilter(block, filter))) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const events = event.handler(block);
|
|
1000
|
+
if (accessedUndefinedKey) {
|
|
1001
|
+
throw new Error(IncompleteBlockError);
|
|
1002
|
+
}
|
|
1003
|
+
for (const event2 of events) {
|
|
1004
|
+
batch.push(event2);
|
|
1005
|
+
}
|
|
923
1006
|
} catch (error) {
|
|
924
1007
|
const status = catchException(error, IncompleteBlockError) ? "incomplete_error" : "handler_error";
|
|
925
1008
|
if (status === "handler_error") {
|
|
@@ -927,9 +1010,9 @@ function defineIndexer(opts) {
|
|
|
927
1010
|
log.error(error.message);
|
|
928
1011
|
}
|
|
929
1012
|
}
|
|
930
|
-
failures[
|
|
1013
|
+
failures[event.id + block.eth_getBlockByNumber.number] ??= {
|
|
931
1014
|
status,
|
|
932
|
-
event_id:
|
|
1015
|
+
event_id: event.id,
|
|
933
1016
|
chain: block.eth_chainId,
|
|
934
1017
|
hash: block.eth_getBlockByNumber.hash,
|
|
935
1018
|
number: block.eth_getBlockByNumber.number,
|
|
@@ -939,11 +1022,15 @@ function defineIndexer(opts) {
|
|
|
939
1022
|
}
|
|
940
1023
|
}
|
|
941
1024
|
}
|
|
942
|
-
if (batch.length === 0)
|
|
1025
|
+
if (batch.length === 0) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
943
1028
|
const start = Date.now();
|
|
944
1029
|
try {
|
|
945
1030
|
await retry(() => storage.upsert(batch), 2);
|
|
946
|
-
if (
|
|
1031
|
+
if (accessedUndefinedKey) {
|
|
1032
|
+
throw new Error(IncompleteBlockError);
|
|
1033
|
+
}
|
|
947
1034
|
} catch (error) {
|
|
948
1035
|
const status = catchException(error, IncompleteBlockError) ? "incomplete_error" : "upsert_error";
|
|
949
1036
|
if (status === "upsert_error") {
|
|
@@ -951,12 +1038,14 @@ function defineIndexer(opts) {
|
|
|
951
1038
|
log.error(error.message);
|
|
952
1039
|
}
|
|
953
1040
|
}
|
|
954
|
-
for (const
|
|
955
|
-
for (const block of
|
|
956
|
-
if (!
|
|
957
|
-
|
|
1041
|
+
for (const event of relevantEvents) {
|
|
1042
|
+
for (const block of proxiedBlocks) {
|
|
1043
|
+
if (!event.filters.some((filter) => matchFilter(block, filter))) {
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
failures[event.id + block.eth_getBlockByNumber.number] ??= {
|
|
958
1047
|
status,
|
|
959
|
-
event_id:
|
|
1048
|
+
event_id: event.id,
|
|
960
1049
|
chain: block.eth_chainId,
|
|
961
1050
|
hash: block.eth_getBlockByNumber.hash,
|
|
962
1051
|
number: block.eth_getBlockByNumber.number,
|
|
@@ -967,13 +1056,13 @@ function defineIndexer(opts) {
|
|
|
967
1056
|
}
|
|
968
1057
|
}
|
|
969
1058
|
const stop = Date.now() - start;
|
|
970
|
-
for (const
|
|
971
|
-
log.debug(`Recorded ${batch.length} ${
|
|
1059
|
+
for (const event of relevantEvents) {
|
|
1060
|
+
log.debug(`Recorded ${batch.length} ${event.id} in ${stop}ms`);
|
|
972
1061
|
}
|
|
973
1062
|
});
|
|
974
1063
|
await Promise.all(promises);
|
|
975
|
-
const
|
|
976
|
-
return { failures:
|
|
1064
|
+
const failuresArray = Object.values(failures);
|
|
1065
|
+
return { failures: failuresArray };
|
|
977
1066
|
};
|
|
978
1067
|
const private_writeEventsAndGetKeys = async (params) => {
|
|
979
1068
|
if (allEvents.length === 0) {
|
|
@@ -982,7 +1071,7 @@ function defineIndexer(opts) {
|
|
|
982
1071
|
if (params.events.length === 0) {
|
|
983
1072
|
return { results: [], keys: [] };
|
|
984
1073
|
}
|
|
985
|
-
const relevant_events = allEvents.filter((
|
|
1074
|
+
const relevant_events = allEvents.filter((event) => params.events.includes(event.id));
|
|
986
1075
|
if (relevant_events.length === 0) {
|
|
987
1076
|
return { results: [], keys: [] };
|
|
988
1077
|
}
|
|
@@ -1012,20 +1101,20 @@ function defineIndexer(opts) {
|
|
|
1012
1101
|
}
|
|
1013
1102
|
const proxy = new Proxy(block, createProxyHandler(""));
|
|
1014
1103
|
await Promise.all(
|
|
1015
|
-
relevant_events.map(async (
|
|
1016
|
-
if (!
|
|
1104
|
+
relevant_events.map(async (event) => {
|
|
1105
|
+
if (!event.filters.some((filter) => matchFilter(proxy, filter))) {
|
|
1017
1106
|
return;
|
|
1018
1107
|
}
|
|
1019
1108
|
let events = [];
|
|
1020
1109
|
try {
|
|
1021
|
-
events =
|
|
1110
|
+
events = event.handler(proxy);
|
|
1022
1111
|
} catch (error) {
|
|
1023
1112
|
if (error instanceof Error) {
|
|
1024
1113
|
log.error(error.message);
|
|
1025
1114
|
}
|
|
1026
|
-
results[
|
|
1115
|
+
results[event.id + proxy.eth_getBlockByNumber.number] = {
|
|
1027
1116
|
status: "handler_error",
|
|
1028
|
-
event_id:
|
|
1117
|
+
event_id: event.id,
|
|
1029
1118
|
chain: proxy.eth_chainId,
|
|
1030
1119
|
hash: proxy.eth_getBlockByNumber.hash,
|
|
1031
1120
|
number: proxy.eth_getBlockByNumber.number,
|
|
@@ -1035,9 +1124,9 @@ function defineIndexer(opts) {
|
|
|
1035
1124
|
return;
|
|
1036
1125
|
}
|
|
1037
1126
|
if (events.length === 0) {
|
|
1038
|
-
results[
|
|
1127
|
+
results[event.id + proxy.eth_getBlockByNumber.number] = {
|
|
1039
1128
|
status: "ok",
|
|
1040
|
-
event_id:
|
|
1129
|
+
event_id: event.id,
|
|
1041
1130
|
chain: proxy.eth_chainId,
|
|
1042
1131
|
hash: proxy.eth_getBlockByNumber.hash,
|
|
1043
1132
|
number: proxy.eth_getBlockByNumber.number,
|
|
@@ -1048,15 +1137,15 @@ function defineIndexer(opts) {
|
|
|
1048
1137
|
}
|
|
1049
1138
|
try {
|
|
1050
1139
|
const start = Date.now();
|
|
1051
|
-
await
|
|
1052
|
-
log.debug(`Recorded ${events.length} ${
|
|
1140
|
+
await event.storage.upsert(events);
|
|
1141
|
+
log.debug(`Recorded ${events.length} ${event.id} in ${Date.now() - start}ms`);
|
|
1053
1142
|
} catch (error) {
|
|
1054
1143
|
if (error instanceof Error) {
|
|
1055
1144
|
log.error(error.message);
|
|
1056
1145
|
}
|
|
1057
|
-
results[
|
|
1146
|
+
results[event.id + proxy.eth_getBlockByNumber.number] = {
|
|
1058
1147
|
status: "upsert_error",
|
|
1059
|
-
event_id:
|
|
1148
|
+
event_id: event.id,
|
|
1060
1149
|
chain: proxy.eth_chainId,
|
|
1061
1150
|
hash: proxy.eth_getBlockByNumber.hash,
|
|
1062
1151
|
number: proxy.eth_getBlockByNumber.number,
|
|
@@ -1065,9 +1154,9 @@ function defineIndexer(opts) {
|
|
|
1065
1154
|
};
|
|
1066
1155
|
return;
|
|
1067
1156
|
}
|
|
1068
|
-
results[
|
|
1157
|
+
results[event.id + proxy.eth_getBlockByNumber.number] = {
|
|
1069
1158
|
status: "ok",
|
|
1070
|
-
event_id:
|
|
1159
|
+
event_id: event.id,
|
|
1071
1160
|
chain: proxy.eth_chainId,
|
|
1072
1161
|
hash: proxy.eth_getBlockByNumber.hash,
|
|
1073
1162
|
number: proxy.eth_getBlockByNumber.number,
|
|
@@ -1085,52 +1174,20 @@ function defineIndexer(opts) {
|
|
|
1085
1174
|
});
|
|
1086
1175
|
return { results: results_array, keys: filtered_keys };
|
|
1087
1176
|
};
|
|
1088
|
-
const event = (event2) => {
|
|
1089
|
-
if (!/^[A-Za-z0-9_-]+$/.test(event2.id)) {
|
|
1090
|
-
throw new Error(`Invalid event id \`${event2.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
1091
|
-
}
|
|
1092
|
-
allEvents.push(event2);
|
|
1093
|
-
const group = eventsGroupedByStorageMap.get(event2.storage) ?? [];
|
|
1094
|
-
group.push(event2);
|
|
1095
|
-
eventsGroupedByStorageMap.set(event2.storage, group);
|
|
1096
|
-
return event2;
|
|
1097
|
-
};
|
|
1098
|
-
const action = (action2) => {
|
|
1099
|
-
if (!/^[A-Za-z0-9_-]+$/.test(action2.id)) {
|
|
1100
|
-
throw new Error(`Invalid action id \`${action2.id}\`. Only characters A-Z, a-z, 0-9, underscores, and hyphens are permitted.`);
|
|
1101
|
-
}
|
|
1102
|
-
if (allActions.some((existing) => existing.id === action2.id)) {
|
|
1103
|
-
throw new Error(`Duplicate action id \`${action2.id}\`.`);
|
|
1104
|
-
}
|
|
1105
|
-
allActions.push(action2);
|
|
1106
|
-
return action2;
|
|
1107
|
-
};
|
|
1108
1177
|
const rpc = {
|
|
1109
1178
|
request: {
|
|
1110
1179
|
public_finalize,
|
|
1111
1180
|
public_getFinalizedHeight,
|
|
1112
1181
|
public_writeFinalizedHead,
|
|
1182
|
+
public_writeFinalizedHeight,
|
|
1113
1183
|
public_writeUnfinalizedHead,
|
|
1114
1184
|
public_deleteReorganisedHead,
|
|
1115
1185
|
private_getEvents,
|
|
1116
1186
|
private_getMetadata,
|
|
1117
1187
|
private_writeEvents,
|
|
1118
1188
|
private_writeEventsAndGetKeys
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
};
|
|
1122
|
-
const server = createServer({
|
|
1123
|
-
transport: local(rpc),
|
|
1124
|
-
quiet: opts.quiet ?? false,
|
|
1125
|
-
signingKey: opts.signingKey
|
|
1126
|
-
});
|
|
1127
|
-
const indexer = {
|
|
1128
|
-
...rpc,
|
|
1129
|
-
event,
|
|
1130
|
-
action,
|
|
1131
|
-
fetch: server.http
|
|
1132
|
-
};
|
|
1133
|
-
return indexer;
|
|
1189
|
+
}};
|
|
1190
|
+
return local(rpc);
|
|
1134
1191
|
}
|
|
1135
1192
|
|
|
1136
|
-
export { defineIndexer };
|
|
1193
|
+
export { defineAction, defineEvent, defineIndexer };
|