diff --git a/packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts b/packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts index 443faff6d0e3..51b8c7046adb 100644 --- a/packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts +++ b/packages/beacon-node/src/chain/opPools/proposerPreferencesPool.ts @@ -17,7 +17,7 @@ import {toRootHex} from "@lodestar/utils"; export class ProposerPreferencesPool { private readonly bySlot = new Map>(); - /** Lookup for bid validation: matches `(bid.slot, get_proposer_dependent_root(parent_state, ...))`. */ + /** Lookup for bid validation: matches `(bid.slot, get_shuffling_dependent_root(store, bid.parent_block_root, epoch))`. */ get(slot: Slot, dependentRootHex: RootHex): gloas.SignedProposerPreferences | null { return this.bySlot.get(slot)?.get(dependentRootHex) ?? null; } diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index 25cb272cb8fa..ca4340668ebb 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -90,13 +90,19 @@ async function validateAggregateAndProof( }); } - // [REJECT] If `aggregate.data.index == 1` (payload present for a past - // block), the execution payload for `block` passes validation. + // [REJECT] If `aggregate.data.index == 1` (payload present for a past block) + // the corresponding execution payload for `block` passes validation. // [IGNORE] When `aggregate.data.index == 1` (payload present for a past block), - // the corresponding execution payload for `block` has been seen (a client MAY queue - // attestations for processing once the payload is retrieved and SHOULD request the - // payload envelope via `ExecutionPayloadEnvelopesByRoot`). - if (block !== null && attData.index === 1 && !chain.seenPayloadEnvelope(toRootHex(attData.beaconBlockRoot))) { + // the corresponding execution payload for `block` has been fully imported, including its + // data -- i.e. `is_payload_verified(store, aggregate.data.beacon_block_root)` returns `True` + // (a client MAY queue attestations for processing until the payload is imported and SHOULD + // request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using + // `aggregate.data.beacon_block_root`). + if ( + block !== null && + attData.index === 1 && + !chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot)) + ) { throw new AttestationError(GossipAction.IGNORE, { code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN, beaconBlockRoot: toRootHex(attData.beaconBlockRoot), diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index f3b7b8863f57..cbbc304a795c 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -316,13 +316,19 @@ async function validateAttestationNoSignatureCheck( }); } - // [REJECT] If `attestation.data.index == 1` (payload present for a past - // block), the execution payload for `block` passes validation. + // [REJECT] If `attestation.data.index == 1` (payload present for a past block), + // the execution payload for `block` passes validation. // [IGNORE] When `attestation.data.index == 1` (payload present for a past block), - // the corresponding execution payload for `block` has been seen (a client MAY queue - // attestations for processing once the payload is retrieved and SHOULD request the - // payload envelope via `ExecutionPayloadEnvelopesByRoot`). - if (block !== null && attData.index === 1 && !chain.seenPayloadEnvelope(toRootHex(attData.beaconBlockRoot))) { + // the execution payload for `block` has been fully imported, including its data -- i.e. + // `is_payload_verified(store, attestation.data.beacon_block_root)` returns `True` + // (a client MAY queue attestations for processing until the payload is imported and + // SHOULD request the payload envelope via `ExecutionPayloadEnvelopesByRoot` using + // `attestation.data.beacon_block_root`). + if ( + block !== null && + attData.index === 1 && + !chain.forkChoice.hasPayloadHexUnsafe(toRootHex(attData.beaconBlockRoot)) + ) { throw new AttestationError(GossipAction.IGNORE, { code: AttestationErrorCode.EXECUTION_PAYLOAD_NOT_SEEN, beaconBlockRoot: toRootHex(attData.beaconBlockRoot), diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index d0dca0ee4260..8739c0e6d3a2 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -8,7 +8,6 @@ import { MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD, - MAX_DEPOSIT_REQUESTS_PER_PAYLOAD, MAX_PAYLOAD_ATTESTATIONS, MAX_PROPOSER_SLASHINGS, MAX_VOLUNTARY_EXITS, @@ -182,7 +181,6 @@ export async function validateGossipBlock( // [REJECT] The counts of `block.body.parent_execution_requests` are within // their respective limits -- i.e. validate that - // `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`, // `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`, // `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`, // `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`, @@ -200,7 +198,6 @@ export async function validateGossipBlock( const body = (block as gloas.BeaconBlock).body; const requests = body.parentExecutionRequests; const countLimits: [string, number, number][] = [ - ["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], ["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], [ "parentExecutionRequests.consolidations", diff --git a/packages/beacon-node/src/chain/validation/executionPayloadBid.ts b/packages/beacon-node/src/chain/validation/executionPayloadBid.ts index 2e29eac1217f..85736795f171 100644 --- a/packages/beacon-node/src/chain/validation/executionPayloadBid.ts +++ b/packages/beacon-node/src/chain/validation/executionPayloadBid.ts @@ -165,9 +165,9 @@ async function validateExecutionPayloadBid( }); } - // [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`. + // [IGNORE] `bid.fee_recipient == proposer_preferences.fee_recipient`. if (!byteArrayEquals(bid.feeRecipient, proposerPreferences.message.feeRecipient)) { - throw new ExecutionPayloadBidError(GossipAction.REJECT, { + throw new ExecutionPayloadBidError(GossipAction.IGNORE, { code: ExecutionPayloadBidErrorCode.PROPOSER_PREFERENCES_FEE_RECIPIENT_MISMATCH, builderIndex: bid.builderIndex, bidFeeRecipient: toHex(bid.feeRecipient), diff --git a/packages/beacon-node/src/chain/validation/executionPayloadEnvelope.ts b/packages/beacon-node/src/chain/validation/executionPayloadEnvelope.ts index 2eb94d6cb337..410db77bed63 100644 --- a/packages/beacon-node/src/chain/validation/executionPayloadEnvelope.ts +++ b/packages/beacon-node/src/chain/validation/executionPayloadEnvelope.ts @@ -3,7 +3,6 @@ import { MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD, - MAX_DEPOSIT_REQUESTS_PER_PAYLOAD, MAX_WITHDRAWALS_PER_PAYLOAD, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, } from "@lodestar/params"; @@ -128,7 +127,6 @@ async function validateExecutionPayloadEnvelope( // are enforced here in gossip validation. const {executionRequests} = envelope; const requestCountLimits: [string, number, number][] = [ - ["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], ["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], ["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD], ["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD], diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index d7934a7c0114..0e32b5bd4004 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -7,7 +7,6 @@ import { MAX_ATTESTER_SLASHING_SIZE, MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, - MAX_SIGNED_BEACON_BLOCK_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, SYNC_COMMITTEE_SUBNET_COUNT, isForkPostAltair, @@ -145,7 +144,7 @@ export function getGossipSSZMaxSize(topic: GossipTopic, maxPayloadSize: number, // Gloas progressive containers have broad theoretical SSZ max sizes; use the preset p2p bounds instead. switch (topic.type) { case GossipType.beacon_block: - return isForkPostGloas(fork) ? MAX_SIGNED_BEACON_BLOCK_SIZE : maxPayloadSize; + return maxPayloadSize; case GossipType.beacon_aggregate_and_proof: return isForkPostGloas(fork) ? MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE : (sszType ?? getGossipSSZType(topic)).maxSize; case GossipType.attester_slashing: diff --git a/packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts index 1797552512d8..78a1e6719bc9 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts @@ -107,8 +107,10 @@ export function validateExecutionPayloadEnvelopesByRangeRequest( // The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve. // Archival nodes may still serve older retained payloads to allow genesis sync. - if (count > config.MAX_REQUEST_BLOCKS_DENEB) { - count = config.MAX_REQUEST_BLOCKS_DENEB; + // Spec: EnvelopesByRange response is bounded by MAX_REQUEST_PAYLOADS (consensus-specs #5383), + // distinct from the MAX_REQUEST_BLOCKS_DENEB cap used for block-by-range. + if (count > config.MAX_REQUEST_PAYLOADS) { + count = config.MAX_REQUEST_PAYLOADS; } return {startSlot, count}; diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 4531c63824f8..d412e73fb493 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -84,8 +84,6 @@ export const defaultSkipOpts: SkipOpts = { // New test suite added in v1.7.0-alpha.8 (consensus-specs #5206); gloas PTC fork choice // handling is not yet implemented in Lodestar. /^gloas\/fork_choice\/on_payload_attestation_message\/.*$/, - // TODO GLOAS: Unskip in #9606 - /^gloas\/operations\/builder_deposit_request\/.*$/, // TODO GLOAS: enable this after gloas fork choice is ready /^gloas\/fork_choice_compliance\/.*/, ], @@ -96,23 +94,6 @@ export const defaultSkipOpts: SkipOpts = { // boost wrongly denied. Fails identically on every pre-gloas fork. // Enable this after https://github.com/ChainSafe/lodestar/issues/9666 is resolved /fork_choice_compliance\/block_tree_test\/pyspec_tests\/block_tree_test_16_201284350_1$/, - // TODO GLOAS: Unskip in #9606 - /^gloas\/operations\/builder_deposit_request\/.*$/, - /\/fork_builder_deposit_followed_by_non_builder_credentials$/, - /\/fork_builder_deposit_uses_deposit_slot_epoch$/, - /\/fork_builder_deposit_version$/, - /\/fork_invalid_builder_deposit_followed_by_valid_builder_deposit$/, - /\/fork_invalid_validator_deposit_followed_by_builder_credentials$/, - /\/fork_mixed_pending_deposits$/, - /\/fork_multiple_builder_deposits$/, - /\/fork_multiple_deposits_same_builder$/, - /\/fork_single_builder_deposit$/, - /\/fork_valid_builder_deposit_followed_by_invalid_builder_deposit$/, - /\/deposit_requests_greater_than_electra_max$/, - /\/process_parent_execution_payload__new_builder_does_not_reuse_topped_up_builder_slot$/, - /\/process_builder_exit_request__success$/, - /\/process_parent_execution_payload__builder_exit_request$/, - /\/switch_to_compounding_with_pending_consolidations_at_limit$/, ], // TODO GLOAS: Investigate why networking tests are failing since alpha.5 skippedRunners: ["networking"], diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index 786b36bbb1e9..88537cadd88c 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -8,7 +8,6 @@ import { MAX_ATTESTER_SLASHING_SIZE, MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, - MAX_SIGNED_BEACON_BLOCK_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, ZERO_HASH, } from "@lodestar/params"; @@ -274,7 +273,7 @@ describe("network / gossip / topic", () => { config.MAX_PAYLOAD_SIZE ), }).toEqual({ - [GossipType.beacon_block]: MAX_SIGNED_BEACON_BLOCK_SIZE, + [GossipType.beacon_block]: config.MAX_PAYLOAD_SIZE, [GossipType.data_column_sidecar]: MAX_DATA_COLUMN_SIDECAR_SIZE, [GossipType.beacon_aggregate_and_proof]: MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, [GossipType.attester_slashing]: MAX_ATTESTER_SLASHING_SIZE, diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 6025cb88431d..18ee4a91428a 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -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**13 (= 8,192) epochs ~36 days - MIN_BUILDER_WITHDRAWABILITY_DELAY: 8192, + // 2**6 (= 64) epochs ~6.8 hours + MIN_BUILDER_WITHDRAWABILITY_DELAY: 64, // 2**8 (= 256) epochs ~27 hours SHARD_COMMITTEE_PERIOD: 256, // 2**11 (= 2,048) Eth1 blocks ~8 hours @@ -99,8 +99,8 @@ export const chainConfig: ChainConfig = { CONTRIBUTION_DUE_BPS_GLOAS: 5000, // 75% of SLOT_DURATION_MS PAYLOAD_ATTESTATION_DUE_BPS: 7500, - // 75% of SLOT_DURATION_MS - PAYLOAD_DUE_BPS: 7500, + // 50% of SLOT_DURATION_MS + PAYLOAD_DUE_BPS: 5000, // Validator cycle // --------------------------------------------------------------- diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index ed9a3a8e22c1..b28de3a8d630 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -93,8 +93,8 @@ export const chainConfig: ChainConfig = { CONTRIBUTION_DUE_BPS_GLOAS: 5000, // 75% of SLOT_DURATION_MS PAYLOAD_ATTESTATION_DUE_BPS: 7500, - // 75% of SLOT_DURATION_MS - PAYLOAD_DUE_BPS: 7500, + // 50% of SLOT_DURATION_MS + PAYLOAD_DUE_BPS: 5000, // Validator cycle // --------------------------------------------------------------- diff --git a/packages/config/test/e2e/ensure-config-is-synced.test.ts b/packages/config/test/e2e/ensure-config-is-synced.test.ts index d874c98f9d04..946b4e7456eb 100644 --- a/packages/config/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/config/test/e2e/ensure-config-is-synced.test.ts @@ -57,9 +57,6 @@ const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [ // These are preset values, not config values - they're tested separately "PRESET_BASE", "CONFIG_NAME", - // TODO GLOAS: Unskip in #9606 - "PAYLOAD_DUE_BPS" as keyof ChainConfig, - "MIN_BUILDER_WITHDRAWABILITY_DELAY" as keyof ChainConfig, ]; /** diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 947e546dd21a..d034f82948ba 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -119,15 +119,12 @@ export const { MAX_PAYLOAD_ATTESTATIONS, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD, - BUILDER_REGISTRY_LIMIT, - BUILDER_PENDING_WITHDRAWALS_LIMIT, MAX_BUILDERS_PER_WITHDRAWALS_SWEEP, MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE, MAX_ATTESTER_SLASHING_SIZE, MAX_DATA_COLUMN_SIDECAR_SIZE, MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE, - MAX_SIGNED_BEACON_BLOCK_SIZE, } = activePreset; //////////// @@ -152,7 +149,7 @@ export const ZERO_HASH_HEX = "0x" + "00".repeat(32); export const BLS_WITHDRAWAL_PREFIX = 0x00; export const ETH1_ADDRESS_WITHDRAWAL_PREFIX = 0x01; export const COMPOUNDING_WITHDRAWAL_PREFIX = 0x02; -export const BUILDER_WITHDRAWAL_PREFIX = 0x03; +export const BUILDER_WITHDRAWAL_PREFIX = 0xb0; // Builder version export const PAYLOAD_BUILDER_VERSION = 0; diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 650a4dd8e484..c1a325207167 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -145,10 +145,8 @@ export const mainnetPreset: BeaconPreset = { // GLOAS PTC_SIZE: 512, MAX_PAYLOAD_ATTESTATIONS: 4, - MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256, // 2**8 + MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64, // 2**6 MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16, // 2**4 - BUILDER_REGISTRY_LIMIT: 1099511627776, // 2**40 - BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576, // 2**20 MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384, // 2**14 // Type-specific SSZ bounds @@ -158,5 +156,4 @@ export const mainnetPreset: BeaconPreset = { MAX_DATA_COLUMN_SIDECAR_SIZE: 8585272, MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: 8585741, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: 196932, - MAX_SIGNED_BEACON_BLOCK_SIZE: 4027336, }; diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 43dd76624023..0c7be8b39e6c 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -146,10 +146,8 @@ export const minimalPreset: BeaconPreset = { // GLOAS PTC_SIZE: 16, MAX_PAYLOAD_ATTESTATIONS: 4, - MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256, // 2**8 + MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64, // 2**6 MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16, // 2**4 - BUILDER_REGISTRY_LIMIT: 1099511627776, // 2**40 - BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576, // 2**20 MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16, // 2**4 // Type-specific SSZ bounds @@ -159,5 +157,4 @@ export const minimalPreset: BeaconPreset = { MAX_DATA_COLUMN_SIDECAR_SIZE: 8585272, MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: 8585741, MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: 196932, - MAX_SIGNED_BEACON_BLOCK_SIZE: 1938012, }; diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 6fad37a1f1e0..49ca04d27d2f 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -109,15 +109,12 @@ export type BeaconPreset = { MAX_PAYLOAD_ATTESTATIONS: number; MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: number; MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: number; - BUILDER_REGISTRY_LIMIT: number; - BUILDER_PENDING_WITHDRAWALS_LIMIT: number; MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: number; MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE: number; MAX_ATTESTER_SLASHING_SIZE: number; MAX_DATA_COLUMN_SIDECAR_SIZE: number; MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: number; MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: number; - MAX_SIGNED_BEACON_BLOCK_SIZE: number; }; /** @@ -232,15 +229,12 @@ export const beaconPresetTypes: BeaconPresetTypes = { MAX_PAYLOAD_ATTESTATIONS: "number", MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: "number", MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: "number", - BUILDER_REGISTRY_LIMIT: "number", - BUILDER_PENDING_WITHDRAWALS_LIMIT: "number", MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: "number", MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE: "number", MAX_ATTESTER_SLASHING_SIZE: "number", MAX_DATA_COLUMN_SIDECAR_SIZE: "number", MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: "number", MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: "number", - MAX_SIGNED_BEACON_BLOCK_SIZE: "number", }; type BeaconPresetTypes = { diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index f6f8784cffef..5d790c576f46 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -11,16 +11,9 @@ import {loadConfigYaml} from "../yaml.js"; * Fields that we filter from local config when doing comparison. * Ideally this should be empty as it is not spec compliant */ -// TODO GLOAS: Remove in #9606 -const ignoredLocalPresetFields: (keyof BeaconPreset)[] = [ - "BUILDER_REGISTRY_LIMIT", - "BUILDER_PENDING_WITHDRAWALS_LIMIT", - "MAX_SIGNED_BEACON_BLOCK_SIZE", - "MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD", -]; - -// TODO GLOAS: Remove in #9606 -const ignoredRemotePresetFields: string[] = ["MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD"]; +const ignoredLocalPresetFields: (keyof BeaconPreset)[] = []; + +const ignoredRemotePresetFields: string[] = []; describe("Ensure config is synced", () => { vi.setConfig({testTimeout: 60 * 1000}); diff --git a/packages/state-transition/src/block/processBuilderDepositRequest.ts b/packages/state-transition/src/block/processBuilderDepositRequest.ts index 1f1a89586b62..8896a71af94d 100644 --- a/packages/state-transition/src/block/processBuilderDepositRequest.ts +++ b/packages/state-transition/src/block/processBuilderDepositRequest.ts @@ -1,8 +1,13 @@ -import {FAR_FUTURE_EPOCH} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, PAYLOAD_BUILDER_VERSION} from "@lodestar/params"; import {gloas} from "@lodestar/types"; import {CachedBeaconStateGloas} from "../types.js"; import {computeEpochAtSlot} from "../util/epoch.js"; -import {addBuilderToRegistry, findBuilderIndexByPubkey, isValidBuilderDepositSignature} from "../util/gloas.js"; +import { + addBuilderToRegistry, + findBuilderIndexByPubkey, + isBuilderWithdrawalCredential, + isValidBuilderDepositSignature, +} from "../util/gloas.js"; /** * Process a builder deposit request from the execution layer: register a new builder @@ -15,6 +20,12 @@ export function processBuilderDepositRequest( request: gloas.BuilderDepositRequest ): void { const {pubkey, withdrawalCredentials, amount, signature} = request; + + // Ignore deposits with unexpected withdrawal credential prefixes. + if (!isBuilderWithdrawalCredential(withdrawalCredentials)) { + return; + } + const builderIndex = findBuilderIndexByPubkey(state, pubkey); if (builderIndex === null) { @@ -22,7 +33,7 @@ export function processBuilderDepositRequest( addBuilderToRegistry( state, pubkey, - withdrawalCredentials[0], + PAYLOAD_BUILDER_VERSION, withdrawalCredentials.subarray(12), amount, state.slot @@ -33,11 +44,13 @@ export function processBuilderDepositRequest( const builder = state.builders.get(builderIndex); - // Increase balance by deposit amount - builder.balance += amount; - - // If exited, reset the withdrawable epoch - if (builder.withdrawableEpoch !== FAR_FUTURE_EPOCH) { + // If the builder has exited and been fully swept (balance drained to 0), reset the + // withdrawable epoch so this top-up becomes withdrawable again. Must run before the + // balance increase, since the reset is gated on the current balance being 0. + if (builder.withdrawableEpoch !== FAR_FUTURE_EPOCH && builder.balance === 0) { builder.withdrawableEpoch = computeEpochAtSlot(state.slot) + state.config.MIN_BUILDER_WITHDRAWABILITY_DELAY; } + + // Increase balance by deposit amount + builder.balance += amount; } diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index 713a60b3bbfc..71767238d602 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -31,11 +31,13 @@ export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array) } export function getBuilderPaymentQuorumThreshold(state: CachedBeaconStateGloas): number { - const quorum = - Math.floor((state.epochCtx.totalActiveBalanceIncrements * EFFECTIVE_BALANCE_INCREMENT) / SLOTS_PER_EPOCH) * - BUILDER_PAYMENT_THRESHOLD_NUMERATOR; + // total active balance exceeds Number.MAX_SAFE_INTEGER at mainnet scale, keep the intermediate math in bigint + const perSlotBalance = + (BigInt(state.epochCtx.totalActiveBalanceIncrements) * BigInt(EFFECTIVE_BALANCE_INCREMENT)) / + BigInt(SLOTS_PER_EPOCH); + const quorum = perSlotBalance * BigInt(BUILDER_PAYMENT_THRESHOLD_NUMERATOR); - return Math.floor(quorum / BUILDER_PAYMENT_THRESHOLD_DENOMINATOR); + return Number(quorum / BigInt(BUILDER_PAYMENT_THRESHOLD_DENOMINATOR)); } function hasBuilderIndexFlag(index: number): boolean { diff --git a/packages/state-transition/test/unit/block/processBuilderDepositRequest.test.ts b/packages/state-transition/test/unit/block/processBuilderDepositRequest.test.ts index 1d70914fad28..e4cde9d97de2 100644 --- a/packages/state-transition/test/unit/block/processBuilderDepositRequest.test.ts +++ b/packages/state-transition/test/unit/block/processBuilderDepositRequest.test.ts @@ -1,7 +1,13 @@ import {beforeEach, describe, expect, it, vi} from "vitest"; import {createBeaconConfig} from "@lodestar/config"; import {getConfig} from "@lodestar/config/test-utils"; -import {BUILDER_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, ForkName, SLOTS_PER_EPOCH} from "@lodestar/params"; +import { + BUILDER_WITHDRAWAL_PREFIX, + FAR_FUTURE_EPOCH, + ForkName, + PAYLOAD_BUILDER_VERSION, + SLOTS_PER_EPOCH, +} from "@lodestar/params"; import {ssz} from "@lodestar/types"; const isValidBuilderDepositSignatureMock = vi.hoisted(() => @@ -49,9 +55,12 @@ function buildGloasState(slot = 0) { ); } -function makeBuilderWithdrawalCredentials(executionAddress: Uint8Array): Uint8Array { +function makeBuilderWithdrawalCredentials( + executionAddress: Uint8Array, + prefix = BUILDER_WITHDRAWAL_PREFIX +): Uint8Array { const creds = new Uint8Array(32); - creds[0] = BUILDER_WITHDRAWAL_PREFIX; + creds[0] = prefix; creds.set(executionAddress, 12); return creds; } @@ -61,17 +70,21 @@ function makeBuilderDepositRequest({ executionAddress = Uint8Array.from({length: 20}, (_, i) => i + 1), amount = 1_000_000_000, signatureFirstByte = 1, // 1 => valid via mock, anything else => invalid + withdrawalCredentials, + prefix = BUILDER_WITHDRAWAL_PREFIX, }: { pubkey?: Uint8Array; executionAddress?: Uint8Array; amount?: number; signatureFirstByte?: number; + withdrawalCredentials?: Uint8Array; + prefix?: number; } = {}) { const signature = new Uint8Array(96); signature[0] = signatureFirstByte; return { pubkey, - withdrawalCredentials: makeBuilderWithdrawalCredentials(executionAddress), + withdrawalCredentials: withdrawalCredentials ?? makeBuilderWithdrawalCredentials(executionAddress, prefix), amount, signature, }; @@ -95,10 +108,20 @@ describe("processBuilderDepositRequest", () => { const builder = state.builders.get(0); expect(builder.balance).toBe(32_000_000_000); expect(builder.executionAddress).toEqual(request.withdrawalCredentials.subarray(12)); - expect(builder.version).toBe(BUILDER_WITHDRAWAL_PREFIX); + expect(builder.version).toBe(PAYLOAD_BUILDER_VERSION); expect(builder.withdrawableEpoch).toBe(FAR_FUTURE_EPOCH); }); + it("drops a new builder request when the withdrawal credentials prefix is not the builder prefix", () => { + const state = buildGloasState(1); + const request = makeBuilderDepositRequest({prefix: 0x01}); + + processBuilderDepositRequest(state, request); + + expect(isValidBuilderDepositSignatureMock).not.toHaveBeenCalled(); + expect(state.builders.length).toBe(0); + }); + it("drops the request when PoP is invalid", () => { const state = buildGloasState(1); const request = makeBuilderDepositRequest({signatureFirstByte: 0}); @@ -113,12 +136,11 @@ describe("processBuilderDepositRequest", () => { const state = buildGloasState(SLOTS_PER_EPOCH); const pubkey = Uint8Array.from({length: 48}, (_, i) => i + 1); const originalAddress = Uint8Array.from({length: 20}, (_, i) => i + 1); - const originalCreds = makeBuilderWithdrawalCredentials(originalAddress); state.builders.push( ssz.gloas.Builder.toViewDU({ pubkey, - version: originalCreds[0], + version: PAYLOAD_BUILDER_VERSION, executionAddress: originalAddress, balance: 32_000_000_000, depositEpoch: 0, @@ -126,8 +148,8 @@ describe("processBuilderDepositRequest", () => { }) ); - // Attacker-shaped top-up: same pubkey but different (would-be) execution address. Top-ups - // must ignore the request's withdrawal credentials and signature entirely. + // Attacker-shaped top-up: same pubkey but different (would-be) execution address. Valid + // top-ups must ignore the request's execution address and signature. const attackerAddress = Uint8Array.from({length: 20}, () => 0xff); const request = makeBuilderDepositRequest({ pubkey, @@ -143,22 +165,54 @@ describe("processBuilderDepositRequest", () => { const builder = state.builders.get(0); expect(builder.balance).toBe(33_000_000_000); expect(builder.executionAddress).toEqual(originalAddress); - expect(builder.version).toBe(BUILDER_WITHDRAWAL_PREFIX); + expect(builder.version).toBe(PAYLOAD_BUILDER_VERSION); + }); + + it("drops a top-up when the withdrawal credentials prefix is not the builder prefix", () => { + const state = buildGloasState(SLOTS_PER_EPOCH); + const pubkey = Uint8Array.from({length: 48}, (_, i) => i + 1); + const executionAddress = Uint8Array.from({length: 20}, (_, i) => i + 1); + + state.builders.push( + ssz.gloas.Builder.toViewDU({ + pubkey, + version: PAYLOAD_BUILDER_VERSION, + executionAddress, + balance: 32_000_000_000, + depositEpoch: 0, + withdrawableEpoch: FAR_FUTURE_EPOCH, + }) + ); + + const request = makeBuilderDepositRequest({ + pubkey, + amount: 1_000_000_000, + prefix: 0x01, + }); + + processBuilderDepositRequest(state, request); + + expect(isValidBuilderDepositSignatureMock).not.toHaveBeenCalled(); + expect(state.builders.length).toBe(1); + const builder = state.builders.get(0); + expect(builder.balance).toBe(32_000_000_000); + expect(builder.executionAddress).toEqual(executionAddress); + expect(builder.version).toBe(PAYLOAD_BUILDER_VERSION); }); - it("resets the withdrawable epoch when topping up an exited builder", () => { + it("resets the withdrawable epoch when topping up an exited, fully-swept builder", () => { const slot = SLOTS_PER_EPOCH * 2; const state = buildGloasState(slot); const pubkey = Uint8Array.from({length: 48}, (_, i) => i + 1); const executionAddress = Uint8Array.from({length: 20}, (_, i) => i + 1); - // Exited builder: finite withdrawableEpoch + // Exited and fully swept builder: finite withdrawableEpoch, zero balance state.builders.push( ssz.gloas.Builder.toViewDU({ pubkey, - version: BUILDER_WITHDRAWAL_PREFIX, + version: PAYLOAD_BUILDER_VERSION, executionAddress, - balance: 1_000_000_000, + balance: 0, depositEpoch: 0, withdrawableEpoch: 1, }) @@ -169,8 +223,36 @@ describe("processBuilderDepositRequest", () => { processBuilderDepositRequest(state, request); const builder = state.builders.get(0); - expect(builder.balance).toBe(2_000_000_000); + expect(builder.balance).toBe(1_000_000_000); const currentEpoch = Math.floor(slot / SLOTS_PER_EPOCH); expect(builder.withdrawableEpoch).toBe(currentEpoch + state.config.MIN_BUILDER_WITHDRAWABILITY_DELAY); }); + + it("does not reset the withdrawable epoch when topping up an exited builder with nonzero balance", () => { + const slot = SLOTS_PER_EPOCH * 2; + const state = buildGloasState(slot); + const pubkey = Uint8Array.from({length: 48}, (_, i) => i + 1); + const executionAddress = Uint8Array.from({length: 20}, (_, i) => i + 1); + + // Exited builder that has NOT been swept: finite withdrawableEpoch, nonzero balance. + // Per spec, the reset is gated on balance == 0, so the withdrawableEpoch must be preserved. + state.builders.push( + ssz.gloas.Builder.toViewDU({ + pubkey, + version: PAYLOAD_BUILDER_VERSION, + executionAddress, + balance: 1_000_000_000, + depositEpoch: 0, + withdrawableEpoch: 1, + }) + ); + + const request = makeBuilderDepositRequest({pubkey, executionAddress, amount: 1_000_000_000}); + + processBuilderDepositRequest(state, request); + + const builder = state.builders.get(0); + expect(builder.balance).toBe(2_000_000_000); + expect(builder.withdrawableEpoch).toBe(1); + }); }); diff --git a/packages/state-transition/test/unit/block/processBuilderExitRequest.test.ts b/packages/state-transition/test/unit/block/processBuilderExitRequest.test.ts index a129057b5318..1e8ca60cf201 100644 --- a/packages/state-transition/test/unit/block/processBuilderExitRequest.test.ts +++ b/packages/state-transition/test/unit/block/processBuilderExitRequest.test.ts @@ -1,7 +1,7 @@ import {describe, expect, it} from "vitest"; import {createBeaconConfig} from "@lodestar/config"; import {getConfig} from "@lodestar/config/test-utils"; -import {BUILDER_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, ForkName, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, ForkName, PAYLOAD_BUILDER_VERSION, SLOTS_PER_EPOCH} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {processBuilderExitRequest} from "../../../src/block/processBuilderExitRequest.js"; import {createCachedBeaconState, createPubkeyCache} from "../../../src/index.js"; @@ -53,7 +53,7 @@ function pushBuilder( state.builders.push( ssz.gloas.Builder.toViewDU({ pubkey, - version: BUILDER_WITHDRAWAL_PREFIX, + version: PAYLOAD_BUILDER_VERSION, executionAddress, balance, depositEpoch, diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 23a831d3400c..258cf6624388 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -326,15 +326,12 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record