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