Skip to content

Commit

Permalink
Altair 1.1.0-alpha.5 - Beacon Chain (#2554)
Browse files Browse the repository at this point in the history
* Remove SYNC_PUBKEYS_PER_AGGREGATE

* Adjust sync committee size and duration

* Use stable sync committee indices when processing block rewards

* Restrict sync committee period calculation boundaries

* Participation flag and incentive review

* Update inactivity penalty deltas processing

* Ensure indices are ordered [source, target, head] everywhere

* Map attestation participation to flag deltas in fork transition

* Fix all current altair spec tests

* Fix lint in beacon-state-transition

* Finality phase0 spec tests are removed in altair-1.1.0-alpha.5

* Remove duplicate call of getNextSyncCommitteeIndices()

* Altair-1.1.0-alpha.5 for network and validator

* Altair-1.1.0-alpha.5 for Sync Protocol

* Fix processSyncCommittee and add respective spec tests

* Add operations minimal spec tests

* Fix EpochProcess, add finality minimal spec test

* Improve the way to access aggregationBits

* Add fork and fork_choice spec tests

* Add genesis minimal spec test

* Add fork transition spec test

* SSZ spec test for altair - SyncCommittee

* Add expectEqualBeaconState() for altair spec tests

* chore: fix yarn check-types

* chore: remove debugged comment
  • Loading branch information
twoeths authored May 28, 2021
1 parent d3985e3 commit b5d6eaf
Show file tree
Hide file tree
Showing 108 changed files with 1,734 additions and 480 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {allForks} from "@chainsafe/lodestar-types";
import {toHexString} from "@chainsafe/ssz";
import {CachedBeaconState} from "../util";

export function processBlockHeader(state: CachedBeaconState<allForks.BeaconState>, block: allForks.BeaconBlock): void {
Expand Down Expand Up @@ -31,7 +32,9 @@ export function processBlockHeader(state: CachedBeaconState<allForks.BeaconState
state.config.types.phase0.BeaconBlockHeader.hashTreeRoot(state.latestBlockHeader)
)
) {
throw new Error("Block parent root does not match state latest block");
throw new Error(
`Block parent root ${toHexString(block.parentRoot)} does not match state latest block, block slot=${slot}`
);
}
// cache current block as the new latest block
state.latestBlockHeader = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import {ParticipationFlags, Uint8} from "@chainsafe/lodestar-types";
import {MutableVector, PersistentVector, TransientVector} from "@chainsafe/persistent-ts";
import {Tree} from "@chainsafe/persistent-merkle-tree";
import {unsafeUint8ArrayToTree} from "./unsafeUint8ArrayToTree";
import {TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX} from "../../altair/constants";

export interface IParticipationStatus {
timelyHead: boolean;
timelyTarget: boolean;
timelySource: boolean;
}

const TIMELY_HEAD = 1 << 0;
const TIMELY_SOURCE = 1 << 1;
const TIMELY_TARGET = 1 << 2;
/** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */
const TIMELY_SOURCE = 1 << TIMELY_SOURCE_FLAG_INDEX;
const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX;
const TIMELY_HEAD = 1 << TIMELY_HEAD_FLAG_INDEX;

export function toParticipationFlags(data: IParticipationStatus): ParticipationFlags {
return (
Expand All @@ -24,9 +26,9 @@ export function toParticipationFlags(data: IParticipationStatus): ParticipationF

export function fromParticipationFlags(flags: ParticipationFlags): IParticipationStatus {
return {
timelyHead: (TIMELY_HEAD & flags) === TIMELY_HEAD,
timelySource: (TIMELY_SOURCE & flags) === TIMELY_SOURCE,
timelyTarget: (TIMELY_TARGET & flags) === TIMELY_TARGET,
timelyHead: (TIMELY_HEAD & flags) === TIMELY_HEAD,
};
}

Expand Down
135 changes: 109 additions & 26 deletions packages/beacon-state-transition/src/allForks/util/epochContext.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import {ByteVector, hash, toHexString, BitList, List, readonlyValues} from "@chainsafe/ssz";
import {ByteVector, hash, toHexString, BitList, List, isTreeBacked, TreeBacked} from "@chainsafe/ssz";
import bls, {CoordType, PublicKey} from "@chainsafe/bls";
import {BLSSignature, CommitteeIndex, Epoch, Slot, ValidatorIndex, phase0, allForks} from "@chainsafe/lodestar-types";
import {
BLSSignature,
CommitteeIndex,
Epoch,
Slot,
ValidatorIndex,
phase0,
allForks,
altair,
Gwei,
} from "@chainsafe/lodestar-types";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {intToBytes} from "@chainsafe/lodestar-utils";
import {bigIntSqrt, intToBytes} from "@chainsafe/lodestar-utils";

import {GENESIS_EPOCH} from "../../constants";
import {
computeEpochAtSlot,
computeProposerIndex,
computeStartSlotAtEpoch,
getAttestingIndicesFromCommittee,
getSeed,
getTotalActiveBalance,
isAggregatorFromCommitteeLength,
zipIndexesInBitList,
} from "../../util";
import {getSyncCommitteeIndices} from "../../altair/state_accessor/sync_committee";
import {getNextSyncCommitteeIndices} from "../../altair/state_accessor/sync_committee";
import {computeEpochShuffling, IEpochShuffling} from "./epochShuffling";
import {MutableVector} from "@chainsafe/persistent-ts";
import {CachedValidatorList} from "./cachedValidatorList";
import {PROPOSER_WEIGHT, SYNC_REWARD_WEIGHT, WEIGHT_DENOMINATOR} from "../../altair/constants";

export type EpochContextOpts = {
pubkey2index?: PubkeyIndexMap;
Expand Down Expand Up @@ -85,9 +97,17 @@ export function createEpochContext(

// Only after altair, compute the indices of the current sync committee
const onAltairFork = currentEpoch >= config.params.ALTAIR_FORK_EPOCH;
const nextPeriodEpoch = currentEpoch + config.params.EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
const currSyncCommitteeIndexes = onAltairFork ? getSyncCommitteeIndices(config, state, currentEpoch) : [];
const nextSyncCommitteeIndexes = onAltairFork ? getSyncCommitteeIndices(config, state, nextPeriodEpoch) : [];
const currSyncCommitteeIndexes = onAltairFork
? computeSyncCommitteeIndices(pubkey2index, state as altair.BeaconState, false)
: [];
const nextSyncCommitteeIndexes = onAltairFork
? computeSyncCommitteeIndices(pubkey2index, state as altair.BeaconState, true)
: [];

const syncParticipantReward = onAltairFork ? computeSyncParticipantReward(config, state) : BigInt(0);
const syncProposerReward = onAltairFork
? (syncParticipantReward * PROPOSER_WEIGHT) / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)
: BigInt(0);

return new EpochContext({
config,
Expand All @@ -101,6 +121,8 @@ export function createEpochContext(
nextSyncCommitteeIndexes,
currSyncComitteeValidatorIndexMap: computeSyncComitteeMap(currSyncCommitteeIndexes),
nextSyncComitteeValidatorIndexMap: computeSyncComitteeMap(nextSyncCommitteeIndexes),
syncParticipantReward,
syncProposerReward,
});
}

Expand Down Expand Up @@ -156,6 +178,7 @@ export function computeProposers(
/**
* Compute all index in sync committee for all validatorIndexes in `syncCommitteeIndexes`.
* Helps reduce work necessary to verify a validatorIndex belongs in a sync committee and which.
* This is similar to compute_subnets_for_sync_committee in https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/validator.md
*/
export function computeSyncComitteeMap(syncCommitteeIndexes: ValidatorIndex[]): SyncComitteeValidatorIndexMap {
const map = new Map<ValidatorIndex, number[]>();
Expand All @@ -167,12 +190,47 @@ export function computeSyncComitteeMap(syncCommitteeIndexes: ValidatorIndex[]):
indexes = [];
map.set(validatorIndex, indexes);
}
indexes.push(i);
if (!indexes.includes(i)) {
indexes.push(i);
}
}

return map;
}

/**
* Extract validator indices from current and next sync committee
*/
export function computeSyncCommitteeIndices(
pubkey2index: PubkeyIndexMap,
state: altair.BeaconState,
isNext: boolean
): phase0.ValidatorIndex[] {
const syncCommittee = isNext ? state.nextSyncCommittee : state.currentSyncCommittee;
const result: phase0.ValidatorIndex[] = [];
for (const pubkey of syncCommittee.pubkeys) {
const validatorIndex = pubkey2index.get(pubkey.valueOf() as Uint8Array);
if (validatorIndex !== undefined) {
result.push(validatorIndex);
}
}
return result;
}

/**
* Same logic in https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#sync-committee-processing
*/
export function computeSyncParticipantReward(config: IBeaconConfig, state: allForks.BeaconState): Gwei {
const {EFFECTIVE_BALANCE_INCREMENT, BASE_REWARD_FACTOR, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SIZE} = config.params;
const totalActiveBalance = getTotalActiveBalance(config, state);
const totalActiveIncrements = totalActiveBalance / EFFECTIVE_BALANCE_INCREMENT;
const baseRewardPerIncrement =
(EFFECTIVE_BALANCE_INCREMENT * BigInt(BASE_REWARD_FACTOR)) / bigIntSqrt(totalActiveBalance);
const totalBaseRewards = baseRewardPerIncrement * totalActiveIncrements;
const maxParticipantRewards = (totalBaseRewards * SYNC_REWARD_WEIGHT) / WEIGHT_DENOMINATOR / BigInt(SLOTS_PER_EPOCH);
return maxParticipantRewards / BigInt(SYNC_COMMITTEE_SIZE);
}

/**
* Called to re-use information, such as the shuffling of the next epoch, after transitioning into a
* new epoch.
Expand All @@ -199,21 +257,26 @@ export function rotateEpochs(
currEpoch % epochCtx.config.params.EPOCHS_PER_SYNC_COMMITTEE_PERIOD === 0 &&
currEpoch > epochCtx.config.params.ALTAIR_FORK_EPOCH
) {
const nextPeriodEpoch = currEpoch + epochCtx.config.params.EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
epochCtx.currSyncCommitteeIndexes = epochCtx.nextSyncCommitteeIndexes;
epochCtx.nextSyncCommitteeIndexes = getSyncCommitteeIndices(epochCtx.config, state, nextPeriodEpoch);
epochCtx.nextSyncCommitteeIndexes = getNextSyncCommitteeIndices(epochCtx.config, state);
epochCtx.currSyncComitteeValidatorIndexMap = epochCtx.nextSyncComitteeValidatorIndexMap;
epochCtx.nextSyncComitteeValidatorIndexMap = computeSyncComitteeMap(epochCtx.nextSyncCommitteeIndexes);
}

// If crossing through the altair fork the caches will be empty, fill them up
if (currEpoch === epochCtx.config.params.ALTAIR_FORK_EPOCH) {
const nextPeriodEpoch = currEpoch + epochCtx.config.params.EPOCHS_PER_SYNC_COMMITTEE_PERIOD;
epochCtx.currSyncCommitteeIndexes = getSyncCommitteeIndices(epochCtx.config, state, currEpoch);
epochCtx.nextSyncCommitteeIndexes = getSyncCommitteeIndices(epochCtx.config, state, nextPeriodEpoch);
const firstCommitteeIndices = getNextSyncCommitteeIndices(epochCtx.config, state);
epochCtx.currSyncCommitteeIndexes = [...firstCommitteeIndices];
epochCtx.nextSyncCommitteeIndexes = [...firstCommitteeIndices];
epochCtx.currSyncComitteeValidatorIndexMap = computeSyncComitteeMap(epochCtx.currSyncCommitteeIndexes);
epochCtx.nextSyncComitteeValidatorIndexMap = computeSyncComitteeMap(epochCtx.nextSyncCommitteeIndexes);
}

if (currEpoch >= epochCtx.config.params.ALTAIR_FORK_EPOCH) {
epochCtx.syncParticipantReward = computeSyncParticipantReward(epochCtx.config, state);
epochCtx.syncProposerReward =
(epochCtx.syncParticipantReward * PROPOSER_WEIGHT) / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT);
}
}

type SyncComitteeValidatorIndexMap = Map<ValidatorIndex, number[]>;
Expand All @@ -229,6 +292,8 @@ interface IEpochContextData {
nextSyncCommitteeIndexes: ValidatorIndex[];
currSyncComitteeValidatorIndexMap: SyncComitteeValidatorIndexMap;
nextSyncComitteeValidatorIndexMap: SyncComitteeValidatorIndexMap;
syncParticipantReward: Gwei;
syncProposerReward: Gwei;
}

/**
Expand All @@ -251,17 +316,19 @@ export class EpochContext {
currentShuffling: IEpochShuffling;
nextShuffling: IEpochShuffling;
/**
* Update freq: every ~ 27h.
* Update freq: every ~ 54h.
* Memory cost: 1024 Number integers.
*/
currSyncCommitteeIndexes: ValidatorIndex[];
nextSyncCommitteeIndexes: ValidatorIndex[];
/**
* Update freq: every ~ 27h.
* Update freq: every ~ 54h.
* Memory cost: Map of Number -> Number with 1024 entries.
*/
currSyncComitteeValidatorIndexMap: SyncComitteeValidatorIndexMap;
nextSyncComitteeValidatorIndexMap: SyncComitteeValidatorIndexMap;
syncParticipantReward: phase0.Gwei;
syncProposerReward: phase0.Gwei;
config: IBeaconConfig;

constructor(data: IEpochContextData) {
Expand All @@ -276,6 +343,8 @@ export class EpochContext {
this.nextSyncCommitteeIndexes = data.nextSyncCommitteeIndexes;
this.currSyncComitteeValidatorIndexMap = data.currSyncComitteeValidatorIndexMap;
this.nextSyncComitteeValidatorIndexMap = data.nextSyncComitteeValidatorIndexMap;
this.syncParticipantReward = data.syncParticipantReward;
this.syncProposerReward = data.syncProposerReward;
}

/**
Expand Down Expand Up @@ -317,14 +386,21 @@ export class EpochContext {
* Return the indexed attestation corresponding to ``attestation``.
*/
getIndexedAttestation(attestation: phase0.Attestation): phase0.IndexedAttestation {
const data = attestation.data;
const bits = Array.from(readonlyValues(attestation.aggregationBits));
const committee = this.getBeaconCommittee(data.slot, data.index);
// No need for a Set, the indices in the committee are already unique.
const attestingIndices: ValidatorIndex[] = [];
for (const [i, index] of committee.entries()) {
if (bits[i]) {
attestingIndices.push(index);
const {aggregationBits, data} = attestation;
const committeeIndices = this.getBeaconCommittee(data.slot, data.index);
let attestingIndices: phase0.ValidatorIndex[];
if (isTreeBacked(attestation)) {
attestingIndices = zipIndexesInBitList(
committeeIndices,
(attestation.aggregationBits as unknown) as TreeBacked<BitList>,
this.config.types.phase0.CommitteeBits
);
} else {
attestingIndices = [];
for (const [i, index] of committeeIndices.entries()) {
if (aggregationBits[i]) {
attestingIndices.push(index);
}
}
}
// sort in-place
Expand All @@ -337,8 +413,15 @@ export class EpochContext {
}

getAttestingIndices(data: phase0.AttestationData, bits: BitList): ValidatorIndex[] {
const committee = this.getBeaconCommittee(data.slot, data.index);
return getAttestingIndicesFromCommittee(committee, Array.from(readonlyValues(bits)) as List<boolean>);
const committeeIndices = this.getBeaconCommittee(data.slot, data.index);
const validatorIndices = isTreeBacked(bits)
? zipIndexesInBitList(
committeeIndices,
(bits as unknown) as TreeBacked<BitList>,
this.config.types.phase0.CommitteeBits
)
: committeeIndices.filter((_, index) => !!bits[index]);
return validatorIndices;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ export function prepareEpochProcessState<T extends allForks.BeaconState>(state:
const effectiveBalance = out.validators[i].effectiveBalance;
if (hasMarkers(status.flags, FLAG_PREV_SOURCE_ATTESTER | FLAG_UNSLASHED)) {
prevSourceUnslStake += effectiveBalance;
if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER)) {
prevTargetUnslStake += effectiveBalance;
if (hasMarkers(status.flags, FLAG_PREV_HEAD_ATTESTER)) {
prevHeadUnslStake += effectiveBalance;
}
}
}
if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER | FLAG_UNSLASHED)) {
prevTargetUnslStake += effectiveBalance;
}
if (hasMarkers(status.flags, FLAG_PREV_HEAD_ATTESTER | FLAG_UNSLASHED)) {
prevHeadUnslStake += effectiveBalance;
}
if (hasMarkers(status.flags, FLAG_CURR_TARGET_ATTESTER | FLAG_UNSLASHED)) {
currTargetUnslStake += effectiveBalance;
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-state-transition/src/altair/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export {
processDeposit,
processProposerSlashing,
processVoluntaryExit,
processSyncCommittee,
};

export function processBlock(
Expand Down
Loading

0 comments on commit b5d6eaf

Please sign in to comment.