The block access list¶
Validation of the EIP-7928 block access list supplied by the stateless host. The supplied bytes are decoded as canonical RLP and consumed in lockstep with the recorder's ordered account/change tables. Validation never reconstructs or re-encodes the list: the original source-backed slice is also the value hashed into the execution-payload header.
function bal_compare_index_word¶
Compares one canonical [index, word] pair.
function bal_compare_index_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
value : word,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let value_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, value_field.source.len);
bal_expect_end(fields);
let decoded_index = bal_ref_uint64(index_field);
let decoded_value = bal_ref_word(value_field);
if (decoded_index != index) | (decoded_value != value) then {
fatal_error(InvalidBlockAccessList)
}
}Requires that a decoded list has no unconsumed children.
function bal_expect_end forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
fatal_error(InvalidBlockAccessList)
}function bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP integer in the host-index/nonce domain.
function bal_ref_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) in
if canonical & (f.content_len <= 8) then {
rlp_decode_uint64(f)
} else {
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP integer in the EVM-word domain.
function bal_ref_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 canonical = rlp_item_uint_canonical(f) in
if canonical & (f.content_len <= 32) then {
rlp_decode_word(f)
} else {
fatal_error(InvalidBlockAccessList)
}function fatal_error(_reason) = exit(())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)
}
}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,
}The witness-carrying fields of a decoded RLP reference. Both the complete encoding and its content are statically contained by the source slice.
struct RlpFieldRef(
'source_off : Int,
'source_len : Int,
'content_len : Int,
),
rlp_field_ref_valid(
'source_off,
'source_len,
'content_len,
) = {
source : StatelessInputSliceFields('source_off, 'source_len),
is_list : bool,
content_len : int('content_len),
}EIP-7928 change position: pre-execution system calls use zero,
transactions use their one-based position, and post-execution system calls
use transaction_count + 1.
type block_access_index = range(0, transaction_count_bound + 1)The complete containment invariant for an RLP field reference. source
is normalized to the complete encoded item. RLP content is its suffix, so
the content offset is derived as source.len - content_len.
type rlp_field_ref_valid(
'source_off : Int,
'source_len : Int,
'content_len : Int,
) -> Bool =
source_valid_range('source_off, 'source_len)
& 0 <= 'content_len
& 'content_len <= 'source_lenThe 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 bal_compare_index_nonce¶
Compares one canonical [index, nonce] pair.
function bal_compare_index_nonce forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
value : account_nonce,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let value_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, value_field.source.len);
bal_expect_end(fields);
let decoded_index = bal_ref_uint64(index_field);
let decoded_value = bal_ref_uint64(value_field);
if (decoded_index != index) | (decoded_value != value) then {
fatal_error(InvalidBlockAccessList)
}
}Requires that a decoded list has no unconsumed children.
function bal_expect_end forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
fatal_error(InvalidBlockAccessList)
}function bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP integer in the host-index/nonce domain.
function bal_ref_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) in
if canonical & (f.content_len <= 8) then {
rlp_decode_uint64(f)
} else {
fatal_error(InvalidBlockAccessList)
}function fatal_error(_reason) = exit(())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)
}
}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,
}The witness-carrying fields of a decoded RLP reference. Both the complete encoding and its content are statically contained by the source slice.
struct RlpFieldRef(
'source_off : Int,
'source_len : Int,
'content_len : Int,
),
rlp_field_ref_valid(
'source_off,
'source_len,
'content_len,
) = {
source : StatelessInputSliceFields('source_off, 'source_len),
is_list : bool,
content_len : int('content_len),
}An account transaction-count nonce (EIP-2681).
type account_nonce = range(0, account_nonce_bound)EIP-7928 change position: pre-execution system calls use zero,
transactions use their one-based position, and post-execution system calls
use transaction_count + 1.
type block_access_index = range(0, transaction_count_bound + 1)The complete containment invariant for an RLP field reference. source
is normalized to the complete encoded item. RLP content is its suffix, so
the content offset is derived as source.len - content_len.
type rlp_field_ref_valid(
'source_off : Int,
'source_len : Int,
'content_len : Int,
) -> Bool =
source_valid_range('source_off, 'source_len)
& 0 <= 'content_len
& 'content_len <= 'source_lenfunction bal_compare_index_code¶
Compares one canonical [index, code] pair without materializing either
source-backed code sequence.
function bal_compare_index_code forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
code_hash : hash,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let code_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, code_field.source.len);
bal_expect_end(fields);
let code = code_db_resolve(code_hash);
let decoded_index = bal_ref_uint64(index_field);
let encoded_code = bal_ref_bytes(code_field);
let expected_code = code_bytes(code);
let code_matches = region_slices_equal(encoded_code, expected_code);
let code_mismatch = not_bool(code_matches);
if (decoded_index != index) | code_mismatch then {
fatal_error(InvalidBlockAccessList)
}
}Requires that a decoded list has no unconsumed children.
function bal_expect_end forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP byte string and returns its content slice.
function bal_ref_bytes 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)) -> (
StatelessInputSlice
) =
let canonical = rlp_ref_bytes_canonical(f) in
if canonical then {
rlp_item_content(f)
} else {
fatal_error(InvalidBlockAccessList)
}function bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP integer in the host-index/nonce domain.
function bal_ref_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) in
if canonical & (f.content_len <= 8) then {
rlp_decode_uint64(f)
} else {
fatal_error(InvalidBlockAccessList)
}function code_bytes(code) = struct { bytes = code.bytes, len = code.len }The code for a code hash; KECCAK_EMPTY resolves to empty code, and
an unwitnessed hash is a deficient witness.
function code_db_resolve(code_hash : hash) -> Code =
if code_hash == KECCAK_EMPTY then {
EMPTY_CODE
} else {
let code = code_db_lookup(code_hash);
if code.len == 0 then {
fatal_error(WitnessDeficient)
} else {
code
}
}function fatal_error(_reason) = exit(())val not_bool = pure {coq: "negb", lean: "_lean_not", _: "not"}: forall ('p : Bool). bool('p) -> bool(not('p))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)
}
}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,
}The witness-carrying fields of a decoded RLP reference. Both the complete encoding and its content are statically contained by the source slice.
struct RlpFieldRef(
'source_off : Int,
'source_len : Int,
'content_len : Int,
),
rlp_field_ref_valid(
'source_off,
'source_len,
'content_len,
) = {
source : StatelessInputSliceFields('source_off, 'source_len),
is_list : bool,
content_len : int('content_len),
}EIP-7928 change position: pre-execution system calls use zero,
transactions use their one-based position, and post-execution system calls
use transaction_count + 1.
type block_access_index = range(0, transaction_count_bound + 1)The common digest type used by trie, code, and block hashes.
type hash = b256The complete containment invariant for an RLP field reference. source
is normalized to the complete encoded item. RLP content is its suffix, so
the content offset is derived as source.len - content_len.
type rlp_field_ref_valid(
'source_off : Int,
'source_len : Int,
'content_len : Int,
) -> Bool =
source_valid_range('source_off, 'source_len)
& 0 <= 'content_len
& 'content_len <= 'source_lenfunction bal_validate_storage_change_values¶
Consumes the encoded changes for one storage slot. Each RLP pop strictly reduces the remaining byte length and must have one matching host event.
function bal_validate_storage_change_values forall 'source_off 'source_len,
source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
slot : word,
) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalStorageChange(change) => {
if change.slot != slot then {
fatal_error(InvalidBlockAccessList)
};
bal_compare_index_word(pair, change.index, change.value)
},
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_storage_change_values(next, slot)
}Compares one canonical [index, word] pair.
function bal_compare_index_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
value : word,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let value_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, value_field.source.len);
bal_expect_end(fields);
let decoded_index = bal_ref_uint64(index_field);
let decoded_value = bal_ref_word(value_field);
if (decoded_index != index) | (decoded_value != value) then {
fatal_error(InvalidBlockAccessList)
}
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryConsumes the encoded changes for one storage slot. Each RLP pop strictly reduces the remaining byte length and must have one matching host event.
function bal_validate_storage_change_values forall 'source_off 'source_len,
source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
slot : word,
) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalStorageChange(change) => {
if change.slot != slot then {
fatal_error(InvalidBlockAccessList)
};
bal_compare_index_word(pair, change.index, change.value)
},
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_storage_change_values(next, slot)
}function fatal_error(_reason) = exit(())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)
}
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundThe 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 bal_validate_storage_changes¶
Consumes canonical [slot, changes] entries in their encoded order.
function bal_validate_storage_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let fields = bal_ref_cursor(slot_field);
let slot_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, slot_value.source.len);
let changes_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, changes_value.source.len);
bal_expect_end(fields);
let changes = bal_ref_cursor(changes_value);
if changes.len == 0 then {
fatal_error(InvalidBlockAccessList)
};
let slot = bal_ref_word(slot_value);
bal_validate_storage_change_values(changes, slot);
1 + bal_validate_storage_changes(next)
}Requires that a decoded list has no unconsumed children.
function bal_expect_end forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
fatal_error(InvalidBlockAccessList)
}function bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Requires a canonical RLP integer in the EVM-word domain.
function bal_ref_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 canonical = rlp_item_uint_canonical(f) in
if canonical & (f.content_len <= 32) then {
rlp_decode_word(f)
} else {
fatal_error(InvalidBlockAccessList)
}Consumes the encoded changes for one storage slot. Each RLP pop strictly reduces the remaining byte length and must have one matching host event.
function bal_validate_storage_change_values forall 'source_off 'source_len,
source_valid_range('source_off, 'source_len). (
cursor : RlpCursor('source_off, 'source_len),
slot : word,
) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalStorageChange(change) => {
if change.slot != slot then {
fatal_error(InvalidBlockAccessList)
};
bal_compare_index_word(pair, change.index, change.value)
},
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_storage_change_values(next, slot)
}Consumes canonical [slot, changes] entries in their encoded order.
function bal_validate_storage_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let fields = bal_ref_cursor(slot_field);
let slot_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, slot_value.source.len);
let changes_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, changes_value.source.len);
bal_expect_end(fields);
let changes = bal_ref_cursor(changes_value);
if changes.len == 0 then {
fatal_error(InvalidBlockAccessList)
};
let slot = bal_ref_word(slot_value);
bal_validate_storage_change_values(changes, slot);
1 + bal_validate_storage_changes(next)
}function fatal_error(_reason) = exit(())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)
}
}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 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)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_boundfunction bal_validate_storage_reads¶
Consumes the read-only storage slots in their encoded order.
function bal_validate_storage_reads forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let slot = bal_ref_word(slot_field);
let event = bal_iter_next();
match event {
BalStorageRead(recorded) => {
if recorded != slot then {
fatal_error(InvalidBlockAccessList)
}
},
_ => fatal_error(InvalidBlockAccessList),
};
1 + bal_validate_storage_reads(next)
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryRequires a canonical RLP integer in the EVM-word domain.
function bal_ref_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 canonical = rlp_item_uint_canonical(f) in
if canonical & (f.content_len <= 32) then {
rlp_decode_word(f)
} else {
fatal_error(InvalidBlockAccessList)
}Consumes the read-only storage slots in their encoded order.
function bal_validate_storage_reads forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let slot = bal_ref_word(slot_field);
let event = bal_iter_next();
match event {
BalStorageRead(recorded) => {
if recorded != slot then {
fatal_error(InvalidBlockAccessList)
}
},
_ => fatal_error(InvalidBlockAccessList),
};
1 + bal_validate_storage_reads(next)
}function fatal_error(_reason) = exit(())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)
}
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundfunction bal_validate_balance_changes¶
Consumes balance changes in their encoded order.
function bal_validate_balance_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalBalanceChange(change) => bal_compare_index_word(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_balance_changes(next)
}Compares one canonical [index, word] pair.
function bal_compare_index_word forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
value : word,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let value_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, value_field.source.len);
bal_expect_end(fields);
let decoded_index = bal_ref_uint64(index_field);
let decoded_value = bal_ref_word(value_field);
if (decoded_index != index) | (decoded_value != value) then {
fatal_error(InvalidBlockAccessList)
}
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryConsumes balance changes in their encoded order.
function bal_validate_balance_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalBalanceChange(change) => bal_compare_index_word(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_balance_changes(next)
}function fatal_error(_reason) = exit(())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)
}
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundfunction bal_validate_nonce_changes¶
Consumes nonce changes in their encoded order.
function bal_validate_nonce_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalNonceChange(change) => bal_compare_index_nonce(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_nonce_changes(next)
}Compares one canonical [index, nonce] pair.
function bal_compare_index_nonce forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
value : account_nonce,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let value_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, value_field.source.len);
bal_expect_end(fields);
let decoded_index = bal_ref_uint64(index_field);
let decoded_value = bal_ref_uint64(value_field);
if (decoded_index != index) | (decoded_value != value) then {
fatal_error(InvalidBlockAccessList)
}
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryConsumes nonce changes in their encoded order.
function bal_validate_nonce_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalNonceChange(change) => bal_compare_index_nonce(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_nonce_changes(next)
}function fatal_error(_reason) = exit(())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)
}
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundfunction bal_validate_code_changes¶
Consumes code changes in their encoded order.
function bal_validate_code_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalCodeChange(change) => bal_compare_index_code(pair, change.index, change.code_hash),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_code_changes(next)
}Compares one canonical [index, code] pair without materializing either
source-backed code sequence.
function bal_compare_index_code forall 'source_off 'source_len 'content_len,
rlp_field_ref_valid('source_off, 'source_len, 'content_len). (
pair : RlpFieldRef('source_off, 'source_len, 'content_len),
index : block_access_index,
code_hash : hash,
) -> (
unit
) = {
let fields = bal_ref_cursor(pair);
let index_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, index_field.source.len);
let code_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, code_field.source.len);
bal_expect_end(fields);
let code = code_db_resolve(code_hash);
let decoded_index = bal_ref_uint64(index_field);
let encoded_code = bal_ref_bytes(code_field);
let expected_code = code_bytes(code);
let code_matches = region_slices_equal(encoded_code, expected_code);
let code_mismatch = not_bool(code_matches);
if (decoded_index != index) | code_mismatch then {
fatal_error(InvalidBlockAccessList)
}
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryConsumes code changes in their encoded order.
function bal_validate_code_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalCodeChange(change) => bal_compare_index_code(pair, change.index, change.code_hash),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_code_changes(next)
}function fatal_error(_reason) = exit(())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)
}
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundfunction bal_validate_accounts¶
Consumes account entries from the canonical RLP list. The encoded cursor is the traversal driver; the host iterator supplies exactly one comparison event for every decoded semantic value.
function bal_validate_accounts forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let account_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, account_field.source.len);
let fields = bal_ref_cursor(account_field);
let address_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, address_field.source.len);
let storage_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_changes_field.source.len);
let storage_reads_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_reads_field.source.len);
let balance_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, balance_changes_field.source.len);
let nonce_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, nonce_changes_field.source.len);
let code_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, code_changes_field.source.len);
bal_expect_end(fields);
let address_bytes = bal_ref_bytes(address_field);
let address_word = rlp_decode_word(address_field);
let account = word_to_address(address_word);
if address_bytes.len != 20 then {
fatal_error(InvalidBlockAccessList)
};
let account_event = bal_iter_next();
match account_event {
BalAccount(recorded) => if recorded != account then {
fatal_error(InvalidBlockAccessList)
},
_ => fatal_error(InvalidBlockAccessList),
};
let storage_changes_cursor = bal_ref_cursor(storage_changes_field);
let storage_changes = bal_validate_storage_changes(storage_changes_cursor);
let storage_reads_cursor = bal_ref_cursor(storage_reads_field);
let storage_reads = bal_validate_storage_reads(storage_reads_cursor);
let balance_changes_cursor = bal_ref_cursor(balance_changes_field);
bal_validate_balance_changes(balance_changes_cursor);
let nonce_changes_cursor = bal_ref_cursor(nonce_changes_field);
bal_validate_nonce_changes(nonce_changes_cursor);
let code_changes_cursor = bal_ref_cursor(code_changes_field);
bal_validate_code_changes(code_changes_cursor);
let account_end_event = bal_iter_next();
match account_end_event {
BalAccountEnd(_) => (),
_ => fatal_error(InvalidBlockAccessList),
};
1 + storage_changes + storage_reads + bal_validate_accounts(next)
}Requires that a decoded list has no unconsumed children.
function bal_expect_end forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
fatal_error(InvalidBlockAccessList)
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryRequires a canonical RLP byte string and returns its content slice.
function bal_ref_bytes 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)) -> (
StatelessInputSlice
) =
let canonical = rlp_ref_bytes_canonical(f) in
if canonical then {
rlp_item_content(f)
} else {
fatal_error(InvalidBlockAccessList)
}function bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Consumes account entries from the canonical RLP list. The encoded cursor is the traversal driver; the host iterator supplies exactly one comparison event for every decoded semantic value.
function bal_validate_accounts forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let account_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, account_field.source.len);
let fields = bal_ref_cursor(account_field);
let address_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, address_field.source.len);
let storage_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_changes_field.source.len);
let storage_reads_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_reads_field.source.len);
let balance_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, balance_changes_field.source.len);
let nonce_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, nonce_changes_field.source.len);
let code_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, code_changes_field.source.len);
bal_expect_end(fields);
let address_bytes = bal_ref_bytes(address_field);
let address_word = rlp_decode_word(address_field);
let account = word_to_address(address_word);
if address_bytes.len != 20 then {
fatal_error(InvalidBlockAccessList)
};
let account_event = bal_iter_next();
match account_event {
BalAccount(recorded) => if recorded != account then {
fatal_error(InvalidBlockAccessList)
},
_ => fatal_error(InvalidBlockAccessList),
};
let storage_changes_cursor = bal_ref_cursor(storage_changes_field);
let storage_changes = bal_validate_storage_changes(storage_changes_cursor);
let storage_reads_cursor = bal_ref_cursor(storage_reads_field);
let storage_reads = bal_validate_storage_reads(storage_reads_cursor);
let balance_changes_cursor = bal_ref_cursor(balance_changes_field);
bal_validate_balance_changes(balance_changes_cursor);
let nonce_changes_cursor = bal_ref_cursor(nonce_changes_field);
bal_validate_nonce_changes(nonce_changes_cursor);
let code_changes_cursor = bal_ref_cursor(code_changes_field);
bal_validate_code_changes(code_changes_cursor);
let account_end_event = bal_iter_next();
match account_end_event {
BalAccountEnd(_) => (),
_ => fatal_error(InvalidBlockAccessList),
};
1 + storage_changes + storage_reads + bal_validate_accounts(next)
}Consumes balance changes in their encoded order.
function bal_validate_balance_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalBalanceChange(change) => bal_compare_index_word(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_balance_changes(next)
}Consumes code changes in their encoded order.
function bal_validate_code_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalCodeChange(change) => bal_compare_index_code(pair, change.index, change.code_hash),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_code_changes(next)
}Consumes nonce changes in their encoded order.
function bal_validate_nonce_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
unit
) = {
if cursor.len == 0 then {
return ()
};
let pair = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, pair.source.len);
let event = bal_iter_next();
match event {
BalNonceChange(change) => bal_compare_index_nonce(pair, change.index, change.value),
_ => fatal_error(InvalidBlockAccessList),
};
bal_validate_nonce_changes(next)
}Consumes canonical [slot, changes] entries in their encoded order.
function bal_validate_storage_changes forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let fields = bal_ref_cursor(slot_field);
let slot_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, slot_value.source.len);
let changes_value = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, changes_value.source.len);
bal_expect_end(fields);
let changes = bal_ref_cursor(changes_value);
if changes.len == 0 then {
fatal_error(InvalidBlockAccessList)
};
let slot = bal_ref_word(slot_value);
bal_validate_storage_change_values(changes, slot);
1 + bal_validate_storage_changes(next)
}Consumes the read-only storage slots in their encoded order.
function bal_validate_storage_reads forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let slot_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, slot_field.source.len);
let slot = bal_ref_word(slot_field);
let event = bal_iter_next();
match event {
BalStorageRead(recorded) => {
if recorded != slot then {
fatal_error(InvalidBlockAccessList)
}
},
_ => fatal_error(InvalidBlockAccessList),
};
1 + bal_validate_storage_reads(next)
}function fatal_error(_reason) = exit(())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 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)
}
}Converts a word to its low 160-bit address in canonical byte order.
function word_to_address(value : word) -> address = {
let zero_bytes = vector_init(20, 0x00);
var result : address = Address(zero_bytes);
result[0] = get_slice_int(8, value, 152);
result[1] = get_slice_int(8, value, 144);
result[2] = get_slice_int(8, value, 136);
result[3] = get_slice_int(8, value, 128);
result[4] = get_slice_int(8, value, 120);
result[5] = get_slice_int(8, value, 112);
result[6] = get_slice_int(8, value, 104);
result[7] = get_slice_int(8, value, 96);
result[8] = get_slice_int(8, value, 88);
result[9] = get_slice_int(8, value, 80);
result[10] = get_slice_int(8, value, 72);
result[11] = get_slice_int(8, value, 64);
result[12] = get_slice_int(8, value, 56);
result[13] = get_slice_int(8, value, 48);
result[14] = get_slice_int(8, value, 40);
result[15] = get_slice_int(8, value, 32);
result[16] = get_slice_int(8, value, 24);
result[17] = get_slice_int(8, value, 16);
result[18] = get_slice_int(8, value, 8);
result[19] = get_slice_int(8, value, 0);
result
}One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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)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_boundfunction validate_block_access_list¶
Validates the canonical EIP-7928 BAL directly against the host recorder.
function validate_block_access_list(
bytes : StatelessInputSliceAtMost(block_access_list_length_bound),
block_gas_limit : block_gas_limit,
) -> (
unit
) = {
bal_prepare_iter();
let root = rlp_single_ref(bytes);
let accounts_cursor = bal_ref_cursor(root);
let bal_items = bal_validate_accounts(accounts_cursor);
let remaining_event = bal_iter_next();
match remaining_event {
BalEmpty(_) => (),
_ => fatal_error(InvalidBlockAccessList),
};
if BLOCK_ACCESS_LIST_ITEM_GAS * bal_items > block_gas_limit then {
fatal_error(BlockAccessListTooLarge)
}
}Pops the next event from the canonical account-delimited BAL stream.
val bal_iter_next = impure { c: "bal_iter_next" } : unit -> BalIterEntryCanonicalizes the recorded keyed tables and resets the flat iterator.
val bal_prepare_iter = impure { c: "bal_prepare_iter" } : unit -> unitfunction bal_ref_cursor(f) =
let framing_canonical = rlp_ref_framing_canonical(f) in
if f.is_list & framing_canonical then {
rlp_decode_list(f)
} else {
fatal_error(InvalidBlockAccessList)
}Consumes account entries from the canonical RLP list. The encoded cursor is the traversal driver; the host iterator supplies exactly one comparison event for every decoded semantic value.
function bal_validate_accounts forall 'source_off 'source_len, source_valid_range('source_off, 'source_len). (cursor :
RlpCursor('source_off, 'source_len)) -> (
range(0, 'source_len)
) =
if cursor.len == 0 then {
0
} else {
let account_field = rlp_decode_item(cursor);
let next = rlp_cursor_advance(cursor, account_field.source.len);
let fields = bal_ref_cursor(account_field);
let address_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, address_field.source.len);
let storage_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_changes_field.source.len);
let storage_reads_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, storage_reads_field.source.len);
let balance_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, balance_changes_field.source.len);
let nonce_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, nonce_changes_field.source.len);
let code_changes_field = rlp_decode_item(fields);
let fields = rlp_cursor_advance(fields, code_changes_field.source.len);
bal_expect_end(fields);
let address_bytes = bal_ref_bytes(address_field);
let address_word = rlp_decode_word(address_field);
let account = word_to_address(address_word);
if address_bytes.len != 20 then {
fatal_error(InvalidBlockAccessList)
};
let account_event = bal_iter_next();
match account_event {
BalAccount(recorded) => if recorded != account then {
fatal_error(InvalidBlockAccessList)
},
_ => fatal_error(InvalidBlockAccessList),
};
let storage_changes_cursor = bal_ref_cursor(storage_changes_field);
let storage_changes = bal_validate_storage_changes(storage_changes_cursor);
let storage_reads_cursor = bal_ref_cursor(storage_reads_field);
let storage_reads = bal_validate_storage_reads(storage_reads_cursor);
let balance_changes_cursor = bal_ref_cursor(balance_changes_field);
bal_validate_balance_changes(balance_changes_cursor);
let nonce_changes_cursor = bal_ref_cursor(nonce_changes_field);
bal_validate_nonce_changes(nonce_changes_cursor);
let code_changes_cursor = bal_ref_cursor(code_changes_field);
bal_validate_code_changes(code_changes_cursor);
let account_end_event = bal_iter_next();
match account_end_event {
BalAccountEnd(_) => (),
_ => fatal_error(InvalidBlockAccessList),
};
1 + storage_changes + storage_reads + bal_validate_accounts(next)
}function fatal_error(_reason) = exit(())function rlp_single_ref(item) = {
let item_length = item.len;
if item_length == 0 then {
fatal_error(RlpDecode)
} else {
let (is_list, content_off, content_len_value) = rlp_ref_hdr(item);
let (content_len as 'content_len) = content_len_value;
if (content_off <= item_length) & (content_len == item_length - content_off) then {
let field : RlpFieldRef('source_off, 'source_len, 'content_len) = struct {
source = item,
is_list = is_list,
content_len = content_len,
};
field
} else {
fatal_error(RlpDecode)
}
}
}let BLOCK_ACCESS_LIST_ITEM_GAS : int(block_access_list_item_gas) = sizeof(block_access_list_item_gas)One event in the canonical account-delimited BAL stream.
union BalIterEntry = {
/* starts one account and carries its address */
BalAccount : address,
/* one indexed post-transaction value for a changed storage slot */
BalStorageChange : BalStorageChangeEntry,
/* one storage slot that was read but never changed */
BalStorageRead : word,
/* one indexed post-transaction balance */
BalBalanceChange : BalBalanceChangeEntry,
/* one indexed post-transaction nonce */
BalNonceChange : BalNonceChangeEntry,
/* one indexed post-transaction code hash */
BalCodeChange : BalCodeChangeEntry,
/* ends the current account */
BalAccountEnd : unit,
/* marks the end of the complete stream */
BalEmpty : unit,
}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 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 ^ 30A block header's gas limit. Its SSZ execution-payload field is uint64;
GASLIMIT widens this bounded natural into an EVM word when it pushes the
value onto the stack (Yellow Paper equation 147).
type block_gas_limit = range(0, block_gas_limit_bound)