@aztec/ethereum 3.0.0-nightly.20250904 → 3.0.0-nightly.20250906
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/config.d.ts.map +1 -1
- package/dest/config.js +40 -15
- package/dest/contracts/rollup.d.ts +12 -0
- package/dest/contracts/rollup.d.ts.map +1 -1
- package/dest/contracts/rollup.js +23 -0
- package/dest/contracts/tally_slashing_proposer.d.ts +15 -2
- package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
- package/dest/contracts/tally_slashing_proposer.js +43 -3
- package/dest/l1_artifacts.d.ts +68 -12
- package/dest/l1_artifacts.d.ts.map +1 -1
- package/dest/l1_contract_addresses.d.ts.map +1 -1
- package/dest/l1_contract_addresses.js +14 -25
- package/dest/l1_reader.js +8 -8
- package/dest/test/chain_monitor.d.ts +7 -0
- package/dest/test/chain_monitor.d.ts.map +1 -1
- package/dest/test/chain_monitor.js +24 -1
- package/package.json +5 -5
- package/src/config.ts +40 -15
- package/src/contracts/rollup.ts +29 -0
- package/src/contracts/tally_slashing_proposer.ts +42 -3
- package/src/l1_contract_addresses.ts +14 -25
- package/src/l1_reader.ts +8 -8
- package/src/test/chain_monitor.ts +25 -1
package/src/config.ts
CHANGED
|
@@ -118,10 +118,15 @@ const TestnetGovernanceConfiguration = {
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
export const getGovernanceConfiguration = (networkName: NetworkNames) => {
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
switch (networkName) {
|
|
122
|
+
case 'alpha-testnet':
|
|
123
|
+
case 'testnet':
|
|
124
|
+
return TestnetGovernanceConfiguration;
|
|
125
|
+
case 'local':
|
|
126
|
+
return LocalGovernanceConfiguration;
|
|
127
|
+
default:
|
|
128
|
+
throw new Error('Unrecognized network name: ' + networkName);
|
|
123
129
|
}
|
|
124
|
-
return LocalGovernanceConfiguration;
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
const TestnetGSEConfiguration = {
|
|
@@ -135,10 +140,15 @@ const LocalGSEConfiguration = {
|
|
|
135
140
|
};
|
|
136
141
|
|
|
137
142
|
export const getGSEConfiguration = (networkName: NetworkNames) => {
|
|
138
|
-
|
|
139
|
-
|
|
143
|
+
switch (networkName) {
|
|
144
|
+
case 'alpha-testnet':
|
|
145
|
+
case 'testnet':
|
|
146
|
+
return TestnetGSEConfiguration;
|
|
147
|
+
case 'local':
|
|
148
|
+
return LocalGSEConfiguration;
|
|
149
|
+
default:
|
|
150
|
+
throw new Error('Unrecognized network name: ' + networkName);
|
|
140
151
|
}
|
|
141
|
-
return LocalGSEConfiguration;
|
|
142
152
|
};
|
|
143
153
|
|
|
144
154
|
// Making a default config here as we are only using it thought the deployment
|
|
@@ -159,10 +169,15 @@ const TestnetRewardConfig = {
|
|
|
159
169
|
};
|
|
160
170
|
|
|
161
171
|
export const getRewardConfig = (networkName: NetworkNames) => {
|
|
162
|
-
|
|
163
|
-
|
|
172
|
+
switch (networkName) {
|
|
173
|
+
case 'alpha-testnet':
|
|
174
|
+
case 'testnet':
|
|
175
|
+
return TestnetRewardConfig;
|
|
176
|
+
case 'local':
|
|
177
|
+
return LocalRewardConfig;
|
|
178
|
+
default:
|
|
179
|
+
throw new Error('Unrecognized network name: ' + networkName);
|
|
164
180
|
}
|
|
165
|
-
return LocalRewardConfig;
|
|
166
181
|
};
|
|
167
182
|
|
|
168
183
|
const LocalRewardBoostConfig = {
|
|
@@ -182,10 +197,15 @@ const TestnetRewardBoostConfig = {
|
|
|
182
197
|
};
|
|
183
198
|
|
|
184
199
|
export const getRewardBoostConfig = (networkName: NetworkNames) => {
|
|
185
|
-
|
|
186
|
-
|
|
200
|
+
switch (networkName) {
|
|
201
|
+
case 'alpha-testnet':
|
|
202
|
+
case 'testnet':
|
|
203
|
+
return TestnetRewardBoostConfig;
|
|
204
|
+
case 'local':
|
|
205
|
+
return LocalRewardBoostConfig;
|
|
206
|
+
default:
|
|
207
|
+
throw new Error('Unrecognized network name: ' + networkName);
|
|
187
208
|
}
|
|
188
|
-
return LocalRewardBoostConfig;
|
|
189
209
|
};
|
|
190
210
|
|
|
191
211
|
// Similar to the above, no need for environment variables for this.
|
|
@@ -206,10 +226,15 @@ const TestnetEntryQueueConfig = {
|
|
|
206
226
|
};
|
|
207
227
|
|
|
208
228
|
export const getEntryQueueConfig = (networkName: NetworkNames) => {
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
switch (networkName) {
|
|
230
|
+
case 'alpha-testnet':
|
|
231
|
+
case 'testnet':
|
|
232
|
+
return TestnetEntryQueueConfig;
|
|
233
|
+
case 'local':
|
|
234
|
+
return LocalEntryQueueConfig;
|
|
235
|
+
default:
|
|
236
|
+
throw new Error('Unrecognized network name: ' + networkName);
|
|
211
237
|
}
|
|
212
|
-
return LocalEntryQueueConfig;
|
|
213
238
|
};
|
|
214
239
|
|
|
215
240
|
export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> = {
|
package/src/contracts/rollup.ts
CHANGED
|
@@ -264,6 +264,35 @@ export class RollupContract {
|
|
|
264
264
|
return block.archive;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Returns rollup constants used for epoch queries.
|
|
269
|
+
* Return type is `L1RollupConstants` which is defined in stdlib,
|
|
270
|
+
* so we cant reference it until we move this contract to that package.
|
|
271
|
+
*/
|
|
272
|
+
@memoize
|
|
273
|
+
public async getRollupConstants(): Promise<{
|
|
274
|
+
l1StartBlock: bigint;
|
|
275
|
+
l1GenesisTime: bigint;
|
|
276
|
+
slotDuration: number;
|
|
277
|
+
epochDuration: number;
|
|
278
|
+
proofSubmissionEpochs: number;
|
|
279
|
+
}> {
|
|
280
|
+
const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
|
|
281
|
+
this.getL1StartBlock(),
|
|
282
|
+
this.getL1GenesisTime(),
|
|
283
|
+
this.getSlotDuration(),
|
|
284
|
+
this.getEpochDuration(),
|
|
285
|
+
this.getProofSubmissionEpochs(),
|
|
286
|
+
]);
|
|
287
|
+
return {
|
|
288
|
+
l1StartBlock,
|
|
289
|
+
l1GenesisTime,
|
|
290
|
+
slotDuration: Number(slotDuration),
|
|
291
|
+
epochDuration: Number(epochDuration),
|
|
292
|
+
proofSubmissionEpochs: Number(proofSubmissionEpochs),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
267
296
|
getSlasher() {
|
|
268
297
|
return this.rollup.read.getSlasher();
|
|
269
298
|
}
|
|
@@ -2,9 +2,9 @@ import { type L1TxRequest, type ViemClient, tryExtractEvent } from '@aztec/ether
|
|
|
2
2
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
5
|
+
import { hexToBuffer } from '@aztec/foundation/string';
|
|
5
6
|
import { TallySlashingProposerAbi } from '@aztec/l1-artifacts/TallySlashingProposerAbi';
|
|
6
7
|
|
|
7
|
-
import EventEmitter from 'events';
|
|
8
8
|
import {
|
|
9
9
|
type GetContractReturnType,
|
|
10
10
|
type Hex,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
* Wrapper around the TallySlashingProposer contract that provides
|
|
19
19
|
* a TypeScript interface for interacting with the consensus-based slashing system.
|
|
20
20
|
*/
|
|
21
|
-
export class TallySlashingProposerContract
|
|
21
|
+
export class TallySlashingProposerContract {
|
|
22
22
|
private readonly contract: GetContractReturnType<typeof TallySlashingProposerAbi, ViemClient>;
|
|
23
23
|
|
|
24
24
|
public readonly type = 'tally' as const;
|
|
@@ -27,7 +27,6 @@ export class TallySlashingProposerContract extends EventEmitter {
|
|
|
27
27
|
public readonly client: ViemClient,
|
|
28
28
|
address: Hex | EthAddress,
|
|
29
29
|
) {
|
|
30
|
-
super();
|
|
31
30
|
this.contract = getContract({
|
|
32
31
|
address: typeof address === 'string' ? address : address.toString(),
|
|
33
32
|
abi: TallySlashingProposerAbi,
|
|
@@ -221,6 +220,21 @@ export class TallySlashingProposerContract extends EventEmitter {
|
|
|
221
220
|
};
|
|
222
221
|
}
|
|
223
222
|
|
|
223
|
+
/** Returns the last vote emitted for a given round */
|
|
224
|
+
public async getLastVote(round: bigint) {
|
|
225
|
+
const { voteCount } = await this.getRound(round);
|
|
226
|
+
const validators = (await this.contract.simulate.getSlashTargetCommittees([round])).result.flat();
|
|
227
|
+
const vote = await this.contract.read.getVotes([round, voteCount - 1n]);
|
|
228
|
+
const decoded = decodeSlashConsensusVotes(hexToBuffer(vote));
|
|
229
|
+
const slashAmounts = await this.getSlashingAmounts();
|
|
230
|
+
return decoded
|
|
231
|
+
.map((units, i) => ({
|
|
232
|
+
validator: EthAddress.fromString(validators[i]),
|
|
233
|
+
slashAmount: slashAmounts[units - 1] ?? 0n,
|
|
234
|
+
}))
|
|
235
|
+
.filter(v => v.slashAmount > 0n);
|
|
236
|
+
}
|
|
237
|
+
|
|
224
238
|
/**
|
|
225
239
|
* Listen for VoteCast events
|
|
226
240
|
* @param callback - Callback function to handle vote cast events
|
|
@@ -265,3 +279,28 @@ export class TallySlashingProposerContract extends EventEmitter {
|
|
|
265
279
|
);
|
|
266
280
|
}
|
|
267
281
|
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Decodes a Buffer containing slash votes back into an array of numbers.
|
|
285
|
+
* Each vote is represented as a 2-bit value (0, 1, 2, or 3) representing slashing units.
|
|
286
|
+
* @dev This should live in stdlib next to encodeSlashConsensusVotes but is here since we
|
|
287
|
+
* do not have a dependency to stdlib from the ethereum package. We need a larger refactor to fix this.
|
|
288
|
+
* @param buffer - The Buffer containing encoded slash votes
|
|
289
|
+
* @returns An array of numbers representing the slash votes
|
|
290
|
+
*/
|
|
291
|
+
export function decodeSlashConsensusVotes(buffer: Buffer): number[] {
|
|
292
|
+
const votes: number[] = [];
|
|
293
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
294
|
+
const voteByte = buffer.readUInt8(i);
|
|
295
|
+
// Decode votes from Solidity's bit order (LSB to MSB)
|
|
296
|
+
// Bits 0-1: validator at index i*4
|
|
297
|
+
// Bits 2-3: validator at index i*4+1
|
|
298
|
+
// Bits 4-5: validator at index i*4+2
|
|
299
|
+
// Bits 6-7: validator at index i*4+3
|
|
300
|
+
votes.push((voteByte >> 0) & 0x03);
|
|
301
|
+
votes.push((voteByte >> 2) & 0x03);
|
|
302
|
+
votes.push((voteByte >> 4) & 0x03);
|
|
303
|
+
votes.push((voteByte >> 6) & 0x03);
|
|
304
|
+
}
|
|
305
|
+
return votes;
|
|
306
|
+
}
|
|
@@ -58,73 +58,62 @@ const parseEnv = (val: string) => EthAddress.fromString(val);
|
|
|
58
58
|
export const l1ContractAddressesMapping: ConfigMappingsType<
|
|
59
59
|
Omit<L1ContractAddresses, 'gseAddress' | 'zkPassportVerifierAddress'>
|
|
60
60
|
> = {
|
|
61
|
-
rollupAddress: {
|
|
62
|
-
env: 'ROLLUP_CONTRACT_ADDRESS',
|
|
63
|
-
description: 'The deployed L1 rollup contract address.',
|
|
64
|
-
parseEnv,
|
|
65
|
-
},
|
|
66
61
|
registryAddress: {
|
|
67
62
|
env: 'REGISTRY_CONTRACT_ADDRESS',
|
|
68
63
|
description: 'The deployed L1 registry contract address.',
|
|
69
64
|
parseEnv,
|
|
70
65
|
},
|
|
66
|
+
slashFactoryAddress: {
|
|
67
|
+
env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
|
|
68
|
+
description: 'The deployed L1 slashFactory contract address',
|
|
69
|
+
parseEnv,
|
|
70
|
+
},
|
|
71
|
+
feeAssetHandlerAddress: {
|
|
72
|
+
env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
|
|
73
|
+
description: 'The deployed L1 feeAssetHandler contract address',
|
|
74
|
+
parseEnv,
|
|
75
|
+
},
|
|
76
|
+
rollupAddress: {
|
|
77
|
+
description: 'The deployed L1 rollup contract address.',
|
|
78
|
+
parseEnv,
|
|
79
|
+
},
|
|
71
80
|
inboxAddress: {
|
|
72
|
-
env: 'INBOX_CONTRACT_ADDRESS',
|
|
73
81
|
description: 'The deployed L1 inbox contract address.',
|
|
74
82
|
parseEnv,
|
|
75
83
|
},
|
|
76
84
|
outboxAddress: {
|
|
77
|
-
env: 'OUTBOX_CONTRACT_ADDRESS',
|
|
78
85
|
description: 'The deployed L1 outbox contract address.',
|
|
79
86
|
parseEnv,
|
|
80
87
|
},
|
|
81
88
|
feeJuiceAddress: {
|
|
82
|
-
env: 'FEE_JUICE_CONTRACT_ADDRESS',
|
|
83
89
|
description: 'The deployed L1 Fee Juice contract address.',
|
|
84
90
|
parseEnv,
|
|
85
91
|
},
|
|
86
92
|
stakingAssetAddress: {
|
|
87
|
-
env: 'STAKING_ASSET_CONTRACT_ADDRESS',
|
|
88
93
|
description: 'The deployed L1 staking asset contract address.',
|
|
89
94
|
parseEnv,
|
|
90
95
|
},
|
|
91
96
|
feeJuicePortalAddress: {
|
|
92
|
-
env: 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS',
|
|
93
97
|
description: 'The deployed L1 Fee Juice portal contract address.',
|
|
94
98
|
parseEnv,
|
|
95
99
|
},
|
|
96
100
|
coinIssuerAddress: {
|
|
97
|
-
env: 'COIN_ISSUER_CONTRACT_ADDRESS',
|
|
98
101
|
description: 'The deployed L1 coinIssuer contract address',
|
|
99
102
|
parseEnv,
|
|
100
103
|
},
|
|
101
104
|
rewardDistributorAddress: {
|
|
102
|
-
env: 'REWARD_DISTRIBUTOR_CONTRACT_ADDRESS',
|
|
103
105
|
description: 'The deployed L1 rewardDistributor contract address',
|
|
104
106
|
parseEnv,
|
|
105
107
|
},
|
|
106
108
|
governanceProposerAddress: {
|
|
107
|
-
env: 'GOVERNANCE_PROPOSER_CONTRACT_ADDRESS',
|
|
108
109
|
description: 'The deployed L1 governanceProposer contract address',
|
|
109
110
|
parseEnv,
|
|
110
111
|
},
|
|
111
112
|
governanceAddress: {
|
|
112
|
-
env: 'GOVERNANCE_CONTRACT_ADDRESS',
|
|
113
113
|
description: 'The deployed L1 governance contract address',
|
|
114
114
|
parseEnv,
|
|
115
115
|
},
|
|
116
|
-
slashFactoryAddress: {
|
|
117
|
-
env: 'SLASH_FACTORY_CONTRACT_ADDRESS',
|
|
118
|
-
description: 'The deployed L1 slashFactory contract address',
|
|
119
|
-
parseEnv,
|
|
120
|
-
},
|
|
121
|
-
feeAssetHandlerAddress: {
|
|
122
|
-
env: 'FEE_ASSET_HANDLER_CONTRACT_ADDRESS',
|
|
123
|
-
description: 'The deployed L1 feeAssetHandler contract address',
|
|
124
|
-
parseEnv,
|
|
125
|
-
},
|
|
126
116
|
stakingAssetHandlerAddress: {
|
|
127
|
-
env: 'STAKING_ASSET_HANDLER_CONTRACT_ADDRESS',
|
|
128
117
|
description: 'The deployed L1 stakingAssetHandler contract address',
|
|
129
118
|
parseEnv,
|
|
130
119
|
},
|
package/src/l1_reader.ts
CHANGED
|
@@ -15,20 +15,20 @@ export interface L1ReaderConfig {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export const l1ReaderConfigMappings: ConfigMappingsType<L1ReaderConfig> = {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
parseEnv: (val: string) => val.split(',').map(url => url.trim()),
|
|
18
|
+
l1Contracts: {
|
|
19
|
+
description: 'The deployed L1 contract addresses',
|
|
20
|
+
nested: l1ContractAddressesMapping,
|
|
22
21
|
},
|
|
23
22
|
l1ChainId: {
|
|
24
23
|
env: 'L1_CHAIN_ID',
|
|
25
24
|
parseEnv: (val: string) => +val,
|
|
26
|
-
defaultValue: 31337,
|
|
27
25
|
description: 'The chain ID of the ethereum host.',
|
|
28
26
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
l1RpcUrls: {
|
|
28
|
+
env: 'ETHEREUM_HOSTS',
|
|
29
|
+
description: 'The RPC Url of the ethereum host.',
|
|
30
|
+
parseEnv: (val: string) => val.split(',').map(url => url.trim()),
|
|
31
|
+
defaultValue: [],
|
|
32
32
|
},
|
|
33
33
|
viemPollingIntervalMS: {
|
|
34
34
|
env: 'L1_READER_VIEM_POLLING_INTERVAL_MS',
|
|
@@ -14,6 +14,7 @@ export type ChainMonitorEventMap = {
|
|
|
14
14
|
'l2-block-proven': [{ l2ProvenBlockNumber: number; l1BlockNumber: number; timestamp: bigint }];
|
|
15
15
|
'l2-messages': [{ totalL2Messages: number; l1BlockNumber: number }];
|
|
16
16
|
'l2-epoch': [{ l2EpochNumber: number; timestamp: bigint; committee: EthAddress[] | undefined }];
|
|
17
|
+
'l2-slot': [{ l2SlotNumber: number; timestamp: bigint }];
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
/** Utility class that polls the chain on quick intervals and logs new L1 blocks, L2 blocks, and L2 proofs. */
|
|
@@ -38,6 +39,8 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
|
|
|
38
39
|
public totalL2Messages: number = 0;
|
|
39
40
|
/** Current L2 epoch number */
|
|
40
41
|
public l2EpochNumber!: bigint;
|
|
42
|
+
/** Current L2 slot number */
|
|
43
|
+
public l2SlotNumber!: bigint;
|
|
41
44
|
|
|
42
45
|
constructor(
|
|
43
46
|
private readonly rollup: RollupContract,
|
|
@@ -143,7 +146,12 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
|
|
|
143
146
|
this.l2EpochNumber = l2Epoch;
|
|
144
147
|
committee = (await this.rollup.getCurrentEpochCommittee())?.map(addr => EthAddress.fromString(addr));
|
|
145
148
|
this.emit('l2-epoch', { l2EpochNumber: Number(l2Epoch), timestamp, committee });
|
|
146
|
-
msg += ` starting new epoch ${this.l2EpochNumber}
|
|
149
|
+
msg += ` starting new epoch ${this.l2EpochNumber} `;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (l2SlotNumber !== this.l2SlotNumber) {
|
|
153
|
+
this.l2SlotNumber = l2SlotNumber;
|
|
154
|
+
this.emit('l2-slot', { l2SlotNumber: Number(l2SlotNumber), timestamp });
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
this.logger.info(msg, {
|
|
@@ -160,4 +168,20 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
|
|
|
160
168
|
|
|
161
169
|
return this;
|
|
162
170
|
}
|
|
171
|
+
|
|
172
|
+
public waitUntilL2Slot(slot: number | bigint): Promise<void> {
|
|
173
|
+
const targetSlot = typeof slot === 'bigint' ? slot.valueOf() : slot;
|
|
174
|
+
if (this.l2SlotNumber >= targetSlot) {
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
}
|
|
177
|
+
return new Promise(resolve => {
|
|
178
|
+
const listener = (data: { l2SlotNumber: number; timestamp: bigint }) => {
|
|
179
|
+
if (data.l2SlotNumber >= targetSlot) {
|
|
180
|
+
this.off('l2-slot', listener);
|
|
181
|
+
resolve();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
this.on('l2-slot', listener);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
163
187
|
}
|