Part 3: Annotated Specification

Helper Functions

Math

integer_squareroot

def integer_squareroot(n: uint64) -> uint64:
    """
    Return the largest integer ``x`` such that ``x**2 <= n``.
    """
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

Validator rewards scale with the reciprocal of the square root of the total active balance of all validators. This is calculated in get_base_reward_per_increment().

In principle integer_squareroot is also used in get_attestation_participation_flag_indices(), to specify the maximum delay for source votes to receive a reward. But this is just the constant, integer_squareroot(SLOTS_PER_EPOCH), which is 5.

Newton's method is used which has pretty good convergence properties, but implementations may use any method that gives identical results.

Used by get_base_reward_per_increment(), get_attestation_participation_flag_indices()

xor

def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32:
    """
    Return the exclusive-or of two 32-byte strings.
    """
    return Bytes32(a ^ b for a, b in zip(bytes_1, bytes_2))

The bitwise xor of two 32-byte quantities is defined here in Python terms.

This is used only in process_randao() when mixing in the new randao reveal.

Fun fact: if you xor two byte types in Java, the result is a 32 bit (signed) integer. This is one reason we need to define the "obvious" here. But mainly, because the spec is executable, we need to tell Python what it doesn't already know.

Used by process_randao()

uint_to_bytes

def uint_to_bytes(n: uint) -> bytes is a function for serializing the uint type object to bytes in ENDIANNESS-endian. The expected length of the output is the byte-length of the uint type.

For the most part, integers are integers and bytes are bytes, and they don't mix much. But there are a few places where we need to convert from integers to bytes:

You'll note that in every case, the purpose of the conversion is for the integer to form part of a byte string that is hashed to create (pseudo-)randomness.

The result of this conversion is dependent on our arbitrary choice of endianness, that is, how we choose to represent integers as strings of bytes. For Eth2, we have chosen little-endian: see the discussion of ENDIANNESS for more background.

The uint_to_bytes() function is not given an explicit implementation in the specification, which is unusual. This to avoid exposing the innards of the Python SSZ implementation (of uint) to the rest of the spec. When running the spec as an executable, it uses the definition in the SSZ utilities.

Used by compute_shuffled_index(), compute_proposer_index(), get_seed(), get_beacon_proposer_index(), get_next_sync_committee_indices()
See also ENDIANNESS, SSZ utilities

bytes_to_uint64

def bytes_to_uint64(data: bytes) -> uint64:
    """
    Return the integer deserialization of ``data`` interpreted as ``ENDIANNESS``-endian.
    """
    return uint64(int.from_bytes(data, ENDIANNESS))

bytes_to_uint64() is the inverse of uint_to_bytes(), and is used by the shuffling algorithm to create a random index from the output of a hash.

It is also used in the validator specification when selecting validators to aggregate attestations, and sync committee messages.

int.from_bytes is a built-in Python 3 method. The uint64 cast is provided by the spec's SSZ implementation.

Used by compute_shuffled_index
See also attestation aggregator selection, sync committee aggregator selection

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2023-07-01 13:17 UTC. Commit 8fa708b.