@aztec/slasher 2.1.0-rc.9 → 3.0.0-devnet.2
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 +60 -11
- package/dest/stores/offenses_store.js +1 -1
- package/dest/tally_slasher_client.d.ts +2 -1
- package/dest/tally_slasher_client.d.ts.map +1 -1
- package/dest/tally_slasher_client.js +22 -11
- package/package.json +10 -10
- package/src/stores/offenses_store.ts +1 -1
- package/src/tally_slasher_client.ts +29 -12
package/README.md
CHANGED
|
@@ -24,11 +24,11 @@ The system supports two slashing models:
|
|
|
24
24
|
|
|
25
25
|
_This is the model currently in use._
|
|
26
26
|
|
|
27
|
-
The tally model uses consensus-based voting where proposers vote on individual validator offenses. Time is divided into rounds, and during each round, proposers submit votes indicating which validators should be slashed. Votes are encoded as bytes where each validator's vote is represented by 2 bits indicating the slash amount (0-3 slash units) for each validator. The L1 contract tallies votes and slashes validators that reach quorum.
|
|
27
|
+
The tally model uses consensus-based voting where proposers vote on individual validator offenses. Time is divided into rounds, and during each round, proposers submit votes indicating which validators from a given past round should be slashed (eg round N votes to slash the validators from round N-2). Votes are encoded as bytes where each validator's vote is represented by 2 bits indicating the slash amount (0-3 slash units) for each validator. The L1 contract tallies votes and slashes validators that reach quorum.
|
|
28
28
|
|
|
29
29
|
Key characteristics:
|
|
30
30
|
- Proposers vote directly on validator offenses
|
|
31
|
-
-
|
|
31
|
+
- Requires a slash offset to vote on validators from past rounds
|
|
32
32
|
- Requires quorum to execute slashing
|
|
33
33
|
- L1 contract determines which offenses reach consensus
|
|
34
34
|
- Execution happens after a delay period for review
|
|
@@ -98,6 +98,8 @@ Key features:
|
|
|
98
98
|
|
|
99
99
|
## Slashable Offenses
|
|
100
100
|
|
|
101
|
+
List of all slashable offenses in the system:
|
|
102
|
+
|
|
101
103
|
### DATA_WITHHOLDING
|
|
102
104
|
**Description**: The data required for proving an epoch was not made publicly available.
|
|
103
105
|
**Detection**: EpochPruneWatcher detects when an epoch cannot be proven due to missing data.
|
|
@@ -121,7 +123,6 @@ Key features:
|
|
|
121
123
|
**Detection**: Validators detect invalid proposals during attestation validation.
|
|
122
124
|
**Target**: Proposer who broadcast the invalid block.
|
|
123
125
|
**Time Unit**: Slot-based offense.
|
|
124
|
-
**Note**: Currently not actively monitored in production.
|
|
125
126
|
|
|
126
127
|
### PROPOSED_INSUFFICIENT_ATTESTATIONS
|
|
127
128
|
**Description**: A proposer submitted a block to L1 without sufficient committee attestations.
|
|
@@ -146,24 +147,72 @@ Key features:
|
|
|
146
147
|
### L1 System Settings (L1ContractsConfig)
|
|
147
148
|
These settings are deployed with the L1 contracts and apply system-wide to the protocol:
|
|
148
149
|
|
|
149
|
-
- `
|
|
150
|
-
- `
|
|
150
|
+
- `slashingQuorumSize`: Votes required to slash (defaults to half the validators in a round, plus one)
|
|
151
|
+
- `slashingRoundSizeInEpochs`: Number of epochs per slashing round
|
|
151
152
|
- `slashingOffsetInRounds`: How many rounds to look back for offenses (tally model)
|
|
152
153
|
- `slashingExecutionDelayInRounds`: Rounds to wait before execution
|
|
153
154
|
- `slashingLifetimeInRounds`: Maximum age of executable rounds
|
|
154
155
|
- `slashingAmounts`: Valid values for each individual slash (tally model)
|
|
155
156
|
|
|
157
|
+
Considerations:
|
|
158
|
+
|
|
159
|
+
- The `slashingQuorumSize` should be more than half and less than the total number of validators in a round, so that we require a majority to slash. The number of validators in a round is the committee size times the number of epochs in a round.
|
|
160
|
+
- The bigger a `slashingRoundSizeInEpochs`, the bigger the upper bound on the quorum size. This increases security, as we need more validators to agree before slashing. However, it also makes slashing slower, and more expensive to execute in terms of gas in the tally model.
|
|
161
|
+
- The `slashingOffsetInRounds` is required because the validators in a given slashing round must vote for _past_ offenses. Otherwise, if someone commits an offense near the end of a round, they can get away with their offense without the validators being able to collect enough votes to slash them. The offset needs to be big enough so that all offenses are discoverable, so this value should be strictly greater than the proof submission window in order to be able to slash for epoch prunes or data withholding.
|
|
162
|
+
- The `slashingExecutionDelayInRounds` allows vetoers to stop an invalid slash. This should be large enough to give vetoers time to act, but strictly smaller than the validator exit window, so an offender cannot escape before they are slashed. It should also be small enough so that an offender that would be kicked out does not get picked up to be a committee member again before their slash is executed. In other words, if a validator commits a serious enough offense that we want them out of the validator set as soon as possible, the execution delay should not allow them to be chosen to participate in another committee.
|
|
163
|
+
|
|
156
164
|
### Local Node Configuration (SlasherConfig)
|
|
165
|
+
|
|
157
166
|
These settings are configured locally on each validator node:
|
|
158
167
|
|
|
159
168
|
- `slashGracePeriodL2Slots`: Number of initial L2 slots where slashing is disabled
|
|
160
|
-
- `slashMaxPayloadSize`: Maximum size of slash payloads (empire model)
|
|
161
169
|
- `slashOffenseExpirationRounds`: Number of rounds after which pending offenses expire
|
|
162
170
|
- `slashValidatorsAlways`: Array of validator addresses that should always be slashed
|
|
163
171
|
- `slashValidatorsNever`: Array of validator addresses that should never be slashed (own validator addresses are automatically added to this list)
|
|
164
|
-
- `
|
|
165
|
-
- `
|
|
166
|
-
- `
|
|
167
|
-
- `
|
|
168
|
-
- `
|
|
172
|
+
- `slashInactivityTargetPercentage`: Percentage of misses during an epoch to be slashed for INACTIVITY
|
|
173
|
+
- `slashInactivityConsecutiveEpochThreshold`: How many consecutive inactive epochs are needed to trigger an INACTIVITY slash on a validator
|
|
174
|
+
- `slashPrunePenalty`: Penalty for VALID_EPOCH_PRUNED
|
|
175
|
+
- `slashDataWithholdingPenalty`: Penalty for DATA_WITHHOLDING
|
|
176
|
+
- `slashInactivityPenalty`: Penalty for INACTIVITY
|
|
177
|
+
- `slashBroadcastedInvalidBlockPenalty`: Penalty for BROADCASTED_INVALID_BLOCK_PROPOSAL
|
|
178
|
+
- `slashProposeInvalidAttestationsPenalty`: Penalty for PROPOSED_INSUFFICIENT_ATTESTATIONS and PROPOSED_INCORRECT_ATTESTATIONS
|
|
179
|
+
- `slashAttestDescendantOfInvalidPenalty`: Penalty for ATTESTED_DESCENDANT_OF_INVALID
|
|
169
180
|
- `slashUnknownPenalty`: Default penalty for unknown offense types
|
|
181
|
+
- `slashMaxPayloadSize`: Maximum size of slash payloads (empire model)
|
|
182
|
+
- `slashMinPenaltyPercentage`: Agree to slashes if they are at least this percentage of the configured penalty (empire model)
|
|
183
|
+
- `slashMaxPenaltyPercentage`: Agree to slashes if they are at most this percentage of the configured penalty (empire model)
|
|
184
|
+
|
|
185
|
+
Considerations:
|
|
186
|
+
|
|
187
|
+
- All penalties should map to one of the `slashingAmounts`. A penalty lower than the smallest slashing amount will not be executable, and a penalty greater than the maximum will be capped at the maximum value.
|
|
188
|
+
- The `slashOffenseExpirationRounds` should be strictly larger than the `slashingOffsetInRounds`. This can be a relatively large value, as it's used only for data store cleanup.
|
|
189
|
+
|
|
190
|
+
## Offenses In-Depth
|
|
191
|
+
|
|
192
|
+
Details about specific offenses in the system:
|
|
193
|
+
|
|
194
|
+
### Inactivity
|
|
195
|
+
|
|
196
|
+
Inactivity slashing is one of the most critical, since it allows purging validators that are not fulfilling their duties, which could potentially bring the chain to a halt. This slashing must be aggressive enough to balance out the rate of the entry queue, in case the queue is filled with inactive validators. Furthermore, if enough inactive validators join the system, it may become impossible to gather enough quorum to pass any governance proposal.
|
|
197
|
+
|
|
198
|
+
Inactivity slashing is handled by the `Sentinel` which monitors performance of all validators slot-by-slot. After each slot, the sentinel assigns one of the following to the block proposer for the slot:
|
|
199
|
+
- `block-mined` if the block was added to L1
|
|
200
|
+
- `block-proposed` if the block received at least one attestation, but didn't make it to L1
|
|
201
|
+
- `block-missed` if the block received no attestations (note that we cannot rely on the P2P proposal alone since it may be invalid, unless we reexecute it)
|
|
202
|
+
|
|
203
|
+
And assigns one of the following to each validator:
|
|
204
|
+
- `attestation-sent` if there was a `block-proposed` or `block-mined` and an attestation from this validator was seen on either on L1 or on the P2P network
|
|
205
|
+
- `attestation-missed` if there was a `block-proposed` or `block-mined` but no attestation was seen
|
|
206
|
+
- none if the slot was a `block-missed`
|
|
207
|
+
|
|
208
|
+
Once an epoch is proven, the sentinel computes the _proven performance_ for the epoch for each validator. Note that we wait until the epoch is proven so we know that the data for all blocks in the epoch was available, and validators who did not attest were effectively inactive. Then, for each validator such that:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
total_failures = count(block-missed) + count(attestation-missed)
|
|
212
|
+
total = count(block-*) + count(attestation-*)
|
|
213
|
+
total_failures / total >= slash_inactivity_target_percentage
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
They are voted to be slashed for inactivity. Note that, if `slashInactivityConsecutiveEpochThreshold` is greater than one, we first check if the above is true for the last `threshold` times the given validator was part of a committee, and only then trigger the offense.
|
|
217
|
+
|
|
218
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createLogger } from '@aztec/aztec.js';
|
|
1
|
+
import { createLogger } from '@aztec/aztec.js/log';
|
|
2
2
|
import { deserializeOffense, getRoundForOffense, serializeOffense } from '@aztec/stdlib/slashing';
|
|
3
3
|
export const SCHEMA_VERSION = 1;
|
|
4
4
|
export class SlasherOffensesStore {
|
|
@@ -67,7 +67,7 @@ export declare class TallySlasherClient implements ProposerSlashActionProvider,
|
|
|
67
67
|
protected unwatchCallbacks: (() => void)[];
|
|
68
68
|
protected roundMonitor: SlashRoundMonitor;
|
|
69
69
|
protected offensesCollector: SlashOffensesCollector;
|
|
70
|
-
constructor(config: TallySlasherClientConfig, settings: TallySlasherSettings, tallySlashingProposer: TallySlashingProposerContract, slasher: SlasherContract, rollup: RollupContract, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, offensesStore: SlasherOffensesStore, log?: import("@aztec/
|
|
70
|
+
constructor(config: TallySlasherClientConfig, settings: TallySlasherSettings, tallySlashingProposer: TallySlashingProposerContract, slasher: SlasherContract, rollup: RollupContract, watchers: Watcher[], epochCache: EpochCache, dateProvider: DateProvider, offensesStore: SlasherOffensesStore, log?: import("@aztec/foundation/log").Logger);
|
|
71
71
|
start(): Promise<void>;
|
|
72
72
|
/**
|
|
73
73
|
* Stop the tally slasher client
|
|
@@ -95,6 +95,7 @@ export declare class TallySlasherClient implements ProposerSlashActionProvider,
|
|
|
95
95
|
/**
|
|
96
96
|
* Checks if a given round is executable and returns an execute-slash action for it if so.
|
|
97
97
|
* Assumes round number has already been checked against lifetime and execution delay.
|
|
98
|
+
* @param executableRound - The round to check for execution
|
|
98
99
|
*/
|
|
99
100
|
private tryGetRoundExecuteAction;
|
|
100
101
|
/** Returns a vote action based on offenses from the target round (with offset applied) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tally_slasher_client.d.ts","sourceRoot":"","sources":["../src/tally_slasher_client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,6BAA6B,EAAE,MAAM,2BAA2B,CAAC;AAK3G,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,KAAK,OAAO,EAEZ,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EAGvB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAEhC,OAAO,EACL,sBAAsB,EACtB,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACpC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,KAAK,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC7F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,oGAAoG;AACpG,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CACzC,yBAAyB,GACvB,8BAA8B,GAAG;IAC/B,wBAAwB,EAAE,MAAM,CAAC;IACjC,8BAA8B,EAAE,MAAM,CAAC;IACvC,yBAAyB,EAAE,MAAM,CAAC;IAClC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,wCAAwC;IACxC,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CACJ,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,4BAA4B,GACjE,IAAI,CAAC,aAAa,EAAE,uBAAuB,GAAG,sBAAsB,GAAG,4BAA4B,CAAC,CAAC;AAEvG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,kBAAmB,YAAW,2BAA2B,EAAE,sBAAsB;IAM1F,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,qBAAqB;IAC7B,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;IAEd,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,GAAG;IAdb,SAAS,CAAC,gBAAgB,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAM;IAChD,SAAS,CAAC,YAAY,EAAE,iBAAiB,CAAC;IAC1C,SAAS,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;gBAG1C,MAAM,EAAE,wBAAwB,EAChC,QAAQ,EAAE,oBAAoB,EAC9B,qBAAqB,EAAE,6BAA6B,EACpD,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,cAAc,EAC9B,QAAQ,EAAE,OAAO,EAAE,EACX,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,oBAAoB,EACnC,GAAG,
|
|
1
|
+
{"version":3,"file":"tally_slasher_client.d.ts","sourceRoot":"","sources":["../src/tally_slasher_client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EthAddress } from '@aztec/aztec.js';
|
|
1
|
+
import { EthAddress } from '@aztec/aztec.js/addresses';
|
|
2
2
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
3
3
|
import { compactArray, partition, times } from '@aztec/foundation/collection';
|
|
4
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -129,7 +129,13 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
129
129
|
const slashingExecutionDelayInRounds = BigInt(this.settings.slashingExecutionDelayInRounds);
|
|
130
130
|
const executableRound = currentRound - slashingExecutionDelayInRounds - 1n;
|
|
131
131
|
const lookBack = BigInt(this.config.slashExecuteRoundsLookBack);
|
|
132
|
-
const
|
|
132
|
+
const slashingLifetimeInRounds = BigInt(this.settings.slashingLifetimeInRounds);
|
|
133
|
+
// Compute the oldest executable round considering both lookBack and lifetimeInRounds
|
|
134
|
+
// A round is only executable if currentRound <= round + lifetimeInRounds
|
|
135
|
+
// So the oldest round we can execute is: currentRound - lifetimeInRounds
|
|
136
|
+
const oldestByLifetime = maxBigint(0n, currentRound - slashingLifetimeInRounds);
|
|
137
|
+
const oldestByLookBack = maxBigint(0n, executableRound - lookBack);
|
|
138
|
+
const oldestExecutableRound = maxBigint(oldestByLifetime, oldestByLookBack);
|
|
133
139
|
// Check if slashing is enabled at all
|
|
134
140
|
if (!await this.slasher.isSlashingEnabled()) {
|
|
135
141
|
this.log.warn(`Slashing is disabled in the Slasher contract (skipping execution)`);
|
|
@@ -139,14 +145,16 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
139
145
|
slotNumber,
|
|
140
146
|
currentRound,
|
|
141
147
|
oldestExecutableRound,
|
|
148
|
+
oldestByLifetime,
|
|
149
|
+
oldestByLookBack,
|
|
142
150
|
executableRound,
|
|
143
151
|
slashingExecutionDelayInRounds,
|
|
144
152
|
lookBack,
|
|
145
|
-
slashingLifetimeInRounds
|
|
153
|
+
slashingLifetimeInRounds
|
|
146
154
|
});
|
|
147
155
|
// Iterate over all rounds, starting from the oldest, until we find one that is executable
|
|
148
156
|
for(let roundToCheck = oldestExecutableRound; roundToCheck <= executableRound; roundToCheck++){
|
|
149
|
-
const action = await this.tryGetRoundExecuteAction(roundToCheck);
|
|
157
|
+
const action = await this.tryGetRoundExecuteAction(roundToCheck, slotNumber);
|
|
150
158
|
if (action) {
|
|
151
159
|
return action;
|
|
152
160
|
}
|
|
@@ -157,17 +165,14 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
157
165
|
/**
|
|
158
166
|
* Checks if a given round is executable and returns an execute-slash action for it if so.
|
|
159
167
|
* Assumes round number has already been checked against lifetime and execution delay.
|
|
160
|
-
|
|
168
|
+
* @param executableRound - The round to check for execution
|
|
169
|
+
*/ async tryGetRoundExecuteAction(executableRound, slotNumber) {
|
|
161
170
|
let logData = {
|
|
162
|
-
executableRound
|
|
171
|
+
executableRound,
|
|
172
|
+
slotNumber
|
|
163
173
|
};
|
|
164
174
|
this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
|
|
165
175
|
try {
|
|
166
|
-
// Check if slashing is enabled at all
|
|
167
|
-
if (!await this.slasher.isSlashingEnabled()) {
|
|
168
|
-
this.log.warn(`Slashing is disabled in the Slasher contract (skipping execution)`, logData);
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
176
|
const roundInfo = await this.tallySlashingProposer.getRound(executableRound);
|
|
172
177
|
logData = {
|
|
173
178
|
...logData,
|
|
@@ -183,6 +188,12 @@ import { SlashRoundMonitor } from './slash_round_monitor.js';
|
|
|
183
188
|
this.log.verbose(`Round ${executableRound} does not have enough votes to execute`, logData);
|
|
184
189
|
return undefined;
|
|
185
190
|
}
|
|
191
|
+
// Check if round is ready to execute at the given slot
|
|
192
|
+
const isReadyToExecute = await this.tallySlashingProposer.isRoundReadyToExecute(executableRound, slotNumber);
|
|
193
|
+
if (!isReadyToExecute) {
|
|
194
|
+
this.log.warn(`Round ${executableRound} is not ready to execute at slot ${slotNumber} according to contract check`, logData);
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
186
197
|
// Check if the round yields any slashing at all
|
|
187
198
|
const { actions: slashActions, committees } = await this.tallySlashingProposer.getTally(executableRound);
|
|
188
199
|
if (slashActions.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/slasher",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-devnet.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -54,20 +54,20 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@aztec/epoch-cache": "
|
|
58
|
-
"@aztec/ethereum": "
|
|
59
|
-
"@aztec/foundation": "
|
|
60
|
-
"@aztec/kv-store": "
|
|
61
|
-
"@aztec/l1-artifacts": "
|
|
62
|
-
"@aztec/stdlib": "
|
|
63
|
-
"@aztec/telemetry-client": "
|
|
57
|
+
"@aztec/epoch-cache": "3.0.0-devnet.2",
|
|
58
|
+
"@aztec/ethereum": "3.0.0-devnet.2",
|
|
59
|
+
"@aztec/foundation": "3.0.0-devnet.2",
|
|
60
|
+
"@aztec/kv-store": "3.0.0-devnet.2",
|
|
61
|
+
"@aztec/l1-artifacts": "3.0.0-devnet.2",
|
|
62
|
+
"@aztec/stdlib": "3.0.0-devnet.2",
|
|
63
|
+
"@aztec/telemetry-client": "3.0.0-devnet.2",
|
|
64
64
|
"source-map-support": "^0.5.21",
|
|
65
65
|
"tslib": "^2.4.0",
|
|
66
|
-
"viem": "2.
|
|
66
|
+
"viem": "npm:@spalladino/viem@2.38.2-eip7594.0",
|
|
67
67
|
"zod": "^3.23.8"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@aztec/aztec.js": "
|
|
70
|
+
"@aztec/aztec.js": "3.0.0-devnet.2",
|
|
71
71
|
"@jest/globals": "^30.0.0",
|
|
72
72
|
"@types/jest": "^30.0.0",
|
|
73
73
|
"@types/node": "^22.15.17",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EthAddress } from '@aztec/aztec.js';
|
|
1
|
+
import { EthAddress } from '@aztec/aztec.js/addresses';
|
|
2
2
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { RollupContract, SlasherContract, TallySlashingProposerContract } from '@aztec/ethereum/contracts';
|
|
4
4
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
@@ -187,7 +187,14 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
187
187
|
const slashingExecutionDelayInRounds = BigInt(this.settings.slashingExecutionDelayInRounds);
|
|
188
188
|
const executableRound = currentRound - slashingExecutionDelayInRounds - 1n;
|
|
189
189
|
const lookBack = BigInt(this.config.slashExecuteRoundsLookBack);
|
|
190
|
-
const
|
|
190
|
+
const slashingLifetimeInRounds = BigInt(this.settings.slashingLifetimeInRounds);
|
|
191
|
+
|
|
192
|
+
// Compute the oldest executable round considering both lookBack and lifetimeInRounds
|
|
193
|
+
// A round is only executable if currentRound <= round + lifetimeInRounds
|
|
194
|
+
// So the oldest round we can execute is: currentRound - lifetimeInRounds
|
|
195
|
+
const oldestByLifetime = maxBigint(0n, currentRound - slashingLifetimeInRounds);
|
|
196
|
+
const oldestByLookBack = maxBigint(0n, executableRound - lookBack);
|
|
197
|
+
const oldestExecutableRound = maxBigint(oldestByLifetime, oldestByLookBack);
|
|
191
198
|
|
|
192
199
|
// Check if slashing is enabled at all
|
|
193
200
|
if (!(await this.slasher.isSlashingEnabled())) {
|
|
@@ -199,15 +206,17 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
199
206
|
slotNumber,
|
|
200
207
|
currentRound,
|
|
201
208
|
oldestExecutableRound,
|
|
209
|
+
oldestByLifetime,
|
|
210
|
+
oldestByLookBack,
|
|
202
211
|
executableRound,
|
|
203
212
|
slashingExecutionDelayInRounds,
|
|
204
213
|
lookBack,
|
|
205
|
-
slashingLifetimeInRounds
|
|
214
|
+
slashingLifetimeInRounds,
|
|
206
215
|
});
|
|
207
216
|
|
|
208
217
|
// Iterate over all rounds, starting from the oldest, until we find one that is executable
|
|
209
218
|
for (let roundToCheck = oldestExecutableRound; roundToCheck <= executableRound; roundToCheck++) {
|
|
210
|
-
const action = await this.tryGetRoundExecuteAction(roundToCheck);
|
|
219
|
+
const action = await this.tryGetRoundExecuteAction(roundToCheck, slotNumber);
|
|
211
220
|
if (action) {
|
|
212
221
|
return action;
|
|
213
222
|
}
|
|
@@ -220,18 +229,16 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
220
229
|
/**
|
|
221
230
|
* Checks if a given round is executable and returns an execute-slash action for it if so.
|
|
222
231
|
* Assumes round number has already been checked against lifetime and execution delay.
|
|
232
|
+
* @param executableRound - The round to check for execution
|
|
223
233
|
*/
|
|
224
|
-
private async tryGetRoundExecuteAction(
|
|
225
|
-
|
|
234
|
+
private async tryGetRoundExecuteAction(
|
|
235
|
+
executableRound: bigint,
|
|
236
|
+
slotNumber: bigint,
|
|
237
|
+
): Promise<ProposerSlashAction | undefined> {
|
|
238
|
+
let logData: Record<string, unknown> = { executableRound, slotNumber };
|
|
226
239
|
this.log.debug(`Testing if slashing round ${executableRound} is executable`, logData);
|
|
227
240
|
|
|
228
241
|
try {
|
|
229
|
-
// Check if slashing is enabled at all
|
|
230
|
-
if (!(await this.slasher.isSlashingEnabled())) {
|
|
231
|
-
this.log.warn(`Slashing is disabled in the Slasher contract (skipping execution)`, logData);
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
242
|
const roundInfo = await this.tallySlashingProposer.getRound(executableRound);
|
|
236
243
|
logData = { ...logData, roundInfo };
|
|
237
244
|
if (roundInfo.isExecuted) {
|
|
@@ -245,6 +252,16 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
|
|
|
245
252
|
return undefined;
|
|
246
253
|
}
|
|
247
254
|
|
|
255
|
+
// Check if round is ready to execute at the given slot
|
|
256
|
+
const isReadyToExecute = await this.tallySlashingProposer.isRoundReadyToExecute(executableRound, slotNumber);
|
|
257
|
+
if (!isReadyToExecute) {
|
|
258
|
+
this.log.warn(
|
|
259
|
+
`Round ${executableRound} is not ready to execute at slot ${slotNumber} according to contract check`,
|
|
260
|
+
logData,
|
|
261
|
+
);
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
248
265
|
// Check if the round yields any slashing at all
|
|
249
266
|
const { actions: slashActions, committees } = await this.tallySlashingProposer.getTally(executableRound);
|
|
250
267
|
if (slashActions.length === 0) {
|