Skip to content
Draft
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
5 changes: 4 additions & 1 deletion packages/beacon-node/src/chain/errors/blockError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export enum BlockErrorCode {
DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE",
/** Block contains too many kzg commitments */
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
/** Bid parent block root does not match block parent root */
BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
}

type ExecutionErrorStatus = Exclude<
Expand Down Expand Up @@ -111,7 +113,8 @@ export type BlockErrorType =
| {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number}
| {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string}
| {code: BlockErrorCode.DATA_UNAVAILABLE}
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number};
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
| {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex};

export class BlockGossipError extends GossipActionError<BlockErrorType> {}

Expand Down
33 changes: 30 additions & 3 deletions packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
isExecutionEnabled,
isExecutionStateType,
} from "@lodestar/state-transition";
import {SignedBeaconBlock, deneb} from "@lodestar/types";
import {sleep, toRootHex} from "@lodestar/utils";
import {SignedBeaconBlock, deneb, gloas} from "@lodestar/types";
import {byteArrayEquals, sleep, toRootHex} from "@lodestar/utils";
import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js";
import {IBeaconChain} from "../interface.js";
import {RegenCaller} from "../regen/index.js";
Expand Down Expand Up @@ -123,12 +123,39 @@ export async function validateGossipBlock(
}
}

if (isForkPostGloas(fork)) {
const bid = (block as gloas.BeaconBlock).body.signedExecutionPayloadBid.message;

// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer
// -- i.e. validate that len(bid.blob_kzg_commitments) <= max_blobs_per_block
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
const maxBlobsPerBlock = config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,
blobKzgCommitmentsLen,
commitmentLimit: maxBlobsPerBlock,
});
}

// [REJECT] The bid's parent (defined by bid.parent_block_root) equals the block's parent (defined by block.parent_root)
if (!byteArrayEquals(bid.parentBlockRoot, block.parentRoot)) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH,
bidParentRoot: toRootHex(bid.parentBlockRoot),
blockParentRoot: parentRoot,
});
}

// TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
// This requires execution engine integration to verify the parent block hash
}

// use getPreState to reload state if needed. It also checks for whether the current finalized checkpoint is an ancestor of the block.
// As a result, we throw an IGNORE (whereas the spec says we should REJECT for this scenario).
// this is something we should change this in the future to make the code airtight to the spec.
// [IGNORE] The block's parent (defined by block.parent_root) has been seen (via both gossip and non-gossip sources) (a client MAY queue blocks for processing once the parent block is retrieved).
// [REJECT] The block's parent (defined by block.parent_root) passes validation.
// TODO GLOAS: post-gloas, we check the validity of bid's parent payload, not the entire beacon block
const blockState = await chain.regen
.getPreState(block, {dontTransferCache: true}, RegenCaller.validateGossipBlock)
.catch(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ async function validateExecutionPayloadEnvelope(
// [IGNORE] The envelope's block root `envelope.block_root` has been seen (via
// gossip or non-gossip sources) (a client MAY queue payload for processing once
// the block is retrieved).
// TODO GLOAS: Need to review this
// TODO GLOAS: Need to review this, we should queue the envelope for later
// processing if the block is not yet known, otherwise we would ignore it here
const block = chain.forkChoice.getBlock(envelope.beaconBlockRoot);
if (block === null) {
throw new ExecutionPayloadEnvelopeError(GossipAction.IGNORE, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function validatePayloadAttestationMessage(
// [REJECT] `payload_attestation_message.signature` is valid with respect to the validator's public key.
const signatureSet = createSingleSignatureSetFromComponents(
chain.index2pubkey[validatorIndex],
getPayloadAttestationDataSigningRoot(chain.config, state.slot, data),
getPayloadAttestationDataSigningRoot(chain.config, data),
payloadAttestationMessage.signature
);

Expand Down
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 @@ -14,7 +14,7 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const ethereumConsensusSpecsTests: DownloadTestsOptions = {
specVersion: "v1.7.0-alpha.1",
specVersion: "v1.7.0-alpha.2",
// Target directory is the host package root: 'packages/*/spec-tests'
outputDir: path.join(__dirname, "../../spec-tests"),
specTestsRepoUrl: "https://github.com/ethereum/consensus-specs",
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/test/spec/utils/specTestIterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const defaultSkipOpts: SkipOpts = {
/^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
/^fulu\/light_client\/single_merkle_proof\/BeaconBlockBody.*/,
/^.+\/light_client\/data_collection\/.*/,
/^gloas\/(finality|fork_choice|sanity|transition)\/.*$/,
/^gloas\/(finality|fork_choice)\/.*$/,
/^gloas\/ssz_static\/ForkChoiceNode.*$/,
],
skippedTests: [],
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/test/unit/util/kzg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,5 @@ describe("KZG", () => {
}
expect(recoveredSidecars.length).toBe(NUMBER_OF_COLUMNS);
expect(ssz.fulu.DataColumnSidecars.equals(recoveredSidecars, sidecars)).toBeTruthy();
});
}, 60_000);
});
4 changes: 2 additions & 2 deletions packages/config/src/chainConfig/configs/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ export const chainConfig: ChainConfig = {
SECONDS_PER_ETH1_BLOCK: 14,
// 2**8 (= 256) epochs ~27 hours
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256,
// 2**12 (= 4,096) epochs ~18 days
MIN_BUILDER_WITHDRAWABILITY_DELAY: 4096,
// 2**6 (= 64) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 64,
// 2**8 (= 256) epochs ~27 hours
SHARD_COMMITTEE_PERIOD: 256,
// 2**11 (= 2,048) Eth1 blocks ~8 hours
Expand Down
4 changes: 2 additions & 2 deletions packages/config/src/chainConfig/configs/minimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const chainConfig: ChainConfig = {
SECONDS_PER_ETH1_BLOCK: 14,
// 2**8 (= 256) epochs
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256,
// [customized] 2**3 (= 8) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 8,
// [customized] 2**1 (= 2) epochs
MIN_BUILDER_WITHDRAWABILITY_DELAY: 2,
// [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit
SHARD_COMMITTEE_PERIOD: 64,
// [customized] process deposits more quickly, but insecure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function isValidIndexedPayloadAttestation(

if (verifySignature) {
return verifySignatureSet(
getIndexedPayloadAttestationSignatureSet(state, indexedPayloadAttestation),
getIndexedPayloadAttestationSignatureSet(state.config, indexedPayloadAttestation),
state.epochCtx.index2pubkey
);
}
Expand Down
13 changes: 8 additions & 5 deletions packages/state-transition/src/block/processDepositRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export function applyDepositForBuilder(
pubkey: BLSPubkey,
withdrawalCredentials: Bytes32,
amount: UintNum64,
signature: Bytes32
signature: Bytes32,
slot: UintNum64
): void {
const builderIndex = findBuilderIndexByPubkey(state, pubkey);

Expand All @@ -25,7 +26,7 @@ export function applyDepositForBuilder(
} else {
// New builder - verify signature and add to registry
if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) {
addBuilderToRegistry(state, pubkey, withdrawalCredentials, amount);
addBuilderToRegistry(state, pubkey, withdrawalCredentials, amount, slot);
}
}
}
Expand All @@ -38,9 +39,11 @@ function addBuilderToRegistry(
state: CachedBeaconStateGloas,
pubkey: BLSPubkey,
withdrawalCredentials: Bytes32,
amount: UintNum64
amount: UintNum64,
slot: UintNum64
): void {
const currentEpoch = computeEpochAtSlot(state.slot);
const depositEpoch = computeEpochAtSlot(slot);

// Try to find a reusable slot from an exited builder with zero balance
let builderIndex = state.builders.length;
Expand All @@ -58,7 +61,7 @@ function addBuilderToRegistry(
version: withdrawalCredentials[0],
executionAddress: withdrawalCredentials.subarray(12),
balance: amount,
depositEpoch: currentEpoch,
depositEpoch: depositEpoch,
withdrawableEpoch: FAR_FUTURE_EPOCH,
});

Expand Down Expand Up @@ -93,7 +96,7 @@ export function processDepositRequest(
// Route to builder if it's an existing builder OR has builder prefix and is not a validator
if (isBuilder || (isBuilderPrefix && !isValidator)) {
// Apply builder deposits immediately
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature);
applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot);
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export function processExecutionPayloadBid(state: CachedBeaconStateGloas, block:
throw Error(`Prev randao ${toHex(bid.prevRandao)} of bid does not match state's randao mix ${toHex(stateRandao)}`);
}

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

if (amount > 0) {
const pendingPaymentView = ssz.gloas.BuilderPendingPayment.toViewDU({
weight: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,6 @@ 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=${toRootHex(envelopeKzgRoot)} committedBid=${toRootHex(committedBid.blobKzgCommitmentsRoot)}`
);
}

if (!byteArrayEquals(committedBid.prevRandao, payload.prevRandao)) {
throw new Error(
`Prev randao mismatch between committed bid and payload committedBid=${toHex(committedBid.prevRandao)} payload=${toHex(payload.prevRandao)}`
Expand Down Expand Up @@ -146,14 +139,6 @@ function validateExecutionPayloadEnvelope(
);
}

// Verify commitments are under limit
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=${maxBlobsPerBlock}`
);
}

// Skipped: Verify the execution payload is valid
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ function getValidatorsSweepWithdrawals(
// Just run a bounded loop max iterating over all withdrawals
// however breaks out once we have MAX_WITHDRAWALS_PER_PAYLOAD
for (let n = 0; n < validatorsLimit; n++) {
if (sweepWithdrawals.length + numPriorWithdrawal === MAX_WITHDRAWALS_PER_PAYLOAD) {
if (sweepWithdrawals.length + numPriorWithdrawal >= MAX_WITHDRAWALS_PER_PAYLOAD) {
break;
}

Expand Down
Loading
Loading