@aztec/stdlib 0.0.1-commit.f5d02921e → 0.0.1-commit.f650c0a5c
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/abi/selector.d.ts +2 -2
- package/dest/abi/selector.d.ts.map +1 -1
- package/dest/abi/selector.js +1 -1
- package/dest/avm/revert_code.d.ts +8 -18
- package/dest/avm/revert_code.d.ts.map +1 -1
- package/dest/avm/revert_code.js +15 -30
- package/dest/gas/gas_settings.d.ts +40 -3
- package/dest/gas/gas_settings.d.ts.map +1 -1
- package/dest/gas/gas_settings.js +50 -6
- package/dest/interfaces/aztec-node-admin.d.ts +4 -11
- package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
- package/dest/interfaces/aztec-node-admin.js +3 -3
- package/dest/interfaces/proving-job.d.ts +166 -166
- package/dest/interfaces/slasher.d.ts +1 -10
- package/dest/interfaces/slasher.d.ts.map +1 -1
- package/dest/interfaces/slasher.js +0 -2
- package/dest/interfaces/validator.d.ts +3 -6
- package/dest/interfaces/validator.d.ts.map +1 -1
- package/dest/l1-contracts/index.d.ts +2 -2
- package/dest/l1-contracts/index.d.ts.map +1 -1
- package/dest/l1-contracts/index.js +1 -1
- package/dest/logs/extended_directional_app_tagging_secret.d.ts +4 -4
- package/dest/logs/extended_directional_app_tagging_secret.d.ts.map +1 -1
- package/dest/logs/extended_directional_app_tagging_secret.js +12 -5
- package/dest/p2p/checkpoint_proposal.d.ts +2 -2
- package/dest/p2p/checkpoint_proposal.d.ts.map +1 -1
- package/dest/p2p/checkpoint_proposal.js +3 -15
- package/dest/slashing/index.d.ts +2 -3
- package/dest/slashing/index.d.ts.map +1 -1
- package/dest/slashing/index.js +1 -2
- package/dest/slashing/serialization.d.ts +2 -6
- package/dest/slashing/serialization.d.ts.map +1 -1
- package/dest/slashing/serialization.js +0 -60
- package/dest/slashing/types.d.ts +4 -90
- package/dest/slashing/types.d.ts.map +1 -1
- package/dest/slashing/types.js +0 -14
- package/dest/slashing/{tally.d.ts → votes.d.ts} +1 -1
- package/dest/slashing/{tally.d.ts.map → votes.d.ts.map} +1 -1
- package/dest/slashing/{tally.js → votes.js} +1 -1
- package/dest/tests/factories.js +1 -1
- package/dest/tests/mocks.d.ts +1 -1
- package/dest/tests/mocks.d.ts.map +1 -1
- package/dest/tests/mocks.js +13 -18
- package/dest/timetable/index.d.ts +2 -1
- package/dest/timetable/index.d.ts.map +1 -1
- package/dest/timetable/index.js +6 -2
- package/dest/tx/simulated_tx.d.ts +3 -3
- package/dest/tx/simulated_tx.d.ts.map +1 -1
- package/dest/tx/simulated_tx.js +2 -2
- package/dest/tx/tx_receipt.d.ts +8 -4
- package/dest/tx/tx_receipt.d.ts.map +1 -1
- package/dest/tx/tx_receipt.js +8 -15
- package/dest/world-state/genesis_data.d.ts +11 -0
- package/dest/world-state/genesis_data.d.ts.map +1 -0
- package/dest/world-state/genesis_data.js +4 -0
- package/dest/world-state/index.d.ts +2 -1
- package/dest/world-state/index.d.ts.map +1 -1
- package/dest/world-state/index.js +1 -0
- package/package.json +8 -8
- package/src/abi/selector.ts +1 -1
- package/src/avm/revert_code.ts +25 -39
- package/src/gas/gas_settings.ts +65 -12
- package/src/interfaces/aztec-node-admin.ts +5 -7
- package/src/interfaces/slasher.ts +0 -6
- package/src/interfaces/validator.ts +1 -4
- package/src/l1-contracts/index.ts +1 -1
- package/src/logs/extended_directional_app_tagging_secret.ts +19 -8
- package/src/p2p/checkpoint_proposal.ts +10 -26
- package/src/slashing/index.ts +1 -2
- package/src/slashing/serialization.ts +1 -81
- package/src/slashing/types.ts +3 -35
- package/src/slashing/{tally.ts → votes.ts} +1 -1
- package/src/tests/factories.ts +1 -1
- package/src/tests/mocks.ts +14 -12
- package/src/timetable/index.ts +7 -2
- package/src/tx/simulated_tx.ts +5 -1
- package/src/tx/tx_receipt.ts +11 -14
- package/src/world-state/genesis_data.ts +15 -0
- package/src/world-state/index.ts +1 -0
- package/dest/l1-contracts/slash_factory.d.ts +0 -45
- package/dest/l1-contracts/slash_factory.d.ts.map +0 -1
- package/dest/l1-contracts/slash_factory.js +0 -158
- package/dest/slashing/empire.d.ts +0 -31
- package/dest/slashing/empire.d.ts.map +0 -1
- package/dest/slashing/empire.js +0 -87
- package/src/l1-contracts/slash_factory.ts +0 -180
- package/src/slashing/empire.ts +0 -104
|
@@ -3,7 +3,8 @@ import { createSafeJsonRpcClient, defaultFetch } from '@aztec/foundation/json-rp
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
5
5
|
import type { ApiSchemaFor } from '../schemas/schemas.js';
|
|
6
|
-
import {
|
|
6
|
+
import { optional } from '../schemas/schemas.js';
|
|
7
|
+
import { type Offense, OffenseSchema } from '../slashing/index.js';
|
|
7
8
|
import { type ComponentsVersions, getVersioningResponseHandler } from '../versioning/index.js';
|
|
8
9
|
import { type ArchiverSpecificConfig, ArchiverSpecificConfigSchema } from './archiver.js';
|
|
9
10
|
import { type SequencerConfig, SequencerConfigSchema } from './configs.js';
|
|
@@ -36,8 +37,9 @@ export interface AztecNodeAdmin {
|
|
|
36
37
|
* Pauses syncing and rolls back the database to the target L2 block number.
|
|
37
38
|
* @param targetBlockNumber - The block number to roll back to.
|
|
38
39
|
* @param force - If true, clears the world state db and p2p dbs if rolling back to behind the finalized block.
|
|
40
|
+
* @param resumeSync - If true (default), resumes archiver and world state sync after rollback.
|
|
39
41
|
*/
|
|
40
|
-
rollbackTo(targetBlockNumber: number, force?: boolean): Promise<void>;
|
|
42
|
+
rollbackTo(targetBlockNumber: number, force?: boolean, resumeSync?: boolean): Promise<void>;
|
|
41
43
|
|
|
42
44
|
/** Pauses archiver and world state syncing. */
|
|
43
45
|
pauseSync(): Promise<void>;
|
|
@@ -45,9 +47,6 @@ export interface AztecNodeAdmin {
|
|
|
45
47
|
/** Resumes archiver and world state syncing. */
|
|
46
48
|
resumeSync(): Promise<void>;
|
|
47
49
|
|
|
48
|
-
/** Returns all monitored payloads by the slasher for the current round. */
|
|
49
|
-
getSlashPayloads(): Promise<SlashPayloadRound[]>;
|
|
50
|
-
|
|
51
50
|
/** Returns all offenses applicable for the given round. */
|
|
52
51
|
getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]>;
|
|
53
52
|
|
|
@@ -100,10 +99,9 @@ export const AztecNodeAdminApiSchema: ApiSchemaFor<AztecNodeAdmin> = {
|
|
|
100
99
|
getConfig: z.function().returns(AztecNodeAdminConfigSchema),
|
|
101
100
|
setConfig: z.function().args(AztecNodeAdminConfigSchema.partial()).returns(z.void()),
|
|
102
101
|
startSnapshotUpload: z.function().args(z.string()).returns(z.void()),
|
|
103
|
-
rollbackTo: z.function().args(z.number()).returns(z.void()),
|
|
102
|
+
rollbackTo: z.function().args(z.number(), optional(z.boolean()), optional(z.boolean())).returns(z.void()),
|
|
104
103
|
pauseSync: z.function().returns(z.void()),
|
|
105
104
|
resumeSync: z.function().returns(z.void()),
|
|
106
|
-
getSlashPayloads: z.function().returns(z.array(SlashPayloadRoundSchema)),
|
|
107
105
|
getSlashOffenses: z
|
|
108
106
|
.function()
|
|
109
107
|
.args(z.union([z.bigint(), z.literal('all'), z.literal('current')]))
|
|
@@ -3,12 +3,8 @@ import { schemas, zodFor } from '@aztec/foundation/schemas';
|
|
|
3
3
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
-
export type SlasherClientType = 'empire' | 'tally';
|
|
7
|
-
|
|
8
6
|
export interface SlasherConfig {
|
|
9
7
|
slashOverridePayload?: EthAddress;
|
|
10
|
-
slashMinPenaltyPercentage: number;
|
|
11
|
-
slashMaxPenaltyPercentage: number;
|
|
12
8
|
slashSelfAllowed?: boolean; // Whether to allow slashes to own validators
|
|
13
9
|
slashValidatorsAlways: EthAddress[]; // Array of validator addresses
|
|
14
10
|
slashValidatorsNever: EthAddress[]; // Array of validator addresses
|
|
@@ -32,8 +28,6 @@ export interface SlasherConfig {
|
|
|
32
28
|
export const SlasherConfigSchema = zodFor<SlasherConfig>()(
|
|
33
29
|
z.object({
|
|
34
30
|
slashOverridePayload: schemas.EthAddress.optional(),
|
|
35
|
-
slashMinPenaltyPercentage: z.number(),
|
|
36
|
-
slashMaxPenaltyPercentage: z.number(),
|
|
37
31
|
slashValidatorsAlways: z.array(schemas.EthAddress),
|
|
38
32
|
slashValidatorsNever: z.array(schemas.EthAddress),
|
|
39
33
|
slashPrunePenalty: schemas.BigInt,
|
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
BlockProposal,
|
|
10
10
|
BlockProposalOptions,
|
|
11
11
|
CheckpointAttestation,
|
|
12
|
-
CheckpointLastBlockData,
|
|
13
12
|
CheckpointProposal,
|
|
14
13
|
CheckpointProposalOptions,
|
|
15
14
|
} from '@aztec/stdlib/p2p';
|
|
@@ -118,8 +117,6 @@ export const ValidatorClientFullConfigSchema = zodFor<Omit<ValidatorClientFullCo
|
|
|
118
117
|
}),
|
|
119
118
|
);
|
|
120
119
|
|
|
121
|
-
export type CreateCheckpointProposalLastBlockData = Omit<CheckpointLastBlockData, 'txHashes'> & { txs: Tx[] };
|
|
122
|
-
|
|
123
120
|
export interface Validator {
|
|
124
121
|
start(): Promise<void>;
|
|
125
122
|
updateConfig(config: Partial<ValidatorClientFullConfig>): void;
|
|
@@ -140,7 +137,7 @@ export interface Validator {
|
|
|
140
137
|
checkpointHeader: CheckpointHeader,
|
|
141
138
|
archive: Fr,
|
|
142
139
|
feeAssetPriceModifier: bigint,
|
|
143
|
-
|
|
140
|
+
lastBlockProposal: BlockProposal | undefined,
|
|
144
141
|
proposerAddress: EthAddress | undefined,
|
|
145
142
|
options: CheckpointProposalOptions,
|
|
146
143
|
): Promise<CheckpointProposal>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
// No exports.
|
|
@@ -23,14 +23,14 @@ import { computeAddressSecret, computePreaddress } from '../keys/derivation.js';
|
|
|
23
23
|
* doesn't seem to be a good way around this.
|
|
24
24
|
*/
|
|
25
25
|
export class ExtendedDirectionalAppTaggingSecret {
|
|
26
|
-
|
|
26
|
+
constructor(
|
|
27
27
|
public readonly secret: Fr,
|
|
28
28
|
public readonly app: AztecAddress,
|
|
29
29
|
) {}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Derives shared tagging secret and from that, the app address and recipient derives the directional app tagging
|
|
33
|
-
* secret.
|
|
33
|
+
* secret. Returns undefined if `externalAddress` is an invalid address.
|
|
34
34
|
*
|
|
35
35
|
* @param localAddress - The complete address of entity A in the shared tagging secret derivation scheme
|
|
36
36
|
* @param localIvsk - The incoming viewing secret key of entity A
|
|
@@ -45,8 +45,12 @@ export class ExtendedDirectionalAppTaggingSecret {
|
|
|
45
45
|
externalAddress: AztecAddress,
|
|
46
46
|
app: AztecAddress,
|
|
47
47
|
recipient: AztecAddress,
|
|
48
|
-
): Promise<ExtendedDirectionalAppTaggingSecret> {
|
|
48
|
+
): Promise<ExtendedDirectionalAppTaggingSecret | undefined> {
|
|
49
49
|
const taggingSecretPoint = await computeSharedTaggingSecret(localAddress, localIvsk, externalAddress);
|
|
50
|
+
if (!taggingSecretPoint) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
const appTaggingSecret = await poseidon2Hash([taggingSecretPoint.x, taggingSecretPoint.y, app]);
|
|
51
55
|
const directionalAppTaggingSecret = await poseidon2Hash([appTaggingSecret, recipient]);
|
|
52
56
|
|
|
@@ -63,18 +67,25 @@ export class ExtendedDirectionalAppTaggingSecret {
|
|
|
63
67
|
}
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
// Returns shared tagging secret computed with Diffie-Hellman key exchange
|
|
70
|
+
// Returns shared tagging secret computed with Diffie-Hellman key exchange, or undefined if `externalAddress` is an
|
|
71
|
+
// invalid address.
|
|
67
72
|
async function computeSharedTaggingSecret(
|
|
68
73
|
localAddress: CompleteAddress,
|
|
69
74
|
localIvsk: Fq,
|
|
70
75
|
externalAddress: AztecAddress,
|
|
71
|
-
): Promise<Point> {
|
|
72
|
-
const knownPreaddress = await computePreaddress(await localAddress.publicKeys.hash(), localAddress.partialAddress);
|
|
73
|
-
// TODO: #8970 - Computation of address point from x coordinate might fail
|
|
74
|
-
const externalAddressPoint = await externalAddress.toAddressPoint();
|
|
76
|
+
): Promise<Point | undefined> {
|
|
75
77
|
// Given A (local complete address) -> B (external address) and h == preaddress
|
|
76
78
|
// Compute shared secret as S = (h_A + local_ivsk_A) * Addr_Point_B
|
|
77
79
|
|
|
80
|
+
const knownPreaddress = await computePreaddress(await localAddress.publicKeys.hash(), localAddress.partialAddress);
|
|
81
|
+
|
|
82
|
+
// An invalid address has no corresponding address point
|
|
83
|
+
if (!(await externalAddress.isValid())) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const externalAddressPoint = await externalAddress.toAddressPoint();
|
|
88
|
+
|
|
78
89
|
// Beware! h_a + local_ivsk_a (also known as the address secret) can lead to an address point with a negative
|
|
79
90
|
// y-coordinate, since there's two possible candidates computeAddressSecret takes care of selecting the one that
|
|
80
91
|
// leads to a positive y-coordinate, which is the only valid address point
|
|
@@ -161,7 +161,7 @@ export class CheckpointProposal extends Gossipable {
|
|
|
161
161
|
checkpointHeader: CheckpointHeader,
|
|
162
162
|
archiveRoot: Fr,
|
|
163
163
|
feeAssetPriceModifier: bigint,
|
|
164
|
-
|
|
164
|
+
lastBlockProposal: BlockProposal | undefined,
|
|
165
165
|
payloadSigner: (payload: Buffer32, context: SigningContext) => Promise<Signature>,
|
|
166
166
|
): Promise<CheckpointProposal> {
|
|
167
167
|
// Sign the checkpoint payload with CHECKPOINT_PROPOSAL duty type
|
|
@@ -175,35 +175,19 @@ export class CheckpointProposal extends Gossipable {
|
|
|
175
175
|
|
|
176
176
|
const checkpointContext: SigningContext = {
|
|
177
177
|
slot: checkpointHeader.slotNumber,
|
|
178
|
-
blockNumber:
|
|
178
|
+
blockNumber: lastBlockProposal?.blockNumber ?? BlockNumber(0),
|
|
179
179
|
dutyType: DutyType.CHECKPOINT_PROPOSAL,
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
-
if (lastBlockInfo) {
|
|
183
|
-
// Sign block proposal before signing checkpoint proposal to ensure HA protection
|
|
184
|
-
const lastBlockProposal = await BlockProposal.createProposalFromSigner(
|
|
185
|
-
lastBlockInfo.blockHeader,
|
|
186
|
-
lastBlockInfo.indexWithinCheckpoint,
|
|
187
|
-
checkpointHeader.inHash,
|
|
188
|
-
archiveRoot,
|
|
189
|
-
lastBlockInfo.txHashes,
|
|
190
|
-
lastBlockInfo.txs,
|
|
191
|
-
payloadSigner,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const checkpointSignature = await payloadSigner(checkpointHash, checkpointContext);
|
|
195
|
-
|
|
196
|
-
return new CheckpointProposal(checkpointHeader, archiveRoot, feeAssetPriceModifier, checkpointSignature, {
|
|
197
|
-
blockHeader: lastBlockInfo.blockHeader,
|
|
198
|
-
indexWithinCheckpoint: lastBlockInfo.indexWithinCheckpoint,
|
|
199
|
-
txHashes: lastBlockInfo.txHashes,
|
|
200
|
-
signature: lastBlockProposal.signature,
|
|
201
|
-
signedTxs: lastBlockProposal.signedTxs,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
182
|
const checkpointSignature = await payloadSigner(checkpointHash, checkpointContext);
|
|
206
|
-
|
|
183
|
+
|
|
184
|
+
return new CheckpointProposal(
|
|
185
|
+
checkpointHeader,
|
|
186
|
+
archiveRoot,
|
|
187
|
+
feeAssetPriceModifier,
|
|
188
|
+
checkpointSignature,
|
|
189
|
+
lastBlockProposal,
|
|
190
|
+
);
|
|
207
191
|
}
|
|
208
192
|
|
|
209
193
|
/**
|
package/src/slashing/index.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { times } from '@aztec/foundation/collection';
|
|
2
1
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
2
|
import { BufferReader, bigintToUInt64BE, bigintToUInt128BE, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
4
3
|
|
|
5
|
-
import type {
|
|
6
|
-
Offense,
|
|
7
|
-
OffenseType,
|
|
8
|
-
SlashPayload,
|
|
9
|
-
SlashPayloadRound,
|
|
10
|
-
ValidatorSlash,
|
|
11
|
-
ValidatorSlashOffense,
|
|
12
|
-
} from './types.js';
|
|
4
|
+
import type { Offense, OffenseType } from './types.js';
|
|
13
5
|
|
|
14
6
|
export function serializeOffense(offense: Offense): Buffer {
|
|
15
7
|
return serializeToBuffer(
|
|
@@ -29,75 +21,3 @@ export function deserializeOffense(buffer: Buffer): Offense {
|
|
|
29
21
|
|
|
30
22
|
return { validator, amount, offenseType: offense, epochOrSlot };
|
|
31
23
|
}
|
|
32
|
-
|
|
33
|
-
function serializeValidatorSlashOffense(offense: ValidatorSlashOffense): Buffer {
|
|
34
|
-
return serializeToBuffer(bigintToUInt64BE(offense.epochOrSlot), offense.offenseType);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function deserializeValidatorSlashOffense(buffer: Buffer | BufferReader): ValidatorSlashOffense {
|
|
38
|
-
const reader = BufferReader.asReader(buffer);
|
|
39
|
-
return {
|
|
40
|
-
epochOrSlot: reader.readUInt64(),
|
|
41
|
-
offenseType: reader.readNumber() as OffenseType,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function serializeValidatorSlash(slash: ValidatorSlash): Buffer {
|
|
46
|
-
return serializeToBuffer(
|
|
47
|
-
slash.validator,
|
|
48
|
-
bigintToUInt128BE(slash.amount),
|
|
49
|
-
slash.offenses.length,
|
|
50
|
-
slash.offenses.map(serializeValidatorSlashOffense),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function deserializeValidatorSlash(buffer: Buffer | BufferReader): ValidatorSlash {
|
|
55
|
-
const reader = BufferReader.asReader(buffer);
|
|
56
|
-
const validator = reader.readObject(EthAddress);
|
|
57
|
-
const amount = reader.readUInt128();
|
|
58
|
-
const offensesCount = reader.readNumber();
|
|
59
|
-
const offenses = times(offensesCount, () => deserializeValidatorSlashOffense(reader));
|
|
60
|
-
|
|
61
|
-
return { validator, amount, offenses };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function serializeSlashPayload(payload: SlashPayload): Buffer {
|
|
65
|
-
return serializeToBuffer(
|
|
66
|
-
payload.address,
|
|
67
|
-
payload.slashes.length,
|
|
68
|
-
payload.slashes.map(serializeValidatorSlash),
|
|
69
|
-
bigintToUInt64BE(payload.timestamp),
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function deserializeSlashPayload(buffer: Buffer): SlashPayload {
|
|
74
|
-
const reader = BufferReader.asReader(buffer);
|
|
75
|
-
const address = reader.readObject(EthAddress);
|
|
76
|
-
const slashesCount = reader.readNumber();
|
|
77
|
-
const slashes = times(slashesCount, () => deserializeValidatorSlash(reader));
|
|
78
|
-
const timestamp = reader.readUInt64();
|
|
79
|
-
return { address, slashes, timestamp };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function serializeSlashPayloadRound(payload: SlashPayloadRound): Buffer {
|
|
83
|
-
return serializeToBuffer(
|
|
84
|
-
payload.address,
|
|
85
|
-
payload.slashes.length,
|
|
86
|
-
payload.slashes.map(serializeValidatorSlash),
|
|
87
|
-
bigintToUInt64BE(payload.timestamp),
|
|
88
|
-
Number(payload.votes),
|
|
89
|
-
bigintToUInt64BE(payload.round),
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function deserializeSlashPayloadRound(buffer: Buffer): SlashPayloadRound {
|
|
94
|
-
const reader = BufferReader.asReader(buffer);
|
|
95
|
-
const address = reader.readObject(EthAddress);
|
|
96
|
-
const slashesCount = reader.readNumber();
|
|
97
|
-
const slashes = times(slashesCount, () => deserializeValidatorSlash(reader));
|
|
98
|
-
const timestamp = reader.readUInt64();
|
|
99
|
-
const votes = BigInt(reader.readNumber());
|
|
100
|
-
const round = reader.readUInt64();
|
|
101
|
-
|
|
102
|
-
return { address, slashes, timestamp, votes, round };
|
|
103
|
-
}
|
package/src/slashing/types.ts
CHANGED
|
@@ -126,45 +126,13 @@ export type ValidatorSlash = {
|
|
|
126
126
|
offenses: ValidatorSlashOffense[];
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
-
/**
|
|
130
|
-
export type SlashPayload = {
|
|
131
|
-
address: EthAddress;
|
|
132
|
-
slashes: ValidatorSlash[];
|
|
133
|
-
timestamp: bigint;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
/** Slash payload with round information from empire slash proposer */
|
|
137
|
-
export type SlashPayloadRound = SlashPayload & { votes: bigint; round: bigint };
|
|
138
|
-
|
|
139
|
-
export const SlashPayloadRoundSchema = zodFor<SlashPayloadRound>()(
|
|
140
|
-
z.object({
|
|
141
|
-
address: schemas.EthAddress,
|
|
142
|
-
timestamp: schemas.BigInt,
|
|
143
|
-
votes: schemas.BigInt,
|
|
144
|
-
round: schemas.BigInt,
|
|
145
|
-
slashes: z.array(
|
|
146
|
-
z.object({
|
|
147
|
-
validator: schemas.EthAddress,
|
|
148
|
-
amount: schemas.BigInt,
|
|
149
|
-
offenses: z.array(z.object({ offenseType: OffenseTypeSchema, epochOrSlot: schemas.BigInt })),
|
|
150
|
-
}),
|
|
151
|
-
),
|
|
152
|
-
}),
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
/** Votes for a validator slash in the consensus slash proposer */
|
|
129
|
+
/** Votes for a validator slash in the slashing proposer */
|
|
156
130
|
export type ValidatorSlashVote = number;
|
|
157
131
|
|
|
158
132
|
export type ProposerSlashAction =
|
|
159
|
-
/**
|
|
160
|
-
| { type: 'create-empire-payload'; data: ValidatorSlash[] }
|
|
161
|
-
/** Vote for a slashing payload on an empire-based slash proposer */
|
|
162
|
-
| { type: 'vote-empire-payload'; payload: EthAddress }
|
|
163
|
-
/** Execute a slashing payload on an empire-based slash proposer */
|
|
164
|
-
| { type: 'execute-empire-payload'; round: bigint }
|
|
165
|
-
/** Vote for offenses on a consensus slashing proposer */
|
|
133
|
+
/** Vote for offenses on the slashing proposer */
|
|
166
134
|
| { type: 'vote-offenses'; votes: ValidatorSlashVote[]; committees: EthAddress[][]; round: bigint }
|
|
167
|
-
/** Execute a slashing round on
|
|
135
|
+
/** Execute a slashing round on the slashing proposer */
|
|
168
136
|
| { type: 'execute-slash'; committees: EthAddress[][]; round: bigint };
|
|
169
137
|
|
|
170
138
|
export type ProposerSlashActionType = ProposerSlashAction['type'];
|
|
@@ -28,7 +28,7 @@ export function getSlashConsensusVotesFromOffenses(
|
|
|
28
28
|
targetCommitteeSize: number;
|
|
29
29
|
maxSlashedValidators?: number;
|
|
30
30
|
},
|
|
31
|
-
logger: Logger = createLogger('slasher:
|
|
31
|
+
logger: Logger = createLogger('slasher:votes'),
|
|
32
32
|
): ValidatorSlashVote[] {
|
|
33
33
|
const { slashingAmounts, targetCommitteeSize, maxSlashedValidators } = settings;
|
|
34
34
|
|
package/src/tests/factories.ts
CHANGED
|
@@ -232,7 +232,7 @@ export function makeTxContext(seed: number = 1): TxContext {
|
|
|
232
232
|
* Creates a default instance of gas settings. No seed value is used to ensure we allocate a sensible amount of gas for testing.
|
|
233
233
|
*/
|
|
234
234
|
export function makeGasSettings() {
|
|
235
|
-
return GasSettings.
|
|
235
|
+
return GasSettings.fallback({ maxFeesPerGas: new GasFees(10, 10) });
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
/**
|
package/src/tests/mocks.ts
CHANGED
|
@@ -134,7 +134,7 @@ export const mockTx = async (
|
|
|
134
134
|
const data = PrivateKernelTailCircuitPublicInputs.empty();
|
|
135
135
|
const firstNullifier = new Nullifier(new Fr(seed + 1), Fr.ZERO, 0);
|
|
136
136
|
data.constants.anchorBlockHeader = anchorBlockHeader;
|
|
137
|
-
data.constants.txContext.gasSettings = GasSettings.
|
|
137
|
+
data.constants.txContext.gasSettings = GasSettings.fallback({ gasLimits, maxFeesPerGas, maxPriorityFeesPerGas });
|
|
138
138
|
data.feePayer = feePayer ?? (await AztecAddress.random());
|
|
139
139
|
data.gasUsed = gasUsed;
|
|
140
140
|
data.constants.txContext.chainId = chainId;
|
|
@@ -200,7 +200,7 @@ export async function mockProcessedTx({
|
|
|
200
200
|
db,
|
|
201
201
|
chainId = Fr.ZERO,
|
|
202
202
|
version = Fr.ZERO,
|
|
203
|
-
gasSettings = GasSettings.
|
|
203
|
+
gasSettings = GasSettings.fallback({ maxFeesPerGas: new GasFees(10, 10) }),
|
|
204
204
|
vkTreeRoot = Fr.ZERO,
|
|
205
205
|
protocolContracts = makeProtocolContracts(seed + 0x100),
|
|
206
206
|
globalVariables = GlobalVariables.empty(),
|
|
@@ -596,28 +596,30 @@ export const makeBlockProposal = (options?: MakeBlockProposalOptions): Promise<B
|
|
|
596
596
|
);
|
|
597
597
|
};
|
|
598
598
|
|
|
599
|
-
export const makeCheckpointProposal = (options?: MakeCheckpointProposalOptions): Promise<CheckpointProposal> => {
|
|
600
|
-
const blockHeader = options?.lastBlock?.blockHeader ?? makeBlockHeader(1);
|
|
599
|
+
export const makeCheckpointProposal = async (options?: MakeCheckpointProposalOptions): Promise<CheckpointProposal> => {
|
|
601
600
|
const checkpointHeader = options?.checkpointHeader ?? makeCheckpointHeader(1);
|
|
602
601
|
const archiveRoot = options?.archiveRoot ?? Fr.random();
|
|
603
602
|
const feeAssetPriceModifier = options?.feeAssetPriceModifier ?? 0n;
|
|
604
603
|
const signer = options?.signer ?? Secp256k1Signer.random();
|
|
605
604
|
|
|
606
|
-
// Build
|
|
607
|
-
const
|
|
608
|
-
? {
|
|
609
|
-
blockHeader,
|
|
610
|
-
indexWithinCheckpoint: options.lastBlock.indexWithinCheckpoint ?? IndexWithinCheckpoint(4),
|
|
611
|
-
|
|
605
|
+
// Build a signed block proposal if lastBlock options are provided
|
|
606
|
+
const lastBlockProposal = options?.lastBlock
|
|
607
|
+
? await makeBlockProposal({
|
|
608
|
+
blockHeader: options.lastBlock.blockHeader,
|
|
609
|
+
indexWithinCheckpoint: options.lastBlock.indexWithinCheckpoint ?? IndexWithinCheckpoint(4),
|
|
610
|
+
inHash: checkpointHeader.inHash,
|
|
611
|
+
archiveRoot,
|
|
612
|
+
txHashes: options.lastBlock.txHashes,
|
|
612
613
|
txs: options.lastBlock.txs,
|
|
613
|
-
|
|
614
|
+
signer,
|
|
615
|
+
})
|
|
614
616
|
: undefined;
|
|
615
617
|
|
|
616
618
|
return CheckpointProposal.createProposalFromSigner(
|
|
617
619
|
checkpointHeader,
|
|
618
620
|
archiveRoot,
|
|
619
621
|
feeAssetPriceModifier,
|
|
620
|
-
|
|
622
|
+
lastBlockProposal,
|
|
621
623
|
payload => Promise.resolve(signer.signMessage(payload)),
|
|
622
624
|
);
|
|
623
625
|
};
|
package/src/timetable/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ export function calculateMaxBlocksPerSlot(
|
|
|
42
42
|
checkpointAssembleTime?: number;
|
|
43
43
|
p2pPropagationTime?: number;
|
|
44
44
|
l1PublishingTime?: number;
|
|
45
|
+
pipelining?: boolean;
|
|
45
46
|
} = {},
|
|
46
47
|
): number {
|
|
47
48
|
if (!blockDurationSec) {
|
|
@@ -56,8 +57,12 @@ export function calculateMaxBlocksPerSlot(
|
|
|
56
57
|
// Calculate checkpoint finalization time (assembly + round-trip propagation + L1 publishing)
|
|
57
58
|
const checkpointFinalizationTime = assembleTime + p2pTime * 2 + l1Time;
|
|
58
59
|
|
|
59
|
-
//
|
|
60
|
-
|
|
60
|
+
// When pipelining, finalization is deferred to the next slot, but we still reserve
|
|
61
|
+
// a sub-slot for validator re-execution so they can produce attestations.
|
|
62
|
+
let timeReservedAtEnd = blockDurationSec;
|
|
63
|
+
if (!opts.pipelining) {
|
|
64
|
+
timeReservedAtEnd += checkpointFinalizationTime;
|
|
65
|
+
}
|
|
61
66
|
|
|
62
67
|
// Time available for building blocks
|
|
63
68
|
const timeAvailableForBlocks = aztecSlotDurationSec - initOffset - timeReservedAtEnd;
|
package/src/tx/simulated_tx.ts
CHANGED
|
@@ -38,7 +38,11 @@ export type ContractOverrides = Record<
|
|
|
38
38
|
* set, it *must* be run without the kernel circuits, or validations will fail
|
|
39
39
|
*/
|
|
40
40
|
export class SimulationOverrides {
|
|
41
|
-
|
|
41
|
+
public contracts?: ContractOverrides;
|
|
42
|
+
|
|
43
|
+
constructor(contracts: ContractOverrides = {}) {
|
|
44
|
+
this.contracts = Object.keys(contracts).length > 0 ? contracts : undefined;
|
|
45
|
+
}
|
|
42
46
|
|
|
43
47
|
static get schema() {
|
|
44
48
|
return z
|
package/src/tx/tx_receipt.ts
CHANGED
|
@@ -31,9 +31,16 @@ export const SortedTxStatuses: TxStatus[] = [
|
|
|
31
31
|
/** Execution result - only set when tx is in a block. */
|
|
32
32
|
export enum TxExecutionResult {
|
|
33
33
|
SUCCESS = 'success',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
REVERTED = 'reverted',
|
|
35
|
+
/** @deprecated Use REVERTED instead. */
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
37
|
+
APP_LOGIC_REVERTED = 'reverted',
|
|
38
|
+
/** @deprecated Use REVERTED instead. */
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
40
|
+
TEARDOWN_REVERTED = 'reverted',
|
|
41
|
+
/** @deprecated Use REVERTED instead. */
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
|
|
43
|
+
BOTH_REVERTED = 'reverted',
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
/**
|
|
@@ -143,16 +150,6 @@ export class TxReceipt {
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
public static executionResultFromRevertCode(revertCode: RevertCode): TxExecutionResult {
|
|
146
|
-
|
|
147
|
-
return TxExecutionResult.SUCCESS;
|
|
148
|
-
} else if (revertCode.equals(RevertCode.APP_LOGIC_REVERTED)) {
|
|
149
|
-
return TxExecutionResult.APP_LOGIC_REVERTED;
|
|
150
|
-
} else if (revertCode.equals(RevertCode.TEARDOWN_REVERTED)) {
|
|
151
|
-
return TxExecutionResult.TEARDOWN_REVERTED;
|
|
152
|
-
} else if (revertCode.equals(RevertCode.BOTH_REVERTED)) {
|
|
153
|
-
return TxExecutionResult.BOTH_REVERTED;
|
|
154
|
-
} else {
|
|
155
|
-
throw new Error(`Unknown revert code: ${revertCode.getCode()}`);
|
|
156
|
-
}
|
|
153
|
+
return revertCode.isOK() ? TxExecutionResult.SUCCESS : TxExecutionResult.REVERTED;
|
|
157
154
|
}
|
|
158
155
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PublicDataTreeLeaf } from '../trees/index.js';
|
|
2
|
+
|
|
3
|
+
/** Data used to initialize the genesis block, including prefilled public state and an optional timestamp. */
|
|
4
|
+
export type GenesisData = {
|
|
5
|
+
/** Public data tree leaves to pre-populate in the genesis state (e.g. fee juice balances). */
|
|
6
|
+
prefilledPublicData: PublicDataTreeLeaf[];
|
|
7
|
+
/** Timestamp for the genesis block header. Defaults to 0 (canonical empty genesis) in production. */
|
|
8
|
+
genesisTimestamp: bigint;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** An empty genesis data with no prefilled state and a zero timestamp. */
|
|
12
|
+
export const EMPTY_GENESIS_DATA: GenesisData = {
|
|
13
|
+
prefilledPublicData: [],
|
|
14
|
+
genesisTimestamp: 0n,
|
|
15
|
+
};
|
package/src/world-state/index.ts
CHANGED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { L1TxRequest } from '@aztec/ethereum/l1-tx-utils';
|
|
2
|
-
import type { ViemClient } from '@aztec/ethereum/types';
|
|
3
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
-
import { type Hex, type Log } from 'viem';
|
|
5
|
-
import type { L1RollupConstants } from '../epoch-helpers/index.js';
|
|
6
|
-
import { type SlashPayload, type ValidatorSlash, type ValidatorSlashOffense } from '../slashing/index.js';
|
|
7
|
-
export declare class SlashFactoryContract {
|
|
8
|
-
readonly client: ViemClient;
|
|
9
|
-
private readonly logger;
|
|
10
|
-
private readonly contract;
|
|
11
|
-
constructor(client: ViemClient, address: Hex | EthAddress);
|
|
12
|
-
get address(): EthAddress;
|
|
13
|
-
buildCreatePayloadRequest(slashes: ValidatorSlash[]): L1TxRequest;
|
|
14
|
-
/** Tries to extract a SlashPayloadCreated event from the given logs. */
|
|
15
|
-
tryExtractSlashPayloadCreatedEvent(logs: Log[]): {
|
|
16
|
-
eventName: "SlashPayloadCreated";
|
|
17
|
-
args: {
|
|
18
|
-
amounts: readonly bigint[];
|
|
19
|
-
offenses: readonly (readonly bigint[])[];
|
|
20
|
-
payloadAddress: `0x${string}`;
|
|
21
|
-
validators: readonly `0x${string}`[];
|
|
22
|
-
};
|
|
23
|
-
} | undefined;
|
|
24
|
-
getSlashPayloadCreatedEvents(): Promise<SlashPayload[]>;
|
|
25
|
-
/**
|
|
26
|
-
* Searches for a slash payload in the events emitted by the contract.
|
|
27
|
-
* This method cannot query for historical payload events, it queries for payloads that have not yet expired.
|
|
28
|
-
* @param payloadAddress The address of the payload to search for.
|
|
29
|
-
* @param constants The L1 rollup constants needed for time calculations.
|
|
30
|
-
*/
|
|
31
|
-
getSlashPayloadFromEvents(payloadAddress: EthAddress, settings: {
|
|
32
|
-
logsBatchSize?: number;
|
|
33
|
-
slashingRoundSize: number;
|
|
34
|
-
slashingPayloadLifetimeInRounds: number;
|
|
35
|
-
} & Pick<L1RollupConstants, 'slotDuration' | 'ethereumSlotDuration'>): Promise<Omit<SlashPayload, 'votes'> | undefined>;
|
|
36
|
-
getAddressAndIsDeployed(slashes: ValidatorSlash[]): Promise<{
|
|
37
|
-
address: EthAddress;
|
|
38
|
-
salt: Hex;
|
|
39
|
-
isDeployed: boolean;
|
|
40
|
-
}>;
|
|
41
|
-
private sortSlashes;
|
|
42
|
-
}
|
|
43
|
-
export declare function packValidatorSlashOffense(offense: ValidatorSlashOffense): bigint;
|
|
44
|
-
export declare function unpackValidatorSlashOffense(packed: bigint): ValidatorSlashOffense;
|
|
45
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2xhc2hfZmFjdG9yeS5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2wxLWNvbnRyYWN0cy9zbGFzaF9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxFQUFFLFdBQVcsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQy9ELE9BQU8sS0FBSyxFQUFFLFVBQVUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBR3hELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUkzRCxPQUFPLEVBQThCLEtBQUssR0FBRyxFQUFFLEtBQUssR0FBRyxFQUFtQyxNQUFNLE1BQU0sQ0FBQztBQUV2RyxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQ25FLE9BQU8sRUFFTCxLQUFLLFlBQVksRUFDakIsS0FBSyxjQUFjLEVBQ25CLEtBQUsscUJBQXFCLEVBRTNCLE1BQU0sc0JBQXNCLENBQUM7QUFFOUIscUJBQWEsb0JBQW9CO2FBS2IsTUFBTSxFQUFFLFVBQVU7SUFKcEMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQTJDO0lBQ2xFLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUE0RDtJQUVyRixZQUNrQixNQUFNLEVBQUUsVUFBVSxFQUNsQyxPQUFPLEVBQUUsR0FBRyxHQUFHLFVBQVUsRUFPMUI7SUFFRCxJQUFXLE9BQU8sZUFFakI7SUFFTSx5QkFBeUIsQ0FBQyxPQUFPLEVBQUUsY0FBYyxFQUFFLEdBQUcsV0FBVyxDQWdCdkU7SUFFRCx3RUFBd0U7SUFDakUsa0NBQWtDLENBQUMsSUFBSSxFQUFFLEdBQUcsRUFBRTs7Ozs7Ozs7a0JBRXBEO0lBRVksNEJBQTRCLElBQUksT0FBTyxDQUFDLFlBQVksRUFBRSxDQUFDLENBZW5FO0lBRUQ7Ozs7O09BS0c7SUFDVSx5QkFBeUIsQ0FDcEMsY0FBYyxFQUFFLFVBQVUsRUFDMUIsUUFBUSxFQUFFO1FBQ1IsYUFBYSxDQUFDLEVBQUUsTUFBTSxDQUFDO1FBQ3ZCLGlCQUFpQixFQUFFLE1BQU0sQ0FBQztRQUMxQiwrQkFBK0IsRUFBRSxNQUFNLENBQUM7S0FDekMsR0FBRyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsY0FBYyxHQUFHLHNCQUFzQixDQUFDLEdBQ25FLE9BQU8sQ0FBQyxJQUFJLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxHQUFHLFNBQVMsQ0FBQyxDQW1EbEQ7SUFFWSx1QkFBdUIsQ0FDbEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxHQUN4QixPQUFPLENBQUM7UUFBRSxPQUFPLEVBQUUsVUFBVSxDQUFDO1FBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQztRQUFDLFVBQVUsRUFBRSxPQUFPLENBQUE7S0FBRSxDQUFDLENBUWxFO0lBRUQsT0FBTyxDQUFDLFdBQVc7Q0FRcEI7QUFFRCx3QkFBZ0IseUJBQXlCLENBQUMsT0FBTyxFQUFFLHFCQUFxQixHQUFHLE1BQU0sQ0FNaEY7QUFFRCx3QkFBZ0IsMkJBQTJCLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxxQkFBcUIsQ0FLakYifQ==
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slash_factory.d.ts","sourceRoot":"","sources":["../../src/l1-contracts/slash_factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAI3D,OAAO,EAA8B,KAAK,GAAG,EAAE,KAAK,GAAG,EAAmC,MAAM,MAAM,CAAC;AAEvG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAE3B,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,oBAAoB;aAKb,MAAM,EAAE,UAAU;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2C;IAClE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4D;IAErF,YACkB,MAAM,EAAE,UAAU,EAClC,OAAO,EAAE,GAAG,GAAG,UAAU,EAO1B;IAED,IAAW,OAAO,eAEjB;IAEM,yBAAyB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,WAAW,CAgBvE;IAED,wEAAwE;IACjE,kCAAkC,CAAC,IAAI,EAAE,GAAG,EAAE;;;;;;;;kBAEpD;IAEY,4BAA4B,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAenE;IAED;;;;;OAKG;IACU,yBAAyB,CACpC,cAAc,EAAE,UAAU,EAC1B,QAAQ,EAAE;QACR,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,+BAA+B,EAAE,MAAM,CAAC;KACzC,GAAG,IAAI,CAAC,iBAAiB,EAAE,cAAc,GAAG,sBAAsB,CAAC,GACnE,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAmDlD;IAEY,uBAAuB,CAClC,OAAO,EAAE,cAAc,EAAE,GACxB,OAAO,CAAC;QAAE,OAAO,EAAE,UAAU,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC,CAQlE;IAED,OAAO,CAAC,WAAW;CAQpB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAMhF;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,qBAAqB,CAKjF"}
|