Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions yarn-project/slasher/src/tally_slasher_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,33 @@ describe('TallySlasherClient', () => {
expect(tallySlashingProposer.getRound).toHaveBeenCalledWith(0n);
expect(tallySlashingProposer.getRound).toHaveBeenCalledWith(1n);
});

it('should respect lifetimeInRounds when computing oldestExecutableRound', async () => {
// Use a large lookBack to test that lifetimeInRounds constrains it
tallySlasherClient.updateConfig({ slashExecuteRoundsLookBack: 20 });

const currentRound = 15n;
const currentSlot = currentRound * BigInt(roundSize);
const slashingLifetimeInRounds = BigInt(settings.slashingLifetimeInRounds); // 10

// The oldest executable round should be currentRound - lifetimeInRounds = 15 - 10 = 5
// NOT currentRound - executionDelay - 1 - lookBack = 15 - 2 - 1 - 20 = -8 (clamped to 0)
const expectedOldestRound = currentRound - slashingLifetimeInRounds; // 5

// Mock rounds 5-12 as executable (executableRound = currentRound - executionDelay - 1 = 15 - 2 - 1 = 12)
tallySlashingProposer.getRound.mockImplementation((round: bigint) =>
Promise.resolve(round >= expectedOldestRound && round <= 12n ? executableRoundData : emptyRoundData),
);

const actions = await tallySlasherClient.getProposerActions(currentSlot);

// Should execute the oldest round (5), not try to execute rounds before that
expect(actions).toHaveLength(1);
expectActionExecuteSlash(actions[0], expectedOldestRound);

// Verify we didn't try to check rounds older than the lifetime allows
expect(tallySlashingProposer.getRound).not.toHaveBeenCalledWith(expectedOldestRound - 1n);
});
});

describe('multiple', () => {
Expand Down
14 changes: 12 additions & 2 deletions yarn-project/slasher/src/tally_slasher_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,14 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
const slashingExecutionDelayInRounds = BigInt(this.settings.slashingExecutionDelayInRounds);
const executableRound = currentRound - slashingExecutionDelayInRounds - 1n;
const lookBack = BigInt(this.config.slashExecuteRoundsLookBack);
const oldestExecutableRound = maxBigint(0n, executableRound - lookBack);
const slashingLifetimeInRounds = BigInt(this.settings.slashingLifetimeInRounds);

// Compute the oldest executable round considering both lookBack and lifetimeInRounds
// A round is only executable if currentRound <= round + lifetimeInRounds
// So the oldest round we can execute is: currentRound - lifetimeInRounds
const oldestByLifetime = maxBigint(0n, currentRound - slashingLifetimeInRounds);
const oldestByLookBack = maxBigint(0n, executableRound - lookBack);
const oldestExecutableRound = maxBigint(oldestByLifetime, oldestByLookBack);

// Check if slashing is enabled at all
if (!(await this.slasher.isSlashingEnabled())) {
Expand All @@ -199,10 +206,12 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
slotNumber,
currentRound,
oldestExecutableRound,
oldestByLifetime,
oldestByLookBack,
executableRound,
slashingExecutionDelayInRounds,
lookBack,
slashingLifetimeInRounds: this.settings.slashingLifetimeInRounds,
slashingLifetimeInRounds,
});

// Iterate over all rounds, starting from the oldest, until we find one that is executable
Expand All @@ -220,6 +229,7 @@ export class TallySlasherClient implements ProposerSlashActionProvider, SlasherC
/**
* Checks if a given round is executable and returns an execute-slash action for it if so.
* Assumes round number has already been checked against lifetime and execution delay.
* @param executableRound - The round to check for execution
*/
private async tryGetRoundExecuteAction(executableRound: bigint): Promise<ProposerSlashAction | undefined> {
let logData: Record<string, unknown> = { executableRound };
Expand Down
Loading