Skip to content
Merged
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
394 changes: 170 additions & 224 deletions src/ethereum/arrow_glacier/fork.py

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion src/ethereum/arrow_glacier/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
`EMPTY_ACCOUNT`.
"""
from dataclasses import dataclass, field
from typing import Callable, Dict, List, Optional, Set, Tuple
from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple

from ethereum_types.bytes import Bytes
from ethereum_types.frozen import modify
Expand Down Expand Up @@ -630,3 +630,20 @@ def get_storage_original(state: State, address: Address, key: Bytes) -> U256:
assert isinstance(original_value, U256)

return original_value


def destroy_touched_empty_accounts(
state: State, touched_accounts: Iterable[Address]
) -> None:
"""
Destroy all touched accounts that are empty.
Parameters
----------
state: `State`
The current state.
touched_accounts: `Iterable[Address]`
All the accounts that have been touched in the current transaction.
"""
for address in touched_accounts:
if account_exists_and_is_empty(state, address):
destroy_account(state, address)
45 changes: 32 additions & 13 deletions src/ethereum/arrow_glacier/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ethereum_rlp import rlp
from ethereum_types.bytes import Bytes, Bytes0, Bytes32
from ethereum_types.frozen import slotted_freezable
from ethereum_types.numeric import U64, U256, Uint
from ethereum_types.numeric import U64, U256, Uint, ulen

from ethereum.crypto.elliptic_curve import SECP256K1N, secp256k1_recover
from ethereum.crypto.hash import Hash32, keccak256
Expand All @@ -18,12 +18,12 @@
from .exceptions import TransactionTypeError
from .fork_types import Address

TX_BASE_COST = 21000
TX_DATA_COST_PER_NON_ZERO = 16
TX_DATA_COST_PER_ZERO = 4
TX_CREATE_COST = 32000
TX_ACCESS_LIST_ADDRESS_COST = 2400
TX_ACCESS_LIST_STORAGE_KEY_COST = 1900
TX_BASE_COST = Uint(21000)
TX_DATA_COST_PER_NON_ZERO = Uint(16)
TX_DATA_COST_PER_ZERO = Uint(4)
TX_CREATE_COST = Uint(32000)
TX_ACCESS_LIST_ADDRESS_COST = Uint(2400)
TX_ACCESS_LIST_STORAGE_KEY_COST = Uint(1900)


@slotted_freezable
Expand Down Expand Up @@ -177,10 +177,10 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint:

Returns
-------
verified : `ethereum.base_types.Uint`
intrinsic_gas : `ethereum.base_types.Uint`
The intrinsic cost of the transaction.
"""
data_cost = 0
data_cost = Uint(0)

for byte in tx.data:
if byte == 0:
Expand All @@ -191,15 +191,15 @@ def calculate_intrinsic_cost(tx: Transaction) -> Uint:
if tx.to == Bytes0(b""):
create_cost = TX_CREATE_COST
else:
create_cost = 0
create_cost = Uint(0)

access_list_cost = 0
access_list_cost = Uint(0)
if isinstance(tx, (AccessListTransaction, FeeMarketTransaction)):
for _address, keys in tx.access_list:
access_list_cost += TX_ACCESS_LIST_ADDRESS_COST
access_list_cost += len(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST
access_list_cost += ulen(keys) * TX_ACCESS_LIST_STORAGE_KEY_COST

return Uint(TX_BASE_COST + data_cost + create_cost + access_list_cost)
return TX_BASE_COST + data_cost + create_cost + access_list_cost


def recover_sender(chain_id: U64, tx: Transaction) -> Address:
Expand Down Expand Up @@ -380,3 +380,22 @@ def signing_hash_1559(tx: FeeMarketTransaction) -> Hash32:
)
)
)


def get_transaction_hash(tx: Union[Bytes, LegacyTransaction]) -> Hash32:
"""
Parameters
----------
tx :
Transaction of interest.

Returns
-------
hash : `ethereum.crypto.hash.Hash32`
Hash of the transaction.
"""
assert isinstance(tx, (LegacyTransaction, Bytes))
if isinstance(tx, LegacyTransaction):
return keccak256(rlp.encode(tx))
else:
return keccak256(tx)
97 changes: 35 additions & 62 deletions src/ethereum/arrow_glacier/utils/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,105 +12,78 @@
Message specific functions used in this arrow_glacier version of
specification.
"""
from typing import FrozenSet, Optional, Tuple, Union

from ethereum_types.bytes import Bytes, Bytes0, Bytes32
from ethereum_types.numeric import U256, Uint
from ethereum_types.bytes import Bytes, Bytes0
from ethereum_types.numeric import Uint

from ..fork_types import Address
from ..state import get_account
from ..vm import Environment, Message
from ..transactions import Transaction
from ..vm import BlockEnvironment, Message, TransactionEnvironment
from ..vm.precompiled_contracts.mapping import PRE_COMPILED_CONTRACTS
from .address import compute_contract_address


def prepare_message(
caller: Address,
target: Union[Bytes0, Address],
value: U256,
data: Bytes,
gas: Uint,
env: Environment,
code_address: Optional[Address] = None,
should_transfer_value: bool = True,
is_static: bool = False,
preaccessed_addresses: FrozenSet[Address] = frozenset(),
preaccessed_storage_keys: FrozenSet[
Tuple[(Address, Bytes32)]
] = frozenset(),
block_env: BlockEnvironment,
tx_env: TransactionEnvironment,
tx: Transaction,
) -> Message:
"""
Execute a transaction against the provided environment.

Parameters
----------
caller :
Address which initiated the transaction
target :
Address whose code will be executed
value :
Value to be transferred.
data :
Array of bytes provided to the code in `target`.
gas :
Gas provided for the code in `target`.
env :
block_env :
Environment for the Ethereum Virtual Machine.
code_address :
This is usually same as the `target` address except when an alternative
accounts code needs to be executed.
eg. `CALLCODE` calling a precompile.
should_transfer_value :
if True ETH should be transferred while executing a message call.
is_static:
if True then it prevents all state-changing operations from being
executed.
preaccessed_addresses:
Addresses that should be marked as accessed prior to the message call
preaccessed_storage_keys:
Storage keys that should be marked as accessed prior to the message
call
tx_env :
Environment for the transaction.
tx :
Transaction to be executed.

Returns
-------
message: `ethereum.arrow_glacier.vm.Message`
Items containing contract creation or message call specific data.
"""
accessed_addresses = set()
accessed_addresses.add(caller)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, the old code here was pretty misleading.

accessed_addresses.add(tx_env.origin)
accessed_addresses.update(PRE_COMPILED_CONTRACTS.keys())
accessed_addresses.update(preaccessed_addresses)
if isinstance(target, Bytes0):
accessed_addresses.update(tx_env.access_list_addresses)

if isinstance(tx.to, Bytes0):
current_target = compute_contract_address(
caller,
get_account(env.state, caller).nonce - Uint(1),
tx_env.origin,
get_account(block_env.state, tx_env.origin).nonce - Uint(1),
)
msg_data = Bytes(b"")
code = data
elif isinstance(target, Address):
current_target = target
msg_data = data
code = get_account(env.state, target).code
if code_address is None:
code_address = target
code = tx.data
code_address = None
elif isinstance(tx.to, Address):
current_target = tx.to
msg_data = tx.data
code = get_account(block_env.state, tx.to).code

code_address = tx.to
else:
raise AssertionError("Target must be address or empty bytes")

accessed_addresses.add(current_target)

return Message(
caller=caller,
target=target,
gas=gas,
value=value,
block_env=block_env,
tx_env=tx_env,
caller=tx_env.origin,
target=tx.to,
gas=tx_env.gas,
value=tx.value,
data=msg_data,
code=code,
depth=Uint(0),
current_target=current_target,
code_address=code_address,
should_transfer_value=should_transfer_value,
is_static=is_static,
should_transfer_value=True,
is_static=False,
accessed_addresses=accessed_addresses,
accessed_storage_keys=set(preaccessed_storage_keys),
accessed_storage_keys=set(tx_env.access_list_storage_keys),
parent_evm=None,
)
70 changes: 57 additions & 13 deletions src/ethereum/arrow_glacier/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,83 @@
`.fork_types.Account`.
"""

from dataclasses import dataclass
from dataclasses import dataclass, field
from typing import List, Optional, Set, Tuple, Union

from ethereum_types.bytes import Bytes, Bytes0, Bytes32
from ethereum_types.numeric import U64, U256, Uint

from ethereum.crypto.hash import Hash32
from ethereum.exceptions import EthereumException

from ..blocks import Log
from ..blocks import Log, Receipt
from ..fork_types import Address
from ..state import State, account_exists_and_is_empty
from ..transactions import LegacyTransaction
from ..trie import Trie
from .precompiled_contracts import RIPEMD160_ADDRESS

__all__ = ("Environment", "Evm", "Message")


@dataclass
class Environment:
class BlockEnvironment:
"""
Items external to the virtual machine itself, provided by the environment.
"""

caller: Address
chain_id: U64
state: State
block_gas_limit: Uint
block_hashes: List[Hash32]
origin: Address
coinbase: Address
number: Uint
base_fee_per_gas: Uint
gas_limit: Uint
gas_price: Uint
time: U256
difficulty: Uint
state: State
chain_id: U64


@dataclass
class BlockOutput:
"""
Output from applying the block body to the present state.

Contains the following:

block_gas_used : `ethereum.base_types.Uint`
Gas used for executing all transactions.
transactions_trie : `ethereum.fork_types.Root`
Trie of all the transactions in the block.
receipts_trie : `ethereum.fork_types.Root`
Trie root of all the receipts in the block.
block_logs : `Bloom`
Logs bloom of all the logs included in all the transactions of the
block.
"""

block_gas_used: Uint = Uint(0)
transactions_trie: Trie[
Bytes, Optional[Union[Bytes, LegacyTransaction]]
] = field(default_factory=lambda: Trie(secured=False, default=None))
receipts_trie: Trie[Bytes, Optional[Union[Bytes, Receipt]]] = field(
default_factory=lambda: Trie(secured=False, default=None)
)
block_logs: Tuple[Log, ...] = field(default_factory=tuple)


@dataclass
class TransactionEnvironment:
"""
Items that are used by contract creation or message call.
"""

origin: Address
gas_price: Uint
gas: Uint
access_list_addresses: Set[Address]
access_list_storage_keys: Set[Tuple[Address, Bytes32]]
index_in_block: Optional[Uint]
tx_hash: Optional[Hash32]
traces: List[dict]


Expand All @@ -56,6 +99,8 @@ class Message:
Items that are used by contract creation or message call.
"""

block_env: BlockEnvironment
tx_env: TransactionEnvironment
caller: Address
target: Union[Bytes0, Address]
current_target: Address
Expand All @@ -81,7 +126,6 @@ class Evm:
memory: bytearray
code: Bytes
gas_left: Uint
env: Environment
valid_jump_destinations: Set[Uint]
logs: Tuple[Log, ...]
refund_counter: int
Expand All @@ -91,7 +135,7 @@ class Evm:
accounts_to_delete: Set[Address]
touched_accounts: Set[Address]
return_data: Bytes
error: Optional[Exception]
error: Optional[EthereumException]
accessed_addresses: Set[Address]
accessed_storage_keys: Set[Tuple[Address, Bytes32]]

Expand All @@ -113,7 +157,7 @@ def incorporate_child_on_success(evm: Evm, child_evm: Evm) -> None:
evm.accounts_to_delete.update(child_evm.accounts_to_delete)
evm.touched_accounts.update(child_evm.touched_accounts)
if account_exists_and_is_empty(
evm.env.state, child_evm.message.current_target
evm.message.block_env.state, child_evm.message.current_target
):
evm.touched_accounts.add(child_evm.message.current_target)
evm.accessed_addresses.update(child_evm.accessed_addresses)
Expand Down Expand Up @@ -142,7 +186,7 @@ def incorporate_child_on_error(evm: Evm, child_evm: Evm) -> None:
evm.touched_accounts.add(RIPEMD160_ADDRESS)
if child_evm.message.current_target == RIPEMD160_ADDRESS:
if account_exists_and_is_empty(
evm.env.state, child_evm.message.current_target
evm.message.block_env.state, child_evm.message.current_target
):
evm.touched_accounts.add(RIPEMD160_ADDRESS)
evm.gas_left += child_evm.gas_left
Loading
Loading