diff --git a/l1-contracts/gas_report.md b/l1-contracts/gas_report.md index ed4b2d8557d..d86cd782f35 100644 --- a/l1-contracts/gas_report.md +++ b/l1-contracts/gas_report.md @@ -4,27 +4,27 @@ | 589795 | 2886 | | | | | | Function Name | min | avg | median | max | # calls | | L2_TOKEN_ADDRESS | 194 | 194 | 194 | 194 | 256 | -| UNDERLYING | 270 | 270 | 270 | 270 | 3064 | -| canonicalRollup | 1016 | 3624 | 5516 | 5516 | 5550 | +| UNDERLYING | 270 | 270 | 270 | 270 | 3052 | +| canonicalRollup | 1016 | 3623 | 5516 | 5516 | 5547 | | depositToAztecPublic | 42745 | 127319 | 127958 | 127958 | 258 | | distributeFees | 27333 | 56798 | 57006 | 57006 | 258 | | initialize | 48963 | 48963 | 48963 | 48963 | 1566 | | src/core/Rollup.sol:Rollup contract | | | | | | |-------------------------------------|-----------------|---------|---------|----------|---------| | Deployment Cost | Deployment Size | | | | | -| 7993984 | 38372 | | | | | +| 7875866 | 37825 | | | | | | Function Name | min | avg | median | max | # calls | | archive | 605 | 605 | 605 | 605 | 2474 | -| cheat__InitialiseValidatorSet | 751841 | 8111188 | 751865 | 15671744 | 519 | +| cheat__InitialiseValidatorSet | 751841 | 7048889 | 751865 | 13518099 | 519 | | claimProverRewards | 31816 | 53337 | 34261 | 93936 | 3 | | claimSequencerRewards | 57196 | 57196 | 57196 | 57196 | 1 | -| deposit | 169711 | 325702 | 342585 | 342585 | 256 | +| deposit | 169711 | 327728 | 342585 | 342585 | 256 | | getAttesters | 1970 | 26343 | 26629 | 26629 | 259 | -| getBlock | 1230 | 1230 | 1230 | 1230 | 886 | +| getBlock | 1230 | 1230 | 1230 | 1230 | 883 | | getCollectiveProverRewardsForEpoch | 636 | 1636 | 1636 | 2636 | 4 | | getCurrentEpoch | 908 | 908 | 908 | 908 | 1032 | -| getCurrentEpochCommittee | 2962 | 2962 | 2962 | 2962 | 1 | -| getCurrentProposer | 3182 | 722366 | 9153 | 2285955 | 815 | +| getCurrentEpochCommittee | 42026 | 42026 | 42026 | 42026 | 1 | +| getCurrentProposer | 44246 | 117389 | 52217 | 263958 | 795 | | getCurrentSlot | 713 | 1332 | 713 | 4713 | 142 | | getEpochCommittee | 2010 | 14068 | 14218 | 14218 | 520 | | getEpochDuration | 439 | 439 | 439 | 439 | 256 | @@ -45,11 +45,11 @@ | getSpecificProverRewardsForEpoch | 822 | 2130 | 1634 | 3634 | 5 | | getTargetCommitteeSize | 462 | 462 | 462 | 462 | 768 | | getTimestampForSlot | 909 | 910 | 909 | 4909 | 2462 | -| propose | 129177 | 377904 | 379855 | 589151 | 2601 | +| propose | 129177 | 377901 | 379855 | 589127 | 2601 | | prune | 25642 | 36116 | 37377 | 41862 | 6 | | setProvingCostPerMana | 28713 | 28713 | 28713 | 28713 | 2 | -| setupEpoch | 208063 | 3476415 | 3553018 | 3553018 | 262 | -| submitEpochRootProof | 64866 | 426520 | 424623 | 456356 | 885 | +| setupEpoch | 208063 | 1372704 | 1400001 | 1400001 | 262 | +| submitEpochRootProof | 64866 | 426856 | 424623 | 456356 | 882 | | src/core/messagebridge/Inbox.sol:Inbox contract | | | | | | |-------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | @@ -62,12 +62,12 @@ | src/core/messagebridge/Outbox.sol:Outbox contract | | | | | | |---------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 586673 | 2646 | | | | | +| 586661 | 2646 | | | | | | Function Name | min | avg | median | max | # calls | -| consume | 28894 | 72013 | 73138 | 73400 | 4262 | +| consume | 28894 | 72056 | 73128 | 73400 | 4766 | | getRootData | 940 | 1363 | 1171 | 3217 | 2732 | | hasMessageBeenConsumedAtBlockAndIndex | 591 | 2583 | 2591 | 2591 | 259 | -| insert | 22188 | 57508 | 68264 | 68264 | 1097 | +| insert | 22188 | 57557 | 68264 | 68264 | 1102 | | src/core/staking/Slasher.sol:Slasher contract | | | | | | |-----------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -81,11 +81,11 @@ | 0 | 0 | | | | | | Function Name | min | avg | median | max | # calls | | executeProposal | 139987 | 139987 | 139987 | 139987 | 1 | -| vote | 63905 | 75272 | 63905 | 120740 | 10 | +| vote | 63907 | 75274 | 63907 | 120742 | 10 | | src/governance/CoinIssuer.sol:CoinIssuer contract | | | | | | |---------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | -| 326421 | 1465 | | | | | +| 326373 | 1465 | | | | | | Function Name | min | avg | median | max | # calls | | RATE | 239 | 239 | 239 | 239 | 768 | | mint | 23901 | 43841 | 26637 | 81105 | 768 | @@ -96,25 +96,25 @@ | Deployment Cost | Deployment Size | | | | | | 2332350 | 10841 | | | | | | Function Name | min | avg | median | max | # calls | -| deposit | 27898 | 171720 | 186529 | 188452 | 9729 | +| deposit | 27898 | 171751 | 186529 | 188452 | 9729 | | dropProposal | 23739 | 40533 | 33600 | 63600 | 2307 | | execute | 26209 | 71290 | 71327 | 161717 | 3076 | -| finaliseWithdraw | 23757 | 45218 | 48283 | 65383 | 6060 | +| finaliseWithdraw | 23757 | 45175 | 48283 | 65383 | 6072 | | getConfiguration | 1913 | 12163 | 19913 | 19913 | 5396 | | getProposal | 3523 | 8023 | 3523 | 31523 | 10590 | | getProposalState | 469 | 11470 | 13558 | 21242 | 23311 | -| getWithdrawal | 1075 | 1075 | 1075 | 1075 | 10134 | +| getWithdrawal | 1075 | 1075 | 1075 | 1075 | 10175 | | governanceProposer | 424 | 1418 | 424 | 2424 | 515 | -| initiateWithdraw | 30945 | 199224 | 211342 | 228958 | 7555 | -| powerAt | 1042 | 1412 | 1042 | 3029 | 4608 | +| initiateWithdraw | 30945 | 199329 | 211342 | 228958 | 7596 | +| powerAt | 1042 | 1412 | 1042 | 3712 | 4608 | | proposalCount | 338 | 1714 | 2338 | 2338 | 1116 | | propose | 23763 | 321926 | 320487 | 337587 | 606 | -| proposeWithLock | 26545 | 421005 | 422627 | 422627 | 257 | -| totalPowerAt | 612 | 1569 | 883 | 3568 | 6064 | +| proposeWithLock | 26545 | 421002 | 422627 | 422627 | 257 | +| totalPowerAt | 612 | 1564 | 883 | 3568 | 6107 | | updateConfiguration | 23457 | 32910 | 24180 | 48186 | 6145 | | updateGovernanceProposer | 21705 | 27184 | 28016 | 28028 | 2048 | -| vote | 30670 | 87817 | 94478 | 94500 | 12289 | -| withdrawalCount | 383 | 391 | 383 | 2383 | 2479 | +| vote | 30670 | 87818 | 94478 | 94500 | 12289 | +| withdrawalCount | 383 | 390 | 383 | 2383 | 2522 | | src/governance/Registry.sol:Registry contract | | | | | | |-----------------------------------------------|-----------------|--------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | @@ -122,28 +122,28 @@ | Function Name | min | avg | median | max | # calls | | getCurrentSnapshot | 664 | 2664 | 2664 | 4664 | 514 | | getGovernance | 341 | 2159 | 2341 | 2341 | 2829 | -| getRollup | 374 | 2358 | 2374 | 2374 | 874093 | +| getRollup | 374 | 2358 | 2374 | 2374 | 872089 | | getSnapshot | 4740 | 4740 | 4740 | 4740 | 257 | | getVersionFor | 743 | 3527 | 2927 | 4927 | 773 | | isRollupRegistered | 657 | 3805 | 2812 | 4812 | 515 | | numberOfVersions | 350 | 1685 | 2350 | 2350 | 770 | | transferOwnership | 28592 | 28592 | 28592 | 28592 | 106 | -| upgrade | 23672 | 103303 | 106801 | 106801 | 6160 | +| upgrade | 23672 | 103297 | 106801 | 106801 | 6148 | | src/governance/RewardDistributor.sol:RewardDistributor contract | | | | | | |-----------------------------------------------------------------|-----------------|-------|--------|-------|---------| | Deployment Cost | Deployment Size | | | | | | 513664 | 2360 | | | | | | Function Name | min | avg | median | max | # calls | -| BLOCK_REWARD | 238 | 238 | 238 | 238 | 380 | -| canonicalRollup | 1143 | 3143 | 3143 | 5643 | 880 | -| claim | 30122 | 45862 | 35599 | 64024 | 513 | +| BLOCK_REWARD | 238 | 238 | 238 | 238 | 374 | +| canonicalRollup | 1143 | 3143 | 3143 | 5643 | 877 | +| claim | 30122 | 45805 | 35599 | 64024 | 513 | | owner | 2384 | 2384 | 2384 | 2384 | 257 | | registry | 347 | 1347 | 1347 | 2347 | 2 | | updateRegistry | 23757 | 23781 | 23757 | 30119 | 257 | | src/governance/proposer/GovernanceProposer.sol:GovernanceProposer contract | | | | | | |----------------------------------------------------------------------------|-----------------|-------|--------|--------|---------| | Deployment Cost | Deployment Size | | | | | -| 639517 | 3151 | | | | | +| 639733 | 3152 | | | | | | Function Name | min | avg | median | max | # calls | | LIFETIME_IN_ROUNDS | 216 | 216 | 216 | 216 | 512 | | M | 261 | 261 | 261 | 261 | 4868 | @@ -154,7 +154,7 @@ | getExecutor | 3397 | 3397 | 3397 | 3397 | 256 | | getInstance | 951 | 951 | 951 | 951 | 256 | | rounds | 865 | 865 | 865 | 865 | 522 | -| vote | 29794 | 50137 | 50072 | 125973 | 859999 | +| vote | 29794 | 50139 | 50074 | 125975 | 858007 | | yeaCount | 851 | 851 | 851 | 851 | 16 | | src/periphery/Forwarder.sol:Forwarder contract | | | | | | |------------------------------------------------|-----------------|-------|--------|--------|---------| diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index ab8356358fa..cc9b8542118 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -82,6 +82,119 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { ) {} + /** + * @notice Validate a header for submission + * + * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header + * without having to deal with viem or anvil for simulating timestamps in the future. + * + * @param _header - The header to validate + * @param _signatures - The signatures to validate + * @param _digest - The digest to validate + * @param _currentTime - The current time + * @param _blobsHash - The blobs hash for this block + * @param _flags - The flags to validate + */ + function validateHeader( + bytes calldata _header, + Signature[] memory _signatures, + bytes32 _digest, + Timestamp _currentTime, + bytes32 _blobsHash, + BlockHeaderValidationFlags memory _flags + ) external override(IRollup) { + ProposeLib.validateHeader( + ValidateHeaderArgs({ + header: HeaderLib.decode(_header), + attestations: _signatures, + digest: _digest, + currentTime: _currentTime, + manaBaseFee: getManaBaseFeeAt(_currentTime, true), + blobsHashesCommitment: _blobsHash, + flags: _flags + }) + ); + } + + /** + * @notice Get the validator set for the current epoch + * @return The validator set for the current epoch + */ + function getCurrentEpochCommittee() + external + override(IValidatorSelection) + returns (address[] memory) + { + return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getCurrentEpoch()); + } + + /** + * @notice Get the committee for a given timestamp + * + * @param _ts - The timestamp to get the committee for + * + * @return The committee for the given timestamp + */ + function getCommitteeAt(Timestamp _ts) + external + override(IValidatorSelection) + returns (address[] memory) + { + return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getEpochAt(_ts)); + } + + /** + * @notice Get the proposer for the current slot + * + * @dev Calls `getCurrentProposer(uint256)` with the current timestamp + * + * @return The address of the proposer + */ + function getCurrentProposer() external override(IValidatorSelection) returns (address) { + return getProposerAt(Timestamp.wrap(block.timestamp)); + } + + /** + * @notice Check if msg.sender can propose at a given time + * + * @param _ts - The timestamp to check + * @param _archive - The archive to check (should be the latest archive) + * + * @return uint256 - The slot at the given timestamp + * @return uint256 - The block number at the given timestamp + */ + function canProposeAtTime(Timestamp _ts, bytes32 _archive) + external + override(IRollup) + returns (Slot, uint256) + { + Slot slot = _ts.slotFromTimestamp(); + RollupStore storage rollupStore = STFLib.getStorage(); + + uint256 pendingBlockNumber = STFLib.getEffectivePendingBlockNumber(_ts); + + Slot lastSlot = rollupStore.blocks[pendingBlockNumber].slotNumber; + + require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); + + // Make sure that the proposer is up to date and on the right chain (ie no reorgs) + bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; + require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); + + Signature[] memory sigs = new Signature[](0); + + ValidatorSelectionLib.verify( + StakingLib.getStorage(), + slot, + slot.epochFromSlot(), + sigs, + _archive, + BlockHeaderValidationFlags({ignoreDA: true, ignoreSignatures: true}) + ); + + return (slot, pendingBlockNumber + 1); + } + function getTargetCommitteeSize() external view override(IValidatorSelection) returns (uint256) { return ValidatorSelectionLib.getStorage().targetCommitteeSize; } @@ -180,40 +293,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { ); } - /** - * @notice Validate a header for submission - * - * @dev This is a convenience function that can be used by the sequencer to validate a "partial" header - * without having to deal with viem or anvil for simulating timestamps in the future. - * - * @param _header - The header to validate - * @param _signatures - The signatures to validate - * @param _digest - The digest to validate - * @param _currentTime - The current time - * @param _blobsHash - The blobs hash for this block - * @param _flags - The flags to validate - */ - function validateHeader( - bytes calldata _header, - Signature[] memory _signatures, - bytes32 _digest, - Timestamp _currentTime, - bytes32 _blobsHash, - BlockHeaderValidationFlags memory _flags - ) external view override(IRollup) { - ProposeLib.validateHeader( - ValidateHeaderArgs({ - header: HeaderLib.decode(_header), - attestations: _signatures, - digest: _digest, - currentTime: _currentTime, - manaBaseFee: getManaBaseFeeAt(_currentTime, true), - blobsHashesCommitment: _blobsHash, - flags: _flags - }) - ); - } - /** * @notice Validate blob transactions against given inputs. * @dev Only exists here for gas estimation. @@ -331,35 +410,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return ValidatorSelectionLib.getStorage().epochs[_epoch].committee; } - /** - * @notice Get the validator set for the current epoch - * @return The validator set for the current epoch - */ - function getCurrentEpochCommittee() - external - view - override(IValidatorSelection) - returns (address[] memory) - { - return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getCurrentEpoch()); - } - - /** - * @notice Get the committee for a given timestamp - * - * @param _ts - The timestamp to get the committee for - * - * @return The committee for the given timestamp - */ - function getCommitteeAt(Timestamp _ts) - external - view - override(IValidatorSelection) - returns (address[] memory) - { - return ValidatorSelectionLib.getCommitteeAt(StakingLib.getStorage(), getEpochAt(_ts)); - } - /** * @notice Get the sample seed for a given timestamp * @@ -421,17 +471,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return _slotNumber.toTimestamp(); } - /** - * @notice Get the proposer for the current slot - * - * @dev Calls `getCurrentProposer(uint256)` with the current timestamp - * - * @return The address of the proposer - */ - function getCurrentProposer() external view override(IValidatorSelection) returns (address) { - return getProposerAt(Timestamp.wrap(block.timestamp)); - } - /** * @notice Computes the slot at a specific time * @@ -558,48 +597,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return STFLib.getStorage().config.rewardDistributor; } - /** - * @notice Check if msg.sender can propose at a given time - * - * @param _ts - The timestamp to check - * @param _archive - The archive to check (should be the latest archive) - * - * @return uint256 - The slot at the given timestamp - * @return uint256 - The block number at the given timestamp - */ - function canProposeAtTime(Timestamp _ts, bytes32 _archive) - external - view - override(IRollup) - returns (Slot, uint256) - { - Slot slot = _ts.slotFromTimestamp(); - RollupStore storage rollupStore = STFLib.getStorage(); - - uint256 pendingBlockNumber = STFLib.getEffectivePendingBlockNumber(_ts); - - Slot lastSlot = rollupStore.blocks[pendingBlockNumber].slotNumber; - - require(slot > lastSlot, Errors.Rollup__SlotAlreadyInChain(lastSlot, slot)); - - // Make sure that the proposer is up to date and on the right chain (ie no reorgs) - bytes32 tipArchive = rollupStore.blocks[pendingBlockNumber].archive; - require(tipArchive == _archive, Errors.Rollup__InvalidArchive(tipArchive, _archive)); - - Signature[] memory sigs = new Signature[](0); - - ValidatorSelectionLib.verify( - StakingLib.getStorage(), - slot, - slot.epochFromSlot(), - sigs, - _archive, - BlockHeaderValidationFlags({ignoreDA: true, ignoreSignatures: true}) - ); - - return (slot, pendingBlockNumber + 1); - } - function getL1FeesAt(Timestamp _timestamp) external view @@ -617,6 +614,33 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return EpochProofLib.BURN_ADDRESS; } + /** + * @notice Get the proposer for the slot at a specific timestamp + * + * @dev This function is very useful for off-chain usage, as it easily allow a client to + * determine who will be the proposer at the NEXT ethereum block. + * Should not be trusted when moving beyond the current epoch, since changes to the + * validator set might not be reflected when we actually reach that epoch (more changes + * might have happened). + * + * @dev The proposer is selected from the validator set of the current epoch. + * + * @dev Should only be access on-chain if epoch is setup, otherwise very expensive. + * + * @dev A return value of address(0) means that the proposer is "open" and can be anyone. + * + * @dev If the current epoch is the first epoch, returns address(0) + * If the current epoch is setup, we will return the proposer for the current slot + * If the current epoch is not setup, we will perform a sample as if it was (gas heavy) + * + * @return The address of the proposer + */ + function getProposerAt(Timestamp _ts) public override(IValidatorSelection) returns (address) { + Slot slot = _ts.slotFromTimestamp(); + Epoch epochNumber = slot.epochFromSlot(); + return ValidatorSelectionLib.getProposerAt(StakingLib.getStorage(), slot, epochNumber); + } + /** * @notice Gets the mana base fee * @@ -688,31 +712,4 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { function getCurrentEpoch() public view override(IValidatorSelection) returns (Epoch) { return Timestamp.wrap(block.timestamp).epochFromTimestamp(); } - - /** - * @notice Get the proposer for the slot at a specific timestamp - * - * @dev This function is very useful for off-chain usage, as it easily allow a client to - * determine who will be the proposer at the NEXT ethereum block. - * Should not be trusted when moving beyond the current epoch, since changes to the - * validator set might not be reflected when we actually reach that epoch (more changes - * might have happened). - * - * @dev The proposer is selected from the validator set of the current epoch. - * - * @dev Should only be access on-chain if epoch is setup, otherwise very expensive. - * - * @dev A return value of address(0) means that the proposer is "open" and can be anyone. - * - * @dev If the current epoch is the first epoch, returns address(0) - * If the current epoch is setup, we will return the proposer for the current slot - * If the current epoch is not setup, we will perform a sample as if it was (gas heavy) - * - * @return The address of the proposer - */ - function getProposerAt(Timestamp _ts) public view override(IValidatorSelection) returns (address) { - Slot slot = _ts.slotFromTimestamp(); - Epoch epochNumber = slot.epochFromSlot(); - return ValidatorSelectionLib.getProposerAt(StakingLib.getStorage(), slot, epochNumber); - } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 423991fbc4a..2b6f5209608 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -158,6 +158,17 @@ interface IRollupCore { } interface IRollup is IRollupCore { + function validateHeader( + bytes calldata _header, + Signature[] memory _signatures, + bytes32 _digest, + Timestamp _currentTime, + bytes32 _blobsHash, + BlockHeaderValidationFlags memory _flags + ) external; + + function canProposeAtTime(Timestamp _ts, bytes32 _archive) external returns (Slot, uint256); + function getTips() external view returns (ChainTips memory); function status(uint256 _myHeaderBlockNumber) @@ -181,17 +192,6 @@ interface IRollup is IRollupCore { bytes calldata _aggregationObject ) external view returns (bytes32[] memory); - function validateHeader( - bytes calldata _header, - Signature[] memory _signatures, - bytes32 _digest, - Timestamp _currentTime, - bytes32 _blobsHash, - BlockHeaderValidationFlags memory _flags - ) external view; - - function canProposeAtTime(Timestamp _ts, bytes32 _archive) external view returns (Slot, uint256); - function validateBlobs(bytes calldata _blobsInputs) external view diff --git a/l1-contracts/src/core/interfaces/IValidatorSelection.sol b/l1-contracts/src/core/interfaces/IValidatorSelection.sol index 387abe16b66..1ad6e55ff91 100644 --- a/l1-contracts/src/core/interfaces/IValidatorSelection.sol +++ b/l1-contracts/src/core/interfaces/IValidatorSelection.sol @@ -30,8 +30,12 @@ interface IValidatorSelectionCore { interface IValidatorSelection is IValidatorSelectionCore { // Likely changing to optimize in Pleistarchus - function getCurrentProposer() external view returns (address); - function getProposerAt(Timestamp _ts) external view returns (address); + function getCurrentProposer() external returns (address); + function getProposerAt(Timestamp _ts) external returns (address); + + // Non view as uses transient storage + function getCurrentEpochCommittee() external returns (address[] memory); + function getCommitteeAt(Timestamp _ts) external returns (address[] memory); // Stable function getCurrentEpoch() external view returns (Epoch); @@ -42,8 +46,6 @@ interface IValidatorSelection is IValidatorSelectionCore { // Likely removal of these to replace with a size and indiviual getter // Get the current epoch committee - function getCurrentEpochCommittee() external view returns (address[] memory); - function getCommitteeAt(Timestamp _ts) external view returns (address[] memory); function getEpochCommittee(Epoch _epoch) external view returns (address[] memory); function getAttesters() external view returns (address[] memory); diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index dcede45e370..e4f693ba2d4 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -96,6 +96,7 @@ library Errors { // SampleLib error SampleLib__IndexOutOfBounds(uint256 requested, uint256 bound); // 0xa12fc559 + error SampleLib__SampleLargerThanIndex(uint256 sample, uint256 index); // 0xa11b0f79 // Sequencer Selection (ValidatorSelection) error ValidatorSelection__EpochNotSetup(); // 0x10816cae diff --git a/l1-contracts/src/core/libraries/crypto/SampleLib.sol b/l1-contracts/src/core/libraries/crypto/SampleLib.sol index 721aa7bd1be..ce7d11f3e3a 100644 --- a/l1-contracts/src/core/libraries/crypto/SampleLib.sol +++ b/l1-contracts/src/core/libraries/crypto/SampleLib.sol @@ -3,232 +3,98 @@ pragma solidity >=0.8.27; import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {SlotDerivation} from "@oz/utils/SlotDerivation.sol"; +import {TransientSlot} from "@oz/utils/TransientSlot.sol"; /** * @title SampleLib * @author Anaxandridas II - * @notice A tiny library to shuffle indices using the swap-or-not algorithm and then - * draw a committee from the shuffled indices. - * - * @dev Using the `swap-or-not` alogirthm that is used by Ethereum consensus client. - * We are using this algorithm, since it can compute a shuffle of individual indices, - * which will be very useful for EVENTUALLY reducing the cost of committee selection. - * - * Currently the library is maximally simple, and will simply do "dumb" sampling to select - * a committee, but re-use parts of computation to improve efficiency. - * - * https://eth2book.info/capella/part2/building_blocks/shuffling/ - * https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf + * @notice A tiny library to draw committee indices using a sample without replacement algorithm. */ library SampleLib { - /** - * @notice Computing a committee the most direct way. - * This is horribly inefficient as we are throwing plenty of things away, but it is useful - * for testing and just showcasing the simplest case. - * - * @param _committeeSize - The size of the committee - * @param _indexCount - The total number of indices - * @param _seed - The seed to use for shuffling - * - * @return indices - The indices of the committee - */ - function computeCommitteeStupid(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) - internal - pure - returns (uint256[] memory) - { - uint256[] memory indices = new uint256[](_committeeSize); - - for (uint256 index = 0; index < _indexCount; index++) { - uint256 sampledIndex = computeShuffledIndex(index, _indexCount, _seed); - if (sampledIndex < _committeeSize) { - indices[sampledIndex] = index; - } - } + using SlotDerivation for string; + using SlotDerivation for bytes32; + using TransientSlot for *; - return indices; - } + // Namespace for transient storage keys used within this library + string private constant OVERRIDE_NAMESPACE = "Aztec.SampleLib.Override"; /** - * @notice Computing a committee slightly more cleverly. - * Only computes for the committee size, and does not sample the full set. - * This is more efficient than the stupid way, but still not optimal. - * To be more clever, we can compute the `shuffeRounds` and `pivots` separately - * such that they get shared accross multiple indices. + * Compute Committee * * @param _committeeSize - The size of the committee * @param _indexCount - The total number of indices * @param _seed - The seed to use for shuffling * + * @dev assumption, _committeeSize <= _indexCount + * * @return indices - The indices of the committee */ - function computeCommitteeClever(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) + function computeCommittee(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) internal - pure returns (uint256[] memory) { - uint256[] memory indices = new uint256[](_committeeSize); - - for (uint256 index = 0; index < _committeeSize; index++) { - uint256 originalIndex = computeOriginalIndex(index, _indexCount, _seed); - indices[index] = originalIndex; - } - - return indices; - } + require( + _committeeSize <= _indexCount, + Errors.SampleLib__SampleLargerThanIndex(_committeeSize, _indexCount) + ); - /** - * @notice Computes the shuffled index - * - * @param _index - The index to shuffle - * @param _indexCount - The total number of indices - * @param _seed - The seed to use for shuffling - */ - function computeShuffledIndex(uint256 _index, uint256 _indexCount, uint256 _seed) - internal - pure - returns (uint256) - { - require(_index < _indexCount, Errors.SampleLib__IndexOutOfBounds(_index, _indexCount)); - uint256 rounds = computeShuffleRounds(_indexCount); + uint256[] memory sampledIndices = new uint256[](_committeeSize); - uint256 index = _index; + uint256 upperLimit = _indexCount - 1; - for (uint256 currentRound = 0; currentRound < rounds; currentRound++) { - uint256 pivot = computePivot(_seed, currentRound, _indexCount); - index = computeInner(_seed, pivot, index, currentRound, _indexCount); + for (uint256 index = 0; index < _committeeSize; index++) { + uint256 sampledIndex = computeSampleIndex(index, upperLimit, _seed); + + // Get index, or its swapped override + sampledIndices[index] = getValue(sampledIndex); + if (upperLimit > 0) { + // Swap with the last index + setOverrideValue(sampledIndex, getValue(upperLimit)); + // Decrement the upper limit + upperLimit--; + } } - return index; + return sampledIndices; } - /** - * @notice Computes the original index from a shuffled index - * - * @dev Notice, that we are using same logic as `computeShuffledIndex` just looping in reverse. - * - * @param _shuffledIndex - The shuffled index - * @param _indexCount - The total number of indices - * @param _seed - The seed to use for shuffling - * - * @return index - The original index - */ - function computeOriginalIndex(uint256 _shuffledIndex, uint256 _indexCount, uint256 _seed) - internal - pure - returns (uint256) - { - require( - _shuffledIndex < _indexCount, Errors.SampleLib__IndexOutOfBounds(_shuffledIndex, _indexCount) - ); - - uint256 rounds = computeShuffleRounds(_indexCount); - - uint256 index = _shuffledIndex; + function setOverrideValue(uint256 _index, uint256 _value) internal { + OVERRIDE_NAMESPACE.erc7201Slot().deriveMapping(_index).asUint256().tstore(_value); + } - for (uint256 currentRound = rounds; currentRound > 0; currentRound--) { - uint256 pivot = computePivot(_seed, currentRound - 1, _indexCount); - index = computeInner(_seed, pivot, index, currentRound - 1, _indexCount); + function getValue(uint256 _index) internal view returns (uint256) { + uint256 overrideValue = getOverrideValue(_index); + if (overrideValue != 0) { + return overrideValue; } - return index; + return _index; } - /** - * @notice Compute the number of shuffle rounds - * - * @dev A safe number of rounds should be 4 * log_2 N where N is the number of indices - * - * @param _count - The number of indices - * - * @return rounds - The number of rounds to shuffle - */ - function computeShuffleRounds(uint256 _count) private pure returns (uint256) { - return log2(_count) * 4; + function getOverrideValue(uint256 _index) internal view returns (uint256) { + return OVERRIDE_NAMESPACE.erc7201Slot().deriveMapping(_index).asUint256().tload(); } /** - * @notice Computes the pivot for a given round + * @notice Compute the sample index for a given index, seed and index count. * - * @param _seed - The seed to use for shuffling - * @param _currentRound - The current round of shuffling + * @param _index - The index to shuffle * @param _indexCount - The total number of indices + * @param _seed - The seed to use for shuffling * - * @return pivot - The pivot for the round + * @return shuffledIndex - The shuffled index */ - function computePivot(uint256 _seed, uint256 _currentRound, uint256 _indexCount) - private + function computeSampleIndex(uint256 _index, uint256 _indexCount, uint256 _seed) + internal pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(_seed, uint8(_currentRound)))) % _indexCount; - } - - /** - * @notice Computes the inner loop (one round) of a shuffle - * - * @param _seed - The seed to use for shuffling - * @param _pivot - The pivot to use for shuffling - * @param _index - The index to shuffle - * @param _currentRound - The current round of shuffling - * @param _indexCount - The total number of indices - * - * @return index - The shuffled index - */ - function computeInner( - uint256 _seed, - uint256 _pivot, - uint256 _index, - uint256 _currentRound, - uint256 _indexCount - ) private pure returns (uint256) { - uint256 flip = (_pivot + _indexCount - _index) % _indexCount; - uint256 position = _index > flip ? _index : flip; - bytes32 source = - keccak256(abi.encodePacked(_seed, uint8(_currentRound), uint32(position / 256))); - uint8 byte_ = uint8(source[(position % 256) / 8]); - uint8 bit_ = (byte_ >> (position % 8)) % 2; - - return bit_ == 1 ? flip : _index; - } - - /** - * @notice Computes the log2 of a uint256 number - * - * @param _x - The number to compute the log2 of - * - * @return y - The log2 of the number - */ - function log2(uint256 _x) private pure returns (uint256 y) { - // https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn - assembly { - let arg := _x - _x := sub(_x, 1) - _x := or(_x, div(_x, 0x02)) - _x := or(_x, div(_x, 0x04)) - _x := or(_x, div(_x, 0x10)) - _x := or(_x, div(_x, 0x100)) - _x := or(_x, div(_x, 0x10000)) - _x := or(_x, div(_x, 0x100000000)) - _x := or(_x, div(_x, 0x10000000000000000)) - _x := or(_x, div(_x, 0x100000000000000000000000000000000)) - _x := add(_x, 1) - let m := mload(0x40) - mstore(m, 0xf8f9cbfae6cc78fbefe7cdc3a1793dfcf4f0e8bbd8cec470b6a28a7a5a3e1efd) - mstore(add(m, 0x20), 0xf5ecf1b3e9debc68e1d9cfabc5997135bfb7a7a3938b7b606b5b4b3f2f1f0ffe) - mstore(add(m, 0x40), 0xf6e4ed9ff2d6b458eadcdf97bd91692de2d4da8fd2d0ac50c6ae9a8272523616) - mstore(add(m, 0x60), 0xc8c0b887b0a8a4489c948c7f847c6125746c645c544c444038302820181008ff) - mstore(add(m, 0x80), 0xf7cae577eec2a03cf3bad76fb589591debb2dd67e0aa9834bea6925f6a4a2e0e) - mstore(add(m, 0xa0), 0xe39ed557db96902cd38ed14fad815115c786af479b7e83247363534337271707) - mstore(add(m, 0xc0), 0xc976c13bb96e881cb166a933a55e490d9d56952b8d4e801485467d2362422606) - mstore(add(m, 0xe0), 0x753a6d1b65325d0c552a4d1345224105391a310b29122104190a110309020100) - mstore(0x40, add(m, 0x100)) - let magic := 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff - let shift := 0x100000000000000000000000000000000000000000000000000000000000000 - let a := div(mul(_x, magic), shift) - y := div(mload(add(m, sub(255, a))), shift) - y := - add(y, mul(256, gt(arg, 0x8000000000000000000000000000000000000000000000000000000000000000))) + // Cannot modulo by 0 + if (_indexCount == 0) { + return 0; } + + return uint256(keccak256(abi.encodePacked(_seed, _index))) % _indexCount; } } diff --git a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol index 6f351fc353f..00db58ceccd 100644 --- a/l1-contracts/src/core/libraries/rollup/ProposeLib.sol +++ b/l1-contracts/src/core/libraries/rollup/ProposeLib.sol @@ -140,42 +140,8 @@ library ProposeLib { emit IRollupCore.L2BlockProposed(blockNumber, _args.archive, v.blobHashes); } - /** - * @notice Gets the mana base fee components - * For more context, consult: - * https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md - * - * @param _timestamp - The timestamp of the block - * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH - * - * @return The mana base fee components - */ - function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset) - internal - view - returns (ManaBaseFeeComponents memory) - { - RollupStore storage rollupStore = STFLib.getStorage(); - - // If we are not the canonical rollup, we cannot claim any fees, so we return 0s - // The congestion multiplier could be computed, but as it could only be used to guide - // might as well save the gas and anyone interested can do it off-chain. - if (address(this) != rollupStore.config.feeAssetPortal.canonicalRollup()) { - return ManaBaseFeeComponents({ - congestionCost: 0, - provingCost: 0, - congestionMultiplier: 0, - dataCost: 0, - gasCost: 0 - }); - } - - uint256 blockOfInterest = STFLib.getEffectivePendingBlockNumber(_timestamp); - - return FeeLib.getManaBaseFeeComponentsAt(blockOfInterest, _timestamp, _inFeeAsset); - } - - function validateHeader(ValidateHeaderArgs memory _args) internal view { + // @note: not view as sampling validators uses tstore + function validateHeader(ValidateHeaderArgs memory _args) internal { require( block.chainid == _args.header.globalVariables.chainId, Errors.Rollup__InvalidChainId(block.chainid, _args.header.globalVariables.chainId) @@ -248,6 +214,41 @@ library ProposeLib { ); } + /** + * @notice Gets the mana base fee components + * For more context, consult: + * https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8757-fees/design.md + * + * @param _timestamp - The timestamp of the block + * @param _inFeeAsset - Whether to return the fee in the fee asset or ETH + * + * @return The mana base fee components + */ + function getManaBaseFeeComponentsAt(Timestamp _timestamp, bool _inFeeAsset) + internal + view + returns (ManaBaseFeeComponents memory) + { + RollupStore storage rollupStore = STFLib.getStorage(); + + // If we are not the canonical rollup, we cannot claim any fees, so we return 0s + // The congestion multiplier could be computed, but as it could only be used to guide + // might as well save the gas and anyone interested can do it off-chain. + if (address(this) != rollupStore.config.feeAssetPortal.canonicalRollup()) { + return ManaBaseFeeComponents({ + congestionCost: 0, + provingCost: 0, + congestionMultiplier: 0, + dataCost: 0, + gasCost: 0 + }); + } + + uint256 blockOfInterest = STFLib.getEffectivePendingBlockNumber(_timestamp); + + return FeeLib.getManaBaseFeeComponentsAt(blockOfInterest, _timestamp, _inFeeAsset); + } + function digest(ProposeArgs memory _args) internal pure returns (bytes32) { return keccak256(abi.encode(SignatureLib.SignatureDomainSeparator.blockAttestation, _args)); } diff --git a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol index 0a8335a0899..0d1398e431b 100644 --- a/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol +++ b/l1-contracts/src/core/libraries/validator-selection/ValidatorSelectionLib.sol @@ -73,7 +73,7 @@ library ValidatorSelectionLib { Signature[] memory _signatures, bytes32 _digest, BlockHeaderValidationFlags memory _flags - ) internal view { + ) internal { // Same logic as we got in getProposerAt // Done do avoid duplicate computing the committee address[] memory committee = getCommitteeAt(_stakingStore, _epochNumber); @@ -128,7 +128,6 @@ library ValidatorSelectionLib { function getProposerAt(StakingStorage storage _stakingStore, Slot _slot, Epoch _epochNumber) internal - view returns (address) { // @note this is deliberately "bad" for the simple reason of code reduction. @@ -157,7 +156,6 @@ library ValidatorSelectionLib { */ function sampleValidators(StakingStorage storage _stakingStore, uint256 _seed) internal - view returns (address[] memory) { uint256 validatorSetSize = _stakingStore.attesters.length(); @@ -174,7 +172,7 @@ library ValidatorSelectionLib { } uint256[] memory indices = - SampleLib.computeCommitteeClever(targetCommitteeSize, validatorSetSize, _seed); + SampleLib.computeCommittee(targetCommitteeSize, validatorSetSize, _seed); address[] memory committee = new address[](targetCommitteeSize); for (uint256 i = 0; i < targetCommitteeSize; i++) { @@ -185,7 +183,6 @@ library ValidatorSelectionLib { function getCommitteeAt(StakingStorage storage _stakingStore, Epoch _epochNumber) internal - view returns (address[] memory) { ValidatorSelectionStorage storage store = getStorage(); diff --git a/l1-contracts/test/validator-selection/Sampling.t.sol b/l1-contracts/test/validator-selection/Sampling.t.sol index f537c96b399..dccdee4cf33 100644 --- a/l1-contracts/test/validator-selection/Sampling.t.sol +++ b/l1-contracts/test/validator-selection/Sampling.t.sol @@ -8,71 +8,29 @@ import {SampleLib} from "@aztec/core/libraries/crypto/SampleLib.sol"; // Adding a contract to get some gas-numbers out. contract Sampler { - function computeShuffledIndex(uint256 _index, uint256 _indexCount, uint256 _seed) + function computeCommittee(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) public - pure - returns (uint256) - { - return SampleLib.computeShuffledIndex(_index, _indexCount, _seed); - } - - function computeOriginalIndex(uint256 _index, uint256 _indexCount, uint256 _seed) - public - pure - returns (uint256) - { - return SampleLib.computeOriginalIndex(_index, _indexCount, _seed); - } - - function computeCommitteeStupid(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) - public - pure - returns (uint256[] memory) - { - return SampleLib.computeCommitteeStupid(_committeeSize, _indexCount, _seed); - } - - function computeCommitteeClever(uint256 _committeeSize, uint256 _indexCount, uint256 _seed) - public - pure returns (uint256[] memory) { - return SampleLib.computeCommitteeClever(_committeeSize, _indexCount, _seed); + return SampleLib.computeCommittee(_committeeSize, _indexCount, _seed); } } contract SamplingTest is Test { Sampler sampler = new Sampler(); - function testShuffle() public view { - // Sizes pulled out of thin air - uint256 setSize = 1024; - uint256 commiteeSize = 32; - - uint256[] memory indices = new uint256[](setSize); - for (uint256 i = 0; i < setSize; i++) { - indices[i] = i; - } - - uint256[] memory shuffledIndices = new uint256[](setSize); - uint256 seed = uint256(keccak256(abi.encodePacked("seed1"))); - - for (uint256 i = 0; i < setSize; i++) { - shuffledIndices[i] = sampler.computeShuffledIndex(indices[i], setSize, seed); - uint256 recoveredIndex = sampler.computeOriginalIndex(shuffledIndices[i], setSize, seed); - assertEq(recoveredIndex, indices[i], "Invalid index"); - } - - uint256[] memory committee = sampler.computeCommitteeStupid(commiteeSize, setSize, seed); - uint256[] memory committeeClever = sampler.computeCommitteeClever(commiteeSize, setSize, seed); - - for (uint256 i = 0; i < commiteeSize; i++) { - assertEq(committee[i], committeeClever[i], "Invalid index"); + function testSampleFuzz(uint8 _committeeSize, uint8 _validatorSetSize, uint256 _seed) public { + vm.assume(_committeeSize <= _validatorSetSize); + vm.assume(_committeeSize > 0); + vm.assume(_seed != 0); // Seed is computed from a hash, which we can safetly assume is non zero - uint256 recoveredIndex = sampler.computeOriginalIndex(committee[i], setSize, seed); - uint256 shuffledIndex = sampler.computeShuffledIndex(recoveredIndex, setSize, seed); + uint256[] memory committee = sampler.computeCommittee(_committeeSize, _validatorSetSize, _seed); - assertEq(committee[i], shuffledIndex, "Invalid shuffled index"); + // Check that none of the indices are the same + for (uint256 i = 0; i < _committeeSize; i++) { + for (uint256 j = i + 1; j < _committeeSize; j++) { + assertNotEq(committee[i], committee[j], "Indices are the same"); + } } } } diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index e4fc6732eab..a08cda99ca7 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -166,8 +166,15 @@ export class RollupContract { return this.rollup.read.getCurrentSlot(); } - getCommitteeAt(timestamp: bigint) { - return this.rollup.read.getCommitteeAt([timestamp]); + async getCommitteeAt(timestamp: bigint) { + const { result } = await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'getCommitteeAt', + args: [timestamp], + }); + + return result; } getSampleSeedAt(timestamp: bigint) { @@ -178,16 +185,37 @@ export class RollupContract { return this.rollup.read.getCurrentSampleSeed(); } - getCurrentEpochCommittee() { - return this.rollup.read.getCurrentEpochCommittee(); + async getCurrentEpochCommittee() { + const { result } = await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'getCurrentEpochCommittee', + args: [], + }); + + return result; } - getCurrentProposer() { - return this.rollup.read.getCurrentProposer(); + async getCurrentProposer() { + const { result } = await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'getCurrentProposer', + args: [], + }); + + return result; } - getProposerAt(timestamp: bigint) { - return this.rollup.read.getProposerAt([timestamp]); + async getProposerAt(timestamp: bigint) { + const { result } = await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'getProposerAt', + args: [timestamp], + }); + + return result; } getBlock(blockNumber: bigint) { @@ -262,7 +290,13 @@ export class RollupContract { account: `0x${string}` | Account, ): Promise { try { - await this.rollup.read.validateHeader(args, { account }); + await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'validateHeader', + args, + account, + }); } catch (error: unknown) { throw formatViemError(error); } @@ -287,10 +321,16 @@ export class RollupContract { } const timeOfNextL1Slot = (await this.client.getBlock()).timestamp + slotDuration; try { - const [slot, blockNumber] = await this.rollup.read.canProposeAtTime( - [timeOfNextL1Slot, `0x${archive.toString('hex')}`], - { account }, - ); + const { + result: [slot, blockNumber], + } = await this.client.simulateContract({ + address: this.address, + abi: RollupAbi, + functionName: 'canProposeAtTime', + args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`], + account, + }); + return [slot, blockNumber]; } catch (err: unknown) { throw formatViemError(err);