Skip to content

fix(testnet): scale per-offense slash penalties so slashing can trigger#24136

Closed
aminsammara wants to merge 1 commit into
v5from
as/testnet-slashing-penalties
Closed

fix(testnet): scale per-offense slash penalties so slashing can trigger#24136
aminsammara wants to merge 1 commit into
v5from
as/testnet-slashing-penalties

Conversation

@aminsammara

Copy link
Copy Markdown
Contributor

Problem

The testnet config shipped with v5.0.0-rc.1 cannot ever slash a validator.

spartan/environments/testnet.env raises the onchain slash amounts to be meaningful relative to the 200,000e18 activation threshold:

AZTEC_SLASH_AMOUNT_SMALL=100000e18
AZTEC_SLASH_AMOUNT_MEDIUM=250000e18
AZTEC_SLASH_AMOUNT_LARGE=250000e18

…but it does not override the per-offense penalties, so they keep the generated defaults from network-defaults.yml (~10e18).

How slashing decides what to vote (yarn-project/stdlib/src/slashing/votes.ts):

  1. The slasher sums an offender's penalties for an epoch.
  2. getSlashUnitsForAmount maps that total to 0/1/2/3 units by comparing against the onchain [SMALL, MEDIUM, LARGE] amounts (read from the SlashingProposer contract).
  3. A total below SMALL0 units → no vote is cast → quorum is never reached → executeRound produces no SlashAction.

With penalties at 10e18 and SMALL at 100000e18, every offense is ~10,000× too small to reach even 1 unit. A slashing round spans only 2 epochs, so realistic accumulation (~1–2 offenses) never gets close. Result: the sentinel correctly detects offenses, but no validator is ever slashed. The collector even logs Offense amount … is below minimum slashing amount … on every offense (slash_offenses_collector.ts:100).

Fix

Override the active per-offense penalties in testnet.env onto the same scale as the onchain amounts:

Offense Penalty Units Effect
Inactivity (liveness) 100000e18 SMALL (1) slash 100k
Unknown (catch-all) 100000e18 SMALL (1) slash 100k
Data withholding 250000e18 LARGE (3) full confiscation
Invalid block 250000e18 LARGE (3) full confiscation
Propose invalid attestations 250000e18 LARGE (3) full confiscation
Propose descendant w/ invalid attestations 250000e18 LARGE (3) full confiscation
Attest invalid checkpoint proposal 250000e18 LARGE (3) full confiscation

The intentionally-disabled offenses (duplicate proposal/attestation, invalid checkpoint proposal — 0 in the defaults) are left untouched.

This is the only fix deployable to the live testnet without redeploying L1 contracts: AZTEC_SLASH_AMOUNT_* are SlashingProposer immutables fixed at genesis, whereas SLASH_*_PENALTY is node-side config read at runtime and reaches pods via deploy_network.sh → helm → pod env.

Notes / follow-ups (not in this PR)

  • Because AZTEC_LOCAL_EJECTION_THRESHOLD=199000e18, any slash on a 200k stake fully ejects the validator (StakingLib.slash): 200k − 100k = 100k < 199k. SMALL burns 100k and returns 100k via exit; LARGE (capped at the 200k balance) burns the whole stake. There is no "small penalty, keep validating" path on testnet — that's a separate tuning decision.
  • SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD defaults to 1, so a single inactive epoch now ejects. If that's too aggressive for testnet, raise the threshold.

testnet.env overrode the onchain slash amounts (SMALL=100000e18,
MEDIUM/LARGE=250000e18) but left the per-offense penalties at the generated
defaults (10e18). The slasher sums an offender's penalties for an epoch and
maps the total to 1/2/3 slash units against [SMALL, MEDIUM, LARGE]; a total
below SMALL maps to 0 units and is never voted on. At 10e18 every offense was
~10,000x below SMALL, so it always rounded down to 0 units and no validator
could ever be slashed on testnet.

Override the active offense penalties onto the same scale as the onchain
amounts: liveness/catch-all faults map to SMALL (1 unit), provable validity
and data-availability faults map to LARGE (3 units). The intentionally-disabled
offenses (duplicate proposal/attestation, invalid checkpoint proposal) are left
at 0.
@aminsammara aminsammara requested a review from a team June 16, 2026 15:22
@aminsammara

Copy link
Copy Markdown
Contributor Author

Closing — this PR was based on an incomplete analysis and is not correct.

The premise was wrong. I claimed the per-offense penalties default to 10e18 on testnet (the compiled slasher-defaults.ts value) and therefore never reach SLASH_AMOUNT_SMALL. That is not what a deployed testnet node runs with. A spartan node starts with NETWORK=testnet, and aztec/src/bin/index.ts calls enrichEnvironmentWithChainName('testnet'), which loads the generated testnetConfig preset (yarn-project/cli/src/config/generated/networks.ts). For any penalty env var the pod does not set, the preset value is applied (enrichEnvironmentWithNetworkConfig only fills keys where process.env[key] === undefined). The pod omits unset penalties entirely (each is guarded by {{- if .Values.node.slash.X }}), so the preset wins.

The testnetConfig preset already encodes the correct, AZIP-16-consistent slashing config:

  • SLASH_INACTIVITY_PENALTY = 1E+23 (= 100000e18 = SLASH_AMOUNT_SMALL) → 1 unit. Slashing does trigger.
  • SLASH_DUPLICATE_PROPOSAL_PENALTY / SLASH_DUPLICATE_ATTESTATION_PENALTY = 2.5E+23 → LARGE.
  • SLASH_DATA_WITHHOLDING_PENALTY, SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY, SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY = 0 — matching mainnet and AZIP-16.

This PR would have actively broken AZIP-16 compliance. All SLASH_*_PENALTY vars are consensus-critical (NETWORK_CONSENSUS_ENV_VARS). Because testnet.env sets ALLOW_OVERRIDING_NETWORK_CONFIG=true, the values here would override the preset (with only a warning), not be rejected — activating three offenses AZIP-16 mandates at 0 (notably data withholding, which is deliberately off to gather tx-propagation telemetry and avoid slashing honest committees during p2p congestion) and escalating invalid-block / propose-invalid-attestations from SMALL to LARGE. Without the override flag it would have crashed every testnet node at startup.

No change to testnet.env is needed: node-side penalties come from the preset; testnet.env only carries the L1-contract-deployment params (AZTEC_SLASH_AMOUNT_*, thresholds) consumed by the contract deployer.

@aminsammara aminsammara deleted the as/testnet-slashing-penalties branch June 16, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant