-
Notifications
You must be signed in to change notification settings - Fork 365
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: pectra compatibility #1053
base: feat/prooftra
Are you sure you want to change the base?
Changes from all commits
1e56f1d
7e4595b
43c8b98
726071a
f5dbfb8
36bfa8c
e52ccb2
a375875
2dead47
9ec3522
b4852c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,8 @@ library BeaconChainProofs { | |
/// | HEIGHT: VALIDATOR_TREE_HEIGHT | ||
/// individual validators | ||
uint256 internal constant BEACON_BLOCK_HEADER_TREE_HEIGHT = 3; | ||
uint256 internal constant BEACON_STATE_TREE_HEIGHT = 5; | ||
uint256 internal constant DENEB_BEACON_STATE_TREE_HEIGHT = 5; | ||
uint256 internal constant PECTRA_BEACON_STATE_TREE_HEIGHT = 6; | ||
uint256 internal constant BALANCE_TREE_HEIGHT = 38; | ||
uint256 internal constant VALIDATOR_TREE_HEIGHT = 40; | ||
|
||
|
@@ -134,17 +135,21 @@ library BeaconChainProofs { | |
/// @param validatorFieldsProof a merkle proof of inclusion of `validatorFields` under `beaconStateRoot` | ||
/// @param validatorIndex the validator's unique index | ||
function verifyValidatorFields( | ||
uint64 proofTimestamp, | ||
uint64 pectraForkTimestamp, | ||
bytes32 beaconStateRoot, | ||
bytes32[] calldata validatorFields, | ||
bytes calldata validatorFieldsProof, | ||
uint40 validatorIndex | ||
) internal view { | ||
require(validatorFields.length == VALIDATOR_FIELDS_LENGTH, InvalidValidatorFieldsLength()); | ||
|
||
uint256 beaconStateTreeHeight = getBeaconStateTreeHeight(proofTimestamp, pectraForkTimestamp); | ||
|
||
/// Note: the reason we use `VALIDATOR_TREE_HEIGHT + 1` here is because the merklization process for | ||
/// this container includes hashing the root of the validator tree with the length of the validator list | ||
require( | ||
validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + BEACON_STATE_TREE_HEIGHT), | ||
validatorFieldsProof.length == 32 * ((VALIDATOR_TREE_HEIGHT + 1) + beaconStateTreeHeight), | ||
InvalidProofLength() | ||
); | ||
|
||
|
@@ -185,10 +190,16 @@ library BeaconChainProofs { | |
/// against the same balance container root. | ||
/// @param beaconBlockRoot merkle root of the beacon block | ||
/// @param proof a beacon balance container root and merkle proof of its inclusion under `beaconBlockRoot` | ||
function verifyBalanceContainer(bytes32 beaconBlockRoot, BalanceContainerProof calldata proof) internal view { | ||
function verifyBalanceContainer( | ||
uint64 proofTimestamp, | ||
uint64 pectraForkTimestamp, | ||
bytes32 beaconBlockRoot, | ||
BalanceContainerProof calldata proof | ||
) internal view { | ||
uint256 beaconStateTreeHeight = getBeaconStateTreeHeight(proofTimestamp, pectraForkTimestamp); | ||
|
||
require( | ||
proof.proof.length == 32 * (BEACON_BLOCK_HEADER_TREE_HEIGHT + BEACON_STATE_TREE_HEIGHT), | ||
InvalidProofLength() | ||
proof.proof.length == 32 * (BEACON_BLOCK_HEADER_TREE_HEIGHT + beaconStateTreeHeight), InvalidProofLength() | ||
); | ||
|
||
/// This proof combines two proofs, so its index accounts for the relative position of leaves in two trees: | ||
|
@@ -197,7 +208,7 @@ library BeaconChainProofs { | |
/// -- beaconStateRoot | ||
/// | HEIGHT: BEACON_STATE_TREE_HEIGHT | ||
/// ---- balancesContainerRoot | ||
uint256 index = (STATE_ROOT_INDEX << (BEACON_STATE_TREE_HEIGHT)) | BALANCE_CONTAINER_INDEX; | ||
uint256 index = (STATE_ROOT_INDEX << (beaconStateTreeHeight)) | BALANCE_CONTAINER_INDEX; | ||
|
||
require( | ||
Merkle.verifyInclusionSha256({ | ||
|
@@ -312,4 +323,13 @@ library BeaconChainProofs { | |
) internal pure returns (uint64) { | ||
return Endian.fromLittleEndianUint64(validatorFields[VALIDATOR_EXIT_EPOCH_INDEX]); | ||
} | ||
|
||
/// @dev We check if the proofTimestamp is <= pectraForkTimestamp because a `proofTimestamp` at the `pectraForkTimestamp` | ||
/// is considered to be Pre-Pectra given the EIP-4788 oracle returns the parent block. | ||
function getBeaconStateTreeHeight( | ||
uint64 proofTimestamp, | ||
uint64 pectraForkTimestamp | ||
) internal pure returns (uint256) { | ||
return proofTimestamp <= pectraForkTimestamp ? DENEB_BEACON_STATE_TREE_HEIGHT : PECTRA_BEACON_STATE_TREE_HEIGHT; | ||
} | ||
Comment on lines
+327
to
+334
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just making some notes: Before we set Additionally, if the contracts are unpaused but the value is still unset, this logic means all proofs are interpreted as pectra proofs, even if we're working with a dencun block. This is good fallback behavior, as the worst case means some proofs will revert until we set the timestamp. 👍 It's the reverse behavior we wanted to avoid - where dencun proof sizes are allowed for pectra blocks. That shouldn't be possible here 👍 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -159,6 +159,8 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
|
||
// Verify `balanceContainerProof` against `beaconBlockRoot` | ||
BeaconChainProofs.verifyBalanceContainer({ | ||
proofTimestamp: checkpointTimestamp, | ||
pectraForkTimestamp: getPectraForkTimestamp(), | ||
beaconBlockRoot: checkpoint.beaconBlockRoot, | ||
proof: balanceContainerProof | ||
}); | ||
|
@@ -254,6 +256,7 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
for (uint256 i = 0; i < validatorIndices.length; i++) { | ||
// forgefmt: disable-next-item | ||
totalAmountToBeRestakedWei += _verifyWithdrawalCredentials( | ||
beaconTimestamp, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I wonder about the usage of Can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the record I think I'm correct here. 2 scenarios:
Naive solution: have the fork selector logic in the proofs library check I think, unfortunately, we need to pause proofs just before the fork, and unpause just after the fork (once we see the first valid block). Although I'm not sure this entirely fixes the issue... will think more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we need to pause + upgrade just after the fork + unpause? ugh. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Synced offline, here is what we need to do:
To do this, here is the process:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the image above, we want to determine what proof type to use, given the proof timestamp tp. Because tp is used to look up a parent block in the EIP-4788 oracle, its proof type corresponds to the last non-skipped block. So, if:
Given our beacon state tree height getter: https://github.com/layr-labs/eigenlayer-contracts/blob/b4852c74cdbe43fea2f7330ea0dc752fcf10b6e9/src/contracts/libraries/BeaconChainProofs.sol#L327-L334 ... |
||
stateRootProof.beaconStateRoot, | ||
validatorIndices[i], | ||
validatorFieldsProofs[i], | ||
|
@@ -341,6 +344,8 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
|
||
// Verify Validator container proof against `beaconStateRoot` | ||
BeaconChainProofs.verifyValidatorFields({ | ||
proofTimestamp: beaconTimestamp, | ||
pectraForkTimestamp: getPectraForkTimestamp(), | ||
beaconStateRoot: stateRootProof.beaconStateRoot, | ||
validatorFields: proof.validatorFields, | ||
validatorFieldsProof: proof.proof, | ||
|
@@ -378,6 +383,7 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
} | ||
|
||
/// @notice Called by EigenPodManager when the owner wants to create another ETH validator. | ||
/// @dev This function only supports staking to a 0x01 validator. For compounding validators, please interact directly with the deposit contract. | ||
function stake( | ||
bytes calldata pubkey, | ||
bytes calldata signature, | ||
|
@@ -419,13 +425,13 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
* @param validatorFields are the fields of the "Validator Container", refer to consensus specs | ||
*/ | ||
function _verifyWithdrawalCredentials( | ||
uint64 beaconTimestamp, | ||
bytes32 beaconStateRoot, | ||
uint40 validatorIndex, | ||
bytes calldata validatorFieldsProof, | ||
bytes32[] calldata validatorFields | ||
) internal returns (uint256) { | ||
bytes32 pubkeyHash = validatorFields.getPubkeyHash(); | ||
ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[pubkeyHash]; | ||
ValidatorInfo memory validatorInfo = _validatorPubkeyHashToInfo[validatorFields.getPubkeyHash()]; | ||
|
||
// Withdrawal credential proofs should only be processed for "INACTIVE" validators | ||
require(validatorInfo.status == VALIDATOR_STATUS.INACTIVE, CredentialsAlreadyVerified()); | ||
|
@@ -473,7 +479,8 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
|
||
// Ensure the validator's withdrawal credentials are pointed at this pod | ||
require( | ||
validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()), | ||
validatorFields.getWithdrawalCredentials() == bytes32(_podWithdrawalCredentials()) | ||
|| validatorFields.getWithdrawalCredentials() == bytes32(_podCompoundingWithdrawalCredentials()), | ||
WithdrawalCredentialsNotForEigenPod() | ||
); | ||
|
||
|
@@ -484,6 +491,8 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
|
||
// Verify passed-in validatorFields against verified beaconStateRoot: | ||
BeaconChainProofs.verifyValidatorFields({ | ||
proofTimestamp: beaconTimestamp, | ||
pectraForkTimestamp: getPectraForkTimestamp(), | ||
beaconStateRoot: beaconStateRoot, | ||
validatorFields: validatorFields, | ||
validatorFieldsProof: validatorFieldsProof, | ||
|
@@ -499,7 +508,7 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
currentCheckpointTimestamp == 0 ? lastCheckpointTimestamp : currentCheckpointTimestamp; | ||
|
||
// Proofs complete - create the validator in state | ||
_validatorPubkeyHashToInfo[pubkeyHash] = ValidatorInfo({ | ||
_validatorPubkeyHashToInfo[validatorFields.getPubkeyHash()] = ValidatorInfo({ | ||
validatorIndex: validatorIndex, | ||
restakedBalanceGwei: restakedBalanceGwei, | ||
lastCheckpointedAt: lastCheckpointedAt, | ||
|
@@ -665,6 +674,10 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
return abi.encodePacked(bytes1(uint8(1)), bytes11(0), address(this)); | ||
} | ||
|
||
function _podCompoundingWithdrawalCredentials() internal view returns (bytes memory) { | ||
return abi.encodePacked(bytes1(uint8(2)), bytes11(0), address(this)); | ||
} | ||
|
||
///@notice Calculates the pubkey hash of a validator's pubkey as per SSZ spec | ||
function _calculateValidatorPubkeyHash( | ||
bytes memory validatorPubkey | ||
|
@@ -731,4 +744,10 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC | |
require(success && result.length > 0, InvalidEIP4788Response()); | ||
return abi.decode(result, (bytes32)); | ||
} | ||
|
||
/// @notice Returns the timestamp of the Pectra fork, read from the `EigenPodManager` contract | ||
/// @dev Specifically, this returns the timestamp of the first non-missed slot at or after the Pectra hard fork | ||
function getPectraForkTimestamp() public view returns (uint64) { | ||
return eigenPodManager.pectraForkTimestamp(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this added to the EigenPod interface?
It already exists on the EPM - why duplicate?