@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
121 lines • 6.76 kB
JavaScript
import { PublicKey } from "@chainsafe/blst";
import { computeEpochAtSlot, createSingleSignatureSetFromComponents, getExecutionPayloadBidSigningRoot, isActiveBuilder, isStatePostGloas, } from "@lodestar/state-transition";
import { toRootHex } from "@lodestar/utils";
import { ExecutionPayloadBidError, ExecutionPayloadBidErrorCode, GossipAction } from "../errors/index.js";
import { RegenCaller } from "../regen/index.js";
export async function validateApiExecutionPayloadBid(chain, signedExecutionPayloadBid) {
return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
}
export async function validateGossipExecutionPayloadBid(chain, signedExecutionPayloadBid) {
return validateExecutionPayloadBid(chain, signedExecutionPayloadBid);
}
async function validateExecutionPayloadBid(chain, signedExecutionPayloadBid) {
const bid = signedExecutionPayloadBid.message;
const parentBlockRootHex = toRootHex(bid.parentBlockRoot);
const parentBlockHashHex = toRootHex(bid.parentBlockHash);
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipExecutionPayloadBid);
if (!isStatePostGloas(state)) {
throw new Error(`Expected gloas+ state for execution payload bid validation, got fork=${state.forkName}`);
}
// [IGNORE] `bid.slot` is the current slot or the next slot.
const currentSlot = chain.clock.currentSlot;
if (bid.slot !== currentSlot && bid.slot !== currentSlot + 1) {
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
code: ExecutionPayloadBidErrorCode.INVALID_SLOT,
builderIndex: bid.builderIndex,
slot: bid.slot,
});
}
// [IGNORE] A `SignedProposerPreferences` matching `bid.slot` and the bid's branch has been
// seen — i.e. `proposal_slot == bid.slot` AND `dependent_root ==
// get_proposer_dependent_root(parent_state, compute_epoch_at_slot(bid.slot))`,
// where `parent_state` is the post-state of `bid.parent_block_root`.
// This is the message referenced as `proposer_preferences` in the following REJECT rules.
// TODO GLOAS: Implement once a ProposerPreferencesPool exists.
// [REJECT] `bid.builder_index` is a valid/active builder index -- i.e.
// `is_active_builder(state, bid.builder_index)` returns `True`.
const builder = state.getBuilder(bid.builderIndex);
if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE,
builderIndex: bid.builderIndex,
});
}
// [REJECT] `bid.execution_payment` is zero.
if (bid.executionPayment !== 0) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
code: ExecutionPayloadBidErrorCode.NON_ZERO_EXECUTION_PAYMENT,
builderIndex: bid.builderIndex,
executionPayment: bid.executionPayment,
});
}
// [REJECT] `bid.fee_recipient == proposer_preferences.fee_recipient`.
// [REJECT] `bid.gas_limit == proposer_preferences.gas_limit`.
// Both compared against the matching `proposer_preferences` defined above (same branch
// via dependent_root, same proposal_slot).
// TODO GLOAS: Implement once a ProposerPreferencesPool exists.
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in the
// consensus layer -- i.e. validate that
// `len(bid.blob_kzg_commitments) <= get_blob_parameters(compute_epoch_at_slot(bid.slot)).max_blobs_per_block`.
const blobKzgCommitmentsLen = bid.blobKzgCommitments.length;
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(bid.slot));
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
code: ExecutionPayloadBidErrorCode.TOO_MANY_KZG_COMMITMENTS,
blobKzgCommitmentsLen,
commitmentLimit: maxBlobsPerBlock,
});
}
// [IGNORE] this is the first signed bid seen with a valid signature from the given builder for this slot.
if (chain.seenExecutionPayloadBids.isKnown(bid.slot, bid.builderIndex)) {
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
code: ExecutionPayloadBidErrorCode.BID_ALREADY_KNOWN,
builderIndex: bid.builderIndex,
slot: bid.slot,
parentBlockRoot: parentBlockRootHex,
parentBlockHash: parentBlockHashHex,
});
}
// [IGNORE] this bid is the highest value bid seen for the tuple
// `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`.
const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex);
if (bestBid !== null && bestBid.value >= bid.value) {
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
code: ExecutionPayloadBidErrorCode.BID_TOO_LOW,
bidValue: bid.value,
currentHighestBid: bestBid.value,
});
}
// [IGNORE] `bid.value` is less or equal than the builder's excess balance --
// i.e. `can_builder_cover_bid(state, builder_index, amount)` returns `True`.
if (!state.canBuilderCoverBid(bid.builderIndex, bid.value)) {
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
code: ExecutionPayloadBidErrorCode.BID_TOO_HIGH,
bidValue: bid.value,
builderBalance: builder.balance,
});
}
// [IGNORE] `bid.parent_block_hash` is the block hash of a known execution
// payload in fork choice.
// TODO GLOAS: implement this
// [IGNORE] `bid.parent_block_root` is the hash tree root of a known beacon
// block in fork choice.
if (!chain.forkChoice.hasBlock(bid.parentBlockRoot)) {
throw new ExecutionPayloadBidError(GossipAction.IGNORE, {
code: ExecutionPayloadBidErrorCode.UNKNOWN_BLOCK_ROOT,
parentBlockRoot: parentBlockRootHex,
});
}
// [REJECT] `signed_execution_payload_bid.signature` is valid with respect to the `bid.builder_index`.
const signatureSet = createSingleSignatureSetFromComponents(PublicKey.fromBytes(builder.pubkey), getExecutionPayloadBidSigningRoot(chain.config, state.slot, bid), signedExecutionPayloadBid.signature);
if (!(await chain.bls.verifySignatureSets([signatureSet]))) {
throw new ExecutionPayloadBidError(GossipAction.REJECT, {
code: ExecutionPayloadBidErrorCode.INVALID_SIGNATURE,
builderIndex: bid.builderIndex,
slot: bid.slot,
});
}
// Valid
chain.seenExecutionPayloadBids.add(bid.slot, bid.builderIndex);
}
//# sourceMappingURL=executionPayloadBid.js.map