Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Altair 1.1.0-alpha.5 - Beacon Chain #2554

Merged
merged 25 commits into from
May 28, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9c18ef2
Remove SYNC_PUBKEYS_PER_AGGREGATE
twoeths May 21, 2021
e73acc8
Adjust sync committee size and duration
twoeths May 21, 2021
9672ee1
Use stable sync committee indices when processing block rewards
twoeths May 21, 2021
30e0900
Restrict sync committee period calculation boundaries
twoeths May 21, 2021
5ee16f0
Participation flag and incentive review
twoeths May 21, 2021
e9e0673
Update inactivity penalty deltas processing
twoeths May 21, 2021
f0fff5a
Ensure indices are ordered [source, target, head] everywhere
twoeths May 21, 2021
712c8bd
Map attestation participation to flag deltas in fork transition
twoeths May 21, 2021
fe4cd2c
Fix all current altair spec tests
twoeths May 23, 2021
b324594
Fix lint in beacon-state-transition
twoeths May 24, 2021
8c38e51
Finality phase0 spec tests are removed in altair-1.1.0-alpha.5
twoeths May 24, 2021
81f01ae
Remove duplicate call of getNextSyncCommitteeIndices()
twoeths May 25, 2021
3f40221
Altair-1.1.0-alpha.5 for network and validator
twoeths May 25, 2021
b8f3339
Altair-1.1.0-alpha.5 for Sync Protocol
twoeths May 25, 2021
5604160
Fix processSyncCommittee and add respective spec tests
twoeths May 26, 2021
bc5b470
Add operations minimal spec tests
twoeths May 26, 2021
18a8c96
Fix EpochProcess, add finality minimal spec test
twoeths May 27, 2021
4a92026
Improve the way to access aggregationBits
twoeths May 27, 2021
30f5512
Add fork and fork_choice spec tests
twoeths May 27, 2021
cc659de
Add genesis minimal spec test
twoeths May 27, 2021
7a32963
Add fork transition spec test
twoeths May 28, 2021
16ea02f
SSZ spec test for altair - SyncCommittee
twoeths May 28, 2021
d056d4a
Add expectEqualBeaconState() for altair spec tests
twoeths May 28, 2021
ca63a52
chore: fix yarn check-types
twoeths May 28, 2021
a894416
chore: remove debugged comment
twoeths May 28, 2021
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 @@ -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
25 changes: 13 additions & 12 deletions packages/beacon-state-transition/src/allForks/util/epochContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getSeed,
isAggregatorFromCommitteeLength,
} 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";
Expand Down Expand Up @@ -72,9 +72,8 @@ 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 ? getNextSyncCommitteeIndices(config, state) : [];
const nextSyncCommitteeIndexes = onAltairFork ? getNextSyncCommitteeIndices(config, state) : [];

return new EpochContext({
config,
Expand Down Expand Up @@ -143,6 +142,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 @@ -154,7 +154,9 @@ export function computeSyncComitteeMap(syncCommitteeIndexes: ValidatorIndex[]):
indexes = [];
map.set(validatorIndex, indexes);
}
indexes.push(i);
if (!indexes.includes(i)) {
indexes.push(i);
}
}

return map;
Expand Down Expand Up @@ -186,18 +188,17 @@ 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);
}
Expand Down Expand Up @@ -238,13 +239,13 @@ 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
WEIGHT_DENOMINATOR,
} from "../constants";
import {getBaseReward} from "../state_accessor";
import {intSqrt} from "@chainsafe/lodestar-utils";

export function processAttestation(
state: CachedBeaconState<altair.BeaconState>,
Expand Down Expand Up @@ -59,29 +60,13 @@ export function processAttestation(
);
}

let epochParticipation, justifiedCheckpoint;
let epochParticipation;
if (data.target.epoch === epochCtx.currentShuffling.epoch) {
epochParticipation = state.currentEpochParticipation;
justifiedCheckpoint = state.currentJustifiedCheckpoint;
} else {
epochParticipation = state.previousEpochParticipation;
justifiedCheckpoint = state.previousJustifiedCheckpoint;
}

// The source and target votes are part of the FFG vote, the head vote is part of the fork choice vote
// Both are tracked to properly incentivise validators
//
// The source vote always matches the justified checkpoint (else its invalid)
// The target vote should match the most recent checkpoint (eg: the first root of the epoch)
// The head vote should match the root at the attestation slot (eg: the root at data.slot)
const isMatchingSource = config.types.phase0.Checkpoint.equals(data.source, justifiedCheckpoint);
if (!isMatchingSource) {
throw new Error(
"Attestation source does not equal justified checkpoint: " +
`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)
if (
!isValidIndexedAttestation(
Expand All @@ -92,10 +77,11 @@ export function processAttestation(
) {
throw new Error("Attestation is not valid");
}
const isMatchingTarget = config.types.Root.equals(data.target.root, getBlockRoot(config, state, data.target.epoch));
// a timely head is only be set if the target is _also_ matching
const isMatchingHead =
isMatchingTarget && config.types.Root.equals(data.beaconBlockRoot, getBlockRootAtSlot(config, state, data.slot));
const {timelySource, timelyTarget, timelyHead} = getAttestationParticipationStatus(
state,
data,
state.slot - data.slot
);

// Retrieve the validator indices from the attestation participation bitfield
const attestingIndices = epochCtx.getAttestingIndices(data, attestation.aggregationBits);
Expand All @@ -106,20 +92,55 @@ export function processAttestation(
for (const index of attestingIndices) {
const status = epochParticipation.getStatus(index) as IParticipationStatus;
const newStatus = {
timelyHead: status.timelyHead || isMatchingHead,
timelySource: true,
timelyTarget: status.timelyTarget || isMatchingTarget,
timelySource: status.timelySource || timelySource,
timelyTarget: status.timelyTarget || timelyTarget,
timelyHead: status.timelyHead || timelyHead,
};
epochParticipation.setStatus(index, newStatus);
// add proposer rewards for source/target/head that updated the state
proposerRewardNumerator +=
getBaseReward(config, state, index) *
(BigInt(!status.timelySource) * TIMELY_SOURCE_WEIGHT +
BigInt(!status.timelyTarget && isMatchingTarget) * TIMELY_TARGET_WEIGHT +
BigInt(!status.timelyHead && isMatchingHead) * TIMELY_HEAD_WEIGHT);
(BigInt(!status.timelySource && timelySource) * TIMELY_SOURCE_WEIGHT +
BigInt(!status.timelyTarget && timelyTarget) * TIMELY_TARGET_WEIGHT +
BigInt(!status.timelyHead && timelyHead) * TIMELY_HEAD_WEIGHT);
}

const proposerRewardDenominator = ((WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR) / PROPOSER_WEIGHT;
const proposerReward = proposerRewardNumerator / proposerRewardDenominator;
increaseBalance(state, epochCtx.getBeaconProposer(state.slot), proposerReward);
}

/**
* https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.4/specs/altair/beacon-chain.md#get_attestation_participation_flag_indices
*/
export function getAttestationParticipationStatus(
state: CachedBeaconState<altair.BeaconState>,
data: phase0.AttestationData,
inclusionDelay: number
): IParticipationStatus {
const {config, epochCtx} = state;
const {SLOTS_PER_EPOCH, MIN_ATTESTATION_INCLUSION_DELAY} = config.params;
let justifiedCheckpoint;
if (data.target.epoch === epochCtx.currentShuffling.epoch) {
justifiedCheckpoint = state.currentJustifiedCheckpoint;
} else {
justifiedCheckpoint = state.previousJustifiedCheckpoint;
}
const isMatchingSource = config.types.phase0.Checkpoint.equals(data.source, justifiedCheckpoint);
if (!isMatchingSource) {
throw new Error(
"Attestation source does not equal justified checkpoint: " +
`source=${JSON.stringify(config.types.phase0.Checkpoint.toJson(data.source))} ` +
`justifiedCheckpoint=${JSON.stringify(config.types.phase0.Checkpoint.toJson(justifiedCheckpoint))}`
);
}
const isMatchingTarget = config.types.Root.equals(data.target.root, getBlockRoot(config, state, data.target.epoch));
// a timely head is only be set if the target is _also_ matching
const isMatchingHead =
isMatchingTarget && config.types.Root.equals(data.beaconBlockRoot, getBlockRootAtSlot(config, state, data.slot));
return {
timelySource: isMatchingSource && inclusionDelay <= intSqrt(SLOTS_PER_EPOCH),
timelyTarget: isMatchingTarget && inclusionDelay <= SLOTS_PER_EPOCH,
timelyHead: isMatchingHead && inclusionDelay === MIN_ATTESTATION_INCLUSION_DELAY,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from "../../util";
import * as phase0 from "../../phase0";
import * as naive from "../../naive";
import {getSyncCommitteeIndices} from "../state_accessor";
import {CachedBeaconState} from "../../allForks/util";

export function processSyncCommittee(
Expand All @@ -25,7 +24,7 @@ export function processSyncCommittee(
const {config} = state;
const previousSlot = Math.max(state.slot, 1) - 1;
const currentEpoch = getCurrentEpoch(config, state);
const committeeIndices = getSyncCommitteeIndices(config, state, currentEpoch);
const committeeIndices = state.currSyncCommitteeIndexes;
const participantIndices = committeeIndices.filter((index) => !!aggregate.syncCommitteeBits[index]);
const committeePubkeys = Array.from(state.currentSyncCommittee.pubkeys);
const participantPubkeys = committeePubkeys.filter((pubkey, index) => !!aggregate.syncCommitteeBits[index]);
Expand All @@ -41,7 +40,8 @@ export function processSyncCommittee(
getBlockRootAtSlot(config, state, previousSlot),
domain
);
if (verifySignatures) {
// different from the spec but not sure how to get through signature verification for default/empty SyncAggregate in the spec test
if (verifySignatures && participantIndices.length > 0) {
twoeths marked this conversation as resolved.
Show resolved Hide resolved
assert.true(
verifyAggregate(
participantPubkeys.map((pubkey) => pubkey.valueOf() as Uint8Array),
Expand Down
6 changes: 3 additions & 3 deletions packages/beacon-state-transition/src/altair/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const TIMELY_HEAD_FLAG_INDEX = 0;
export const TIMELY_SOURCE_FLAG_INDEX = 1;
export const TIMELY_TARGET_FLAG_INDEX = 2;
export const TIMELY_SOURCE_FLAG_INDEX = 0;
export const TIMELY_TARGET_FLAG_INDEX = 1;
export const TIMELY_HEAD_FLAG_INDEX = 2;

export const TIMELY_HEAD_WEIGHT = BigInt(12);
export const TIMELY_SOURCE_WEIGHT = BigInt(12);
Expand Down
42 changes: 17 additions & 25 deletions packages/beacon-state-transition/src/altair/epoch/balance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {altair, Gwei, ValidatorIndex} from "@chainsafe/lodestar-types";
import {bigIntSqrt} from "@chainsafe/lodestar-utils";
import {getFlagIndicesAndWeights} from "../misc";
import {PARTICIPATION_FLAG_WEIGHTS} from "../misc";
import * as phase0 from "../../phase0";
import {
CachedBeaconState,
Expand All @@ -26,15 +26,14 @@ import {
export function getFlagIndexDeltas(
state: CachedBeaconState<altair.BeaconState>,
process: IEpochProcess,
flagIndex: number,
weight: bigint
flagIndex: number
): [Gwei[], Gwei[]] {
const {config} = state;
const validatorCount = state.validators.length;
const rewards = newZeroedBigIntArray(validatorCount);
const penalties = newZeroedBigIntArray(validatorCount);

const increment = config.params.EFFECTIVE_BALANCE_INCREMENT;
const {EFFECTIVE_BALANCE_INCREMENT} = config.params;

let flag;
let stakeSummaryKey: keyof IEpochStakeSummary;
Expand All @@ -52,8 +51,10 @@ export function getFlagIndexDeltas(
throw new Error(`Unable to process flagIndex: ${flagIndex}`);
}

const unslashedParticipatingIncrements = process.prevEpochUnslashedStake[stakeSummaryKey] / increment;
const activeIncrements = process.totalActiveStake / increment;
const weight = PARTICIPATION_FLAG_WEIGHTS[flagIndex];
const unslashedParticipatingIncrements =
process.prevEpochUnslashedStake[stakeSummaryKey] / EFFECTIVE_BALANCE_INCREMENT;
const activeIncrements = process.totalActiveStake / EFFECTIVE_BALANCE_INCREMENT;

for (let i = 0; i < process.statuses.length; i++) {
const status = process.statuses[i];
Expand All @@ -62,14 +63,11 @@ export function getFlagIndexDeltas(
}
const baseReward = getBaseReward(state, process, i);
if (hasMarkers(status.flags, flag)) {
if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) {
// This flag reward cancels the inactivity penalty corresponding to the flag index
rewards[i] += (baseReward * weight) / WEIGHT_DENOMINATOR;
} else {
if (!isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) {
const rewardNumerator = baseReward * weight * unslashedParticipatingIncrements;
rewards[i] += rewardNumerator / (activeIncrements * WEIGHT_DENOMINATOR);
}
} else {
} else if (flagIndex !== TIMELY_HEAD_FLAG_INDEX) {
penalties[i] += (baseReward * weight) / WEIGHT_DENOMINATOR;
}
}
Expand All @@ -88,20 +86,14 @@ export function getInactivityPenaltyDeltas(
const rewards = newZeroedBigIntArray(validatorCount);
const penalties = newZeroedBigIntArray(validatorCount);

if (isInInactivityLeak(config, (state as unknown) as phase0.BeaconState)) {
for (let i = 0; i < process.statuses.length; i++) {
const status = process.statuses[i];
if (hasMarkers(status.flags, FLAG_ELIGIBLE_ATTESTER)) {
for (const [_, weight] of getFlagIndicesAndWeights()) {
// This inactivity penalty cancels the flag reward rcorresponding to the flag index
penalties[i] += (getBaseReward(state, process, i) * weight) / WEIGHT_DENOMINATOR;
}
if (!hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_OR_UNSLASHED)) {
const penaltyNumerator = process.validators[i].effectiveBalance * BigInt(state.inactivityScores[i]);
const penaltyDenominator =
config.params.INACTIVITY_SCORE_BIAS * config.params.INACTIVITY_PENALTY_QUOTIENT_ALTAIR;
penalties[i] += penaltyNumerator / penaltyDenominator;
}
for (let i = 0; i < process.statuses.length; i++) {
const status = process.statuses[i];
if (hasMarkers(status.flags, FLAG_ELIGIBLE_ATTESTER)) {
if (!hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_OR_UNSLASHED)) {
const penaltyNumerator = process.validators[i].effectiveBalance * BigInt(state.inactivityScores[i]);
const penaltyDenominator =
config.params.INACTIVITY_SCORE_BIAS * config.params.INACTIVITY_PENALTY_QUOTIENT_ALTAIR;
penalties[i] += penaltyNumerator / penaltyDenominator;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ import {
hasMarkers,
IEpochProcess,
} from "../../allForks/util";
import {GENESIS_EPOCH} from "../../constants";
import {isInInactivityLeak} from "../../util";

export function processInactivityUpdates(state: CachedBeaconState<altair.BeaconState>, process: IEpochProcess): void {
if (state.currentShuffling.epoch === GENESIS_EPOCH) {
return;
}
const {config} = state;
const {INACTIVITY_SCORE_BIAS, INACTIVITY_SCORE_RECOVERY_RATE} = config.params;
const inActivityLeak = isInInactivityLeak(config, (state as unknown) as phase0.BeaconState);
for (let i = 0; i < process.statuses.length; i++) {
const status = process.statuses[i];
if (hasMarkers(status.flags, FLAG_ELIGIBLE_ATTESTER)) {
if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_OR_UNSLASHED)) {
if (state.inactivityScores[i] > 0) {
state.inactivityScores[i] -= 1;
}
} else if (inActivityLeak) {
state.inactivityScores[i] += Number(config.params.INACTIVITY_SCORE_BIAS);
state.inactivityScores[i] -= Math.min(1, state.inactivityScores[i]);
} else {
state.inactivityScores[i] += Number(INACTIVITY_SCORE_BIAS);
}
if (!inActivityLeak) {
state.inactivityScores[i] -= Math.min(Number(INACTIVITY_SCORE_RECOVERY_RATE), state.inactivityScores[i]);
}
}
}
Expand Down
Loading