Skip to content

Commit f0d2ca6

Browse files
authored
Merge pull request #1524 from carver/light-client-chain-validation
Support light client header validation during sync
2 parents 0495813 + 74c48d1 commit f0d2ca6

File tree

2 files changed

+37
-43
lines changed

2 files changed

+37
-43
lines changed

eth/chains/base.py

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
from eth_utils import (
3535
encode_hex,
3636
)
37+
from eth_utils.toolz import (
38+
concatv,
39+
sliding_window,
40+
)
3741

3842
from eth.constants import (
3943
BLANK_ROOT_HASH,
@@ -315,13 +319,37 @@ def validate_gaslimit(self, header: BlockHeader) -> None:
315319
def validate_uncles(self, block: BaseBlock) -> None:
316320
raise NotImplementedError("Chain classes must implement this method")
317321

318-
@abstractmethod
322+
@classmethod
319323
def validate_chain(
320-
self,
321-
parent: BlockHeader,
322-
chain: Tuple[BlockHeader, ...],
324+
cls,
325+
root: BlockHeader,
326+
descendants: Tuple[BlockHeader, ...],
323327
seal_check_random_sample_rate: int = 1) -> None:
324-
raise NotImplementedError("Chain classes must implement this method")
328+
"""
329+
Validate that all of the descendents are valid, given that the root header is valid.
330+
331+
By default, check the seal validity (Proof-of-Work on Ethereum 1.x mainnet) of all headers.
332+
This can be expensive. Instead, check a random sample of seals using
333+
seal_check_random_sample_rate.
334+
"""
335+
336+
all_indices = range(len(descendants))
337+
if seal_check_random_sample_rate == 1:
338+
indices_to_check_seal = set(all_indices)
339+
else:
340+
sample_size = len(all_indices) // seal_check_random_sample_rate
341+
indices_to_check_seal = set(random.sample(all_indices, sample_size))
342+
343+
header_pairs = sliding_window(2, concatv([root], descendants))
344+
345+
for index, (parent, child) in enumerate(header_pairs):
346+
if child.parent_hash != parent.hash:
347+
raise ValidationError(
348+
"Invalid header chain; {} has parent {}, but expected {}".format(
349+
child, child.parent_hash, parent.hash))
350+
should_check_seal = index in indices_to_check_seal
351+
vm_class = cls.get_vm_class_for_block_number(child.block_number)
352+
vm_class.validate_header(child, parent, check_seal=should_check_seal)
325353

326354

327355
class Chain(BaseChain):
@@ -820,31 +848,6 @@ def validate_uncles(self, block: BaseBlock) -> None:
820848
uncle_vm_class = self.get_vm_class_for_block_number(uncle.block_number)
821849
uncle_vm_class.validate_uncle(block, uncle, uncle_parent)
822850

823-
def validate_chain(
824-
self,
825-
parent: BlockHeader,
826-
chain: Tuple[BlockHeader, ...],
827-
seal_check_random_sample_rate: int = 1) -> None:
828-
829-
all_indices = list(range(len(chain)))
830-
if seal_check_random_sample_rate == 1:
831-
headers_to_check_seal = set(all_indices)
832-
else:
833-
sample_size = len(all_indices) // seal_check_random_sample_rate
834-
headers_to_check_seal = set(random.sample(all_indices, sample_size))
835-
836-
for i, header in enumerate(chain):
837-
if header.parent_hash != parent.hash:
838-
raise ValidationError(
839-
"Invalid header chain; {} has parent {}, but expected {}".format(
840-
header, header.parent_hash, parent.hash))
841-
vm_class = self.get_vm_class_for_block_number(header.block_number)
842-
if i in headers_to_check_seal:
843-
vm_class.validate_header(header, parent, check_seal=True)
844-
else:
845-
vm_class.validate_header(header, parent, check_seal=False)
846-
parent = header
847-
848851

849852
@to_set
850853
def _extract_uncle_hashes(blocks: Iterable[BaseBlock]) -> Iterable[Hash32]:

trinity/chains/light.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
from trinity.sync.light.service import (
5757
BaseLightPeerChain,
5858
)
59+
from trinity.utils.async_dispatch import (
60+
async_method,
61+
)
5962

6063
from .base import BaseAsyncChain
6164

@@ -250,16 +253,4 @@ def validate_seal(self, header: BlockHeader) -> None:
250253
def validate_uncles(self, block: BaseBlock) -> None:
251254
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
252255

253-
def validate_chain(
254-
self,
255-
parent: BlockHeader,
256-
chain: Tuple[BlockHeader, ...],
257-
seal_check_random_sample_rate: int = 1) -> None:
258-
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
259-
260-
async def coro_validate_chain(
261-
self,
262-
parent: BlockHeader,
263-
chain: Tuple[BlockHeader, ...],
264-
seal_check_random_sample_rate: int = 1) -> None:
265-
raise NotImplementedError("Chain classes must implement " + inspect.stack()[0][3])
256+
coro_validate_chain = async_method('validate_chain')

0 commit comments

Comments
 (0)