Trie node types¶
Merkle-Patricia trie node forms, references, and decoding (YP Appendix D).
type InlineNode¶
A self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}A KECCAK-256 / storage-key sized digest, in canonical protocol byte order.
type b256 = vector(32, inc, byte)let MPT_HASH_LENGTH¶
let MPT_HASH_LENGTH : int(32) = WORD_BYTE_LENGTHlet WORD_BYTE_LENGTH : int(32) = 32type TrieLeafValue¶
A leaf value retained by trie assembly. Authenticated witness and transaction leaves borrow immutable input bytes; newly encoded state, receipt, and withdrawal leaves borrow the scratch arena.
union TrieLeafValue = {
/* a leaf borrowing immutable stateless input bytes */
InputTrieLeaf : StatelessInputSlice,
/* a leaf borrowing freshly encoded scratch bytes */
ScratchTrieLeaf : ScratchSlice,
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}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 inline_node_from_scratch_slice¶
Copies a sub-32-byte scratch node encoding into an inline node value.
function inline_node_from_scratch_slice(bytes : ScratchSlice) -> InlineNode = {
let length = bytes.len;
if length < MPT_HASH_LENGTH then {
let encoded = slice_load(bytes, 0);
struct { data = word_to_hash(encoded), len = length }
} else {
fatal_error(WitnessDeficient)
}
}function fatal_error(_reason) = exit(())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
}let MPT_HASH_LENGTH : int(32) = WORD_BYTE_LENGTHThe 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 self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}function inline_node_from_input_slice¶
Copies a sub-32-byte input node encoding into an inline node value.
function inline_node_from_input_slice(bytes : StatelessInputSlice) -> InlineNode = {
let length = bytes.len;
if length < MPT_HASH_LENGTH then {
let encoded = slice_load(bytes, 0);
struct { data = word_to_hash(encoded), len = length }
} else {
fatal_error(WitnessDeficient)
}
}function fatal_error(_reason) = exit(())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
}let MPT_HASH_LENGTH : int(32) = WORD_BYTE_LENGTHThe 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 self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}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 inline_node_slice¶
Materializes an inline node in scratch memory as a byte slice.
function inline_node_slice(node : InlineNode) -> ScratchSlice = {
let start = scratch_reserve(node.len);
scratch_push_b256(node.data, node.len);
scratch_finish(start)
}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
}Appends a prefix of a fixed 32-byte value at the cursor.
function scratch_push_b256(data : b256, len : range(0, 32)) -> unit = {
if len != 0 then {
let arena = scratch_arena;
scratch_arena = host_scratch_store_b256(arena.len, data, len)
}
}function scratch_reserve(len) = {
let arena = scratch_arena;
let reserved = host_scratch_reserve(arena.len, len);
assert(reserved, "scratch reserve");
arena.len
}A self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}A scratch-arena range with its coordinate and length packed existentially.
type ScratchSlice = {
'off 'len,
scratch_valid_range('off, 'len).
ScratchSliceFields('off, 'len)
}function inline_node_hash¶
Hashes an inline node from its existing scratch representation.
function inline_node_hash(node : InlineNode) -> hash = {
let mark = scratch_begin();
let encoded = inline_node_slice(node);
let digest = keccak256(encoded);
scratch_rewind(mark);
digest
}Materializes an inline node in scratch memory as a byte slice.
function inline_node_slice(node : InlineNode) -> ScratchSlice = {
let start = scratch_reserve(node.len);
scratch_push_b256(node.data, node.len);
scratch_finish(start)
}Marks the start of a scratch construction.
function scratch_begin() -> source_pointer = {
let arena = scratch_arena;
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 self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}The common digest type used by trie, code, and block hashes.
type hash = b256type NodeRef¶
A reference to a trie node: empty, inline (encodings under 32 bytes), or by KECCAK-256 hash (YP Appendix D, Eq. 207).
union NodeRef = {
/* the empty node */
EmptyRef : unit,
/* an authenticated node embedded in witness input */
InputInlineRef : StatelessInputSliceAtMost(31),
/* a freshly encoded node embedded in a generated parent */
ScratchInlineRef : InlineNode,
/* a node referenced by its KECCAK-256 hash */
HashRef : hash,
}A self-contained trie-node encoding shorter than 32 bytes.
struct InlineNode = {
data : b256,
len : range(0, 31),
}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)
}The common digest type used by trie, code, and block hashes.
type hash = b256type BranchRefs¶
The sixteen child references of a branch, indexed by nibble.
type BranchRefs = vector(16, dec, NodeRef)A reference to a trie node: empty, inline (encodings under 32 bytes), or by KECCAK-256 hash (YP Appendix D, Eq. 207).
union NodeRef = {
/* the empty node */
EmptyRef : unit,
/* an authenticated node embedded in witness input */
InputInlineRef : StatelessInputSliceAtMost(31),
/* a freshly encoded node embedded in a generated parent */
ScratchInlineRef : InlineNode,
/* a node referenced by its KECCAK-256 hash */
HashRef : hash,
}type branch_content_length¶
The RLP payload of a branch contains sixteen child references of at most 33 bytes and one empty value byte.
type branch_content_length = range(0, 529)function branch_content_length_add¶
Advances the branch payload length while preserving its structural bound.
function branch_content_length_add(current : branch_content_length, addition : range(0, 33)) -> branch_content_length =
if addition <= 529 - current then {
current + addition
} else {
fatal_error(RlpDecode)
}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,
}The RLP payload of a branch contains sixteen child references of at most 33 bytes and one empty value byte.
type branch_content_length = range(0, 529)