@@ -60,8 +60,8 @@ import {TransientSlot} from "@oz/utils/TransientSlot.sol";
6060 * 4. Seed Management:
6161 * - Sample seeds determine committee and proposer selection for each epoch
6262 * - Seeds use prevrandao from L1 blocks combined with epoch number for unpredictability
63- * - Seeds are set 2 epochs in advance to prevent last-minute manipulation and provide L1-reorg resistance
64- * - First two epochs use maximum seed values (type(uint224).max) for bootstrap (this results in the committee
63+ * - Prevrandao are set 2 epochs in advance to prevent last-minute manipulation and provide L1-reorg resistance
64+ * - First two epochs use randao values (type(uint224).max) for bootstrap (this results in the committee
6565 * being predictable in the first 2 epochs which is considered acceptable when bootstrapping the network)
6666 *
6767 * 5. Caching and Optimization:
@@ -134,9 +134,8 @@ library ValidatorSelectionLib {
134134 ValidatorSelectionStorage storage store = getStorage ();
135135 store.targetCommitteeSize = _targetCommitteeSize;
136136
137- // Set the sample seed for the first 2 epochs to max
138- store.seeds.push (0 , type (uint224 ).max);
139- store.seeds.push (1 , type (uint224 ).max);
137+ // Set the initial randao
138+ store.randaos.push (0 , uint224 (block .prevrandao ));
140139 }
141140
142141 /**
@@ -155,11 +154,11 @@ library ValidatorSelectionLib {
155154
156155 //################ Seeds ################
157156 // Get the sample seed for this current epoch.
158- uint224 sampleSeed = getSampleSeed (_epochNumber);
157+ uint256 sampleSeed = getSampleSeed (_epochNumber);
159158
160- // Set the sample seed for the next epoch if required
159+ // Checkpoint randao for future sampling if required
161160 // function handles the case where it is already set
162- setSampleSeedForNextEpoch (_epochNumber);
161+ checkpointRandao (_epochNumber);
163162
164163 //################ Committee ################
165164 // If the committee is not set for this epoch, we need to sample it
@@ -228,7 +227,7 @@ library ValidatorSelectionLib {
228227 }
229228
230229 // Get the proposer from the committee based on the epoch, slot, and sample seed
231- uint224 sampleSeed = getSampleSeed (_epochNumber);
230+ uint256 sampleSeed = getSampleSeed (_epochNumber);
232231 proposerIndex = computeProposerIndex (_epochNumber, _slot, sampleSeed, committeeSize);
233232 proposer = committee[proposerIndex];
234233
@@ -384,7 +383,7 @@ library ValidatorSelectionLib {
384383
385384 Epoch epochNumber = _slot.epochFromSlot ();
386385
387- uint224 sampleSeed = getSampleSeed (epochNumber);
386+ uint256 sampleSeed = getSampleSeed (epochNumber);
388387 (uint32 ts , uint256 [] memory indices ) = sampleValidatorsIndices (epochNumber, sampleSeed);
389388 uint256 committeeSize = indices.length ;
390389 if (committeeSize == 0 ) {
@@ -402,7 +401,7 @@ library ValidatorSelectionLib {
402401 * @param _seed The cryptographic seed for sampling randomness
403402 * @return The array of validator addresses selected for the committee
404403 */
405- function sampleValidators (Epoch _epoch , uint224 _seed ) internal returns (address [] memory ) {
404+ function sampleValidators (Epoch _epoch , uint256 _seed ) internal returns (address [] memory ) {
406405 (uint32 ts , uint256 [] memory indices ) = sampleValidatorsIndices (_epoch, _seed);
407406 return StakingLib.getAttestersFromIndicesAtTime (Timestamp.wrap (ts), indices);
408407 }
@@ -415,7 +414,7 @@ library ValidatorSelectionLib {
415414 * @return The array of committee member addresses for the epoch
416415 */
417416 function getCommitteeAt (Epoch _epochNumber ) internal returns (address [] memory ) {
418- uint224 seed = getSampleSeed (_epochNumber);
417+ uint256 seed = getSampleSeed (_epochNumber);
419418 return sampleValidators (_epochNumber, seed);
420419 }
421420
@@ -444,41 +443,34 @@ library ValidatorSelectionLib {
444443 }
445444
446445 /**
447- * @notice Sets the sample seed for the epoch two epochs in the future
448- * @dev Calls setSampleSeedForEpoch with _epoch + 2 to maintain the two-epoch advance requirement.
449- * This ensures randomness seeds are set well in advance to prevent manipulation.
450- * @param _epoch The current epoch (seed will be set for _epoch + 2)
446+ * @notice Checkpoints randao value for future usage
447+ * @dev Checks if already stored before computing and storing the randao value.
448+ * Offset the epoch by 2 to maintain the two-epoch advance requirement.
449+ * This ensures randomness are set well in advance to prevent manipulation.
450+ * @param _epoch The current epoch (randao will be set for _epoch + 2)
451+ * Passed to reduce recomputation
451452 */
452- function setSampleSeedForNextEpoch (Epoch _epoch ) internal {
453- setSampleSeedForEpoch (_epoch + Epoch.wrap (2 ));
454- }
455-
456- /**
457- * @notice Sets the sample seed for a specific epoch if not already set
458- * @dev Checks if the seed is already stored before computing and storing a new one.
459- * Uses computeNextSeed() to generate cryptographically secure randomness.
460- * @param _epoch The epoch to set the sample seed for
461- */
462- function setSampleSeedForEpoch (Epoch _epoch ) internal {
453+ function checkpointRandao (Epoch _epoch ) internal {
463454 ValidatorSelectionStorage storage store = getStorage ();
464- uint32 epoch = Epoch.unwrap (_epoch).toUint32 ();
455+
456+ // Compute the offset
457+ uint32 epoch = Epoch.unwrap (_epoch).toUint32 () + 2 ;
465458
466459 // Check if the latest checkpoint is for the next epoch
467- // It should be impossible that zero epoch snapshots exist, as in the genesis state we push the first sample seed
460+ // It should be impossible that zero epoch snapshots exist, as in the genesis state we push the first values
468461 // into the store
469- (, uint32 mostRecentSeedEpoch ,) = store.seeds .latestCheckpoint ();
462+ (, uint32 mostRecentEpoch ,) = store.randaos .latestCheckpoint ();
470463
471- // If the sample seed for the next epoch is already set, we can skip the computation
472- if (mostRecentSeedEpoch == epoch) {
464+ // If the randao for the next epoch is already set, we can skip the computation
465+ if (mostRecentEpoch == epoch) {
473466 return ;
474467 }
475468
476- // If the most recently stored seed is less than the epoch we are querying, then we need to compute its seed for
469+ // If the most recently stored epoch is less than the epoch we are querying, then we need to store randao for
477470 // later use
478- if (mostRecentSeedEpoch < epoch) {
479- // Compute the sample seed for the next epoch
480- uint224 nextSeed = computeNextSeed (_epoch);
481- store.seeds.push (epoch, nextSeed);
471+ if (mostRecentEpoch < epoch) {
472+ // Truncate the randao to be used for future sampling.
473+ store.randaos.push (epoch, uint224 (block .prevrandao ));
482474 }
483475 }
484476
@@ -558,15 +550,14 @@ library ValidatorSelectionLib {
558550
559551 /**
560552 * @notice Gets the cryptographic sample seed for an epoch
561- * @dev Retrieves the seed from the checkpointed seeds mapping using upperLookup.
562- * The seed is computed as keccak256(epoch, block.prevrandao) during epoch setup.
563- * Never called for epoch 0 or future epochs - only for current/past epochs.
553+ * @dev Retrieves the randao from the checkpointed randaos mapping using upperLookup.
554+ * Then computes the sample seed using keccak256(epoch, randao)
564555 * @param _epoch The epoch to get the sample seed for
565- * @return The 224-bit sample seed used for validator selection randomness
556+ * @return The sample seed used for validator selection randomness
566557 */
567- function getSampleSeed (Epoch _epoch ) internal view returns (uint224 ) {
558+ function getSampleSeed (Epoch _epoch ) internal view returns (uint256 ) {
568559 ValidatorSelectionStorage storage store = getStorage ();
569- return store.seeds .upperLookup (Epoch.unwrap (_epoch).toUint32 ());
560+ return uint256 ( keccak256 ( abi.encode (_epoch, store.randaos .upperLookup (Epoch.unwrap (_epoch).toUint32 ())) ));
570561 }
571562
572563 /**
@@ -607,7 +598,7 @@ library ValidatorSelectionLib {
607598 * @return indices Array of validator indices selected for the committee
608599 * @custom:reverts Errors.ValidatorSelection__InsufficientCommitteeSize if not enough validators available
609600 */
610- function sampleValidatorsIndices (Epoch _epoch , uint224 _seed ) private returns (uint32 , uint256 [] memory ) {
601+ function sampleValidatorsIndices (Epoch _epoch , uint256 _seed ) private returns (uint32 , uint256 [] memory ) {
611602 ValidatorSelectionStorage storage store = getStorage ();
612603 uint32 ts = epochToSampleTime (_epoch);
613604 uint256 validatorSetSize = StakingLib.getAttesterCountAtTime (Timestamp.wrap (ts));
@@ -625,18 +616,6 @@ library ValidatorSelectionLib {
625616 return (ts, SampleLib.computeCommittee (targetCommitteeSize, validatorSetSize, _seed));
626617 }
627618
628- /**
629- * @notice Computes the cryptographic seed for an epoch using L1 randomness
630- * @dev Combines epoch number with block.prevrandao to create unpredictable but deterministic seed.
631- * Including epoch prevents foundry testing issues where prevrandao might be zero.
632- * @param _epoch The epoch to compute the seed for
633- * @return The computed 224-bit seed (truncated from 256-bit keccak output)
634- */
635- function computeNextSeed (Epoch _epoch ) private view returns (uint224 ) {
636- // Allow for unsafe (lossy) downcast as we do not care if we loose bits
637- return uint224 (uint256 (keccak256 (abi.encode (_epoch, block .prevrandao ))));
638- }
639-
640619 /**
641620 * @notice Computes the keccak256 commitment hash for a committee member array
642621 * @dev Creates a cryptographic commitment to the committee composition that can be verified later.
0 commit comments