Skip to content

Commit d0194a4

Browse files
committed
chore: update get sample seed
1 parent a7e30e7 commit d0194a4

8 files changed

Lines changed: 92 additions & 100 deletions

File tree

l1-contracts/src/core/RollupCore.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,8 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
526526
* `ExtRollupLib.propose(...)` -> `ProposeLib.propose(...)` -> `ValidatorSelectionLib.setupEpoch(...)`).
527527
*
528528
* If there are missed proposals then setupEpoch does not get called automatically. Since the next committee
529-
* selection is computed based on the latest stored seed and the epoch number, we would only fail to get a
530-
* fresh seed if:
529+
* selection is computed based on the stored randao and the epoch number, failing to update the randao stored
530+
* will keep the committee predictable longer into the future. We would only fail to get a fresh randao if:
531531
* 1. All the proposals in the epoch were missed
532532
* 2. Nobody called setupEpoch on the Rollup contract
533533
*
@@ -540,13 +540,13 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
540540
}
541541

542542
/**
543-
* @notice Captures the seed for validator selection in the next epoch
544-
* @dev Can be called by anyone. Takes a snapshot of the current state to ensure
545-
* unpredictable but deterministic validator selection. Automatically called
546-
* from setupEpoch.
543+
* @notice Captures the randao for future validator selection
544+
* @dev Can be called by anyone. Takes a snapshot of the current randao to ensure unpredictable but deterministic
545+
* validator selection. Automatically called from setupEpoch. Can be used as a cheaper alternative to
546+
* `setupEpoch` to update the randao checkpoints.
547547
*/
548-
function setupSeedSnapshotForNextEpoch() public override(IValidatorSelectionCore) {
549-
ExtRollupLib2.setupSeedSnapshotForNextEpoch();
548+
function checkpointRandao() public override(IValidatorSelectionCore) {
549+
ExtRollupLib2.checkpointRandao();
550550
}
551551

552552
/**

l1-contracts/src/core/interfaces/IValidatorSelection.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
99
struct ValidatorSelectionStorage {
1010
// A mapping to snapshots of the validator set
1111
mapping(Epoch => bytes32 committeeCommitment) committeeCommitments;
12-
// Checkpointed map of epoch -> sample seed
13-
Checkpoints.Trace224 seeds;
12+
// Checkpointed map of epoch -> randao value
13+
Checkpoints.Trace224 randaos;
1414
uint256 targetCommitteeSize;
1515
}
1616

1717
interface IValidatorSelectionCore {
1818
function setupEpoch() external;
19-
function setupSeedSnapshotForNextEpoch() external;
19+
function checkpointRandao() external;
2020
}
2121

2222
interface IValidatorSelection is IValidatorSelectionCore, IEmperor {

l1-contracts/src/core/libraries/rollup/ExtRollupLib2.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ library ExtRollupLib2 {
7171
ValidatorSelectionLib.setupEpoch(currentEpoch);
7272
}
7373

74-
function setupSeedSnapshotForNextEpoch() external {
74+
function checkpointRandao() external {
7575
Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp();
76-
ValidatorSelectionLib.setSampleSeedForNextEpoch(currentEpoch);
76+
ValidatorSelectionLib.checkpointRandao(currentEpoch);
7777
}
7878

7979
function updateStakingQueueConfig(StakingQueueConfig memory _config) external {

l1-contracts/src/core/libraries/rollup/ValidatorSelectionLib.sol

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

l1-contracts/test/validator-selection/ValidatorSelection.t.sol

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase {
136136
assertEq(expectedProposer, actualProposer, "Invalid proposer");
137137
}
138138

139-
function testCommitteeForNonSetupEpoch(uint8 _epochsToJump) public setup(4, 4) progressEpochs(2) {
139+
function testCommitteeForNonSetupEpoch() public setup(8, 4) progressEpochs(2) {
140140
Epoch pre = rollup.getCurrentEpoch();
141-
vm.warp(block.timestamp + uint256(_epochsToJump) * rollup.getEpochDuration() * rollup.getSlotDuration());
141+
// Jump 8 epochs into the future to ensure that it haven't been setup.
142+
vm.warp(block.timestamp + 8 * rollup.getEpochDuration() * rollup.getSlotDuration());
142143

143144
Epoch post = rollup.getCurrentEpoch();
144145

@@ -151,8 +152,8 @@ contract ValidatorSelectionTest is ValidatorSelectionTestBase {
151152
assertEq(preCommittee.length, expectedSize, "Invalid committee size");
152153
assertEq(postCommittee.length, expectedSize, "Invalid committee size");
153154

154-
// Elements in the committee should be the same
155-
assertEq(preCommittee, postCommittee, "Committee elements have changed");
155+
// Elements in the committee should **not** be the same, as the epoch is mixed into the seed
156+
assertNotEq(preCommittee, postCommittee, "Committee elements have not changed");
156157
}
157158

158159
function testStableCommittee(uint8 _timeToJump) public setup(4, 4) progressEpochs(2) {

0 commit comments

Comments
 (0)