-
Notifications
You must be signed in to change notification settings - Fork 614
feat(contracts): static + snapshotted validator set #13046
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
Merged
Merged
Changes from 13 commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
7374db1
validator snapshots
Maddiaa0 e74a472
test: add cheating time
Maddiaa0 dfbddb2
checkpoint
Maddiaa0 0b4038a
chore: shift sampling
Maddiaa0 50d96e3
fix: other tests
Maddiaa0 642572d
clean
Maddiaa0 0829959
include tree
Maddiaa0 6f72dd2
chore: remove redundant test
Maddiaa0 30bbf83
clean
Maddiaa0 a9c4263
fix: post rebase
Maddiaa0 93c6939
chore: embed time cheater into staking cheater + prev linting rules
Maddiaa0 76b0454
include: benchmarks, it gets worse before it gets better
Maddiaa0 3c85b8f
chore: include setupepoch in view function
Maddiaa0 d166a5f
chore: more isolated tree testing
Maddiaa0 7243887
Merge branch 'master' into md/val-snapshot
Maddiaa0 ee20cc5
chore: remove old tests
Maddiaa0 633839c
feat: also snapshot size of set
Maddiaa0 bb0868a
chore: update other tests
Maddiaa0 b485608
fix: staking asset handler tests
Maddiaa0 dd2869d
fmt
Maddiaa0 80160f5
fix: bench test
Maddiaa0 90200fd
Merge branch 'master' into md/val-snapshot
Maddiaa0 91796ff
chore: update bench
Maddiaa0 5186c39
chore: add length and values at epoch test
Maddiaa0 999c6b7
chore: less optimizer runs
Maddiaa0 2b111ed
chore: remove bytecode hash
Maddiaa0 c912aa1
chore: remove unneeded setup epoch calls
Maddiaa0 1c9822d
chore: expect revert wrapper lib
Maddiaa0 52050cf
Merge branch 'master' into md/val-snapshot
Maddiaa0 4869a41
fix: merge update
Maddiaa0 c57c437
Merge branch 'master' into md/val-snapshot
Maddiaa0 8eb8e35
fix: migrate to checkpoint lib
Maddiaa0 8d774e7
exp: remove foundry changes
Maddiaa0 e2213d1
comments
Maddiaa0 d165b11
fmt
Maddiaa0 2141087
WARNING: NO ATTESTERS ON THE ORIGINAL COMMITTEE
Maddiaa0 42c147f
Merge branch 'master' into md/val-snapshot
Maddiaa0 9e7579c
fix: update slashing tesst
Maddiaa0 698e969
fix: slashing test take 2
Maddiaa0 4c5086c
chore: update
Maddiaa0 86b1bd3
fmt
Maddiaa0 5de3410
test: add tests for new guards
Maddiaa0 c082c86
chore: extra test case
Maddiaa0 0500342
chore: remove length query from future epoch
Maddiaa0 28cc590
chore: fuzz tests for add.t.sol
Maddiaa0 b51dcd5
fix: prop test suite
Maddiaa0 39de094
ordering
Maddiaa0 8fa9dc9
chore: use l1 tx utils
Maddiaa0 001c2b4
Merge branch 'master' into md/val-snapshot
Maddiaa0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,26 @@ | ||
| | src/core/Rollup.sol:Rollup contract | | | | | | | ||
| |-------------------------------------|-----------------|----------|----------|----------|---------| | ||
| | Deployment Cost | Deployment Size | | | | | | ||
| | 7944840 | 38653 | | | | | | ||
| | 8154934 | 39929 | | | | | | ||
| | Function Name | min | avg | median | max | # calls | | ||
| | cheat__InitialiseValidatorSet | 13524835 | 13524835 | 13524835 | 13524835 | 1 | | ||
| | cheat__InitialiseValidatorSet | 15884661 | 15884661 | 15884661 | 15884661 | 1 | | ||
| | getBlock | 1230 | 1230 | 1230 | 1230 | 12 | | ||
| | getBurnAddress | 369 | 369 | 369 | 369 | 1 | | ||
| | getCurrentEpoch | 1017 | 1017 | 1017 | 1017 | 397 | | ||
| | getCurrentProposer | 139908 | 143780 | 140137 | 263958 | 200 | | ||
| | getCurrentSlot | 823 | 833 | 823 | 4823 | 397 | | ||
| | getEpochCommittee | 135768 | 139483 | 135780 | 259358 | 100 | | ||
| | getEpochForBlock | 1016 | 1016 | 1016 | 1016 | 196 | | ||
| | getCurrentEpoch | 979 | 979 | 979 | 979 | 397 | | ||
| | getCurrentProposer | 135981 | 143548 | 136210 | 383219 | 200 | | ||
| | getCurrentSlot | 735 | 740 | 735 | 2735 | 397 | | ||
| | getEpochCommittee | 135880 | 143291 | 135892 | 382675 | 100 | | ||
| | getEpochForBlock | 1065 | 1065 | 1065 | 1065 | 196 | | ||
| | getFeeHeader | 1457 | 1457 | 1457 | 1457 | 95 | | ||
| | getManaBaseFeeAt | 20442 | 26863 | 27182 | 34313 | 195 | | ||
| | getManaBaseFeeAt | 20381 | 26792 | 27121 | 32242 | 195 | | ||
| | getPendingBlockNumber | 507 | 511 | 507 | 2507 | 401 | | ||
| | getProvenBlockNumber | 490 | 490 | 490 | 490 | 3 | | ||
| | getTimestampForSlot | 887 | 887 | 887 | 887 | 195 | | ||
| | getTimestampForSlot | 802 | 802 | 802 | 802 | 195 | | ||
| | setProvingCostPerMana | 25915 | 25942 | 25915 | 28715 | 101 | | ||
| | submitEpochRootProof | 771062 | 783900 | 771086 | 809552 | 3 | | ||
| | submitEpochRootProof | 767179 | 780017 | 767203 | 805669 | 3 | | ||
| | src/periphery/Forwarder.sol:Forwarder contract | | | | | | | ||
| |------------------------------------------------|-----------------|--------|--------|---------|---------| | ||
| | Deployment Cost | Deployment Size | | | | | | ||
| | 358690 | 1553 | | | | | | ||
| | Function Name | min | avg | median | max | # calls | | ||
| | forward | 634788 | 685012 | 645781 | 1916159 | 100 | | ||
| | forward | 630841 | 685437 | 641834 | 2057987 | 100 | | ||
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
l1-contracts/src/core/libraries/staking/AddressSnapshotLib.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright 2025 Aztec Labs. | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {Errors} from "@aztec/core/libraries/Errors.sol"; | ||
| import {Timestamp, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; | ||
| import {SafeCast} from "@oz/utils/math/SafeCast.sol"; | ||
|
|
||
| /** | ||
| * @notice Structure to store a set of addresses with their historical snapshots | ||
| * @param size The current number of addresses in the set | ||
| * @param checkpoints Mapping of index to array of address snapshots | ||
| * @param validatorToIndex Mapping of validator address to its current index in the set | ||
| */ | ||
| struct SnapshottedAddressSet { | ||
| uint256 size; | ||
| mapping(uint256 index => AddressSnapshot[]) checkpoints; | ||
| // Store up to date position for each validator | ||
| mapping(address validator => uint256 index) validatorToIndex; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| /** | ||
| * @notice Structure to store a single address snapshot | ||
| * @param addr The address being snapshotted | ||
| * @param epochNumber The epoch number when this snapshot was taken | ||
| */ | ||
| struct AddressSnapshot { | ||
| address addr; | ||
| uint96 epochNumber; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| /** | ||
| * @title AddressSnapshotLib | ||
| * @notice A library for managing a set of addresses with historical snapshots | ||
| * @dev This library provides functionality similar to EnumerableSet but can track addresses across different epochs | ||
| * and allows querying the state of addresses at any point in time | ||
| */ | ||
| library AddressSnapshotLib { | ||
| using SafeCast for uint256; | ||
|
|
||
| /** | ||
| * @notice Bit mask used to indicate presence of a validator in the set | ||
| */ | ||
| uint256 internal constant PRESENCE_BIT = 1 << 255; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
|
|
||
| /** | ||
| * @notice Adds a validator to the set | ||
| * @param _self The storage reference to the set | ||
| * @param _validator The address of the validator to add | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| * @return bool True if the validator was added, false if it was already present | ||
| */ | ||
| function add(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { | ||
| // Prevent against double insertion | ||
| if (_self.validatorToIndex[_validator] != 0) { | ||
| return false; | ||
| } | ||
|
|
||
| // Insert into the end of the array | ||
| uint256 index = _self.size; | ||
| Epoch _epochNumber = TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp)); | ||
|
|
||
| // Include max bit to indicate that the validator is in the set - means 0 index is not 0 | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| uint256 indexWithBit = index | PRESENCE_BIT; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
|
|
||
| _self.validatorToIndex[_validator] = indexWithBit; | ||
| _self.checkpoints[index].push( | ||
| AddressSnapshot({addr: _validator, epochNumber: Epoch.unwrap(_epochNumber).toUint96()}) | ||
| ); | ||
| _self.size += 1; | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Removes a validator from the set by index | ||
| * @param _self The storage reference to the set | ||
| * @param _index The index of the validator to remove | ||
| * @return bool True if the validator was removed, false otherwise | ||
| */ | ||
| function remove(SnapshottedAddressSet storage _self, uint256 _index) internal returns (bool) { | ||
| // To remove from the list, we push the last item into the index and reduce the size | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| uint256 lastIndex = _self.size - 1; | ||
| uint256 lastSnapshotLength = _self.checkpoints[lastIndex].length; | ||
|
|
||
| uint256 epochNow = Epoch.unwrap(TimeLib.epochFromTimestamp(Timestamp.wrap(block.timestamp))); | ||
|
|
||
| AddressSnapshot memory lastSnapshot = _self.checkpoints[lastIndex][lastSnapshotLength - 1]; | ||
|
|
||
| address lastValidator = lastSnapshot.addr; | ||
| uint256 newLocation = _index | PRESENCE_BIT; | ||
|
|
||
| // If we are removing the last item, we cannot swap it with anything | ||
| // so we append a new address of zero for this epoch | ||
| // And since we are removing it, we set the location to 0 | ||
| if (lastIndex == _index) { | ||
| lastSnapshot.addr = address(0); | ||
| newLocation = 0; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| _self.validatorToIndex[lastValidator] = newLocation; | ||
|
|
||
| lastSnapshot.epochNumber = epochNow.toUint96(); | ||
| // Check if there's already a checkpoint for this index in the current epoch | ||
| uint256 checkpointCount = _self.checkpoints[_index].length; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| if ( | ||
| checkpointCount > 0 && _self.checkpoints[_index][checkpointCount - 1].epochNumber == epochNow | ||
| ) { | ||
| // If there's already a checkpoint for this epoch, update it instead of adding a new one | ||
| _self.checkpoints[_index][checkpointCount - 1] = lastSnapshot; | ||
| } else { | ||
| // Otherwise add a new checkpoint | ||
| _self.checkpoints[_index].push(lastSnapshot); | ||
| } | ||
|
|
||
| _self.size -= 1; | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Removes a validator from the set by address | ||
| * @param _self The storage reference to the set | ||
| * @param _validator The address of the validator to remove | ||
| * @return bool True if the validator was removed, false if it wasn't found | ||
| */ | ||
| function remove(SnapshottedAddressSet storage _self, address _validator) internal returns (bool) { | ||
| uint256 index = _self.validatorToIndex[_validator]; | ||
|
Maddiaa0 marked this conversation as resolved.
Outdated
|
||
| if (index == 0) { | ||
| return false; | ||
| } | ||
|
|
||
| // Remove top most bit | ||
| index = index & ~PRESENCE_BIT; | ||
| return remove(_self, index); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets the current address at a specific index | ||
| * @param _self The storage reference to the set | ||
| * @param _index The index to query | ||
| * @return address The current address at the given index | ||
| */ | ||
| function at(SnapshottedAddressSet storage _self, uint256 _index) internal view returns (address) { | ||
| uint256 numCheckpoints = _self.checkpoints[_index].length; | ||
|
|
||
| if (_index >= _self.size) { | ||
| revert Errors.AddressSnapshotLib__IndexOutOfBounds(_index, _self.size); | ||
| } | ||
|
|
||
| if (numCheckpoints == 0) { | ||
| return address(0); | ||
| } | ||
|
|
||
| AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; | ||
| return lastSnapshot.addr; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets the address at a specific index and epoch | ||
| * @param _self The storage reference to the set | ||
| * @param _index The index to query | ||
| * @param _epoch The epoch number to query | ||
| * @return address The address at the given index and epoch | ||
| */ | ||
| function getAddressFromIndexAtEpoch( | ||
|
LHerskind marked this conversation as resolved.
|
||
| SnapshottedAddressSet storage _self, | ||
| uint256 _index, | ||
| Epoch _epoch | ||
| ) internal view returns (address) { | ||
|
LHerskind marked this conversation as resolved.
|
||
| uint256 numCheckpoints = _self.checkpoints[_index].length; | ||
| uint96 epoch = Epoch.unwrap(_epoch).toUint96(); | ||
|
|
||
| if (numCheckpoints == 0) { | ||
| return address(0); | ||
| } | ||
|
|
||
| // Check the most recent checkpoint | ||
| AddressSnapshot memory lastSnapshot = _self.checkpoints[_index][numCheckpoints - 1]; | ||
| if (lastSnapshot.epochNumber <= epoch) { | ||
| return lastSnapshot.addr; | ||
| } | ||
|
|
||
| // Binary search | ||
| uint256 lower = 0; | ||
|
|
||
| unchecked { | ||
| uint256 upper = numCheckpoints - 1; | ||
| while (upper > lower) { | ||
| uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow | ||
| AddressSnapshot memory cp = _self.checkpoints[_index][center]; | ||
| if (cp.epochNumber == epoch) { | ||
| return cp.addr; | ||
| } else if (cp.epochNumber < epoch) { | ||
| lower = center; | ||
| } else { | ||
| upper = center - 1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return _self.checkpoints[_index][lower].addr; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets the current size of the set | ||
| * @param _self The storage reference to the set | ||
| * @return uint256 The number of addresses in the set | ||
| */ | ||
| function length(SnapshottedAddressSet storage _self) internal view returns (uint256) { | ||
| return _self.size; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets all current addresses in the set | ||
| * @param _self The storage reference to the set | ||
| * @return address[] Array of all current addresses in the set | ||
| */ | ||
| function values(SnapshottedAddressSet storage _self) internal view returns (address[] memory) { | ||
| uint256 size = _self.size; | ||
| address[] memory vals = new address[](size); | ||
| for (uint256 i; i < size;) { | ||
| vals[i] = at(_self, i); | ||
|
|
||
| unchecked { | ||
| i++; | ||
| } | ||
| } | ||
| return vals; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.