Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: drop slashing reporting #370

Merged
merged 9 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"gindex": "node script/gindex.mjs"
},
"dependencies": {
"@lodestar/types": "^1.18.1",
"@lodestar/types": "^1.23.1",
"@openzeppelin/contracts": "5.0.2",
"@openzeppelin/contracts-upgradeable": "5.0.2",
"@openzeppelin/merkle-tree": "^1.0.6",
Expand Down
106 changes: 106 additions & 0 deletions script/DeployCSVerifier.s.sol
vgorkavenko marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: 2024 Lido <[email protected]>
// SPDX-License-Identifier: GPL-3.0

// Usage: forge script ./script/DeployCSVerifier:DeployCSVerifier[Holesky|Mainnet]

pragma solidity 0.8.24;

import { Script } from "forge-std/Script.sol";
import { console2 as console } from "forge-std/console2.sol";

import { CSVerifier } from "../src/CSVerifier.sol";
import { GIndex } from "../src/lib/GIndex.sol";
import { Slot } from "../src/lib/Types.sol";

struct Config {
address withdrawalVault;
address module;
GIndex gIFirstWithdrawalPrev;
GIndex gIFirstWithdrawalCurr;
GIndex gIFirstValidatorPrev;
GIndex gIFirstValidatorCurr;
GIndex gIHistoricalSummariesPrev;
GIndex gIHistoricalSummariesCurr;
Slot firstSupportedSlot;
Slot pivotSlot;
uint64 slotsPerEpoch;
}

// Check the constants below via `yarn run gindex`.

GIndex constant HISTORICAL_SUMMARIES_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000003b00
);
GIndex constant FIRST_WITHDRAWAL_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000e1c004
);
GIndex constant FIRST_VALIDATOR_DENEB = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000056000000000028
);

GIndex constant HISTORICAL_SUMMARIES_ELECTRA = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000000000000005b00
);
GIndex constant FIRST_WITHDRAWAL_ELECTRA = GIndex.wrap(
0x000000000000000000000000000000000000000000000000000000000161c004
);
GIndex constant FIRST_VALIDATOR_ELECTRA = GIndex.wrap(
0x0000000000000000000000000000000000000000000000000096000000000028
);

abstract contract DeployCSVerifier is Script {
Config internal config;

function run() public {
CSVerifier verifier = new CSVerifier({
withdrawalAddress: config.withdrawalVault,
module: config.module,
slotsPerEpoch: config.slotsPerEpoch,
gIFirstWithdrawalPrev: config.gIFirstWithdrawalPrev,
gIFirstWithdrawalCurr: config.gIFirstWithdrawalCurr,
gIFirstValidatorPrev: config.gIFirstValidatorPrev,
gIFirstValidatorCurr: config.gIFirstValidatorCurr,
gIHistoricalSummariesPrev: config.gIHistoricalSummariesPrev,
gIHistoricalSummariesCurr: config.gIHistoricalSummariesCurr,
firstSupportedSlot: config.firstSupportedSlot,
pivotSlot: config.pivotSlot
});
console.log("CSVerifier deployed at:", address(verifier));
}
}

contract DeployCSVerifierHolesky is DeployCSVerifier {
constructor() {
config = Config({
withdrawalVault: 0xF0179dEC45a37423EAD4FaD5fCb136197872EAd9,
vgorkavenko marked this conversation as resolved.
Show resolved Hide resolved
module: 0x4562c3e63c2e586cD1651B958C22F88135aCAd4f,
slotsPerEpoch: 32,
gIFirstWithdrawalPrev: FIRST_WITHDRAWAL_DENEB,
gIFirstWithdrawalCurr: FIRST_WITHDRAWAL_ELECTRA,
gIFirstValidatorPrev: FIRST_VALIDATOR_DENEB,
gIFirstValidatorCurr: FIRST_VALIDATOR_ELECTRA,
gIHistoricalSummariesPrev: HISTORICAL_SUMMARIES_DENEB,
gIHistoricalSummariesCurr: HISTORICAL_SUMMARIES_ELECTRA,
firstSupportedSlot: Slot.wrap(950272),
vgorkavenko marked this conversation as resolved.
Show resolved Hide resolved
pivotSlot: Slot.wrap(0) // TODO: Update with Electra slot.
});
}
}

contract DeployCSVerifierMainnet is DeployCSVerifier {
constructor() {
config = Config({
withdrawalVault: 0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f,
module: 0xdA7dE2ECdDfccC6c3AF10108Db212ACBBf9EA83F,
slotsPerEpoch: 32,
gIFirstWithdrawalPrev: FIRST_WITHDRAWAL_DENEB,
gIFirstWithdrawalCurr: FIRST_WITHDRAWAL_ELECTRA,
gIFirstValidatorPrev: FIRST_VALIDATOR_DENEB,
gIFirstValidatorCurr: FIRST_VALIDATOR_ELECTRA,
gIHistoricalSummariesPrev: HISTORICAL_SUMMARIES_DENEB,
gIHistoricalSummariesCurr: HISTORICAL_SUMMARIES_ELECTRA,
firstSupportedSlot: Slot.wrap(8626176),
pivotSlot: Slot.wrap(0) // TODO: Update with Electra slot.
});
}
}
5 changes: 0 additions & 5 deletions script/fork-helpers/NodeOperators.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,6 @@ contract NodeOperators is
);
}

function slash(uint256 noId, uint256 keyIndex) external broadcastVerifier {
csm.submitInitialSlashing(noId, keyIndex);
assertTrue(csm.isValidatorSlashed(noId, keyIndex));
}

function targetLimit(
uint256 noId,
uint256 targetLimitMode,
Expand Down
4 changes: 2 additions & 2 deletions script/gindex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { concatGindices } from "@chainsafe/persistent-merkle-tree";
import { ssz } from "@lodestar/types";

for (const fork of ["deneb"]) {
/** @type ssz.deneb */
for (const fork of ["deneb", "electra"]) {
/** @type ssz.deneb|ssz.electra */
const Fork = ssz[fork];

{
Expand Down
40 changes: 5 additions & 35 deletions src/CSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ contract CSModule is

uint256 private _nonce;
mapping(uint256 => NodeOperator) private _nodeOperators;
// @dev see _keyPointer function for details of noKeyIndexPacked structure
/// @dev see _keyPointer function for details of noKeyIndexPacked structure
mapping(uint256 noKeyIndexPacked => bool) private _isValidatorWithdrawn;
/// @dev DEPRECATED! No writes expected after Pectra hard-fork
mapping(uint256 noKeyIndexPacked => bool) private _isValidatorSlashed;

uint64 private _totalDepositedValidators;
Expand Down Expand Up @@ -888,13 +889,13 @@ contract CSModule is
emit WithdrawalSubmitted(nodeOperatorId, keyIndex, amount, pubkey);

if (isSlashed) {
// NOTE: Can't remove the check so far to avoid double-accounting of penalty. Make sure
// we decided to go with CSVerifier with no processSalshingProof function deployed first
// with some meaningful grace period.
if (_isValidatorSlashed[pointer]) {
// Account already burned value
unchecked {
amount += INITIAL_SLASHING_PENALTY;
}
} else {
_isValidatorSlashed[pointer] = true;
}
// Bond curve should be reset to default in case of slashing. See https://hackmd.io/@lido/SygBLW5ja
accounting.resetBondCurve(nodeOperatorId);
Expand All @@ -913,37 +914,6 @@ contract CSModule is
});
}

/// @inheritdoc ICSModule
function submitInitialSlashing(
uint256 nodeOperatorId,
uint256 keyIndex
) external onlyRole(VERIFIER_ROLE) {
_onlyExistingNodeOperator(nodeOperatorId);
NodeOperator storage no = _nodeOperators[nodeOperatorId];
if (keyIndex >= no.totalDepositedKeys) {
revert SigningKeysInvalidOffset();
}

uint256 pointer = _keyPointer(nodeOperatorId, keyIndex);

if (_isValidatorSlashed[pointer]) {
revert AlreadySubmitted();
}

_isValidatorSlashed[pointer] = true;

bytes memory pubkey = SigningKeys.loadKeys(nodeOperatorId, keyIndex, 1);
emit InitialSlashingSubmitted(nodeOperatorId, keyIndex, pubkey);
madlabman marked this conversation as resolved.
Show resolved Hide resolved

accounting.penalize(nodeOperatorId, INITIAL_SLASHING_PENALTY);

// Nonce should be updated if depositableValidators change
_updateDepositableValidatorsCount({
nodeOperatorId: nodeOperatorId,
incrementNonceIfUpdated: true
});
}

/// @inheritdoc IStakingModule
/// @dev Resets the key removal charge
/// @dev Changing the WC means that the current deposit data in the queue is not valid anymore and can't be deposited
Expand Down
47 changes: 0 additions & 47 deletions src/CSVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -103,53 +103,6 @@ contract CSVerifier is ICSVerifier {
PIVOT_SLOT = pivotSlot;
}

/// @inheritdoc ICSVerifier
function processSlashingProof(
ProvableBeaconBlockHeader calldata beaconBlock,
SlashingWitness calldata witness,
uint256 nodeOperatorId,
uint256 keyIndex
) external {
if (beaconBlock.header.slot < FIRST_SUPPORTED_SLOT) {
revert UnsupportedSlot(beaconBlock.header.slot);
}

{
bytes32 trustedHeaderRoot = _getParentBlockRoot(
beaconBlock.rootsTimestamp
);
if (trustedHeaderRoot != beaconBlock.header.hashTreeRoot()) {
revert InvalidBlockHeader();
}
}

bytes memory pubkey = MODULE.getSigningKeys(
nodeOperatorId,
keyIndex,
1
);

Validator memory validator = Validator({
pubkey: pubkey,
withdrawalCredentials: witness.withdrawalCredentials,
effectiveBalance: witness.effectiveBalance,
slashed: true,
activationEligibilityEpoch: witness.activationEligibilityEpoch,
activationEpoch: witness.activationEpoch,
exitEpoch: witness.exitEpoch,
withdrawableEpoch: witness.withdrawableEpoch
});

SSZ.verifyProof({
proof: witness.validatorProof,
root: beaconBlock.header.stateRoot,
leaf: validator.hashTreeRoot(),
gI: _getValidatorGI(witness.validatorIndex, beaconBlock.header.slot)
});

MODULE.submitInitialSlashing(nodeOperatorId, keyIndex);
}

/// @inheritdoc ICSVerifier
function processWithdrawalProof(
ProvableBeaconBlockHeader calldata beaconBlock,
Expand Down
19 changes: 3 additions & 16 deletions src/interfaces/ICSModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,6 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
uint256 amount,
bytes pubkey
);
event InitialSlashingSubmitted(
uint256 indexed nodeOperatorId,
uint256 keyIndex,
bytes pubkey
);

event PublicRelease();
event KeyRemovalChargeSet(uint256 amount);
Expand Down Expand Up @@ -516,16 +511,6 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
uint256 keysCount
) external view returns (bytes memory keys, bytes memory signatures);

/// @notice Report Node Operator's key as slashed and apply the initial slashing penalty
/// @notice Called by the Verifier contract.
/// See `CSVerifier.processSlashingProof` to use this method permissionless
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the slashed key in the Node Operator's keys storage
function submitInitialSlashing(
uint256 nodeOperatorId,
uint256 keyIndex
) external;

/// @notice Report Node Operator's key as withdrawn and settle withdrawn amount
/// @notice Called by the Verifier contract.
/// See `CSVerifier.processWithdrawalProof` to use this method permissionless
Expand All @@ -540,7 +525,9 @@ interface ICSModule is IQueueLib, INOAddresses, IAssetRecovererLib {
bool isSlashed
) external;

/// @notice Check if the given Node Operator's key is reported as slashed
/// @notice DEPRECATED! Check if the given Node Operator's key is reported as slashed
/// @notice Since pectra update the contract doesn't store slashing flag of a withdrawn
/// validator
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the key to check
/// @return Validator reported as slashed flag
Expand Down
12 changes: 0 additions & 12 deletions src/interfaces/ICSVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,6 @@ interface ICSVerifier {

function MODULE() external view returns (ICSModule);

/// @notice Verify slashing proof and report slashing to the module for valid proofs
/// @param beaconBlock Beacon block header
/// @param witness Slashing witness against the `beaconBlock`'s state root.
/// @param nodeOperatorId ID of the Node Operator
/// @param keyIndex Index of the validator key in the Node Operator's key storage
function processSlashingProof(
ProvableBeaconBlockHeader calldata beaconBlock,
SlashingWitness calldata witness,
uint256 nodeOperatorId,
uint256 keyIndex
) external;

/// @notice Verify withdrawal proof and report withdrawal to the module for valid proofs
/// @param beaconBlock Beacon block header
/// @param witness Withdrawal witness against the `beaconBlock`'s state root.
Expand Down
Loading
Loading