Skip to content

Commit

Permalink
Merge branch 'nc/devnet-4' into nc/spec-cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ensi321 authored Oct 12, 2024
2 parents 4fc7ee0 + 4f3676a commit 17ffdf7
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ export async function produceBlockBody<T extends BlockType>(
} else {
blobsResult = {type: BlobsResultType.preDeneb};
}

if (ForkSeq[fork] >= ForkSeq.electra) {
const {executionRequests} = builderRes;
if (executionRequests === undefined) {
throw Error(`Invalid builder getHeader response for fork=${fork}, missing executionRequests`);
}
(blockBody as electra.BlindedBeaconBlockBody).executionRequests = executionRequests;
}
}

// blockType === BlockType.Full
Expand Down Expand Up @@ -455,6 +463,7 @@ async function prepareExecutionPayloadHeader(
header: ExecutionPayloadHeader;
executionPayloadValue: Wei;
blobKzgCommitments?: deneb.BlobKzgCommitments;
executionRequests?: electra.ExecutionRequests;
}> {
if (!chain.executionBuilder) {
throw Error("executionBuilder required");
Expand Down
5 changes: 4 additions & 1 deletion packages/beacon-node/src/execution/builder/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SignedBeaconBlockOrContents,
SignedBlindedBeaconBlock,
ExecutionPayloadHeader,
electra,
} from "@lodestar/types";
import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
Expand Down Expand Up @@ -114,6 +115,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
header: ExecutionPayloadHeader;
executionPayloadValue: Wei;
blobKzgCommitments?: deneb.BlobKzgCommitments;
executionRequests?: electra.ExecutionRequests;
}> {
const signedBuilderBid = (await this.api.getHeader({slot, parentHash, proposerPubkey})).value();

Expand All @@ -123,7 +125,8 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {

const {header, value: executionPayloadValue} = signedBuilderBid.message;
const {blobKzgCommitments} = signedBuilderBid.message as deneb.BuilderBid;
return {header, executionPayloadValue, blobKzgCommitments};
const {executionRequests} = signedBuilderBid.message as electra.BuilderBid;
return {header, executionPayloadValue, blobKzgCommitments, executionRequests};
}

async submitBlindedBlock(signedBlindedBlock: SignedBlindedBeaconBlock): Promise<SignedBeaconBlockOrContents> {
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/execution/builder/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SignedBeaconBlockOrContents,
ExecutionPayloadHeader,
SignedBlindedBeaconBlock,
electra,
} from "@lodestar/types";
import {ForkExecution} from "@lodestar/params";

Expand Down Expand Up @@ -36,6 +37,7 @@ export interface IExecutionBuilder {
header: ExecutionPayloadHeader;
executionPayloadValue: Wei;
blobKzgCommitments?: deneb.BlobKzgCommitments;
executionRequests?: electra.ExecutionRequests;
}>;
submitBlindedBlock(signedBlock: SignedBlindedBeaconBlock): Promise<SignedBeaconBlockOrContents>;
}
2 changes: 1 addition & 1 deletion packages/beacon-node/test/spec/specTestVersioning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const ethereumConsensusSpecsTests: DownloadTestsOptions = {
specVersion: "v1.5.0-alpha.7",
specVersion: "v1.5.0-alpha.8",
// Target directory is the host package root: 'packages/*/spec-tests'
outputDir: path.join(__dirname, "../../spec-tests"),
specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests",
Expand Down
80 changes: 69 additions & 11 deletions packages/state-transition/src/block/processConsolidationRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,38 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT}

import {CachedBeaconStateElectra} from "../types.js";
import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js";
import {hasExecutionWithdrawalCredential} from "../util/electra.js";
import {hasExecutionWithdrawalCredential, switchToCompoundingValidator} from "../util/electra.js";
import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js";
import {hasEth1WithdrawalCredential} from "../util/capella.js";

// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
export function processConsolidationRequest(
state: CachedBeaconStateElectra,
consolidationRequest: electra.ConsolidationRequest
): void {
// If the pending consolidations queue is full, consolidation requests are ignored
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);

if (sourceIndex === null || targetIndex === null) {
return;
}

// If there is too little available consolidation churn limit, consolidation requests are ignored
if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
if (isValidSwitchToCompoundRequest(state, consolidationRequest)) {
switchToCompoundingValidator(state, sourceIndex);
// Early return since we have already switched validator to compounding
return;
}

const {sourcePubkey, targetPubkey} = consolidationRequest;
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);

if (sourceIndex === null || targetIndex === null) {
// If the pending consolidations queue is full, consolidation requests are ignored
if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) {
return;
}

// If there is too little available consolidation churn limit, consolidation requests are ignored
if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) {
return;
}
// Verify that source != target, so a consolidation cannot be used as an exit.
if (sourceIndex === targetIndex) {
return;
Expand All @@ -46,7 +53,7 @@ export function processConsolidationRequest(
return;
}

if (Buffer.compare(sourceWithdrawalAddress, consolidationRequest.sourceAddress) !== 0) {
if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) {
return;
}

Expand All @@ -70,4 +77,55 @@ export function processConsolidationRequest(
targetIndex,
});
state.pendingConsolidations.push(pendingConsolidation);

// Churn any target excess active balance of target and raise its max
if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) {
switchToCompoundingValidator(state, targetIndex);
}
}

/**
* Determine if we should set consolidation target validator to compounding credential
*/
function isValidSwitchToCompoundRequest(
state: CachedBeaconStateElectra,
consolidationRequest: electra.ConsolidationRequest
): boolean {
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey);
const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey);

// Verify pubkey exists
if (sourceIndex === null) {
return false;
}

// Switch to compounding requires source and target be equal
if (sourceIndex !== targetIndex) {
return false;
}

const sourceValidator = state.validators.getReadonly(sourceIndex);
const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12);
// Verify request has been authorized
if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) {
return false;
}

// Verify source withdrawal credentials
if (!hasEth1WithdrawalCredential(sourceValidator.withdrawalCredentials)) {
return false;
}

// Verify the source is active
if (!isActiveValidator(sourceValidator, state.epochCtx.epoch)) {
return false;
}

// Verify exit for source has not been initiated
if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) {
return false;
}

return true;
}
8 changes: 0 additions & 8 deletions packages/state-transition/src/cache/epochTransitionCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,6 @@ export interface EpochTransitionCache {
*/
validators: phase0.Validator[];

/**
* This is for electra only
* Validators that're switched to compounding during processPendingConsolidations(), not available in beforeProcessEpoch()
*/
newCompoundingValidators?: Set<ValidatorIndex>;

/**
* balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates().
* processRewardsAndPenalties() already has a regular Javascript array of balances.
Expand Down Expand Up @@ -518,8 +512,6 @@ export function beforeProcessEpoch(
inclusionDelays,
flags,
validators,
// will be assigned in processPendingConsolidations()
newCompoundingValidators: undefined,
// Will be assigned in processRewardsAndPenalties()
balances: undefined,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {ValidatorIndex} from "@lodestar/types";
import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js";
import {decreaseBalance, increaseBalance} from "../util/balance.js";
import {getMaxEffectiveBalance} from "../util/validator.js";
import {switchToCompoundingValidator} from "../util/electra.js";

/**
* Starting from Electra:
Expand All @@ -22,7 +20,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
let nextPendingConsolidation = 0;
const validators = state.validators;
const cachedBalances = cache.balances;
const newCompoundingValidators = new Set<ValidatorIndex>();

for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) {
const {sourceIndex, targetIndex} = pendingConsolidation;
Expand All @@ -36,9 +33,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
if (sourceValidator.withdrawableEpoch > nextEpoch) {
break;
}
// Churn any target excess active balance of target and raise its max
switchToCompoundingValidator(state, targetIndex);
newCompoundingValidators.add(targetIndex);
// Move active balance to target. Excess balance is withdrawable.
const maxEffectiveBalance = getMaxEffectiveBalance(state.validators.getReadonly(sourceIndex).withdrawalCredentials);
const sourceEffectiveBalance = Math.min(state.balances.get(sourceIndex), maxEffectiveBalance);
Expand All @@ -52,6 +46,5 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca
nextPendingConsolidation++;
}

cache.newCompoundingValidators = newCompoundingValidators;
state.pendingConsolidations = state.pendingConsolidations.sliceFrom(nextPendingConsolidation);
}
16 changes: 7 additions & 9 deletions packages/state-transition/src/util/electra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Arr
export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void {
const validator = state.validators.get(index);

if (hasEth1WithdrawalCredential(validator.withdrawalCredentials)) {
// directly modifying the byte leads to ssz missing the modification resulting into
// wrong root compute, although slicing can be avoided but anyway this is not going
// to be a hot path so its better to clean slice and avoid side effects
const newWithdrawalCredentials = validator.withdrawalCredentials.slice();
newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX;
validator.withdrawalCredentials = newWithdrawalCredentials;
queueExcessActiveBalance(state, index);
}
// directly modifying the byte leads to ssz missing the modification resulting into
// wrong root compute, although slicing can be avoided but anyway this is not going
// to be a hot path so its better to clean slice and avoid side effects
const newWithdrawalCredentials = validator.withdrawalCredentials.slice();
newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX;
validator.withdrawalCredentials = newWithdrawalCredentials;
queueExcessActiveBalance(state, index);
}

export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: ValidatorIndex): void {
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/electra/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export const BlindedBeaconBlockBody = new ContainerType(
executionPayloadHeader: ExecutionPayloadHeader,
blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges,
blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments,
executionRequests: ExecutionRequests, // New in ELECTRA
},
{typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);
Expand All @@ -235,6 +236,7 @@ export const BuilderBid = new ContainerType(
{
header: ExecutionPayloadHeader, // Modified in ELECTRA
blindedBlobsBundle: denebSsz.BlobKzgCommitments,
executionRequests: ExecutionRequests, // New in ELECTRA
value: UintBn256,
pubkey: BLSPubkey,
},
Expand Down

0 comments on commit 17ffdf7

Please sign in to comment.