Skip to content

Commit

Permalink
Merge 62279f7 into 7beccf0
Browse files Browse the repository at this point in the history
  • Loading branch information
dapplion authored Sep 14, 2021
2 parents 7beccf0 + 62279f7 commit 6b1ff9e
Show file tree
Hide file tree
Showing 29 changed files with 433 additions and 263 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {allForks, phase0, ValidatorIndex} from "@chainsafe/lodestar-types";
import {allForks, phase0} from "@chainsafe/lodestar-types";
import {ForkName} from "@chainsafe/lodestar-params";

import {isSlashableValidator, isSlashableAttestationData} from "../../util";
import {isSlashableValidator, isSlashableAttestationData, getAttesterSlashableIndices} from "../../util";
import {CachedBeaconState} from "../util";
import {isValidIndexedAttestation} from "./isValidIndexedAttestation";
import {slashValidatorAllForks} from "./slashValidator";
Expand All @@ -22,20 +22,12 @@ export function processAttesterSlashing(
): void {
assertValidAttesterSlashing(state as CachedBeaconState<allForks.BeaconState>, attesterSlashing, verifySignatures);

// TODO: Is there a more performant intersection algorythm? This should be a utility function: intersect()
const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices);
const attSet2 = new Set(attesterSlashing.attestation2.attestingIndices);
const indices: ValidatorIndex[] = [];
for (const i of attSet1.values()) {
if (attSet2.has(i)) {
indices.push(i);
}
}
const intersectingIndices = getAttesterSlashableIndices(attesterSlashing);

let slashedAny = false;
const validators = state.validators; // Get the validators sub tree once for all indices
// TODO: Why do we need to sort()? If it necessary add a comment with why
for (const index of indices.sort((a, b) => a - b)) {
// Spec requires to sort indexes beforehand
for (const index of intersectingIndices.sort((a, b) => a - b)) {
if (isSlashableValidator(validators[index], state.epochCtx.currentShuffling.epoch)) {
slashValidatorAllForks(fork, state, index, blockProcess);
slashedAny = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,35 @@ export function processVoluntaryExitAllForks(
blockProcess: BlockProcess,
verifySignature = true
): void {
assertValidVoluntaryExit(state as CachedBeaconState<allForks.BeaconState>, signedVoluntaryExit, verifySignature);
if (!isValidVoluntaryExit(state as CachedBeaconState<allForks.BeaconState>, signedVoluntaryExit, verifySignature)) {
throw Error("Invalid voluntary exit");
}

const validator = state.validators[signedVoluntaryExit.message.validatorIndex];
initiateValidatorExit(state as CachedBeaconState<allForks.BeaconState>, validator);
}

export function assertValidVoluntaryExit(
export function isValidVoluntaryExit(
state: CachedBeaconState<allForks.BeaconState>,
signedVoluntaryExit: phase0.SignedVoluntaryExit,
verifySignature = true
): void {
): boolean {
const {config, epochCtx} = state;
const voluntaryExit = signedVoluntaryExit.message;
const validator = state.validators[voluntaryExit.validatorIndex];
const currentEpoch = epochCtx.currentShuffling.epoch;
// verify the validator is active
if (!isActiveValidator(validator, currentEpoch)) {
throw new Error("VoluntaryExit validator is not active");
}
// verify exit has not been initiated
if (validator.exitEpoch !== FAR_FUTURE_EPOCH) {
throw new Error(`VoluntaryExit validator exit has already been initiated: exitEpoch=${validator.exitEpoch}`);
}
// exits must specify an epoch when they become valid; they are not valid before then
if (!(currentEpoch >= voluntaryExit.epoch)) {
throw new Error(`VoluntaryExit epoch is not yet valid: epoch=${voluntaryExit.epoch} currentEpoch=${currentEpoch}`);
}
// verify the validator had been active long enough
if (!(currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD)) {
throw new Error("VoluntaryExit validator has not been active for long enough");
}

// verify signature
if (verifySignature) {
if (!verifyVoluntaryExitSignature(state as CachedBeaconState<allForks.BeaconState>, signedVoluntaryExit)) {
throw new Error("VoluntaryExit has an invalid signature");
}
}
return (
// verify the validator is active
isActiveValidator(validator, currentEpoch) &&
// verify exit has not been initiated
validator.exitEpoch === FAR_FUTURE_EPOCH &&
// exits must specify an epoch when they become valid; they are not valid before then
currentEpoch >= voluntaryExit.epoch &&
// verify the validator had been active long enough
currentEpoch >= validator.activationEpoch + config.SHARD_COMMITTEE_PERIOD &&
// verify signature
(!verifySignature ||
verifyVoluntaryExitSignature(state as CachedBeaconState<allForks.BeaconState>, signedVoluntaryExit))
);
}
15 changes: 14 additions & 1 deletion packages/beacon-state-transition/src/util/attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import {MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params";
import {phase0, Slot, ssz} from "@chainsafe/lodestar-types";
import {phase0, Slot, ssz, ValidatorIndex} from "@chainsafe/lodestar-types";

/**
* Check if [[data1]] and [[data2]] are slashable according to Casper FFG rules.
Expand All @@ -22,3 +22,16 @@ export function isValidAttestationSlot(attestationSlot: Slot, currentSlot: Slot)
attestationSlot + MIN_ATTESTATION_INCLUSION_DELAY <= currentSlot && currentSlot <= attestationSlot + SLOTS_PER_EPOCH
);
}

export function getAttesterSlashableIndices(attesterSlashing: phase0.AttesterSlashing): ValidatorIndex[] {
const indices: ValidatorIndex[] = [];
const attSet1 = new Set(attesterSlashing.attestation1.attestingIndices);
const attArr2 = attesterSlashing.attestation2.attestingIndices;
for (let i = 0, len = attArr2.length; i < len; i++) {
const index = attArr2[i];
if (attSet1.has(index)) {
indices.push(index);
}
}
return indices;
}
30 changes: 16 additions & 14 deletions packages/lodestar/src/api/impl/beacon/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export function getBeaconPoolApi({
logger,
metrics,
network,
db,
}: Pick<ApiModules, "chain" | "logger" | "metrics" | "network" | "db">): IBeaconPoolApi {
}: Pick<ApiModules, "chain" | "logger" | "metrics" | "network">): IBeaconPoolApi {
return {
async getPoolAttestations(filters) {
// Already filtered by slot
Expand All @@ -33,15 +32,15 @@ export function getBeaconPoolApi({
},

async getPoolAttesterSlashings() {
return {data: await db.attesterSlashing.values()};
return {data: chain.opPool.getAllAttesterSlashings()};
},

async getPoolProposerSlashings() {
return {data: await db.proposerSlashing.values()};
return {data: chain.opPool.getAllProposerSlashings()};
},

async getPoolVoluntaryExits() {
return {data: await db.voluntaryExit.values()};
return {data: chain.opPool.getAllVoluntaryExits()};
},

async submitPoolAttestations(attestations) {
Expand Down Expand Up @@ -85,19 +84,22 @@ export function getBeaconPoolApi({
}
},

async submitPoolAttesterSlashing(slashing) {
await validateGossipAttesterSlashing(chain, db, slashing);
await Promise.all([network.gossip.publishAttesterSlashing(slashing), db.attesterSlashing.add(slashing)]);
async submitPoolAttesterSlashing(attesterSlashing) {
await validateGossipAttesterSlashing(chain, attesterSlashing);
chain.opPool.insertAttesterSlashing(attesterSlashing);
await network.gossip.publishAttesterSlashing(attesterSlashing);
},

async submitPoolProposerSlashing(slashing) {
await validateGossipProposerSlashing(chain, db, slashing);
await Promise.all([network.gossip.publishProposerSlashing(slashing), db.proposerSlashing.add(slashing)]);
async submitPoolProposerSlashing(proposerSlashing) {
await validateGossipProposerSlashing(chain, proposerSlashing);
chain.opPool.insertProposerSlashing(proposerSlashing);
await network.gossip.publishProposerSlashing(proposerSlashing);
},

async submitPoolVoluntaryExit(exit) {
await validateGossipVoluntaryExit(chain, db, exit);
await Promise.all([network.gossip.publishVoluntaryExit(exit), db.voluntaryExit.add(exit)]);
async submitPoolVoluntaryExit(voluntaryExit) {
await validateGossipVoluntaryExit(chain, voluntaryExit);
chain.opPool.insertVoluntaryExit(voluntaryExit);
await network.gossip.publishVoluntaryExit(voluntaryExit);
},

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/lodestar/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const SYNC_TOLERANCE_EPOCHS = 8;
export function getValidatorApi({
chain,
config,
db,
eth1,
logger,
metrics,
Expand Down Expand Up @@ -145,7 +144,7 @@ export function getValidatorApi({

timer = metrics?.blockProductionTime.startTimer();
const block = await assembleBlock(
{config, chain, db, eth1, metrics},
{config, chain, eth1, metrics},
slot,
randaoReveal,
toGraffitiBuffer(graffiti || "")
Expand Down
17 changes: 15 additions & 2 deletions packages/lodestar/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ import {
SeenSyncCommitteeMessages,
SeenContributionAndProof,
} from "./seenCache";
import {AttestationPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools";
import {
AggregatedAttestationPool,
AttestationPool,
SyncCommitteeMessagePool,
SyncContributionAndProofPool,
OpPool,
} from "./opPools";
import {ForkDigestContext, IForkDigestContext} from "../util/forkDigestContext";
import {LightClientIniter} from "./lightClient";
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool";
import {Archiver} from "./archiver";

export interface IBeaconChainModules {
Expand Down Expand Up @@ -68,6 +73,7 @@ export class BeaconChain implements IBeaconChain {
readonly aggregatedAttestationPool = new AggregatedAttestationPool();
readonly syncCommitteeMessagePool = new SyncCommitteeMessagePool();
readonly syncContributionAndProofPool = new SyncContributionAndProofPool();
readonly opPool = new OpPool();

// Gossip seen cache
readonly seenAttesters = new SeenAttesters();
Expand Down Expand Up @@ -156,8 +162,15 @@ export class BeaconChain implements IBeaconChain {
this.checkpointStateCache.clear();
}

/** Populate in-memory caches with persisted data. Call at least once on startup */
async loadFromDisk(): Promise<void> {
await this.opPool.fromPersisted(this.db);
}

/** Persist in-memory data to the DB. Call at least once before stopping the process */
async persistToDisk(): Promise<void> {
await this.archiver.persistToDisk();
await this.opPool.toPersisted(this.db);
}

getGenesisTime(): Number64 {
Expand Down
4 changes: 3 additions & 1 deletion packages/lodestar/src/chain/errors/voluntaryExitError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import {GossipActionError} from "./gossipValidation";
export enum VoluntaryExitErrorCode {
ALREADY_EXISTS = "VOLUNTARY_EXIT_ERROR_ALREADY_EXISTS",
INVALID = "VOLUNTARY_EXIT_ERROR_INVALID",
INVALID_SIGNATURE = "VOLUNTARY_EXIT_ERROR_INVALID_SIGNATURE",
}
export type VoluntaryExitErrorType =
| {code: VoluntaryExitErrorCode.ALREADY_EXISTS}
| {code: VoluntaryExitErrorCode.INVALID; error: Error};
| {code: VoluntaryExitErrorCode.INVALID}
| {code: VoluntaryExitErrorCode.INVALID_SIGNATURE};

export class VoluntaryExitError extends GossipActionError<VoluntaryExitErrorType> {}
29 changes: 8 additions & 21 deletions packages/lodestar/src/chain/factory/block/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,16 @@
*/

import {List} from "@chainsafe/ssz";
import {
ForkName,
MAX_ATTESTATIONS,
MAX_ATTESTER_SLASHINGS,
MAX_PROPOSER_SLASHINGS,
MAX_VOLUNTARY_EXITS,
} from "@chainsafe/lodestar-params";
import {ForkName} from "@chainsafe/lodestar-params";
import {Bytes96, Bytes32, phase0, allForks, altair, Root, Slot} from "@chainsafe/lodestar-types";
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {CachedBeaconState} from "@chainsafe/lodestar-beacon-state-transition";

import {IBeaconDb} from "../../../db";
import {IEth1ForBlockProduction} from "../../../eth1";
import {IBeaconChain} from "../../interface";

export async function assembleBody(
{
chain,
config,
db,
eth1,
}: {chain: IBeaconChain; config: IChainForkConfig; db: IBeaconDb; eth1: IEth1ForBlockProduction},
{chain, config, eth1}: {chain: IBeaconChain; config: IChainForkConfig; eth1: IEth1ForBlockProduction},
currentState: CachedBeaconState<allForks.BeaconState>,
randaoReveal: Bytes96,
graffiti: Bytes32,
Expand All @@ -43,13 +31,12 @@ export async function assembleBody(
// }
// }

const [proposerSlashings, attesterSlashings, attestations, voluntaryExits, {eth1Data, deposits}] = await Promise.all([
db.proposerSlashing.values({limit: MAX_PROPOSER_SLASHINGS}),
db.attesterSlashing.values({limit: MAX_ATTESTER_SLASHINGS}),
chain.aggregatedAttestationPool.getAttestationsForBlock(currentState).slice(0, MAX_ATTESTATIONS),
db.voluntaryExit.values({limit: MAX_VOLUNTARY_EXITS}),
eth1.getEth1DataAndDeposits(currentState as CachedBeaconState<allForks.BeaconState>),
]);
const [attesterSlashings, proposerSlashings] = chain.opPool.getSlashings(currentState);
const voluntaryExits = chain.opPool.getVoluntaryExits(currentState);
const attestations = chain.aggregatedAttestationPool.getAttestationsForBlock(currentState);
const {eth1Data, deposits} = await eth1.getEth1DataAndDeposits(
currentState as CachedBeaconState<allForks.BeaconState>
);

const blockBodyPhase0: phase0.BeaconBlockBody = {
randaoReveal,
Expand Down
6 changes: 2 additions & 4 deletions packages/lodestar/src/chain/factory/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {CachedBeaconState, allForks} from "@chainsafe/lodestar-beacon-state-tran
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {Bytes96, Root, Slot} from "@chainsafe/lodestar-types";
import {ZERO_HASH} from "../../../constants";
import {IBeaconDb} from "../../../db";
import {IEth1ForBlockProduction} from "../../../eth1";
import {IMetrics} from "../../../metrics";
import {IBeaconChain} from "../../interface";
Expand All @@ -16,13 +15,12 @@ import {RegenCaller} from "../../regen";
type AssembleBlockModules = {
config: IChainForkConfig;
chain: IBeaconChain;
db: IBeaconDb;
eth1: IEth1ForBlockProduction;
metrics: IMetrics | null;
};

export async function assembleBlock(
{config, chain, db, eth1, metrics}: AssembleBlockModules,
{config, chain, eth1, metrics}: AssembleBlockModules,
slot: Slot,
randaoReveal: Bytes96,
graffiti = ZERO_HASH
Expand All @@ -35,7 +33,7 @@ export async function assembleBlock(
proposerIndex: state.getBeaconProposer(slot),
parentRoot: head.blockRoot,
stateRoot: ZERO_HASH,
body: await assembleBody({config, chain, db, eth1}, state, randaoReveal, graffiti, slot, {
body: await assembleBody({config, chain, eth1}, state, randaoReveal, graffiti, slot, {
parentSlot: slot - 1,
parentBlockRoot: head.blockRoot,
}),
Expand Down
6 changes: 5 additions & 1 deletion packages/lodestar/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
SeenSyncCommitteeMessages,
SeenContributionAndProof,
} from "./seenCache";
import {AttestationPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools";
import {AttestationPool, OpPool, SyncCommitteeMessagePool, SyncContributionAndProofPool} from "./opPools";
import {IForkDigestContext} from "../util/forkDigestContext";
import {LightClientIniter} from "./lightClient";
import {AggregatedAttestationPool} from "./opPools/aggregatedAttestationPool";
Expand Down Expand Up @@ -75,6 +75,7 @@ export interface IBeaconChain {
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool: SyncContributionAndProofPool;
readonly opPool: OpPool;

// Gossip seen cache
readonly seenAttesters: SeenAttesters;
Expand All @@ -85,6 +86,9 @@ export interface IBeaconChain {

/** Stop beacon chain processing */
close(): void;
/** Populate in-memory caches with persisted data. Call at least once on startup */
loadFromDisk(): Promise<void>;
/** Persist in-memory data to the DB. Call at least once before stopping the process */
persistToDisk(): Promise<void>;
getGenesisTime(): Number64;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class AggregatedAttestationPool {
}

/**
* Get attestations to be included in a block.
* Get attestations to be included in a block. Returns $MAX_ATTESTATIONS items
*/
getAttestationsForBlock(state: CachedBeaconState<allForks.BeaconState>): phase0.Attestation[] {
const stateSlot = state.slot;
Expand Down
9 changes: 5 additions & 4 deletions packages/lodestar/src/chain/opPools/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./attestationPool";
export * from "./aggregatedAttestationPool";
export * from "./syncCommitteeMessagePool";
export * from "./syncContributionAndProofPool";
export {AggregatedAttestationPool} from "./aggregatedAttestationPool";
export {AttestationPool} from "./attestationPool";
export {SyncCommitteeMessagePool} from "./syncCommitteeMessagePool";
export {SyncContributionAndProofPool} from "./syncContributionAndProofPool";
export {OpPool} from "./opPool";
Loading

0 comments on commit 6b1ff9e

Please sign in to comment.