Skip to content

Commit 940acfd

Browse files
committed
Do not sample entire committee when checking proposer
1 parent 71e376a commit 940acfd

File tree

3 files changed

+78
-31
lines changed

3 files changed

+78
-31
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,14 @@ library StakingLib {
353353
);
354354
}
355355

356+
function getAttesterFromIndexAtTime(uint256 _index, Timestamp _timestamp)
357+
internal
358+
view
359+
returns (address)
360+
{
361+
return getStorage().gse.getAttesterFromIndexAtTime(address(this), _index, _timestamp);
362+
}
363+
356364
function getAttestersFromIndicesAtTime(Timestamp _timestamp, uint256[] memory _indices)
357365
internal
358366
view

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

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ library ValidatorSelectionLib {
219219
setCachedProposer(_slot, proposer, stack.proposerIndex);
220220
}
221221

222-
// Q: Do we still need to cache the proposer?
223222
function setCachedProposer(Slot _slot, address _proposer, uint256 _proposerIndex) internal {
224223
require(
225224
_proposerIndex <= type(uint96).max,
@@ -230,25 +229,24 @@ library ValidatorSelectionLib {
230229
}
231230

232231
function getProposerAt(Slot _slot) internal returns (address, uint256) {
233-
(address cachedProposer, uint256 proposerIndex) = getCachedProposer(_slot);
232+
(address cachedProposer, uint256 cachedProposerIndex) = getCachedProposer(_slot);
234233
if (cachedProposer != address(0)) {
235-
return (cachedProposer, proposerIndex);
234+
return (cachedProposer, cachedProposerIndex);
236235
}
237236

238-
// @note this is deliberately "bad" for the simple reason of code reduction.
239-
// it does not need to actually return the full committee and then draw from it
240-
// it can just return the proposer directly, but then we duplicate the code
241-
// which we just don't have room for right now...
242237
Epoch epochNumber = _slot.epochFromSlot();
243238

244239
uint224 sampleSeed = getSampleSeed(epochNumber);
245-
address[] memory committee = sampleValidators(epochNumber, sampleSeed);
246-
if (committee.length == 0) {
240+
(uint32 ts, uint256[] memory indices) = sampleValidatorsIndices(epochNumber, sampleSeed);
241+
uint256 committeeSize = indices.length;
242+
if (committeeSize == 0) {
247243
return (address(0), 0);
248244
}
249-
250-
uint256 index = computeProposerIndex(epochNumber, _slot, sampleSeed, committee.length);
251-
return (committee[index], index);
245+
uint256 proposerIndex = computeProposerIndex(epochNumber, _slot, sampleSeed, committeeSize);
246+
return (
247+
StakingLib.getAttesterFromIndexAtTime(indices[proposerIndex], Timestamp.wrap(ts)),
248+
proposerIndex
249+
);
252250
}
253251

254252
/**
@@ -260,24 +258,7 @@ library ValidatorSelectionLib {
260258
* @return The validators for the given epoch
261259
*/
262260
function sampleValidators(Epoch _epoch, uint224 _seed) internal returns (address[] memory) {
263-
ValidatorSelectionStorage storage store = getStorage();
264-
uint32 ts = epochToSampleTime(_epoch);
265-
uint256 validatorSetSize = StakingLib.getAttesterCountAtTime(Timestamp.wrap(ts));
266-
uint256 targetCommitteeSize = store.targetCommitteeSize;
267-
268-
require(
269-
validatorSetSize >= targetCommitteeSize,
270-
Errors.ValidatorSelection__InsufficientCommitteeSize(validatorSetSize, targetCommitteeSize)
271-
);
272-
273-
if (targetCommitteeSize == 0) {
274-
return new address[](0);
275-
}
276-
277-
// Sample the larger committee
278-
uint256[] memory indices =
279-
SampleLib.computeCommittee(targetCommitteeSize, validatorSetSize, _seed);
280-
261+
(uint32 ts, uint256[] memory indices) = sampleValidatorsIndices(_epoch, _seed);
281262
return StakingLib.getAttestersFromIndicesAtTime(Timestamp.wrap(ts), indices);
282263
}
283264

@@ -420,6 +401,35 @@ library ValidatorSelectionLib {
420401
}
421402
}
422403

404+
/**
405+
* @notice Samples a validator set for a specific epoch and returns their indices within the set.
406+
*
407+
* @dev Only used internally, should never be called for anything but the "next" epoch
408+
* Allowing us to always use `lastSeed`.
409+
*
410+
* @return The sample time and the indices of the validators for the given epoch
411+
*/
412+
function sampleValidatorsIndices(Epoch _epoch, uint224 _seed)
413+
internal
414+
returns (uint32, uint256[] memory)
415+
{
416+
ValidatorSelectionStorage storage store = getStorage();
417+
uint32 ts = epochToSampleTime(_epoch);
418+
uint256 validatorSetSize = StakingLib.getAttesterCountAtTime(Timestamp.wrap(ts));
419+
uint256 targetCommitteeSize = store.targetCommitteeSize;
420+
421+
require(
422+
validatorSetSize >= targetCommitteeSize,
423+
Errors.ValidatorSelection__InsufficientCommitteeSize(validatorSetSize, targetCommitteeSize)
424+
);
425+
426+
if (targetCommitteeSize == 0) {
427+
return (ts, new uint256[](0));
428+
}
429+
430+
return (ts, SampleLib.computeCommittee(targetCommitteeSize, validatorSetSize, _seed));
431+
}
432+
423433
/**
424434
* @notice Computes the nextSeed for an epoch
425435
*
@@ -457,7 +467,7 @@ library ValidatorSelectionLib {
457467
* @return The index of the proposer
458468
*/
459469
function computeProposerIndex(Epoch _epoch, Slot _slot, uint256 _seed, uint256 _size)
460-
private
470+
internal
461471
pure
462472
returns (uint256)
463473
{

l1-contracts/test/RollupGetters.t.sol

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQ
1414
import {ValidatorSelectionTestBase} from "./validator-selection/ValidatorSelectionBase.sol";
1515
import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol";
1616
import {IBoosterCore} from "@aztec/core/reward-boost/RewardBooster.sol";
17+
import {ValidatorSelectionLib} from "@aztec/core/libraries/rollup/ValidatorSelectionLib.sol";
1718

1819
/**
1920
* Testing the things that should be getters are not updating state!
@@ -103,6 +104,34 @@ contract RollupShouldBeGetters is ValidatorSelectionTestBase {
103104
assertEq(writes.length, 0, "No writes should be done");
104105
}
105106

107+
// Checks that getProposerAt yields the same result as sampling the entire committee
108+
// and then fetching the proposer from it given the proposer index.
109+
function test_getProposerFromCommittee(uint16 _slot, bool _setup) external setup(4, 4) {
110+
timeCheater.cheat__jumpForwardEpochs(2);
111+
Slot s = Slot.wrap(timeCheater.currentSlot()) + Slot.wrap(_slot);
112+
Timestamp t = timeCheater.slotToTimestamp(s);
113+
114+
vm.warp(Timestamp.unwrap(t));
115+
116+
if (_setup) {
117+
rollup.setupEpoch();
118+
}
119+
120+
vm.record();
121+
122+
address proposer = rollup.getProposerAt(t);
123+
124+
address[] memory committee = rollup.getCommitteeAt(t);
125+
uint256 seed = rollup.getSampleSeedAt(t);
126+
Epoch epoch = rollup.getEpochAt(t);
127+
uint256 proposerIndex = ValidatorSelectionLib.computeProposerIndex(epoch, s, seed, 4);
128+
129+
assertEq(proposer, committee[proposerIndex], "proposer should be the same");
130+
131+
(, bytes32[] memory writes) = vm.accesses(address(rollup));
132+
assertEq(writes.length, 0, "No writes should be done");
133+
}
134+
106135
function test_validateHeader() external setup(4, 4) {
107136
// Todo this one is a bit annoying here really. We need a lot of header information.
108137
}

0 commit comments

Comments
 (0)