Skip to content

Commit

Permalink
chore(val): reject proposals not for the current or next slot (#10450)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maddiaa0 authored Dec 10, 2024
1 parent a28b581 commit 27620f5
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 15 deletions.
12 changes: 6 additions & 6 deletions yarn-project/epoch-cache/src/epoch_cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
13 changes: 9 additions & 4 deletions yarn-project/epoch-cache/src/epoch_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 };
}

/**
Expand Down
39 changes: 36 additions & 3 deletions yarn-project/validator-client/src/validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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));

Expand All @@ -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));

Expand Down
13 changes: 11 additions & 2 deletions yarn-project/validator-client/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 27620f5

Please sign in to comment.