Skip to content

Commit ccf0292

Browse files
authored
Merge pull request #1670 from Bhargavasomu/lazy_evaluation_prev_hashes
Lazily evaluate vm.previous_hashes
2 parents e3e4d1f + c5d0bd1 commit ccf0292

File tree

6 files changed

+78
-16
lines changed

6 files changed

+78
-16
lines changed

eth/_utils/generator.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import itertools
2+
from typing import ( # noqa: F401
3+
Generic,
4+
Iterable,
5+
Iterator,
6+
List,
7+
TypeVar,
8+
)
9+
10+
11+
TItem = TypeVar('TItem')
12+
13+
14+
class CachedIterable(Generic[TItem], Iterable[TItem]):
15+
def __init__(self, iterable: Iterable[TItem]) -> None:
16+
self._cached_results = [] # type: List[TItem]
17+
self._iterator = iter(iterable)
18+
19+
def __iter__(self) -> Iterator[TItem]:
20+
return itertools.chain(self._cached_results, self._cache_and_yield())
21+
22+
def _cache_and_yield(self) -> Iterator[TItem]:
23+
for item in self._iterator:
24+
self._cached_results.append(item)
25+
yield item

eth/rlp/headers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import time
22
from typing import (
3+
Iterable,
34
Optional,
4-
Tuple,
55
Union,
66
overload,
77
)
@@ -203,7 +203,7 @@ def from_parent(cls,
203203
return header
204204

205205
def create_execution_context(
206-
self, prev_hashes: Tuple[Hash32, ...]) -> ExecutionContext:
206+
self, prev_hashes: Iterable[Hash32]) -> ExecutionContext:
207207

208208
return ExecutionContext(
209209
coinbase=self.coinbase,

eth/vm/base.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
abstractmethod,
55
)
66
import contextlib
7-
import functools
7+
import itertools
88
import logging
99
from typing import (
1010
Any,
@@ -28,7 +28,6 @@
2828
)
2929

3030
from eth_utils import (
31-
to_tuple,
3231
ValidationError,
3332
)
3433

@@ -258,7 +257,6 @@ def get_nephew_reward(cls) -> int:
258257

259258
@classmethod
260259
@abstractmethod
261-
@to_tuple
262260
def get_prev_hashes(cls,
263261
last_block_hash: Hash32,
264262
chaindb: BaseChainDB) -> Optional[Iterable[Hash32]]:
@@ -691,8 +689,6 @@ def get_block_class(cls) -> Type[BaseBlock]:
691689
return cls.block_class
692690

693691
@classmethod
694-
@functools.lru_cache(maxsize=32)
695-
@to_tuple
696692
def get_prev_hashes(cls,
697693
last_block_hash: Hash32,
698694
chaindb: BaseChainDB) -> Optional[Iterable[Hash32]]:
@@ -709,7 +705,7 @@ def get_prev_hashes(cls,
709705
break
710706

711707
@property
712-
def previous_hashes(self) -> Optional[Tuple[Hash32, ...]]:
708+
def previous_hashes(self) -> Optional[Iterable[Hash32]]:
713709
"""
714710
Convenience API for accessing the previous 255 block hashes.
715711
"""
@@ -919,7 +915,7 @@ def get_state_class(cls) -> Type[BaseState]:
919915
def state_in_temp_block(self) -> Iterator[BaseState]:
920916
header = self.block.header
921917
temp_block = self.generate_block_from_parent_header_and_coinbase(header, header.coinbase)
922-
prev_hashes = (header.hash, ) + self.previous_hashes
918+
prev_hashes = itertools.chain((header.hash, ), self.previous_hashes)
923919

924920
state = self.get_state_class()(
925921
db=self.chaindb.db,

eth/vm/execution_context.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
from typing import Tuple
1+
from typing import (
2+
Iterable,
3+
)
24

35
from eth_typing import (
46
Address,
57
Hash32,
68
)
79

10+
from eth._utils.generator import CachedIterable
11+
812

913
class ExecutionContext:
1014
_coinbase = None
@@ -22,13 +26,13 @@ def __init__(
2226
block_number: int,
2327
difficulty: int,
2428
gas_limit: int,
25-
prev_hashes: Tuple[Hash32, ...]) -> None:
29+
prev_hashes: Iterable[Hash32]) -> None:
2630
self._coinbase = coinbase
2731
self._timestamp = timestamp
2832
self._block_number = block_number
2933
self._difficulty = difficulty
3034
self._gas_limit = gas_limit
31-
self._prev_hashes = prev_hashes
35+
self._prev_hashes = CachedIterable(prev_hashes)
3236

3337
@property
3438
def coinbase(self) -> Address:
@@ -51,5 +55,5 @@ def gas_limit(self) -> int:
5155
return self._gas_limit
5256

5357
@property
54-
def prev_hashes(self) -> Tuple[Hash32, ...]:
58+
def prev_hashes(self) -> Iterable[Hash32]:
5559
return self._prev_hashes

eth/vm/state.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
Address,
2020
Hash32,
2121
)
22+
from eth_utils.toolz import nth
2223

2324
from eth.constants import (
2425
BLANK_ROOT_HASH,
@@ -201,12 +202,16 @@ def get_ancestor_hash(self, block_number: int) -> Hash32:
201202
is_ancestor_depth_out_of_range = (
202203
ancestor_depth >= MAX_PREV_HEADER_DEPTH or
203204
ancestor_depth < 0 or
204-
ancestor_depth >= len(self.execution_context.prev_hashes)
205+
block_number < 0
205206
)
206207
if is_ancestor_depth_out_of_range:
207208
return Hash32(b'')
208-
ancestor_hash = self.execution_context.prev_hashes[ancestor_depth]
209-
return ancestor_hash
209+
210+
try:
211+
return nth(ancestor_depth, self.execution_context.prev_hashes)
212+
except StopIteration:
213+
# Ancestor with specified depth not present
214+
return Hash32(b'')
210215

211216
#
212217
# Computation
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from eth._utils.generator import CachedIterable
2+
3+
from eth_utils.toolz import (
4+
first,
5+
nth,
6+
)
7+
import itertools
8+
9+
10+
def test_cached_generator():
11+
use_once = itertools.count()
12+
repeated_use = CachedIterable(use_once)
13+
14+
for find_val in [1, 0, 10, 5]:
15+
assert find_val == nth(find_val, repeated_use)
16+
17+
18+
def test_laziness():
19+
def crash_after_first_val():
20+
yield 1
21+
raise Exception("oops, iterated past first value")
22+
23+
repeated_use = CachedIterable(crash_after_first_val())
24+
assert first(repeated_use) == 1
25+
assert first(repeated_use) == 1
26+
27+
28+
def test_cached_generator_iterable():
29+
input_vals = [2]
30+
repeated_use = CachedIterable(input_vals)
31+
assert list(repeated_use) == input_vals
32+
assert list(repeated_use) == input_vals

0 commit comments

Comments
 (0)