Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions packages/state-transition/src/block/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ForkSeq} from "@lodestar/params";
import {BeaconBlock, BlindedBeaconBlock, altair, capella, gloas} from "@lodestar/types";
import {ForkPostGloas, ForkSeq} from "@lodestar/params";
import {BeaconBlock, BlindedBeaconBlock, altair, capella} from "@lodestar/types";
import {BeaconStateTransitionMetrics} from "../metrics.js";
import {
CachedBeaconStateAllForks,
Expand Down Expand Up @@ -76,7 +76,7 @@ export function processBlock(
}

if (fork >= ForkSeq.gloas) {
processExecutionPayloadBid(state as CachedBeaconStateGloas, block as gloas.BeaconBlock);
processExecutionPayloadBid(state as CachedBeaconStateGloas, block as BeaconBlock<ForkPostGloas>);
}

processRandao(state, block, verifySignatures);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {FAR_FUTURE_EPOCH, ForkSeq, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params";
import {electra, ssz} from "@lodestar/types";
import {CachedBeaconStateElectra} from "../types.js";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
import {hasEth1WithdrawalCredential} from "../util/capella.js";
import {
hasCompoundingWithdrawalCredential,
Expand All @@ -14,7 +14,7 @@ import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidat
// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest
export function processConsolidationRequest(
fork: ForkSeq,
state: CachedBeaconStateElectra,
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
consolidationRequest: electra.ConsolidationRequest
): void {
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
Expand Down Expand Up @@ -104,7 +104,7 @@ export function processConsolidationRequest(
* Determine if we should set consolidation target validator to compounding credential
*/
function isValidSwitchToCompoundRequest(
state: CachedBeaconStateElectra,
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
consolidationRequest: electra.ConsolidationRequest
): boolean {
const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest;
Expand Down
7 changes: 5 additions & 2 deletions packages/state-transition/src/block/processDepositRequest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params";
import {electra, ssz} from "@lodestar/types";
import {CachedBeaconStateElectra} from "../types.js";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";

export function processDepositRequest(state: CachedBeaconStateElectra, depositRequest: electra.DepositRequest): void {
export function processDepositRequest(
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
depositRequest: electra.DepositRequest
): void {
if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) {
state.depositRequestsStartIndex = depositRequest.index;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,7 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
state.builderPendingPayments.set(SLOTS_PER_EPOCH + (bid.slot % SLOTS_PER_EPOCH), pendingPaymentView);
}

state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU({
...bid,
});
state.latestExecutionPayloadBid = ssz.gloas.ExecutionPayloadBid.toViewDU(bid);
}

function verifyExecutionPayloadBidSignature(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {PublicKey, Signature, verify} from "@chainsafe/blst";
import {byteArrayEquals} from "@chainsafe/ssz";
import {DOMAIN_BEACON_BUILDER, ForkSeq, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {DOMAIN_BEACON_BUILDER, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {gloas, ssz} from "@lodestar/types";
import {toHex} from "@lodestar/utils";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.ts";
import {toHex, toRootHex} from "@lodestar/utils";
import {CachedBeaconStateGloas} from "../types.ts";
import {computeExitEpochAndUpdateChurn, computeSigningRoot, computeTimeAtSlot} from "../util/index.ts";
import {processConsolidationRequest} from "./processConsolidationRequest.ts";
import {processDepositRequest} from "./processDepositRequest.ts";
Expand All @@ -17,6 +17,7 @@ export function processExecutionPayloadEnvelope(
): void {
const envelope = signedEnvelope.message;
const payload = envelope.payload;
const fork = state.config.getForkSeq(envelope.slot);

if (verify) {
const builderIndex = envelope.builderIndex;
Expand All @@ -32,18 +33,15 @@ export function processExecutionPayloadEnvelope(
const requests = envelope.executionRequests;

for (const deposit of requests.deposits) {
// TODO GLOAS: Fix type or properly cast it to the correct state type
processDepositRequest(state as unknown as CachedBeaconStateElectra, deposit);
processDepositRequest(state, deposit);
}

for (const withdrawal of requests.withdrawals) {
// TODO GLOAS: Fix type or properly cast it to the correct state type
processWithdrawalRequest(ForkSeq.gloas, state as unknown as CachedBeaconStateElectra, withdrawal);
processWithdrawalRequest(fork, state, withdrawal);
}

for (const consolidation of requests.consolidations) {
// TODO GLOAS: Fix type or properly cast it to the correct state type
processConsolidationRequest(ForkSeq.gloas, state as unknown as CachedBeaconStateElectra, consolidation);
processConsolidationRequest(fork, state, consolidation);
}

// Queue the builder payment
Expand All @@ -66,7 +64,7 @@ export function processExecutionPayloadEnvelope(

if (verify && !byteArrayEquals(envelope.stateRoot, state.hashTreeRoot())) {
throw new Error(
`Envelope's state root does not match state envelope=${envelope.stateRoot} state=${state.hashTreeRoot()}`
`Envelope's state root does not match state envelope=${toRootHex(envelope.stateRoot)} state=${toRootHex(state.hashTreeRoot())}`
);
}
}
Expand All @@ -85,7 +83,7 @@ function validateExecutionPayloadEnvelope(
// Verify consistency with the beacon block
if (!byteArrayEquals(envelope.beaconBlockRoot, state.latestBlockHeader.hashTreeRoot())) {
throw new Error(
`Envelope's block is not the latest block header envelope=${toHex(envelope.beaconBlockRoot)} latestBlockHeader=${toHex(state.latestBlockHeader.hashTreeRoot())}`
`Envelope's block is not the latest block header envelope=${toRootHex(envelope.beaconBlockRoot)} latestBlockHeader=${toRootHex(state.latestBlockHeader.hashTreeRoot())}`
);
}

Expand All @@ -106,15 +104,15 @@ function validateExecutionPayloadEnvelope(
const envelopeKzgRoot = ssz.deneb.BlobKzgCommitments.hashTreeRoot(envelope.blobKzgCommitments);
if (!byteArrayEquals(committedBid.blobKzgCommitmentsRoot, envelopeKzgRoot)) {
throw new Error(
`Kzg commitment root mismatch between envelope and committed bid envelope=${toHex(envelopeKzgRoot)} committedBid=${toHex(committedBid.blobKzgCommitmentsRoot)}`
`Kzg commitment root mismatch between envelope and committed bid envelope=${toRootHex(envelopeKzgRoot)} committedBid=${toRootHex(committedBid.blobKzgCommitmentsRoot)}`
);
}

// Verify the withdrawals root
const envelopeWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(envelope.payload.withdrawals);
if (!byteArrayEquals(state.latestWithdrawalsRoot, envelopeWithdrawalsRoot)) {
throw new Error(
`Withdrawals root mismatch between envelope and latest withdrawals root envelope=${toHex(envelopeWithdrawalsRoot)} latestWithdrawalRoot=${toHex(state.latestWithdrawalsRoot)}`
`Withdrawals root mismatch between envelope and latest withdrawals root envelope=${toRootHex(envelopeWithdrawalsRoot)} latestWithdrawalRoot=${toRootHex(state.latestWithdrawalsRoot)}`
);
}

Expand All @@ -128,14 +126,14 @@ function validateExecutionPayloadEnvelope(
// Verify the block hash
if (!byteArrayEquals(committedBid.blockHash, payload.blockHash)) {
throw new Error(
`Block hash mismatch between envelope's payload and committed bid envelope=${toHex(payload.blockHash)} committedBid=${toHex(committedBid.blockHash)}`
`Block hash mismatch between envelope's payload and committed bid envelope=${toRootHex(payload.blockHash)} committedBid=${toRootHex(committedBid.blockHash)}`
);
}

// Verify consistency of the parent hash with respect to the previous execution payload
if (!byteArrayEquals(payload.parentHash, state.latestBlockHash)) {
throw new Error(
`Parent hash mismatch between envelope's payload and state envelope=${toHex(payload.parentHash)} state=${toHex(state.latestBlockHash)}`
`Parent hash mismatch between envelope's payload and state envelope=${toRootHex(payload.parentHash)} state=${toRootHex(state.latestBlockHash)}`
);
}

Expand All @@ -154,9 +152,10 @@ function validateExecutionPayloadEnvelope(
}

// Verify commitments are under limit
if (envelope.blobKzgCommitments.length > state.config.getMaxBlobsPerBlock(state.epochCtx.epoch)) {
const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(state.epochCtx.epoch);
if (envelope.blobKzgCommitments.length > maxBlobsPerBlock) {
throw new Error(
`Kzg commitments exceed limit commitment.length=${envelope.blobKzgCommitments.length} limit=${state.config.getBlobParameters(state.epochCtx.epoch).maxBlobsPerBlock}`
`Kzg commitments exceed limit commitment.length=${envelope.blobKzgCommitments.length} limit=${maxBlobsPerBlock}`
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
} from "@lodestar/params";
import {electra, phase0, ssz} from "@lodestar/types";
import {toHex} from "@lodestar/utils";
import {CachedBeaconStateElectra} from "../types.js";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js";
import {computeExitEpochAndUpdateChurn} from "../util/epoch.js";
import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js";
import {initiateValidatorExit} from "./initiateValidatorExit.js";

export function processWithdrawalRequest(
fork: ForkSeq,
state: CachedBeaconStateElectra,
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
withdrawalRequest: electra.WithdrawalRequest
): void {
const amount = Number(withdrawalRequest.amount);
Expand Down Expand Up @@ -81,7 +81,7 @@ export function processWithdrawalRequest(
function isValidatorEligibleForWithdrawOrExit(
validator: phase0.Validator,
sourceAddress: Uint8Array,
state: CachedBeaconStateElectra
state: CachedBeaconStateElectra | CachedBeaconStateGloas
): boolean {
const {withdrawalCredentials} = validator;
const addressStr = toHex(withdrawalCredentials.subarray(12));
Expand Down
20 changes: 11 additions & 9 deletions packages/state-transition/src/block/processWithdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export function getExpectedWithdrawals(
? allBuilderPendingWithdrawals[i]
: stateGloas.builderPendingWithdrawals.getReadonly(i);

if (withdrawal.withdrawableEpoch > epoch || withdrawals.length === MAX_WITHDRAWALS_PER_PAYLOAD) {
if (withdrawal.withdrawableEpoch > epoch || withdrawals.length + 1 === MAX_WITHDRAWALS_PER_PAYLOAD) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This loop condition appears to have an off-by-one error. The check withdrawals.length + 1 === MAX_WITHDRAWALS_PER_PAYLOAD will cause the loop to break when withdrawals.length is MAX_WITHDRAWALS_PER_PAYLOAD - 1, resulting in one less withdrawal being processed than the maximum allowed.

For example, if MAX_WITHDRAWALS_PER_PAYLOAD is 16 and withdrawals.length becomes 15, this condition 15 + 1 === 16 will be true, and the loop will break, leaving only 15 withdrawals in the list instead of the maximum of 16.

The condition should probably be withdrawals.length >= MAX_WITHDRAWALS_PER_PAYLOAD to allow the list to be filled up to its maximum capacity. This is also consistent with the check used later in this function.

Suggested change
if (withdrawal.withdrawableEpoch > epoch || withdrawals.length + 1 === MAX_WITHDRAWALS_PER_PAYLOAD) {
if (withdrawal.withdrawableEpoch > epoch || withdrawals.length >= MAX_WITHDRAWALS_PER_PAYLOAD) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's same as the spec here

  for withdrawal in state.builder_pending_withdrawals:
        if (
            withdrawal.withdrawable_epoch > epoch
            or len(withdrawals) + 1 == MAX_WITHDRAWALS_PER_PAYLOAD
        ):

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised spec test didn't catch this bug

break;
}

Expand All @@ -173,14 +173,16 @@ export function getExpectedWithdrawals(
balance - MIN_ACTIVATION_BALANCE < withdrawal.amount ? balance - MIN_ACTIVATION_BALANCE : withdrawal.amount;
}

withdrawals.push({
index: withdrawalIndex,
validatorIndex: withdrawal.builderIndex,
address: withdrawal.feeRecipient,
amount: BigInt(withdrawableBalance),
});
withdrawalIndex++;
withdrawnBalances.set(withdrawal.builderIndex, totalWithdrawn + withdrawableBalance);
if (withdrawableBalance > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am surprised spec test didn't catch this bug

withdrawals.push({
index: withdrawalIndex,
validatorIndex: withdrawal.builderIndex,
address: withdrawal.feeRecipient,
amount: BigInt(withdrawableBalance),
});
withdrawalIndex++;
withdrawnBalances.set(withdrawal.builderIndex, totalWithdrawn + withdrawableBalance);
}
}
processedBuilderWithdrawalsCount++;
}
Expand Down
16 changes: 8 additions & 8 deletions packages/state-transition/src/epoch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ export function processEpoch(
}
}

if (fork >= ForkSeq.gloas) {
const timer = metrics?.epochTransitionStepTime.startTimer({
step: EpochTransitionStep.processBuilderPendingPayments,
});
processBuilderPendingPayments(state as CachedBeaconStateGloas);
timer?.();
}

{
const timer = metrics?.epochTransitionStepTime.startTimer({
step: EpochTransitionStep.processEffectiveBalanceUpdates,
Expand Down Expand Up @@ -203,12 +211,4 @@ export function processEpoch(
processProposerLookahead(fork, state as CachedBeaconStateFulu, cache);
timer?.();
}

if (fork >= ForkSeq.gloas) {
const timer = metrics?.epochTransitionStepTime.startTimer({
step: EpochTransitionStep.processBuilderPendingPayments,
});
processBuilderPendingPayments(state as CachedBeaconStateGloas);
timer?.();
}
}
16 changes: 11 additions & 5 deletions packages/state-transition/src/util/electra.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {COMPOUNDING_WITHDRAWAL_PREFIX, GENESIS_SLOT, MIN_ACTIVATION_BALANCE} from "@lodestar/params";
import {ValidatorIndex, ssz} from "@lodestar/types";
import {G2_POINT_AT_INFINITY} from "../constants/constants.js";
import {CachedBeaconStateElectra} from "../types.js";
import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
import {hasEth1WithdrawalCredential} from "./capella.js";
import {hasBuilderWithdrawalCredential} from "./gloas.ts";

Expand All @@ -17,7 +17,10 @@ export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Arr
);
}

export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void {
export function switchToCompoundingValidator(
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
index: ValidatorIndex
): void {
const validator = state.validators.get(index);

// directly modifying the byte leads to ssz missing the modification resulting into
Expand All @@ -33,7 +36,10 @@ export function switchToCompoundingValidator(state: CachedBeaconStateElectra, in
queueExcessActiveBalance(state, index);
}

export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: ValidatorIndex): void {
export function queueExcessActiveBalance(
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
index: ValidatorIndex
): void {
const balance = state.balances.get(index);
if (balance > MIN_ACTIVATION_BALANCE) {
const validator = state.validators.getReadonly(index);
Expand All @@ -56,15 +62,15 @@ export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index:
/**
* Since we share pubkey2index, pubkey maybe added by other epoch transition but we don't have that validator in this state
*/
export function isPubkeyKnown(state: CachedBeaconStateElectra, pubkey: Uint8Array): boolean {
export function isPubkeyKnown(state: CachedBeaconStateElectra | CachedBeaconStateGloas, pubkey: Uint8Array): boolean {
return isValidatorKnown(state, state.epochCtx.getValidatorIndex(pubkey));
}

/**
* Since we share pubkey2index, validatorIndex maybe not null but we don't have that validator in this state
*/
export function isValidatorKnown(
state: CachedBeaconStateElectra,
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
index: ValidatorIndex | null
): index is ValidatorIndex {
return index !== null && index < state.validators.length;
Expand Down
2 changes: 1 addition & 1 deletion packages/state-transition/src/util/epoch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function computeExitEpochAndUpdateChurn(
}

export function computeConsolidationEpochAndUpdateChurn(
state: CachedBeaconStateElectra,
state: CachedBeaconStateElectra | CachedBeaconStateGloas,
consolidationBalance: Gwei
): number {
let earliestConsolidationEpoch = Math.max(
Expand Down
1 change: 0 additions & 1 deletion packages/state-transition/src/util/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export function isMergeTransitionBlock(state: BeaconStateExecutions, body: bella
* Merge is complete when the state includes execution layer data:
* state.latestExecutionPayloadHeader NOT EMPTY
*/
// TODO GLOAS: See if we need to update this for gloas
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should just remove the merge transition code, it's probably simpler than making it somehow work with gloas

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created #8661 to track it

export function isMergeTransitionComplete(state: BeaconStateExecutions): boolean {
if (!isCapellaStateType(state)) {
return !ssz.bellatrix.ExecutionPayloadHeader.equals(
Expand Down
Loading