Skip to content

Commit

Permalink
Merge e040142 into fd8e335
Browse files Browse the repository at this point in the history
  • Loading branch information
twoeths committed Sep 22, 2022
2 parents fd8e335 + e040142 commit 065fde3
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 24 deletions.
19 changes: 18 additions & 1 deletion packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {altair, ssz} from "@lodestar/types";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
import {toHexString} from "@chainsafe/ssz";
import {
CachedBeaconStateAltair,
Expand Down Expand Up @@ -141,6 +141,23 @@ export async function importBlock(
}
}

// FORK_CHOICE_ATT_EPOCH_LIMIT is for attestation to become valid
// but AttesterSlashing could be found before that time and still able to submit valid attestations
// until slashed validator become inactive, see computeActivationExitEpoch() function
if (
!opts.skipImportingAttestations &&
blockEpoch >= currentEpoch - FORK_CHOICE_ATT_EPOCH_LIMIT - 1 - MAX_SEED_LOOKAHEAD
) {
for (const slashing of block.message.body.attesterSlashings) {
try {
// all AttesterSlashings are valid before reaching this
this.forkChoice.onAttesterSlashing(slashing);
} catch (e) {
this.logger.warn("Error processing AttesterSlashing from block", {slot: block.message.slot}, e as Error);
}
}
}

// - Write block and state to hot db
// - Write block and state to snapshot_cache
if (block.message.slot % SLOTS_PER_EPOCH === 0) {
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/network/gossip/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipH

try {
chain.opPool.insertAttesterSlashing(attesterSlashing);
chain.forkChoice.onAttesterSlashing(attesterSlashing);
} catch (e) {
logger.error("Error adding attesterSlashing to pool", {}, e as Error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ describe("getAttestationsForBlock", () => {
finalizedCheckpoint: {...finalizedCheckpoint, rootHex: toHexString(finalizedCheckpoint.root)},
unrealizedFinalizedCheckpoint: {...finalizedCheckpoint, rootHex: toHexString(finalizedCheckpoint.root)},
justifiedBalancesGetter: () => originalState.epochCtx.effectiveBalanceIncrements,
equivocatingIndices: new Set(),
};
forkchoice = new ForkChoice(originalState.config, fcStore, protoArray);
});
Expand Down
37 changes: 33 additions & 4 deletions packages/beacon-node/test/spec/presets/fork_choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ export const forkChoiceTest: TestRunnerFn<ForkChoiceTestCase, void> = (fork) =>
chain.forkChoice.onAttestation(headState.epochCtx.getIndexedAttestation(attestation));
}

// attester slashing step
else if (isAttesterSlashing(step)) {
logger.debug(`Step ${i}/${stepsLen} attester slashing`, {
root: step.attester_slashing,
valid: Boolean(step.valid),
});
const attesterSlashing = testcase.attesterSlashings.get(step.attester_slashing);
if (!attesterSlashing) throw Error(`No attester slashing ${step.attester_slashing}`);
chain.forkChoice.onAttesterSlashing(attesterSlashing);
}

// block step
else if (isBlock(step)) {
const isValid = Boolean(step.valid ?? true);
Expand Down Expand Up @@ -227,6 +238,7 @@ export const forkChoiceTest: TestRunnerFn<ForkChoiceTestCase, void> = (fork) =>
const blocks = new Map<string, allForks.SignedBeaconBlock>();
const powBlocks = new Map<string, bellatrix.PowBlock>();
const attestations = new Map<string, phase0.Attestation>();
const attesterSlashings = new Map<string, phase0.AttesterSlashing>();
for (const key in t) {
const blockMatch = key.match(BLOCK_FILE_NAME);
if (blockMatch) {
Expand All @@ -240,6 +252,10 @@ export const forkChoiceTest: TestRunnerFn<ForkChoiceTestCase, void> = (fork) =>
if (attMatch) {
attestations.set(key, t[key]);
}
const attesterSlashingMatch = key.match(ATTESTER_SLASHING_FILE_NAME);
if (attesterSlashingMatch) {
attesterSlashings.set(key, t[key]);
}
}
return {
meta: t["meta"] as ForkChoiceTestCase["meta"],
Expand All @@ -249,14 +265,12 @@ export const forkChoiceTest: TestRunnerFn<ForkChoiceTestCase, void> = (fork) =>
blocks,
powBlocks,
attestations,
attesterSlashings,
};
},
timeout: 10000,
// eslint-disable-next-line @typescript-eslint/no-empty-function
expectFunc: () => {},
shouldSkip: (_testCase, name, _index) => {
return name.includes("discard_equivocations");
},
},
};
};
Expand All @@ -268,7 +282,7 @@ function toSpecTestCheckpoint(checkpoint: CheckpointWithHex): SpecTestCheckpoint
};
}

type Step = OnTick | OnAttestation | OnBlock | OnPowBlock | Checks;
type Step = OnTick | OnAttestation | OnAttesterSlashing | OnBlock | OnPowBlock | Checks;

type SpecTestCheckpoint = {epoch: bigint; root: string};

Expand All @@ -289,6 +303,16 @@ type OnAttestation = {
valid?: number;
};

type OnAttesterSlashing = {
/**
* the name of the `attester_slashing_<32-byte-root>.ssz_snappy` file.
* To execute `on_attester_slashing(store, attester_slashing)` with the given attester slashing.
*/
attester_slashing: string;
/** optional, default to `true` */
valid?: number;
};

type OnBlock = {
/** the name of the `block_<32-byte-root>.ssz_snappy` file. To execute `on_block(store, block)` */
block: string;
Expand Down Expand Up @@ -330,6 +354,7 @@ type ForkChoiceTestCase = {
blocks: Map<string, allForks.SignedBeaconBlock>;
powBlocks: Map<string, bellatrix.PowBlock>;
attestations: Map<string, phase0.Attestation>;
attesterSlashings: Map<string, phase0.AttesterSlashing>;
};

function isTick(step: Step): step is OnTick {
Expand All @@ -340,6 +365,10 @@ function isAttestation(step: Step): step is OnAttestation {
return typeof (step as OnAttestation).attestation === "string";
}

function isAttesterSlashing(step: Step): step is OnAttesterSlashing {
return typeof (step as OnAttesterSlashing).attester_slashing === "string";
}

function isBlock(step: Step): step is OnBlock {
return typeof (step as OnBlock).block === "string";
}
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/test/utils/mocks/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ function mockForkChoice(): IForkChoice {
getJustifiedCheckpoint: () => checkpoint,
onBlock: () => {},
onAttestation: () => {},
onAttesterSlashing: () => {},
getLatestMessage: () => undefined,
updateTime: () => {},
getTime: () => 0,
Expand Down
24 changes: 22 additions & 2 deletions packages/fork-choice/src/forkChoice/forkChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
isBellatrixBlockBodyType,
isBellatrixStateType,
isExecutionEnabled,
getAttesterSlashableIndices,
} from "@lodestar/state-transition";
import {computeUnrealizedCheckpoints} from "@lodestar/state-transition/epoch";
import {IChainConfig, IChainForkConfig} from "@lodestar/config";
Expand Down Expand Up @@ -192,7 +193,13 @@ export class ForkChoice implements IForkChoice {
// Check if scores need to be calculated/updated
// eslint-disable-next-line prefer-const
const justifiedBalances = this.fcStore.justified.balances;
const deltas = computeDeltas(this.protoArray.indices, this.votes, justifiedBalances, justifiedBalances);
const deltas = computeDeltas(
this.protoArray.indices,
this.votes,
justifiedBalances,
justifiedBalances,
this.fcStore.equivocatingIndices
);
/**
* The structure in line with deltas to propogate boost up the branch
* starting from the proposerIndex
Expand Down Expand Up @@ -521,7 +528,9 @@ export class ForkChoice implements IForkChoice {

if (slot < this.fcStore.currentSlot) {
for (const validatorIndex of attestation.attestingIndices) {
this.addLatestMessage(validatorIndex, targetEpoch, blockRootHex);
if (!this.fcStore.equivocatingIndices.has(validatorIndex)) {
this.addLatestMessage(validatorIndex, targetEpoch, blockRootHex);
}
}
} else {
// The spec declares:
Expand All @@ -539,6 +548,17 @@ export class ForkChoice implements IForkChoice {
}
}

/**
* Small different from the spec:
* We already call is_slashable_attestation_data() and is_valid_indexed_attestation
* in state transition so no need to do it again
*/
onAttesterSlashing(attesterSlashing: phase0.AttesterSlashing): void {
// TODO: we already call in in state-transition, find a way not to recompute it again
const intersectingIndices = getAttesterSlashableIndices(attesterSlashing);
intersectingIndices.forEach((validatorIndex) => this.fcStore.equivocatingIndices.add(validatorIndex));
}

getLatestMessage(validatorIndex: ValidatorIndex): LatestMessage | undefined {
const vote = this.votes[validatorIndex];
if (vote === undefined) {
Expand Down
8 changes: 8 additions & 0 deletions packages/fork-choice/src/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ export interface IForkChoice {
* will not be run here.
*/
onAttestation(attestation: phase0.IndexedAttestation, attDataRoot?: string): void;
/**
* Register attester slashing in order not to consider their votes in `getHead`
*
* ## Specification
*
* https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#on_attester_slashing
*/
onAttesterSlashing(slashing: phase0.AttesterSlashing): void;
getLatestMessage(validatorIndex: ValidatorIndex): LatestMessage | undefined;
/**
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
Expand Down
4 changes: 3 additions & 1 deletion packages/fork-choice/src/forkChoice/store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {EffectiveBalanceIncrements, CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {phase0, Slot, RootHex} from "@lodestar/types";
import {phase0, Slot, RootHex, ValidatorIndex} from "@lodestar/types";
import {toHexString} from "@chainsafe/ssz";
import {CheckpointHexWithBalance} from "./interface.js";

Expand Down Expand Up @@ -43,6 +43,7 @@ export interface IForkChoiceStore {
finalizedCheckpoint: CheckpointWithHex;
unrealizedFinalizedCheckpoint: CheckpointWithHex;
justifiedBalancesGetter: JustifiedBalancesGetter;
equivocatingIndices: Set<ValidatorIndex>;
}

/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/member-ordering */
Expand All @@ -56,6 +57,7 @@ export class ForkChoiceStore implements IForkChoiceStore {
unrealizedJustified: CheckpointHexWithBalance;
private _finalizedCheckpoint: CheckpointWithHex;
unrealizedFinalizedCheckpoint: CheckpointWithHex;
equivocatingIndices = new Set<ValidatorIndex>();

constructor(
public currentSlot: Slot,
Expand Down
22 changes: 21 additions & 1 deletion packages/fork-choice/src/protoArray/computeDeltas.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {ValidatorIndex} from "@lodestar/types";
import {EffectiveBalanceIncrements} from "@lodestar/state-transition";
import {VoteTracker, HEX_ZERO_HASH} from "./interface.js";
import {ProtoArrayError, ProtoArrayErrorCode} from "./errors.js";
Expand All @@ -15,7 +16,8 @@ export function computeDeltas(
indices: Map<string, number>,
votes: VoteTracker[],
oldBalances: EffectiveBalanceIncrements,
newBalances: EffectiveBalanceIncrements
newBalances: EffectiveBalanceIncrements,
equivocatingIndices: Set<ValidatorIndex>
): number[] {
const deltas = Array.from({length: indices.size}, () => 0);
const zeroHash = HEX_ZERO_HASH;
Expand All @@ -42,6 +44,24 @@ export function computeDeltas(
// on-boarded fewer validators than the prior fork.
const newBalance = newBalances[vIndex] || 0;

if (equivocatingIndices.has(vIndex)) {
// this function could be called multiple times but we only want to process slashing validator for 1 time
if (currentRoot !== zeroHash) {
const currentDeltaIndex = indices.get(currentRoot);
if (currentDeltaIndex !== undefined) {
if (currentDeltaIndex >= deltas.length) {
throw new ProtoArrayError({
code: ProtoArrayErrorCode.INVALID_NODE_DELTA,
index: currentDeltaIndex,
});
}
deltas[currentDeltaIndex] -= oldBalance;
}
}
vote.currentRoot = zeroHash;
continue;
}

if (currentRoot !== nextRoot || oldBalance !== newBalance) {
// We ignore the vote if it is not known in `indices .
// We assume that it is outside of our tree (ie: pre-finalization) and therefore not interesting
Expand Down
6 changes: 0 additions & 6 deletions packages/fork-choice/src/protoArray/protoArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,6 @@ export class ProtoArray {
? -node.weight
: deltas[nodeIndex] + currentBoost - previousBoost;

if (nodeDelta === undefined) {
throw new ProtoArrayError({
code: ProtoArrayErrorCode.INVALID_NODE_DELTA,
index: nodeIndex,
});
}
// Apply the delta to the node
node.weight += nodeDelta;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe("ForkChoice", () => {
finalizedCheckpoint: {epoch: genesisEpoch, root: fromHexString(finalizedRoot), rootHex: finalizedRoot},
unrealizedFinalizedCheckpoint: {epoch: genesisEpoch, root: fromHexString(finalizedRoot), rootHex: finalizedRoot},
justifiedBalancesGetter: () => balances,
equivocatingIndices: new Set(),
};

forkchoice = new ForkChoice(config, fcStore, protoArr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe("computeDeltas", () => {
return votes;
},
fn: (votes) => {
computeDeltas(indices, votes, oldBalances, newBalances);
computeDeltas(indices, votes, oldBalances, newBalances, new Set());
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe("Forkchoice", function () {
finalizedCheckpoint: {epoch: genesisEpoch, root: fromHexString(finalizedRoot), rootHex: finalizedRoot},
unrealizedFinalizedCheckpoint: {epoch: genesisEpoch, root: fromHexString(finalizedRoot), rootHex: finalizedRoot},
justifiedBalancesGetter: () => new Uint8Array([32]),
equivocatingIndices: new Set(),
};

const getParentBlockRoot = (slot: number, skippedSlots: number[] = []): RootHex => {
Expand Down

0 comments on commit 065fde3

Please sign in to comment.