@@ -130,12 +130,12 @@ library ValidatorSelectionLib {
130130 * The first two epochs use maximum seed values for startup.
131131 * @param _targetCommitteeSize The desired number of validators in each epoch's committee
132132 */
133- function initialize (uint256 _targetCommitteeSize ) internal {
133+ function initialize (uint256 _targetCommitteeSize , uint256 _lagInEpochs ) internal {
134134 ValidatorSelectionStorage storage store = getStorage ();
135- store.targetCommitteeSize = _targetCommitteeSize;
135+ store.targetCommitteeSize = _targetCommitteeSize.toUint32 ();
136+ store.lagInEpochs = _lagInEpochs.toUint32 ();
136137
137- // Set the initial randao
138- store.randaos.push (0 , uint224 (block .prevrandao ));
138+ checkpointRandao (Epoch.wrap (0 ));
139139 }
140140
141141 /**
@@ -453,33 +453,22 @@ library ValidatorSelectionLib {
453453
454454 /**
455455 * @notice Checkpoints randao value for future usage
456- * @dev Checks if already stored before computing and storing the randao value.
457- * Offset the epoch by 2 to maintain the two-epoch advance requirement.
458- * This ensures randomness are set well in advance to prevent manipulation.
459- * @param _epoch The current epoch (randao will be set for _epoch + 2)
460- * Passed to reduce recomputation
456+ * @dev Checks if already stored before storing the randao value.
457+ * @param _epoch The current epoch
461458 */
462459 function checkpointRandao (Epoch _epoch ) internal {
463460 ValidatorSelectionStorage storage store = getStorage ();
464461
465- // Compute the offset
466- uint32 epoch = Epoch.unwrap (_epoch).toUint32 () + 2 ;
467-
468462 // Check if the latest checkpoint is for the next epoch
469463 // It should be impossible that zero epoch snapshots exist, as in the genesis state we push the first values
470464 // into the store
471- (, uint32 mostRecentEpoch ,) = store.randaos.latestCheckpoint ();
472-
473- // If the randao for the next epoch is already set, we can skip the computation
474- if (mostRecentEpoch == epoch) {
475- return ;
476- }
465+ (, uint32 mostRecentTs ,) = store.randaos.latestCheckpoint ();
466+ uint32 ts = Timestamp.unwrap (_epoch.toTimestamp ()).toUint32 ();
477467
478468 // If the most recently stored epoch is less than the epoch we are querying, then we need to store randao for
479- // later use
480- if (mostRecentEpoch < epoch) {
481- // Truncate the randao to be used for future sampling.
482- store.randaos.push (epoch, uint224 (block .prevrandao ));
469+ // later use. We truncate to save storage costs.
470+ if (mostRecentTs < ts) {
471+ store.randaos.push (ts, uint224 (block .prevrandao ));
483472 }
484473 }
485474
@@ -539,22 +528,16 @@ library ValidatorSelectionLib {
539528 * @notice Converts an epoch number to the timestamp used for validator set sampling
540529 * @dev Calculates the sampling timestamp by:
541530 * 1. Taking the epoch start timestamp
542- * 2. Subtracting one full epoch duration to ensure stability
543- * 3. Subtracting 1 second to get end-of-block state
531+ * 2. Subtracting `lagInEpochs` full epoch duration to ensure stability
544532 *
545533 * This ensures validator set sampling uses stable historical data that won't be
546534 * affected by last-minute changes or L1 reorgs during synchronization.
547535 * @param _epoch The epoch to calculate sampling time for
548536 * @return The Unix timestamp (uint32) to use for validator set sampling
549537 */
550538 function epochToSampleTime (Epoch _epoch ) internal view returns (uint32 ) {
551- // We do -1, as the snapshots practically happen at the end of the block, e.g.,
552- // a tx manipulating the set in at $t$ would be visible already at lookup $t$ if after that
553- // transactions. But reading at $t-1$ would be the state at the end of $t-1$ which is the state
554- // as we "start" time $t$. We then shift that back by an entire L2 epoch to guarantee
555- // we are not hit by last-minute changes or L1 reorgs when syncing validators from our clients.
556-
557- return Timestamp.unwrap (_epoch.toTimestamp ()).toUint32 () - uint32 (TimeLib.getEpochDurationInSeconds ()) - 1 ;
539+ uint32 sub = getStorage ().lagInEpochs * TimeLib.getEpochDurationInSeconds ().toUint32 ();
540+ return Timestamp.unwrap (_epoch.toTimestamp ()).toUint32 () - sub;
558541 }
559542
560543 /**
@@ -566,7 +549,17 @@ library ValidatorSelectionLib {
566549 */
567550 function getSampleSeed (Epoch _epoch ) internal view returns (uint256 ) {
568551 ValidatorSelectionStorage storage store = getStorage ();
569- return uint256 (keccak256 (abi.encode (_epoch, store.randaos.upperLookup (Epoch.unwrap (_epoch).toUint32 ()))));
552+ uint32 ts = epochToSampleTime (_epoch);
553+ return uint256 (keccak256 (abi.encode (_epoch, store.randaos.upperLookup (ts))));
554+ }
555+
556+ function getSamplingSize (Epoch _epoch ) internal view returns (uint256 ) {
557+ uint32 ts = epochToSampleTime (_epoch);
558+ return StakingLib.getAttesterCountAtTime (Timestamp.wrap (ts));
559+ }
560+
561+ function getLagInEpochs () internal view returns (uint256 ) {
562+ return getStorage ().lagInEpochs;
570563 }
571564
572565 /**
0 commit comments