Skip to content

Commit

Permalink
Altair sim tests (#2529)
Browse files Browse the repository at this point in the history
* Fix multiple issues for SyncCommittee integration

* Add upgradeState for altair

* Simplify handling SyncDutyAndProof for multiple subnets

* Multiplex BeaconBlock types everywhere

* Multiplex BeaconState types everywhere but the stfn

* Update multifork state transition

* Run Altair from epoch 0

* Fix signature verification for altair blocks

* Fix getCurrentAndNextFork

* Fix altair genesis

* Fix getGenesis API

* Fix genesis applyDeposits

* Use subnet service start/stop

* Fix lodestar unit tests

* Fix e2e + spec tests

* Fix lint

* Add sim tests with ALTAIR_FORK_EPOCH=1

* Fix validator unit tests

* Impl translate_participation when upgrading beacon state from phase0 to altair

* Fix upgradeState

Co-authored-by: dapplion <35266934+dapplion@users.noreply.github.com>
Co-authored-by: Cayman <caymannava@gmail.com>
  • Loading branch information
3 people authored May 18, 2021
1 parent 1229e29 commit 7b70ab6
Show file tree
Hide file tree
Showing 82 changed files with 586 additions and 486 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ export function getProposerSlashingSignatureSets(
(signedHeader): ISignatureSet => {
const epoch = computeEpochAtSlot(config, signedHeader.message.slot);
const domain = getDomain(config, state, config.params.DOMAIN_BEACON_PROPOSER, epoch);
const beaconBlockHeaderType = config.getTypes(signedHeader.message.slot).BeaconBlockHeader;

return {
type: SignatureSetType.single,
pubkey,
signingRoot: computeSigningRoot(config, config.types.phase0.BeaconBlockHeader, signedHeader.message, domain),
signingRoot: computeSigningRoot(config, beaconBlockHeaderType, signedHeader.message, domain),
signature: signedHeader.signature.valueOf() as Uint8Array,
};
}
Expand Down
24 changes: 24 additions & 0 deletions packages/beacon-state-transition/src/allForks/slot/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {allForks} from "@chainsafe/lodestar-types";
import {CachedBeaconState} from "../util";
import {ZERO_HASH} from "../../constants";

/**
* Dial state to next slot. Common for all forks
*/
export function processSlot(state: CachedBeaconState<allForks.BeaconState>): void {
const {config} = state;
const types = config.getTypes(state.slot);

// Cache state root
const previousStateRoot = types.BeaconState.hashTreeRoot(state);
state.stateRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousStateRoot;

// Cache latest block header state root
if (types.Root.equals(state.latestBlockHeader.stateRoot, ZERO_HASH)) {
state.latestBlockHeader.stateRoot = previousStateRoot;
}

// Cache block root
const previousBlockRoot = types.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader);
state.blockRoots[state.slot % config.params.SLOTS_PER_HISTORICAL_ROOT] = previousBlockRoot;
}
155 changes: 71 additions & 84 deletions packages/beacon-state-transition/src/allForks/stateTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ import * as phase0 from "../phase0";
import * as altair from "../altair";
import {IBeaconStateTransitionMetrics} from "../metrics";
import {verifyProposerSignature} from "./signatureSets";
import {CachedBeaconState} from "./util";

type StateTransitionFunctions = {
processSlots(
state: CachedBeaconState<allForks.BeaconState>,
slot: Slot,
metrics?: IBeaconStateTransitionMetrics | null
): CachedBeaconState<allForks.BeaconState>;
upgradeState(state: CachedBeaconState<allForks.BeaconState>): CachedBeaconState<allForks.BeaconState>;
import {CachedBeaconState, rotateEpochs} from "./util";
import {processSlot} from "./slot";
import {computeEpochAtSlot} from "../util";
import {GENESIS_EPOCH} from "../constants";

type StateAllForks = CachedBeaconState<allForks.BeaconState>;
type StatePhase0 = CachedBeaconState<phase0.BeaconState>;

type ProcessBlockFn = (state: StateAllForks, block: allForks.BeaconBlock, verifySignatures: boolean) => void;
type ProcessEpochFn = (state: StateAllForks) => CachedBeaconState<allForks.BeaconState>;

const processBlockByFork: Record<ForkName, ProcessBlockFn> = {
[ForkName.phase0]: phase0.processBlock as ProcessBlockFn,
[ForkName.altair]: altair.processBlock as ProcessBlockFn,
};

/**
* Record of fork to state transition functions
*/
const implementations: Record<ForkName, StateTransitionFunctions> = {
[ForkName.phase0]: (phase0 as unknown) as StateTransitionFunctions,
[ForkName.altair]: (altair as unknown) as StateTransitionFunctions,
const processEpochByFork: Record<ForkName, ProcessEpochFn> = {
[ForkName.phase0]: phase0.processEpoch as ProcessEpochFn,
[ForkName.altair]: altair.processEpoch as ProcessEpochFn,
};

// Multifork capable state transition
Expand All @@ -43,30 +45,31 @@ export function stateTransition(

let postState = state.clone();

// Turn caches into a data-structure optimized for fast writes
postState.setStateCachesAsTransient();

// process slots (including those with no blocks) since block
// includes state upgrades
postState = _processSlots(postState, blockSlot, metrics);
// Process slots (including those with no blocks) since block.
// Includes state upgrades
postState = processSlotsWithTransientCache(postState, blockSlot, metrics);

// verify signature
// Verify proposer signature only
if (verifyProposer) {
if (!verifyProposerSignature(postState, signedBlock)) {
throw new Error("Invalid block signature");
}
}

// process block

// Process block
processBlock(postState, block, options, metrics);

// verify state root
// Verify state root
if (verifyStateRoot) {
if (!config.types.Root.equals(block.stateRoot, postState.tree.root)) {
throw new Error("Invalid state root");
}
}

// Turn caches into a data-structure optimized for hashing and structural sharing
postState.setStateCachesAsPersistent();

return postState;
Expand All @@ -84,26 +87,13 @@ export function processBlock(
metrics?: IBeaconStateTransitionMetrics | null
): void {
const {verifySignatures = true} = options || {};
const blockFork = postState.config.getForkName(block.slot);

switch (blockFork) {
case ForkName.phase0:
phase0.processBlock(
postState as CachedBeaconState<phase0.BeaconState>,
block as phase0.BeaconBlock,
verifySignatures,
metrics
);
break;
case ForkName.altair:
altair.processBlock(
postState as CachedBeaconState<altair.BeaconState>,
block as altair.BeaconBlock,
verifySignatures
);
break;
default:
throw new Error(`Block processing not implemented for fork ${blockFork}`);
const fork = postState.config.getForkName(block.slot);

const timer = metrics?.stfnProcessBlock.startTimer();
try {
processBlockByFork[fork](postState, block, verifySignatures);
} finally {
if (timer) timer();
}
}

Expand All @@ -119,60 +109,57 @@ export function processSlots(
): CachedBeaconState<allForks.BeaconState> {
let postState = state.clone();

// Turn caches into a data-structure optimized for fast writes
postState.setStateCachesAsTransient();

postState = _processSlots(postState, slot, metrics);
postState = processSlotsWithTransientCache(postState, slot, metrics);

// Turn caches into a data-structure optimized for hashing and structural sharing
postState.setStateCachesAsPersistent();

return postState;
}

// eslint-disable-next-line @typescript-eslint/naming-convention
function _processSlots(
state: CachedBeaconState<allForks.BeaconState>,
/**
* All processSlot() logic but separate so stateTransition() can recycle the caches
*/
function processSlotsWithTransientCache(
postState: StateAllForks,
slot: Slot,
metrics?: IBeaconStateTransitionMetrics | null
): CachedBeaconState<allForks.BeaconState> {
let postState = state;
const {config} = state;
const preSlot = state.slot;

// forks sorted in order
const forkInfos = Object.values(config.forks);
// for each fork
for (let i = 0; i < forkInfos.length; i++) {
const currentForkInfo = forkInfos[i];
const nextForkInfo = forkInfos[i + 1];

const impl = implementations[currentForkInfo.name];
if (!impl) {
throw new Error(`Slot processing not implemented for fork ${currentForkInfo.name}`);
}
// if there's no next fork, process slots without worrying about fork upgrades and exit
if (!nextForkInfo) {
impl.processSlots(postState, slot);
break;
}
const nextForkStartSlot = config.params.SLOTS_PER_EPOCH * nextForkInfo.epoch;
): StateAllForks {
const {config} = postState;
if (postState.slot > slot) {
throw Error(`Too old slot ${slot}, current=${postState.slot}`);
}

// if the starting state slot is after the current fork, skip to the next fork
if (preSlot > nextForkStartSlot) {
continue;
while (postState.slot < slot) {
processSlot(postState);

// Process epoch on the first slot of the next epoch
if ((postState.slot + 1) % config.params.SLOTS_PER_EPOCH === 0) {
const fork = postState.config.getForkName(postState.slot + 1);
const timer = metrics?.stfnEpochTransition.startTimer();
try {
processEpochByFork[fork](postState);
postState.slot++;
rotateEpochs(postState.epochCtx, postState, postState.validators);
} finally {
if (timer) timer();
}

// Upgrade state if exactly at epoch boundary
switch (computeEpochAtSlot(config, postState.slot)) {
case GENESIS_EPOCH:
break; // Don't do any upgrades at genesis epoch
case config.params.ALTAIR_FORK_EPOCH:
postState = altair.upgradeState(postState as StatePhase0) as StateAllForks;
break;
}
} else {
postState.slot++;
}
// if the requested slot is not after the next fork, process slots and exit
if (slot < nextForkStartSlot) {
impl.processSlots(postState, slot, metrics);
break;
}
const nextImpl = implementations[currentForkInfo.name];
if (!nextImpl) {
throw new Error(`Slot processing not implemented for fork ${nextForkInfo.name}`);
}
// else (the requested slot is equal or after the next fork), process up to the fork
impl.processSlots(postState, nextForkStartSlot);

postState = nextImpl.upgradeState(postState);
}

return postState;
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ export function processAttestation(
if (!isMatchingSource) {
throw new Error(
"Attestation source does not equal justified checkpoint: " +
`source=${config.types.phase0.Checkpoint.toJson(data.source)} ` +
`justifiedCheckpoint=${config.types.phase0.Checkpoint.toJson(justifiedCheckpoint)}`
`source=${JSON.stringify(config.types.phase0.Checkpoint.toJson(data.source))} ` +
`justifiedCheckpoint=${JSON.stringify(config.types.phase0.Checkpoint.toJson(justifiedCheckpoint))}`
);
}
// this check is done last because its the most expensive (if signature verification is toggled on)
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-state-transition/src/altair/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// fast
export * from "./block";
export * from "./epoch";
export * from "./slot";
export * from "./upgradeState";
// naive
export * as naive from "../naive/altair";
export * from "./state_accessor";
Expand Down
38 changes: 0 additions & 38 deletions packages/beacon-state-transition/src/altair/slot.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function getFlagIndexDeltas(
for (const index of naive.phase0.getEligibleValidatorIndices(config, (state as unknown) as phase0.BeaconState)) {
const baseReward = getBaseReward(config, state, index);
if (unslashedParticipatingIndices.indexOf(index) !== -1) {
if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) {
if (isInInactivityLeak(config, state)) {
// This flag reward cancels the inactivity penalty corresponding to the flag index
rewards[index] += (baseReward * weight) / WEIGHT_DENOMINATOR;
} else {
Expand All @@ -60,7 +60,7 @@ export function getInactivityPenaltyDeltas(config: IBeaconConfig, state: altair.
const penalties = newZeroedBigIntArray(validatorCount);
const previousEpoch = getPreviousEpoch(config, state);

if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) {
if (isInInactivityLeak(config, state)) {
const matchingTargetAttestingIndices = getUnslashedParticipatingIndices(
config,
state,
Expand Down
Loading

0 comments on commit 7b70ab6

Please sign in to comment.