@aztec/archiver 0.0.0-test.1 → 0.0.1-commit.03f7ef2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -6
- package/dest/archiver/archiver.d.ts +201 -94
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +1141 -396
- package/dest/archiver/archiver_store.d.ts +171 -83
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +2389 -393
- package/dest/archiver/config.d.ts +7 -22
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +30 -14
- package/dest/archiver/errors.d.ts +33 -1
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +49 -0
- package/dest/archiver/index.d.ts +3 -4
- package/dest/archiver/index.d.ts.map +1 -1
- package/dest/archiver/index.js +1 -2
- package/dest/archiver/instrumentation.d.ts +14 -6
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +69 -17
- package/dest/archiver/kv_archiver_store/block_store.d.ts +91 -21
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +476 -86
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +4 -4
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +13 -19
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +12 -9
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +30 -16
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +80 -75
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +142 -83
- package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +153 -113
- package/dest/archiver/kv_archiver_store/message_store.d.ts +25 -18
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +152 -49
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
- package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
- package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
- package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
- package/dest/archiver/l1/calldata_retriever.js +471 -0
- package/dest/archiver/l1/data_retrieval.d.ts +90 -0
- package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
- package/dest/archiver/l1/data_retrieval.js +331 -0
- package/dest/archiver/l1/debug_tx.d.ts +19 -0
- package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
- package/dest/archiver/l1/debug_tx.js +73 -0
- package/dest/archiver/l1/spire_proposer.d.ts +70 -0
- package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
- package/dest/archiver/l1/spire_proposer.js +157 -0
- package/dest/archiver/l1/trace_tx.d.ts +97 -0
- package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
- package/dest/archiver/l1/trace_tx.js +91 -0
- package/dest/archiver/l1/types.d.ts +12 -0
- package/dest/archiver/l1/types.d.ts.map +1 -0
- package/dest/archiver/l1/types.js +3 -0
- package/dest/archiver/l1/validate_trace.d.ts +29 -0
- package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
- package/dest/archiver/l1/validate_trace.js +150 -0
- package/dest/archiver/structs/data_retrieval.d.ts +1 -1
- package/dest/archiver/structs/inbox_message.d.ts +15 -0
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -0
- package/dest/archiver/structs/inbox_message.js +39 -0
- package/dest/archiver/structs/published.d.ts +2 -11
- package/dest/archiver/structs/published.d.ts.map +1 -1
- package/dest/archiver/structs/published.js +1 -1
- package/dest/archiver/validation.d.ts +17 -0
- package/dest/archiver/validation.d.ts.map +1 -0
- package/dest/archiver/validation.js +98 -0
- package/dest/factory.d.ts +9 -14
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +22 -52
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/rpc/index.d.ts +2 -3
- package/dest/rpc/index.d.ts.map +1 -1
- package/dest/rpc/index.js +1 -4
- package/dest/test/index.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts +16 -8
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +19 -14
- package/dest/test/mock_l1_to_l2_message_source.d.ts +9 -6
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +21 -7
- package/dest/test/mock_l2_block_source.d.ts +52 -13
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +140 -15
- package/dest/test/mock_structs.d.ts +10 -0
- package/dest/test/mock_structs.d.ts.map +1 -0
- package/dest/test/mock_structs.js +38 -0
- package/package.json +29 -30
- package/src/archiver/archiver.ts +1477 -501
- package/src/archiver/archiver_store.ts +197 -88
- package/src/archiver/archiver_store_test_suite.ts +2403 -350
- package/src/archiver/config.ts +38 -46
- package/src/archiver/errors.ts +85 -0
- package/src/archiver/index.ts +2 -3
- package/src/archiver/instrumentation.ts +91 -22
- package/src/archiver/kv_archiver_store/block_store.ts +640 -101
- package/src/archiver/kv_archiver_store/contract_class_store.ts +14 -24
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +36 -28
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +193 -113
- package/src/archiver/kv_archiver_store/log_store.ts +205 -127
- package/src/archiver/kv_archiver_store/message_store.ts +213 -54
- package/src/archiver/l1/README.md +98 -0
- package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
- package/src/archiver/l1/calldata_retriever.ts +641 -0
- package/src/archiver/l1/data_retrieval.ts +512 -0
- package/src/archiver/l1/debug_tx.ts +99 -0
- package/src/archiver/l1/spire_proposer.ts +160 -0
- package/src/archiver/l1/trace_tx.ts +128 -0
- package/src/archiver/l1/types.ts +13 -0
- package/src/archiver/l1/validate_trace.ts +211 -0
- package/src/archiver/structs/inbox_message.ts +41 -0
- package/src/archiver/structs/published.ts +1 -11
- package/src/archiver/validation.ts +124 -0
- package/src/factory.ts +28 -69
- package/src/index.ts +1 -1
- package/src/rpc/index.ts +1 -5
- package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
- package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
- package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
- package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
- package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
- package/src/test/fixtures/trace_transaction-proxied.json +128 -0
- package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
- package/src/test/mock_archiver.ts +22 -16
- package/src/test/mock_l1_to_l2_message_source.ts +20 -8
- package/src/test/mock_l2_block_source.ts +186 -21
- package/src/test/mock_structs.ts +50 -0
- package/dest/archiver/data_retrieval.d.ts +0 -74
- package/dest/archiver/data_retrieval.d.ts.map +0 -1
- package/dest/archiver/data_retrieval.js +0 -283
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +0 -12
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/nullifier_store.js +0 -73
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +0 -23
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +0 -49
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +0 -175
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +0 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +0 -636
- package/src/archiver/data_retrieval.ts +0 -422
- package/src/archiver/kv_archiver_store/nullifier_store.ts +0 -97
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +0 -61
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +0 -801
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import { INITIAL_L2_BLOCK_NUM, MAX_NOTE_HASHES_PER_TX
|
|
1
|
+
import { INITIAL_L2_BLOCK_NUM, MAX_NOTE_HASHES_PER_TX } from '@aztec/constants';
|
|
2
|
+
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
5
|
import { BufferReader, numToUInt32BE } from '@aztec/foundation/serialize';
|
|
4
|
-
import {
|
|
6
|
+
import { L2BlockHash } from '@aztec/stdlib/block';
|
|
7
|
+
import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, PublicLog, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
5
8
|
/**
|
|
6
9
|
* A store for logs
|
|
7
10
|
*/ export class LogStore {
|
|
8
11
|
db;
|
|
9
12
|
blockStore;
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
+
// `tag` --> private logs
|
|
14
|
+
#privateLogsByTag;
|
|
15
|
+
// `{contractAddress}_${tag}` --> public logs
|
|
16
|
+
#publicLogsByContractAndTag;
|
|
17
|
+
#privateLogKeysByBlock;
|
|
18
|
+
#publicLogKeysByBlock;
|
|
13
19
|
#publicLogsByBlock;
|
|
14
20
|
#contractClassLogsByBlock;
|
|
15
21
|
#logsMaxPageSize;
|
|
@@ -18,100 +24,121 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
18
24
|
this.db = db;
|
|
19
25
|
this.blockStore = blockStore;
|
|
20
26
|
this.#log = createLogger('archiver:log_store');
|
|
21
|
-
this.#
|
|
22
|
-
this.#
|
|
23
|
-
this.#
|
|
27
|
+
this.#privateLogsByTag = db.openMap('archiver_private_tagged_logs_by_tag');
|
|
28
|
+
this.#publicLogsByContractAndTag = db.openMap('archiver_public_tagged_logs_by_tag');
|
|
29
|
+
this.#privateLogKeysByBlock = db.openMap('archiver_private_log_keys_by_block');
|
|
30
|
+
this.#publicLogKeysByBlock = db.openMap('archiver_public_log_keys_by_block');
|
|
24
31
|
this.#publicLogsByBlock = db.openMap('archiver_public_logs_by_block');
|
|
25
32
|
this.#contractClassLogsByBlock = db.openMap('archiver_contract_class_logs_by_block');
|
|
26
33
|
this.#logsMaxPageSize = logsMaxPageSize;
|
|
27
34
|
}
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Extracts tagged logs from a single block, grouping them into private and public maps.
|
|
37
|
+
*
|
|
38
|
+
* @param block - The L2 block to extract logs from.
|
|
39
|
+
* @returns An object containing the private and public tagged logs for the block.
|
|
40
|
+
*/ async #extractTaggedLogsFromBlock(block) {
|
|
41
|
+
const blockHash = L2BlockHash.fromField(await block.hash());
|
|
42
|
+
// SiloedTag (as string) -> array of log buffers.
|
|
43
|
+
const privateTaggedLogs = new Map();
|
|
44
|
+
// "{contractAddress}_{tag}" (as string) -> array of log buffers.
|
|
45
|
+
const publicTaggedLogs = new Map();
|
|
30
46
|
const dataStartIndexForBlock = block.header.state.partial.noteHashTree.nextAvailableLeafIndex - block.body.txEffects.length * MAX_NOTE_HASHES_PER_TX;
|
|
31
47
|
block.body.txEffects.forEach((txEffect, txIndex)=>{
|
|
32
48
|
const txHash = txEffect.txHash;
|
|
33
49
|
const dataStartIndexForTx = dataStartIndexForBlock + txIndex * MAX_NOTE_HASHES_PER_TX;
|
|
34
|
-
txEffect.privateLogs.forEach((log)=>{
|
|
50
|
+
txEffect.privateLogs.forEach((log, logIndex)=>{
|
|
51
|
+
// Private logs use SiloedTag (already siloed by kernel)
|
|
35
52
|
const tag = log.fields[0];
|
|
36
|
-
|
|
37
|
-
currentLogs
|
|
38
|
-
|
|
53
|
+
this.#log.debug(`Found private log with tag ${tag.toString()} in block ${block.number}`);
|
|
54
|
+
const currentLogs = privateTaggedLogs.get(tag.toString()) ?? [];
|
|
55
|
+
currentLogs.push(new TxScopedL2Log(txHash, dataStartIndexForTx, logIndex, block.number, blockHash, block.timestamp, log).toBuffer());
|
|
56
|
+
privateTaggedLogs.set(tag.toString(), currentLogs);
|
|
39
57
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
txEffect.publicLogs.forEach((log)=>{
|
|
50
|
-
// Check that each log stores 2 lengths in its first field. If not, it's not a tagged log:
|
|
51
|
-
const firstFieldBuf = log.log[0].toBuffer();
|
|
52
|
-
// See macros/note/mod/ and see how finalization_log[0] is constructed, to understand this monstrosity. (It wasn't me).
|
|
53
|
-
// Search the codebase for "disgusting encoding" to see other hardcoded instances of this encoding, that you might need to change if you ever find yourself here.
|
|
54
|
-
if (!firstFieldBuf.subarray(0, 27).equals(Buffer.alloc(27)) || firstFieldBuf[29] !== 0) {
|
|
55
|
-
// See parseLogFromPublic - the first field of a tagged log is 5 bytes structured:
|
|
56
|
-
// [ publicLen[0], publicLen[1], 0, privateLen[0], privateLen[1]]
|
|
57
|
-
this.#log.warn(`Skipping public log with invalid first field: ${log.log[0]}`);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
// Check that the length values line up with the log contents
|
|
61
|
-
const publicValuesLength = firstFieldBuf.subarray(-5).readUint16BE();
|
|
62
|
-
const privateValuesLength = firstFieldBuf.subarray(-5).readUint16BE(3);
|
|
63
|
-
// Add 1 for the first field holding lengths
|
|
64
|
-
const totalLogLength = 1 + publicValuesLength + privateValuesLength;
|
|
65
|
-
// Note that zeroes can be valid log values, so we can only assert that we do not go over the given length
|
|
66
|
-
if (totalLogLength > PUBLIC_LOG_DATA_SIZE_IN_FIELDS || log.log.slice(totalLogLength).find((f)=>!f.isZero())) {
|
|
67
|
-
this.#log.warn(`Skipping invalid tagged public log with first field: ${log.log[0]}`);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
// The first elt stores lengths as above => tag is in fields[1]
|
|
71
|
-
const tag = log.log[1];
|
|
72
|
-
this.#log.debug(`Found tagged public log with tag ${tag.toString()} in block ${block.number}`);
|
|
73
|
-
const currentLogs = taggedLogs.get(tag.toString()) ?? [];
|
|
74
|
-
currentLogs.push(new TxScopedL2Log(txHash, dataStartIndexForTx, block.number, /* isFromPublic */ true, log.toBuffer()).toBuffer());
|
|
75
|
-
taggedLogs.set(tag.toString(), currentLogs);
|
|
58
|
+
txEffect.publicLogs.forEach((log, logIndex)=>{
|
|
59
|
+
// Public logs use Tag directly (not siloed) and are stored with contract address
|
|
60
|
+
const tag = log.fields[0];
|
|
61
|
+
const contractAddress = log.contractAddress;
|
|
62
|
+
const key = `${contractAddress.toString()}_${tag.toString()}`;
|
|
63
|
+
this.#log.debug(`Found public log with tag ${tag.toString()} from contract ${contractAddress.toString()} in block ${block.number}`);
|
|
64
|
+
const currentLogs = publicTaggedLogs.get(key) ?? [];
|
|
65
|
+
currentLogs.push(new TxScopedL2Log(txHash, dataStartIndexForTx, logIndex, block.number, blockHash, block.timestamp, log).toBuffer());
|
|
66
|
+
publicTaggedLogs.set(key, currentLogs);
|
|
76
67
|
});
|
|
77
68
|
});
|
|
78
|
-
return
|
|
69
|
+
return {
|
|
70
|
+
privateTaggedLogs,
|
|
71
|
+
publicTaggedLogs
|
|
72
|
+
};
|
|
79
73
|
}
|
|
80
74
|
/**
|
|
81
|
-
*
|
|
82
|
-
* @param blocks - The blocks
|
|
83
|
-
* @returns
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for (const [tag, logs] of
|
|
75
|
+
* Extracts and aggregates tagged logs from a list of blocks.
|
|
76
|
+
* @param blocks - The blocks to extract logs from.
|
|
77
|
+
* @returns A map from tag (as string) to an array of serialized private logs belonging to that tag, and a map from
|
|
78
|
+
* "{contractAddress}_{tag}" (as string) to an array of serialized public logs belonging to that key.
|
|
79
|
+
*/ async #extractTaggedLogs(blocks) {
|
|
80
|
+
const taggedLogsInBlocks = await Promise.all(blocks.map((block)=>this.#extractTaggedLogsFromBlock(block)));
|
|
81
|
+
// Now we merge the maps from each block into a single map.
|
|
82
|
+
const privateTaggedLogs = taggedLogsInBlocks.reduce((acc, { privateTaggedLogs })=>{
|
|
83
|
+
for (const [tag, logs] of privateTaggedLogs.entries()){
|
|
90
84
|
const currentLogs = acc.get(tag) ?? [];
|
|
91
85
|
acc.set(tag, currentLogs.concat(logs));
|
|
92
86
|
}
|
|
93
87
|
return acc;
|
|
94
|
-
});
|
|
95
|
-
const
|
|
88
|
+
}, new Map());
|
|
89
|
+
const publicTaggedLogs = taggedLogsInBlocks.reduce((acc, { publicTaggedLogs })=>{
|
|
90
|
+
for (const [key, logs] of publicTaggedLogs.entries()){
|
|
91
|
+
const currentLogs = acc.get(key) ?? [];
|
|
92
|
+
acc.set(key, currentLogs.concat(logs));
|
|
93
|
+
}
|
|
94
|
+
return acc;
|
|
95
|
+
}, new Map());
|
|
96
|
+
return {
|
|
97
|
+
privateTaggedLogs,
|
|
98
|
+
publicTaggedLogs
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Append new logs to the store's list.
|
|
103
|
+
* @param blocks - The blocks for which to add the logs.
|
|
104
|
+
* @returns True if the operation is successful.
|
|
105
|
+
*/ async addLogs(blocks) {
|
|
106
|
+
const { privateTaggedLogs, publicTaggedLogs } = await this.#extractTaggedLogs(blocks);
|
|
107
|
+
const keysOfPrivateLogsToUpdate = Array.from(privateTaggedLogs.keys());
|
|
108
|
+
const keysOfPublicLogsToUpdate = Array.from(publicTaggedLogs.keys());
|
|
96
109
|
return this.db.transactionAsync(async ()=>{
|
|
97
|
-
const
|
|
98
|
-
tag,
|
|
99
|
-
logBuffers: await this.#
|
|
110
|
+
const currentPrivateTaggedLogs = await Promise.all(keysOfPrivateLogsToUpdate.map(async (key)=>({
|
|
111
|
+
tag: key,
|
|
112
|
+
logBuffers: await this.#privateLogsByTag.getAsync(key)
|
|
100
113
|
})));
|
|
101
|
-
|
|
114
|
+
currentPrivateTaggedLogs.forEach((taggedLogBuffer)=>{
|
|
102
115
|
if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
|
|
103
|
-
|
|
116
|
+
privateTaggedLogs.set(taggedLogBuffer.tag, taggedLogBuffer.logBuffers.concat(privateTaggedLogs.get(taggedLogBuffer.tag)));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const currentPublicTaggedLogs = await Promise.all(keysOfPublicLogsToUpdate.map(async (key)=>({
|
|
120
|
+
key,
|
|
121
|
+
logBuffers: await this.#publicLogsByContractAndTag.getAsync(key)
|
|
122
|
+
})));
|
|
123
|
+
currentPublicTaggedLogs.forEach((taggedLogBuffer)=>{
|
|
124
|
+
if (taggedLogBuffer.logBuffers && taggedLogBuffer.logBuffers.length > 0) {
|
|
125
|
+
publicTaggedLogs.set(taggedLogBuffer.key, taggedLogBuffer.logBuffers.concat(publicTaggedLogs.get(taggedLogBuffer.key)));
|
|
104
126
|
}
|
|
105
127
|
});
|
|
106
128
|
for (const block of blocks){
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
const blockHash = await block.hash();
|
|
130
|
+
const privateTagsInBlock = [];
|
|
131
|
+
for (const [tag, logs] of privateTaggedLogs.entries()){
|
|
132
|
+
await this.#privateLogsByTag.set(tag, logs);
|
|
133
|
+
privateTagsInBlock.push(tag);
|
|
134
|
+
}
|
|
135
|
+
await this.#privateLogKeysByBlock.set(block.number, privateTagsInBlock);
|
|
136
|
+
const publicKeysInBlock = [];
|
|
137
|
+
for (const [key, logs] of publicTaggedLogs.entries()){
|
|
138
|
+
await this.#publicLogsByContractAndTag.set(key, logs);
|
|
139
|
+
publicKeysInBlock.push(key);
|
|
111
140
|
}
|
|
112
|
-
await this.#
|
|
113
|
-
const privateLogsInBlock = block.body.txEffects.map((txEffect)=>txEffect.privateLogs).flat().map((log)=>log.toBuffer());
|
|
114
|
-
await this.#privateLogsByBlock.set(block.number, Buffer.concat(privateLogsInBlock));
|
|
141
|
+
await this.#publicLogKeysByBlock.set(block.number, publicKeysInBlock);
|
|
115
142
|
const publicLogsInBlock = block.body.txEffects.map((txEffect, txIndex)=>[
|
|
116
143
|
numToUInt32BE(txIndex),
|
|
117
144
|
numToUInt32BE(txEffect.publicLogs.length),
|
|
@@ -122,53 +149,60 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
122
149
|
numToUInt32BE(txEffect.contractClassLogs.length),
|
|
123
150
|
txEffect.contractClassLogs.map((log)=>log.toBuffer())
|
|
124
151
|
].flat()).flat();
|
|
125
|
-
await this.#publicLogsByBlock.set(block.number,
|
|
126
|
-
await this.#contractClassLogsByBlock.set(block.number,
|
|
152
|
+
await this.#publicLogsByBlock.set(block.number, this.#packWithBlockHash(blockHash, publicLogsInBlock));
|
|
153
|
+
await this.#contractClassLogsByBlock.set(block.number, this.#packWithBlockHash(blockHash, contractClassLogsInBlock));
|
|
127
154
|
}
|
|
128
155
|
return true;
|
|
129
156
|
});
|
|
130
157
|
}
|
|
158
|
+
#packWithBlockHash(blockHash, data) {
|
|
159
|
+
return Buffer.concat([
|
|
160
|
+
blockHash.toBuffer(),
|
|
161
|
+
...data
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
#unpackBlockHash(reader) {
|
|
165
|
+
const blockHash = reader.remainingBytes() > 0 ? reader.readObject(Fr) : undefined;
|
|
166
|
+
if (!blockHash) {
|
|
167
|
+
throw new Error('Failed to read block hash from log entry buffer');
|
|
168
|
+
}
|
|
169
|
+
return L2BlockHash.fromField(blockHash);
|
|
170
|
+
}
|
|
131
171
|
deleteLogs(blocks) {
|
|
132
172
|
return this.db.transactionAsync(async ()=>{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
173
|
+
await Promise.all(blocks.map(async (block)=>{
|
|
174
|
+
// Delete private logs
|
|
175
|
+
const privateKeys = await this.#privateLogKeysByBlock.getAsync(block.number) ?? [];
|
|
176
|
+
await Promise.all(privateKeys.map((tag)=>this.#privateLogsByTag.delete(tag)));
|
|
177
|
+
// Delete public logs
|
|
178
|
+
const publicKeys = await this.#publicLogKeysByBlock.getAsync(block.number) ?? [];
|
|
179
|
+
await Promise.all(publicKeys.map((key)=>this.#publicLogsByContractAndTag.delete(key)));
|
|
180
|
+
}));
|
|
137
181
|
await Promise.all(blocks.map((block)=>Promise.all([
|
|
138
|
-
this.#privateLogsByBlock.delete(block.number),
|
|
139
182
|
this.#publicLogsByBlock.delete(block.number),
|
|
140
|
-
this.#
|
|
183
|
+
this.#privateLogKeysByBlock.delete(block.number),
|
|
184
|
+
this.#publicLogKeysByBlock.delete(block.number),
|
|
185
|
+
this.#contractClassLogsByBlock.delete(block.number)
|
|
141
186
|
])));
|
|
142
|
-
await Promise.all(tagsToDelete.map((tag)=>this.#logsByTag.delete(tag.toString())));
|
|
143
187
|
return true;
|
|
144
188
|
});
|
|
145
189
|
}
|
|
146
190
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const logs = [];
|
|
153
|
-
for await (const buffer of this.#privateLogsByBlock.valuesAsync({
|
|
154
|
-
start,
|
|
155
|
-
limit
|
|
156
|
-
})){
|
|
157
|
-
const reader = new BufferReader(buffer);
|
|
158
|
-
while(reader.remainingBytes() > 0){
|
|
159
|
-
logs.push(reader.readObject(PrivateLog));
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return logs;
|
|
191
|
+
* Gets all private logs that match any of the `tags`. For each tag, an array of matching logs is returned. An empty
|
|
192
|
+
* array implies no logs match that tag.
|
|
193
|
+
*/ async getPrivateLogsByTags(tags) {
|
|
194
|
+
const logs = await Promise.all(tags.map((tag)=>this.#privateLogsByTag.getAsync(tag.toString())));
|
|
195
|
+
return logs.map((logBuffers)=>logBuffers?.map((logBuffer)=>TxScopedL2Log.fromBuffer(logBuffer)) ?? []);
|
|
163
196
|
}
|
|
164
197
|
/**
|
|
165
|
-
* Gets all logs that match any of the
|
|
166
|
-
*
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
* Gets all public logs that match any of the `tags` from the specified contract. For each tag, an array of matching
|
|
199
|
+
* logs is returned. An empty array implies no logs match that tag.
|
|
200
|
+
*/ async getPublicLogsByTagsFromContract(contractAddress, tags) {
|
|
201
|
+
const logs = await Promise.all(tags.map((tag)=>{
|
|
202
|
+
const key = `${contractAddress.toString()}_${tag.value.toString()}`;
|
|
203
|
+
return this.#publicLogsByContractAndTag.getAsync(key);
|
|
204
|
+
}));
|
|
205
|
+
return logs.map((logBuffers)=>logBuffers?.map((logBuffer)=>TxScopedL2Log.fromBuffer(logBuffer)) ?? []);
|
|
172
206
|
}
|
|
173
207
|
/**
|
|
174
208
|
* Gets public logs based on the provided filter.
|
|
@@ -199,6 +233,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
199
233
|
[]
|
|
200
234
|
];
|
|
201
235
|
const reader = new BufferReader(buffer);
|
|
236
|
+
const blockHash = this.#unpackBlockHash(reader);
|
|
202
237
|
while(reader.remainingBytes() > 0){
|
|
203
238
|
const indexOfTx = reader.readNumber();
|
|
204
239
|
const numLogsInTx = reader.readNumber();
|
|
@@ -209,7 +244,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
209
244
|
}
|
|
210
245
|
const txLogs = publicLogsInBlock[txIndex];
|
|
211
246
|
const logs = [];
|
|
212
|
-
const maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
|
|
247
|
+
const maxLogsHit = this.#accumulateLogs(logs, blockNumber, blockHash, txIndex, txLogs, filter);
|
|
213
248
|
return {
|
|
214
249
|
logs,
|
|
215
250
|
maxLogsHit
|
|
@@ -234,6 +269,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
234
269
|
[]
|
|
235
270
|
];
|
|
236
271
|
const reader = new BufferReader(logBuffer);
|
|
272
|
+
const blockHash = this.#unpackBlockHash(reader);
|
|
237
273
|
while(reader.remainingBytes() > 0){
|
|
238
274
|
const indexOfTx = reader.readNumber();
|
|
239
275
|
const numLogsInTx = reader.readNumber();
|
|
@@ -244,7 +280,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
244
280
|
}
|
|
245
281
|
for(let txIndex = filter.afterLog?.txIndex ?? 0; txIndex < publicLogsInBlock.length; txIndex++){
|
|
246
282
|
const txLogs = publicLogsInBlock[txIndex];
|
|
247
|
-
maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
|
|
283
|
+
maxLogsHit = this.#accumulateLogs(logs, blockNumber, blockHash, txIndex, txLogs, filter);
|
|
248
284
|
if (maxLogsHit) {
|
|
249
285
|
this.#log.debug(`Max logs hit at block ${blockNumber}`);
|
|
250
286
|
break loopOverBlocks;
|
|
@@ -285,6 +321,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
285
321
|
[]
|
|
286
322
|
];
|
|
287
323
|
const reader = new BufferReader(contractClassLogsBuffer);
|
|
324
|
+
const blockHash = this.#unpackBlockHash(reader);
|
|
288
325
|
while(reader.remainingBytes() > 0){
|
|
289
326
|
const indexOfTx = reader.readNumber();
|
|
290
327
|
const numLogsInTx = reader.readNumber();
|
|
@@ -295,7 +332,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
295
332
|
}
|
|
296
333
|
const txLogs = contractClassLogsInBlock[txIndex];
|
|
297
334
|
const logs = [];
|
|
298
|
-
const maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
|
|
335
|
+
const maxLogsHit = this.#accumulateLogs(logs, blockNumber, blockHash, txIndex, txLogs, filter);
|
|
299
336
|
return {
|
|
300
337
|
logs,
|
|
301
338
|
maxLogsHit
|
|
@@ -320,6 +357,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
320
357
|
[]
|
|
321
358
|
];
|
|
322
359
|
const reader = new BufferReader(logBuffer);
|
|
360
|
+
const blockHash = this.#unpackBlockHash(reader);
|
|
323
361
|
while(reader.remainingBytes() > 0){
|
|
324
362
|
const indexOfTx = reader.readNumber();
|
|
325
363
|
const numLogsInTx = reader.readNumber();
|
|
@@ -330,7 +368,7 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
330
368
|
}
|
|
331
369
|
for(let txIndex = filter.afterLog?.txIndex ?? 0; txIndex < contractClassLogsInBlock.length; txIndex++){
|
|
332
370
|
const txLogs = contractClassLogsInBlock[txIndex];
|
|
333
|
-
maxLogsHit = this.#accumulateLogs(logs, blockNumber, txIndex, txLogs, filter);
|
|
371
|
+
maxLogsHit = this.#accumulateLogs(logs, blockNumber, blockHash, txIndex, txLogs, filter);
|
|
334
372
|
if (maxLogsHit) {
|
|
335
373
|
this.#log.debug(`Max logs hit at block ${blockNumber}`);
|
|
336
374
|
break loopOverBlocks;
|
|
@@ -342,16 +380,18 @@ import { ContractClassLog, ExtendedContractClassLog, ExtendedPublicLog, LogId, P
|
|
|
342
380
|
maxLogsHit
|
|
343
381
|
};
|
|
344
382
|
}
|
|
345
|
-
#accumulateLogs(results, blockNumber, txIndex, txLogs, filter) {
|
|
383
|
+
#accumulateLogs(results, blockNumber, blockHash, txIndex, txLogs, filter = {}) {
|
|
346
384
|
let maxLogsHit = false;
|
|
347
385
|
let logIndex = typeof filter.afterLog?.logIndex === 'number' ? filter.afterLog.logIndex + 1 : 0;
|
|
348
386
|
for(; logIndex < txLogs.length; logIndex++){
|
|
349
387
|
const log = txLogs[logIndex];
|
|
350
388
|
if (!filter.contractAddress || log.contractAddress.equals(filter.contractAddress)) {
|
|
351
389
|
if (log instanceof ContractClassLog) {
|
|
352
|
-
results.push(new ExtendedContractClassLog(new LogId(blockNumber, txIndex, logIndex), log));
|
|
390
|
+
results.push(new ExtendedContractClassLog(new LogId(BlockNumber(blockNumber), blockHash, txIndex, logIndex), log));
|
|
391
|
+
} else if (log instanceof PublicLog) {
|
|
392
|
+
results.push(new ExtendedPublicLog(new LogId(BlockNumber(blockNumber), blockHash, txIndex, logIndex), log));
|
|
353
393
|
} else {
|
|
354
|
-
|
|
394
|
+
throw new Error('Unknown log type');
|
|
355
395
|
}
|
|
356
396
|
if (results.length >= this.#logsMaxPageSize) {
|
|
357
397
|
maxLogsHit = true;
|
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
2
|
+
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
+
import { type AztecAsyncKVStore, type CustomRange } from '@aztec/kv-store';
|
|
5
|
+
import { type InboxMessage } from '../structs/inbox_message.js';
|
|
6
|
+
export declare class MessageStoreError extends Error {
|
|
7
|
+
readonly inboxMessage: InboxMessage;
|
|
8
|
+
constructor(message: string, inboxMessage: InboxMessage);
|
|
9
|
+
}
|
|
8
10
|
export declare class MessageStore {
|
|
9
11
|
#private;
|
|
10
12
|
private db;
|
|
11
13
|
constructor(db: AztecAsyncKVStore);
|
|
12
14
|
getTotalL1ToL2MessageCount(): Promise<bigint>;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
getSynchedL1BlockNumber(): Promise<bigint | undefined>;
|
|
18
|
-
setSynchedL1BlockNumber(l1BlockNumber: bigint): Promise<void>;
|
|
15
|
+
/** Gets the last L1 block synced. */
|
|
16
|
+
getSynchedL1Block(): Promise<L1BlockId | undefined>;
|
|
17
|
+
/** Sets the last L1 block synced */
|
|
18
|
+
setSynchedL1Block(l1Block: L1BlockId): Promise<void>;
|
|
19
19
|
/**
|
|
20
20
|
* Append L1 to L2 messages to the store.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* Requires new messages to be in order and strictly after the last message added.
|
|
22
|
+
* Throws if out of order messages are added or if the rolling hash is invalid.
|
|
23
23
|
*/
|
|
24
|
-
addL1ToL2Messages(messages:
|
|
24
|
+
addL1ToL2Messages(messages: InboxMessage[]): Promise<void>;
|
|
25
25
|
/**
|
|
26
26
|
* Gets the L1 to L2 message index in the L1 to L2 message tree.
|
|
27
27
|
* @param l1ToL2Message - The L1 to L2 message.
|
|
28
28
|
* @returns The index of the L1 to L2 message in the L1 to L2 message tree (undefined if not found).
|
|
29
29
|
*/
|
|
30
30
|
getL1ToL2MessageIndex(l1ToL2Message: Fr): Promise<bigint | undefined>;
|
|
31
|
-
|
|
31
|
+
getLastMessage(): Promise<InboxMessage | undefined>;
|
|
32
|
+
getL1ToL2Messages(checkpointNumber: CheckpointNumber): Promise<Fr[]>;
|
|
33
|
+
iterateL1ToL2Messages(range?: CustomRange<bigint>): AsyncIterableIterator<InboxMessage>;
|
|
34
|
+
removeL1ToL2Messages(startIndex: bigint): Promise<void>;
|
|
35
|
+
rollbackL1ToL2MessagesToCheckpoint(targetCheckpointNumber: CheckpointNumber): Promise<void>;
|
|
36
|
+
private indexToKey;
|
|
37
|
+
private leafToIndexKey;
|
|
38
|
+
private increaseTotalMessageCount;
|
|
32
39
|
}
|
|
33
|
-
//# sourceMappingURL=
|
|
40
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVzc2FnZV9zdG9yZS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2FyY2hpdmVyL2t2X2FyY2hpdmVyX3N0b3JlL21lc3NhZ2Vfc3RvcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDMUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFFbkUsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBSXBELE9BQU8sRUFDTCxLQUFLLGlCQUFpQixFQUd0QixLQUFLLFdBQVcsRUFFakIsTUFBTSxpQkFBaUIsQ0FBQztBQUd6QixPQUFPLEVBQ0wsS0FBSyxZQUFZLEVBSWxCLE1BQU0sNkJBQTZCLENBQUM7QUFFckMscUJBQWEsaUJBQWtCLFNBQVEsS0FBSzthQUd4QixZQUFZLEVBQUUsWUFBWTtJQUY1QyxZQUNFLE9BQU8sRUFBRSxNQUFNLEVBQ0MsWUFBWSxFQUFFLFlBQVksRUFJM0M7Q0FDRjtBQUVELHFCQUFhLFlBQVk7O0lBWVgsT0FBTyxDQUFDLEVBQUU7SUFBdEIsWUFBb0IsRUFBRSxFQUFFLGlCQUFpQixFQUt4QztJQUVZLDBCQUEwQixJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FFekQ7SUFFRCxxQ0FBcUM7SUFDeEIsaUJBQWlCLElBQUksT0FBTyxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUMsQ0FRL0Q7SUFFRCxvQ0FBb0M7SUFDdkIsaUJBQWlCLENBQUMsT0FBTyxFQUFFLFNBQVMsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBR2hFO0lBRUQ7Ozs7T0FJRztJQUNJLGlCQUFpQixDQUFDLFFBQVEsRUFBRSxZQUFZLEVBQUUsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBNkZoRTtJQUVEOzs7O09BSUc7SUFDSSxxQkFBcUIsQ0FBQyxhQUFhLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDLENBRTNFO0lBRVksY0FBYyxJQUFJLE9BQU8sQ0FBQyxZQUFZLEdBQUcsU0FBUyxDQUFDLENBRy9EO0lBRVksaUJBQWlCLENBQUMsZ0JBQWdCLEVBQUUsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBdUJoRjtJQUVhLHFCQUFxQixDQUFDLEtBQUssR0FBRSxXQUFXLENBQUMsTUFBTSxDQUFNLEdBQUcscUJBQXFCLENBQUMsWUFBWSxDQUFDLENBS3hHO0lBRU0sb0JBQW9CLENBQUMsVUFBVSxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLENBZ0I3RDtJQUVNLGtDQUFrQyxDQUFDLHNCQUFzQixFQUFFLGdCQUFnQixHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FJakc7SUFFRCxPQUFPLENBQUMsVUFBVTtJQUlsQixPQUFPLENBQUMsY0FBYztZQUlSLHlCQUF5QjtDQVN4QyJ9
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message_store.d.ts","sourceRoot":"","sources":["../../../src/archiver/kv_archiver_store/message_store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"message_store.d.ts","sourceRoot":"","sources":["../../../src/archiver/kv_archiver_store/message_store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAEnE,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAIpD,OAAO,EACL,KAAK,iBAAiB,EAGtB,KAAK,WAAW,EAEjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,6BAA6B,CAAC;AAErC,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,YAAY,EAAE,YAAY;IAF5C,YACE,OAAO,EAAE,MAAM,EACC,YAAY,EAAE,YAAY,EAI3C;CACF;AAED,qBAAa,YAAY;;IAYX,OAAO,CAAC,EAAE;IAAtB,YAAoB,EAAE,EAAE,iBAAiB,EAKxC;IAEY,0BAA0B,IAAI,OAAO,CAAC,MAAM,CAAC,CAEzD;IAED,qCAAqC;IACxB,iBAAiB,IAAI,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAQ/D;IAED,oCAAoC;IACvB,iBAAiB,CAAC,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAGhE;IAED;;;;OAIG;IACI,iBAAiB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FhE;IAED;;;;OAIG;IACI,qBAAqB,CAAC,aAAa,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE3E;IAEY,cAAc,IAAI,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAG/D;IAEY,iBAAiB,CAAC,gBAAgB,EAAE,gBAAgB,GAAG,OAAO,CAAC,EAAE,EAAE,CAAC,CAuBhF;IAEa,qBAAqB,CAAC,KAAK,GAAE,WAAW,CAAC,MAAM,CAAM,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAKxG;IAEM,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7D;IAEM,kCAAkC,CAAC,sBAAsB,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjG;IAED,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;YAIR,yBAAyB;CASxC"}
|