Skip to content

Commit 6bc1db2

Browse files
authored
Move receipt validation to Chain and VM methods (#1256)
* move receipt validation to Chain and VM method * fix light chain * PR feedback
1 parent ca05454 commit 6bc1db2

File tree

8 files changed

+107
-25
lines changed

8 files changed

+107
-25
lines changed

eth/chains/base.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@
7878
BlockHeader,
7979
HeaderParams,
8080
)
81+
from eth.rlp.receipts import (
82+
Receipt,
83+
)
8184
from eth.rlp.transactions import (
8285
BaseTransaction,
8386
BaseUnsignedTransaction,
@@ -277,6 +280,10 @@ def import_block(self,
277280
#
278281
# Validation API
279282
#
283+
@abstractmethod
284+
def validate_receipt(self, receipt: Receipt, at_header: BlockHeader) -> None:
285+
raise NotImplementedError("Chain classes must implement this method")
286+
280287
@abstractmethod
281288
def validate_block(self, block: BaseBlock) -> None:
282289
raise NotImplementedError("Chain classes must implement this method")
@@ -665,6 +672,10 @@ def import_block(self,
665672
#
666673
# Validation API
667674
#
675+
def validate_receipt(self, receipt: Receipt, at_header: BlockHeader) -> None:
676+
VM = self.get_vm_class(at_header)
677+
VM.validate_receipt(receipt)
678+
668679
def validate_block(self, block: BaseBlock) -> None:
669680
"""
670681
Performs validation on a block that is either being mined or imported.
@@ -876,3 +887,8 @@ async def coro_validate_chain(
876887
chain: Tuple[BlockHeader, ...],
877888
seal_check_random_sample_rate: int = 1) -> None:
878889
raise NotImplementedError()
890+
891+
async def coro_validate_receipt(self,
892+
receipt: Receipt,
893+
at_header: BlockHeader) -> None:
894+
raise NotImplementedError()

eth/rlp/receipts.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@
66
CountableList,
77
binary,
88
)
9-
from eth_utils import (
10-
ValidationError,
11-
)
129

1310
from eth_bloom import BloomFilter
1411

1512
from .sedes import (
1613
int256,
17-
int32,
1814
)
1915

2016
from .logs import Log
@@ -48,20 +44,6 @@ def __init__(self,
4844
logs=logs,
4945
)
5046

51-
for log_idx, log in enumerate(self.logs):
52-
if log.address not in self.bloom_filter:
53-
raise ValidationError(
54-
"The address from the log entry at position {0} is not "
55-
"present in the provided bloom filter.".format(log_idx)
56-
)
57-
for topic_idx, topic in enumerate(log.topics):
58-
if int32.serialize(topic) not in self.bloom_filter:
59-
raise ValidationError(
60-
"The topic at position {0} from the log entry at "
61-
"position {1} is not present in the provided bloom "
62-
"filter.".format(topic_idx, log_idx)
63-
)
64-
6547
@property
6648
def bloom_filter(self) -> BloomFilter:
6749
return BloomFilter(self.bloom)

eth/vm/base.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import contextlib
77
import functools
88
import logging
9-
from typing import ( # noqa: F401
10-
List,
9+
from typing import (
1110
Type,
1211
)
1312

@@ -36,13 +35,16 @@
3635
from eth.exceptions import (
3736
HeaderNotFound,
3837
)
39-
from eth.rlp.blocks import ( # noqa: F401
38+
from eth.rlp.blocks import (
4039
BaseBlock,
4140
)
4241
from eth.rlp.headers import (
4342
BlockHeader,
4443
)
45-
from eth.rlp.receipts import Receipt # noqa: F401
44+
from eth.rlp.receipts import Receipt
45+
from eth.rlp.sedes import (
46+
int32,
47+
)
4648
from eth.utils.datatypes import (
4749
Configurable,
4850
)
@@ -189,7 +191,7 @@ def generate_block_from_parent_header_and_coinbase(cls, parent_header, coinbase)
189191

190192
@classmethod
191193
@abstractmethod
192-
def get_block_class(cls) -> Type['BaseBlock']:
194+
def get_block_class(cls) -> Type[BaseBlock]:
193195
raise NotImplementedError("VM classes must implement this method")
194196

195197
@staticmethod
@@ -251,6 +253,11 @@ def get_transaction_class(cls):
251253
#
252254
# Validate
253255
#
256+
@classmethod
257+
@abstractmethod
258+
def validate_receipt(self, receipt: Receipt) -> None:
259+
raise NotImplementedError("VM classes must implement this method")
260+
254261
@abstractmethod
255262
def validate_block(self, block):
256263
raise NotImplementedError("VM classes must implement this method")
@@ -348,6 +355,7 @@ def apply_transaction(self, header, transaction):
348355
self.validate_transaction_against_header(header, transaction)
349356
state_root, computation = self.state.apply_transaction(transaction)
350357
receipt = self.make_receipt(header, transaction, computation, self.state)
358+
self.validate_receipt(receipt)
351359

352360
new_header = header.copy(
353361
bloom=int(BloomFilter(header.bloom) | receipt.bloom),
@@ -606,7 +614,7 @@ def generate_block_from_parent_header_and_coinbase(cls, parent_header, coinbase)
606614
return block
607615

608616
@classmethod
609-
def get_block_class(cls) -> Type['BaseBlock']:
617+
def get_block_class(cls) -> Type[BaseBlock]:
610618
"""
611619
Return the :class:`~eth.rlp.blocks.Block` class that this VM uses for blocks.
612620
"""
@@ -664,6 +672,22 @@ def get_transaction_class(cls):
664672
#
665673
# Validate
666674
#
675+
@classmethod
676+
def validate_receipt(cls, receipt: Receipt) -> None:
677+
for log_idx, log in enumerate(receipt.logs):
678+
if log.address not in receipt.bloom_filter:
679+
raise ValidationError(
680+
"The address from the log entry at position {0} is not "
681+
"present in the provided bloom filter.".format(log_idx)
682+
)
683+
for topic_idx, topic in enumerate(log.topics):
684+
if int32.serialize(topic) not in receipt.bloom_filter:
685+
raise ValidationError(
686+
"The topic at position {0} from the log entry at "
687+
"position {1} is not present in the provided bloom "
688+
"filter.".format(topic_idx, log_idx)
689+
)
690+
667691
def validate_block(self, block):
668692
"""
669693
Validate the the given block.

eth/vm/forks/byzantium/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
Type,
33
)
44

5+
from eth_utils import (
6+
encode_hex,
7+
ValidationError,
8+
)
9+
510
from eth.constants import (
611
MAX_UNCLE_DEPTH,
712
)
813
from eth.rlp.blocks import BaseBlock # noqa: F401
14+
from eth.rlp.receipts import Receipt
915
from eth.validation import (
1016
validate_lte,
1117
)
@@ -38,6 +44,12 @@ def make_byzantium_receipt(base_header, transaction, computation, state):
3844
return frontier_receipt.copy(state_root=status_code)
3945

4046

47+
EIP658_STATUS_CODES = {
48+
EIP658_TRANSACTION_STATUS_CODE_SUCCESS,
49+
EIP658_TRANSACTION_STATUS_CODE_FAILURE,
50+
}
51+
52+
4153
class ByzantiumVM(SpuriousDragonVM):
4254
# fork name
4355
fork = 'byzantium'
@@ -52,6 +64,19 @@ class ByzantiumVM(SpuriousDragonVM):
5264
configure_header = configure_byzantium_header
5365
make_receipt = staticmethod(make_byzantium_receipt)
5466

67+
@classmethod
68+
def validate_receipt(cls, receipt: Receipt) -> None:
69+
super().validate_receipt(receipt)
70+
if receipt.state_root not in EIP658_STATUS_CODES:
71+
raise ValidationError(
72+
"The receipt's `state_root` must be one of [{0}, {1}]. Got: "
73+
"{2}".format(
74+
encode_hex(EIP658_TRANSACTION_STATUS_CODE_SUCCESS),
75+
encode_hex(EIP658_TRANSACTION_STATUS_CODE_FAILURE),
76+
encode_hex(receipt.state_root),
77+
)
78+
)
79+
5580
@staticmethod
5681
def get_block_reward():
5782
return EIP649_BLOCK_REWARD

tests/trinity/core/integration_test_helpers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,17 @@ class FakeAsyncRopstenChain(RopstenChain):
7676
chaindb_class = FakeAsyncChainDB
7777
coro_import_block = coro_import_block
7878
coro_validate_chain = async_passthrough('validate_chain')
79+
coro_validate_receipt = async_passthrough('validate_receipt')
7980

8081

8182
class FakeAsyncMainnetChain(MainnetChain):
8283
chaindb_class = FakeAsyncChainDB
8384
coro_import_block = coro_import_block
8485
coro_validate_chain = async_passthrough('validate_chain')
86+
coro_validate_receipt = async_passthrough('validate_receipt')
8587

8688

8789
class FakeAsyncChain(MiningChain):
8890
coro_import_block = coro_import_block
8991
coro_validate_chain = async_passthrough('validate_chain')
92+
coro_validate_receipt = async_passthrough('validate_receipt')

trinity/chains/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ class DBManager(BaseManager):
260260
class ChainProxy(BaseProxy):
261261
coro_import_block = async_method('import_block')
262262
coro_validate_chain = async_method('validate_chain')
263+
coro_validate_receipt = async_method('validate_receipt')
263264
get_vm_configuration = sync_method('get_vm_configuration')
264265
get_vm_class = sync_method('get_vm_class')
265266
get_vm_class_for_block_number = sync_method('get_vm_class_for_block_number')

trinity/chains/light.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ def mine_block(self, *args: Any, **kwargs: Any) -> BaseBlock:
213213
#
214214
# Validation API
215215
#
216+
def validate_receipt(self, receipt: Receipt, at_header: BlockHeader) -> None:
217+
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
218+
216219
def validate_block(self, block: BaseBlock) -> None:
217220
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
218221

trinity/sync/full/chain.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323

2424
from eth_typing import Hash32
2525

26+
from eth_utils import ValidationError
27+
2628
from eth.constants import (
2729
BLANK_ROOT_HASH, EMPTY_UNCLE_HASH, GENESIS_PARENT_HASH)
2830
from eth.rlp.headers import BlockHeader
2931
from eth.rlp.receipts import Receipt
3032
from eth.rlp.transactions import BaseTransaction
3133

3234
from p2p import protocol
35+
from p2p.p2p_proto import DisconnectReason
3336
from p2p.exceptions import NoEligiblePeers, PeerConnectionLost
3437
from p2p.protocol import Command
3538

@@ -354,7 +357,32 @@ async def _get_receipts(self,
354357
if not receipt_bundles:
355358
return tuple(), batch
356359

357-
receipts, trie_roots_and_data_dicts = zip(*receipt_bundles)
360+
# First validate the receipts.
361+
header_by_root = {
362+
header.receipt_root: header
363+
for header in batch
364+
if not _is_receipts_empty(header)
365+
}
366+
receipts_by_root = {
367+
receipt_root: receipts
368+
for (receipts, (receipt_root, _))
369+
in receipt_bundles
370+
if receipt_root != BLANK_ROOT_HASH
371+
}
372+
for receipt_root, header in header_by_root.items():
373+
for receipt in receipts_by_root[receipt_root]:
374+
try:
375+
await self.chain.coro_validate_receipt(receipt, header)
376+
except ValidationError as err:
377+
self.logger.info(
378+
"Disconnecting from %s: sent invalid receipt: %s",
379+
peer,
380+
err,
381+
)
382+
await peer.disconnect(DisconnectReason.bad_protocol)
383+
return tuple(), batch
384+
385+
_, trie_roots_and_data_dicts = zip(*receipt_bundles)
358386
receipt_roots, trie_data_dicts = zip(*trie_roots_and_data_dicts)
359387
receipt_roots_set = set(receipt_roots)
360388
missing = tuple(

0 commit comments

Comments
 (0)