Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion l1-contracts/src/core/RollupCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ contract RollupCore is
onlyOwner
{
CheatLib.cheat__InitialiseValidatorSet(_args);
setupEpoch();
Copy link
Contributor

Choose a reason for hiding this comment

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

This one was leftover from last? 👀

}

function setEpochVerifier(address _verifier) external override(ITestRollup) onlyOwner {
Expand Down
9 changes: 4 additions & 5 deletions l1-contracts/src/core/interfaces/IValidatorSelection.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@
pragma solidity >=0.8.27;

import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol";

import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
/**
* @notice The data structure for an epoch
* @param committee - The attesters for the epoch
* @param sampleSeed - The seed used to sample the attesters of the epoch
* @param nextSeed - The seed used to influence the NEXT epoch
*/

struct EpochData {
// TODO: remove in favor of commitment to comittee
address[] committee;
uint256 sampleSeed;
uint256 nextSeed;
}

struct ValidatorSelectionStorage {
// A mapping to snapshots of the validator set
mapping(Epoch => EpochData) epochs;
// The last stored randao value, same value as `seed` in the last inserted epoch
uint256 lastSeed;
// Checkpointed map of epoch -> sample seed
Checkpoints.Trace224 seeds;
uint256 targetCommitteeSize;
}

Expand Down
8 changes: 6 additions & 2 deletions l1-contracts/src/core/libraries/rollup/ExtRollupLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
pragma solidity >=0.8.27;

import {SubmitEpochRootProofArgs, PublicInputArgs} from "@aztec/core/interfaces/IRollup.sol";
import {Epoch, Timestamp, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {StakingLib} from "./../staking/StakingLib.sol";
import {ValidatorSelectionLib} from "./../validator-selection/ValidatorSelectionLib.sol";
import {BlobLib} from "./BlobLib.sol";
import {EpochProofLib} from "./EpochProofLib.sol";
import {ProposeLib, ProposeArgs, Signature} from "./ProposeLib.sol";

// We are using this library such that we can more easily "link" just a larger external library
// instead of a few smaller ones.

library ExtRollupLib {
using TimeLib for Timestamp;

function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external {
EpochProofLib.submitEpochRootProof(_args);
}
Expand All @@ -30,7 +33,8 @@ library ExtRollupLib {
}

function setupEpoch() external {
ValidatorSelectionLib.setupEpoch(StakingLib.getStorage());
Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp();
ValidatorSelectionLib.setupEpoch(StakingLib.getStorage(), currentEpoch);
}

function getEpochProofPublicInputs(
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/src/core/libraries/rollup/ProposeLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ library ProposeLib {
BlobLib.validateBlobs(_blobInput, _checkBlob);

Header memory header = HeaderLib.decode(_args.header);

v.headerHash = HeaderLib.hash(_args.header);

ValidatorSelectionLib.setupEpoch(StakingLib.getStorage());
Epoch currentEpoch = Timestamp.wrap(block.timestamp).epochFromTimestamp();
ValidatorSelectionLib.setupEpoch(StakingLib.getStorage(), currentEpoch);

ManaBaseFeeComponents memory components =
getManaBaseFeeComponentsAt(Timestamp.wrap(block.timestamp), true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
} from "@aztec/core/libraries/staking/AddressSnapshotLib.sol";
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol";
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
import {Checkpoints} from "@oz/utils/structs/Checkpoints.sol";
import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol";

library ValidatorSelectionLib {
Expand All @@ -24,34 +26,42 @@ library ValidatorSelectionLib {
using SignatureLib for Signature;
using TimeLib for Timestamp;
using AddressSnapshotLib for SnapshottedAddressSet;
using Checkpoints for Checkpoints.Trace224;
using SafeCast for *;

bytes32 private constant VALIDATOR_SELECTION_STORAGE_POSITION =
keccak256("aztec.validator_selection.storage");

function initialize(uint256 _targetCommitteeSize) internal {
ValidatorSelectionStorage storage store = getStorage();
store.targetCommitteeSize = _targetCommitteeSize;

// Set the sample seed for the first epoch to max
store.seeds.push(0, type(uint224).max);
}

/**
* @notice Performs a setup of an epoch if needed. The setup will
* - Sample the validator set for the epoch
* - Set the seed for the epoch
* - Update the last seed
*
* @dev Since this is a reference optimising for simplicity, we store the actual validator set in the epoch structure.
* This is very heavy on gas, so start crying because the gas here will melt the poles
* https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp
* - Set the seed for the next epoch
*/
function setupEpoch(StakingStorage storage _stakingStore) internal {
Epoch epochNumber = Timestamp.wrap(block.timestamp).epochFromTimestamp();
function setupEpoch(StakingStorage storage _stakingStore, Epoch _epochNumber) internal {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a note for my sanity, we use the case where _epochNumber != currentEpoch when we mught wanna look at a historical and not setup epoch as the sampleValidators are using it.

ValidatorSelectionStorage storage store = getStorage();
EpochData storage epoch = store.epochs[epochNumber];

if (epoch.sampleSeed == 0) {
epoch.sampleSeed = getSampleSeed(epochNumber);
epoch.nextSeed = store.lastSeed = computeNextSeed(epochNumber);
epoch.committee = sampleValidators(_stakingStore, epochNumber, epoch.sampleSeed);
//################ Seeds ################
Copy link
Contributor

Choose a reason for hiding this comment

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

🙏

// Get the sample seed for this current epoch.
uint224 sampleSeed = getSampleSeed(_epochNumber);

// Set the sample seed for the next epoch if required
// function handles the case where it is already set
setSampleSeedForEpoch(_epochNumber + Epoch.wrap(1));

//################ Committee ################
// If the committee is not set for this epoch, we need to sample it
EpochData storage epoch = store.epochs[_epochNumber];
uint256 committeeLength = epoch.committee.length;
if (committeeLength == 0) {
epoch.committee = sampleValidators(_stakingStore, _epochNumber, sampleSeed);
}
}

Expand Down Expand Up @@ -159,7 +169,7 @@ library ValidatorSelectionLib {
*
* @return The validators for the given epoch
*/
function sampleValidators(StakingStorage storage _stakingStore, Epoch _epoch, uint256 _seed)
function sampleValidators(StakingStorage storage _stakingStore, Epoch _epoch, uint224 _seed)
internal
returns (address[] memory)
{
Expand Down Expand Up @@ -187,30 +197,53 @@ library ValidatorSelectionLib {
return committee;
}

/**
* @notice Get the committee for an epoch
*
* @param _epochNumber - The epoch to get the committee for
*
* @return The committee for the epoch
*/
function getCommitteeAt(StakingStorage storage _stakingStore, Epoch _epochNumber)
internal
returns (address[] memory)
{
ValidatorSelectionStorage storage store = getStorage();
EpochData storage epoch = store.epochs[_epochNumber];

if (epoch.sampleSeed != 0) {
uint256 committeeSize = epoch.committee.length;
if (committeeSize == 0) {
return new address[](0);
}
return epoch.committee;
// If no committee has been stored, then we need to setup the epoch
uint256 committeeSize = epoch.committee.length;
if (committeeSize == 0) {
// This will set epoch.committee and the next sample seed in the store, meaning epoch.commitee on the line below will be set (storage reference)
setupEpoch(_stakingStore, _epochNumber);
}
return epoch.committee;
}

// Allow anyone if there is no validator set
if (_stakingStore.attesters.length() == 0) {
return new address[](0);
}
/**
* @notice Sets the sample seed for an epoch
*
* @param _epoch - The epoch to set the sample seed for
*/
function setSampleSeedForEpoch(Epoch _epoch) internal {
ValidatorSelectionStorage storage store = getStorage();
uint32 epoch = Epoch.unwrap(_epoch).toUint32();

// Check if the latest checkpoint is for the next epoch
Copy link
Contributor

Choose a reason for hiding this comment

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

If we set the initial using the constructor as well it might simplify us slightly here as well as we won't have the case where the last checkpoint does not exists so one less branch to deal with.

// It should be impossible that zero epoch snapshots exist, as in the genesis state we push the first sample seed into the store
(, uint32 mostRecentSeedEpoch,) = store.seeds.latestCheckpoint();

// Emulate a sampling of the validators
uint256 sampleSeed = getSampleSeed(_epochNumber);
// If the sample seed for the next epoch is already set, we can skip the computation
if (mostRecentSeedEpoch == epoch) {
return;
}

return sampleValidators(_stakingStore, _epochNumber, sampleSeed);
// If the most recently stored seed is less than the epoch we are querying, then we need to compute it's seed for later use
if (mostRecentSeedEpoch < epoch) {
// Compute the sample seed for the next epoch
uint224 nextSeed = computeNextSeed(_epoch);
store.seeds.push(epoch, nextSeed);
}
}

/**
Expand All @@ -227,22 +260,13 @@ library ValidatorSelectionLib {
*
* @return The sample seed for the epoch
*/
function getSampleSeed(Epoch _epoch) internal view returns (uint256) {
if (Epoch.unwrap(_epoch) == 0) {
return type(uint256).max;
}
function getSampleSeed(Epoch _epoch) internal view returns (uint224) {
ValidatorSelectionStorage storage store = getStorage();
uint256 sampleSeed = store.epochs[_epoch].sampleSeed;
if (sampleSeed != 0) {
return sampleSeed;
uint224 sampleSeed = store.seeds.upperLookup(Epoch.unwrap(_epoch).toUint32());
if (sampleSeed == 0) {
sampleSeed = type(uint224).max;
}

sampleSeed = store.epochs[_epoch - Epoch.wrap(1)].nextSeed;
if (sampleSeed != 0) {
return sampleSeed;
}

return store.lastSeed;
return sampleSeed;
}

function getStorage() internal pure returns (ValidatorSelectionStorage storage storageStruct) {
Expand All @@ -262,8 +286,9 @@ library ValidatorSelectionLib {
*
* @return The computed seed
*/
function computeNextSeed(Epoch _epoch) private view returns (uint256) {
return uint256(keccak256(abi.encode(_epoch, block.prevrandao)));
function computeNextSeed(Epoch _epoch) private view returns (uint224) {
// Allow for unsafe (lossy) downcast as we do not care if we loose bits
return uint224(uint256(keccak256(abi.encode(_epoch, block.prevrandao))));
}

/**
Expand Down
Loading