The stateless input decoder¶
Materialized references into the concrete SszStatelessInput wire format
and the stateless guest decoder. The layout constants transcribe the SSZ
schema; the decoder resolves every variable region once and hands
consumers explicit source spans.
Constants¶
The wire-format layout: field offsets transcribing the SSZ schema, grouped by container.
let SSZ_BODY¶
The input starts with a 2-byte big-endian schema id; the SSZ body follows.
let SSZ_BODY : int(2) = 2let IN_NPR_OFF¶
let IN_NPR_OFF : int(0) = 0let IN_WITNESS_OFF¶
let IN_WITNESS_OFF : int(4) = 4let IN_CHAIN_CONFIG_OFF¶
let IN_CHAIN_CONFIG_OFF : int(8) = 8let IN_PUBLIC_KEYS_OFF¶
let IN_PUBLIC_KEYS_OFF : int(12) = 12let NPR_PAYLOAD_OFF¶
let NPR_PAYLOAD_OFF : int(0) = 0let NPR_VHASHES_OFF¶
let NPR_VHASHES_OFF : int(4) = 4let NPR_BEACON_ROOT¶
let NPR_BEACON_ROOT : int(8) = 8let NPR_REQUESTS_OFF¶
let NPR_REQUESTS_OFF : int(40) = 40let REQ_DEPOSITS_OFF¶
let REQ_DEPOSITS_OFF : int(0) = 0let REQ_WITHDRAWALS_OFF¶
let REQ_WITHDRAWALS_OFF : int(4) = 4let REQ_CONSOLIDATIONS_OFF¶
let REQ_CONSOLIDATIONS_OFF : int(8) = 8let REQ_BUILDER_DEPOSITS_OFF¶
let REQ_BUILDER_DEPOSITS_OFF : int(12) = 12let REQ_BUILDER_EXITS_OFF¶
let REQ_BUILDER_EXITS_OFF : int(16) = 16let PL_FEE_RECIPIENT¶
let PL_FEE_RECIPIENT : int(32) = 32let PL_STATE_ROOT¶
let PL_STATE_ROOT : int(52) = 52let PL_RECEIPTS_ROOT¶
let PL_RECEIPTS_ROOT : int(84) = 84let PL_LOGS_BLOOM¶
let PL_LOGS_BLOOM : int(116) = 116let PL_PREV_RANDAO¶
let PL_PREV_RANDAO : int(372) = 372let PL_BLOCK_NUMBER¶
let PL_BLOCK_NUMBER : int(404) = 404let PL_GAS_LIMIT¶
let PL_GAS_LIMIT : int(412) = 412let PL_GAS_USED¶
let PL_GAS_USED : int(420) = 420let PL_TIMESTAMP¶
let PL_TIMESTAMP : int(428) = 428let PL_EXTRA_OFF¶
let PL_EXTRA_OFF : int(436) = 436let PL_BASE_FEE¶
let PL_BASE_FEE : int(440) = 440let PL_BLOCK_HASH¶
let PL_BLOCK_HASH : int(472) = 472let PL_TXS_OFF¶
let PL_TXS_OFF : int(504) = 504let PL_WDS_OFF¶
let PL_WDS_OFF : int(508) = 508let PL_BLOB_GAS_USED¶
let PL_BLOB_GAS_USED : int(512) = 512let PL_EXCESS_BLOB_GAS¶
let PL_EXCESS_BLOB_GAS : int(520) = 520let PL_BAL_OFF¶
let PL_BAL_OFF : int(528) = 528let PL_SLOT_NUMBER¶
let PL_SLOT_NUMBER : int(532) = 532let WD_SIZE¶
let WD_SIZE : int(44) = 44let WD_INDEX¶
let WD_INDEX : int(0) = 0let WD_VALIDATOR_INDEX¶
let WD_VALIDATOR_INDEX : int(8) = 8let WD_ADDRESS¶
let WD_ADDRESS : int(16) = 16let WD_AMOUNT¶
let WD_AMOUNT : int(36) = 36let CC_CHAIN_ID¶
let CC_CHAIN_ID : int(0) = 0let CC_ACTIVE_FORK_OFF¶
let CC_ACTIVE_FORK_OFF : int(8) = 8let FC_ACTIVATION_OFF¶
let FC_ACTIVATION_OFF : int(0) = 0let FA_BLOCK_NUMBER_OFF¶
let FA_BLOCK_NUMBER_OFF : int(0) = 0let FA_TIMESTAMP_OFF¶
let FA_TIMESTAMP_OFF : int(4) = 4let WIT_STATE_OFF¶
let WIT_STATE_OFF : int(0) = 0let WIT_CODES_OFF¶
let WIT_CODES_OFF : int(4) = 4let WIT_HEADERS_OFF¶
let WIT_HEADERS_OFF : int(8) = 8let STATELESS_INPUT_FIXED_LENGTH¶
let STATELESS_INPUT_FIXED_LENGTH : int(18) = 18let STATELESS_INPUT_BODY_FIXED_LENGTH¶
let STATELESS_INPUT_BODY_FIXED_LENGTH : int(16) = 16let NEW_PAYLOAD_REQUEST_FIXED_LENGTH¶
let NEW_PAYLOAD_REQUEST_FIXED_LENGTH : int(44) = 44let EXECUTION_PAYLOAD_FIXED_LENGTH¶
let EXECUTION_PAYLOAD_FIXED_LENGTH : int(540) = 540let EXECUTION_REQUESTS_FIXED_LENGTH¶
let EXECUTION_REQUESTS_FIXED_LENGTH : int(20) = 20let EXECUTION_WITNESS_FIXED_LENGTH¶
let EXECUTION_WITNESS_FIXED_LENGTH : int(12) = 12let PUBLIC_KEY_LENGTH¶
let PUBLIC_KEY_LENGTH : int(65) = 65let CHAIN_CONFIG_HEADER_LENGTH¶
let CHAIN_CONFIG_HEADER_LENGTH : int(12) = 12let CHAIN_CONFIG_MIN_LENGTH¶
let CHAIN_CONFIG_MIN_LENGTH : int(24) = 24let MAX_EXTRA_DATA_LENGTH¶
let MAX_EXTRA_DATA_LENGTH : range(extra_data_length_bound, extra_data_length_bound) = sizeof(extra_data_length_bound)Maximum byte length of execution-payload extra data. Provenance:
consensus MAX_EXTRA_DATA_BYTES and Amsterdam
SszExecutionPayload.extra_data.
type extra_data_length_bound : Int = 2 ^ 5let MAX_TRANSACTION_LENGTH¶
let MAX_TRANSACTION_LENGTH : range(transaction_length_bound, transaction_length_bound) = sizeof(
transaction_length_bound
)Maximum byte length of one encoded transaction envelope. Provenance:
Amsterdam MAX_BYTES_PER_TRANSACTION in
SszExecutionPayload.transactions.
type transaction_length_bound : Int = 2 ^ 30let MAX_TRANSACTIONS_PER_PAYLOAD¶
let MAX_TRANSACTIONS_PER_PAYLOAD : range(transaction_count_bound, transaction_count_bound) = sizeof(
transaction_count_bound
)Maximum transactions in the execution-payload SSZ list. Provenance:
Bellatrix MAX_TRANSACTIONS_PER_PAYLOAD and Amsterdam
SszExecutionPayload.transactions.
type transaction_count_bound : Int = 2 ^ 20let MAX_WITHDRAWALS_PER_PAYLOAD¶
let MAX_WITHDRAWALS_PER_PAYLOAD : range(withdrawal_count_bound, withdrawal_count_bound) = sizeof(withdrawal_count_bound)Maximum withdrawals in the execution-payload SSZ list. Provenance:
Capella MAX_WITHDRAWALS_PER_PAYLOAD and Amsterdam
SszExecutionPayload.withdrawals.
type withdrawal_count_bound : Int = 2 ^ 4let MAX_BLOCK_ACCESS_LIST_LENGTH¶
let MAX_BLOCK_ACCESS_LIST_LENGTH : range(block_access_list_length_bound, block_access_list_length_bound) = sizeof(
block_access_list_length_bound
)Maximum byte length of the block access list. Provenance: Amsterdam
SszExecutionPayload.block_access_list uses
ByteList[MAX_BYTES_PER_TRANSACTION].
type block_access_list_length_bound : Int = 2 ^ 30let MAX_BLOB_COMMITMENTS_PER_BLOCK¶
let MAX_BLOB_COMMITMENTS_PER_BLOCK : range(blob_commitment_count_bound, blob_commitment_count_bound) = sizeof(
blob_commitment_count_bound
)Maximum blob commitments in the stateless-input SSZ list.
type blob_commitment_count_bound : Int = 2 ^ 12let MAX_WITNESS_NODES¶
let MAX_WITNESS_NODES : range(witness_node_count_bound, witness_node_count_bound) = sizeof(witness_node_count_bound)Maximum witness trie nodes in the stateless-input SSZ list.
type witness_node_count_bound : Int = 2 ^ 22let MAX_WITNESS_NODE_LENGTH¶
let MAX_WITNESS_NODE_LENGTH : range(witness_node_length_bound, witness_node_length_bound) = sizeof(
witness_node_length_bound
)Maximum byte length of one witnessed trie node.
type witness_node_length_bound : Int = 2 ^ 10let MAX_WITNESS_CODES¶
let MAX_WITNESS_CODES : range(witness_code_count_bound, witness_code_count_bound) = sizeof(witness_code_count_bound)Maximum witness code entries in the stateless-input SSZ list.
type witness_code_count_bound : Int = 2 ^ 18let MAX_WITNESS_CODE_LENGTH¶
let MAX_WITNESS_CODE_LENGTH : range(witness_code_length_bound, witness_code_length_bound) = sizeof(
witness_code_length_bound
)Maximum byte length of one witnessed code entry.
type witness_code_length_bound : Int = 2 ^ 16let MAX_WITNESS_HEADERS¶
let MAX_WITNESS_HEADERS : range(witness_header_count_bound, witness_header_count_bound) = sizeof(
witness_header_count_bound
)Maximum witness headers in the stateless-input SSZ list.
type witness_header_count_bound : Int = 2 ^ 8let MAX_WITNESS_HEADER_LENGTH¶
let MAX_WITNESS_HEADER_LENGTH : range(witness_header_length_bound, witness_header_length_bound) = sizeof(
witness_header_length_bound
)Maximum byte length of one witnessed parent header.
type witness_header_length_bound : Int = 2 ^ 10let MAX_PUBLIC_KEYS¶
let MAX_PUBLIC_KEYS : range(public_key_count_bound, public_key_count_bound) = sizeof(public_key_count_bound)Maximum witnessed public keys in the stateless-input SSZ list.
type public_key_count_bound : Int = 2 ^ 15function ssz_offset_table_position¶
Returns the byte position of an entry in a uint32 SSZ offset table.
function ssz_offset_table_position(index : ssz_offset_index) -> ssz_offset = {
index * 4
}A container-relative offset carried by an SSZ uint32.
type ssz_offset = range(0, 2 ^ 32 - 1)An index into a table of four-byte SSZ offsets.
type ssz_offset_index = range(0, 2 ^ 30 - 1)function ssz_list_cursor¶
A cursor at the first element of a variable-element SSZ list.
function ssz_list_cursor forall ('maximum : Int), source_valid_length('maximum). (items : BoundedSszListRef('maximum)) -> (
BoundedSszListCursor('maximum)
) = {
let bytes = items.bytes;
let current =
if items.count != 0 then {
let first_offset = ssz_u32(bytes, 0);
ssz_offset_to_source_pointer(first_offset)
} else {
bytes.len
};
struct { items = items, index = 0, current = current }
}Narrows a wire-bounded SSZ offset at the host byte-position boundary.
function ssz_offset_to_source_pointer(value : ssz_offset) -> stateless_input_pointer =
valuefunction ssz_u32(input, offset) = ssz_u32_at(input, offset)A sequential cursor over a variable-element SSZ list.
struct BoundedSszListCursor('maximum : Int), source_valid_length('maximum) = {
items : BoundedSszListRef('maximum),
index : range(0, 'maximum),
current : source_pointer,
}A reference to an SSZ list: its byte span and schema-bounded element count.
struct BoundedSszListRef('maximum : Int), source_valid_length('maximum) = {
bytes : StatelessInputSlice,
count : range(0, 'maximum),
/* Zero means that this generic view carries no per-item schema bound. */
max_item_length : source_length,
}Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundfunction ssz_list_cursor_empty¶
Whether the cursor has consumed all elements.
function ssz_list_cursor_empty forall ('maximum : Int), source_valid_length('maximum). (cursor :
BoundedSszListCursor('maximum)) -> (
bool
) =
cursor.items.count <= cursor.indexA sequential cursor over a variable-element SSZ list.
struct BoundedSszListCursor('maximum : Int), source_valid_length('maximum) = {
items : BoundedSszListRef('maximum),
index : range(0, 'maximum),
current : source_pointer,
}Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundfunction ssz_list_pop¶
The next element's span, and the advanced cursor.
function ssz_list_pop forall ('maximum : Int), source_valid_length('maximum) & 'maximum <= 2 ^ 30 - 1. (cursor :
BoundedSszListCursor('maximum)) -> (
(StatelessInputSlice, BoundedSszListCursor('maximum))
) = {
let count = cursor.items.count;
let index = cursor.index;
let next_index : range(0, 'maximum) =
if index < count then index + 1 else fatal_error(InvalidConfig);
let items = cursor.items;
let bytes = items.bytes;
let nat = bytes.len;
let next =
if next_index < items.count then {
let table_position = ssz_offset_table_position(next_index);
let next_offset = ssz_u32_in_slice(bytes, table_position);
ssz_offset_to_source_pointer(next_offset)
} else {
nat
};
let current_value = cursor.current;
let next_value = next;
if (current_value <= next_value) & (next_value <= nat) then {
let item_length = next_value - current_value;
if (items.max_item_length != 0) & (items.max_item_length < item_length) then {
fatal_error(InvalidConfig)
};
let item = sub_slice(bytes, current_value, item_length);
(item, struct { items = items, index = next_index, current = next })
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())Returns the byte position of an entry in a uint32 SSZ offset table.
function ssz_offset_table_position(index : ssz_offset_index) -> ssz_offset = {
index * 4
}Narrows a wire-bounded SSZ offset at the host byte-position boundary.
function ssz_offset_to_source_pointer(value : ssz_offset) -> stateless_input_pointer =
valueReads an offset-table entry after establishing that the dynamic table position is contained by its enclosing input slice.
function ssz_u32_in_slice(input : StatelessInputSlice, offset : ssz_offset) -> ssz_offset =
if offset <= input.len & 4 <= input.len - offset then {
ssz_u32_at(input, offset)
} else {
fatal_error(InvalidConfig)
}A sequential cursor over a variable-element SSZ list.
struct BoundedSszListCursor('maximum : Int), source_valid_length('maximum) = {
items : BoundedSszListRef('maximum),
index : range(0, 'maximum),
current : source_pointer,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundfunction ssz_list_at¶
Returns a variable-width list item by resolving its adjacent offsets.
function ssz_list_at forall ('maximum : Int) ('index : Int),
source_valid_length('maximum) & 'maximum <= 2 ^ 30 - 1 & 0 <= 'index. (
items : BoundedSszListRef('maximum),
index : int('index),
) -> (
StatelessInputSlice
) = {
let bytes = items.bytes;
let count = items.count;
let item_index : range(0, 'maximum - 1) =
if index < count then index else fatal_error(InvalidConfig);
let next_index : range(0, 'maximum) = item_index + 1;
let start_position = ssz_offset_table_position(item_index);
let start_offset = ssz_u32_in_slice(bytes, start_position);
let start = ssz_offset_to_source_pointer(start_offset);
let stop : source_pointer =
if next_index < items.count then {
let stop_position = ssz_offset_table_position(next_index);
let stop_offset = ssz_u32_in_slice(bytes, stop_position);
ssz_offset_to_source_pointer(stop_offset)
} else {
bytes.len
};
let start_value = start;
let stop_value = stop;
let items_length = bytes.len;
if (start_value <= stop_value) & (stop_value <= items_length) then {
let item_length = stop_value - start_value;
if (items.max_item_length != 0) & (items.max_item_length < item_length) then {
fatal_error(InvalidConfig)
};
sub_slice(bytes, start, item_length)
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())Returns the byte position of an entry in a uint32 SSZ offset table.
function ssz_offset_table_position(index : ssz_offset_index) -> ssz_offset = {
index * 4
}Narrows a wire-bounded SSZ offset at the host byte-position boundary.
function ssz_offset_to_source_pointer(value : ssz_offset) -> stateless_input_pointer =
valueReads an offset-table entry after establishing that the dynamic table position is contained by its enclosing input slice.
function ssz_u32_in_slice(input : StatelessInputSlice, offset : ssz_offset) -> ssz_offset =
if offset <= input.len & 4 <= input.len - offset then {
ssz_u32_at(input, offset)
} else {
fatal_error(InvalidConfig)
}A reference to an SSZ list: its byte span and schema-bounded element count.
struct BoundedSszListRef('maximum : Int), source_valid_length('maximum) = {
bytes : StatelessInputSlice,
count : range(0, 'maximum),
/* Zero means that this generic view carries no per-item schema bound. */
max_item_length : source_length,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An absolute byte position in a named source region.
type source_pointer = range(0, default_host_region_bound)Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundfunction ssz_fixed_list_at¶
function ssz_fixed_list_at(items, index, item_size) = {
let bytes = items.bytes;
let width_value = item_size;
let offset_value = index * width_value;
let items_length = bytes.len;
if (index < items.count) & (offset_value + width_value <= items_length) then {
sub_slice(bytes, offset_value, width_value)
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())function ssz_fixed_list_at(items, index, item_size) = {
let bytes = items.bytes;
let width_value = item_size;
let offset_value = index * width_value;
let items_length = bytes.len;
if (index < items.count) & (offset_value + width_value <= items_length) then {
sub_slice(bytes, offset_value, width_value)
} else {
fatal_error(InvalidConfig)
}
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}function ssz_fixed_list_pop¶
function ssz_fixed_list_pop(items, item_size) = {
let bytes = items.bytes;
let items_length = bytes.len;
let width = item_size;
let count = items.count;
if (0 < count) & (width <= items_length) then {
let item = sub_slice(bytes, 0, item_size);
let rest : BoundedSszListRef('maximum) = struct {
bytes = slice_suffix(bytes, width),
count = count - 1,
max_item_length = items.max_item_length,
};
(item, rest)
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())function ssz_fixed_list_pop(items, item_size) = {
let bytes = items.bytes;
let items_length = bytes.len;
let width = item_size;
let count = items.count;
if (0 < count) & (width <= items_length) then {
let item = sub_slice(bytes, 0, item_size);
let rest : BoundedSszListRef('maximum) = struct {
bytes = slice_suffix(bytes, width),
count = count - 1,
max_item_length = items.max_item_length,
};
(item, rest)
} else {
fatal_error(InvalidConfig)
}
}A reference to an SSZ list: its byte span and schema-bounded element count.
struct BoundedSszListRef('maximum : Int), source_valid_length('maximum) = {
bytes : StatelessInputSlice,
count : range(0, 'maximum),
/* Zero means that this generic view carries no per-item schema bound. */
max_item_length : source_length,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}type StatelessInputRef¶
Every variable region of the input, resolved once before decoding. Consumers receive explicit source spans instead of re-reading nested SSZ offset tables.
struct StatelessInputRef = {
protocol : ProtocolProfile,
new_payload_request : StatelessInputSlice,
execution_payload : StatelessInputSliceAtLeast(540),
versioned_hashes : StatelessInputSlice,
deposits : StatelessInputSlice,
withdrawal_requests : StatelessInputSlice,
consolidation_requests : StatelessInputSlice,
builder_deposit_requests : StatelessInputSlice,
builder_exit_requests : StatelessInputSlice,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
transactions : TransactionListRef,
withdrawals : WithdrawalListRef,
block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound),
witness_state : WitnessNodeListRef,
witness_codes : WitnessCodeListRef,
witness_headers : WitnessHeaderListRef,
chain_config : StatelessInputSlice,
public_keys : StatelessInputSlice,
}A protocol profile with its parameter tuple packed existentially; unpacking recovers the admitted combination's equations.
type ProtocolProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'transaction_total_gas_limit
'transaction_regular_gas_limit
'transaction_blob_limit
'refund_divisor,
protocol_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
).
ProtocolProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}A stateless-input range of at least 'minimum bytes.
type StatelessInputSliceAtLeast('minimum : Int) = {
'off 'len,
stateless_input_valid_range('off, 'len) & 0 <= 'minimum & 'minimum <= 'len.
StatelessInputSliceFields('off, 'len)
}A stateless-input range of at most 'maximum bytes.
type StatelessInputSliceAtMost('maximum : Int) = {
'off 'len,
stateless_input_valid_range('off, 'len) & 0 <= 'maximum & 'len <= 'maximum.
StatelessInputSliceFields('off, 'len)
}A schema-bounded source reference to encoded transaction envelopes.
type TransactionListRef = BoundedSszListRef(transaction_count_bound)A schema-bounded source reference to withdrawals.
type WithdrawalListRef = BoundedSszListRef(withdrawal_count_bound)A schema-bounded source reference to witnessed code entries.
type WitnessCodeListRef = BoundedSszListRef(witness_code_count_bound)A schema-bounded source reference to witnessed parent headers.
type WitnessHeaderListRef = BoundedSszListRef(witness_header_count_bound)A schema-bounded source reference to witnessed trie nodes.
type WitnessNodeListRef = BoundedSszListRef(witness_node_count_bound)Maximum byte length of the block access list. Provenance: Amsterdam
SszExecutionPayload.block_access_list uses
ByteList[MAX_BYTES_PER_TRANSACTION].
type block_access_list_length_bound : Int = 2 ^ 30Maximum byte length of execution-payload extra data. Provenance:
consensus MAX_EXTRA_DATA_BYTES and Amsterdam
SszExecutionPayload.extra_data.
type extra_data_length_bound : Int = 2 ^ 5function ssz_container_bytes¶
function ssz_container_bytes(bytes, minimum) = {
let fields = bytes;
if minimum <= fields.len then {
fields
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())function ssz_container_bytes(bytes, minimum) = {
let fields = bytes;
if minimum <= fields.len then {
fields
} else {
fatal_error(InvalidConfig)
}
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}type SszContainerCursor¶
A sequential position within one SSZ container's variable region. The container slice carries the region base and limit, so offsets cannot be mixed with those of an enclosing container.
struct SszContainerCursor = {
bytes : StatelessInputSlice,
current : source_pointer,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An absolute byte position in a named source region.
type source_pointer = range(0, default_host_region_bound)function ssz_container_cursor¶
function ssz_container_cursor(bytes, fixed_length) =
struct { bytes = bytes, current = fixed_length }function ssz_container_cursor(bytes, fixed_length) =
struct { bytes = bytes, current = fixed_length }function ssz_take¶
Takes the next variable field, ending at its container-relative SSZ offset, and returns the advanced cursor.
function ssz_take(cursor : SszContainerCursor, stop : ssz_offset) -> (StatelessInputSlice, SszContainerCursor) = {
let current_value = cursor.current;
let bytes = cursor.bytes;
let container_length = bytes.len;
if (current_value <= stop) & (stop <= container_length) then {
let stop_pointer : source_pointer = stop;
let span_length = stop - current_value;
let span = sub_slice(bytes, current_value, span_length);
(span, struct { bytes = bytes, current = stop_pointer })
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A sequential position within one SSZ container's variable region. The container slice carries the region base and limit, so offsets cannot be mixed with those of an enclosing container.
struct SszContainerCursor = {
bytes : StatelessInputSlice,
current : source_pointer,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An absolute byte position in a named source region.
type source_pointer = range(0, default_host_region_bound)A container-relative offset carried by an SSZ uint32.
type ssz_offset = range(0, 2 ^ 32 - 1)function ssz_finish¶
Takes the remainder of a container after its last offset-delimited field.
function ssz_finish(cursor : SszContainerCursor) -> StatelessInputSlice = {
let current_value = cursor.current;
let bytes = cursor.bytes;
let container_length = bytes.len;
if current_value <= container_length then {
let remaining = container_length - current_value;
sub_slice(bytes, current_value, remaining)
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A sequential position within one SSZ container's variable region. The container slice carries the region base and limit, so offsets cannot be mixed with those of an enclosing container.
struct SszContainerCursor = {
bytes : StatelessInputSlice,
current : source_pointer,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}function ssz_bounded_variable_list_ref¶
Constructs a variable-item list with its SSZ count and per-item byte limits attached. Item lengths are checked lazily when reached, before a consumer can construct any narrower optimized value.
function ssz_bounded_variable_list_ref forall ('maximum : Int) ('item_length : Int),
source_valid_length('maximum) & source_valid_length('item_length). (
bytes : StatelessInputSlice,
maximum_count : int('maximum),
maximum_item_length : int('item_length),
) -> (
BoundedSszListRef('maximum)
) = {
let span = bytes.len;
let raw_count : ssz_offset_index =
if span == 0 then {
0
} else {
if span < SSZ_OFF_BYTES then {
fatal_error(InvalidConfig)
};
let first_offset = ssz_u32(bytes, 0);
let count : ssz_offset_index = tdiv_nat(first_offset, 4);
let offset_remainder = tmod_int(first_offset, 4);
if (offset_remainder != 0) | (count == 0) | first_offset > span then {
fatal_error(InvalidConfig)
};
count
};
let count : range(0, 'maximum) =
if raw_count <= maximum_count then raw_count else fatal_error(InvalidConfig);
struct { bytes = bytes, count = count, max_item_length = maximum_item_length }
}function fatal_error(_reason) = exit(())function ssz_u32(input, offset) = ssz_u32_at(input, offset)Truncating division specialized to a non-negative dividend and positive divisor. Singleton operands determine the exact natural-number result.
val tdiv_nat = pure {smt: "div", ocaml: "quotient", interpreter: "quotient", lem: "integerDiv", c: "tdiv_int", cpp: "tdiv_int", systemverilog: "tdiv_int", coq: "Z.quot", lean: "Nat.div", _: "tdiv_int"}: forall ('n : Int) ('m : Int), ('n >= 0 & 'm >= 1).
(int('n), int('m)) -> int(div('n, 'm))The width of one entry in an SSZ variable-field offset table
(uint32, little-endian).
let SSZ_OFF_BYTES : int(4) = 4A reference to an SSZ list: its byte span and schema-bounded element count.
struct BoundedSszListRef('maximum : Int), source_valid_length('maximum) = {
bytes : StatelessInputSlice,
count : range(0, 'maximum),
/* Zero means that this generic view carries no per-item schema bound. */
max_item_length : source_length,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundAn index into a table of four-byte SSZ offsets.
type ssz_offset_index = range(0, 2 ^ 30 - 1)function ssz_bounded_fixed_list_ref¶
Constructs a schema-bounded fixed-item SSZ list reference.
function ssz_bounded_fixed_list_ref forall ('maximum : Int) ('item_size : Int),
source_valid_length('maximum) & source_valid_length('item_size). (
bytes : StatelessInputSlice,
item_size : int('item_size),
maximum_count : int('maximum),
) -> (
BoundedSszListRef('maximum)
) = {
let width : range(1, 'item_size) =
if 0 < item_size then item_size else fatal_error(InvalidConfig);
let span = bytes.len;
let raw_count = span / width;
if span != raw_count * width then {
fatal_error(InvalidConfig)
};
let count : range(0, 'maximum) =
if raw_count <= maximum_count then raw_count else fatal_error(InvalidConfig);
struct { bytes = bytes, count = count, max_item_length = item_size }
}function fatal_error(_reason) = exit(())A reference to an SSZ list: its byte span and schema-bounded element count.
struct BoundedSszListRef('maximum : Int), source_valid_length('maximum) = {
bytes : StatelessInputSlice,
count : range(0, 'maximum),
/* Zero means that this generic view carries no per-item schema bound. */
max_item_length : source_length,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}Whether one relative source coordinate is representable.
type source_valid_length('value : Int) -> Bool =
0 <= 'value & 'value <= default_host_region_boundfunction decode_stateless_input_ref¶
Resolves the input's offset tables into a
StatelessInputRef, validating the schema id
and every region bound; a malformed frame is InvalidConfig.
function decode_stateless_input_ref(input : StatelessInputSlice) -> StatelessInputRef = {
let fixed_length = STATELESS_INPUT_FIXED_LENGTH;
let body_offset_value = SSZ_BODY;
let input_fields : StatelessInputSliceAtLeast(18) =
if fixed_length <= input.len then input else fatal_error(InvalidConfig);
let schema_version = slice_byte(input_fields, 1);
if schema_version != 0x01 then {
fatal_error(InvalidConfig)
};
let schema_fork = slice_byte(input_fields, 0);
let schema_matches = schema_protocol_profile_forwards_matches(schema_fork);
let schema_mismatch = not_bool(schema_matches);
if schema_mismatch then {
fatal_error(InvalidConfig)
};
let protocol : ProtocolProfile = schema_protocol_profile(schema_fork);
let body_bytes = slice_suffix(input_fields, body_offset_value);
let body = ssz_container_bytes(body_bytes, STATELESS_INPUT_BODY_FIXED_LENGTH);
let container_start : int(0) = 0;
let new_payload_request_position = ssz_field_offset(container_start, IN_NPR_OFF);
let new_payload_request_offset = ssz_u32(body, new_payload_request_position);
let witness_position = ssz_field_offset(container_start, IN_WITNESS_OFF);
let witness_offset = ssz_u32(body, witness_position);
let chain_config_position = ssz_field_offset(container_start, IN_CHAIN_CONFIG_OFF);
let chain_config_offset = ssz_u32(body, chain_config_position);
let public_keys_position = ssz_field_offset(container_start, IN_PUBLIC_KEYS_OFF);
let public_keys_offset = ssz_u32(body, public_keys_position);
let body_fixed_length = STATELESS_INPUT_BODY_FIXED_LENGTH;
if new_payload_request_offset != body_fixed_length then {
fatal_error(InvalidConfig)
};
let body_cursor = ssz_container_cursor(body, STATELESS_INPUT_BODY_FIXED_LENGTH);
let (new_payload_request_bytes, body_after_payload_request) = ssz_take(body_cursor, witness_offset);
let new_payload_request = ssz_container_bytes(new_payload_request_bytes, NEW_PAYLOAD_REQUEST_FIXED_LENGTH);
let (execution_witness_bytes, body_after_witness) = ssz_take(body_after_payload_request, chain_config_offset);
let execution_witness = ssz_container_bytes(execution_witness_bytes, EXECUTION_WITNESS_FIXED_LENGTH);
let (chain_config, body_after_chain_config) = ssz_take(body_after_witness, public_keys_offset);
let public_keys = ssz_finish(body_after_chain_config);
let npr_start : int(0) = 0;
let payload_position = ssz_field_offset(npr_start, NPR_PAYLOAD_OFF);
let payload_offset = ssz_u32(new_payload_request, payload_position);
let versioned_hashes_position = ssz_field_offset(npr_start, NPR_VHASHES_OFF);
let versioned_hashes_offset = ssz_u32(new_payload_request, versioned_hashes_position);
let requests_position = ssz_field_offset(npr_start, NPR_REQUESTS_OFF);
let requests_offset = ssz_u32(new_payload_request, requests_position);
let npr_fixed_length = NEW_PAYLOAD_REQUEST_FIXED_LENGTH;
if payload_offset != npr_fixed_length then {
fatal_error(InvalidConfig)
};
let npr_cursor = ssz_container_cursor(new_payload_request, NEW_PAYLOAD_REQUEST_FIXED_LENGTH);
let (execution_payload_bytes, npr_after_payload) = ssz_take(npr_cursor, versioned_hashes_offset);
let (versioned_hashes, npr_after_versioned_hashes) = ssz_take(npr_after_payload, requests_offset);
let execution_requests_bytes = ssz_finish(npr_after_versioned_hashes);
let execution_requests = ssz_container_bytes(execution_requests_bytes, EXECUTION_REQUESTS_FIXED_LENGTH);
let execution_payload = ssz_container_bytes(execution_payload_bytes, EXECUTION_PAYLOAD_FIXED_LENGTH);
let payload_start : int(0) = 0;
let extra_data_position = ssz_field_offset(payload_start, PL_EXTRA_OFF);
let extra_data_offset = ssz_u32(execution_payload, extra_data_position);
let transactions_position = ssz_field_offset(payload_start, PL_TXS_OFF);
let transactions_offset = ssz_u32(execution_payload, transactions_position);
let withdrawals_position = ssz_field_offset(payload_start, PL_WDS_OFF);
let withdrawals_offset = ssz_u32(execution_payload, withdrawals_position);
let block_access_list_position = ssz_field_offset(payload_start, PL_BAL_OFF);
let block_access_list_offset = ssz_u32(execution_payload, block_access_list_position);
let payload_fixed_length = EXECUTION_PAYLOAD_FIXED_LENGTH;
if extra_data_offset != payload_fixed_length then {
fatal_error(InvalidConfig)
};
let payload_cursor = ssz_container_cursor(execution_payload, EXECUTION_PAYLOAD_FIXED_LENGTH);
let (extra_data_bytes, payload_after_extra_data) = ssz_take(payload_cursor, transactions_offset);
let (transaction_bytes, payload_after_transactions) = ssz_take(payload_after_extra_data, withdrawals_offset);
let transactions = ssz_bounded_variable_list_ref(
transaction_bytes,
MAX_TRANSACTIONS_PER_PAYLOAD,
MAX_TRANSACTION_LENGTH,
);
let (withdrawal_bytes, payload_after_withdrawals) = ssz_take(payload_after_transactions, block_access_list_offset);
let withdrawals = ssz_bounded_fixed_list_ref(withdrawal_bytes, WD_SIZE, MAX_WITHDRAWALS_PER_PAYLOAD);
let block_access_list_bytes = ssz_finish(payload_after_withdrawals);
let extra_data : StatelessInputSliceAtMost(extra_data_length_bound) =
if extra_data_bytes.len <= MAX_EXTRA_DATA_LENGTH then extra_data_bytes else fatal_error(InvalidConfig);
let block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound) =
if block_access_list_bytes.len <= MAX_BLOCK_ACCESS_LIST_LENGTH
then block_access_list_bytes
else fatal_error(InvalidConfig);
let requests_start : int(0) = 0;
let deposits_position = ssz_field_offset(requests_start, REQ_DEPOSITS_OFF);
let deposits_offset = ssz_u32(execution_requests, deposits_position);
let withdrawal_requests_position = ssz_field_offset(requests_start, REQ_WITHDRAWALS_OFF);
let withdrawal_requests_offset = ssz_u32(execution_requests, withdrawal_requests_position);
let consolidation_requests_position = ssz_field_offset(requests_start, REQ_CONSOLIDATIONS_OFF);
let consolidation_requests_offset = ssz_u32(execution_requests, consolidation_requests_position);
let builder_deposit_requests_position = ssz_field_offset(requests_start, REQ_BUILDER_DEPOSITS_OFF);
let builder_deposit_requests_offset = ssz_u32(execution_requests, builder_deposit_requests_position);
let builder_exit_requests_position = ssz_field_offset(requests_start, REQ_BUILDER_EXITS_OFF);
let builder_exit_requests_offset = ssz_u32(execution_requests, builder_exit_requests_position);
let requests_fixed_length = EXECUTION_REQUESTS_FIXED_LENGTH;
if deposits_offset != requests_fixed_length then {
fatal_error(InvalidConfig)
};
let requests_cursor = ssz_container_cursor(execution_requests, EXECUTION_REQUESTS_FIXED_LENGTH);
let (deposits, requests_after_deposits) = ssz_take(requests_cursor, withdrawal_requests_offset);
let (withdrawal_requests, requests_after_withdrawals) = ssz_take(
requests_after_deposits,
consolidation_requests_offset,
);
let (consolidation_requests, requests_after_consolidations) = ssz_take(
requests_after_withdrawals,
builder_deposit_requests_offset,
);
let (builder_deposit_requests, requests_after_builder_deposits) = ssz_take(
requests_after_consolidations,
builder_exit_requests_offset,
);
let builder_exit_requests = ssz_finish(requests_after_builder_deposits);
let witness_start : int(0) = 0;
let witness_state_position = ssz_field_offset(witness_start, WIT_STATE_OFF);
let witness_state_offset = ssz_u32(execution_witness, witness_state_position);
let witness_codes_position = ssz_field_offset(witness_start, WIT_CODES_OFF);
let witness_codes_offset = ssz_u32(execution_witness, witness_codes_position);
let witness_headers_position = ssz_field_offset(witness_start, WIT_HEADERS_OFF);
let witness_headers_offset = ssz_u32(execution_witness, witness_headers_position);
let witness_fixed_length = EXECUTION_WITNESS_FIXED_LENGTH;
if witness_state_offset != witness_fixed_length then {
fatal_error(InvalidConfig)
};
let witness_cursor = ssz_container_cursor(execution_witness, EXECUTION_WITNESS_FIXED_LENGTH);
let (witness_state_bytes, witness_after_state) = ssz_take(witness_cursor, witness_codes_offset);
let witness_state = ssz_bounded_variable_list_ref(witness_state_bytes, MAX_WITNESS_NODES, MAX_WITNESS_NODE_LENGTH);
let (witness_code_bytes, witness_after_codes) = ssz_take(witness_after_state, witness_headers_offset);
let witness_codes = ssz_bounded_variable_list_ref(witness_code_bytes, MAX_WITNESS_CODES, MAX_WITNESS_CODE_LENGTH);
let witness_header_bytes = ssz_finish(witness_after_codes);
let witness_headers = ssz_bounded_variable_list_ref(
witness_header_bytes,
MAX_WITNESS_HEADERS,
MAX_WITNESS_HEADER_LENGTH,
);
let public_key_bytes = public_keys.len;
let public_key_length = PUBLIC_KEY_LENGTH;
let public_key_count = public_key_bytes / public_key_length;
if public_key_bytes != public_key_count * public_key_length then {
fatal_error(InvalidConfig)
};
if MAX_PUBLIC_KEYS < public_key_count then {
fatal_error(InvalidConfig)
};
let _ = ssz_bounded_fixed_list_ref(versioned_hashes, WORD_BYTE_LENGTH, MAX_BLOB_COMMITMENTS_PER_BLOCK);
struct {
protocol = protocol,
new_payload_request = new_payload_request,
execution_payload = execution_payload,
versioned_hashes = versioned_hashes,
deposits = deposits,
withdrawal_requests = withdrawal_requests,
consolidation_requests = consolidation_requests,
builder_deposit_requests = builder_deposit_requests,
builder_exit_requests = builder_exit_requests,
extra_data = extra_data,
transactions = transactions,
withdrawals = withdrawals,
block_access_list = block_access_list,
witness_state = witness_state,
witness_codes = witness_codes,
witness_headers = witness_headers,
chain_config = chain_config,
public_keys = public_keys,
}
}function fatal_error(_reason) = exit(())val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))The schema's stable fork byte selects one complete protocol profile. The branches are the sole table of admitted schema/profile combinations.
function schema_protocol_profile(schema_fork : byte) -> ProtocolProfile =
match schema_fork {
0x0a => {
let profile = struct {
fork = Berlin,
blob_schedule =
blob_schedule(
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
excess_blob_gas_limit =
compute_profile_excess_blob_gas_limit(
Berlin,
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
deployed_code_size_limit = sizeof(pre_amsterdam_deployed_code_size_limit),
initcode_size_limit = sizeof(inactive_initcode_size_limit),
transaction_total_gas_limit = sizeof(ssz_uint_bound),
transaction_regular_gas_limit = sizeof(ssz_uint_bound),
transaction_blob_limit = sizeof(blob_schedule_inactive_count),
refund_divisor = sizeof(pre_london_refund_divisor),
} :
ProtocolProfileFields(
berlin_fork_value,
blob_schedule_inactive_count,
blob_schedule_inactive_count,
inactive_blob_fee_update_fraction,
pre_amsterdam_deployed_code_size_limit,
inactive_initcode_size_limit,
ssz_uint_bound,
ssz_uint_bound,
blob_schedule_inactive_count,
pre_london_refund_divisor,
);
pack_protocol_profile(profile)
},
0x0b => {
let profile = struct {
fork = London,
blob_schedule =
blob_schedule(
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
excess_blob_gas_limit =
compute_profile_excess_blob_gas_limit(
London,
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
deployed_code_size_limit = sizeof(pre_amsterdam_deployed_code_size_limit),
initcode_size_limit = sizeof(inactive_initcode_size_limit),
transaction_total_gas_limit = sizeof(ssz_uint_bound),
transaction_regular_gas_limit = sizeof(ssz_uint_bound),
transaction_blob_limit = sizeof(blob_schedule_inactive_count),
refund_divisor = sizeof(post_london_refund_divisor),
} :
ProtocolProfileFields(
london_fork_value,
blob_schedule_inactive_count,
blob_schedule_inactive_count,
inactive_blob_fee_update_fraction,
pre_amsterdam_deployed_code_size_limit,
inactive_initcode_size_limit,
ssz_uint_bound,
ssz_uint_bound,
blob_schedule_inactive_count,
post_london_refund_divisor,
);
pack_protocol_profile(profile)
},
0x0c => {
let profile = struct {
fork = ArrowGlacier,
blob_schedule =
blob_schedule(
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
excess_blob_gas_limit =
compute_profile_excess_blob_gas_limit(
ArrowGlacier,
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
sizeof(inactive_blob_fee_update_fraction),
),
deployed_code_size_limit = sizeof(pre_amsterdam_deployed_code_size_limit),
initcode_size_limit = sizeof(inactive_initcode_size_limit),
transaction_total_gas_limit = sizeof(ssz_uint_bound),
transaction_regular_gas_limit = sizeof(ssz_uint_bound),
transaction_blob_limit = sizeof(blob_schedule_inactive_count),
refund_divisor = sizeof(post_london_refund_divisor),
} :
ProtocolProfileFields(
arrow_glacier_fork_value,
blob_schedule_inactive_count,
blob_schedule_inactive_count,
inactive_blob_fee_update_fraction,
pre_amsterdam_deployed_code_size_limit,
inactive_initcode_size_limit,
ssz_uint_bound,
ssz_uint_bound,
blob_schedule_inactive_count,
post_london_refund_divisor,
);
pack_protocol_profile(profile)
},
0x0d => {
let profile = struct {
fork = GrayGlacier,
blob_schedule =
blob_schedule(
sizeof(blob_schedule_inactive_count),
sizeof(blob_schedule_inactive_count),
…Whether a schema fork byte has a protocol-profile branch.
function schema_protocol_profile_forwards_matches(schema_fork : byte) -> bool =
(schema_fork == 0x0a)
| (schema_fork == 0x0b)
| (schema_fork == 0x0c)
| (schema_fork == 0x0d)
| (schema_fork == 0x0e)
| (schema_fork == 0x0f)
| (schema_fork == 0x10)
| (schema_fork == 0x11)
| (schema_fork == 0x12)
| (schema_fork == 0x13)
| (schema_fork == 0x14)
| (schema_fork == 0x15)Constructs a schema-bounded fixed-item SSZ list reference.
function ssz_bounded_fixed_list_ref forall ('maximum : Int) ('item_size : Int),
source_valid_length('maximum) & source_valid_length('item_size). (
bytes : StatelessInputSlice,
item_size : int('item_size),
maximum_count : int('maximum),
) -> (
BoundedSszListRef('maximum)
) = {
let width : range(1, 'item_size) =
if 0 < item_size then item_size else fatal_error(InvalidConfig);
let span = bytes.len;
let raw_count = span / width;
if span != raw_count * width then {
fatal_error(InvalidConfig)
};
let count : range(0, 'maximum) =
if raw_count <= maximum_count then raw_count else fatal_error(InvalidConfig);
struct { bytes = bytes, count = count, max_item_length = item_size }
}Constructs a variable-item list with its SSZ count and per-item byte limits attached. Item lengths are checked lazily when reached, before a consumer can construct any narrower optimized value.
function ssz_bounded_variable_list_ref forall ('maximum : Int) ('item_length : Int),
source_valid_length('maximum) & source_valid_length('item_length). (
bytes : StatelessInputSlice,
maximum_count : int('maximum),
maximum_item_length : int('item_length),
) -> (
BoundedSszListRef('maximum)
) = {
let span = bytes.len;
let raw_count : ssz_offset_index =
if span == 0 then {
0
} else {
if span < SSZ_OFF_BYTES then {
fatal_error(InvalidConfig)
};
let first_offset = ssz_u32(bytes, 0);
let count : ssz_offset_index = tdiv_nat(first_offset, 4);
let offset_remainder = tmod_int(first_offset, 4);
if (offset_remainder != 0) | (count == 0) | first_offset > span then {
fatal_error(InvalidConfig)
};
count
};
let count : range(0, 'maximum) =
if raw_count <= maximum_count then raw_count else fatal_error(InvalidConfig);
struct { bytes = bytes, count = count, max_item_length = maximum_item_length }
}function ssz_container_bytes(bytes, minimum) = {
let fields = bytes;
if minimum <= fields.len then {
fields
} else {
fatal_error(InvalidConfig)
}
}function ssz_container_cursor(bytes, fixed_length) =
struct { bytes = bytes, current = fixed_length }function ssz_field_offset(base, delta) = base + deltaTakes the remainder of a container after its last offset-delimited field.
function ssz_finish(cursor : SszContainerCursor) -> StatelessInputSlice = {
let current_value = cursor.current;
let bytes = cursor.bytes;
let container_length = bytes.len;
if current_value <= container_length then {
let remaining = container_length - current_value;
sub_slice(bytes, current_value, remaining)
} else {
fatal_error(InvalidConfig)
}
}Takes the next variable field, ending at its container-relative SSZ offset, and returns the advanced cursor.
function ssz_take(cursor : SszContainerCursor, stop : ssz_offset) -> (StatelessInputSlice, SszContainerCursor) = {
let current_value = cursor.current;
let bytes = cursor.bytes;
let container_length = bytes.len;
if (current_value <= stop) & (stop <= container_length) then {
let stop_pointer : source_pointer = stop;
let span_length = stop - current_value;
let span = sub_slice(bytes, current_value, span_length);
(span, struct { bytes = bytes, current = stop_pointer })
} else {
fatal_error(InvalidConfig)
}
}function ssz_u32(input, offset) = ssz_u32_at(input, offset)let EXECUTION_PAYLOAD_FIXED_LENGTH : int(540) = 540let EXECUTION_REQUESTS_FIXED_LENGTH : int(20) = 20let EXECUTION_WITNESS_FIXED_LENGTH : int(12) = 12let IN_CHAIN_CONFIG_OFF : int(8) = 8let IN_NPR_OFF : int(0) = 0let IN_PUBLIC_KEYS_OFF : int(12) = 12let IN_WITNESS_OFF : int(4) = 4let MAX_BLOB_COMMITMENTS_PER_BLOCK : range(blob_commitment_count_bound, blob_commitment_count_bound) = sizeof(
blob_commitment_count_bound
)let MAX_BLOCK_ACCESS_LIST_LENGTH : range(block_access_list_length_bound, block_access_list_length_bound) = sizeof(
block_access_list_length_bound
)let MAX_EXTRA_DATA_LENGTH : range(extra_data_length_bound, extra_data_length_bound) = sizeof(extra_data_length_bound)let MAX_PUBLIC_KEYS : range(public_key_count_bound, public_key_count_bound) = sizeof(public_key_count_bound)let MAX_TRANSACTIONS_PER_PAYLOAD : range(transaction_count_bound, transaction_count_bound) = sizeof(
transaction_count_bound
)let MAX_TRANSACTION_LENGTH : range(transaction_length_bound, transaction_length_bound) = sizeof(
transaction_length_bound
)let MAX_WITHDRAWALS_PER_PAYLOAD : range(withdrawal_count_bound, withdrawal_count_bound) = sizeof(withdrawal_count_bound)let MAX_WITNESS_CODES : range(witness_code_count_bound, witness_code_count_bound) = sizeof(witness_code_count_bound)let MAX_WITNESS_CODE_LENGTH : range(witness_code_length_bound, witness_code_length_bound) = sizeof(
witness_code_length_bound
)let MAX_WITNESS_HEADERS : range(witness_header_count_bound, witness_header_count_bound) = sizeof(
witness_header_count_bound
)let MAX_WITNESS_HEADER_LENGTH : range(witness_header_length_bound, witness_header_length_bound) = sizeof(
witness_header_length_bound
)let MAX_WITNESS_NODES : range(witness_node_count_bound, witness_node_count_bound) = sizeof(witness_node_count_bound)let MAX_WITNESS_NODE_LENGTH : range(witness_node_length_bound, witness_node_length_bound) = sizeof(
witness_node_length_bound
)let NEW_PAYLOAD_REQUEST_FIXED_LENGTH : int(44) = 44let NPR_PAYLOAD_OFF : int(0) = 0let NPR_REQUESTS_OFF : int(40) = 40let NPR_VHASHES_OFF : int(4) = 4let PL_BAL_OFF : int(528) = 528let PL_EXTRA_OFF : int(436) = 436let PL_TXS_OFF : int(504) = 504let PL_WDS_OFF : int(508) = 508let PUBLIC_KEY_LENGTH : int(65) = 65let REQ_BUILDER_DEPOSITS_OFF : int(12) = 12let REQ_BUILDER_EXITS_OFF : int(16) = 16let REQ_CONSOLIDATIONS_OFF : int(8) = 8let REQ_DEPOSITS_OFF : int(0) = 0let REQ_WITHDRAWALS_OFF : int(4) = 4The input starts with a 2-byte big-endian schema id; the SSZ body follows.
let SSZ_BODY : int(2) = 2let STATELESS_INPUT_BODY_FIXED_LENGTH : int(16) = 16let STATELESS_INPUT_FIXED_LENGTH : int(18) = 18let WD_SIZE : int(44) = 44let WIT_CODES_OFF : int(4) = 4let WIT_HEADERS_OFF : int(8) = 8let WIT_STATE_OFF : int(0) = 0let WORD_BYTE_LENGTH : int(32) = 32The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A protocol profile with its parameter tuple packed existentially; unpacking recovers the admitted combination's equations.
type ProtocolProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'transaction_total_gas_limit
'transaction_regular_gas_limit
'transaction_blob_limit
'refund_divisor,
protocol_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
).
ProtocolProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
)
}Every variable region of the input, resolved once before decoding. Consumers receive explicit source spans instead of re-reading nested SSZ offset tables.
struct StatelessInputRef = {
protocol : ProtocolProfile,
new_payload_request : StatelessInputSlice,
execution_payload : StatelessInputSliceAtLeast(540),
versioned_hashes : StatelessInputSlice,
deposits : StatelessInputSlice,
withdrawal_requests : StatelessInputSlice,
consolidation_requests : StatelessInputSlice,
builder_deposit_requests : StatelessInputSlice,
builder_exit_requests : StatelessInputSlice,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
transactions : TransactionListRef,
withdrawals : WithdrawalListRef,
block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound),
witness_state : WitnessNodeListRef,
witness_codes : WitnessCodeListRef,
witness_headers : WitnessHeaderListRef,
chain_config : StatelessInputSlice,
public_keys : StatelessInputSlice,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}A stateless-input range of at least 'minimum bytes.
type StatelessInputSliceAtLeast('minimum : Int) = {
'off 'len,
stateless_input_valid_range('off, 'len) & 0 <= 'minimum & 'minimum <= 'len.
StatelessInputSliceFields('off, 'len)
}A stateless-input range of at most 'maximum bytes.
type StatelessInputSliceAtMost('maximum : Int) = {
'off 'len,
stateless_input_valid_range('off, 'len) & 0 <= 'maximum & 'len <= 'maximum.
StatelessInputSliceFields('off, 'len)
}Maximum byte length of the block access list. Provenance: Amsterdam
SszExecutionPayload.block_access_list uses
ByteList[MAX_BYTES_PER_TRANSACTION].
type block_access_list_length_bound : Int = 2 ^ 30Maximum byte length of execution-payload extra data. Provenance:
consensus MAX_EXTRA_DATA_BYTES and Amsterdam
SszExecutionPayload.extra_data.
type extra_data_length_bound : Int = 2 ^ 5function sha256_request_digest¶
The EIP-7685 per-type request digest:
sha256(request_type ‖ request_data).
function sha256_request_digest(request_type : byte, s : StatelessInputSlice) -> hash = {
let digest_length = scratch_length_add(1, s.len);
let mark = scratch_reserve(digest_length);
scratch_push_byte(request_type);
scratch_push_slice(s);
let preimage = scratch_finish(mark);
let digest = sha256(preimage);
scratch_rewind(mark);
digest
}The slice covering everything pushed since start.
function scratch_finish(start : source_pointer) -> ScratchSlice =
let start_offset = start in
let arena = scratch_arena in
let stop_offset = arena.len in
if start_offset <= stop_offset then {
sub_slice(arena, start, stop_offset - start_offset)
} else {
assert(false, "scratch finish mark");
EMPTY_SCRATCH_SLICE
}function scratch_length_add(left, right) =
if right <= sizeof(scratch_region_bound) - left then {
left + right
} else {
assert(false, "scratch length overflow");
0
}Appends one byte without constructing a Sail list.
function scratch_push_byte(data : byte) -> unit = {
let arena = scratch_arena;
scratch_arena = host_scratch_store_byte(arena.len, data)
}function scratch_reserve(len) = {
let arena = scratch_arena;
let reserved = host_scratch_reserve(arena.len, len);
assert(reserved, "scratch reserve");
arena.len
}Discards everything pushed since mark.
function scratch_rewind(mark : source_pointer) -> unit =
let mark_offset = mark in
let arena = scratch_arena in
let cursor_offset = arena.len in
if mark_offset <= cursor_offset then {
scratch_arena = sub_slice(arena, 0, mark);
host_scratch_truncate(mark)
} else {
assert(false, "scratch rewind mark")
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An 8-bit byte.
type byte = bits(8)The common digest type used by trie, code, and block hashes.
type hash = b256function index_witness_nodes_cursor¶
Hashes and indexes every remaining witness trie node from its source slice.
function index_witness_nodes_cursor(cursor : WitnessNodeListCursor) -> unit = {
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (node, next) = ssz_list_pop(cursor);
let node_hash = keccak256(node);
nodedb_insert(node_hash, node.bytes, node.len);
index_witness_nodes_cursor(next)
}Hashes and indexes every remaining witness trie node from its source slice.
function index_witness_nodes_cursor(cursor : WitnessNodeListCursor) -> unit = {
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (node, next) = ssz_list_pop(cursor);
let node_hash = keccak256(node);
nodedb_insert(node_hash, node.bytes, node.len);
index_witness_nodes_cursor(next)
}Indexes a witness trie node: records its (offset, length) span in
the SSZ buffer under its KECCAK-256 hash.
val nodedb_insert = impure { c: "nodedb_insert" } : (hash, stateless_input_pointer, stateless_input_length) -> unitWhether the cursor has consumed all elements.
function ssz_list_cursor_empty forall ('maximum : Int), source_valid_length('maximum). (cursor :
BoundedSszListCursor('maximum)) -> (
bool
) =
cursor.items.count <= cursor.indexThe next element's span, and the advanced cursor.
function ssz_list_pop forall ('maximum : Int), source_valid_length('maximum) & 'maximum <= 2 ^ 30 - 1. (cursor :
BoundedSszListCursor('maximum)) -> (
(StatelessInputSlice, BoundedSszListCursor('maximum))
) = {
let count = cursor.items.count;
let index = cursor.index;
let next_index : range(0, 'maximum) =
if index < count then index + 1 else fatal_error(InvalidConfig);
let items = cursor.items;
let bytes = items.bytes;
let nat = bytes.len;
let next =
if next_index < items.count then {
let table_position = ssz_offset_table_position(next_index);
let next_offset = ssz_u32_in_slice(bytes, table_position);
ssz_offset_to_source_pointer(next_offset)
} else {
nat
};
let current_value = cursor.current;
let next_value = next;
if (current_value <= next_value) & (next_value <= nat) then {
let item_length = next_value - current_value;
if (items.max_item_length != 0) & (items.max_item_length < item_length) then {
fatal_error(InvalidConfig)
};
let item = sub_slice(bytes, current_value, item_length);
(item, struct { items = items, index = next_index, current = next })
} else {
fatal_error(InvalidConfig)
}
}A sequential cursor over witnessed trie nodes.
type WitnessNodeListCursor = BoundedSszListCursor(witness_node_count_bound)function index_witness_nodes¶
Indexes every witness trie node into the node-db, keyed by its KECCAK-256 digest, directly from the SSZ list reference.
function index_witness_nodes(nodes : WitnessNodeListRef) -> unit = {
let cursor = ssz_list_cursor(nodes);
index_witness_nodes_cursor(cursor)
}Hashes and indexes every remaining witness trie node from its source slice.
function index_witness_nodes_cursor(cursor : WitnessNodeListCursor) -> unit = {
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (node, next) = ssz_list_pop(cursor);
let node_hash = keccak256(node);
nodedb_insert(node_hash, node.bytes, node.len);
index_witness_nodes_cursor(next)
}A cursor at the first element of a variable-element SSZ list.
function ssz_list_cursor forall ('maximum : Int), source_valid_length('maximum). (items : BoundedSszListRef('maximum)) -> (
BoundedSszListCursor('maximum)
) = {
let bytes = items.bytes;
let current =
if items.count != 0 then {
let first_offset = ssz_u32(bytes, 0);
ssz_offset_to_source_pointer(first_offset)
} else {
bytes.len
};
struct { items = items, index = 0, current = current }
}A schema-bounded source reference to witnessed trie nodes.
type WitnessNodeListRef = BoundedSszListRef(witness_node_count_bound)function index_witness_codes_cursor¶
Analyzes and indexes every remaining witness code body from its source slice.
function index_witness_codes_cursor(cursor : WitnessCodeListCursor) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (code, next) = ssz_list_pop(cursor);
/* The witness envelope permits code blobs up to 2^16 bytes
* independently of the active fork's deployment limit. */
let code_length = code.len;
if MAX_WITNESS_CODE_LENGTH < code_length then {
fatal_error(InvalidConfig)
};
let executable = code_db_intern_input(code);
let _ = code_db_insert(executable, profile.fork);
index_witness_codes_cursor(next)
}Analyzes and stores code, returning its content hash.
function code_db_insert(code : CodeSlice, fork : Fork) -> hash = {
let jumpdest_table = analyze_code(code, fork);
let analyzed = analyzed_code(code, jumpdest_table);
code_db_store(analyzed)
}Normalizes stateless-input code into the code arena before analysis.
function code_db_intern_input(bytes : StatelessInputSlice) -> CodeSlice = {
let region = code_region_from_input(bytes);
validated_code_slice(region)
}function fatal_error(_reason) = exit(())Analyzes and indexes every remaining witness code body from its source slice.
function index_witness_codes_cursor(cursor : WitnessCodeListCursor) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (code, next) = ssz_list_pop(cursor);
/* The witness envelope permits code blobs up to 2^16 bytes
* independently of the active fork's deployment limit. */
let code_length = code.len;
if MAX_WITNESS_CODE_LENGTH < code_length then {
fatal_error(InvalidConfig)
};
let executable = code_db_intern_input(code);
let _ = code_db_insert(executable, profile.fork);
index_witness_codes_cursor(next)
}Whether the cursor has consumed all elements.
function ssz_list_cursor_empty forall ('maximum : Int), source_valid_length('maximum). (cursor :
BoundedSszListCursor('maximum)) -> (
bool
) =
cursor.items.count <= cursor.indexThe next element's span, and the advanced cursor.
function ssz_list_pop forall ('maximum : Int), source_valid_length('maximum) & 'maximum <= 2 ^ 30 - 1. (cursor :
BoundedSszListCursor('maximum)) -> (
(StatelessInputSlice, BoundedSszListCursor('maximum))
) = {
let count = cursor.items.count;
let index = cursor.index;
let next_index : range(0, 'maximum) =
if index < count then index + 1 else fatal_error(InvalidConfig);
let items = cursor.items;
let bytes = items.bytes;
let nat = bytes.len;
let next =
if next_index < items.count then {
let table_position = ssz_offset_table_position(next_index);
let next_offset = ssz_u32_in_slice(bytes, table_position);
ssz_offset_to_source_pointer(next_offset)
} else {
nat
};
let current_value = cursor.current;
let next_value = next;
if (current_value <= next_value) & (next_value <= nat) then {
let item_length = next_value - current_value;
if (items.max_item_length != 0) & (items.max_item_length < item_length) then {
fatal_error(InvalidConfig)
};
let item = sub_slice(bytes, current_value, item_length);
(item, struct { items = items, index = next_index, current = next })
} else {
fatal_error(InvalidConfig)
}
}let MAX_WITNESS_CODE_LENGTH : range(witness_code_length_bound, witness_code_length_bound) = sizeof(
witness_code_length_bound
)The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEThe reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A sequential cursor over witnessed code entries.
type WitnessCodeListCursor = BoundedSszListCursor(witness_code_count_bound)A contract-code length.
type code_length = range(0, code_region_bound)function index_witness_codes¶
Indexes every witness code blob into the content-addressed code store.
function index_witness_codes(codes : WitnessCodeListRef) -> unit = {
let cursor = ssz_list_cursor(codes);
index_witness_codes_cursor(cursor)
}Analyzes and indexes every remaining witness code body from its source slice.
function index_witness_codes_cursor(cursor : WitnessCodeListCursor) -> unit = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(cursor);
if cursor_empty then {
return ()
};
let (code, next) = ssz_list_pop(cursor);
/* The witness envelope permits code blobs up to 2^16 bytes
* independently of the active fork's deployment limit. */
let code_length = code.len;
if MAX_WITNESS_CODE_LENGTH < code_length then {
fatal_error(InvalidConfig)
};
let executable = code_db_intern_input(code);
let _ = code_db_insert(executable, profile.fork);
index_witness_codes_cursor(next)
}A cursor at the first element of a variable-element SSZ list.
function ssz_list_cursor forall ('maximum : Int), source_valid_length('maximum). (items : BoundedSszListRef('maximum)) -> (
BoundedSszListCursor('maximum)
) = {
let bytes = items.bytes;
let current =
if items.count != 0 then {
let first_offset = ssz_u32(bytes, 0);
ssz_offset_to_source_pointer(first_offset)
} else {
bytes.len
};
struct { items = items, index = 0, current = current }
}A schema-bounded source reference to witnessed code entries.
type WitnessCodeListRef = BoundedSszListRef(witness_code_count_bound)type ParentHeaderFields¶
The parent-header fields required to initialize execution context.
struct ParentHeaderFields = {
parent_hash : hash,
state_root : hash,
base_fee : word,
blob_gas_used : blob_gas_used,
excess_blob_gas : excess_blob_gas,
have_parent : bool,
have_state : bool,
have_base_fee : bool,
have_blob_gas : bool,
have_excess_blob_gas : bool,
}Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)The common digest type used by trie, code, and block hashes.
type hash = b256The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)let EMPTY_PARENT_HEADER_FIELDS¶
let EMPTY_PARENT_HEADER_FIELDS : ParentHeaderFields = struct {
parent_hash = ZERO_HASH,
state_root = ZERO_HASH,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
have_parent = false,
have_state = false,
have_base_fee = false,
have_blob_gas = false,
have_excess_blob_gas = false,
}let ZERO_HASH : hash = hash_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The parent-header fields required to initialize execution context.
struct ParentHeaderFields = {
parent_hash : hash,
state_root : hash,
base_fee : word,
blob_gas_used : blob_gas_used,
excess_blob_gas : excess_blob_gas,
have_parent : bool,
have_state : bool,
have_base_fee : bool,
have_blob_gas : bool,
have_excess_blob_gas : bool,
}Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)type parent_header_field_cursor¶
A bounded parent-header field position, including the unused-field sentinel.
type parent_header_field_cursor = range(0, 19)function next_parent_header_field¶
Advances the parent-header field cursor, saturating at its unused-field sentinel.
function next_parent_header_field(index : parent_header_field_cursor) -> parent_header_field_cursor =
if index < 19 then {
index + 1
} else {
19
}A bounded parent-header field position, including the unused-field sentinel.
type parent_header_field_cursor = range(0, 19)function decode_parent_header_fields¶
Extracts the execution-relevant fields while walking one parent header.
function decode_parent_header_fields forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
field_index : parent_header_field_cursor,
fields : ParentHeaderFields,
) -> (
ParentHeaderFields
) =
if cursor.len == 0 then {
fields
} else {
let field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, field.source.len);
var decoded = fields;
if field_index == 0 then {
let parent_hash_word = rlp_decode_word(field);
decoded.parent_hash = word_to_hash(parent_hash_word);
decoded.have_parent = true
} else if field_index == 3 then {
let state_root_word = rlp_decode_word(field);
decoded.state_root = word_to_hash(state_root_word);
decoded.have_state = true
} else if field_index == 15 then {
decoded.base_fee = rlp_decode_u256(field);
decoded.have_base_fee = true
} else if field_index == 17 then {
let value = rlp_decode_uint64(field);
let count = value / sizeof(gas_per_blob_value);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if (count <= profile.blob_schedule.max) & (value == sizeof(gas_per_blob_value) * count) then {
decoded.blob_gas_used = sizeof(gas_per_blob_value) * count;
decoded.have_blob_gas = true
} else {
fatal_error(RlpDecode)
}
} else if field_index == 18 then {
let value = rlp_decode_uint64(field);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let limit = profile.excess_blob_gas_limit;
if value <= limit then {
decoded.excess_blob_gas = value;
decoded.have_excess_blob_gas = true
} else {
fatal_error(RlpDecode)
}
};
let next_field = next_parent_header_field(field_index);
decode_parent_header_fields(next, next_field, decoded)
}function blob_schedule(target, maximum, denominator) =
struct { target = target, max = maximum, base_fee_update_fraction = denominator }Extracts the execution-relevant fields while walking one parent header.
function decode_parent_header_fields forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
field_index : parent_header_field_cursor,
fields : ParentHeaderFields,
) -> (
ParentHeaderFields
) =
if cursor.len == 0 then {
fields
} else {
let field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, field.source.len);
var decoded = fields;
if field_index == 0 then {
let parent_hash_word = rlp_decode_word(field);
decoded.parent_hash = word_to_hash(parent_hash_word);
decoded.have_parent = true
} else if field_index == 3 then {
let state_root_word = rlp_decode_word(field);
decoded.state_root = word_to_hash(state_root_word);
decoded.have_state = true
} else if field_index == 15 then {
decoded.base_fee = rlp_decode_u256(field);
decoded.have_base_fee = true
} else if field_index == 17 then {
let value = rlp_decode_uint64(field);
let count = value / sizeof(gas_per_blob_value);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if (count <= profile.blob_schedule.max) & (value == sizeof(gas_per_blob_value) * count) then {
decoded.blob_gas_used = sizeof(gas_per_blob_value) * count;
decoded.have_blob_gas = true
} else {
fatal_error(RlpDecode)
}
} else if field_index == 18 then {
let value = rlp_decode_uint64(field);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let limit = profile.excess_blob_gas_limit;
if value <= limit then {
decoded.excess_blob_gas = value;
decoded.have_excess_blob_gas = true
} else {
fatal_error(RlpDecode)
}
};
let next_field = next_parent_header_field(field_index);
decode_parent_header_fields(next, next_field, decoded)
}function fatal_error(_reason) = exit(())Advances the parent-header field cursor, saturating at its unused-field sentinel.
function next_parent_header_field(index : parent_header_field_cursor) -> parent_header_field_cursor =
if index < 19 then {
index + 1
} else {
19
}function rlp_cursor_advance(cursor, consumed) =
slice_suffix(cursor, consumed)function rlp_decode_item(cursor) = {
if cursor.len == 0 then {
fatal_error(RlpDecode)
};
let (is_list, content_off, content_len_value) = rlp_ref_hdr(cursor);
let (content_len as 'content_len) = content_len_value;
if cursor.len < content_off then {
fatal_error(RlpDecode)
};
if cursor.len - content_off < content_len then {
fatal_error(RlpDecode)
};
let (full_len as 'full_len) = content_off + content_len;
if (0 < full_len) & (full_len <= cursor.len) then {
let field_source = sub_slice(cursor, 0, full_len);
let field : RlpFieldRef('source_off, 'full_len, 'content_len) = struct {
source = field_source,
is_list = is_list,
content_len = content_len,
};
field
} else {
fatal_error(RlpDecode)
}
}Decodes a canonical unsigned integer field into a word; throws otherwise.
function rlp_decode_u256 forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (f :
RlpFieldRef('source_off, 'source_len, 'content_len)) -> (
word
) =
let canonical = rlp_item_uint_canonical(f) in
if canonical then {
rlp_decode_word(f)
} else {
fatal_error(RlpDecode)
}Decodes a canonical unsigned integer into the uint64 wire domain used by EIP-2681 account nonces and EIP-4844 excess blob gas.
function rlp_decode_uint64 forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (f :
RlpFieldRef('source_off, 'source_len, 'content_len)) -> (
ssz_uint
) = {
let canonical = rlp_item_uint_canonical(f);
let invalid_encoding = not_bool(canonical);
if invalid_encoding | (RLP_UINT64_LENGTH_LIMIT < f.content_len) then {
fatal_error(RlpDecode)
};
let content_length = f.content_len;
let content = rlp_item_content(f);
/* The semantic check above establishes content_length <= 8. Modulo nine
* only exposes that already-proven bound to Sail's dependent type system. */
let width : range(0, 8) = tmod_nat(content_length, 9);
rlp_uint64_width(content, width)
}Decodes a string field of at most 32 bytes into a word.
function rlp_decode_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (f :
RlpFieldRef('source_off, 'source_len, 'content_len)) -> (
word
) = {
let n = f.content_len;
if f.is_list | (RLP_WORD_LENGTH_LIMIT < n) then {
fatal_error(RlpDecode)
} else {
slice_load_n(f.source, f.source.len - n, n)
}
}Serializes an EVM word as a 32-byte big-endian digest.
function word_to_hash(value : word) -> hash = {
let zero_bytes = vector_init(32, 0x00);
var result : hash = B256(zero_bytes);
result[0] = get_slice_int(8, value, 248);
result[1] = get_slice_int(8, value, 240);
result[2] = get_slice_int(8, value, 232);
result[3] = get_slice_int(8, value, 224);
result[4] = get_slice_int(8, value, 216);
result[5] = get_slice_int(8, value, 208);
result[6] = get_slice_int(8, value, 200);
result[7] = get_slice_int(8, value, 192);
result[8] = get_slice_int(8, value, 184);
result[9] = get_slice_int(8, value, 176);
result[10] = get_slice_int(8, value, 168);
result[11] = get_slice_int(8, value, 160);
result[12] = get_slice_int(8, value, 152);
result[13] = get_slice_int(8, value, 144);
result[14] = get_slice_int(8, value, 136);
result[15] = get_slice_int(8, value, 128);
result[16] = get_slice_int(8, value, 120);
result[17] = get_slice_int(8, value, 112);
result[18] = get_slice_int(8, value, 104);
result[19] = get_slice_int(8, value, 96);
result[20] = get_slice_int(8, value, 88);
result[21] = get_slice_int(8, value, 80);
result[22] = get_slice_int(8, value, 72);
result[23] = get_slice_int(8, value, 64);
result[24] = get_slice_int(8, value, 56);
result[25] = get_slice_int(8, value, 48);
result[26] = get_slice_int(8, value, 40);
result[27] = get_slice_int(8, value, 32);
result[28] = get_slice_int(8, value, 24);
result[29] = get_slice_int(8, value, 16);
result[30] = get_slice_int(8, value, 8);
result[31] = get_slice_int(8, value, 0);
result
}The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEThe reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}The parent-header fields required to initialize execution context.
struct ParentHeaderFields = {
parent_hash : hash,
state_root : hash,
base_fee : word,
blob_gas_used : blob_gas_used,
excess_blob_gas : excess_blob_gas,
have_parent : bool,
have_state : bool,
have_base_fee : bool,
have_blob_gas : bool,
have_excess_blob_gas : bool,
}A one-pass RLP cursor is the unconsumed suffix of its source. Decoding yields a field whose length witnesses a valid advance; the caller owns the corresponding cursor transition.
type RlpCursor('source_off : Int, 'source_len : Int),
source_valid_range('source_off, 'source_len) =
StatelessInputSliceFields('source_off, 'source_len)Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)Blob gas charged per blob, 2^17 (EIP-4844).
type gas_per_blob_value : Int = 2 ^ 17A bounded parent-header field position, including the unused-field sentinel.
type parent_header_field_cursor = range(0, 19)Common bound for relative source coordinates used by generic cursor operations. Nominal slices retain their region-specific invariant.
type source_valid_range('off : Int, 'len : Int) -> Bool =
0 <= 'off & 0 <= 'len & 'off + 'len <= default_host_region_boundtype WitnessHeaderIndex¶
The running authentication and execution context of the witness header chain.
struct WitnessHeaderIndex = {
cursor : WitnessHeaderListCursor,
previous_hash : hash,
valid : bool,
parent_state_root : hash,
parent_base_fee_per_gas : word,
parent_blob_gas_used : blob_gas_used,
parent_excess_blob_gas : excess_blob_gas,
parent_fields_valid : bool,
}A sequential cursor over witnessed parent headers.
type WitnessHeaderListCursor = BoundedSszListCursor(witness_header_count_bound)Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)The common digest type used by trie, code, and block hashes.
type hash = b256The EVM 256-bit machine word (YP §9.1). A transparent range keeps the mathematical subtype relation visible: narrower non-negative ranges can be passed as words without a model-level conversion.
type word = range(0, 2 ^ 256 - 1)function index_witness_header_cursor¶
Authenticates and indexes each remaining ancestor header in chain order.
function index_witness_header_cursor(state : WitnessHeaderIndex) -> WitnessHeaderIndex = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(state.cursor);
if cursor_empty then {
state
} else {
let index = state.cursor.index;
let (header, next) = ssz_list_pop(state.cursor);
let is_last = next.index == state.cursor.items.count;
var result = state;
result.cursor = next;
if (index != 0) | is_last then {
let fields = rlp_node_cursor(header);
let decoded = decode_parent_header_fields(fields, 0, EMPTY_PARENT_HEADER_FIELDS);
if index != 0 then {
if not_bool(decoded.have_parent) then {
result.valid = false
} else if decoded.parent_hash != state.previous_hash then {
result.valid = false
}
};
if is_last then {
result.parent_state_root = decoded.state_root;
result.parent_base_fee_per_gas = decoded.base_fee;
result.parent_blob_gas_used = decoded.blob_gas_used;
result.parent_excess_blob_gas = decoded.excess_blob_gas;
result.parent_fields_valid = decoded.have_state;
if profile.fork >= Cancun then {
if not_bool(decoded.have_base_fee) then {
result.parent_fields_valid = false
};
if decoded.have_blob_gas != decoded.have_excess_blob_gas then {
result.parent_fields_valid = false
}
}
}
};
let current_hash = keccak256(header);
result.previous_hash = current_hash;
let header_count = state.cursor.items.count;
let next_index = next.index;
let distance : range(0, witness_header_count_bound) =
if next_index <= header_count then header_count - next_index else fatal_error(WitnessDeficient);
if distance < 256 then {
let ancestor : ancestor_index = distance;
ancestor_hash_write(ancestor, current_hash)
};
index_witness_header_cursor(result)
}
}Records an ancestor block hash while the witness header chain is
indexed; index d holds the ancestor at distance d + 1.
val ancestor_hash_write = impure { c: "ancestor_hash_write" } : (ancestor_index, hash) -> unitExtracts the execution-relevant fields while walking one parent header.
function decode_parent_header_fields forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
field_index : parent_header_field_cursor,
fields : ParentHeaderFields,
) -> (
ParentHeaderFields
) =
if cursor.len == 0 then {
fields
} else {
let field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, field.source.len);
var decoded = fields;
if field_index == 0 then {
let parent_hash_word = rlp_decode_word(field);
decoded.parent_hash = word_to_hash(parent_hash_word);
decoded.have_parent = true
} else if field_index == 3 then {
let state_root_word = rlp_decode_word(field);
decoded.state_root = word_to_hash(state_root_word);
decoded.have_state = true
} else if field_index == 15 then {
decoded.base_fee = rlp_decode_u256(field);
decoded.have_base_fee = true
} else if field_index == 17 then {
let value = rlp_decode_uint64(field);
let count = value / sizeof(gas_per_blob_value);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if (count <= profile.blob_schedule.max) & (value == sizeof(gas_per_blob_value) * count) then {
decoded.blob_gas_used = sizeof(gas_per_blob_value) * count;
decoded.have_blob_gas = true
} else {
fatal_error(RlpDecode)
}
} else if field_index == 18 then {
let value = rlp_decode_uint64(field);
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let limit = profile.excess_blob_gas_limit;
if value <= limit then {
decoded.excess_blob_gas = value;
decoded.have_excess_blob_gas = true
} else {
fatal_error(RlpDecode)
}
};
let next_field = next_parent_header_field(field_index);
decode_parent_header_fields(next, next_field, decoded)
}function fatal_error(_reason) = exit(())Authenticates and indexes each remaining ancestor header in chain order.
function index_witness_header_cursor(state : WitnessHeaderIndex) -> WitnessHeaderIndex = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(state.cursor);
if cursor_empty then {
state
} else {
let index = state.cursor.index;
let (header, next) = ssz_list_pop(state.cursor);
let is_last = next.index == state.cursor.items.count;
var result = state;
result.cursor = next;
if (index != 0) | is_last then {
let fields = rlp_node_cursor(header);
let decoded = decode_parent_header_fields(fields, 0, EMPTY_PARENT_HEADER_FIELDS);
if index != 0 then {
if not_bool(decoded.have_parent) then {
result.valid = false
} else if decoded.parent_hash != state.previous_hash then {
result.valid = false
}
};
if is_last then {
result.parent_state_root = decoded.state_root;
result.parent_base_fee_per_gas = decoded.base_fee;
result.parent_blob_gas_used = decoded.blob_gas_used;
result.parent_excess_blob_gas = decoded.excess_blob_gas;
result.parent_fields_valid = decoded.have_state;
if profile.fork >= Cancun then {
if not_bool(decoded.have_base_fee) then {
result.parent_fields_valid = false
};
if decoded.have_blob_gas != decoded.have_excess_blob_gas then {
result.parent_fields_valid = false
}
}
}
};
let current_hash = keccak256(header);
result.previous_hash = current_hash;
let header_count = state.cursor.items.count;
let next_index = next.index;
let distance : range(0, witness_header_count_bound) =
if next_index <= header_count then header_count - next_index else fatal_error(WitnessDeficient);
if distance < 256 then {
let ancestor : ancestor_index = distance;
ancestor_hash_write(ancestor, current_hash)
};
index_witness_header_cursor(result)
}
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))A cursor over the children of a byte sequence that must be exactly one RLP list (e.g. a trie node).
function rlp_node_cursor(node : StatelessInputSlice) -> (
{'source_off 'source_len,
source_valid_range('source_off, 'source_len).
RlpCursor('source_off, 'source_len)}
) = {
if node.len == 0 then {
fatal_error(RlpDecode)
} else {
let item = rlp_single_ref(node);
rlp_decode_list(item)
}
}Whether the cursor has consumed all elements.
function ssz_list_cursor_empty forall ('maximum : Int), source_valid_length('maximum). (cursor :
BoundedSszListCursor('maximum)) -> (
bool
) =
cursor.items.count <= cursor.indexThe next element's span, and the advanced cursor.
function ssz_list_pop forall ('maximum : Int), source_valid_length('maximum) & 'maximum <= 2 ^ 30 - 1. (cursor :
BoundedSszListCursor('maximum)) -> (
(StatelessInputSlice, BoundedSszListCursor('maximum))
) = {
let count = cursor.items.count;
let index = cursor.index;
let next_index : range(0, 'maximum) =
if index < count then index + 1 else fatal_error(InvalidConfig);
let items = cursor.items;
let bytes = items.bytes;
let nat = bytes.len;
let next =
if next_index < items.count then {
let table_position = ssz_offset_table_position(next_index);
let next_offset = ssz_u32_in_slice(bytes, table_position);
ssz_offset_to_source_pointer(next_offset)
} else {
nat
};
let current_value = cursor.current;
let next_value = next;
if (current_value <= next_value) & (next_value <= nat) then {
let item_length = next_value - current_value;
if (items.max_item_length != 0) & (items.max_item_length < item_length) then {
fatal_error(InvalidConfig)
};
let item = sub_slice(bytes, current_value, item_length);
(item, struct { items = items, index = next_index, current = next })
} else {
fatal_error(InvalidConfig)
}
}EIP-1153/4844; precompiles 0x01-0x0a.
let Cancun : int(first_blob_fork_value) = sizeof(first_blob_fork_value)let EMPTY_PARENT_HEADER_FIELDS : ParentHeaderFields = struct {
parent_hash = ZERO_HASH,
state_root = ZERO_HASH,
base_fee = ZERO_WORD,
blob_gas_used = 0,
excess_blob_gas = 0,
have_parent = false,
have_state = false,
have_base_fee = false,
have_blob_gas = false,
have_excess_blob_gas = false,
}The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEThe reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}The running authentication and execution context of the witness header chain.
struct WitnessHeaderIndex = {
cursor : WitnessHeaderListCursor,
previous_hash : hash,
valid : bool,
parent_state_root : hash,
parent_base_fee_per_gas : word,
parent_blob_gas_used : blob_gas_used,
parent_excess_blob_gas : excess_blob_gas,
parent_fields_valid : bool,
}An index into the 256 most recent ancestor block hashes.
type ancestor_index = range(0, 255)Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)Maximum witness headers in the stateless-input SSZ list.
type witness_header_count_bound : Int = 2 ^ 8function index_witness_headers¶
Parses the witness header chain once. Each header is hashed into the
distance-indexed ancestor table (the BLOCKHASH source), adjacent
parent hashes are checked (a break is HeaderChainBroken), and the
newest header's execution context — parent state root, base fee, blob
gas — is decoded while its fields are consumed head-to-tail.
function index_witness_headers(headers : WitnessHeaderListRef) -> WitnessContext = {
let cursor = ssz_list_cursor(headers);
let initial : WitnessHeaderIndex = struct {
cursor = cursor,
previous_hash = ZERO_HASH,
valid = headers.count != 0,
parent_state_root = ZERO_HASH,
parent_base_fee_per_gas = ZERO_WORD,
parent_blob_gas_used = 0,
parent_excess_blob_gas = 0,
parent_fields_valid = false,
};
let indexed = index_witness_header_cursor(initial);
k_n_headers = headers.count;
let invalid = not_bool(indexed.valid);
if invalid then {
fatal_error(WitnessDeficient)
};
let parent_fields_invalid = not_bool(indexed.parent_fields_valid);
if parent_fields_invalid then {
fatal_error(RlpDecode)
};
struct {
parent_hash = indexed.previous_hash,
parent_state_root = indexed.parent_state_root,
parent_base_fee_per_gas = indexed.parent_base_fee_per_gas,
parent_blob_gas_used = indexed.parent_blob_gas_used,
parent_excess_blob_gas = indexed.parent_excess_blob_gas,
}
}function fatal_error(_reason) = exit(())Authenticates and indexes each remaining ancestor header in chain order.
function index_witness_header_cursor(state : WitnessHeaderIndex) -> WitnessHeaderIndex = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
let cursor_empty = ssz_list_cursor_empty(state.cursor);
if cursor_empty then {
state
} else {
let index = state.cursor.index;
let (header, next) = ssz_list_pop(state.cursor);
let is_last = next.index == state.cursor.items.count;
var result = state;
result.cursor = next;
if (index != 0) | is_last then {
let fields = rlp_node_cursor(header);
let decoded = decode_parent_header_fields(fields, 0, EMPTY_PARENT_HEADER_FIELDS);
if index != 0 then {
if not_bool(decoded.have_parent) then {
result.valid = false
} else if decoded.parent_hash != state.previous_hash then {
result.valid = false
}
};
if is_last then {
result.parent_state_root = decoded.state_root;
result.parent_base_fee_per_gas = decoded.base_fee;
result.parent_blob_gas_used = decoded.blob_gas_used;
result.parent_excess_blob_gas = decoded.excess_blob_gas;
result.parent_fields_valid = decoded.have_state;
if profile.fork >= Cancun then {
if not_bool(decoded.have_base_fee) then {
result.parent_fields_valid = false
};
if decoded.have_blob_gas != decoded.have_excess_blob_gas then {
result.parent_fields_valid = false
}
}
}
};
let current_hash = keccak256(header);
result.previous_hash = current_hash;
let header_count = state.cursor.items.count;
let next_index = next.index;
let distance : range(0, witness_header_count_bound) =
if next_index <= header_count then header_count - next_index else fatal_error(WitnessDeficient);
if distance < 256 then {
let ancestor : ancestor_index = distance;
ancestor_hash_write(ancestor, current_hash)
};
index_witness_header_cursor(result)
}
}val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))A cursor at the first element of a variable-element SSZ list.
function ssz_list_cursor forall ('maximum : Int), source_valid_length('maximum). (items : BoundedSszListRef('maximum)) -> (
BoundedSszListCursor('maximum)
) = {
let bytes = items.bytes;
let current =
if items.count != 0 then {
let first_offset = ssz_u32(bytes, 0);
ssz_offset_to_source_pointer(first_offset)
} else {
bytes.len
};
struct { items = items, index = 0, current = current }
}let ZERO_HASH : hash = hash_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)let ZERO_WORD : word = word_from_bits(0x0000000000000000000000000000000000000000000000000000000000000000)The number of witnessed ancestor headers (k_blockhash range).
register k_n_headers : ancestor_hash_count = 0The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}Authenticated facts about the parent block that validation rules compare the payload header against.
struct WitnessContext = {
parent_hash : hash,
parent_state_root : hash,
parent_base_fee_per_gas : word,
parent_blob_gas_used : blob_gas_used,
parent_excess_blob_gas : excess_blob_gas,
}The running authentication and execution context of the witness header chain.
struct WitnessHeaderIndex = {
cursor : WitnessHeaderListCursor,
previous_hash : hash,
valid : bool,
parent_state_root : hash,
parent_base_fee_per_gas : word,
parent_blob_gas_used : blob_gas_used,
parent_excess_blob_gas : excess_blob_gas,
parent_fields_valid : bool,
}A schema-bounded source reference to witnessed parent headers.
type WitnessHeaderListRef = BoundedSszListRef(witness_header_count_bound)function decode_payload_blob_gas_used¶
Decodes the execution-payload header fields from their fixed SSZ offsets.
function decode_payload_blob_gas_used(payload : StatelessInputSlice, profile : ProtocolProfile) -> blob_gas_used = {
let value = decode_ssz_uint(payload, PL_BLOB_GAS_USED);
/* EIP-4844 makes this field GAS_PER_BLOB times the number of blobs.
* The decoded profile supplies the active schedule maximum. */
let count = value / sizeof(gas_per_blob_value);
if (count <= profile.blob_schedule.max) & (value == sizeof(gas_per_blob_value) * count) then {
sizeof(gas_per_blob_value) * count
} else {
fatal_error(InvalidBlobGasUsed)
}
}function blob_schedule(target, maximum, denominator) =
struct { target = target, max = maximum, base_fee_update_fraction = denominator }function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function fatal_error(_reason) = exit(())let PL_BLOB_GAS_USED : int(512) = 512The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A protocol profile with its parameter tuple packed existentially; unpacking recovers the admitted combination's equations.
type ProtocolProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'transaction_total_gas_limit
'transaction_regular_gas_limit
'transaction_blob_limit
'refund_divisor,
protocol_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
).
ProtocolProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}Blob gas charged per blob, 2^17 (EIP-4844).
type gas_per_blob_value : Int = 2 ^ 17function decode_payload_excess_blob_gas¶
Narrows the SSZ uint64 excess field to the supported-fork
reachable-chain invariant. The wider wire value remains explicit in
excess_blob_gas_wire_bound; this check relies on an authenticated,
previously valid parent chain, not on SSZ alone.
function decode_payload_excess_blob_gas(payload : StatelessInputSlice, profile : ProtocolProfile) -> excess_blob_gas = {
let value = decode_ssz_uint(payload, PL_EXCESS_BLOB_GAS);
let limit = profile.excess_blob_gas_limit;
if value <= limit then {
value
} else {
fatal_error(InvalidExcessBlobGas)
}
}function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function fatal_error(_reason) = exit(())let PL_EXCESS_BLOB_GAS : int(520) = 520The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A protocol profile with its parameter tuple packed existentially; unpacking recovers the admitted combination's equations.
type ProtocolProfile = {
'fork
'target
'maximum
'denominator
'code_limit
'initcode_limit
'transaction_total_gas_limit
'transaction_regular_gas_limit
'transaction_blob_limit
'refund_divisor,
protocol_profile_parameters(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
).
ProtocolProfileFields(
'fork,
'target,
'maximum,
'denominator,
'code_limit,
'initcode_limit,
'transaction_total_gas_limit,
'transaction_regular_gas_limit,
'transaction_blob_limit,
'refund_divisor,
)
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)function decode_block_header_ssz¶
Decodes the execution-payload header fields from their fixed SSZ offsets.
function decode_block_header_ssz(input_ref : StatelessInputRef) -> BlockHeader = {
let payload = input_ref.execution_payload;
let gas_limit_value = decode_ssz_uint(payload, PL_GAS_LIMIT);
let gas_used_value = decode_ssz_uint(payload, PL_GAS_USED);
let prev_randao_hash = ssz_bytes32(payload, PL_PREV_RANDAO);
let prev_randao = hash_to_word(prev_randao_hash);
let number = decode_ssz_uint(payload, PL_BLOCK_NUMBER);
let timestamp = decode_ssz_uint(payload, PL_TIMESTAMP);
let base_fee = ssz_u256(payload, PL_BASE_FEE);
let blob_gas_used = decode_payload_blob_gas_used(payload, input_ref.protocol);
let excess_blob_gas = decode_payload_excess_blob_gas(payload, input_ref.protocol);
let state_root = ssz_bytes32(payload, PL_STATE_ROOT);
let receipts_root = ssz_bytes32(payload, PL_RECEIPTS_ROOT);
let logs_bloom = sub_slice(payload, PL_LOGS_BLOOM, 256);
let fee_recipient = ssz_addr(payload, PL_FEE_RECIPIENT);
let parent_hash = ssz_bytes32(payload, 0);
let parent_beacon_block_root = ssz_bytes32(input_ref.new_payload_request, NPR_BEACON_ROOT);
let slot_number = decode_ssz_uint(payload, PL_SLOT_NUMBER);
struct {
number = number,
timestamp = timestamp,
gas_limit = gas_limit_value,
gas_used = gas_used_value,
prev_randao = prev_randao,
base_fee = base_fee,
blob_gas_used = blob_gas_used,
excess_blob_gas = excess_blob_gas,
state_root = state_root,
receipts_root = receipts_root,
logs_bloom = logs_bloom,
fee_recipient = fee_recipient,
parent_hash = parent_hash,
parent_beacon_block_root = parent_beacon_block_root,
slot_number = slot_number,
extra_data = input_ref.extra_data,
}
}Decodes the execution-payload header fields from their fixed SSZ offsets.
function decode_payload_blob_gas_used(payload : StatelessInputSlice, profile : ProtocolProfile) -> blob_gas_used = {
let value = decode_ssz_uint(payload, PL_BLOB_GAS_USED);
/* EIP-4844 makes this field GAS_PER_BLOB times the number of blobs.
* The decoded profile supplies the active schedule maximum. */
let count = value / sizeof(gas_per_blob_value);
if (count <= profile.blob_schedule.max) & (value == sizeof(gas_per_blob_value) * count) then {
sizeof(gas_per_blob_value) * count
} else {
fatal_error(InvalidBlobGasUsed)
}
}Narrows the SSZ uint64 excess field to the supported-fork
reachable-chain invariant. The wider wire value remains explicit in
excess_blob_gas_wire_bound; this check relies on an authenticated,
previously valid parent chain, not on SSZ alone.
function decode_payload_excess_blob_gas(payload : StatelessInputSlice, profile : ProtocolProfile) -> excess_blob_gas = {
let value = decode_ssz_uint(payload, PL_EXCESS_BLOB_GAS);
let limit = profile.excess_blob_gas_limit;
if value <= limit then {
value
} else {
fatal_error(InvalidExcessBlobGas)
}
}function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}Interprets a digest as the corresponding big-endian EVM word.
function hash_to_word(bytes : hash) -> word =
unsigned(
bytes[0]
@ bytes[1]
@ bytes[2]
@ bytes[3]
@ bytes[4]
@ bytes[5]
@ bytes[6]
@ bytes[7]
@ bytes[8]
@ bytes[9]
@ bytes[10]
@ bytes[11]
@ bytes[12]
@ bytes[13]
@ bytes[14]
@ bytes[15]
@ bytes[16]
@ bytes[17]
@ bytes[18]
@ bytes[19]
@ bytes[20]
@ bytes[21]
@ bytes[22]
@ bytes[23]
@ bytes[24]
@ bytes[25]
@ bytes[26]
@ bytes[27]
@ bytes[28]
@ bytes[29]
@ bytes[30]
@ bytes[31],
)function ssz_addr(input, offset) = {
let value = slice_load_n(input, offset, ADDRESS_BYTE_LENGTH);
word_to_address(value)
}function ssz_bytes32(input, offset) = {
let value = slice_load(input, offset);
word_to_hash(value)
}Decodes a 32-byte little-endian SSZ integer into an EVM word.
function ssz_u256(input, offset) = {
var result : word = WORD_ZERO;
foreach (k from 0 to 31) {
let source_index = ssz_u256_index(k);
let source_offset = ssz_field_offset(offset, source_index);
let source_byte = slice_byte(input, source_offset);
let byte_value = unsigned(source_byte);
let shifted_result = word_mul(result, 256);
result = word_add(shifted_result, byte_value)
};
result
}let NPR_BEACON_ROOT : int(8) = 8let PL_BASE_FEE : int(440) = 440let PL_BLOCK_NUMBER : int(404) = 404let PL_FEE_RECIPIENT : int(32) = 32let PL_GAS_LIMIT : int(412) = 412let PL_GAS_USED : int(420) = 420let PL_LOGS_BLOOM : int(116) = 116let PL_PREV_RANDAO : int(372) = 372let PL_RECEIPTS_ROOT : int(84) = 84let PL_SLOT_NUMBER : int(532) = 532let PL_STATE_ROOT : int(52) = 52let PL_TIMESTAMP : int(428) = 428The execution-payload header fields the model reads and validates
(YP §4.4). Scalar wire bounds come from the consensus/Amsterdam SSZ
ExecutionPayload schema. gas_used <= gas_limit and the active
blob-schedule rules are execution-protocol constraints checked when the
payload is admitted. extra_data retains the schema's
ByteList[MAX_EXTRA_DATA_BYTES] bound while staying source-backed; it is
RLP-encoded whole for the header hash and never inspected. The fixed
logs_bloom commitment likewise stays source-backed until a semantic
consumer explicitly decodes it.
struct BlockHeader = {
number : block_number,
timestamp : block_timestamp,
gas_limit : block_gas_limit,
gas_used : block_gas,
prev_randao : word,
base_fee : word,
/* EIP-4844: a multiple of GAS_PER_BLOB within the active schedule. */
blob_gas_used : blob_gas_used,
/* EIP-4844 uint64 on the wire; narrowed to the documented reachable-chain
invariant at the authenticated input boundary. */
excess_blob_gas : excess_blob_gas,
state_root : hash,
receipts_root : hash,
logs_bloom : LogsBloomRef,
fee_recipient : address,
parent_hash : hash,
parent_beacon_block_root : hash,
/* uint64 (EIP-7843 and the Amsterdam stateless SSZ schema). */
slot_number : slot_number,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
}Every variable region of the input, resolved once before decoding. Consumers receive explicit source spans instead of re-reading nested SSZ offset tables.
struct StatelessInputRef = {
protocol : ProtocolProfile,
new_payload_request : StatelessInputSlice,
execution_payload : StatelessInputSliceAtLeast(540),
versioned_hashes : StatelessInputSlice,
deposits : StatelessInputSlice,
withdrawal_requests : StatelessInputSlice,
consolidation_requests : StatelessInputSlice,
builder_deposit_requests : StatelessInputSlice,
builder_exit_requests : StatelessInputSlice,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
transactions : TransactionListRef,
withdrawals : WithdrawalListRef,
block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound),
witness_state : WitnessNodeListRef,
witness_codes : WitnessCodeListRef,
witness_headers : WitnessHeaderListRef,
chain_config : StatelessInputSlice,
public_keys : StatelessInputSlice,
}Blob gas used by one supported block. The existential count retains that
every value is exactly a multiple of GAS_PER_BLOB; profile-indexed
decoding applies the selected schedule's tighter range before values enter
this heterogeneous header domain.
type blob_gas_used = {
'count,
0 <= 'count
& 'count <= bpo2_blob_max_count.
int(gas_per_blob_value * 'count)
}The accumulated excess blob gas carried between headers (EIP-4844).
type excess_blob_gas = range(0, excess_blob_gas_bound)A beacon-chain slot number. Provenance: EIP-7843 and Amsterdam's
stateless SSZ payload declare this field as uint64.
type slot_number = range(0, ssz_uint_bound)function decode_withdrawal¶
Decodes one fixed-layout SSZ withdrawal element.
function decode_withdrawal(withdrawal : StatelessInputSlice) -> Withdrawal = {
struct {
index = decode_ssz_uint(withdrawal, WD_INDEX),
validator_index = decode_ssz_uint(withdrawal, WD_VALIDATOR_INDEX),
address = ssz_addr(withdrawal, WD_ADDRESS),
amount = decode_ssz_uint(withdrawal, WD_AMOUNT),
}
}function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function ssz_addr(input, offset) = {
let value = slice_load_n(input, offset, ADDRESS_BYTE_LENGTH);
word_to_address(value)
}let WD_ADDRESS : int(16) = 16let WD_AMOUNT : int(36) = 36let WD_INDEX : int(0) = 0let WD_VALIDATOR_INDEX : int(8) = 8A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An EIP-4895 beacon-chain withdrawal: index, validator, recipient, and amount in gwei.
struct Withdrawal = {
index : withdrawal_index,
validator_index : validator_index,
address : address,
amount : withdrawal_amount,
}A 20-byte account address (YP §4.1), in canonical protocol byte order.
type address = vector(20, inc, byte)An EIP-4895 validator index, encoded as SSZ uint64.
type validator_index = range(0, ssz_uint_bound)function decode_chain_config¶
Decodes the SszChainConfig at cc: the chain id (u64 at 0) and
the active Amsterdam SszForkConfig activation.
The activation point (optional block number / timestamp, List[u64,1]
each) must be reached by this payload: at least one bound set, none
exceeding the payload's — a future activation invalidates the block.
function decode_chain_config(
cc : StatelessInputSlice,
number : block_number,
timestamp : block_timestamp,
) -> (
ChainConfig
) = {
let cc_length = cc.len;
let header_length = CHAIN_CONFIG_HEADER_LENGTH;
let minimum_length = CHAIN_CONFIG_MIN_LENGTH;
if cc_length < header_length then {
fatal_error(InvalidConfig)
};
let f_offset = ssz_u32(cc, CC_ACTIVE_FORK_OFF);
if (f_offset != 12) | (cc_length < minimum_length) then {
fatal_error(InvalidConfig)
};
let f : int(12) = 12;
let activation_position = ssz_field_offset(f, FC_ACTIVATION_OFF);
let activation_offset = ssz_u32(cc, activation_position);
if activation_offset != 4 then {
fatal_error(InvalidConfig)
};
let activation_start : int(16) = 16;
let activation_fixed_end : int(24) = 24;
let a : int(16) = 16;
let block_number_position = ssz_field_offset(a, FA_BLOCK_NUMBER_OFF);
let block_number_offset = ssz_u32(cc, block_number_position);
let timestamp_position = ssz_field_offset(a, FA_TIMESTAMP_OFF);
let timestamp_offset = ssz_u32(cc, timestamp_position);
let block_number_start : stateless_input_pointer =
if block_number_offset <= cc_length - activation_start
then activation_start + block_number_offset
else fatal_error(InvalidConfig);
let timestamp_start : stateless_input_pointer =
if timestamp_offset <= cc_length - activation_start
then activation_start + timestamp_offset
else fatal_error(InvalidConfig);
if activation_fixed_end != block_number_start then {
fatal_error(InvalidConfig)
};
if timestamp_start < block_number_start then {
fatal_error(InvalidConfig)
};
if cc_length < timestamp_start then {
fatal_error(InvalidConfig)
};
let bn_length = timestamp_start - block_number_start;
let ts_length = cc_length - timestamp_start;
if ((bn_length != 0) & (bn_length != SSZ_UINT_BYTES)) | ((ts_length != 0) & (ts_length != SSZ_UINT_BYTES)) then {
fatal_error(InvalidConfig)
};
if (bn_length == 0) & (ts_length == 0) then {
fatal_error(InvalidConfig)
};
if bn_length == SSZ_UINT_BYTES then {
let activation_block : block_number = decode_ssz_uint(cc, block_number_start);
if number < activation_block then {
fatal_error(InvalidConfig)
}
};
if ts_length == SSZ_UINT_BYTES then {
let activation_timestamp : block_timestamp = decode_ssz_uint(cc, timestamp_start);
if timestamp < activation_timestamp then {
fatal_error(InvalidConfig)
}
};
struct { chain_id = decode_ssz_uint(cc, CC_CHAIN_ID) }
}function decode_ssz_uint(input, offset) = {
let byte0 = slice_byte(input, offset);
let offset1 = ssz_field_offset(offset, 1);
let byte1 = slice_byte(input, offset1);
let offset2 = ssz_field_offset(offset, 2);
let byte2 = slice_byte(input, offset2);
let offset3 = ssz_field_offset(offset, 3);
let byte3 = slice_byte(input, offset3);
let offset4 = ssz_field_offset(offset, 4);
let byte4 = slice_byte(input, offset4);
let offset5 = ssz_field_offset(offset, 5);
let byte5 = slice_byte(input, offset5);
let offset6 = ssz_field_offset(offset, 6);
let byte6 = slice_byte(input, offset6);
let offset7 = ssz_field_offset(offset, 7);
let byte7 = slice_byte(input, offset7);
unsigned(byte0)
+ unsigned(byte1)
* 2 ^ 8
+ unsigned(byte2)
* 2 ^ 16
+ unsigned(byte3)
* 2 ^ 24
+ unsigned(byte4)
* 2 ^ 32
+ unsigned(byte5)
* 2 ^ 40
+ unsigned(byte6)
* 2 ^ 48
+ unsigned(byte7)
* 2 ^ 56
}function fatal_error(_reason) = exit(())function ssz_field_offset(base, delta) = base + deltafunction ssz_u32(input, offset) = ssz_u32_at(input, offset)let CC_ACTIVE_FORK_OFF : int(8) = 8let CC_CHAIN_ID : int(0) = 0let CHAIN_CONFIG_HEADER_LENGTH : int(12) = 12let CHAIN_CONFIG_MIN_LENGTH : int(24) = 24let FA_BLOCK_NUMBER_OFF : int(0) = 0let FA_TIMESTAMP_OFF : int(4) = 4let FC_ACTIVATION_OFF : int(0) = 0let SSZ_UINT_BYTES : int(8) = 8The decoded chain configuration for the executing payload.
struct ChainConfig = {
chain_id : chain_identifier,
}The reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A stateless-input range with its coordinate and length packed existentially.
type StatelessInputSlice = {
'off 'len,
stateless_input_valid_range('off, 'len).
StatelessInputSliceFields('off, 'len)
}An execution block number. Provenance: the execution-payload SSZ schema
declares block_number: uint64. The execution rules do not impose a
tighter supported-fork bound.
type block_number = range(0, ssz_uint_bound)An execution block timestamp in seconds. Provenance: the
execution-payload SSZ schema declares timestamp: uint64; no tighter
supported-fork protocol bound is applied.
type block_timestamp = range(0, ssz_uint_bound)A coordinate in the immutable stateless-input envelope.
type stateless_input_pointer = range(0, stateless_input_region_bound)function decode_stateless_input¶
Decodes the semantic payload structure — header, chain config, body references — without touching an encoded transaction or withdrawal element, and installs the header and chain parameters in the kernel.
function decode_stateless_input(input_ref : StatelessInputRef) -> StatelessInput = {
let payload = input_ref.execution_payload;
let header = decode_block_header_ssz(input_ref);
let chain_config = decode_chain_config(input_ref.chain_config, header.number, header.timestamp);
k_set_header(header);
k_chain_id = chain_config.chain_id;
k_execution_profile = execution_profile_for(input_ref.protocol, header.gas_limit);
struct {
payload =
struct {
expected_block_hash = ssz_bytes32(input_ref.execution_payload, PL_BLOCK_HASH),
block =
struct {
header = header,
body =
struct {
transactions = input_ref.transactions,
withdrawals = input_ref.withdrawals,
block_access_list = input_ref.block_access_list,
},
},
},
chain_config = chain_config,
}
}Decodes the execution-payload header fields from their fixed SSZ offsets.
function decode_block_header_ssz(input_ref : StatelessInputRef) -> BlockHeader = {
let payload = input_ref.execution_payload;
let gas_limit_value = decode_ssz_uint(payload, PL_GAS_LIMIT);
let gas_used_value = decode_ssz_uint(payload, PL_GAS_USED);
let prev_randao_hash = ssz_bytes32(payload, PL_PREV_RANDAO);
let prev_randao = hash_to_word(prev_randao_hash);
let number = decode_ssz_uint(payload, PL_BLOCK_NUMBER);
let timestamp = decode_ssz_uint(payload, PL_TIMESTAMP);
let base_fee = ssz_u256(payload, PL_BASE_FEE);
let blob_gas_used = decode_payload_blob_gas_used(payload, input_ref.protocol);
let excess_blob_gas = decode_payload_excess_blob_gas(payload, input_ref.protocol);
let state_root = ssz_bytes32(payload, PL_STATE_ROOT);
let receipts_root = ssz_bytes32(payload, PL_RECEIPTS_ROOT);
let logs_bloom = sub_slice(payload, PL_LOGS_BLOOM, 256);
let fee_recipient = ssz_addr(payload, PL_FEE_RECIPIENT);
let parent_hash = ssz_bytes32(payload, 0);
let parent_beacon_block_root = ssz_bytes32(input_ref.new_payload_request, NPR_BEACON_ROOT);
let slot_number = decode_ssz_uint(payload, PL_SLOT_NUMBER);
struct {
number = number,
timestamp = timestamp,
gas_limit = gas_limit_value,
gas_used = gas_used_value,
prev_randao = prev_randao,
base_fee = base_fee,
blob_gas_used = blob_gas_used,
excess_blob_gas = excess_blob_gas,
state_root = state_root,
receipts_root = receipts_root,
logs_bloom = logs_bloom,
fee_recipient = fee_recipient,
parent_hash = parent_hash,
parent_beacon_block_root = parent_beacon_block_root,
slot_number = slot_number,
extra_data = input_ref.extra_data,
}
}Decodes the SszChainConfig at cc: the chain id (u64 at 0) and
the active Amsterdam SszForkConfig activation.
The activation point (optional block number / timestamp, List[u64,1]
each) must be reached by this payload: at least one bound set, none
exceeding the payload's — a future activation invalidates the block.
function decode_chain_config(
cc : StatelessInputSlice,
number : block_number,
timestamp : block_timestamp,
) -> (
ChainConfig
) = {
let cc_length = cc.len;
let header_length = CHAIN_CONFIG_HEADER_LENGTH;
let minimum_length = CHAIN_CONFIG_MIN_LENGTH;
if cc_length < header_length then {
fatal_error(InvalidConfig)
};
let f_offset = ssz_u32(cc, CC_ACTIVE_FORK_OFF);
if (f_offset != 12) | (cc_length < minimum_length) then {
fatal_error(InvalidConfig)
};
let f : int(12) = 12;
let activation_position = ssz_field_offset(f, FC_ACTIVATION_OFF);
let activation_offset = ssz_u32(cc, activation_position);
if activation_offset != 4 then {
fatal_error(InvalidConfig)
};
let activation_start : int(16) = 16;
let activation_fixed_end : int(24) = 24;
let a : int(16) = 16;
let block_number_position = ssz_field_offset(a, FA_BLOCK_NUMBER_OFF);
let block_number_offset = ssz_u32(cc, block_number_position);
let timestamp_position = ssz_field_offset(a, FA_TIMESTAMP_OFF);
let timestamp_offset = ssz_u32(cc, timestamp_position);
let block_number_start : stateless_input_pointer =
if block_number_offset <= cc_length - activation_start
then activation_start + block_number_offset
else fatal_error(InvalidConfig);
let timestamp_start : stateless_input_pointer =
if timestamp_offset <= cc_length - activation_start
then activation_start + timestamp_offset
else fatal_error(InvalidConfig);
if activation_fixed_end != block_number_start then {
fatal_error(InvalidConfig)
};
if timestamp_start < block_number_start then {
fatal_error(InvalidConfig)
};
if cc_length < timestamp_start then {
fatal_error(InvalidConfig)
};
let bn_length = timestamp_start - block_number_start;
let ts_length = cc_length - timestamp_start;
if ((bn_length != 0) & (bn_length != SSZ_UINT_BYTES)) | ((ts_length != 0) & (ts_length != SSZ_UINT_BYTES)) then {
fatal_error(InvalidConfig)
};
if (bn_length == 0) & (ts_length == 0) then {
fatal_error(InvalidConfig)
};
if bn_length == SSZ_UINT_BYTES then {
let activation_block : block_number = decode_ssz_uint(cc, block_number_start);
if number < activation_block then {
fatal_error(InvalidConfig)
}
};
if ts_length == SSZ_UINT_BYTES then {
let activation_timestamp : block_timestamp = decode_ssz_uint(cc, timestamp_start);
if timestamp < activation_timestamp then {
fatal_error(InvalidConfig)
}
};
struct { chain_id = decode_ssz_uint(cc, CC_CHAIN_ID) }
}function execution_profile_for(protocol, block_limit) =
struct { protocol = protocol, gas = gas_limits_for(protocol, block_limit) }Installs the block header.
function k_set_header(h : BlockHeader) -> unit = k_header = hfunction ssz_bytes32(input, offset) = {
let value = slice_load(input, offset);
word_to_hash(value)
}let PL_BLOCK_HASH : int(472) = 472The chain id (CHAINID, EIP-155 domains).
register k_chain_id : chain_identifier = 1The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEThe guest's decoded input: the execution payload and the chain configuration it executes under.
struct StatelessInput = {
payload : ExecutionPayload,
chain_config : ChainConfig,
}Every variable region of the input, resolved once before decoding. Consumers receive explicit source spans instead of re-reading nested SSZ offset tables.
struct StatelessInputRef = {
protocol : ProtocolProfile,
new_payload_request : StatelessInputSlice,
execution_payload : StatelessInputSliceAtLeast(540),
versioned_hashes : StatelessInputSlice,
deposits : StatelessInputSlice,
withdrawal_requests : StatelessInputSlice,
consolidation_requests : StatelessInputSlice,
builder_deposit_requests : StatelessInputSlice,
builder_exit_requests : StatelessInputSlice,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
transactions : TransactionListRef,
withdrawals : WithdrawalListRef,
block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound),
witness_state : WitnessNodeListRef,
witness_codes : WitnessCodeListRef,
witness_headers : WitnessHeaderListRef,
chain_config : StatelessInputSlice,
public_keys : StatelessInputSlice,
}function index_execution_witness¶
Indexes the source-backed witness (nodes, codes, headers) and installs its authenticated parent-state anchor; no witness list is materialized.
function index_execution_witness(input_ref : StatelessInputRef) -> WitnessContext = {
nodedb_reset();
index_witness_nodes(input_ref.witness_state);
index_witness_codes(input_ref.witness_codes);
let witness = index_witness_headers(input_ref.witness_headers);
k_parent_state_root = witness.parent_state_root;
witness
}Indexes every witness code blob into the content-addressed code store.
function index_witness_codes(codes : WitnessCodeListRef) -> unit = {
let cursor = ssz_list_cursor(codes);
index_witness_codes_cursor(cursor)
}Parses the witness header chain once. Each header is hashed into the
distance-indexed ancestor table (the BLOCKHASH source), adjacent
parent hashes are checked (a break is HeaderChainBroken), and the
newest header's execution context — parent state root, base fee, blob
gas — is decoded while its fields are consumed head-to-tail.
function index_witness_headers(headers : WitnessHeaderListRef) -> WitnessContext = {
let cursor = ssz_list_cursor(headers);
let initial : WitnessHeaderIndex = struct {
cursor = cursor,
previous_hash = ZERO_HASH,
valid = headers.count != 0,
parent_state_root = ZERO_HASH,
parent_base_fee_per_gas = ZERO_WORD,
parent_blob_gas_used = 0,
parent_excess_blob_gas = 0,
parent_fields_valid = false,
};
let indexed = index_witness_header_cursor(initial);
k_n_headers = headers.count;
let invalid = not_bool(indexed.valid);
if invalid then {
fatal_error(WitnessDeficient)
};
let parent_fields_invalid = not_bool(indexed.parent_fields_valid);
if parent_fields_invalid then {
fatal_error(RlpDecode)
};
struct {
parent_hash = indexed.previous_hash,
parent_state_root = indexed.parent_state_root,
parent_base_fee_per_gas = indexed.parent_base_fee_per_gas,
parent_blob_gas_used = indexed.parent_blob_gas_used,
parent_excess_blob_gas = indexed.parent_excess_blob_gas,
}
}Indexes every witness trie node into the node-db, keyed by its KECCAK-256 digest, directly from the SSZ list reference.
function index_witness_nodes(nodes : WitnessNodeListRef) -> unit = {
let cursor = ssz_list_cursor(nodes);
index_witness_nodes_cursor(cursor)
}Empties the node index (start of witness indexing).
val nodedb_reset = impure { c: "nodedb_reset" } : unit -> unitThe authenticated parent state root, the anchor of every stateless read.
register k_parent_state_root : hash = ZERO_HASHEvery variable region of the input, resolved once before decoding. Consumers receive explicit source spans instead of re-reading nested SSZ offset tables.
struct StatelessInputRef = {
protocol : ProtocolProfile,
new_payload_request : StatelessInputSlice,
execution_payload : StatelessInputSliceAtLeast(540),
versioned_hashes : StatelessInputSlice,
deposits : StatelessInputSlice,
withdrawal_requests : StatelessInputSlice,
consolidation_requests : StatelessInputSlice,
builder_deposit_requests : StatelessInputSlice,
builder_exit_requests : StatelessInputSlice,
extra_data : StatelessInputSliceAtMost(extra_data_length_bound),
transactions : TransactionListRef,
withdrawals : WithdrawalListRef,
block_access_list : StatelessInputSliceAtMost(block_access_list_length_bound),
witness_state : WitnessNodeListRef,
witness_codes : WitnessCodeListRef,
witness_headers : WitnessHeaderListRef,
chain_config : StatelessInputSlice,
public_keys : StatelessInputSlice,
}Authenticated facts about the parent block that validation rules compare the payload header against.
struct WitnessContext = {
parent_hash : hash,
parent_state_root : hash,
parent_base_fee_per_gas : word,
parent_blob_gas_used : blob_gas_used,
parent_excess_blob_gas : excess_blob_gas,
}function decode_transaction¶
function decode_transaction(transaction, public_key) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if transaction.len <= sizeof(transaction_length_bound) then {
rlp_decode_tx(transaction, public_key, profile.transaction_blob_limit)
} else {
fatal_error(InvalidConfig)
}
}function decode_transaction(transaction, public_key) = {
let execution_profile = k_execution_profile;
let profile = execution_profile.protocol;
if transaction.len <= sizeof(transaction_length_bound) then {
rlp_decode_tx(transaction, public_key, profile.transaction_blob_limit)
} else {
fatal_error(InvalidConfig)
}
}function fatal_error(_reason) = exit(())function rlp_decode_tx(tx, pubkey, blob_limit) = {
let public_key_body = sub_slice(pubkey, 1, PUBLIC_KEY_BODY_LENGTH);
let public_key_hash = keccak256(public_key_body);
let public_key_word = hash_to_word(public_key_hash);
let sender = word_to_address(public_key_word);
let tx_length = tx.len;
let b0 : byte =
if tx_length == 0 then fatal_error(RlpDecode) else slice_byte(tx, 0);
let ttype : byte =
if b0[7 .. 6] == 0b11 then 0x00 else b0; /* 0xc0 = RLP list tag */
let typed = ttype != 0x00;
let payload : TransactionInputSlice =
if typed then if 1 <= tx_length then sub_slice(tx, 1, tx_length - 1) else fatal_error(RlpDecode) else tx;
let payload_input : StatelessInputSlice = payload;
let fields = rlp_node_cursor(payload_input);
let tx_type : TxType = match ttype {
0x00 => LegacyTx,
0x01 => AccessListTx,
0x02 => FeeMarketTx,
0x03 => BlobTx,
0x04 => SetCodeTx,
_ => fatal_error(RlpDecode),
};
match tx_type {
LegacyTx => {
let decoded = decode_legacy_tx(tx, pubkey, sender, fields);
pack_transaction(decoded)
},
AccessListTx => {
let decoded = decode_access_list_tx(tx, pubkey, sender, fields);
pack_transaction(decoded)
},
FeeMarketTx => {
let decoded = decode_fee_market_tx(tx, pubkey, sender, fields);
pack_transaction(decoded)
},
BlobTx => {
let decoded = decode_blob_tx(tx, pubkey, blob_limit, sender, fields);
pack_transaction(decoded)
},
SetCodeTx => {
let decoded = decode_set_code_tx(tx, pubkey, sender, fields);
pack_transaction(decoded)
},
}
}The active protocol policy and all gas limits derived from the executing header, selected together while decoding the stateless input.
register k_execution_profile : ExecutionProfile = DEFAULT_EXECUTION_PROFILEThe reason a block fails validation; one variant per violated block-validity rule.
enum FatalError = {
/* chain config: wrong fork / inactive activation */
InvalidConfig,
/* witness ancestor headers not contiguous */
HeaderChainBroken,
/* a transaction failed to RLP-decode */
RlpDecode,
/* a tx signature did not authenticate its sender */
InvalidSignature,
/* header.gas_limit is outside the consensus domain */
InvalidGasLimit,
/* EIP-7778: a tx exceeds the block's remaining gas */
GasUsedExceedsLimit,
/* a tx exceeds the block's remaining blob gas */
BlobGasLimitExceeded,
/* an invalid tx or a failed block-end system call */
ExecutionInvalid,
/* recomputed cumulative gas != header.gas_used */
InvalidGasUsed,
/* recomputed blob gas != header.blob_gas_used */
InvalidBlobGasUsed,
/* header.excess_blob_gas != expected */
InvalidExcessBlobGas,
/* recomputed post-state root != header.state_root */
InvalidStateRoot,
/* recomputed receipts root != header.receipts_root */
InvalidReceiptsRoot,
/* recomputed logs bloom != header.logs_bloom */
InvalidLogsBloom,
/* recomputed block hash != payload expected hash */
InvalidBlockHash,
/* header.parent_hash != authenticated parent */
InvalidParentHash,
/* EIP-7928: BAL item count > gas_limit / 2000 */
BlockAccessListTooLarge,
/* reconstructed EIP-7928 BAL bytes mismatch */
InvalidBlockAccessList,
/* reconstructed EIP-7685 request bytes mismatch */
InvalidExecutionRequests,
/* a missing/inconsistent proof node (thrown at use) */
WitnessDeficient,
/* an exact protocol integer exceeds its bounded execution representation */
NumericOverflow,
}A fork-selected per-transaction blob limit drawn from the supported schedules; zero for profiles before blob transactions activate.
type transaction_blob_limit = {
'value,
transaction_blob_limit_value('value).
int('value)
}Maximum byte length of one encoded transaction envelope. Provenance:
Amsterdam MAX_BYTES_PER_TRANSACTION in
SszExecutionPayload.transactions.
type transaction_length_bound : Int = 2 ^ 30