py-geth 4.3.0__py3-none-any.whl → 5.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
geth/mixins.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from __future__ import (
2
- absolute_import,
2
+ annotations,
3
3
  )
4
4
 
5
5
  import datetime
@@ -7,7 +7,15 @@ import logging
7
7
  import os
8
8
  import queue
9
9
  import time
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Any,
13
+ Callable,
14
+ )
10
15
 
16
+ from geth.exceptions import (
17
+ PyGethAttributeError,
18
+ )
11
19
  from geth.utils.filesystem import (
12
20
  ensure_path_exists,
13
21
  )
@@ -19,13 +27,13 @@ from geth.utils.timeout import (
19
27
  )
20
28
 
21
29
 
22
- def construct_logger_file_path(prefix, suffix):
30
+ def construct_logger_file_path(prefix: str, suffix: str) -> str:
23
31
  ensure_path_exists("./logs")
24
32
  timestamp = datetime.datetime.now().strftime(f"{prefix}-%Y%m%d-%H%M%S-{suffix}.log")
25
33
  return os.path.join("logs", timestamp)
26
34
 
27
35
 
28
- def _get_file_logger(name, filename):
36
+ def _get_file_logger(name: str, filename: str) -> logging.Logger:
29
37
  # create logger with 'spam_application'
30
38
  logger = logging.getLogger(name)
31
39
  logger.setLevel(logging.DEBUG)
@@ -46,8 +54,15 @@ def _get_file_logger(name, filename):
46
54
  return logger
47
55
 
48
56
 
49
- class JoinableQueue(queue.Queue):
50
- def __iter__(self):
57
+ # only needed until we drop support for python 3.8
58
+ if TYPE_CHECKING:
59
+ BaseQueue = queue.Queue[Any]
60
+ else:
61
+ BaseQueue = queue.Queue
62
+
63
+
64
+ class JoinableQueue(BaseQueue):
65
+ def __iter__(self) -> Any:
51
66
  while True:
52
67
  item = self.get()
53
68
 
@@ -65,62 +80,70 @@ class JoinableQueue(queue.Queue):
65
80
 
66
81
  yield item
67
82
 
68
- def join(self, timeout=None):
83
+ def join(self, timeout: int | None = None) -> None:
69
84
  with Timeout(timeout) as _timeout:
70
85
  while not self.empty():
71
86
  time.sleep(0)
72
87
  _timeout.check()
73
88
 
74
89
 
75
- class InterceptedStreamsMixin(object):
90
+ class InterceptedStreamsMixin:
76
91
  """
77
92
  Mixin class for GethProcess instances that feeds all of the stdout and
78
93
  stderr lines into some set of provided callback functions.
79
94
  """
80
95
 
81
- stdout_callbacks = None
82
- stderr_callbacks = None
96
+ stdout_callbacks: list[Callable[[str], None]]
97
+ stderr_callbacks: list[Callable[[str], None]]
83
98
 
84
- def __init__(self, *args, **kwargs):
85
- super(InterceptedStreamsMixin, self).__init__(*args, **kwargs)
99
+ def __init__(self, *args: Any, **kwargs: Any):
100
+ super().__init__(*args, **kwargs)
86
101
  self.stdout_callbacks = []
87
102
  self.stdout_queue = JoinableQueue()
88
103
 
89
104
  self.stderr_callbacks = []
90
105
  self.stderr_queue = JoinableQueue()
91
106
 
92
- def register_stdout_callback(self, callback_fn):
107
+ def register_stdout_callback(self, callback_fn: Callable[[str], None]) -> None:
93
108
  self.stdout_callbacks.append(callback_fn)
94
109
 
95
- def register_stderr_callback(self, callback_fn):
110
+ def register_stderr_callback(self, callback_fn: Callable[[str], None]) -> None:
96
111
  self.stderr_callbacks.append(callback_fn)
97
112
 
98
- def produce_stdout_queue(self):
99
- for line in iter(self.proc.stdout.readline, b""):
100
- self.stdout_queue.put(line)
101
- time.sleep(0)
113
+ def produce_stdout_queue(self) -> None:
114
+ if hasattr(self, "proc"):
115
+ for line in iter(self.proc.stdout.readline, b""):
116
+ self.stdout_queue.put(line)
117
+ time.sleep(0)
118
+ else:
119
+ raise PyGethAttributeError("No `proc` attribute found")
102
120
 
103
- def produce_stderr_queue(self):
104
- for line in iter(self.proc.stderr.readline, b""):
105
- self.stderr_queue.put(line)
106
- time.sleep(0)
121
+ def produce_stderr_queue(self) -> None:
122
+ if hasattr(self, "proc"):
123
+ for line in iter(self.proc.stderr.readline, b""):
124
+ self.stderr_queue.put(line)
125
+ time.sleep(0)
126
+ else:
127
+ raise PyGethAttributeError("No `proc` attribute found")
107
128
 
108
- def consume_stdout_queue(self):
129
+ def consume_stdout_queue(self) -> None:
109
130
  for line in self.stdout_queue:
110
131
  for fn in self.stdout_callbacks:
111
132
  fn(line.strip())
112
133
  self.stdout_queue.task_done()
113
134
  time.sleep(0)
114
135
 
115
- def consume_stderr_queue(self):
136
+ def consume_stderr_queue(self) -> None:
116
137
  for line in self.stderr_queue:
117
138
  for fn in self.stderr_callbacks:
118
139
  fn(line.strip())
119
140
  self.stderr_queue.task_done()
120
141
  time.sleep(0)
121
142
 
122
- def start(self):
123
- super(InterceptedStreamsMixin, self).start()
143
+ def start(self) -> None:
144
+ # type ignored because this is a mixin but will always have a start method
145
+ # because it will be mixed with BaseGethProcess
146
+ super().start() # type: ignore[misc]
124
147
 
125
148
  spawn(self.produce_stdout_queue)
126
149
  spawn(self.produce_stderr_queue)
@@ -128,8 +151,10 @@ class InterceptedStreamsMixin(object):
128
151
  spawn(self.consume_stdout_queue)
129
152
  spawn(self.consume_stderr_queue)
130
153
 
131
- def stop(self):
132
- super(InterceptedStreamsMixin, self).stop()
154
+ def stop(self) -> None:
155
+ # type ignored because this is a mixin but will always have a stop method
156
+ # because it will be mixed with BaseGethProcess
157
+ super().stop() # type: ignore[misc]
133
158
 
134
159
  try:
135
160
  self.stdout_queue.put(StopIteration)
@@ -145,7 +170,7 @@ class InterceptedStreamsMixin(object):
145
170
 
146
171
 
147
172
  class LoggingMixin(InterceptedStreamsMixin):
148
- def __init__(self, *args, **kwargs):
173
+ def __init__(self, *args: Any, **kwargs: Any):
149
174
  stdout_logfile_path = kwargs.pop(
150
175
  "stdout_logfile_path",
151
176
  construct_logger_file_path("geth", "stdout"),
@@ -155,7 +180,7 @@ class LoggingMixin(InterceptedStreamsMixin):
155
180
  construct_logger_file_path("geth", "stderr"),
156
181
  )
157
182
 
158
- super(LoggingMixin, self).__init__(*args, **kwargs)
183
+ super().__init__(*args, **kwargs)
159
184
 
160
185
  stdout_logger = _get_file_logger("geth-stdout", stdout_logfile_path)
161
186
  stderr_logger = _get_file_logger("geth-stderr", stderr_logfile_path)
geth/process.py CHANGED
@@ -1,21 +1,34 @@
1
+ from __future__ import (
2
+ annotations,
3
+ )
4
+
5
+ from abc import (
6
+ ABC,
7
+ abstractmethod,
8
+ )
9
+ import json
1
10
  import logging
2
11
  import os
3
- import socket
4
12
  import subprocess
5
13
  import time
6
- import warnings
7
-
8
- try:
9
- from urllib.request import (
10
- URLError,
11
- urlopen,
12
- )
13
- except ImportError:
14
- from urllib2 import (
15
- urlopen,
16
- URLError,
17
- )
14
+ from types import (
15
+ TracebackType,
16
+ )
17
+ from typing import (
18
+ cast,
19
+ )
20
+ from urllib.error import (
21
+ URLError,
22
+ )
23
+ from urllib.request import (
24
+ urlopen,
25
+ )
26
+
27
+ import semantic_version
18
28
 
29
+ from geth import (
30
+ get_geth_version,
31
+ )
19
32
  from geth.accounts import (
20
33
  ensure_account_exists,
21
34
  get_accounts,
@@ -25,13 +38,18 @@ from geth.chain import (
25
38
  get_default_base_dir,
26
39
  get_genesis_file_path,
27
40
  get_live_data_dir,
28
- get_ropsten_data_dir,
41
+ get_sepolia_data_dir,
29
42
  initialize_chain,
30
43
  is_live_chain,
31
- is_ropsten_chain,
44
+ is_sepolia_chain,
45
+ )
46
+ from geth.exceptions import (
47
+ PyGethNotImplementedError,
48
+ PyGethValueError,
32
49
  )
33
- from geth.utils.dag import (
34
- is_dag_generated,
50
+ from geth.types import (
51
+ GethKwargsTypedDict,
52
+ IO_Any,
35
53
  )
36
54
  from geth.utils.networking import (
37
55
  get_ipc_socket,
@@ -42,24 +60,32 @@ from geth.utils.proc import (
42
60
  from geth.utils.timeout import (
43
61
  Timeout,
44
62
  )
63
+ from geth.utils.validation import (
64
+ GenesisDataTypedDict,
65
+ validate_genesis_data,
66
+ validate_geth_kwargs,
67
+ )
45
68
  from geth.wrapper import (
46
69
  construct_popen_command,
47
70
  construct_test_chain_kwargs,
48
71
  )
49
72
 
50
73
  logger = logging.getLogger(__name__)
74
+ with open(os.path.join(os.path.dirname(__file__), "genesis.json")) as genesis_file:
75
+ GENESIS_JSON = json.load(genesis_file)
51
76
 
52
77
 
53
- class BaseGethProcess(object):
78
+ class BaseGethProcess(ABC):
54
79
  _proc = None
55
80
 
56
81
  def __init__(
57
82
  self,
58
- geth_kwargs,
59
- stdin=subprocess.PIPE,
60
- stdout=subprocess.PIPE,
61
- stderr=subprocess.PIPE,
83
+ geth_kwargs: GethKwargsTypedDict,
84
+ stdin: IO_Any = subprocess.PIPE,
85
+ stdout: IO_Any = subprocess.PIPE,
86
+ stderr: IO_Any = subprocess.PIPE,
62
87
  ):
88
+ validate_geth_kwargs(geth_kwargs)
63
89
  self.geth_kwargs = geth_kwargs
64
90
  self.command = construct_popen_command(**geth_kwargs)
65
91
  self.stdin = stdin
@@ -68,12 +94,12 @@ class BaseGethProcess(object):
68
94
 
69
95
  is_running = False
70
96
 
71
- def start(self):
97
+ def start(self) -> None:
72
98
  if self.is_running:
73
- raise ValueError("Already running")
99
+ raise PyGethValueError("Already running")
74
100
  self.is_running = True
75
101
 
76
- logger.info("Launching geth: %s", " ".join(self.command))
102
+ logger.info(f"Launching geth: {' '.join(self.command)}")
77
103
  self.proc = subprocess.Popen(
78
104
  self.command,
79
105
  stdin=self.stdin,
@@ -81,48 +107,61 @@ class BaseGethProcess(object):
81
107
  stderr=self.stderr,
82
108
  )
83
109
 
84
- def __enter__(self):
110
+ def __enter__(self) -> BaseGethProcess:
85
111
  self.start()
86
112
  return self
87
113
 
88
- def stop(self):
114
+ def stop(self) -> None:
89
115
  if not self.is_running:
90
- raise ValueError("Not running")
116
+ raise PyGethValueError("Not running")
91
117
 
92
118
  if self.proc.poll() is None:
93
119
  kill_proc(self.proc)
94
120
 
95
121
  self.is_running = False
96
122
 
97
- def __exit__(self, *exc_info):
123
+ def __exit__(
124
+ self,
125
+ exc_type: type[BaseException] | None,
126
+ exc_value: BaseException | None,
127
+ tb: TracebackType | None,
128
+ ) -> None:
98
129
  self.stop()
99
130
 
100
131
  @property
101
- def is_alive(self):
132
+ @abstractmethod
133
+ def data_dir(self) -> str:
134
+ raise PyGethNotImplementedError("Must be implemented by subclasses.")
135
+
136
+ @property
137
+ def is_alive(self) -> bool:
102
138
  return self.is_running and self.proc.poll() is None
103
139
 
104
140
  @property
105
- def is_stopped(self):
141
+ def is_stopped(self) -> bool:
106
142
  return self.proc is not None and self.proc.poll() is not None
107
143
 
108
144
  @property
109
- def accounts(self):
145
+ def accounts(self) -> tuple[str, ...]:
110
146
  return get_accounts(**self.geth_kwargs)
111
147
 
112
148
  @property
113
- def rpc_enabled(self):
114
- return self.geth_kwargs.get("rpc_enabled", False)
149
+ def rpc_enabled(self) -> bool:
150
+ _rpc_enabled = self.geth_kwargs.get("rpc_enabled", False)
151
+ return cast(bool, _rpc_enabled)
115
152
 
116
153
  @property
117
- def rpc_host(self):
118
- return self.geth_kwargs.get("rpc_host", "127.0.0.1")
154
+ def rpc_host(self) -> str:
155
+ _rpc_host = self.geth_kwargs.get("rpc_host", "127.0.0.1")
156
+ return cast(str, _rpc_host)
119
157
 
120
158
  @property
121
- def rpc_port(self):
122
- return self.geth_kwargs.get("rpc_port", "8545")
159
+ def rpc_port(self) -> str:
160
+ _rpc_port = self.geth_kwargs.get("rpc_port", "8545")
161
+ return cast(str, _rpc_port)
123
162
 
124
163
  @property
125
- def is_rpc_ready(self):
164
+ def is_rpc_ready(self) -> bool:
126
165
  try:
127
166
  urlopen(f"http://{self.rpc_host}:{self.rpc_port}")
128
167
  except URLError:
@@ -130,9 +169,9 @@ class BaseGethProcess(object):
130
169
  else:
131
170
  return True
132
171
 
133
- def wait_for_rpc(self, timeout=0):
172
+ def wait_for_rpc(self, timeout: int = 0) -> None:
134
173
  if not self.rpc_enabled:
135
- raise ValueError("RPC interface is not enabled")
174
+ raise PyGethValueError("RPC interface is not enabled")
136
175
 
137
176
  with Timeout(timeout) as _timeout:
138
177
  while True:
@@ -142,36 +181,33 @@ class BaseGethProcess(object):
142
181
  _timeout.check()
143
182
 
144
183
  @property
145
- def ipc_enabled(self):
184
+ def ipc_enabled(self) -> bool:
146
185
  return not self.geth_kwargs.get("ipc_disable", None)
147
186
 
148
187
  @property
149
- def ipc_path(self):
150
- return self.geth_kwargs.get(
151
- "ipc_path",
152
- os.path.abspath(
153
- os.path.expanduser(
154
- os.path.join(
155
- self.data_dir,
156
- "geth.ipc",
157
- )
188
+ def ipc_path(self) -> str:
189
+ return self.geth_kwargs.get("ipc_path") or os.path.abspath(
190
+ os.path.expanduser(
191
+ os.path.join(
192
+ self.data_dir,
193
+ "geth.ipc",
158
194
  )
159
- ),
195
+ )
160
196
  )
161
197
 
162
198
  @property
163
- def is_ipc_ready(self):
199
+ def is_ipc_ready(self) -> bool:
164
200
  try:
165
201
  with get_ipc_socket(self.ipc_path):
166
202
  pass
167
- except socket.error:
203
+ except OSError:
168
204
  return False
169
205
  else:
170
206
  return True
171
207
 
172
- def wait_for_ipc(self, timeout=0):
208
+ def wait_for_ipc(self, timeout: int = 0) -> None:
173
209
  if not self.ipc_enabled:
174
- raise ValueError("IPC interface is not enabled")
210
+ raise PyGethValueError("IPC interface is not enabled")
175
211
 
176
212
  with Timeout(timeout) as _timeout:
177
213
  while True:
@@ -180,78 +216,49 @@ class BaseGethProcess(object):
180
216
  time.sleep(0.1)
181
217
  _timeout.check()
182
218
 
183
- @property
184
- def is_dag_generated(self):
185
- return is_dag_generated()
186
-
187
- @property
188
- def is_mining(self):
189
- return self.geth_kwargs.get("mine", False)
190
-
191
- def wait_for_dag(self, timeout=0):
192
- if not self.is_mining and not self.geth_kwargs.get("autodag", False):
193
- raise ValueError("Geth not configured to generate DAG")
194
-
195
- with Timeout(timeout) as _timeout:
196
- while True:
197
- if self.is_dag_generated:
198
- break
199
- time.sleep(0.1)
200
- _timeout.check()
201
-
202
219
 
203
220
  class MainnetGethProcess(BaseGethProcess):
204
- def __init__(self, geth_kwargs=None):
221
+ def __init__(self, geth_kwargs: GethKwargsTypedDict | None = None):
205
222
  if geth_kwargs is None:
206
223
  geth_kwargs = {}
207
224
 
208
225
  if "data_dir" in geth_kwargs:
209
- raise ValueError("You cannot specify `data_dir` for a MainnetGethProcess")
226
+ raise PyGethValueError(
227
+ "You cannot specify `data_dir` for a MainnetGethProcess"
228
+ )
210
229
 
211
- super(MainnetGethProcess, self).__init__(geth_kwargs)
230
+ super().__init__(geth_kwargs)
212
231
 
213
232
  @property
214
- def data_dir(self):
233
+ def data_dir(self) -> str:
215
234
  return get_live_data_dir()
216
235
 
217
236
 
218
- class LiveGethProcess(MainnetGethProcess):
219
- def __init__(self, *args, **kwargs):
220
- warnings.warn(
221
- DeprecationWarning(
222
- "The `LiveGethProcess` has been renamed to `MainnetGethProcess`. "
223
- "The `LiveGethProcess` alias will be removed in subsequent releases"
224
- ),
225
- stacklevel=2,
226
- )
227
- super(LiveGethProcess, self).__init__(*args, **kwargs)
228
-
229
-
230
- class RopstenGethProcess(BaseGethProcess):
231
- def __init__(self, geth_kwargs=None):
237
+ class SepoliaGethProcess(BaseGethProcess):
238
+ def __init__(self, geth_kwargs: GethKwargsTypedDict | None = None):
232
239
  if geth_kwargs is None:
233
240
  geth_kwargs = {}
234
241
 
235
242
  if "data_dir" in geth_kwargs:
236
- raise ValueError(
243
+ raise PyGethValueError(
237
244
  f"You cannot specify `data_dir` for a {type(self).__name__}"
238
245
  )
239
246
  if "network_id" in geth_kwargs:
240
- raise ValueError(
247
+ raise PyGethValueError(
241
248
  f"You cannot specify `network_id` for a {type(self).__name__}"
242
249
  )
243
250
 
244
- geth_kwargs["network_id"] = "3"
245
- geth_kwargs["data_dir"] = get_ropsten_data_dir()
251
+ geth_kwargs["network_id"] = "11155111"
252
+ geth_kwargs["data_dir"] = get_sepolia_data_dir()
246
253
 
247
- super(RopstenGethProcess, self).__init__(geth_kwargs)
254
+ super().__init__(geth_kwargs)
248
255
 
249
256
  @property
250
- def data_dir(self):
251
- return get_ropsten_data_dir()
257
+ def data_dir(self) -> str:
258
+ return get_sepolia_data_dir()
252
259
 
253
260
 
254
- class TestnetGethProcess(RopstenGethProcess):
261
+ class TestnetGethProcess(SepoliaGethProcess):
255
262
  """
256
263
  Alias for whatever the current primary testnet chain is.
257
264
  """
@@ -259,53 +266,70 @@ class TestnetGethProcess(RopstenGethProcess):
259
266
 
260
267
  class DevGethProcess(BaseGethProcess):
261
268
  """
262
- A local private chain for development.
269
+ Geth developer mode process for testing purposes.
263
270
  """
264
271
 
265
- def __init__(self, chain_name, base_dir=None, overrides=None, genesis_data=None):
272
+ _data_dir: str
273
+
274
+ def __init__(
275
+ self,
276
+ chain_name: str,
277
+ base_dir: str | None = None,
278
+ overrides: GethKwargsTypedDict | None = None,
279
+ genesis_data: GenesisDataTypedDict | None = None,
280
+ ):
266
281
  if overrides is None:
267
282
  overrides = {}
268
283
 
269
284
  if genesis_data is None:
270
- genesis_data = {}
285
+ genesis_data = GenesisDataTypedDict(**GENESIS_JSON)
286
+
287
+ validate_genesis_data(genesis_data)
271
288
 
272
289
  if "data_dir" in overrides:
273
- raise ValueError("You cannot specify `data_dir` for a DevGethProcess")
290
+ raise PyGethValueError("You cannot specify `data_dir` for a DevGethProcess")
274
291
 
275
292
  if base_dir is None:
276
293
  base_dir = get_default_base_dir()
277
294
 
278
- self.data_dir = get_chain_data_dir(base_dir, chain_name)
279
- geth_kwargs = construct_test_chain_kwargs(data_dir=self.data_dir, **overrides)
295
+ self._data_dir = get_chain_data_dir(base_dir, chain_name)
296
+ overrides["data_dir"] = self.data_dir
297
+ geth_kwargs = construct_test_chain_kwargs(**overrides)
298
+ validate_geth_kwargs(geth_kwargs)
280
299
 
281
300
  # ensure that an account is present
282
301
  coinbase = ensure_account_exists(**geth_kwargs)
283
302
 
284
303
  # ensure that the chain is initialized
285
304
  genesis_file_path = get_genesis_file_path(self.data_dir)
286
-
287
305
  needs_init = all(
288
306
  (
289
307
  not os.path.exists(genesis_file_path),
290
308
  not is_live_chain(self.data_dir),
291
- not is_ropsten_chain(self.data_dir),
309
+ not is_sepolia_chain(self.data_dir),
292
310
  )
293
311
  )
294
-
295
312
  if needs_init:
296
- genesis_data.setdefault(
297
- "alloc",
298
- dict(
299
- [
300
- (
301
- coinbase,
302
- {
303
- "balance": "1000000000000000000000000000000" # 1 billion ether # noqa: E501
304
- },
305
- ),
306
- ]
307
- ),
313
+ genesis_data["coinbase"] = coinbase
314
+ genesis_data.setdefault("alloc", {}).setdefault(
315
+ coinbase, {"balance": "1000000000000000000000000000000"}
308
316
  )
309
- initialize_chain(genesis_data, **geth_kwargs)
310
317
 
311
- super(DevGethProcess, self).__init__(geth_kwargs)
318
+ modify_genesis_based_on_geth_version(genesis_data)
319
+ initialize_chain(genesis_data, self.data_dir)
320
+
321
+ super().__init__(geth_kwargs)
322
+
323
+ @property
324
+ def data_dir(self) -> str:
325
+ return self._data_dir
326
+
327
+
328
+ def modify_genesis_based_on_geth_version(genesis_data: GenesisDataTypedDict) -> None:
329
+ geth_version = get_geth_version()
330
+ if geth_version <= semantic_version.Version("1.14.0"):
331
+ # geth <= v1.14.0 needs negative `terminalTotalDifficulty` to load EVM
332
+ # instructions correctly: https://github.com/ethereum/go-ethereum/pull/29579
333
+ if "config" not in genesis_data:
334
+ genesis_data["config"] = {}
335
+ genesis_data["config"]["terminalTotalDifficulty"] = -1
geth/py.typed ADDED
File without changes