Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {processAttestation} from "./processAttestation";
import {processAttesterSlashing} from "./processAttesterSlashing";
import {processDeposit} from "./processDeposit";
import {processProposerSlashing} from "./processProposerSlashing";
import {processVoluntaryExit} from "./processVoluntaryExit";
import {processVoluntaryExits} from "./processVoluntaryExit";

export {
processBlockHeader,
Expand All @@ -20,7 +20,7 @@ export {
processAttesterSlashing,
processDeposit,
processProposerSlashing,
processVoluntaryExit,
processVoluntaryExits,
};

export function processBlock(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {readOnlyMap} from "@chainsafe/ssz";
import {BeaconState, ValidatorIndex} from "@chainsafe/lodestar-types";
import {readOnlyForEach, readOnlyMap} from "@chainsafe/ssz";
import {BeaconState, Validator, ValidatorIndex} from "@chainsafe/lodestar-types";

import {FAR_FUTURE_EPOCH} from "../../constants";
import {computeActivationExitEpoch, getChurnLimit} from "../../util";
import {EpochContext} from "../util";

/**
* Initiate a single validator exit, similar to the spec, for backward reference only.
*/
export function initiateValidatorExit(epochCtx: EpochContext, state: BeaconState, index: ValidatorIndex): void {
const config = epochCtx.config;
// return if validator already initiated exit
Expand All @@ -29,3 +32,46 @@ export function initiateValidatorExit(epochCtx: EpochContext, state: BeaconState
validator.exitEpoch = exitQueueEpoch;
validator.withdrawableEpoch = validator.exitEpoch + config.params.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
}

/**
* Optimized version of initiateValidatorExit where we process validators in batch.
* The main thing is to loop through state.validators exactly 1 time.
*/
export function initiateMultipleValidatorExits(
epochCtx: EpochContext,
state: BeaconState,
indexes: ValidatorIndex[] = []
): void {
if (!indexes || indexes.length === 0) return;
const config = epochCtx.config;
// compute exit queue epoch
const validatorExitEpochs = [];
const exitEpochs = [];
readOnlyForEach(state.validators, (v: Validator) => {
validatorExitEpochs.push(v.exitEpoch);
if (v.exitEpoch !== FAR_FUTURE_EPOCH) {
exitEpochs.push(v.exitEpoch);
}
});
const churnLimit = getChurnLimit(config, epochCtx.currentShuffling.activeIndices.length);
const currentEpoch = epochCtx.currentShuffling.epoch;
const activationExitEpoch = computeActivationExitEpoch(config, currentEpoch);
for (const index of indexes) {
// continue if validator already initiated exit
const validator = state.validators[index];
if (validator.exitEpoch !== FAR_FUTURE_EPOCH) {
continue;
}
let exitQueueEpoch = Math.max(...exitEpochs, activationExitEpoch);
const exitQueueChurn = validatorExitEpochs.filter((exitEpoch) => exitEpoch === exitQueueEpoch).length;
if (exitQueueChurn >= churnLimit) {
exitQueueEpoch += 1;
}

// set validator exit epoch and withdrawable epoch
validator.exitEpoch = exitQueueEpoch;
validator.withdrawableEpoch = exitQueueEpoch + config.params.MIN_VALIDATOR_WITHDRAWABILITY_DELAY;
validatorExitEpochs[index] = exitQueueEpoch;
exitEpochs.push(exitQueueEpoch);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {processProposerSlashing} from "./processProposerSlashing";
import {processAttesterSlashing} from "./processAttesterSlashing";
import {processAttestation} from "./processAttestation";
import {processDeposit} from "./processDeposit";
import {processVoluntaryExit} from "./processVoluntaryExit";
import {processVoluntaryExits} from "./processVoluntaryExit";

type Operation = ProposerSlashing | AttesterSlashing | Attestation | Deposit | VoluntaryExit;
type OperationFunction = (epochCtx: EpochContext, state: BeaconState, op: Operation, verify: boolean) => void;
Expand All @@ -41,10 +41,11 @@ export function processOperations(
[body.attesterSlashings, processAttesterSlashing],
[body.attestations, processAttestation],
[body.deposits, processDeposit],
[body.voluntaryExits, processVoluntaryExit],
] as [List<Operation>, OperationFunction][]).forEach(([operations, processOp]) => {
readOnlyForEach(operations, (op) => {
processOp(epochCtx, state, op, verifySignatures);
});
});
// process voluntary exits in batch to optimize performance
processVoluntaryExits(epochCtx, state, body.voluntaryExits, verifySignatures);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,52 @@
import {Signature} from "@chainsafe/bls";
import {BeaconState, SignedVoluntaryExit} from "@chainsafe/lodestar-types";
import {BeaconState, SignedVoluntaryExit, ValidatorIndex} from "@chainsafe/lodestar-types";
import {List, readOnlyForEach} from "@chainsafe/ssz";

import {DomainType, FAR_FUTURE_EPOCH} from "../../constants";
import {computeSigningRoot, getDomain, isActiveValidator} from "../../util";
import {EpochContext} from "../util";
import {initiateValidatorExit} from "./initiateValidatorExit";
import {initiateMultipleValidatorExits, initiateValidatorExit} from "./initiateValidatorExit";

/**
* Process a single voluntary exit, for backward reference only.
*/
export function processVoluntaryExit(
epochCtx: EpochContext,
state: BeaconState,
signedVoluntaryExit: SignedVoluntaryExit,
verifySignature = true
): void {
verifyVoluntaryExit(epochCtx, state, signedVoluntaryExit, verifySignature);
// initiate exit
const voluntaryExit = signedVoluntaryExit.message;
initiateValidatorExit(epochCtx, state, voluntaryExit.validatorIndex);
}

/**
* Process voluntary exits in batch
*/
export function processVoluntaryExits(
epochCtx: EpochContext,
state: BeaconState,
signedVoluntaryExits: List<SignedVoluntaryExit>,
verifySignature = true
): void {
const indexes = new Set<ValidatorIndex>();
readOnlyForEach(signedVoluntaryExits, (signedVoluntaryExit) => {
verifyVoluntaryExit(epochCtx, state, signedVoluntaryExit, verifySignature);
indexes.add(signedVoluntaryExit.message.validatorIndex);
});
initiateMultipleValidatorExits(epochCtx, state, Array.from(indexes));
}

/**
* The same to processVoluntaryExit without calling initiateValidatorExit.
*/
function verifyVoluntaryExit(
epochCtx: EpochContext,
state: BeaconState,
signedVoluntaryExit: SignedVoluntaryExit,
verifySignature = true
): void {
const config = epochCtx.config;
const voluntaryExit = signedVoluntaryExit.message;
Expand Down Expand Up @@ -48,6 +84,4 @@ export function processVoluntaryExit(
throw new Error("VoluntaryExit has an invalid signature");
}
}
// initiate exit
initiateValidatorExit(epochCtx, state, voluntaryExit.validatorIndex);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {BeaconState, ValidatorIndex} from "@chainsafe/lodestar-types";

import {decreaseBalance, increaseBalance} from "../../util";
import {EpochContext} from "../util";
import {initiateValidatorExit} from "./initiateValidatorExit";
import {initiateMultipleValidatorExits} from "./initiateValidatorExit";

export function slashValidator(
epochCtx: EpochContext,
Expand All @@ -17,7 +17,7 @@ export function slashValidator(
PROPOSER_REWARD_QUOTIENT,
} = epochCtx.config.params;
const epoch = epochCtx.currentShuffling.epoch;
initiateValidatorExit(epochCtx, state, slashedIndex);
initiateMultipleValidatorExits(epochCtx, state, [slashedIndex]);
const validator = state.validators[slashedIndex];
validator.slashed = true;
validator.withdrawableEpoch = Math.max(validator.withdrawableEpoch, epoch + EPOCHS_PER_SLASHINGS_VECTOR);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {join} from "path";
import {expect} from "chai";
import {BeaconState} from "@chainsafe/lodestar-types";
import {BeaconState, SignedVoluntaryExit} from "@chainsafe/lodestar-types";
import {config} from "@chainsafe/lodestar-config/lib/presets/mainnet";
import {EpochContext} from "@chainsafe/lodestar-beacon-state-transition";
import {processVoluntaryExit} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block";
import {processVoluntaryExits} from "@chainsafe/lodestar-beacon-state-transition/lib/fast/block";
import {describeDirectorySpecTest} from "@chainsafe/lodestar-spec-test-util/lib/single";
import {IProcessVoluntaryExitTestCase} from "./type";
import {SPEC_TEST_LOCATION} from "../../../utils/specTestCases";
import {List} from "@chainsafe/ssz";

describeDirectorySpecTest<IProcessVoluntaryExitTestCase, BeaconState>(
"process voluntary exit mainnet",
Expand All @@ -15,7 +16,7 @@ describeDirectorySpecTest<IProcessVoluntaryExitTestCase, BeaconState>(
const state = testcase.pre;
const epochCtx = new EpochContext(config);
epochCtx.loadState(state);
processVoluntaryExit(epochCtx, state, testcase.voluntary_exit);
processVoluntaryExits(epochCtx, state, [testcase.voluntary_exit] as List<SignedVoluntaryExit>);
return state;
},
{
Expand Down