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: pectra compatibility #1053

Open
wants to merge 27 commits into
base: feat/prooftra
Choose a base branch
from
Open

Conversation

ypatil12
Copy link
Collaborator

@ypatil12 ypatil12 commented Jan 28, 2025

Updates checkpoint proof system to be Pectra compatible. The breaking change to EigenPods is the BeaconState container increasing to have 37 fields, which results in the tree height to be > 5.

Overview

We need to solve for the following cases:

  • Prevent deneb proofs from being submitted to pectra blocks
  • Ensure that the PECTRA_FORK_TIMESTAMP is the first timestamp at or after the pectra hard fork for which there is a non missed slot

To do this, here is the upgrade process:

  1. Pause checkpoint starting & credential proofs
  2. Upgrade after fork is hit
  3. Run script to detect the first timestamp at or after the pectra hard fork for which there is a non missed slot
  4. Set pectra fork timestamp to the first timestamp at which there is a pectra block header
  5. Unpause

EigenPod

  • Updated balance container and validator container proofs to pass in a proof timestamp & pectra fork timestamp to check against which tree height to use for the beacon state
  • Modify storing variables in memory to handle stack too deep errors
  • Note that since the 4788 oracle returns the PARENT beacon block root, our check against the pectra fork timestamp returns the previous tree length for proofs that are <= pectraForkTimestamp

Deprecation Plan

  • Post pectra, we can upgrade the EigenPod to deprecate the fork timestamp case handling once all in progress pre-Pectra checkpoints have been completed

TODOs

  • Unit Tests
  • Integration Tests simulating upgrade
  • Mekong Deployment
  • Update Integration Test User to use validators >32 ETH

@ypatil12 ypatil12 mentioned this pull request Jan 28, 2025
3 tasks
@ypatil12 ypatil12 force-pushed the slashing-magnitudes branch from 7a6f8a6 to fbe5ee2 Compare January 28, 2025 18:23
@ypatil12 ypatil12 changed the base branch from slashing-magnitudes to dev January 28, 2025 21:48
@ypatil12 ypatil12 force-pushed the yash/pectra-compatibility branch 4 times, most recently from 8b3076f to 8679ebb Compare January 28, 2025 21:55
@ypatil12 ypatil12 changed the base branch from dev to feat/prooftra January 29, 2025 22:11
@ypatil12 ypatil12 changed the base branch from feat/prooftra to dev January 29, 2025 22:31
@ypatil12 ypatil12 force-pushed the yash/pectra-compatibility branch from 8679ebb to 392022e Compare January 30, 2025 01:14
@ypatil12 ypatil12 changed the base branch from dev to feat/prooftra January 30, 2025 01:15
@ypatil12 ypatil12 force-pushed the yash/pectra-compatibility branch 4 times, most recently from d71e908 to 2e9ab01 Compare January 30, 2025 02:12
@ypatil12 ypatil12 marked this pull request as ready for review January 30, 2025 20:34
eigenmikem
eigenmikem previously approved these changes Feb 4, 2025
Copy link
Contributor

@eigenmikem eigenmikem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK the fork timestamp has never been altered once broadcast, at least not in the last few years, so I think hardcoding it is fine

@ypatil12 ypatil12 force-pushed the yash/pectra-compatibility branch from d849720 to f5dbfb8 Compare February 4, 2025 23:16
@@ -254,6 +255,7 @@ contract EigenPod is Initializable, ReentrancyGuardUpgradeable, EigenPodPausingC
for (uint256 i = 0; i < validatorIndices.length; i++) {
// forgefmt: disable-next-item
totalAmountToBeRestakedWei += _verifyWithdrawalCredentials(
beaconTimestamp,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I wonder about the usage of getParentBlockRoot above.

Can beaconTimestamp belong to the block after the fork, but the proof itself is over the parent block (aka pre-fork)?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record I think I'm correct here. 2 scenarios:

  1. I try to verify withdrawal credentials the block after the hard fork. getParentBlockRoot grabs a block from before the hard fork; that's what my proof needs to be against.
  2. I start a checkpoint just after the hard fork. The checkpoint block root used for proofs is from a block just before the hard fork.

Naive solution: have the fork selector logic in the proofs library check proofTimestamp - 12. However, this runs into potential issues with skipped slots -- I might have a proof timestamp 2 blocks after the hard fork, but there was a skipped slot right at the fork, so the proof block is from before the fork.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need to pause + upgrade just after the fork + unpause? ugh.

Copy link
Collaborator Author

@ypatil12 ypatil12 Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synced offline, here is what we need to do:

  • Prevent deneb proofs from being submitted to pectra blocks
  • Ensure that the PECTRA_FORK_TIMESTAMP is the first timestamp at or after the pectra hard fork for which there is a non-missed slot. Checkpoint proofs store the proof timestamp. If there are missed slots at the hard fork timestamp, it's possible, like Alex mentions above, that the beaconTimestamp is post pectra but the block header is deneb.

To do this, here is the process:

  1. Pause checkpoint starting & credential proofs
  2. Upgrade after fork is hit
  3. Set pectra fork timestamp to the first timestamp at which there is a pectra block header
  4. Unpause

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Collaborator

Choose a reason for hiding this comment

The 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:

  • tp > t1, use pectra logic
  • tp <= t1, use dencun logic

Given our beacon state tree height getter: https://github.com/layr-labs/eigenlayer-contracts/blob/b4852c74cdbe43fea2f7330ea0dc752fcf10b6e9/src/contracts/libraries/BeaconChainProofs.sol#L327-L334

... pectraForkTimestamp should be set to the first valid, non-skipped block after the Pectra hard fork.

Copy link
Collaborator

@wadealexc wadealexc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this PR targeting testnet?

We're going to need a different PR for mainnet, since that's landing before slashing, right? (This EigenPod file is compatible with testnet slashing, but not mainnet)

@ypatil12
Copy link
Collaborator Author

ypatil12 commented Feb 5, 2025

Is this PR targeting testnet?

Yup, targeting testnet. There will be a separate mainnet PR. cc @wadealexc

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()];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming stack too deep here

@@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be backlog to add stake function with compounding validators when we implement eip7002

@@ -35,4 +37,22 @@ abstract contract UpgradeTest is IntegrationCheckUtils {
isUpgraded = true;
emit log("_upgradeEigenLayerContracts: slashing upgrade complete");
}

uint64 constant PECTRA_FORK_TIMESTAMP = 1739352768;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you getting this value? Is this the expected timestamp of the holesky fork?

Wouldn't we need to wait to determine this until after the fork? We need the first non-skipped slot, which may not be this specific value.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in slack -- this is basically a filler value because we can't know this beforehand.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually - since this is a timestamp from a week ago, this is probably messing with tests?

We should maybe just have fork timestamp be current timestamp + 12 or something - that way we know we're moving forward in time if we warp to it.


/// @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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's consider moving this logic into EigenPod, since it's specific to the EIP-4788 oracle (and the EPM getter), which this library doesn't handle.

We can pass the proofs library an enum (DENEB|ELECTRA), instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


/// @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() internal view returns (uint64) {
Copy link
Collaborator

@wadealexc wadealexc Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a require(result != 0) here?

We should never be in a state where we can call this and have it return zero, so this is a sanity check that when we unpause, we have set a value :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +104 to +105
// 3. Upgrade EigenPodManager & EigenPod
_upgradeEigenLayerContracts();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already calls _handlePectraFork, so you're duplicating actions here to some extent. Can we pick whether we're adding special fork logic in UpgradeTest, or in this file?

Comment on lines +120 to +125
// 2. Randomly warp to just after the fork timestamp
// If we do not warp, proofs will be against deneb state
if (_randBool()) {
// If we warp, proofs will be against electra state
cheats.warp(block.timestamp + 1);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm pretty sure this is testing against electra regardless of whether you warp or not

@@ -562,7 +562,7 @@ abstract contract IntegrationDeployer is ExistingDeploymentParser {

function _genRandUser(string memory name, uint userType) internal returns (User user) {
// Create User contract based on userType:
if (forkType == LOCAL) {
if (forkType == LOCAL || (forkType == MAINNET && isUpgraded)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems this has some cherry picked stuff from another branch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, will be resolved once I merge this into feat/prooftra and rebase that off of dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants