Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
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
99 changes: 77 additions & 22 deletions eth/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,76 @@ def as_signed_transaction(self, private_key: PrivateKey) -> 'SignedTransactionAP
...


class TransactionBuilderAPI(ABC):
"""
Responsible for creating and encoding transactions.

Most simply, the builder is responsible for some pieces of the encoding for
RLP. In legacy transactions, this happens using rlp.Serializeable. It is
also responsible for initializing the transactions. The two transaction
initializers assume legacy transactions, for now.

Some VMs support multiple distinct transaction types. In that case, the
builder is responsible for dispatching on the different types.
"""
@classmethod
@abstractmethod
def deserialize(cls, encoded: bytes) -> 'SignedTransactionAPI':
"""
Extract a transaction from an encoded RLP object.

This method is used by rlp.decode(..., sedes=TransactionBuilderAPI).
"""
...

@classmethod
@abstractmethod
def serialize(cls, obj: 'SignedTransactionAPI') -> bytes:
"""
Encode a transaction to a series of bytes used by RLP.

In the case of legacy transactions, it will actually be a list of
bytes. That doesn't show up here, because pyrlp doesn't export type
annotations.

This method is used by rlp.encode(obj).
"""
...

@classmethod
@abstractmethod
def create_unsigned_transaction(cls,
*,
nonce: int,
gas_price: int,
gas: int,
to: Address,
value: int,
data: bytes) -> UnsignedTransactionAPI:
"""
Create an unsigned transaction.
"""
...

@classmethod
@abstractmethod
def new_transaction(
cls,
nonce: int,
gas_price: int,
gas: int,
to: Address,
value: int,
data: bytes,
v: int,
r: int,
s: int) -> 'SignedTransactionAPI':
"""
Create a signed transaction.
"""
...


class SignedTransactionAPI(BaseTransactionAPI, TransactionFieldsAPI):

def __init__(self, *args: Any, **kwargs: Any) -> None:
Expand Down Expand Up @@ -345,21 +415,6 @@ def get_message_for_signing(self) -> bytes:
"""
...

@classmethod
@abstractmethod
def create_unsigned_transaction(cls,
*,
nonce: int,
gas_price: int,
gas: int,
to: Address,
value: int,
data: bytes) -> UnsignedTransactionAPI:
"""
Create an unsigned transaction.
"""
...

# We can remove this API and inherit from rlp.Serializable when it becomes typesafe
def as_dict(self) -> Dict[Hashable, Any]:
"""
Expand All @@ -374,7 +429,7 @@ class BlockAPI(ABC):
"""
header: BlockHeaderAPI
transactions: Tuple[SignedTransactionAPI, ...]
transaction_class: Type[SignedTransactionAPI] = None
transaction_builder: Type[TransactionBuilderAPI] = None
uncles: Tuple[BlockHeaderAPI, ...]

@abstractmethod
Expand All @@ -386,9 +441,9 @@ def __init__(self,

@classmethod
@abstractmethod
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
"""
Return the transaction class that is valid for the block.
Return the transaction builder for the block.
"""
...

Expand Down Expand Up @@ -812,7 +867,7 @@ def add_transaction(self,
def get_block_transactions(
self,
block_header: BlockHeaderAPI,
transaction_class: Type[SignedTransactionAPI]) -> Tuple[SignedTransactionAPI, ...]:
transaction_builder: Type[TransactionBuilderAPI]) -> Tuple[SignedTransactionAPI, ...]:
"""
Return an iterable of transactions for the block speficied by the
given block header.
Expand Down Expand Up @@ -851,7 +906,7 @@ def get_transaction_by_index(
self,
block_number: BlockNumber,
transaction_index: int,
transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI:
transaction_builder: Type[TransactionBuilderAPI]) -> SignedTransactionAPI:
"""
Return the transaction at the specified `transaction_index` from the
block specified by `block_number` from the canonical chain.
Expand Down Expand Up @@ -2987,9 +3042,9 @@ def create_unsigned_transaction(cls,

@classmethod
@abstractmethod
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
"""
Return the class that this VM uses for transactions.
Return the class that this VM uses to build and encode transactions.
"""
...

Expand Down
2 changes: 1 addition & 1 deletion eth/chains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def get_canonical_transaction_by_index(self,
return self.chaindb.get_transaction_by_index(
block_number,
index,
VM_class.get_transaction_class(),
VM_class.get_transaction_builder(),
)

def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI:
Expand Down
13 changes: 7 additions & 6 deletions eth/db/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
AtomicDatabaseAPI,
ReceiptAPI,
SignedTransactionAPI,
TransactionBuilderAPI,
)
from eth.constants import (
EMPTY_UNCLE_HASH,
Expand Down Expand Up @@ -308,8 +309,8 @@ def add_transaction(self,
def get_block_transactions(
self,
header: BlockHeaderAPI,
transaction_class: Type[SignedTransactionAPI]) -> Tuple[SignedTransactionAPI, ...]:
return self._get_block_transactions(header.transaction_root, transaction_class)
transaction_builder: Type[TransactionBuilderAPI]) -> Tuple[SignedTransactionAPI, ...]:
return self._get_block_transactions(header.transaction_root, transaction_builder)

def get_block_transaction_hashes(self, block_header: BlockHeaderAPI) -> Tuple[Hash32, ...]:
"""
Expand Down Expand Up @@ -348,7 +349,7 @@ def get_transaction_by_index(
self,
block_number: BlockNumber,
transaction_index: int,
transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI:
transaction_builder: Type[TransactionBuilderAPI]) -> SignedTransactionAPI:
try:
block_header = self.get_canonical_block_header_by_number(block_number)
except HeaderNotFound:
Expand All @@ -357,7 +358,7 @@ def get_transaction_by_index(
encoded_index = rlp.encode(transaction_index)
encoded_transaction = transaction_db[encoded_index]
if encoded_transaction != b'':
return rlp.decode(encoded_transaction, sedes=transaction_class)
return rlp.decode(encoded_transaction, sedes=transaction_builder)
else:
raise TransactionNotFound(
f"No transaction is at index {transaction_index} of block {block_number}"
Expand Down Expand Up @@ -412,12 +413,12 @@ def _get_block_transaction_data(db: DatabaseAPI, transaction_root: Hash32) -> It
def _get_block_transactions(
self,
transaction_root: Hash32,
transaction_class: Type[SignedTransactionAPI]) -> Iterable[SignedTransactionAPI]:
transaction_builder: Type[TransactionBuilderAPI]) -> Iterable[SignedTransactionAPI]:
"""
Memoizable version of `get_block_transactions`
"""
for encoded_transaction in self._get_block_transaction_data(self.db, transaction_root):
yield rlp.decode(encoded_transaction, sedes=transaction_class)
yield rlp.decode(encoded_transaction, sedes=transaction_builder)

@staticmethod
def _remove_transaction_from_canonical_chain(db: DatabaseAPI, transaction_hash: Hash32) -> None:
Expand Down
12 changes: 12 additions & 0 deletions eth/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ class TransactionNotFound(PyEVMError):
pass


class UnrecognizedTransactionType(PyEVMError):
"""
Raised when an encoded transaction is using a first byte that is valid, but
unrecognized. According to EIP 2718, the byte may be in the range [0, 0x7f].
As of the Berlin hard fork, all of those versions are undefined, except for
0x01 in EIP 2930.
"""
@property
def type_int(self) -> int:
return self.args[0]


class ReceiptNotFound(PyEVMError):
"""
Raised when the Receipt with the given receipt index does not exist.
Expand Down
12 changes: 6 additions & 6 deletions eth/rlp/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
)
from eth.abc import (
BlockAPI,
SignedTransactionAPI,
TransactionBuilderAPI,
)


class BaseBlock(Configurable, rlp.Serializable, BlockAPI):
transaction_class: Type[SignedTransactionAPI] = None
transaction_builder: Type[TransactionBuilderAPI] = None

@classmethod
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
if cls.transaction_class is None:
raise AttributeError("Block subclasses must declare a transaction_class")
return cls.transaction_class
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
if cls.transaction_builder is None:
raise AttributeError("Block subclasses must declare a transaction_builder")
return cls.transaction_builder

@property
def is_genesis(self) -> bool:
Expand Down
9 changes: 8 additions & 1 deletion eth/rlp/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
BaseTransactionAPI,
ComputationAPI,
SignedTransactionAPI,
TransactionBuilderAPI,
TransactionFieldsAPI,
UnsignedTransactionAPI,
)
Expand Down Expand Up @@ -60,7 +61,13 @@ def hash(self) -> Hash32:
return keccak(rlp.encode(self))


class BaseTransaction(BaseTransactionFields, BaseTransactionMethods, SignedTransactionAPI): # noqa: E501
class BaseTransaction(BaseTransactionFields, BaseTransactionMethods, SignedTransactionAPI, TransactionBuilderAPI): # noqa: E501
# "Legacy" transactions implemented by BaseTransaction are a combination of
# the transaction codec (TransactionBuilderAPI) *and* the transaction
# object (SignedTransactionAPI). In a multi-transaction-type world, that
# becomes less desirable, and that responsibility splits up. See Berlin
# transactions, for example.

# this is duplicated to make the rlp library happy, otherwise it complains
# about no fields being defined but inheriting from multiple `Serializable`
# bases.
Expand Down
9 changes: 5 additions & 4 deletions eth/vm/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
ReceiptAPI,
SignedTransactionAPI,
StateAPI,
TransactionBuilderAPI,
UnsignedTransactionAPI,
VirtualMachineAPI,
)
Expand Down Expand Up @@ -477,7 +478,7 @@ def previous_hashes(self) -> Optional[Iterable[Hash32]]:
# Transactions
#
def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI:
return self.get_transaction_class()(*args, **kwargs)
return self.get_transaction_builder().new_transaction(*args, **kwargs)

@classmethod
def create_unsigned_transaction(cls,
Expand All @@ -488,7 +489,7 @@ def create_unsigned_transaction(cls,
to: Address,
value: int,
data: bytes) -> UnsignedTransactionAPI:
return cls.get_transaction_class().create_unsigned_transaction(
return cls.get_transaction_builder().create_unsigned_transaction(
nonce=nonce,
gas_price=gas_price,
gas=gas,
Expand All @@ -498,8 +499,8 @@ def create_unsigned_transaction(cls,
)

@classmethod
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
return cls.get_block_class().get_transaction_class()
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
return cls.get_block_class().get_transaction_builder()

#
# Validate
Expand Down
12 changes: 9 additions & 3 deletions eth/vm/forks/berlin/blocks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from typing import Type

from rlp.sedes import (
CountableList,
)

from eth.abc import (
TransactionBuilderAPI,
)
from eth.rlp.headers import (
BlockHeader,
)
Expand All @@ -9,14 +15,14 @@
)

from .transactions import (
BerlinTransaction,
BerlinTransactionBuilder,
)


class BerlinBlock(MuirGlacierBlock):
transaction_class = BerlinTransaction
transaction_builder: Type[TransactionBuilderAPI] = BerlinTransactionBuilder # type: ignore
fields = [
('header', BlockHeader),
('transactions', CountableList(transaction_class)),
('transactions', CountableList(transaction_builder)),
('uncles', CountableList(BlockHeader))
]
Loading