Skip to content

Commit 27620f5

Browse files
authored
chore(val): reject proposals not for the current or next slot (#10450)
1 parent a28b581 commit 27620f5

File tree

4 files changed

+62
-15
lines changed

4 files changed

+62
-15
lines changed

yarn-project/epoch-cache/src/epoch_cache.test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,18 @@ describe('EpochCache', () => {
8888
// Hence the chosen values for testCommittee below
8989

9090
// Get validator for slot 0
91-
let [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot();
92-
expect(currentValidator).toEqual(testCommittee[1]);
91+
const { currentProposer } = await epochCache.getProposerInCurrentOrNextSlot();
92+
expect(currentProposer).toEqual(testCommittee[1]);
9393

9494
// Move to next slot
9595
jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000);
96-
[currentValidator] = await epochCache.getProposerInCurrentOrNextSlot();
97-
expect(currentValidator).toEqual(testCommittee[1]);
96+
const { currentProposer: nextProposer } = await epochCache.getProposerInCurrentOrNextSlot();
97+
expect(nextProposer).toEqual(testCommittee[1]);
9898

9999
// Move to slot that wraps around validator set
100100
jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000);
101-
[currentValidator] = await epochCache.getProposerInCurrentOrNextSlot();
102-
expect(currentValidator).toEqual(testCommittee[0]);
101+
const { currentProposer: nextNextProposer } = await epochCache.getProposerInCurrentOrNextSlot();
102+
expect(nextNextProposer).toEqual(testCommittee[0]);
103103
});
104104

105105
it('Should request to update the validator set when on the epoch boundary', async () => {

yarn-project/epoch-cache/src/epoch_cache.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ export class EpochCache {
151151
* If we are at an epoch boundary, then we can update the cache for the next epoch, this is the last check
152152
* we do in the validator client, so we can update the cache here.
153153
*/
154-
async getProposerInCurrentOrNextSlot(): Promise<[EthAddress, EthAddress]> {
154+
async getProposerInCurrentOrNextSlot(): Promise<{
155+
currentProposer: EthAddress;
156+
nextProposer: EthAddress;
157+
currentSlot: bigint;
158+
nextSlot: bigint;
159+
}> {
155160
// Validators are sorted by their index in the committee, and getValidatorSet will cache
156161
const committee = await this.getCommittee();
157162
const { slot: currentSlot, epoch: currentEpoch } = this.getEpochAndSlotNow();
@@ -176,10 +181,10 @@ export class EpochCache {
176181
BigInt(committee.length),
177182
);
178183

179-
const calculatedProposer = committee[Number(proposerIndex)];
180-
const nextCalculatedProposer = committee[Number(nextProposerIndex)];
184+
const currentProposer = committee[Number(proposerIndex)];
185+
const nextProposer = committee[Number(nextProposerIndex)];
181186

182-
return [calculatedProposer, nextCalculatedProposer];
187+
return { currentProposer, nextProposer, currentSlot, nextSlot };
183188
}
184189

185190
/**

yarn-project/validator-client/src/validator.test.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ describe('ValidationService', () => {
101101
// mock the p2pClient.getTxStatus to return undefined for all transactions
102102
p2pClient.getTxStatus.mockImplementation(() => undefined);
103103
epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() =>
104-
Promise.resolve([proposal.getSender(), proposal.getSender()]),
104+
Promise.resolve({
105+
currentProposer: proposal.getSender(),
106+
nextProposer: proposal.getSender(),
107+
currentSlot: proposal.slotNumber.toBigInt(),
108+
nextSlot: proposal.slotNumber.toBigInt() + 1n,
109+
}),
105110
);
106111
epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true));
107112

@@ -119,7 +124,12 @@ describe('ValidationService', () => {
119124

120125
// Setup epoch cache mocks
121126
epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() =>
122-
Promise.resolve([proposal.getSender(), proposal.getSender()]),
127+
Promise.resolve({
128+
currentProposer: proposal.getSender(),
129+
nextProposer: proposal.getSender(),
130+
currentSlot: proposal.slotNumber.toBigInt(),
131+
nextSlot: proposal.slotNumber.toBigInt() + 1n,
132+
}),
123133
);
124134
epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false));
125135

@@ -132,7 +142,30 @@ describe('ValidationService', () => {
132142

133143
// Setup epoch cache mocks
134144
epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() =>
135-
Promise.resolve([EthAddress.random(), EthAddress.random()]),
145+
Promise.resolve({
146+
currentProposer: EthAddress.random(),
147+
nextProposer: EthAddress.random(),
148+
currentSlot: proposal.slotNumber.toBigInt(),
149+
nextSlot: proposal.slotNumber.toBigInt() + 1n,
150+
}),
151+
);
152+
epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true));
153+
154+
const attestation = await validatorClient.attestToProposal(proposal);
155+
expect(attestation).toBeUndefined();
156+
});
157+
158+
it('Should not return an attestation if the proposal is not for the current or next slot', async () => {
159+
const proposal = makeBlockProposal();
160+
161+
// Setup epoch cache mocks
162+
epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() =>
163+
Promise.resolve({
164+
currentProposer: proposal.getSender(),
165+
nextProposer: proposal.getSender(),
166+
currentSlot: proposal.slotNumber.toBigInt() + 20n,
167+
nextSlot: proposal.slotNumber.toBigInt() + 21n,
168+
}),
136169
);
137170
epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true));
138171

yarn-project/validator-client/src/validator.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,21 @@ export class ValidatorClient extends WithTracer implements Validator {
134134
}
135135

136136
// Check that the proposal is from the current proposer, or the next proposer.
137-
const [currentProposer, nextSlotProposer] = await this.epochCache.getProposerInCurrentOrNextSlot();
138-
if (!proposal.getSender().equals(currentProposer) && !proposal.getSender().equals(nextSlotProposer)) {
137+
const proposalSender = proposal.getSender();
138+
const { currentProposer, nextProposer, currentSlot, nextSlot } =
139+
await this.epochCache.getProposerInCurrentOrNextSlot();
140+
if (!proposalSender.equals(currentProposer) && !proposalSender.equals(nextProposer)) {
139141
this.log.verbose(`Not the current or next proposer, skipping attestation`);
140142
return undefined;
141143
}
142144

145+
// Check that the proposal is for the current or next slot
146+
const slotNumberBigInt = proposal.slotNumber.toBigInt();
147+
if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) {
148+
this.log.verbose(`Not the current or next slot, skipping attestation`);
149+
return undefined;
150+
}
151+
143152
// Check that all of the tranasctions in the proposal are available in the tx pool before attesting
144153
this.log.verbose(`request to attest`, {
145154
archive: proposal.payload.archive.toString(),

0 commit comments

Comments
 (0)