-
Notifications
You must be signed in to change notification settings - Fork 598
feat(rollup): seed snapshots #13577
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(rollup): seed snapshots #13577
Changes from all commits
53abe87
5a6fe8a
6bbadd4
9724ef2
b212389
de29295
40967d9
4321429
1ecec4f
08d1132
091c43e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
|
@@ -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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a note for my sanity, we use the case where |
||
| 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 ################ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -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) | ||
| { | ||
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -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) { | ||
|
|
@@ -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)))); | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
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? 👀