Skip to content

Commit 7ac5ef2

Browse files
authored
Merge pull request #1978 from carver/eip-2718-typed-txns
Add typed transactions - EIP 2718 - Berlin
2 parents 995e10c + 8ac8e95 commit 7ac5ef2

File tree

22 files changed

+310
-89
lines changed

22 files changed

+310
-89
lines changed

eth/abc.py

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,76 @@ def as_signed_transaction(self, private_key: PrivateKey) -> 'SignedTransactionAP
267267
...
268268

269269

270+
class TransactionBuilderAPI(ABC):
271+
"""
272+
Responsible for creating and encoding transactions.
273+
274+
Most simply, the builder is responsible for some pieces of the encoding for
275+
RLP. In legacy transactions, this happens using rlp.Serializeable. It is
276+
also responsible for initializing the transactions. The two transaction
277+
initializers assume legacy transactions, for now.
278+
279+
Some VMs support multiple distinct transaction types. In that case, the
280+
builder is responsible for dispatching on the different types.
281+
"""
282+
@classmethod
283+
@abstractmethod
284+
def deserialize(cls, encoded: bytes) -> 'SignedTransactionAPI':
285+
"""
286+
Extract a transaction from an encoded RLP object.
287+
288+
This method is used by rlp.decode(..., sedes=TransactionBuilderAPI).
289+
"""
290+
...
291+
292+
@classmethod
293+
@abstractmethod
294+
def serialize(cls, obj: 'SignedTransactionAPI') -> bytes:
295+
"""
296+
Encode a transaction to a series of bytes used by RLP.
297+
298+
In the case of legacy transactions, it will actually be a list of
299+
bytes. That doesn't show up here, because pyrlp doesn't export type
300+
annotations.
301+
302+
This method is used by rlp.encode(obj).
303+
"""
304+
...
305+
306+
@classmethod
307+
@abstractmethod
308+
def create_unsigned_transaction(cls,
309+
*,
310+
nonce: int,
311+
gas_price: int,
312+
gas: int,
313+
to: Address,
314+
value: int,
315+
data: bytes) -> UnsignedTransactionAPI:
316+
"""
317+
Create an unsigned transaction.
318+
"""
319+
...
320+
321+
@classmethod
322+
@abstractmethod
323+
def new_transaction(
324+
cls,
325+
nonce: int,
326+
gas_price: int,
327+
gas: int,
328+
to: Address,
329+
value: int,
330+
data: bytes,
331+
v: int,
332+
r: int,
333+
s: int) -> 'SignedTransactionAPI':
334+
"""
335+
Create a signed transaction.
336+
"""
337+
...
338+
339+
270340
class SignedTransactionAPI(BaseTransactionAPI, TransactionFieldsAPI):
271341

272342
def __init__(self, *args: Any, **kwargs: Any) -> None:
@@ -345,21 +415,6 @@ def get_message_for_signing(self) -> bytes:
345415
"""
346416
...
347417

348-
@classmethod
349-
@abstractmethod
350-
def create_unsigned_transaction(cls,
351-
*,
352-
nonce: int,
353-
gas_price: int,
354-
gas: int,
355-
to: Address,
356-
value: int,
357-
data: bytes) -> UnsignedTransactionAPI:
358-
"""
359-
Create an unsigned transaction.
360-
"""
361-
...
362-
363418
# We can remove this API and inherit from rlp.Serializable when it becomes typesafe
364419
def as_dict(self) -> Dict[Hashable, Any]:
365420
"""
@@ -374,7 +429,7 @@ class BlockAPI(ABC):
374429
"""
375430
header: BlockHeaderAPI
376431
transactions: Tuple[SignedTransactionAPI, ...]
377-
transaction_class: Type[SignedTransactionAPI] = None
432+
transaction_builder: Type[TransactionBuilderAPI] = None
378433
uncles: Tuple[BlockHeaderAPI, ...]
379434

380435
@abstractmethod
@@ -386,9 +441,9 @@ def __init__(self,
386441

387442
@classmethod
388443
@abstractmethod
389-
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
444+
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
390445
"""
391-
Return the transaction class that is valid for the block.
446+
Return the transaction builder for the block.
392447
"""
393448
...
394449

@@ -812,7 +867,7 @@ def add_transaction(self,
812867
def get_block_transactions(
813868
self,
814869
block_header: BlockHeaderAPI,
815-
transaction_class: Type[SignedTransactionAPI]) -> Tuple[SignedTransactionAPI, ...]:
870+
transaction_builder: Type[TransactionBuilderAPI]) -> Tuple[SignedTransactionAPI, ...]:
816871
"""
817872
Return an iterable of transactions for the block speficied by the
818873
given block header.
@@ -851,7 +906,7 @@ def get_transaction_by_index(
851906
self,
852907
block_number: BlockNumber,
853908
transaction_index: int,
854-
transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI:
909+
transaction_builder: Type[TransactionBuilderAPI]) -> SignedTransactionAPI:
855910
"""
856911
Return the transaction at the specified `transaction_index` from the
857912
block specified by `block_number` from the canonical chain.
@@ -2987,9 +3042,9 @@ def create_unsigned_transaction(cls,
29873042

29883043
@classmethod
29893044
@abstractmethod
2990-
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
3045+
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
29913046
"""
2992-
Return the class that this VM uses for transactions.
3047+
Return the class that this VM uses to build and encode transactions.
29933048
"""
29943049
...
29953050

eth/chains/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def get_canonical_transaction_by_index(self,
391391
return self.chaindb.get_transaction_by_index(
392392
block_number,
393393
index,
394-
VM_class.get_transaction_class(),
394+
VM_class.get_transaction_builder(),
395395
)
396396

397397
def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI:

eth/db/chain.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
AtomicDatabaseAPI,
3131
ReceiptAPI,
3232
SignedTransactionAPI,
33+
TransactionBuilderAPI,
3334
)
3435
from eth.constants import (
3536
EMPTY_UNCLE_HASH,
@@ -308,8 +309,8 @@ def add_transaction(self,
308309
def get_block_transactions(
309310
self,
310311
header: BlockHeaderAPI,
311-
transaction_class: Type[SignedTransactionAPI]) -> Tuple[SignedTransactionAPI, ...]:
312-
return self._get_block_transactions(header.transaction_root, transaction_class)
312+
transaction_builder: Type[TransactionBuilderAPI]) -> Tuple[SignedTransactionAPI, ...]:
313+
return self._get_block_transactions(header.transaction_root, transaction_builder)
313314

314315
def get_block_transaction_hashes(self, block_header: BlockHeaderAPI) -> Tuple[Hash32, ...]:
315316
"""
@@ -348,7 +349,7 @@ def get_transaction_by_index(
348349
self,
349350
block_number: BlockNumber,
350351
transaction_index: int,
351-
transaction_class: Type[SignedTransactionAPI]) -> SignedTransactionAPI:
352+
transaction_builder: Type[TransactionBuilderAPI]) -> SignedTransactionAPI:
352353
try:
353354
block_header = self.get_canonical_block_header_by_number(block_number)
354355
except HeaderNotFound:
@@ -357,7 +358,7 @@ def get_transaction_by_index(
357358
encoded_index = rlp.encode(transaction_index)
358359
encoded_transaction = transaction_db[encoded_index]
359360
if encoded_transaction != b'':
360-
return rlp.decode(encoded_transaction, sedes=transaction_class)
361+
return rlp.decode(encoded_transaction, sedes=transaction_builder)
361362
else:
362363
raise TransactionNotFound(
363364
f"No transaction is at index {transaction_index} of block {block_number}"
@@ -412,12 +413,12 @@ def _get_block_transaction_data(db: DatabaseAPI, transaction_root: Hash32) -> It
412413
def _get_block_transactions(
413414
self,
414415
transaction_root: Hash32,
415-
transaction_class: Type[SignedTransactionAPI]) -> Iterable[SignedTransactionAPI]:
416+
transaction_builder: Type[TransactionBuilderAPI]) -> Iterable[SignedTransactionAPI]:
416417
"""
417418
Memoizable version of `get_block_transactions`
418419
"""
419420
for encoded_transaction in self._get_block_transaction_data(self.db, transaction_root):
420-
yield rlp.decode(encoded_transaction, sedes=transaction_class)
421+
yield rlp.decode(encoded_transaction, sedes=transaction_builder)
421422

422423
@staticmethod
423424
def _remove_transaction_from_canonical_chain(db: DatabaseAPI, transaction_hash: Hash32) -> None:

eth/exceptions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ class TransactionNotFound(PyEVMError):
4646
pass
4747

4848

49+
class UnrecognizedTransactionType(PyEVMError):
50+
"""
51+
Raised when an encoded transaction is using a first byte that is valid, but
52+
unrecognized. According to EIP 2718, the byte may be in the range [0, 0x7f].
53+
As of the Berlin hard fork, all of those versions are undefined, except for
54+
0x01 in EIP 2930.
55+
"""
56+
@property
57+
def type_int(self) -> int:
58+
return self.args[0]
59+
60+
4961
class ReceiptNotFound(PyEVMError):
5062
"""
5163
Raised when the Receipt with the given receipt index does not exist.

eth/rlp/blocks.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@
1212
)
1313
from eth.abc import (
1414
BlockAPI,
15-
SignedTransactionAPI,
15+
TransactionBuilderAPI,
1616
)
1717

1818

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

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

2828
@property
2929
def is_genesis(self) -> bool:

eth/rlp/transactions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
BaseTransactionAPI,
2020
ComputationAPI,
2121
SignedTransactionAPI,
22+
TransactionBuilderAPI,
2223
TransactionFieldsAPI,
2324
UnsignedTransactionAPI,
2425
)
@@ -60,7 +61,13 @@ def hash(self) -> Hash32:
6061
return keccak(rlp.encode(self))
6162

6263

63-
class BaseTransaction(BaseTransactionFields, BaseTransactionMethods, SignedTransactionAPI): # noqa: E501
64+
class BaseTransaction(BaseTransactionFields, BaseTransactionMethods, SignedTransactionAPI, TransactionBuilderAPI): # noqa: E501
65+
# "Legacy" transactions implemented by BaseTransaction are a combination of
66+
# the transaction codec (TransactionBuilderAPI) *and* the transaction
67+
# object (SignedTransactionAPI). In a multi-transaction-type world, that
68+
# becomes less desirable, and that responsibility splits up. See Berlin
69+
# transactions, for example.
70+
6471
# this is duplicated to make the rlp library happy, otherwise it complains
6572
# about no fields being defined but inheriting from multiple `Serializable`
6673
# bases.

eth/vm/base.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
ReceiptAPI,
4040
SignedTransactionAPI,
4141
StateAPI,
42+
TransactionBuilderAPI,
4243
UnsignedTransactionAPI,
4344
VirtualMachineAPI,
4445
)
@@ -477,7 +478,7 @@ def previous_hashes(self) -> Optional[Iterable[Hash32]]:
477478
# Transactions
478479
#
479480
def create_transaction(self, *args: Any, **kwargs: Any) -> SignedTransactionAPI:
480-
return self.get_transaction_class()(*args, **kwargs)
481+
return self.get_transaction_builder().new_transaction(*args, **kwargs)
481482

482483
@classmethod
483484
def create_unsigned_transaction(cls,
@@ -488,7 +489,7 @@ def create_unsigned_transaction(cls,
488489
to: Address,
489490
value: int,
490491
data: bytes) -> UnsignedTransactionAPI:
491-
return cls.get_transaction_class().create_unsigned_transaction(
492+
return cls.get_transaction_builder().create_unsigned_transaction(
492493
nonce=nonce,
493494
gas_price=gas_price,
494495
gas=gas,
@@ -498,8 +499,8 @@ def create_unsigned_transaction(cls,
498499
)
499500

500501
@classmethod
501-
def get_transaction_class(cls) -> Type[SignedTransactionAPI]:
502-
return cls.get_block_class().get_transaction_class()
502+
def get_transaction_builder(cls) -> Type[TransactionBuilderAPI]:
503+
return cls.get_block_class().get_transaction_builder()
503504

504505
#
505506
# Validate

eth/vm/forks/berlin/blocks.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
from typing import Type
2+
13
from rlp.sedes import (
24
CountableList,
35
)
6+
7+
from eth.abc import (
8+
TransactionBuilderAPI,
9+
)
410
from eth.rlp.headers import (
511
BlockHeader,
612
)
@@ -9,14 +15,14 @@
915
)
1016

1117
from .transactions import (
12-
BerlinTransaction,
18+
BerlinTransactionBuilder,
1319
)
1420

1521

1622
class BerlinBlock(MuirGlacierBlock):
17-
transaction_class = BerlinTransaction
23+
transaction_builder: Type[TransactionBuilderAPI] = BerlinTransactionBuilder # type: ignore
1824
fields = [
1925
('header', BlockHeader),
20-
('transactions', CountableList(transaction_class)),
26+
('transactions', CountableList(transaction_builder)),
2127
('uncles', CountableList(BlockHeader))
2228
]

0 commit comments

Comments
 (0)