From 27620f5ccd7bb23cf9d8cb8a913386338c47a08c Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:46:06 +0800 Subject: [PATCH] chore(val): reject proposals not for the current or next slot (#10450) --- .../epoch-cache/src/epoch_cache.test.ts | 12 +++--- yarn-project/epoch-cache/src/epoch_cache.ts | 13 +++++-- .../validator-client/src/validator.test.ts | 39 +++++++++++++++++-- .../validator-client/src/validator.ts | 13 ++++++- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/yarn-project/epoch-cache/src/epoch_cache.test.ts b/yarn-project/epoch-cache/src/epoch_cache.test.ts index 4cde40eff1e..f21d7a3b77c 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.test.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.test.ts @@ -88,18 +88,18 @@ describe('EpochCache', () => { // Hence the chosen values for testCommittee below // Get validator for slot 0 - let [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[1]); + const { currentProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(currentProposer).toEqual(testCommittee[1]); // Move to next slot jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 1000); - [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[1]); + const { currentProposer: nextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(nextProposer).toEqual(testCommittee[1]); // Move to slot that wraps around validator set jest.setSystemTime(initialTime + Number(SLOT_DURATION) * 3 * 1000); - [currentValidator] = await epochCache.getProposerInCurrentOrNextSlot(); - expect(currentValidator).toEqual(testCommittee[0]); + const { currentProposer: nextNextProposer } = await epochCache.getProposerInCurrentOrNextSlot(); + expect(nextNextProposer).toEqual(testCommittee[0]); }); it('Should request to update the validator set when on the epoch boundary', async () => { diff --git a/yarn-project/epoch-cache/src/epoch_cache.ts b/yarn-project/epoch-cache/src/epoch_cache.ts index f2a1b09fdc6..962f59e3d3a 100644 --- a/yarn-project/epoch-cache/src/epoch_cache.ts +++ b/yarn-project/epoch-cache/src/epoch_cache.ts @@ -151,7 +151,12 @@ export class EpochCache { * If we are at an epoch boundary, then we can update the cache for the next epoch, this is the last check * we do in the validator client, so we can update the cache here. */ - async getProposerInCurrentOrNextSlot(): Promise<[EthAddress, EthAddress]> { + async getProposerInCurrentOrNextSlot(): Promise<{ + currentProposer: EthAddress; + nextProposer: EthAddress; + currentSlot: bigint; + nextSlot: bigint; + }> { // Validators are sorted by their index in the committee, and getValidatorSet will cache const committee = await this.getCommittee(); const { slot: currentSlot, epoch: currentEpoch } = this.getEpochAndSlotNow(); @@ -176,10 +181,10 @@ export class EpochCache { BigInt(committee.length), ); - const calculatedProposer = committee[Number(proposerIndex)]; - const nextCalculatedProposer = committee[Number(nextProposerIndex)]; + const currentProposer = committee[Number(proposerIndex)]; + const nextProposer = committee[Number(nextProposerIndex)]; - return [calculatedProposer, nextCalculatedProposer]; + return { currentProposer, nextProposer, currentSlot, nextSlot }; } /** diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index df2bee4ccbb..7b6fdca5a26 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -101,7 +101,12 @@ describe('ValidationService', () => { // mock the p2pClient.getTxStatus to return undefined for all transactions p2pClient.getTxStatus.mockImplementation(() => undefined); epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([proposal.getSender(), proposal.getSender()]), + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); @@ -119,7 +124,12 @@ describe('ValidationService', () => { // Setup epoch cache mocks epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([proposal.getSender(), proposal.getSender()]), + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(false)); @@ -132,7 +142,30 @@ describe('ValidationService', () => { // Setup epoch cache mocks epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => - Promise.resolve([EthAddress.random(), EthAddress.random()]), + Promise.resolve({ + currentProposer: EthAddress.random(), + nextProposer: EthAddress.random(), + currentSlot: proposal.slotNumber.toBigInt(), + nextSlot: proposal.slotNumber.toBigInt() + 1n, + }), + ); + epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); + + const attestation = await validatorClient.attestToProposal(proposal); + expect(attestation).toBeUndefined(); + }); + + it('Should not return an attestation if the proposal is not for the current or next slot', async () => { + const proposal = makeBlockProposal(); + + // Setup epoch cache mocks + epochCache.getProposerInCurrentOrNextSlot.mockImplementation(() => + Promise.resolve({ + currentProposer: proposal.getSender(), + nextProposer: proposal.getSender(), + currentSlot: proposal.slotNumber.toBigInt() + 20n, + nextSlot: proposal.slotNumber.toBigInt() + 21n, + }), ); epochCache.isInCommittee.mockImplementation(() => Promise.resolve(true)); diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index 0671495b76e..84bed8fdc49 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -134,12 +134,21 @@ export class ValidatorClient extends WithTracer implements Validator { } // Check that the proposal is from the current proposer, or the next proposer. - const [currentProposer, nextSlotProposer] = await this.epochCache.getProposerInCurrentOrNextSlot(); - if (!proposal.getSender().equals(currentProposer) && !proposal.getSender().equals(nextSlotProposer)) { + const proposalSender = proposal.getSender(); + const { currentProposer, nextProposer, currentSlot, nextSlot } = + await this.epochCache.getProposerInCurrentOrNextSlot(); + if (!proposalSender.equals(currentProposer) && !proposalSender.equals(nextProposer)) { this.log.verbose(`Not the current or next proposer, skipping attestation`); return undefined; } + // Check that the proposal is for the current or next slot + const slotNumberBigInt = proposal.slotNumber.toBigInt(); + if (slotNumberBigInt !== currentSlot && slotNumberBigInt !== nextSlot) { + this.log.verbose(`Not the current or next slot, skipping attestation`); + return undefined; + } + // Check that all of the tranasctions in the proposal are available in the tx pool before attesting this.log.verbose(`request to attest`, { archive: proposal.payload.archive.toString(),