@aztec/world-state 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a
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/dest/instrumentation/instrumentation.d.ts +4 -3
- package/dest/instrumentation/instrumentation.d.ts.map +1 -1
- package/dest/instrumentation/instrumentation.js +4 -9
- package/dest/native/ipc_world_state_instance.d.ts +87 -0
- package/dest/native/ipc_world_state_instance.d.ts.map +1 -0
- package/dest/native/ipc_world_state_instance.js +660 -0
- package/dest/native/merkle_trees_facade.d.ts +6 -3
- package/dest/native/merkle_trees_facade.d.ts.map +1 -1
- package/dest/native/merkle_trees_facade.js +53 -113
- package/dest/native/message.d.ts +14 -229
- package/dest/native/message.d.ts.map +1 -1
- package/dest/native/message.js +0 -42
- package/dest/native/native_world_state.d.ts +15 -5
- package/dest/native/native_world_state.d.ts.map +1 -1
- package/dest/native/native_world_state.js +97 -51
- package/dest/native/native_world_state_instance.d.ts +58 -41
- package/dest/native/native_world_state_instance.d.ts.map +1 -1
- package/dest/native/native_world_state_instance.js +5 -203
- package/dest/native/world_state_operation.d.ts +7 -0
- package/dest/native/world_state_operation.d.ts.map +1 -0
- package/dest/native/world_state_operation.js +5 -0
- package/dest/synchronizer/config.d.ts +1 -1
- package/dest/synchronizer/config.d.ts.map +1 -1
- package/dest/synchronizer/config.js +9 -10
- package/dest/synchronizer/factory.d.ts +3 -3
- package/dest/synchronizer/factory.d.ts.map +1 -1
- package/dest/synchronizer/factory.js +5 -5
- package/dest/synchronizer/server_world_state_synchronizer.d.ts +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.d.ts.map +1 -1
- package/dest/synchronizer/server_world_state_synchronizer.js +16 -13
- package/dest/testing.js +1 -1
- package/package.json +14 -10
- package/src/instrumentation/instrumentation.ts +6 -18
- package/src/native/ipc_world_state_instance.ts +830 -0
- package/src/native/merkle_trees_facade.ts +76 -108
- package/src/native/message.ts +13 -296
- package/src/native/native_world_state.ts +113 -79
- package/src/native/native_world_state_instance.ts +96 -284
- package/src/native/world_state_operation.ts +33 -0
- package/src/synchronizer/config.ts +14 -10
- package/src/synchronizer/factory.ts +8 -7
- package/src/synchronizer/server_world_state_synchronizer.ts +11 -14
- package/src/testing.ts +1 -1
- package/dest/native/world_state_ops_queue.d.ts +0 -19
- package/dest/native/world_state_ops_queue.d.ts.map +0 -1
- package/dest/native/world_state_ops_queue.js +0 -146
- package/src/native/world_state_ops_queue.ts +0 -190
|
@@ -22,14 +22,13 @@ import {
|
|
|
22
22
|
PublicDataTreeLeafPreimage,
|
|
23
23
|
} from '@aztec/stdlib/trees';
|
|
24
24
|
import { type BlockHeader, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
|
|
25
|
-
import {
|
|
25
|
+
import type { WorldStateRevision } from '@aztec/stdlib/world-state';
|
|
26
26
|
|
|
27
27
|
import assert from 'assert';
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
30
|
type SerializedIndexedLeaf,
|
|
31
31
|
type SerializedLeafValue,
|
|
32
|
-
WorldStateMessageType,
|
|
33
32
|
blockStateReference,
|
|
34
33
|
treeStateReferenceToSnapshot,
|
|
35
34
|
} from './message.js';
|
|
@@ -46,8 +45,12 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
46
45
|
return this.initialHeader;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
getRevision():
|
|
50
|
-
return
|
|
48
|
+
getRevision(): WorldStateRevision {
|
|
49
|
+
return this.revision;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getIpcPath(): string {
|
|
53
|
+
return this.instance.getIpcPath();
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
findLeafIndices(treeId: MerkleTreeId, values: MerkleTreeLeafType<MerkleTreeId>[]): Promise<(bigint | undefined)[]> {
|
|
@@ -58,13 +61,13 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
58
61
|
treeId: MerkleTreeId,
|
|
59
62
|
values: MerkleTreeLeafType<MerkleTreeId>[],
|
|
60
63
|
): Promise<({ path: SiblingPath<N>; index: bigint } | undefined)[]> {
|
|
61
|
-
const
|
|
62
|
-
leaves: values.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf))),
|
|
63
|
-
revision: this.revision,
|
|
64
|
+
const paths = await this.instance.findSiblingPaths(
|
|
64
65
|
treeId,
|
|
65
|
-
|
|
66
|
+
this.revision,
|
|
67
|
+
values.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf))),
|
|
68
|
+
);
|
|
66
69
|
|
|
67
|
-
return
|
|
70
|
+
return paths.map(path => {
|
|
68
71
|
if (!path) {
|
|
69
72
|
return undefined;
|
|
70
73
|
}
|
|
@@ -77,14 +80,14 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
77
80
|
leaves: MerkleTreeLeafType<MerkleTreeId>[],
|
|
78
81
|
startIndex: bigint,
|
|
79
82
|
): Promise<(bigint | undefined)[]> {
|
|
80
|
-
const
|
|
81
|
-
leaves: leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf))),
|
|
82
|
-
revision: this.revision,
|
|
83
|
+
const indices = await this.instance.findLeafIndices(
|
|
83
84
|
treeId,
|
|
85
|
+
this.revision,
|
|
86
|
+
leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf))),
|
|
84
87
|
startIndex,
|
|
85
|
-
|
|
88
|
+
);
|
|
86
89
|
|
|
87
|
-
return
|
|
90
|
+
return indices.map(index => {
|
|
88
91
|
if (typeof index === 'number' || typeof index === 'bigint') {
|
|
89
92
|
return BigInt(index);
|
|
90
93
|
} else {
|
|
@@ -94,11 +97,7 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
async getLeafPreimage(treeId: IndexedTreeId, leafIndex: bigint): Promise<IndexedTreeLeafPreimage | undefined> {
|
|
97
|
-
const resp = await this.instance.
|
|
98
|
-
leafIndex,
|
|
99
|
-
revision: this.revision,
|
|
100
|
-
treeId,
|
|
101
|
-
});
|
|
100
|
+
const resp = await this.instance.getLeafPreimage(treeId, this.revision, leafIndex);
|
|
102
101
|
|
|
103
102
|
return resp ? deserializeIndexedLeaf(resp) : undefined;
|
|
104
103
|
}
|
|
@@ -107,11 +106,7 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
107
106
|
treeId: ID,
|
|
108
107
|
leafIndex: bigint,
|
|
109
108
|
): Promise<MerkleTreeLeafType<ID> | undefined> {
|
|
110
|
-
const resp = await this.instance.
|
|
111
|
-
leafIndex,
|
|
112
|
-
revision: this.revision,
|
|
113
|
-
treeId,
|
|
114
|
-
});
|
|
109
|
+
const resp = await this.instance.getLeafValue(treeId, this.revision, leafIndex);
|
|
115
110
|
|
|
116
111
|
if (!resp) {
|
|
117
112
|
return undefined;
|
|
@@ -129,11 +124,7 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
129
124
|
treeId: IndexedTreeId,
|
|
130
125
|
value: bigint,
|
|
131
126
|
): Promise<{ index: bigint; alreadyPresent: boolean } | undefined> {
|
|
132
|
-
const resp = await this.instance.
|
|
133
|
-
key: new Fr(value),
|
|
134
|
-
revision: this.revision,
|
|
135
|
-
treeId,
|
|
136
|
-
});
|
|
127
|
+
const resp = await this.instance.findLowLeaf(treeId, this.revision, new Fr(value));
|
|
137
128
|
return {
|
|
138
129
|
alreadyPresent: resp.alreadyPresent,
|
|
139
130
|
index: BigInt(resp.index),
|
|
@@ -141,73 +132,54 @@ export class MerkleTreesFacade implements MerkleTreeReadOperations {
|
|
|
141
132
|
}
|
|
142
133
|
|
|
143
134
|
async getSiblingPath<N extends number>(treeId: MerkleTreeId, leafIndex: bigint): Promise<SiblingPath<N>> {
|
|
144
|
-
const siblingPath = await this.instance.
|
|
145
|
-
leafIndex,
|
|
146
|
-
revision: this.revision,
|
|
147
|
-
treeId,
|
|
148
|
-
});
|
|
135
|
+
const siblingPath = await this.instance.getSiblingPath(treeId, this.revision, leafIndex);
|
|
149
136
|
|
|
150
137
|
return new SiblingPath(siblingPath.length, siblingPath) as any;
|
|
151
138
|
}
|
|
152
139
|
|
|
153
140
|
async getStateReference(): Promise<StateReference> {
|
|
154
|
-
const
|
|
155
|
-
revision: this.revision,
|
|
156
|
-
});
|
|
141
|
+
const state = await this.instance.getStateReference(this.revision);
|
|
157
142
|
|
|
158
143
|
return new StateReference(
|
|
159
|
-
treeStateReferenceToSnapshot(
|
|
144
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
|
|
160
145
|
new PartialStateReference(
|
|
161
|
-
treeStateReferenceToSnapshot(
|
|
162
|
-
treeStateReferenceToSnapshot(
|
|
163
|
-
treeStateReferenceToSnapshot(
|
|
146
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.NOTE_HASH_TREE]),
|
|
147
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.NULLIFIER_TREE]),
|
|
148
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.PUBLIC_DATA_TREE]),
|
|
164
149
|
),
|
|
165
150
|
);
|
|
166
151
|
}
|
|
167
152
|
|
|
168
153
|
async getInitialStateReference(): Promise<StateReference> {
|
|
169
|
-
const
|
|
154
|
+
const state = await this.instance.getInitialStateReference();
|
|
170
155
|
|
|
171
156
|
return new StateReference(
|
|
172
|
-
treeStateReferenceToSnapshot(
|
|
157
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.L1_TO_L2_MESSAGE_TREE]),
|
|
173
158
|
new PartialStateReference(
|
|
174
|
-
treeStateReferenceToSnapshot(
|
|
175
|
-
treeStateReferenceToSnapshot(
|
|
176
|
-
treeStateReferenceToSnapshot(
|
|
159
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.NOTE_HASH_TREE]),
|
|
160
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.NULLIFIER_TREE]),
|
|
161
|
+
treeStateReferenceToSnapshot(state[MerkleTreeId.PUBLIC_DATA_TREE]),
|
|
177
162
|
),
|
|
178
163
|
);
|
|
179
164
|
}
|
|
180
165
|
|
|
181
166
|
async getTreeInfo(treeId: MerkleTreeId): Promise<TreeInfo> {
|
|
182
|
-
|
|
183
|
-
treeId: treeId,
|
|
184
|
-
revision: this.revision,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
depth: resp.depth,
|
|
189
|
-
root: resp.root,
|
|
190
|
-
size: BigInt(resp.size),
|
|
191
|
-
treeId,
|
|
192
|
-
};
|
|
167
|
+
return await this.instance.getTreeInfo(treeId, this.revision);
|
|
193
168
|
}
|
|
194
169
|
|
|
195
170
|
async getBlockNumbersForLeafIndices<ID extends MerkleTreeId>(
|
|
196
171
|
treeId: ID,
|
|
197
172
|
leafIndices: bigint[],
|
|
198
173
|
): Promise<(BlockNumber | undefined)[]> {
|
|
199
|
-
const
|
|
200
|
-
treeId,
|
|
201
|
-
revision: this.revision,
|
|
202
|
-
leafIndices,
|
|
203
|
-
});
|
|
174
|
+
const blockNumbers = await this.instance.getBlockNumbersForLeafIndices(treeId, this.revision, leafIndices);
|
|
204
175
|
|
|
205
|
-
return
|
|
176
|
+
return blockNumbers.map(x => (x === undefined || x === null ? undefined : BlockNumber(Number(x))));
|
|
206
177
|
}
|
|
207
178
|
}
|
|
208
179
|
|
|
209
180
|
export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTreeWriteOperations {
|
|
210
181
|
private log = createLogger('world-state:merkle-trees-fork-facade');
|
|
182
|
+
private closePromise: Promise<void> | undefined;
|
|
211
183
|
|
|
212
184
|
constructor(
|
|
213
185
|
instance: NativeWorldStateInstance,
|
|
@@ -220,19 +192,19 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
220
192
|
super(instance, initialHeader, revision);
|
|
221
193
|
}
|
|
222
194
|
async updateArchive(header: BlockHeader): Promise<void> {
|
|
223
|
-
await this.instance.
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
195
|
+
await this.instance.updateArchive(
|
|
196
|
+
this.revision.forkId,
|
|
197
|
+
blockStateReference(header.state),
|
|
198
|
+
(await header.hash()).toBuffer(),
|
|
199
|
+
);
|
|
228
200
|
}
|
|
229
201
|
|
|
230
202
|
async appendLeaves<ID extends MerkleTreeId>(treeId: ID, leaves: MerkleTreeLeafType<ID>[]): Promise<void> {
|
|
231
|
-
await this.instance.
|
|
232
|
-
leaves: leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf as any))),
|
|
233
|
-
forkId: this.revision.forkId,
|
|
203
|
+
await this.instance.appendLeaves(
|
|
234
204
|
treeId,
|
|
235
|
-
|
|
205
|
+
this.revision.forkId,
|
|
206
|
+
leaves.map(leaf => serializeLeaf(hydrateLeaf(treeId, leaf as any))),
|
|
207
|
+
);
|
|
236
208
|
}
|
|
237
209
|
|
|
238
210
|
async batchInsert<TreeHeight extends number, SubtreeSiblingPathHeight extends number, ID extends IndexedTreeId>(
|
|
@@ -241,24 +213,19 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
241
213
|
subtreeHeight: number,
|
|
242
214
|
): Promise<BatchInsertionResult<TreeHeight, SubtreeSiblingPathHeight>> {
|
|
243
215
|
const leaves = rawLeaves.map((leaf: Buffer) => hydrateLeaf(treeId, leaf)).map(serializeLeaf);
|
|
244
|
-
const resp = await this.instance.
|
|
245
|
-
leaves,
|
|
246
|
-
treeId,
|
|
247
|
-
forkId: this.revision.forkId,
|
|
248
|
-
subtreeDepth: subtreeHeight,
|
|
249
|
-
});
|
|
216
|
+
const resp = await this.instance.batchInsert(treeId, this.revision.forkId, leaves, subtreeHeight);
|
|
250
217
|
|
|
251
218
|
return {
|
|
252
219
|
newSubtreeSiblingPath: new SiblingPath<SubtreeSiblingPathHeight>(
|
|
253
|
-
resp.
|
|
254
|
-
resp.
|
|
220
|
+
resp.subtreePath.length as any,
|
|
221
|
+
resp.subtreePath,
|
|
255
222
|
),
|
|
256
|
-
sortedNewLeaves: resp.
|
|
223
|
+
sortedNewLeaves: resp.sortedLeaves
|
|
257
224
|
.map(([leaf]) => leaf)
|
|
258
225
|
.map(deserializeLeafValue)
|
|
259
226
|
.map(serializeToBuffer),
|
|
260
|
-
sortedNewLeavesIndexes: resp.
|
|
261
|
-
lowLeavesWitnessData: resp.
|
|
227
|
+
sortedNewLeavesIndexes: resp.sortedLeaves.map(([, index]) => index),
|
|
228
|
+
lowLeavesWitnessData: resp.lowLeafWitnessData.map(data => ({
|
|
262
229
|
index: BigInt(data.index),
|
|
263
230
|
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
264
231
|
siblingPath: new SiblingPath<TreeHeight>(data.path.length as any, data.path),
|
|
@@ -271,19 +238,15 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
271
238
|
rawLeaves: Buffer[],
|
|
272
239
|
): Promise<SequentialInsertionResult<TreeHeight>> {
|
|
273
240
|
const leaves = rawLeaves.map((leaf: Buffer) => hydrateLeaf(treeId, leaf)).map(serializeLeaf);
|
|
274
|
-
const resp = await this.instance.
|
|
275
|
-
leaves,
|
|
276
|
-
treeId,
|
|
277
|
-
forkId: this.revision.forkId,
|
|
278
|
-
});
|
|
241
|
+
const resp = await this.instance.sequentialInsert(treeId, this.revision.forkId, leaves);
|
|
279
242
|
|
|
280
243
|
return {
|
|
281
|
-
lowLeavesWitnessData: resp.
|
|
244
|
+
lowLeavesWitnessData: resp.lowLeafWitnessData.map(data => ({
|
|
282
245
|
index: BigInt(data.index),
|
|
283
246
|
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
284
247
|
siblingPath: new SiblingPath<TreeHeight>(data.path.length as any, data.path),
|
|
285
248
|
})),
|
|
286
|
-
insertionWitnessData: resp.
|
|
249
|
+
insertionWitnessData: resp.insertionWitnessData.map(data => ({
|
|
287
250
|
index: BigInt(data.index),
|
|
288
251
|
leafPreimage: deserializeIndexedLeaf(data.leaf),
|
|
289
252
|
siblingPath: new SiblingPath<TreeHeight>(data.path.length as any, data.path),
|
|
@@ -291,14 +254,29 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
291
254
|
};
|
|
292
255
|
}
|
|
293
256
|
|
|
294
|
-
public
|
|
257
|
+
public close(): Promise<void> {
|
|
295
258
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
259
|
+
// Share the in-flight close promise across duplicate dispose calls so DELETE_FORK is sent at most once.
|
|
260
|
+
if (this.closePromise) {
|
|
261
|
+
return this.closePromise;
|
|
262
|
+
}
|
|
263
|
+
this.closePromise = this.doClose();
|
|
264
|
+
return this.closePromise;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private async doClose(): Promise<void> {
|
|
296
268
|
try {
|
|
297
|
-
await this.instance.
|
|
269
|
+
await this.instance.deleteFork(this.revision.forkId);
|
|
298
270
|
} catch (err: any) {
|
|
299
271
|
// Ignore errors due to native instance being closed during shutdown.
|
|
300
272
|
// This can happen when validators are still processing block proposals while the node is stopping.
|
|
301
|
-
if (err?.message === 'Native instance is closed') {
|
|
273
|
+
if (err?.message === 'Native instance is closed' || err?.message === 'IPC instance is closed') {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Ignore "Fork not found": the native fork was already destroyed by a pending-chain unwind or a
|
|
277
|
+
// historical prune (both call C++ remove_forks_for_block). Fork IDs are monotonic and never reused,
|
|
278
|
+
// so swallowing this on close cannot mask a deletion of a different fork.
|
|
279
|
+
if (err?.message === 'Fork not found') {
|
|
302
280
|
return;
|
|
303
281
|
}
|
|
304
282
|
throw err;
|
|
@@ -310,9 +288,6 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
310
288
|
void sleep(this.opts.closeDelayMs)
|
|
311
289
|
.then(() => this.close())
|
|
312
290
|
.catch(err => {
|
|
313
|
-
if (err && 'message' in err && err.message === 'Native instance is closed') {
|
|
314
|
-
return; // Ignore errors due to native instance being closed
|
|
315
|
-
}
|
|
316
291
|
this.log.warn('Error closing MerkleTreesForkFacade after delay', { err });
|
|
317
292
|
});
|
|
318
293
|
} else {
|
|
@@ -322,34 +297,27 @@ export class MerkleTreesForkFacade extends MerkleTreesFacade implements MerkleTr
|
|
|
322
297
|
|
|
323
298
|
public async createCheckpoint(): Promise<number> {
|
|
324
299
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
325
|
-
|
|
326
|
-
return resp.depth;
|
|
300
|
+
return await this.instance.createCheckpoint(this.revision.forkId);
|
|
327
301
|
}
|
|
328
302
|
|
|
329
303
|
public async commitCheckpoint(): Promise<void> {
|
|
330
304
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
331
|
-
await this.instance.
|
|
305
|
+
await this.instance.commitCheckpoint(this.revision.forkId);
|
|
332
306
|
}
|
|
333
307
|
|
|
334
308
|
public async revertCheckpoint(): Promise<void> {
|
|
335
309
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
336
|
-
await this.instance.
|
|
310
|
+
await this.instance.revertCheckpoint(this.revision.forkId);
|
|
337
311
|
}
|
|
338
312
|
|
|
339
313
|
public async commitAllCheckpointsTo(depth: number): Promise<void> {
|
|
340
314
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
341
|
-
await this.instance.
|
|
342
|
-
forkId: this.revision.forkId,
|
|
343
|
-
depth,
|
|
344
|
-
});
|
|
315
|
+
await this.instance.commitAllCheckpoints(this.revision.forkId, depth);
|
|
345
316
|
}
|
|
346
317
|
|
|
347
318
|
public async revertAllCheckpointsTo(depth: number): Promise<void> {
|
|
348
319
|
assert.notEqual(this.revision.forkId, 0, 'Fork ID must be set');
|
|
349
|
-
await this.instance.
|
|
350
|
-
forkId: this.revision.forkId,
|
|
351
|
-
depth,
|
|
352
|
-
});
|
|
320
|
+
await this.instance.revertAllCheckpoints(this.revision.forkId, depth);
|
|
353
321
|
}
|
|
354
322
|
}
|
|
355
323
|
|