Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/ethereum/forks/amsterdam/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
The Amsterdam fork.
"""

from ethereum.fork_criteria import Unscheduled

Expand Down
170 changes: 170 additions & 0 deletions src/ethereum/forks/amsterdam/binary_tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
"""
Binary Merkle Tree.

.. contents:: Table of Contents
:backlinks: none
:local:

"""

from dataclasses import dataclass, field
from typing import Callable, Dict

from ethereum_types.numeric import U256, Uint


@dataclass
class BinaryTree:
"""
Binary Merkle Tree.
"""

binary_hash: Callable[[U256, U256], U256]
empty_node: Callable[[U256], U256]
_data: Dict[U256, U256] = field(default_factory=dict)


GTI_ROOT = U256(1)
GTI_MAX_LEVEL = U256(1) << 255


def btree_get(tree: BinaryTree, index: U256) -> U256:
"""
Returns the node value at the given generalized tree index. If it is an
internal node with an invalidated value then the value is recursively
recalculated on demand and also cached for future use.

Note that this function does not automatically expand previously untouched
empty subtrees.
"""
if index not in tree._data:
if index >= GTI_MAX_LEVEL:
raise AssertionError("Trying to get non-existent node")
left = btree_get(tree, index * 2)
right = btree_get(tree, index * 2 + 1)
tree._data[index] = tree.binary_hash(left, right)
return tree._data[index]


def btree_set(tree: BinaryTree, index, value: U256) -> None:
"""
Sets the leaf node value at the given generalized tree index and
invalidates any cached ancestor nodes. If the node did not exist previously
then it expands the tree, assuming that it is an empty, previously
untouched region.
"""
if index not in tree._data:
btree_expand(tree, index)
tree._data[index] = value
_invalidate_ancestors(tree, index)


def _invalidate_ancestors(tree: BinaryTree, index: U256) -> None:
"""
Invalidates ancestors of the given node, moving down towards the root until
an already invalidated node is found. It is assumed that an invalidated
node always has invalidated ancestors.
"""
while index != GTI_ROOT:
index //= 2
if index not in tree._data:
return
del tree._data[index]


def btree_expand(tree: BinaryTree, index: U256) -> None:
"""
Expands the tree ensuring that the given node exists and can be updated,
assuming that it is in an empty, previously untouched region.

Note that empty tree expansion relies on the empty_node callback function
that defines the tree structure. This structure might include optional
containers that are represented by a node that can either have a zero value
or the root hash of the container. Expanding the tree beyond the root node
of such a container initializes the container, changing the actual contents
of the tree. For this reason an expand operation invalidates the ancestors
of the expanded node.
"""
if index in tree._data:
if tree._data[index] != tree.empty_node(index):
raise AssertionError("Trying to expand non-empty subtree")
return
tree._data[index] = tree.empty_node(index)
if index == GTI_ROOT:
return
parent = index // 2
sibling = parent * 4 + 1 - index
btree_expand(tree, parent)
tree._data[sibling] = tree.empty_node(sibling)
_invalidate_ancestors(tree, index)


def btree_collapse(tree: BinaryTree, index: U256) -> None:
"""
Collapses the descendants of the given node into a single hash node.

Note that a collapsed subtree should not be expanded again.
"""
btree_get(tree, index)
if index * 2 in tree._data:
btree_collapse(tree, index * 2)
del tree._data[index * 2]
if index * 2 + 1 in tree._data:
btree_collapse(tree, index * 2 + 1)
del tree._data[index * 2 + 1]


def gti_height(index: U256) -> Uint:
"""
Returns the height of a generalized tree index. The height of the root node
is zero, all other nodes have the height of their parent plus one.
"""
# TODO: more efficient implementation?
height = Uint(0)
while index > GTI_ROOT:
height += 1
index >>= 1
return height


def gti_vector(root: U256, index, height: Uint) -> U256:
"""
Returns the generalized tree index of a vector item.
"""
return root << height + index


def gti_merge(index, sub_index: U256) -> U256:
"""
Returns the generalized tree index that has a relative position sub_index
from the position index.
"""
sub_height = gti_height(sub_index)
return (index - 1) << sub_height + sub_index


def gti_split_below(index: U256, level: Uint) -> U256:
"""
Splits the path leading to the given generalized tree index at the
specified height and returns the index at the given height. If the height
of the given index is less than the specified level then the original
index is returned.
"""
height = gti_height(index)
if height > level:
index >>= height - level
return index


def gti_split_above(index: U256, level: Uint) -> U256:
"""
Splits the path leading to the given generalized tree index at the
specified height and returns the relative sub-index pointing to the
original index from the given height. If the height of the given index is
less than the specified level then the root index is returned.
"""
height = gti_height(index)
if height <= level:
return GTI_ROOT
gti_base = GTI_ROOT << height - level
return gti_base + (index & (gti_base - 1))
8 changes: 1 addition & 7 deletions src/ethereum/forks/amsterdam/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,7 @@ class Header:
[Trie]: ref:ethereum.forks.amsterdam.trie.Trie
"""

bloom: Bloom
"""
Bloom filter for logs generated by transactions in this block.
Constructed from all logs in the block using the [logs bloom] mechanism.

[logs bloom]: ref:ethereum.forks.amsterdam.bloom.logs_bloom
"""
log_index_root: Root

difficulty: Uint
"""
Expand Down
29 changes: 27 additions & 2 deletions src/ethereum/forks/amsterdam/fork.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@
TransactionTypeContractCreationError,
)
from .fork_types import Account, Address, Authorization, VersionedHash
from .log_index import (
LogIndexState,
log_index_add_block_entry,
log_index_add_log_entries,
log_index_add_tx_entry,
log_index_root,
)
from .requests import (
CONSOLIDATION_REQUEST_TYPE,
DEPOSIT_REQUEST_TYPE,
Expand Down Expand Up @@ -122,6 +129,7 @@ class BlockChain:

blocks: List[Block]
state: State
log_index: LogIndexState
chain_id: U64


Expand Down Expand Up @@ -222,6 +230,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
block_env = vm.BlockEnvironment(
chain_id=chain.chain_id,
state=chain.state,
log_index=chain.log_index,
block_gas_limit=block.header.gas_limit,
block_hashes=get_last_256_block_hashes(chain),
coinbase=block.header.coinbase,
Expand All @@ -239,12 +248,14 @@ def state_transition(chain: BlockChain, block: Block) -> None:
withdrawals=block.withdrawals,
)
block_state_root = state_root(block_env.state)
block_log_index_root = log_index_root(block_env.log_index)
transactions_root = root(block_output.transactions_trie)
receipt_root = root(block_output.receipts_trie)
block_logs_bloom = logs_bloom(block_output.block_logs)
withdrawals_root = root(block_output.withdrawals_trie)
requests_hash = compute_requests_hash(block_output.requests)

log_index_add_block_entry(block_env.log_index, block.header)

if block_output.block_gas_used != block.header.gas_used:
raise InvalidBlock(
f"{block_output.block_gas_used} != {block.header.gas_used}"
Expand All @@ -255,7 +266,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
raise InvalidBlock
if receipt_root != block.header.receipt_root:
raise InvalidBlock
if block_logs_bloom != block.header.bloom:
if block_log_index_root != block.header.log_index_root:
raise InvalidBlock
if withdrawals_root != block.header.withdrawals_root:
raise InvalidBlock
Expand Down Expand Up @@ -984,6 +995,20 @@ def process_transaction(
receipt = make_receipt(
tx, tx_output.error, block_output.block_gas_used, tx_output.logs
)
log_index_add_tx_entry(
block_env.log_index,
block_env.number,
tx_env.tx_hash,
keccak256(receipt),
index,
)
log_index_add_log_entries(
block_env.log_index,
block_env.number,
tx_env.tx_hash,
index,
tx_output.logs,
)

receipt_key = rlp.encode(Uint(index))
block_output.receipt_keys += (receipt_key,)
Expand Down
Loading
Loading