@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 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
- - Uses a slash offset to vote on validators from past rounds (e.g., round N votes on round N-2)
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
- - `slashingRoundSize`: Number of slots per slashing round (default: 192, must be multiple of epochs)
150
- - `slashingQuorumSize`: Votes required to slash (tally model)
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
- - `slashPrunePenalty`: Penalty for DATA_WITHHOLDING and VALID_EPOCH_PRUNED offenses
165
- - `slashInactivityPenalty`: Penalty for INACTIVITY offenses
166
- - `slashBroadcastedInvalidBlockPenalty`: Penalty for broadcasting invalid blocks
167
- - `slashProposeInvalidAttestationsPenalty`: Penalty for proposing with insufficient/incorrect attestations
168
- - `slashAttestDescendantOfInvalidPenalty`: Penalty for attesting to descendants of invalid blocks
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/aztec.js").Logger);
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,mCAAoC;IAMpC,KAAK;IAuBlB;;OAEG;IACU,IAAI;IAejB,iCAAiC;IAC1B,SAAS,IAAI,aAAa;IAIjC,8CAA8C;IACvC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC;IAIlD,6FAA6F;cAC7E,cAAc,CAAC,KAAK,EAAE,MAAM;IAK5C,gGAAgG;cAChF,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG;IAKvF;;;;OAIG;IACU,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IASnF;;;OAGG;cACa,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAmCnG;;;OAGG;YACW,wBAAwB;IA+DtC,0FAA0F;cAC1E,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAkFnG,mFAAmF;IACnF,OAAO,CAAC,kCAAkC;IAQ1C;;;OAGG;IACI,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAIvD;;;;;OAKG;IACU,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IASvE,0CAA0C;IACnC,kBAAkB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/C;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;CAIxB"}
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,yCAAoC;IAMpC,KAAK;IAuBlB;;OAEG;IACU,IAAI;IAejB,iCAAiC;IAC1B,SAAS,IAAI,aAAa;IAIjC,8CAA8C;IACvC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC;IAIlD,6FAA6F;cAC7E,cAAc,CAAC,KAAK,EAAE,MAAM;IAK5C,gGAAgG;cAChF,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG;IAKvF;;;;OAIG;IACU,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IASnF;;;OAGG;cACa,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IA4CnG;;;;OAIG;YACW,wBAAwB;IAsEtC,0FAA0F;cAC1E,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAkFnG,mFAAmF;IACnF,OAAO,CAAC,kCAAkC;IAQ1C;;;OAGG;IACI,gBAAgB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAIvD;;;;;OAKG;IACU,sBAAsB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IASvE,0CAA0C;IACnC,kBAAkB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/C;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;CAIxB"}
@@ -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 oldestExecutableRound = maxBigint(0n, executableRound - lookBack);
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: this.settings.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
- */ async tryGetRoundExecuteAction(executableRound) {
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": "2.1.0-rc.9",
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": "2.1.0-rc.9",
58
- "@aztec/ethereum": "2.1.0-rc.9",
59
- "@aztec/foundation": "2.1.0-rc.9",
60
- "@aztec/kv-store": "2.1.0-rc.9",
61
- "@aztec/l1-artifacts": "2.1.0-rc.9",
62
- "@aztec/stdlib": "2.1.0-rc.9",
63
- "@aztec/telemetry-client": "2.1.0-rc.9",
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.23.7",
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": "2.1.0-rc.9",
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 { createLogger } from '@aztec/aztec.js';
1
+ import { createLogger } from '@aztec/aztec.js/log';
2
2
  import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncMultiMap, AztecAsyncSet } from '@aztec/kv-store';
3
3
  import {
4
4
  type Offense,
@@ -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 oldestExecutableRound = maxBigint(0n, executableRound - lookBack);
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: this.settings.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(executableRound: bigint): Promise<ProposerSlashAction | undefined> {
225
- let logData: Record<string, unknown> = { executableRound };
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) {