eth2.0-specs/specs/merge/validator.md

5.3 KiB

The Merge -- Honest Validator

Notice: This document is a work-in-progress for researchers and implementers.

Table of contents

Introduction

This document represents the changes to be made in the code of an "honest validator" to implement executable beacon chain proposal.

Prerequisites

This document is an extension of the Altair -- Honest Validator guide. All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden.

All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of The Merge are requisite for this document and used throughout. Please see related Beacon Chain doc before continuing and use them as a reference throughout.

Protocols

ExecutionEngine

The following methods are added to the ExecutionEngine protocol for use as a validator:

prepare_payload

Given the set of execution payload attributes initiates a process of building an execution payload on top of the execution chain tip identified by parent_hash.

def prepare_payload(self: ExecutionEngine,
                    parent_hash: Hash32,
                    timestamp: uint64,
                    random: Bytes32,
                    fee_recipient: Bytes20) -> uint64:
    """
    Returns ``payload_id`` that is used to obtain the execution payload in a subsequent ``get_payload`` call.
    """
    ...

get_payload

Given the payload_id returns the most recent version of the execution payload that has been built since the corresponding call to prepare_payload method.

def get_payload(self: ExecutionEngine, payload_id: uint64) -> ExecutionPayload:
    """
    Returns ``execution_payload`` object.
    """
    ...

The body of each of these functions is implementation dependent. The Engine API may be used to implement them with an external execution engine.

Beacon chain responsibilities

All validator responsibilities remain unchanged other than those noted below. Namely, the transition block handling and the addition of ExecutionPayload.

Block proposal

Constructing the BeaconBlockBody

ExecutionPayload

To obtain an execution payload a proposer of a block must take the following actions:

  1. Set payload_id = prepare_execution_payload(state, pow_chain, fee_recipient, execution_engine), where:
    • state is the state for which is_proposer(state, validator_index) returns True
    • pow_chain is a list that abstractly represents all blocks in the PoW chain
    • fee_recipient is the value suggested to be used for the coinbase field of the execution payload
def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]:
    # `pow_chain` abstractly represents all blocks in the PoW chain
    for block in pow_chain:
        parent = get_pow_block(block.parent_hash)
        if block.total_difficulty >= total_difficulty and parent.total_difficulty < total_difficulty:
            return block

    return None


def prepare_execution_payload(state: BeaconState,
                              pow_chain: Sequence[PowBlock],
                              fee_recipient: Bytes20,
                              execution_engine: ExecutionEngine) -> Optional[uint64]:
    if not is_merge_complete(state):
        terminal_pow_block = get_pow_block_at_total_difficulty(TERMINAL_TOTAL_DIFFICULTY, pow_chain)
        if terminal_pow_block is None:
            # Pre-merge, no prepare payload call is needed
            return None
        else:
            # Signify merge via producing on top of the last PoW block
            parent_hash = terminal_pow_block.block_hash
    else:
        # Post-merge, normal payload
        parent_hash = state.latest_execution_payload_header.block_hash

    timestamp = compute_timestamp_at_slot(state, state.slot)
    random = get_randao_mix(state, get_current_epoch(state))
    return execution_engine.prepare_payload(parent_hash, timestamp, random, fee_recipient)
  1. Set block.body.execution_payload = get_execution_payload(payload_id, execution_engine), where:
def get_execution_payload(payload_id: Optional[uint64], execution_engine: ExecutionEngine) -> ExecutionPayload:
    if payload_id is None:
        # Pre-merge, empty payload
        return ExecutionPayload()
    else:
        return execution_engine.get_payload(payload_id)

Note: It is recommended for a validator to call prepare_execution_payload as soon as input parameters become known, and make subsequent calls to this function if any of these parameters has been updated.