chore: use config from beacon chain (#8703)

**Motivation**

- as a preparation for lodestar-z integration, we should not access
config from any cached BeaconState

**Description**

- use chain.config instead

part of #8652

---------

Co-authored-by: Tuyen Nguyen <twoeths@users.noreply.github.com>
This commit is contained in:
twoeths
2025-12-18 09:25:53 +07:00
committed by GitHub
parent f4236afdba
commit c151a164f2
57 changed files with 268 additions and 125 deletions

View File

@@ -139,6 +139,7 @@ export async function verifyBlocksInEpoch(
// All signatures at once
opts.skipVerifyBlockSignatures !== true
? verifyBlocksSignatures(
this.config,
this.index2pubkey,
this.bls,
this.logger,

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {CachedBeaconStateAllForks, Index2PubkeyCache, getBlockSignatureSets} from "@lodestar/state-transition";
import {IndexedAttestation, SignedBeaconBlock} from "@lodestar/types";
import {Logger} from "@lodestar/utils";
@@ -15,6 +16,7 @@ import {ImportBlockOpts} from "./types.js";
* Since all data is known in advance all signatures are verified at once in parallel.
*/
export async function verifyBlocksSignatures(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
bls: IBlsVerifier,
logger: Logger,
@@ -39,7 +41,7 @@ export async function verifyBlocksSignatures(
: //
// Verify signatures per block to track which block is invalid
bls.verifySignatureSets(
getBlockSignatureSets(index2pubkey, preState0, block, indexedAttestationsByBlock[i], {
getBlockSignatureSets(config, index2pubkey, preState0, block, indexedAttestationsByBlock[i], {
skipProposerSignature: opts.validProposerSignature,
})
);

View File

@@ -142,7 +142,7 @@ export class BeaconChain implements IBeaconChain {
readonly aggregatedAttestationPool: AggregatedAttestationPool;
readonly syncCommitteeMessagePool: SyncCommitteeMessagePool;
readonly syncContributionAndProofPool;
readonly opPool = new OpPool();
readonly opPool: OpPool;
// Gossip seen cache
readonly seenAttesters = new SeenAttesters();
@@ -260,6 +260,7 @@ export class BeaconChain implements IBeaconChain {
this.aggregatedAttestationPool = new AggregatedAttestationPool(this.config, metrics);
this.syncCommitteeMessagePool = new SyncCommitteeMessagePool(config, clock, this.opts?.preaggregateSlotDistance);
this.syncContributionAndProofPool = new SyncContributionAndProofPool(config, clock, metrics, logger);
this.opPool = new OpPool(config);
this.seenAggregatedAttestations = new SeenAggregatedAttestations(metrics);
this.seenContributionAndProof = new SeenContributionAndProof(metrics);
@@ -334,6 +335,7 @@ export class BeaconChain implements IBeaconChain {
this.cpStateDatastore = fileDataStore ? new FileCPStateDatastore(dataDir) : new DbCPStateDatastore(this.db);
checkpointStateCache = new PersistentCheckpointStateCache(
{
config,
metrics,
logger,
clock,
@@ -1306,7 +1308,7 @@ export class BeaconChain implements IBeaconChain {
const postState = this.regen.getStateSync(toRootHex(block.stateRoot)) ?? undefined;
return computeBlockRewards(block, preState.clone(), postState?.clone());
return computeBlockRewards(this.config, block, preState.clone(), postState?.clone());
}
async getAttestationsRewards(
@@ -1330,7 +1332,7 @@ export class BeaconChain implements IBeaconChain {
throw Error(`State is not in cache for slot ${slot}`);
}
const rewards = await computeAttestationsRewards(this.pubkey2index, cachedState, validatorIds);
const rewards = await computeAttestationsRewards(this.config, this.pubkey2index, cachedState, validatorIds);
return {rewards, executionOptimistic, finalized};
}
@@ -1347,6 +1349,6 @@ export class BeaconChain implements IBeaconChain {
preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
return computeSyncCommitteeRewards(this.index2pubkey, block, preState.clone(), validatorIds);
return computeSyncCommitteeRewards(this.config, this.index2pubkey, block, preState.clone(), validatorIds);
}
}

View File

@@ -1,6 +1,6 @@
import {Signature, aggregateSignatures} from "@chainsafe/blst";
import {BitArray} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {BeaconConfig} from "@lodestar/config";
import {IForkChoice} from "@lodestar/fork-choice";
import {
ForkName,
@@ -162,7 +162,7 @@ export class AggregatedAttestationPool {
private lowestPermissibleSlot = 0;
constructor(
private readonly config: ChainForkConfig,
private readonly config: BeaconConfig,
private readonly metrics: Metrics | null = null
) {
metrics?.opPool.aggregatedAttestationPool.attDataPerSlot.addCollect(() => this.onScrapeMetrics(metrics));
@@ -249,7 +249,7 @@ export class AggregatedAttestationPool {
const stateEpoch = state.epochCtx.epoch;
const statePrevEpoch = stateEpoch - 1;
const notSeenValidatorsFn = getNotSeenValidatorsFn(state);
const notSeenValidatorsFn = getNotSeenValidatorsFn(this.config, state);
const validateAttestationDataFn = getValidateAttestationDataFn(forkChoice, state);
const attestationsByScore: AttestationWithScore[] = [];
@@ -362,7 +362,7 @@ export class AggregatedAttestationPool {
const statePrevEpoch = stateEpoch - 1;
const rootCache = new RootCache(state);
const notSeenValidatorsFn = getNotSeenValidatorsFn(state);
const notSeenValidatorsFn = getNotSeenValidatorsFn(this.config, state);
const validateAttestationDataFn = getValidateAttestationDataFn(forkChoice, state);
const slots = Array.from(this.attestationGroupByIndexByDataHexBySlot.keys()).sort((a, b) => b - a);
@@ -656,7 +656,7 @@ export class MatchingDataAttestationGroup {
private readonly attestations: AttestationWithIndex[] = [];
constructor(
private readonly config: ChainForkConfig,
private readonly config: BeaconConfig,
readonly committee: Uint32Array,
readonly data: phase0.AttestationData
) {}
@@ -864,9 +864,9 @@ export function aggregateConsolidation({byCommittee, attData}: AttestationsConso
* Pre-compute participation from a CachedBeaconStateAllForks, for use to check if an attestation's committee
* has already attested or not.
*/
export function getNotSeenValidatorsFn(state: CachedBeaconStateAllForks): GetNotSeenValidatorsFn {
export function getNotSeenValidatorsFn(config: BeaconConfig, state: CachedBeaconStateAllForks): GetNotSeenValidatorsFn {
const stateSlot = state.slot;
if (state.config.getForkName(stateSlot) === ForkName.phase0) {
if (config.getForkName(stateSlot) === ForkName.phase0) {
// Get attestations to be included in a phase0 block.
// As we are close to altair, this is not really important, it's mainly for e2e.
// The performance is not great due to the different BeaconState data structure to altair.

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {Id, Repository} from "@lodestar/db";
import {
BLS_WITHDRAWAL_PREFIX,
@@ -51,6 +52,8 @@ export class OpPool {
/** Map of validator index -> SignedBLSToExecutionChange */
private readonly blsToExecutionChanges = new Map<ValidatorIndex, SignedBLSToExecutionChangeVersioned>();
constructor(private readonly config: BeaconConfig) {}
// Getters for metrics
get attesterSlashingsSize(): number {
@@ -191,9 +194,8 @@ export class OpPool {
phase0.SignedVoluntaryExit[],
capella.SignedBLSToExecutionChange[],
] {
const {config} = state;
const stateEpoch = computeEpochAtSlot(state.slot);
const stateFork = config.getForkSeq(state.slot);
const stateFork = this.config.getForkSeq(state.slot);
const toBeSlashedIndices = new Set<ValidatorIndex>();
const proposerSlashings: phase0.ProposerSlashing[] = [];
@@ -265,7 +267,7 @@ export class OpPool {
// a future fork.
isVoluntaryExitSignatureIncludable(
stateFork,
config.getForkSeq(computeStartSlotAtEpoch(voluntaryExit.message.epoch))
this.config.getForkSeq(computeStartSlotAtEpoch(voluntaryExit.message.epoch))
)
) {
voluntaryExits.push(voluntaryExit);
@@ -368,14 +370,13 @@ export class OpPool {
* Prune if validator has already exited at or before the finalized checkpoint of the head.
*/
private pruneVoluntaryExits(headState: CachedBeaconStateAllForks): void {
const {config} = headState;
const headStateFork = config.getForkSeq(headState.slot);
const headStateFork = this.config.getForkSeq(headState.slot);
const finalizedEpoch = headState.finalizedCheckpoint.epoch;
for (const [key, voluntaryExit] of this.voluntaryExits.entries()) {
// VoluntaryExit messages signed in the previous fork become invalid and can never be included in any future
// block, so just drop as the head state advances into the next fork.
if (config.getForkSeq(computeStartSlotAtEpoch(voluntaryExit.message.epoch)) < headStateFork) {
if (this.config.getForkSeq(computeStartSlotAtEpoch(voluntaryExit.message.epoch)) < headStateFork) {
this.voluntaryExits.delete(key);
}
@@ -392,9 +393,8 @@ export class OpPool {
* to opPool once gossipsub seen cache TTL passes.
*/
private pruneBlsToExecutionChanges(headBlock: SignedBeaconBlock, headState: CachedBeaconStateAllForks): void {
const {config} = headState;
const recentBlsToExecutionChanges =
config.getForkSeq(headBlock.message.slot) >= ForkSeq.capella
this.config.getForkSeq(headBlock.message.slot) >= ForkSeq.capella
? (headBlock as capella.SignedBeaconBlock).message.body.blsToExecutionChanges
: [];

View File

@@ -165,7 +165,7 @@ export async function produceBlockBody<T extends BlockType>(
// even though shouldOverrideBuilder is relevant for the engine response, for simplicity of typing
// we just return it undefined for the builder which anyway doesn't get consumed downstream
let shouldOverrideBuilder: boolean | undefined;
const fork = currentState.config.getForkName(blockSlot);
const fork = this.config.getForkName(blockSlot);
const produceResult = {
type: blockType,
fork,
@@ -644,7 +644,7 @@ export async function produceCommonBlockBody<T extends BlockType>(
? this.metrics?.executionBlockProductionTimeSteps
: this.metrics?.builderBlockProductionTimeSteps;
const fork = currentState.config.getForkName(slot);
const fork = this.config.getForkName(slot);
// TODO:
// Iterate through the naive aggregation pool and ensure all the attestations from there

View File

@@ -1,5 +1,6 @@
import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
import {routes} from "@lodestar/api";
import {BeaconConfig} from "@lodestar/config";
import {
EFFECTIVE_BALANCE_INCREMENT,
ForkName,
@@ -38,11 +39,12 @@ const defaultAttestationsReward = {head: 0, target: 0, source: 0, inclusionDelay
const defaultAttestationsPenalty = {target: 0, source: 0};
export async function computeAttestationsRewards(
config: BeaconConfig,
pubkey2index: PubkeyIndexMap,
state: CachedBeaconStateAllForks,
validatorIds?: (ValidatorIndex | string)[]
): Promise<AttestationsRewards> {
const fork = state.config.getForkName(state.slot);
const fork = config.getForkName(state.slot);
if (fork === ForkName.phase0) {
throw Error("Unsupported fork. Attestations rewards calculation is not available in phase0");
}
@@ -50,8 +52,13 @@ export async function computeAttestationsRewards(
const stateAltair = state as CachedBeaconStateAltair;
const transitionCache = beforeProcessEpoch(stateAltair);
const [idealRewards, penalties] = computeIdealAttestationsRewardsAndPenaltiesAltair(stateAltair, transitionCache);
const [idealRewards, penalties] = computeIdealAttestationsRewardsAndPenaltiesAltair(
config,
stateAltair,
transitionCache
);
const totalRewards = computeTotalAttestationsRewardsAltair(
config,
pubkey2index,
stateAltair,
transitionCache,
@@ -64,12 +71,13 @@ export async function computeAttestationsRewards(
}
function computeIdealAttestationsRewardsAndPenaltiesAltair(
config: BeaconConfig,
state: CachedBeaconStateAllForks,
transitionCache: EpochTransitionCache
): [IdealAttestationsReward[], AttestationsPenalty[]] {
const baseRewardPerIncrement = transitionCache.baseRewardPerIncrement;
const activeBalanceByIncrement = transitionCache.totalActiveStakeByIncrement;
const fork = state.config.getForkName(state.slot);
const fork = config.getForkName(state.slot);
const maxEffectiveBalance = isForkPostElectra(fork) ? MAX_EFFECTIVE_BALANCE_ELECTRA : MAX_EFFECTIVE_BALANCE;
const maxEffectiveBalanceByIncrement = Math.floor(maxEffectiveBalance / EFFECTIVE_BALANCE_INCREMENT);
@@ -139,6 +147,7 @@ function computeIdealAttestationsRewardsAndPenaltiesAltair(
// Same calculation as `getRewardsAndPenaltiesAltair` but returns the breakdown of rewards instead of aggregated
function computeTotalAttestationsRewardsAltair(
config: BeaconConfig,
pubkey2index: PubkeyIndexMap,
state: CachedBeaconStateAltair,
transitionCache: EpochTransitionCache,
@@ -148,7 +157,7 @@ function computeTotalAttestationsRewardsAltair(
): TotalAttestationsReward[] {
const rewards = [];
const {flags} = transitionCache;
const {epochCtx, config} = state;
const {epochCtx} = state;
const validatorIndices = validatorIds
.map((id) => (typeof id === "number" ? id : pubkey2index.get(fromHex(id))))
.filter((index) => index !== undefined); // Validator indices to include in the result

View File

@@ -1,4 +1,5 @@
import {routes} from "@lodestar/api";
import {BeaconConfig} from "@lodestar/config";
import {
ForkName,
WHISTLEBLOWER_REWARD_QUOTIENT,
@@ -26,11 +27,12 @@ type SubRewardValue = number; // All reward values should be integer
* 3) Reporting slashable behaviours from proposer and attester
*/
export async function computeBlockRewards(
config: BeaconConfig,
block: BeaconBlock,
preState: CachedBeaconStateAllForks,
postState?: CachedBeaconStateAllForks
): Promise<BlockRewards> {
const fork = preState.config.getForkName(block.slot);
const fork = config.getForkName(block.slot);
const {attestations: cachedAttestationsReward = 0, syncAggregate: cachedSyncAggregateReward = 0} =
postState?.proposerRewards ?? {};
let blockAttestationReward = cachedAttestationsReward;
@@ -40,7 +42,7 @@ export async function computeBlockRewards(
blockAttestationReward =
fork === ForkName.phase0
? computeBlockAttestationRewardPhase0(block as phase0.BeaconBlock, preState as CachedBeaconStatePhase0)
: computeBlockAttestationRewardAltair(block as altair.BeaconBlock, preState as CachedBeaconStateAltair);
: computeBlockAttestationRewardAltair(config, block as altair.BeaconBlock, preState as CachedBeaconStateAltair);
}
if (syncAggregateReward === 0) {
@@ -78,10 +80,11 @@ function computeBlockAttestationRewardPhase0(
* Reuses `processAttestationsAltair()`. Has dependency on RewardCache
*/
function computeBlockAttestationRewardAltair(
config: BeaconConfig,
block: altair.BeaconBlock,
preState: CachedBeaconStateAltair
): SubRewardValue {
const fork = preState.config.getForkSeq(block.slot);
const fork = config.getForkSeq(block.slot);
const {attestations} = block.body;
processAttestationsAltair(fork, preState, attestations, false);

View File

@@ -1,4 +1,5 @@
import {routes} from "@lodestar/api";
import {BeaconConfig} from "@lodestar/config";
import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params";
import {CachedBeaconStateAllForks, CachedBeaconStateAltair, Index2PubkeyCache} from "@lodestar/state-transition";
import {BeaconBlock, ValidatorIndex, altair} from "@lodestar/types";
@@ -7,12 +8,13 @@ export type SyncCommitteeRewards = routes.beacon.SyncCommitteeRewards;
type BalanceRecord = {val: number}; // Use val for convenient way to increment/decrement balance
export async function computeSyncCommitteeRewards(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
block: BeaconBlock,
preState: CachedBeaconStateAllForks,
validatorIds: (ValidatorIndex | string)[] = []
): Promise<SyncCommitteeRewards> {
const fork = preState.config.getForkName(block.slot);
const fork = config.getForkName(block.slot);
if (fork === ForkName.phase0) {
throw Error("Cannot get sync rewards as phase0 block does not have sync committee");
}

View File

@@ -1,4 +1,5 @@
import {routes} from "@lodestar/api";
import {BeaconConfig} from "@lodestar/config";
import {
CachedBeaconStateAllForks,
computeStartSlotAtEpoch,
@@ -24,6 +25,7 @@ export type PersistentCheckpointStateCacheOpts = {
};
type PersistentCheckpointStateCacheModules = {
config: BeaconConfig;
metrics?: Metrics | null;
logger: Logger;
clock?: IClock | null;
@@ -107,6 +109,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
private readonly cache: MapTracker<CacheKey, CacheItem>;
/** Epoch -> Set<blockRoot> */
private readonly epochIndex = new MapDef<Epoch, Set<RootHex>>(() => new Set<string>());
private readonly config: BeaconConfig;
private readonly metrics: Metrics | null | undefined;
private readonly logger: Logger;
private readonly clock: IClock | null | undefined;
@@ -120,10 +123,20 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
private readonly bufferPool?: BufferPool | null;
constructor(
{metrics, logger, clock, signal, datastore, blockStateCache, bufferPool}: PersistentCheckpointStateCacheModules,
{
config,
metrics,
logger,
clock,
signal,
datastore,
blockStateCache,
bufferPool,
}: PersistentCheckpointStateCacheModules,
opts: PersistentCheckpointStateCacheOpts
) {
this.cache = new MapTracker(metrics?.cpStateCache);
this.config = config;
if (metrics) {
this.metrics = metrics;
metrics.cpStateCache.size.addCollect(() => {
@@ -484,7 +497,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache {
}
const blockSlot = state.slot;
const processCPStatesTimeMs = state.config.getSlotComponentDurationMs(PROCESS_CHECKPOINT_STATES_BPS);
const processCPStatesTimeMs = this.config.getSlotComponentDurationMs(PROCESS_CHECKPOINT_STATES_BPS);
// we always have clock in production, fallback value is only for test
const msFromSlot = this.clock?.msFromSlot(blockSlot) ?? processCPStatesTimeMs;
const msToProcessCPStates = processCPStatesTimeMs - msFromSlot;

View File

@@ -51,7 +51,7 @@ export async function validateAttesterSlashing(
});
}
const signatureSets = getAttesterSlashingSignatureSets(chain.index2pubkey, state, attesterSlashing);
const signatureSets = getAttesterSlashingSignatureSets(chain.config, chain.index2pubkey, state, attesterSlashing);
if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true, priority: prioritizeBls}))) {
throw new AttesterSlashingError(GossipAction.REJECT, {
code: AttesterSlashingErrorCode.INVALID,

View File

@@ -153,7 +153,7 @@ export async function validateGossipBlock(
// [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
if (!chain.seenBlockInputCache.isVerifiedProposerSignature(blockSlot, blockRoot, signedBlock.signature)) {
const signatureSet = getBlockProposerSignatureSet(chain.index2pubkey, blockState, signedBlock);
const signatureSet = getBlockProposerSignatureSet(chain.config, chain.index2pubkey, blockState, signedBlock);
// Don't batch so verification is not delayed
if (!(await chain.bls.verifySignatureSets([signatureSet], {verifyOnMainThread: true}))) {
throw new BlockGossipError(GossipAction.REJECT, {

View File

@@ -41,7 +41,7 @@ async function validateBlsToExecutionChange(
// NOTE: No need to advance head state since the signature's fork is handled with `broadcastedOnFork`,
// and chanes relevant to `isValidBlsToExecutionChange()` happen only on processBlock(), not processEpoch()
const state = chain.getHeadState();
const {config} = state;
const {config} = chain;
// [REJECT] All of the conditions within process_bls_to_execution_change pass validation.
// verifySignature = false, verified in batch below

View File

@@ -44,7 +44,7 @@ async function validateProposerSlashing(
});
}
const signatureSets = getProposerSlashingSignatureSets(chain.index2pubkey, state, proposerSlashing);
const signatureSets = getProposerSlashingSignatureSets(chain.config, chain.index2pubkey, state, proposerSlashing);
if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true, priority: prioritizeBls}))) {
throw new ProposerSlashingError(GossipAction.REJECT, {
code: ProposerSlashingErrorCode.INVALID,

View File

@@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
epoch: Epoch,
aggregateAndProof: SignedAggregateAndProof
): Uint8Array {
// previously, we call `const aggregatorDomain = state.config.getDomain(state.slot, DOMAIN_AGGREGATE_AND_PROOF, slot);`
// previously, we call `const aggregatorDomain = config.getDomain(state.slot, DOMAIN_AGGREGATE_AND_PROOF, slot);`
// at fork boundary, it's required to dial to target epoch https://github.com/ChainSafe/lodestar/blob/v1.11.3/packages/beacon-node/src/chain/validation/attestation.ts#L573
// instead of that, just use the fork of slot in the attestation data
const slot = computeStartSlotAtEpoch(epoch);

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_CONTRIBUTION_AND_PROOF} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
@@ -9,11 +10,12 @@ import {
import {altair, ssz} from "@lodestar/types";
export function getContributionAndProofSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedContributionAndProof: altair.SignedContributionAndProof
): ISignatureSet {
const domain = state.config.getDomain(
const domain = config.getDomain(
state.slot,
DOMAIN_CONTRIBUTION_AND_PROOF,
signedContributionAndProof.message.contribution.slot

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_SYNC_COMMITTEE} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
@@ -9,11 +10,12 @@ import {
import {altair, ssz} from "@lodestar/types";
export function getSyncCommitteeSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
syncCommittee: altair.SyncCommitteeMessage
): ISignatureSet {
const domain = state.config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, syncCommittee.slot);
const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, syncCommittee.slot);
return {
type: SignatureSetType.single,

View File

@@ -1,14 +1,16 @@
import {PublicKey} from "@chainsafe/blst";
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_SYNC_COMMITTEE} from "@lodestar/params";
import {CachedBeaconStateAltair, ISignatureSet, SignatureSetType, computeSigningRoot} from "@lodestar/state-transition";
import {altair, ssz} from "@lodestar/types";
export function getSyncCommitteeContributionSignatureSet(
config: BeaconConfig,
state: CachedBeaconStateAltair,
contribution: altair.SyncCommitteeContribution,
pubkeys: PublicKey[]
): ISignatureSet {
const domain = state.config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, contribution.slot);
const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, contribution.slot);
return {
type: SignatureSetType.aggregate,
pubkeys,

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
@@ -9,11 +10,11 @@ import {
import {altair, ssz} from "@lodestar/types";
export function getSyncCommitteeSelectionProofSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
contributionAndProof: altair.ContributionAndProof
): ISignatureSet {
const {config} = state;
const slot = contributionAndProof.contribution.slot;
const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, slot);
const signingData: altair.SyncAggregatorSelectionData = {

View File

@@ -89,7 +89,7 @@ async function validateSyncCommitteeSigOnly(
syncCommittee: altair.SyncCommitteeMessage,
prioritizeBls = false
): Promise<void> {
const signatureSet = getSyncCommitteeSignatureSet(chain.index2pubkey, headState, syncCommittee);
const signatureSet = getSyncCommitteeSignatureSet(chain.config, chain.index2pubkey, headState, syncCommittee);
if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
throw new SyncCommitteeError(GossipAction.REJECT, {
code: SyncCommitteeErrorCode.INVALID_SIGNATURE,

View File

@@ -78,14 +78,19 @@ export async function validateSyncCommitteeGossipContributionAndProof(
const signatureSets = [
// [REJECT] The contribution_and_proof.selection_proof is a valid signature of the SyncAggregatorSelectionData
// derived from the contribution by the validator with index contribution_and_proof.aggregator_index.
getSyncCommitteeSelectionProofSignatureSet(index2pubkey, headState, contributionAndProof),
getSyncCommitteeSelectionProofSignatureSet(chain.config, index2pubkey, headState, contributionAndProof),
// [REJECT] The aggregator signature, signed_contribution_and_proof.signature, is valid.
getContributionAndProofSignatureSet(index2pubkey, headState, signedContributionAndProof),
getContributionAndProofSignatureSet(chain.config, index2pubkey, headState, signedContributionAndProof),
// [REJECT] The aggregate signature is valid for the message beacon_block_root and aggregate pubkey derived from
// the participation info in aggregation_bits for the subcommittee specified by the contribution.subcommittee_index.
getSyncCommitteeContributionSignatureSet(headState as CachedBeaconStateAltair, contribution, participantPubkeys),
getSyncCommitteeContributionSignatureSet(
chain.config,
headState as CachedBeaconStateAltair,
contribution,
participantPubkeys
),
];
if (!(await chain.bls.verifySignatureSets(signatureSets, {batchable: true}))) {

View File

@@ -59,7 +59,7 @@ async function validateVoluntaryExit(
});
}
const signatureSet = getVoluntaryExitSignatureSet(chain.index2pubkey, state, voluntaryExit);
const signatureSet = getVoluntaryExitSignatureSet(chain.config, chain.index2pubkey, state, voluntaryExit);
if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
throw new VoluntaryExitError(GossipAction.REJECT, {
code: VoluntaryExitErrorCode.INVALID_SIGNATURE,

View File

@@ -750,9 +750,13 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}
// GENESIS_SLOT doesn't has valid signature
if (anchorBlock.message.slot === GENESIS_SLOT) return;
await verifyBlockProposerSignature(this.chain.index2pubkey, this.chain.bls, this.chain.getHeadState(), [
anchorBlock,
]);
await verifyBlockProposerSignature(
this.chain.config,
this.chain.index2pubkey,
this.chain.bls,
this.chain.getHeadState(),
[anchorBlock]
);
// We can write to the disk if this is ahead of prevFinalizedCheckpointBlock otherwise
// we will need to go make checks on the top of sync loop before writing as it might
@@ -818,6 +822,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter}
// If any of the block's proposer signature fail, we can't trust this peer at all
if (verifiedBlocks.length > 0) {
await verifyBlockProposerSignature(
this.chain.config,
this.chain.index2pubkey,
this.chain.bls,
this.chain.getHeadState(),

View File

@@ -46,6 +46,7 @@ export function verifyBlockSequence(
}
export async function verifyBlockProposerSignature(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
bls: IBlsVerifier,
state: CachedBeaconStateAllForks,
@@ -54,7 +55,8 @@ export async function verifyBlockProposerSignature(
if (blocks.length === 1 && blocks[0].message.slot === GENESIS_SLOT) return;
const signatures = blocks.reduce((sigs: ISignatureSet[], block) => {
// genesis block doesn't have valid signature
if (block.message.slot !== GENESIS_SLOT) sigs.push(getBlockProposerSignatureSet(index2pubkey, state, block));
if (block.message.slot !== GENESIS_SLOT)
sigs.push(getBlockProposerSignatureSet(config, index2pubkey, state, block));
return sigs;
}, []);

View File

@@ -1,6 +1,6 @@
import {Mock, Mocked, vi} from "vitest";
import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map";
import {ChainForkConfig} from "@lodestar/config";
import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
import {config as defaultConfig} from "@lodestar/config/default";
import {EpochDifference, ForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {Logger} from "@lodestar/utils";
@@ -133,8 +133,8 @@ vi.mock("../../src/chain/chain.js", async (importActual) => {
getClientVersion: vi.fn(),
},
executionBuilder: {},
opPool: new OpPool(),
aggregatedAttestationPool: new AggregatedAttestationPool(config),
opPool: new OpPool(config as BeaconConfig),
aggregatedAttestationPool: new AggregatedAttestationPool(config as BeaconConfig),
syncContributionAndProofPool: new SyncContributionAndProofPool(config, clock),
// @ts-expect-error
beaconProposerCache: new BeaconProposerCache(),

View File

@@ -1,6 +1,6 @@
import {beforeAll, bench, describe} from "@chainsafe/benchmark";
import {BitArray, toHexString} from "@chainsafe/ssz";
import {createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {createBeaconConfig, defaultChainConfig} from "@lodestar/config";
import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray} from "@lodestar/fork-choice";
import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH} from "@lodestar/params";
import {
@@ -232,7 +232,7 @@ function getAggregatedAttestationPool(
numMissedVotes: number,
numBadVotes: number
): AggregatedAttestationPool {
const config = createChainForkConfig(defaultChainConfig);
const config = createBeaconConfig(defaultChainConfig, Buffer.alloc(32, 0xaa));
const pool = new AggregatedAttestationPool(config);
for (let epochSlot = 0; epochSlot < SLOTS_PER_EPOCH; epochSlot++) {

View File

@@ -1,4 +1,6 @@
import {beforeAll, bench, describe} from "@chainsafe/benchmark";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {
ForkName,
MAX_ATTESTER_SLASHINGS,
@@ -20,6 +22,7 @@ import {
describe("opPool", () => {
let originalState: CachedBeaconStateAltair;
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
beforeAll(
() => {
@@ -31,7 +34,7 @@ describe("opPool", () => {
bench({
id: "getSlashingsAndExits - default max",
beforeEach: () => {
const pool = new OpPool();
const pool = new OpPool(config);
fillAttesterSlashing(pool, originalState, MAX_ATTESTER_SLASHINGS);
fillProposerSlashing(pool, originalState, MAX_PROPOSER_SLASHINGS);
fillVoluntaryExits(pool, originalState, MAX_VOLUNTARY_EXITS);
@@ -48,7 +51,7 @@ describe("opPool", () => {
bench({
id: "getSlashingsAndExits - 2k",
beforeEach: () => {
const pool = new OpPool();
const pool = new OpPool(config);
const maxItemsInPool = 2_000;
fillAttesterSlashing(pool, originalState, maxItemsInPool);

View File

@@ -1,7 +1,8 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {SecretKey, Signature, aggregateSignatures, fastAggregateVerify} from "@chainsafe/blst";
import {BitArray, fromHexString, toHexString} from "@chainsafe/ssz";
import {createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {createBeaconConfig, createChainForkConfig} from "@lodestar/config";
import {chainConfig as chainConfigDefault} from "@lodestar/config/default";
import {
ACTIVE_PRESET,
FAR_FUTURE_EPOCH,
@@ -48,9 +49,6 @@ describe("AggregatedAttestationPool - Altair", () => {
let pool: AggregatedAttestationPool;
const fork = ForkName.altair;
const config = createChainForkConfig({
...defaultChainConfig,
});
const altairForkEpoch = 2020;
const currentEpoch = altairForkEpoch + 10;
const currentSlot = SLOTS_PER_EPOCH * currentEpoch;
@@ -93,6 +91,10 @@ describe("AggregatedAttestationPool - Altair", () => {
let altairState: CachedBeaconStateAllForks;
let forkchoiceStub: MockedForkChoice;
const config = createBeaconConfig(
createChainForkConfig({...chainConfigDefault, ALTAIR_FORK_EPOCH: altairForkEpoch}),
originalState.genesisValidatorsRoot
);
beforeEach(() => {
pool = new AggregatedAttestationPool(config);
@@ -107,7 +109,7 @@ describe("AggregatedAttestationPool - Altair", () => {
it("getNotSeenValidatorsFn", () => {
// previousEpochParticipation and currentEpochParticipation is created inside generateCachedState
// 0 and 1 are fully participated
const notSeenValidatorFn = getNotSeenValidatorsFn(altairState);
const notSeenValidatorFn = getNotSeenValidatorsFn(config, altairState);
// seen attesting indices are 0, 1 => not seen are 2, 3
expect(notSeenValidatorFn(currentEpoch, currentSlot - 1, committeeIndex)).toEqual(new Set([2, 3]));
// attestations in current slot are always included (since altairState.slot = currentSlot + 1)
@@ -169,14 +171,15 @@ describe("AggregatedAttestationPool - get packed attestations - Electra", () =>
let pool: AggregatedAttestationPool;
const fork = ForkName.electra;
const electraForkEpoch = 2020;
const config = createChainForkConfig({
...defaultChainConfig,
const chainConfig = createChainForkConfig({
...chainConfigDefault,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: electraForkEpoch,
});
const config = createBeaconConfig(chainConfig, Buffer.alloc(32, 0xaa));
const currentEpoch = electraForkEpoch + 10;
const currentSlot = SLOTS_PER_EPOCH * currentEpoch;
@@ -393,9 +396,7 @@ describe("AggregatedAttestationPool - get packed attestations - Electra", () =>
});
describe("MatchingDataAttestationGroup.add()", () => {
const config = createChainForkConfig({
...defaultChainConfig,
});
const config = createBeaconConfig(chainConfigDefault, Buffer.alloc(32, 0xaa));
const testCases: {id: string; attestationsToAdd: {bits: number[]; res: InsertOutcome; isKept: boolean}[]}[] = [
{
@@ -465,9 +466,7 @@ describe("MatchingDataAttestationGroup.add()", () => {
});
describe("MatchingDataAttestationGroup.getAttestationsForBlock", () => {
const config = createChainForkConfig({
...defaultChainConfig,
});
const config = createBeaconConfig(chainConfigDefault, Buffer.alloc(32, 0xaa));
const maxAttestations = 2;
const testCases: {

View File

@@ -1,4 +1,6 @@
import {beforeAll, beforeEach, describe, expect, it} from "vitest";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {ACTIVE_PRESET, PresetName, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {CachedBeaconStateAllForks, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {RootHex, phase0} from "@lodestar/types";
@@ -27,6 +29,7 @@ describe("PersistentCheckpointStateCache", () => {
let fileApisBuffer: Map<string, Uint8Array>;
let states: Record<"cp0a" | "cp0b" | "cp1" | "cp2", CachedBeaconStateAllForks>;
let stateBytes: Record<"cp0a" | "cp0b" | "cp1" | "cp2", Uint8Array>;
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
beforeAll(() => {
root0a = Buffer.alloc(32);
@@ -91,6 +94,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),
@@ -165,6 +169,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),
@@ -241,6 +246,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),
@@ -546,6 +552,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),
@@ -817,6 +824,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),
@@ -907,6 +915,7 @@ describe("PersistentCheckpointStateCache", () => {
const datastore = getTestDatastore(fileApisBuffer);
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),

View File

@@ -32,7 +32,7 @@ describe("api/validator - produceBlockV3", () => {
const config = createBeaconConfig(chainConfig, genesisValidatorsRoot);
beforeEach(() => {
modules = getApiTestModules();
modules = getApiTestModules({config});
api = getValidatorApi(defaultApiOptions, {...modules, config});
state = generateCachedBellatrixState();

View File

@@ -1,4 +1,6 @@
import {beforeEach, describe, expect, it} from "vitest";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {RegenCaller} from "../../../../src/chain/regen/interface.js";
@@ -10,6 +12,7 @@ import {testLogger} from "../../../utils/logger.js";
import {generateCachedState} from "../../../utils/state.js";
describe("regen", () => {
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
//
// epoch: 19 20 21 22 23
// |-----------|-----------|-----------|-----------|
@@ -74,6 +77,7 @@ describe("regen", () => {
beforeEach(() => {
cache = new PersistentCheckpointStateCache(
{
config,
datastore,
logger: testLogger(),
blockStateCache: new FIFOBlockStateCache({}, {}),

View File

@@ -1,4 +1,6 @@
import {describe, expect, it, vi} from "vitest";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {SYNC_COMMITTEE_SIZE} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
@@ -15,6 +17,7 @@ import {
import {computeBlockRewards} from "../../../../src/chain/rewards/blockRewards.js";
describe("chain / rewards / blockRewards", () => {
const config = createBeaconConfig({...chainConfigDef, ALTAIR_FORK_EPOCH: 0}, Buffer.alloc(32, 0xaa));
const testCases: {id: string; timeout?: number; opts: BlockAltairOpts}[] = [
{
id: "Normal case",
@@ -92,7 +95,11 @@ describe("chain / rewards / blockRewards", () => {
// Populate tree root caches of the state
state.hashTreeRoot();
cachedStateAltairPopulateCaches(state);
const calculatedBlockReward = await computeBlockRewards(block.message, state as CachedBeaconStateAllForks);
const calculatedBlockReward = await computeBlockRewards(
config,
block.message,
state as CachedBeaconStateAllForks
);
const {proposerIndex, total, attestations, syncAggregate, proposerSlashings, attesterSlashings} =
calculatedBlockReward;
@@ -153,6 +160,7 @@ describe("chain / rewards / blockRewards", () => {
postState.proposerRewards = {attestations: 1000, syncAggregate: 1001, slashing: 1002};
const calculatedBlockReward = await computeBlockRewards(
config,
block.message,
preState as CachedBeaconStateAllForks,
postState

View File

@@ -1,4 +1,6 @@
import {afterEach, beforeEach, describe, it, vi} from "vitest";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {phase0, ssz} from "@lodestar/types";
import {AttesterSlashingErrorCode} from "../../../../src/chain/errors/attesterSlashingError.js";
import {validateGossipAttesterSlashing} from "../../../../src/chain/validation/attesterSlashing.js";
@@ -9,9 +11,10 @@ import {generateCachedState} from "../../../utils/state.js";
describe("GossipMessageValidator", () => {
let chainStub: MockedBeaconChain;
let opPool: MockedBeaconChain["opPool"];
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
beforeEach(() => {
chainStub = getMockedBeaconChain();
chainStub = getMockedBeaconChain({config});
opPool = chainStub.opPool;
const state = generateCachedState();

View File

@@ -1,6 +1,6 @@
import {Mock, Mocked, beforeEach, describe, it, vi} from "vitest";
import {createChainForkConfig} from "@lodestar/config";
import {config} from "@lodestar/config/default";
import {createBeaconConfig, createChainForkConfig} from "@lodestar/config";
import {config as configDef} from "@lodestar/config/default";
import {ProtoBlock} from "@lodestar/fork-choice";
import {ForkName, ForkPostDeneb, ForkPreFulu} from "@lodestar/params";
import {SignedBeaconBlock, ssz} from "@lodestar/types";
@@ -26,15 +26,16 @@ describe("gossip block validation", () => {
const signature = EMPTY_SIGNATURE;
const maxSkipSlots = 10;
const denebConfig = createChainForkConfig({
...config,
...configDef,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
});
const config = createBeaconConfig(configDef, Buffer.alloc(32, 0xaa));
beforeEach(() => {
chain = getMockedBeaconChain();
chain = getMockedBeaconChain({config});
vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot);
forkChoice = chain.forkChoice;
forkChoice.getBlockHex.mockReturnValue(null);

View File

@@ -83,7 +83,7 @@ describe("validate bls to execution change", () => {
const signedBlsToExecChange = {message: blsToExecutionChange, signature: wsk.sign(signingRoot).toBytes()};
beforeEach(() => {
chainStub = getMockedBeaconChain();
chainStub = getMockedBeaconChain({config});
opPool = chainStub.opPool;
vi.spyOn(chainStub, "getHeadState").mockReturnValue(state);
vi.spyOn(chainStub, "getHeadStateAtCurrentEpoch");

View File

@@ -1,4 +1,6 @@
import {afterEach, beforeEach, describe, it, vi} from "vitest";
import {createBeaconConfig} from "@lodestar/config";
import {chainConfig as chainConfigDef} from "@lodestar/config/default";
import {phase0, ssz} from "@lodestar/types";
import {ProposerSlashingErrorCode} from "../../../../src/chain/errors/proposerSlashingError.js";
import {validateGossipProposerSlashing} from "../../../../src/chain/validation/proposerSlashing.js";
@@ -9,9 +11,10 @@ import {generateCachedState} from "../../../utils/state.js";
describe("validate proposer slashing", () => {
let chainStub: MockedBeaconChain;
let opPool: MockedBeaconChain["opPool"];
const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa));
beforeEach(() => {
chainStub = getMockedBeaconChain();
chainStub = getMockedBeaconChain({config});
opPool = chainStub.opPool;
const state = generateCachedState();

View File

@@ -1,6 +1,6 @@
import {Mock, afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {toHexString} from "@chainsafe/ssz";
import {createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {SLOTS_PER_EPOCH} from "@lodestar/params";
import {Epoch, Slot, altair} from "@lodestar/types";
import {SyncCommitteeErrorCode} from "../../../../src/chain/errors/syncCommitteeError.js";
@@ -20,7 +20,10 @@ describe("Sync Committee Signature validation", () => {
let altairForkEpochBk: Epoch;
const altairForkEpoch = 2020;
const currentSlot = SLOTS_PER_EPOCH * (altairForkEpoch + 1);
const config = createChainForkConfig(Object.assign({}, defaultChainConfig, {ALTAIR_FORK_EPOCH: altairForkEpoch}));
const chainConfig = createChainForkConfig(
Object.assign({}, defaultChainConfig, {ALTAIR_FORK_EPOCH: altairForkEpoch})
);
const config = createBeaconConfig(chainConfig, Buffer.alloc(32, 0xaa));
// all validators have same pubkey
const validatorIndexInSyncCommittee = 15;
@@ -34,7 +37,7 @@ describe("Sync Committee Signature validation", () => {
});
beforeEach(() => {
chain = getMockedBeaconChain();
chain = getMockedBeaconChain({config});
(
chain as {
seenSyncCommitteeMessages: SeenSyncCommitteeMessages;

View File

@@ -1,7 +1,7 @@
import {afterEach, beforeAll, beforeEach, describe, it, vi} from "vitest";
import {SecretKey} from "@chainsafe/blst";
import {createBeaconConfig} from "@lodestar/config";
import {config} from "@lodestar/config/default";
import {BeaconConfig, createBeaconConfig, createChainForkConfig} from "@lodestar/config";
import {chainConfig} from "@lodestar/config/default";
import {DOMAIN_VOLUNTARY_EXIT, FAR_FUTURE_EPOCH, SLOTS_PER_EPOCH} from "@lodestar/params";
import {
CachedBeaconStateAllForks,
@@ -22,6 +22,7 @@ describe("validate voluntary exit", () => {
let state: CachedBeaconStateAllForks;
let signedVoluntaryExit: phase0.SignedVoluntaryExit;
let opPool: MockedBeaconChain["opPool"];
let config: BeaconConfig;
beforeAll(() => {
const sk = SecretKey.fromKeygen(Buffer.alloc(32));
@@ -29,7 +30,7 @@ describe("validate voluntary exit", () => {
const stateEmpty = ssz.phase0.BeaconState.defaultValue();
// Validator has to be active for long enough
stateEmpty.slot = config.SHARD_COMMITTEE_PERIOD * SLOTS_PER_EPOCH;
stateEmpty.slot = chainConfig.SHARD_COMMITTEE_PERIOD * SLOTS_PER_EPOCH;
// Add a validator that's active since genesis and ready to exit
const validator = ssz.phase0.Validator.toViewDU({
@@ -55,13 +56,14 @@ describe("validate voluntary exit", () => {
);
const signingRoot = computeSigningRoot(ssz.phase0.VoluntaryExit, voluntaryExit, domain);
signedVoluntaryExit = {message: voluntaryExit, signature: sk.sign(signingRoot).toBytes()};
const _state = generateState(stateEmpty, config);
const _state = generateState(stateEmpty, createChainForkConfig(chainConfig));
config = createBeaconConfig(chainConfig, _state.genesisValidatorsRoot);
state = createCachedBeaconStateTest(_state, createBeaconConfig(config, _state.genesisValidatorsRoot));
state = createCachedBeaconStateTest(_state, config);
});
beforeEach(() => {
chainStub = getMockedBeaconChain();
chainStub = getMockedBeaconChain({config});
opPool = chainStub.opPool;
vi.spyOn(chainStub, "getHeadStateAtCurrentEpoch").mockResolvedValue(state);
vi.spyOn(opPool, "hasSeenBlsToExecutionChange");

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {ForkSeq, MAX_COMMITTEES_PER_SLOT, MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params";
import {IndexedAttestation, IndexedAttestationBigint} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -9,6 +10,7 @@ import {verifySignatureSet} from "../util/index.js";
* Check if `indexedAttestation` has sorted and unique indices and a valid aggregate signature.
*/
export function isValidIndexedAttestation(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
indexedAttestation: IndexedAttestation,
@@ -19,12 +21,13 @@ export function isValidIndexedAttestation(
}
if (verifySignature) {
return verifySignatureSet(getIndexedAttestationSignatureSet(index2pubkey, state, indexedAttestation));
return verifySignatureSet(getIndexedAttestationSignatureSet(config, index2pubkey, state, indexedAttestation));
}
return true;
}
export function isValidIndexedAttestationBigint(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
indexedAttestation: IndexedAttestationBigint,
@@ -35,7 +38,7 @@ export function isValidIndexedAttestationBigint(
}
if (verifySignature) {
return verifySignatureSet(getIndexedAttestationBigintSignatureSet(index2pubkey, state, indexedAttestation));
return verifySignatureSet(getIndexedAttestationBigintSignatureSet(config, index2pubkey, state, indexedAttestation));
}
return true;
}

View File

@@ -52,6 +52,7 @@ export function processAttestationPhase0(
if (
!isValidIndexedAttestation(
state.config,
epochCtx.index2pubkey,
state,
epochCtx.getIndexedAttestation(ForkSeq.phase0, attestation),

View File

@@ -64,7 +64,13 @@ export function processAttestationsAltair(
// TODO: Why should we verify an indexed attestation that we just created? If it's just for the signature
// we can verify only that and nothing else.
if (verifySignature) {
const sigSet = getAttestationWithIndicesSignatureSet(epochCtx.index2pubkey, state, attestation, attestingIndices);
const sigSet = getAttestationWithIndicesSignatureSet(
state.config,
epochCtx.index2pubkey,
state,
attestation,
attestingIndices
);
if (!verifySignatureSet(sigSet)) {
throw new Error("Attestation signature is not valid");
}

View File

@@ -55,7 +55,7 @@ export function assertValidAttesterSlashing(
// be higher than the clock and the slashing would still be valid. Same applies to attestation data index, which
// can be any arbitrary value. Must use bigint variants to hash correctly to all possible values
for (const [i, attestation] of [attestation1, attestation2].entries()) {
if (!isValidIndexedAttestationBigint(index2pubkey, state, attestation, verifySignatures)) {
if (!isValidIndexedAttestationBigint(state.config, index2pubkey, state, attestation, verifySignatures)) {
throw new Error(`AttesterSlashing attestation${i} is invalid`);
}
}

View File

@@ -77,7 +77,12 @@ export function assertValidProposerSlashing(
// verify signatures
if (verifySignatures) {
const signatureSets = getProposerSlashingSignatureSets(state.epochCtx.index2pubkey, state, proposerSlashing);
const signatureSets = getProposerSlashingSignatureSets(
state.config,
state.epochCtx.index2pubkey,
state,
proposerSlashing
);
for (let i = 0; i < signatureSets.length; i++) {
if (!verifySignatureSet(signatureSets[i])) {
throw new Error(`ProposerSlashing header${i + 1} signature invalid`);

View File

@@ -17,7 +17,7 @@ export function processRandao(state: CachedBeaconStateAllForks, block: BeaconBlo
const randaoReveal = block.body.randaoReveal;
// verify RANDAO reveal
if (verifySignature && !verifyRandaoSignature(epochCtx.index2pubkey, state, block)) {
if (verifySignature && !verifyRandaoSignature(state.config, epochCtx.index2pubkey, state, block)) {
throw new Error("RANDAO reveal is an invalid signature");
}

View File

@@ -1,4 +1,5 @@
import {byteArrayEquals} from "@chainsafe/ssz";
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_SYNC_COMMITTEE, SYNC_COMMITTEE_SIZE} from "@lodestar/params";
import {altair, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -24,7 +25,13 @@ export function processSyncAggregate(
if (verifySignatures) {
// This is to conform to the spec - we want the signature to be verified
const participantIndices = block.body.syncAggregate.syncCommitteeBits.intersectValues(committeeIndices);
const signatureSet = getSyncCommitteeSignatureSet(state.epochCtx.index2pubkey, state, block, participantIndices);
const signatureSet = getSyncCommitteeSignatureSet(
state.config,
state.epochCtx.index2pubkey,
state,
block,
participantIndices
);
// When there's no participation we consider the signature valid and just ignore i
if (signatureSet !== null && !verifySignatureSet(signatureSet)) {
throw Error("Sync committee signature invalid");
@@ -64,6 +71,7 @@ export function processSyncAggregate(
}
export function getSyncCommitteeSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
block: altair.BeaconBlock,
@@ -107,7 +115,7 @@ export function getSyncCommitteeSignatureSet(
throw Error("Empty sync committee signature is not infinity");
}
const domain = state.config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, previousSlot);
const domain = config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, previousSlot);
return {
type: SignatureSetType.aggregate,

View File

@@ -74,7 +74,10 @@ export function getVoluntaryExitValidity(
return VoluntaryExitValidity.pendingWithdrawals;
}
if (verifySignature && !verifyVoluntaryExitSignature(epochCtx.index2pubkey, state, signedVoluntaryExit)) {
if (
verifySignature &&
!verifyVoluntaryExitSignature(state.config, epochCtx.index2pubkey, state, signedVoluntaryExit)
) {
return VoluntaryExitValidity.invalidSignature;
}

View File

@@ -17,6 +17,7 @@ import {
} from "./types.js";
export type BeaconStateCache = {
/** @deprecated should not access config outside of state-transition package */
config: BeaconConfig;
epochCtx: EpochCache;
/** Count of clones created from this BeaconStateCache instance. readonly to prevent accidental usage downstream */

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params";
import {AttesterSlashing, IndexedAttestationBigint, SignedBeaconBlock, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -6,33 +7,36 @@ import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtE
/** Get signature sets from all AttesterSlashing objects in a block */
export function getAttesterSlashingsSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock
): ISignatureSet[] {
return signedBlock.message.body.attesterSlashings.flatMap((attesterSlashing) =>
getAttesterSlashingSignatureSets(index2pubkey, state, attesterSlashing)
getAttesterSlashingSignatureSets(config, index2pubkey, state, attesterSlashing)
);
}
/** Get signature sets from a single AttesterSlashing object */
export function getAttesterSlashingSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
attesterSlashing: AttesterSlashing
): ISignatureSet[] {
return [attesterSlashing.attestation1, attesterSlashing.attestation2].map((attestation) =>
getIndexedAttestationBigintSignatureSet(index2pubkey, state, attestation)
getIndexedAttestationBigintSignatureSet(config, index2pubkey, state, attestation)
);
}
export function getIndexedAttestationBigintSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
indexedAttestation: IndexedAttestationBigint
): ISignatureSet {
const slot = computeStartSlotAtEpoch(Number(indexedAttestation.data.target.epoch as bigint));
const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_ATTESTER, slot);
const domain = config.getDomain(state.slot, DOMAIN_BEACON_ATTESTER, slot);
return {
type: SignatureSetType.aggregate,

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {ForkSeq} from "@lodestar/params";
import {IndexedAttestation, SignedBeaconBlock, altair, capella} from "@lodestar/types";
import {getSyncCommitteeSignatureSet} from "../block/processSyncCommittee.js";
@@ -26,6 +27,7 @@ export * from "./voluntaryExits.js";
* Deposits are not included because they can legally have invalid signatures.
*/
export function getBlockSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock,
@@ -39,20 +41,21 @@ export function getBlockSignatureSets(
const fork = state.config.getForkSeq(signedBlock.message.slot);
const signatureSets = [
getRandaoRevealSignatureSet(index2pubkey, state, signedBlock.message),
...getProposerSlashingsSignatureSets(index2pubkey, state, signedBlock),
...getAttesterSlashingsSignatureSets(index2pubkey, state, signedBlock),
...getAttestationsSignatureSets(index2pubkey, state, signedBlock, indexedAttestations),
...getVoluntaryExitsSignatureSets(index2pubkey, state, signedBlock),
getRandaoRevealSignatureSet(config, index2pubkey, state, signedBlock.message),
...getProposerSlashingsSignatureSets(config, index2pubkey, state, signedBlock),
...getAttesterSlashingsSignatureSets(config, index2pubkey, state, signedBlock),
...getAttestationsSignatureSets(config, index2pubkey, state, signedBlock, indexedAttestations),
...getVoluntaryExitsSignatureSets(config, index2pubkey, state, signedBlock),
];
if (!opts?.skipProposerSignature) {
signatureSets.push(getBlockProposerSignatureSet(index2pubkey, state, signedBlock));
signatureSets.push(getBlockProposerSignatureSet(config, index2pubkey, state, signedBlock));
}
// Only after altair fork, validate tSyncCommitteeSignature
if (fork >= ForkSeq.altair) {
const syncCommitteeSignatureSet = getSyncCommitteeSignatureSet(
config,
index2pubkey,
state as CachedBeaconStateAltair,
(signedBlock as altair.SignedBeaconBlock).message

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params";
import {IndexedAttestation, SignedBeaconBlock, phase0, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -10,16 +11,18 @@ import {
} from "../util/index.js";
export function getAttestationDataSigningRoot(
config: BeaconConfig,
state: CachedBeaconStateAllForks,
data: phase0.AttestationData
): Uint8Array {
const slot = computeStartSlotAtEpoch(data.target.epoch);
const domain = state.config.getDomain(state.slot, DOMAIN_BEACON_ATTESTER, slot);
const domain = config.getDomain(state.slot, DOMAIN_BEACON_ATTESTER, slot);
return computeSigningRoot(ssz.phase0.AttestationData, data, domain);
}
export function getAttestationWithIndicesSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
attestation: Pick<phase0.Attestation, "data" | "signature">,
@@ -27,17 +30,19 @@ export function getAttestationWithIndicesSignatureSet(
): ISignatureSet {
return createAggregateSignatureSetFromComponents(
attestingIndices.map((i) => index2pubkey[i]),
getAttestationDataSigningRoot(state, attestation.data),
getAttestationDataSigningRoot(config, state, attestation.data),
attestation.signature
);
}
export function getIndexedAttestationSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
indexedAttestation: IndexedAttestation
): ISignatureSet {
return getAttestationWithIndicesSignatureSet(
config,
index2pubkey,
state,
indexedAttestation,
@@ -46,6 +51,7 @@ export function getIndexedAttestationSignatureSet(
}
export function getAttestationsSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock,
@@ -57,6 +63,6 @@ export function getAttestationsSignatureSets(
);
}
return indexedAttestations.map((indexedAttestation) =>
getIndexedAttestationSignatureSet(index2pubkey, state, indexedAttestation)
getIndexedAttestationSignatureSet(config, index2pubkey, state, indexedAttestation)
);
}

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
import {SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, isBlindedBeaconBlock, phase0, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -6,20 +7,21 @@ import {computeSigningRoot} from "../util/index.js";
import {ISignatureSet, SignatureSetType, verifySignatureSet} from "../util/signatureSets.js";
export function verifyProposerSignature(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock
): boolean {
const signatureSet = getBlockProposerSignatureSet(index2pubkey, state, signedBlock);
const signatureSet = getBlockProposerSignatureSet(config, index2pubkey, state, signedBlock);
return verifySignatureSet(signatureSet);
}
export function getBlockProposerSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock
): ISignatureSet {
const {config} = state;
const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlock.message.slot);
const blockType = isBlindedBeaconBlock(signedBlock.message)

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -8,6 +9,7 @@ import {ISignatureSet, SignatureSetType, computeSigningRoot} from "../util/index
* Extract signatures to allow validating all block signatures at once
*/
export function getProposerSlashingSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
proposerSlashing: phase0.ProposerSlashing
@@ -17,11 +19,7 @@ export function getProposerSlashingSignatureSets(
// In state transition, ProposerSlashing headers are only partially validated. Their slot could be higher than the
// clock and the slashing would still be valid. Must use bigint variants to hash correctly to all possible values
return [proposerSlashing.signedHeader1, proposerSlashing.signedHeader2].map((signedHeader): ISignatureSet => {
const domain = state.config.getDomain(
state.slot,
DOMAIN_BEACON_PROPOSER,
Number(signedHeader.message.slot as bigint)
);
const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, Number(signedHeader.message.slot as bigint));
return {
type: SignatureSetType.single,
@@ -33,11 +31,12 @@ export function getProposerSlashingSignatureSets(
}
export function getProposerSlashingsSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock
): ISignatureSet[] {
return signedBlock.message.body.proposerSlashings.flatMap((proposerSlashing) =>
getProposerSlashingSignatureSets(index2pubkey, state, proposerSlashing)
getProposerSlashingSignatureSets(config, index2pubkey, state, proposerSlashing)
);
}

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_RANDAO} from "@lodestar/params";
import {BeaconBlock, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
@@ -11,24 +12,26 @@ import {
} from "../util/index.js";
export function verifyRandaoSignature(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
block: BeaconBlock
): boolean {
return verifySignatureSet(getRandaoRevealSignatureSet(index2pubkey, state, block));
return verifySignatureSet(getRandaoRevealSignatureSet(config, index2pubkey, state, block));
}
/**
* Extract signatures to allow validating all block signatures at once
*/
export function getRandaoRevealSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
block: BeaconBlock
): ISignatureSet {
// should not get epoch from epochCtx
const epoch = computeEpochAtSlot(block.slot);
const domain = state.config.getDomain(state.slot, DOMAIN_RANDAO, block.slot);
const domain = config.getDomain(state.slot, DOMAIN_RANDAO, block.slot);
return {
type: SignatureSetType.single,

View File

@@ -1,3 +1,4 @@
import {BeaconConfig} from "@lodestar/config";
import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types";
import {Index2PubkeyCache} from "../cache/pubkeyCache.js";
import {CachedBeaconStateAllForks} from "../types.js";
@@ -10,23 +11,25 @@ import {
} from "../util/index.js";
export function verifyVoluntaryExitSignature(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedVoluntaryExit: phase0.SignedVoluntaryExit
): boolean {
return verifySignatureSet(getVoluntaryExitSignatureSet(index2pubkey, state, signedVoluntaryExit));
return verifySignatureSet(getVoluntaryExitSignatureSet(config, index2pubkey, state, signedVoluntaryExit));
}
/**
* Extract signatures to allow validating all block signatures at once
*/
export function getVoluntaryExitSignatureSet(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedVoluntaryExit: phase0.SignedVoluntaryExit
): ISignatureSet {
const slot = computeStartSlotAtEpoch(signedVoluntaryExit.message.epoch);
const domain = state.config.getDomainForVoluntaryExit(state.slot, slot);
const domain = config.getDomainForVoluntaryExit(state.slot, slot);
return {
type: SignatureSetType.single,
@@ -37,11 +40,12 @@ export function getVoluntaryExitSignatureSet(
}
export function getVoluntaryExitsSignatureSets(
config: BeaconConfig,
index2pubkey: Index2PubkeyCache,
state: CachedBeaconStateAllForks,
signedBlock: SignedBeaconBlock
): ISignatureSet[] {
return signedBlock.message.body.voluntaryExits.map((voluntaryExit) =>
getVoluntaryExitSignatureSet(index2pubkey, state, voluntaryExit)
getVoluntaryExitSignatureSet(config, index2pubkey, state, voluntaryExit)
);
}

View File

@@ -111,7 +111,10 @@ export function stateTransition(
postState = processSlotsWithTransientCache(postState, blockSlot, options, {metrics, validatorMonitor});
// Verify proposer signature only
if (verifyProposer && !verifyProposerSignature(postState.epochCtx.index2pubkey, postState, signedBlock)) {
if (
verifyProposer &&
!verifyProposerSignature(postState.config, postState.epochCtx.index2pubkey, postState, signedBlock)
) {
throw new Error("Invalid block signature");
}

View File

@@ -45,7 +45,7 @@ describe("validate indexed attestation", () => {
data: attestationData,
signature: EMPTY_SIGNATURE,
};
expect(isValidIndexedAttestation(state.epochCtx.index2pubkey, state, indexedAttestation, false)).toBe(
expect(isValidIndexedAttestation(state.config, state.epochCtx.index2pubkey, state, indexedAttestation, false)).toBe(
expectedValue
);
});

View File

@@ -70,7 +70,13 @@ describe("signatureSets", () => {
state.epochCtx.getIndexedAttestation(fork, attestation)
);
const signatureSets = getBlockSignatureSets(state.epochCtx.index2pubkey, state, signedBlock, indexedAttestations);
const signatureSets = getBlockSignatureSets(
state.config,
state.epochCtx.index2pubkey,
state,
signedBlock,
indexedAttestations
);
expect(signatureSets.length).toBe(
// block signature
1 +