astreum 0.3.16__py3-none-any.whl → 0.3.46__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.
Files changed (60) hide show
  1. astreum/__init__.py +1 -2
  2. astreum/communication/__init__.py +15 -11
  3. astreum/communication/difficulty.py +39 -0
  4. astreum/communication/disconnect.py +57 -0
  5. astreum/communication/handlers/handshake.py +105 -62
  6. astreum/communication/handlers/object_request.py +179 -149
  7. astreum/communication/handlers/object_response.py +7 -1
  8. astreum/communication/handlers/ping.py +9 -0
  9. astreum/communication/handlers/route_request.py +7 -1
  10. astreum/communication/handlers/route_response.py +7 -1
  11. astreum/communication/incoming_queue.py +96 -0
  12. astreum/communication/message_pow.py +36 -0
  13. astreum/communication/models/peer.py +4 -0
  14. astreum/communication/models/ping.py +27 -6
  15. astreum/communication/models/route.py +4 -0
  16. astreum/communication/{start.py → node.py} +10 -11
  17. astreum/communication/outgoing_queue.py +108 -0
  18. astreum/communication/processors/incoming.py +110 -37
  19. astreum/communication/processors/outgoing.py +35 -2
  20. astreum/communication/processors/peer.py +133 -58
  21. astreum/communication/setup.py +272 -113
  22. astreum/communication/util.py +14 -0
  23. astreum/node.py +99 -92
  24. astreum/storage/actions/get.py +79 -48
  25. astreum/storage/actions/set.py +171 -156
  26. astreum/storage/providers.py +24 -0
  27. astreum/storage/setup.py +23 -22
  28. astreum/utils/config.py +234 -45
  29. astreum/utils/logging.py +1 -1
  30. astreum/{consensus → validation}/__init__.py +0 -4
  31. astreum/validation/constants.py +2 -0
  32. astreum/{consensus → validation}/genesis.py +4 -6
  33. astreum/validation/models/block.py +544 -0
  34. astreum/validation/models/fork.py +511 -0
  35. astreum/{consensus → validation}/models/receipt.py +17 -4
  36. astreum/{consensus → validation}/models/transaction.py +45 -3
  37. astreum/validation/node.py +190 -0
  38. astreum/{consensus → validation}/validator.py +1 -1
  39. astreum/validation/workers/__init__.py +8 -0
  40. astreum/{consensus → validation}/workers/validation.py +360 -333
  41. astreum/verification/__init__.py +4 -0
  42. astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
  43. astreum/verification/node.py +61 -0
  44. astreum/verification/worker.py +183 -0
  45. {astreum-0.3.16.dist-info → astreum-0.3.46.dist-info}/METADATA +43 -9
  46. astreum-0.3.46.dist-info/RECORD +79 -0
  47. astreum/consensus/models/block.py +0 -364
  48. astreum/consensus/models/chain.py +0 -66
  49. astreum/consensus/models/fork.py +0 -100
  50. astreum/consensus/setup.py +0 -83
  51. astreum/consensus/start.py +0 -67
  52. astreum/consensus/workers/__init__.py +0 -9
  53. astreum/consensus/workers/verify.py +0 -90
  54. astreum-0.3.16.dist-info/RECORD +0 -72
  55. /astreum/{consensus → validation}/models/__init__.py +0 -0
  56. /astreum/{consensus → validation}/models/account.py +0 -0
  57. /astreum/{consensus → validation}/models/accounts.py +0 -0
  58. {astreum-0.3.16.dist-info → astreum-0.3.46.dist-info}/WHEEL +0 -0
  59. {astreum-0.3.16.dist-info → astreum-0.3.46.dist-info}/licenses/LICENSE +0 -0
  60. {astreum-0.3.16.dist-info → astreum-0.3.46.dist-info}/top_level.txt +0 -0
@@ -1,150 +1,303 @@
1
- import socket, threading
2
- from queue import Queue
3
- from typing import Tuple, Optional
4
- from cryptography.hazmat.primitives import serialization
5
- from cryptography.hazmat.primitives.asymmetric import ed25519
6
- from cryptography.hazmat.primitives.asymmetric.x25519 import (
7
- X25519PrivateKey,
8
- X25519PublicKey,
9
- )
10
-
11
- from typing import TYPE_CHECKING
12
- if TYPE_CHECKING:
13
- from .. import Node
14
-
15
- from . import Route, Message
1
+ import heapq, os, socket, threading, time
2
+ from pathlib import Path
3
+ from queue import Queue
4
+ from typing import Tuple, Optional, Set
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric import ed25519
7
+ from cryptography.hazmat.primitives.asymmetric.x25519 import (
8
+ X25519PrivateKey,
9
+ X25519PublicKey,
10
+ )
11
+
12
+ from typing import TYPE_CHECKING
13
+ if TYPE_CHECKING:
14
+ from .. import Node
15
+
16
+ from . import Route, Message
16
17
  from .processors.incoming import (
17
18
  process_incoming_messages,
18
19
  populate_incoming_messages,
19
20
  )
20
21
  from .processors.outgoing import process_outgoing_messages
21
22
  from .processors.peer import manage_peer
23
+ from .outgoing_queue import enqueue_outgoing
22
24
  from .util import address_str_to_host_and_port
23
25
  from ..utils.bytes import hex_to_bytes
24
-
25
- def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
26
- """DH key for relaying (always X25519)."""
27
- if hex_key:
28
- return X25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key))
29
- return X25519PrivateKey.generate()
30
-
31
- def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
32
- """Signing key for validation (Ed25519), or None if absent."""
33
- return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
34
- if hex_key else None
35
-
36
- def make_routes(
37
- relay_pk: X25519PublicKey,
38
- val_sk: Optional[ed25519.Ed25519PrivateKey]
39
- ) -> Tuple[Route, Optional[Route]]:
40
- """Peer route (DH pubkey) + optional validation route (ed pubkey)."""
41
- peer_rt = Route(relay_pk)
42
- val_rt = Route(val_sk.public_key()) if val_sk else None
43
- return peer_rt, val_rt
44
-
26
+
27
+ def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
28
+ """DH key for relaying (always X25519)."""
29
+ if hex_key:
30
+ return X25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key))
31
+ return X25519PrivateKey.generate()
32
+
33
+ def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
34
+ """Signing key for validation (Ed25519), or None if absent."""
35
+ return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
36
+ if hex_key else None
37
+
38
+ def make_routes(
39
+ relay_pk: X25519PublicKey,
40
+ val_sk: Optional[ed25519.Ed25519PrivateKey]
41
+ ) -> Tuple[Route, Optional[Route]]:
42
+ """Peer route (DH pubkey) + optional validation route (ed pubkey)."""
43
+ peer_rt = Route(relay_pk)
44
+ val_rt = Route(val_sk.public_key()) if val_sk else None
45
+ return peer_rt, val_rt
46
+
45
47
  def make_maps():
46
48
  """Empty lookup maps: peers and addresses."""
47
49
  return
48
50
 
49
51
 
50
- def communication_setup(node: "Node", config: dict):
51
- node.logger.info("Setting up node communication")
52
- node.use_ipv6 = config.get('use_ipv6', False)
53
- node.peers_lock = threading.RLock()
52
+ def _resolve_default_seed_ips(node: "Node", default_seed: Optional[str]) -> Set[str]:
53
+ if default_seed is None:
54
+ return set()
55
+ try:
56
+ host, port = address_str_to_host_and_port(default_seed)
57
+ except Exception as exc:
58
+ node.logger.warning("Invalid default seed %s: %s", default_seed, exc)
59
+ return set()
60
+ try:
61
+ infos = socket.getaddrinfo(host, port, type=socket.SOCK_DGRAM)
62
+ except Exception as exc:
63
+ node.logger.warning("Failed resolving default seed %s:%s: %s", host, port, exc)
64
+ return set()
65
+ resolved = {info[4][0] for info in infos if info[4]}
66
+ if resolved:
67
+ resolved_list = ", ".join(sorted(resolved))
68
+ node.logger.info("Default seed resolved to %s", resolved_list)
69
+ else:
70
+ node.logger.warning("No IPs resolved for default seed %s:%s", host, port)
71
+ return resolved
54
72
 
55
- # key loading
56
- node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
57
- node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
58
73
 
59
- # derive pubs + routes
60
- node.relay_public_key = node.relay_secret_key.public_key()
61
- node.relay_public_key_bytes = node.relay_public_key.public_bytes(
62
- encoding=serialization.Encoding.Raw,
63
- format=serialization.PublicFormat.Raw,
64
- )
65
- node.validation_public_key = (
66
- node.validation_secret_key.public_key().public_bytes(
67
- encoding=serialization.Encoding.Raw,
68
- format=serialization.PublicFormat.Raw,
74
+ def advertise_cold_storage(node: "Node") -> None:
75
+ """Advertise all cold storage atom ids to the closest known peer."""
76
+ node_logger = node.logger
77
+ cold_path = node.config.get("cold_storage_path")
78
+ if not cold_path:
79
+ node_logger.debug("Cold storage disabled; skipping cold atom advertisement")
80
+ return
81
+ advertise_limit = node.config.get("cold_storage_advertise_limit", 1000)
82
+ if advertise_limit == 0:
83
+ node_logger.debug(
84
+ "Cold storage advertisement disabled; skipping cold atom advertisement"
69
85
  )
70
- if node.validation_secret_key
71
- else None
72
- )
73
- node.peer_route, node.validation_route = make_routes(
74
- node.relay_public_key,
75
- node.validation_secret_key
86
+ return
87
+
88
+ directory = Path(cold_path)
89
+ if not directory.exists():
90
+ node_logger.warning("Cold storage path %s missing; cannot advertise atoms", directory)
91
+ return
92
+ if not directory.is_dir():
93
+ node_logger.warning("Cold storage path %s is not a directory; skipping", directory)
94
+ return
95
+
96
+ advertised = 0
97
+ skipped = 0
98
+ if advertise_limit < 0:
99
+ for file_path in directory.glob("*.bin"):
100
+ if not file_path.is_file():
101
+ skipped += 1
102
+ continue
103
+ atom_hex = file_path.stem
104
+ if len(atom_hex) != 64:
105
+ skipped += 1
106
+ continue
107
+ try:
108
+ atom_id = bytes.fromhex(atom_hex)
109
+ except ValueError:
110
+ skipped += 1
111
+ continue
112
+ if len(atom_id) != 32:
113
+ skipped += 1
114
+ continue
115
+ node._network_set(atom_id)
116
+ advertised += 1
117
+ else:
118
+ heap = []
119
+ for entry in os.scandir(directory):
120
+ name = entry.name
121
+ if not name.endswith(".bin"):
122
+ continue
123
+ if not entry.is_file():
124
+ skipped += 1
125
+ continue
126
+ atom_hex = name[:-4]
127
+ if len(atom_hex) != 64:
128
+ skipped += 1
129
+ continue
130
+ try:
131
+ atom_id = bytes.fromhex(atom_hex)
132
+ except ValueError:
133
+ skipped += 1
134
+ continue
135
+ if len(atom_id) != 32:
136
+ skipped += 1
137
+ continue
138
+ try:
139
+ mtime = entry.stat().st_mtime
140
+ except OSError:
141
+ skipped += 1
142
+ continue
143
+ if len(heap) < advertise_limit:
144
+ heapq.heappush(heap, (mtime, atom_id))
145
+ else:
146
+ if mtime > heap[0][0]:
147
+ heapq.heapreplace(heap, (mtime, atom_id))
148
+ for _, atom_id in sorted(heap, key=lambda item: item[0], reverse=True):
149
+ node._network_set(atom_id)
150
+ advertised += 1
151
+
152
+ node_logger.info(
153
+ "Cold storage advertisement complete (advertised=%s, skipped=%s)",
154
+ advertised,
155
+ skipped,
76
156
  )
77
157
 
78
- # connection state & atom request tracking
79
- node.is_connected = False
80
- node.atom_requests = set()
81
- node.atom_requests_lock = threading.RLock()
82
158
 
83
- # sockets + queues + threads
84
- incoming_port = config.get('incoming_port', 7373)
159
+ def manage_storage_index(node: "Node") -> None:
160
+ interval = node.config.get("storage_index_interval", 0)
161
+ if not interval:
162
+ node.logger.info("Storage index advertiser disabled")
163
+ return
164
+ node.logger.info("Storage index advertiser started (interval=%ss)", interval)
165
+ stop = getattr(node, "communication_stop_event", None)
166
+ while stop is None or not stop.is_set():
167
+ if stop is not None and stop.wait(interval):
168
+ break
169
+ try:
170
+ advertise_cold_storage(node)
171
+ except Exception:
172
+ node.logger.exception("Storage index advertisement failed")
173
+ node.logger.info("Storage index advertiser stopped")
174
+
175
+
176
+ def communication_setup(node: "Node", config: dict):
177
+ node.logger.info("Setting up node communication")
178
+ node.use_ipv6 = config.get('use_ipv6', False)
179
+ node.peers_lock = threading.RLock()
180
+ node.communication_stop_event = threading.Event()
181
+ default_seed = config.get("default_seed")
182
+ node.default_seed_ips = _resolve_default_seed_ips(node, default_seed)
183
+
184
+ # key loading
185
+ node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
186
+ node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
187
+
188
+ # derive pubs + routes
189
+ node.relay_public_key = node.relay_secret_key.public_key()
190
+ node.relay_public_key_bytes = node.relay_public_key.public_bytes(
191
+ encoding=serialization.Encoding.Raw,
192
+ format=serialization.PublicFormat.Raw,
193
+ )
194
+ node.validation_public_key = (
195
+ node.validation_secret_key.public_key().public_bytes(
196
+ encoding=serialization.Encoding.Raw,
197
+ format=serialization.PublicFormat.Raw,
198
+ )
199
+ if node.validation_secret_key
200
+ else None
201
+ )
202
+ node.peer_route, node.validation_route = make_routes(
203
+ node.relay_public_key,
204
+ node.validation_secret_key
205
+ )
206
+
207
+ # connection state & atom request tracking
208
+ node.is_connected = False
209
+ node.atom_requests = set()
210
+ node.atom_requests_lock = threading.RLock()
211
+
212
+ # sockets + queues + threads
213
+ incoming_port = config.get("incoming_port")
214
+ if incoming_port is None:
215
+ raise ValueError("incoming_port must be configured before communication setup")
85
216
  fam = socket.AF_INET6 if node.use_ipv6 else socket.AF_INET
86
217
  node.incoming_socket = socket.socket(fam, socket.SOCK_DGRAM)
87
218
  if node.use_ipv6:
88
219
  node.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
89
- node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port or 0))
90
- node.incoming_port = node.incoming_socket.getsockname()[1]
220
+ node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port))
221
+ bound_port = node.incoming_socket.getsockname()[1]
222
+ if incoming_port != 0 and bound_port != incoming_port:
223
+ raise OSError(
224
+ f"incoming_port mismatch: requested {incoming_port}, got {bound_port}"
225
+ )
226
+ node.config["incoming_port"] = bound_port if incoming_port == 0 else incoming_port
227
+ node.incoming_socket.settimeout(0.5)
91
228
  node.logger.info(
92
229
  "Incoming UDP socket bound to %s:%s",
93
230
  "::" if node.use_ipv6 else "0.0.0.0",
94
- node.incoming_port,
231
+ node.config["incoming_port"],
95
232
  )
96
233
  node.incoming_queue = Queue()
234
+ node.incoming_queue_size = 0
235
+ node.incoming_queue_size_lock = threading.RLock()
236
+ node.incoming_queue_size_limit = node.config.get("incoming_queue_size_limit", 0)
237
+ node.incoming_queue_timeout = node.config.get("incoming_queue_timeout", 0)
97
238
  node.incoming_populate_thread = threading.Thread(
98
239
  target=populate_incoming_messages,
99
240
  args=(node,),
100
241
  daemon=True,
101
242
  )
102
- node.incoming_process_thread = threading.Thread(
103
- target=process_incoming_messages,
104
- args=(node,),
105
- daemon=True,
106
- )
107
- node.incoming_populate_thread.start()
108
- node.incoming_process_thread.start()
109
-
110
- node.outgoing_socket = socket.socket(
111
- socket.AF_INET6 if node.use_ipv6 else socket.AF_INET,
112
- socket.SOCK_DGRAM,
113
- )
243
+ node.incoming_process_thread = threading.Thread(
244
+ target=process_incoming_messages,
245
+ args=(node,),
246
+ daemon=True,
247
+ )
248
+ node.incoming_populate_thread.start()
249
+ node.incoming_process_thread.start()
250
+
251
+ node.outgoing_socket = socket.socket(
252
+ socket.AF_INET6 if node.use_ipv6 else socket.AF_INET,
253
+ socket.SOCK_DGRAM,
254
+ )
255
+ node.outgoing_socket.settimeout(0.5)
114
256
  node.outgoing_queue = Queue()
257
+ node.outgoing_queue_size = 0
258
+ node.outgoing_queue_size_lock = threading.RLock()
259
+ node.outgoing_queue_size_limit = node.config.get("outgoing_queue_size_limit", 0)
260
+ node.outgoing_queue_timeout = node.config.get("outgoing_queue_timeout", 0)
115
261
 
116
262
  node.outgoing_thread = threading.Thread(
117
263
  target=process_outgoing_messages,
118
264
  args=(node,),
119
265
  daemon=True,
120
266
  )
121
- node.outgoing_thread.start()
122
-
123
- # other workers & maps
124
- # track atom requests we initiated; guarded by atom_requests_lock on the node
125
- node.peer_manager_thread = threading.Thread(
126
- target=manage_peer,
127
- args=(node,),
128
- daemon=True
267
+ node.outgoing_thread.start()
268
+
269
+ # other workers & maps
270
+ # track atom requests we initiated; guarded by atom_requests_lock on the node
271
+ node.peer_manager_thread = threading.Thread(
272
+ target=manage_peer,
273
+ args=(node,),
274
+ daemon=True
275
+ )
276
+ node.peer_manager_thread.start()
277
+
278
+ with node.peers_lock:
279
+ node.peers = {} # Dict[bytes,Peer]
280
+
281
+ latest_block_hex = config.get("latest_block_hash")
282
+ if latest_block_hex:
283
+ try:
284
+ node.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
285
+ except Exception as exc:
286
+ node.logger.warning("Invalid latest_block_hash in config: %s", exc)
287
+ node.latest_block_hash = None
288
+ else:
289
+ node.latest_block_hash = None
290
+
291
+ node.logger.info(
292
+ "Communication ready (incoming_port=%s, outgoing_socket_initialized=%s, bootstrap_count=%s)",
293
+ node.config["incoming_port"],
294
+ node.outgoing_socket is not None,
295
+ len(node.bootstrap_peers),
129
296
  )
130
- node.peer_manager_thread.start()
131
-
132
- with node.peers_lock:
133
- node.peers = {} # Dict[bytes,Peer]
134
-
135
- latest_block_hex = config.get("latest_block_hash")
136
- if latest_block_hex:
137
- try:
138
- node.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
139
- except Exception as exc:
140
- node.logger.warning("Invalid latest_block_hash in config: %s", exc)
141
- node.latest_block_hash = None
142
- else:
143
- node.latest_block_hash = None
297
+ node.is_connected = True
144
298
 
145
- # bootstrap pings
146
- bootstrap_peers = config.get('bootstrap', [])
147
- for addr in bootstrap_peers:
299
+ # bootstrap pings (requires connected state for enqueue_outgoing)
300
+ for addr in node.bootstrap_peers:
148
301
  try:
149
302
  host, port = address_str_to_host_and_port(addr) # type: ignore[arg-type]
150
303
  except Exception as exc:
@@ -156,13 +309,19 @@ def communication_setup(node: "Node", config: dict):
156
309
  sender=node.relay_public_key,
157
310
  content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
158
311
  )
159
- node.outgoing_queue.put((handshake_message.to_bytes(), (host, port)))
312
+ enqueue_outgoing(
313
+ node,
314
+ (host, port),
315
+ message=handshake_message,
316
+ difficulty=1,
317
+ )
160
318
  node.logger.info("Sent bootstrap handshake to %s:%s", host, port)
161
-
162
- node.logger.info(
163
- "Communication ready (incoming_port=%s, outgoing_socket_initialized=%s, bootstrap_count=%s)",
164
- node.incoming_port,
165
- node.outgoing_socket is not None,
166
- len(bootstrap_peers),
319
+ if node.bootstrap_peers:
320
+ node._bootstrap_last_attempt = time.time()
321
+ advertise_cold_storage(node)
322
+ node.storage_index_thread = threading.Thread(
323
+ target=manage_storage_index,
324
+ args=(node,),
325
+ daemon=True,
167
326
  )
168
- node.is_connected = True
327
+ node.storage_index_thread.start()
@@ -1,5 +1,9 @@
1
1
  from typing import Tuple
2
2
 
3
+ from typing import TYPE_CHECKING
4
+ if TYPE_CHECKING:
5
+ from .. import Node
6
+
3
7
 
4
8
  def address_str_to_host_and_port(address: str) -> Tuple[str, int]:
5
9
  """Parse `host:port` (or `[ipv6]:port`) into a tuple."""
@@ -47,3 +51,13 @@ def xor_distance(a: bytes, b: bytes) -> int:
47
51
  if len(a) != len(b):
48
52
  raise ValueError("xor distance requires operands of equal length")
49
53
  return int.from_bytes(bytes(x ^ y for x, y in zip(a, b)), "big", signed=False)
54
+
55
+
56
+ def get_bootstrap_peers(node: "Node") -> list[str]:
57
+ default_seed = node.config["default_seed"]
58
+ additional_seeds = node.config["additional_seeds"]
59
+ peers = []
60
+ if default_seed is not None:
61
+ peers.append(default_seed)
62
+ peers.extend(additional_seeds)
63
+ return peers
astreum/node.py CHANGED
@@ -1,95 +1,102 @@
1
- """Core Astreum Node implementation."""
2
-
3
- from __future__ import annotations
4
-
5
- import threading
6
- import uuid
7
- from typing import Dict
8
-
9
- from astreum.communication.start import connect_to_network_and_verify
10
- from astreum.communication.models.peer import (
11
- add_peer as peers_add_peer,
12
- replace_peer as peers_replace_peer,
13
- get_peer as peers_get_peer,
14
- remove_peer as peers_remove_peer,
15
- )
16
- from astreum.consensus.start import process_blocks_and_transactions
17
- from astreum.machine import Expr, high_eval, low_eval, script_eval
18
- from astreum.machine.models.environment import Env, env_get, env_set
19
- from astreum.machine.models.expression import get_expr_list_from_storage
20
- from astreum.storage.models.atom import get_atom_list_from_storage
21
- from astreum.storage.actions.get import (
22
- _hot_storage_get,
23
- _cold_storage_get,
24
- _network_get,
25
- storage_get,
26
- local_get,
27
- )
28
- from astreum.storage.actions.set import (
29
- _hot_storage_set,
30
- _cold_storage_set,
31
- _network_set,
32
- )
33
- from astreum.storage.requests import add_atom_req, has_atom_req, pop_atom_req
34
- from astreum.storage.setup import storage_setup
35
- from astreum.utils.config import config_setup
36
- from astreum.utils.logging import logging_setup
37
-
38
-
1
+ """Core Astreum Node implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ import uuid
7
+ from typing import Dict
8
+
9
+ from astreum.communication.node import connect_node
10
+ from astreum.communication.util import get_bootstrap_peers
11
+ from astreum.communication.disconnect import disconnect_node
12
+ from astreum.communication.models.peer import (
13
+ add_peer as peers_add_peer,
14
+ replace_peer as peers_replace_peer,
15
+ get_peer as peers_get_peer,
16
+ remove_peer as peers_remove_peer,
17
+ )
18
+ from astreum.validation.node import validate_blockchain
19
+ from astreum.verification.node import verify_blockchain
20
+ from astreum.machine import Expr, high_eval, low_eval, script_eval
21
+ from astreum.machine.models.environment import Env, env_get, env_set
22
+ from astreum.machine.models.expression import get_expr_list_from_storage
23
+ from astreum.storage.models.atom import get_atom_list_from_storage
24
+ from astreum.storage.actions.get import (
25
+ _hot_storage_get,
26
+ _cold_storage_get,
27
+ _network_get,
28
+ storage_get,
29
+ local_get,
30
+ )
31
+ from astreum.storage.actions.set import (
32
+ _hot_storage_set,
33
+ _cold_storage_set,
34
+ _network_set,
35
+ )
36
+ from astreum.storage.requests import add_atom_req, has_atom_req, pop_atom_req
37
+ from astreum.storage.setup import storage_setup
38
+ from astreum.utils.config import config_setup
39
+ from astreum.utils.logging import logging_setup
40
+
41
+
39
42
  class Node:
40
43
  def __init__(self, config: dict = {}):
41
44
  self.config = config_setup(config=config)
42
-
43
- self.logger = logging_setup(self.config)
44
-
45
- self.logger.info("Starting Astreum Node")
46
-
47
- # Chain Configuration
48
- self.logger.info(f"Chain configured as: {self.config["chain"]} ({self.config["chain_id"]})")
49
-
50
- # Storage Setup
51
- storage_setup(self, config=self.config)
52
-
53
- # Machine Setup
54
- self.environments: Dict[uuid.UUID, Env] = {}
55
- self.machine_environments_lock = threading.RLock()
56
- self.is_connected = False
57
- self.latest_block_hash = None
58
- self.latest_block = None
59
- self.nonce_time_ms = 0 # rolling measurement of last nonce search duration
60
-
61
- connect = connect_to_network_and_verify
62
- validate = process_blocks_and_transactions
63
-
64
- low_eval = low_eval
65
- high_eval = high_eval
66
- script_eval = script_eval
67
-
68
- env_get = env_get
69
- env_set = env_set
70
-
71
- # Storage
72
- ## Get
73
- _hot_storage_get = _hot_storage_get
74
- _cold_storage_get = _cold_storage_get
75
- _network_get = _network_get
76
-
77
- ## Set
78
- _hot_storage_set = _hot_storage_set
79
- _cold_storage_set = _cold_storage_set
80
- _network_set = _network_set
81
-
82
- storage_get = storage_get
83
- local_get = local_get
84
-
85
- get_expr_list_from_storage = get_expr_list_from_storage
86
- get_atom_list_from_storage = get_atom_list_from_storage
87
-
88
- add_atom_req = add_atom_req
89
- has_atom_req = has_atom_req
90
- pop_atom_req = pop_atom_req
91
-
92
- add_peer = peers_add_peer
93
- replace_peer = peers_replace_peer
94
- get_peer = peers_get_peer
95
- remove_peer = peers_remove_peer
45
+ self.bootstrap_peers = get_bootstrap_peers(self)
46
+
47
+ self.logger = logging_setup(self.config)
48
+
49
+ self.logger.info("Starting Astreum Node")
50
+
51
+ # Chain Configuration
52
+ self.logger.info(f"Chain configured as: {self.config["chain"]} ({self.config["chain_id"]})")
53
+
54
+ # Storage Setup
55
+ storage_setup(self, config=self.config)
56
+
57
+ # Machine Setup
58
+ self.environments: Dict[uuid.UUID, Env] = {}
59
+ self.machine_environments_lock = threading.RLock()
60
+ self.is_connected = False
61
+ self.latest_block_hash = None
62
+ self.latest_block = None
63
+
64
+ connect = connect_node
65
+ disconnect = disconnect_node
66
+
67
+ verify = verify_blockchain
68
+
69
+ validate = validate_blockchain
70
+
71
+ low_eval = low_eval
72
+ high_eval = high_eval
73
+ script_eval = script_eval
74
+
75
+ env_get = env_get
76
+ env_set = env_set
77
+
78
+ # Storage
79
+ ## Get
80
+ _hot_storage_get = _hot_storage_get
81
+ _cold_storage_get = _cold_storage_get
82
+ _network_get = _network_get
83
+
84
+ ## Set
85
+ _hot_storage_set = _hot_storage_set
86
+ _cold_storage_set = _cold_storage_set
87
+ _network_set = _network_set
88
+
89
+ storage_get = storage_get
90
+ local_get = local_get
91
+
92
+ get_expr_list_from_storage = get_expr_list_from_storage
93
+ get_atom_list_from_storage = get_atom_list_from_storage
94
+
95
+ add_atom_req = add_atom_req
96
+ has_atom_req = has_atom_req
97
+ pop_atom_req = pop_atom_req
98
+
99
+ add_peer = peers_add_peer
100
+ replace_peer = peers_replace_peer
101
+ get_peer = peers_get_peer
102
+ remove_peer = peers_remove_peer