astreum 0.2.61__py3-none-any.whl → 0.3.1__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 (75) hide show
  1. astreum/__init__.py +16 -7
  2. astreum/{_communication → communication}/__init__.py +3 -3
  3. astreum/communication/handlers/handshake.py +83 -0
  4. astreum/communication/handlers/ping.py +48 -0
  5. astreum/communication/handlers/storage_request.py +81 -0
  6. astreum/communication/models/__init__.py +0 -0
  7. astreum/{_communication → communication/models}/route.py +5 -5
  8. astreum/communication/setup.py +205 -0
  9. astreum/communication/start.py +38 -0
  10. astreum/consensus/__init__.py +20 -0
  11. astreum/consensus/genesis.py +66 -0
  12. astreum/consensus/models/__init__.py +0 -0
  13. astreum/consensus/models/account.py +84 -0
  14. astreum/consensus/models/accounts.py +72 -0
  15. astreum/consensus/models/block.py +364 -0
  16. astreum/{_consensus → consensus/models}/chain.py +7 -7
  17. astreum/{_consensus → consensus/models}/fork.py +8 -8
  18. astreum/consensus/models/receipt.py +98 -0
  19. astreum/{_consensus → consensus/models}/transaction.py +76 -78
  20. astreum/{_consensus → consensus}/setup.py +18 -50
  21. astreum/consensus/start.py +68 -0
  22. astreum/consensus/validator.py +95 -0
  23. astreum/{_consensus → consensus}/workers/discovery.py +20 -1
  24. astreum/consensus/workers/validation.py +291 -0
  25. astreum/{_consensus → consensus}/workers/verify.py +31 -2
  26. astreum/machine/__init__.py +20 -0
  27. astreum/machine/evaluations/__init__.py +0 -0
  28. astreum/{_lispeum → machine/evaluations}/high_evaluation.py +16 -15
  29. astreum/machine/evaluations/low_evaluation.py +281 -0
  30. astreum/machine/evaluations/script_evaluation.py +27 -0
  31. astreum/machine/models/__init__.py +0 -0
  32. astreum/machine/models/environment.py +31 -0
  33. astreum/{_lispeum → machine/models}/expression.py +36 -8
  34. astreum/machine/tokenizer.py +90 -0
  35. astreum/node.py +73 -781
  36. astreum/storage/__init__.py +7 -0
  37. astreum/storage/actions/get.py +69 -0
  38. astreum/storage/actions/set.py +132 -0
  39. astreum/{_storage → storage/models}/atom.py +55 -57
  40. astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
  41. astreum/storage/setup.py +44 -15
  42. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/METADATA +25 -24
  43. astreum-0.3.1.dist-info/RECORD +62 -0
  44. astreum/_communication/setup.py +0 -322
  45. astreum/_consensus/__init__.py +0 -20
  46. astreum/_consensus/account.py +0 -95
  47. astreum/_consensus/accounts.py +0 -38
  48. astreum/_consensus/block.py +0 -311
  49. astreum/_consensus/genesis.py +0 -72
  50. astreum/_consensus/receipt.py +0 -136
  51. astreum/_consensus/workers/validation.py +0 -125
  52. astreum/_lispeum/__init__.py +0 -16
  53. astreum/_lispeum/environment.py +0 -13
  54. astreum/_lispeum/low_evaluation.py +0 -123
  55. astreum/_lispeum/tokenizer.py +0 -22
  56. astreum/_node.py +0 -198
  57. astreum/_storage/__init__.py +0 -7
  58. astreum/_storage/setup.py +0 -35
  59. astreum/format.py +0 -75
  60. astreum/models/block.py +0 -441
  61. astreum/models/merkle.py +0 -205
  62. astreum/models/patricia.py +0 -393
  63. astreum/storage/object.py +0 -68
  64. astreum-0.2.61.dist-info/RECORD +0 -57
  65. /astreum/{models → communication/handlers}/__init__.py +0 -0
  66. /astreum/{_communication → communication/models}/message.py +0 -0
  67. /astreum/{_communication → communication/models}/peer.py +0 -0
  68. /astreum/{_communication → communication/models}/ping.py +0 -0
  69. /astreum/{_communication → communication}/util.py +0 -0
  70. /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
  71. /astreum/{_lispeum → machine/models}/meter.py +0 -0
  72. /astreum/{_lispeum → machine}/parser.py +0 -0
  73. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
  74. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
  75. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
astreum/node.py CHANGED
@@ -1,781 +1,73 @@
1
- # DEPRECATED
2
-
3
- # import socket
4
- # import threading
5
- # import time
6
- # from queue import Queue
7
- # from pathlib import Path
8
- # from typing import Tuple, Dict, Union, Optional, List
9
- # from datetime import datetime, timedelta, timezone
10
- # import uuid
11
-
12
- # from astreum.lispeum.environment import Env
13
- # from astreum.lispeum.expression import Expr
14
- # from astreum.relay.peer import Peer
15
- # from astreum.relay.route import Route
16
- # from astreum.relay.setup import load_ed25519, load_x25519, make_routes, setup_outgoing, setup_udp
17
- # from astreum.storage.object import ObjectRequest, ObjectRequestType, ObjectResponse, ObjectResponseType
18
- # from astreum.storage.setup import storage_setup
19
-
20
- # from .models.transaction import Transaction
21
- # from .format import encode, decode
22
- # from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
23
- # from cryptography.hazmat.primitives import serialization
24
- # from .crypto import ed25519, x25519
25
- # import blake3
26
- # import struct
27
- # from .models.message import Message, MessageTopic
28
-
29
- # def encode_ip_address(host: str, port: int) -> bytes:
30
- # ip_bytes = socket.inet_pton(socket.AF_INET6 if ':' in host else socket.AF_INET, host)
31
- # port_bytes = struct.pack("!H", port)
32
- # return ip_bytes + port_bytes
33
-
34
- # def decode_ip_address(data: bytes) -> tuple[str, int]:
35
- # if len(data) == 6:
36
- # ip = socket.inet_ntop(socket.AF_INET, data[:4])
37
- # port = struct.unpack("!H", data[4:6])[0]
38
- # elif len(data) == 18:
39
- # ip = socket.inet_ntop(socket.AF_INET6, data[:16])
40
- # port = struct.unpack("!H", data[16:18])[0]
41
- # else:
42
- # raise ValueError("Invalid address byte format")
43
- # return ip, port
44
-
45
- # class Node:
46
- # def __init__(self, config: dict = {}):
47
- # self._machine_setup()
48
- # machine_only = bool(config.get('machine-only', True))
49
- # if not machine_only:
50
- # (
51
- # self.storage_path,
52
- # self.memory_storage,
53
- # self.storage_get_relay_timeout,
54
- # self.storage_index
55
- # ) = storage_setup(config)
56
-
57
- # self._relay_setup(config=config)
58
- # self._validation_setup(config=config)
59
-
60
- # def _validation_setup(self, config: dict):
61
- # if True:
62
- # self.validator_transactions: Dict[bytes, Transaction] = {}
63
- # # validator thread
64
- # pass
65
-
66
- # def _create_block(self):
67
- # pass
68
-
69
- # def _relay_setup(self, config: dict):
70
- # self.use_ipv6 = config.get('use_ipv6', False)
71
-
72
- # # key loading
73
- # self.relay_secret_key = load_x25519(config.get('relay_secret_key'))
74
- # self.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
75
-
76
- # # derive pubs + routes
77
- # self.relay_public_key = self.relay_secret_key.public_key()
78
- # self.peer_route, self.validation_route = make_routes(
79
- # self.relay_public_key,
80
- # self.validation_secret_key
81
- # )
82
-
83
- # # sockets + queues + threads
84
- # (self.incoming_socket,
85
- # self.incoming_port,
86
- # self.incoming_queue,
87
- # self.incoming_populate_thread,
88
- # self.incoming_process_thread
89
- # ) = setup_udp(config.get('incoming_port', 7373), self.use_ipv6)
90
-
91
- # (self.outgoing_socket,
92
- # self.outgoing_queue,
93
- # self.outgoing_thread
94
- # ) = setup_outgoing(self.use_ipv6)
95
-
96
- # # other workers & maps
97
- # self.object_request_queue = Queue()
98
- # self.peer_manager_thread = threading.Thread(
99
- # target=self._relay_peer_manager,
100
- # daemon=True
101
- # )
102
- # self.peer_manager_thread.start()
103
-
104
- # self.peers, self.addresses = {}, {} # peers: Dict[X25519PublicKey,Peer], addresses: Dict[(str,int),X25519PublicKey]
105
-
106
- # # bootstrap pings
107
- # for addr in config.get('bootstrap', []):
108
- # self._send_ping(addr)
109
-
110
- # def _local_object_get(self, data_hash: bytes) -> Optional[bytes]:
111
- # if self.memory_storage is not None:
112
- # return self.memory_storage.get(data_hash)
113
-
114
- # file_path = self.storage_path / data_hash.hex()
115
- # if file_path.exists():
116
- # return file_path.read_bytes()
117
- # return None
118
-
119
- # def _local_object_put(self, hash: bytes, data: bytes) -> bool:
120
- # if self.memory_storage is not None:
121
- # self.memory_storage[hash] = data
122
- # return True
123
-
124
- # file_path = self.storage_path / hash.hex()
125
- # file_path.write_bytes(data)
126
- # return True
127
-
128
- # def _object_get(self, hash: bytes) -> Optional[bytes]:
129
- # local_data = self._local_object_get(hash)
130
- # if local_data:
131
- # return local_data
132
-
133
- # # find the nearest peer route node to the hash and send an object request
134
- # closest_peer = self._get_closest_local_peer(hash)
135
- # if closest_peer:
136
- # object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
137
- # self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
138
-
139
- # # wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
140
- # start_time = time.time()
141
- # while time.time() - start_time < self.storage_get_relay_timeout:
142
- # # Check if the object has been stored locally
143
- # local_data = self._local_object_get(hash)
144
- # if local_data:
145
- # return local_data
146
- # # Sleep briefly to avoid hammering the local storage
147
- # time.sleep(0.1)
148
-
149
- # # If we reach here, the object was not received within the timeout period
150
- # return None
151
-
152
- # # RELAY METHODS
153
- # def _relay_incoming_queue_populating(self):
154
- # while True:
155
- # try:
156
- # data, addr = self.incoming_socket.recvfrom(4096)
157
- # self.incoming_queue.put((data, addr))
158
- # except Exception as e:
159
- # print(f"Error in _relay_populate_incoming_queue: {e}")
160
-
161
- # def _relay_incoming_queue_processing(self):
162
- # while True:
163
- # try:
164
- # data, addr = self.incoming_queue.get()
165
- # message = Message.from_bytes(data)
166
- # match message.topic:
167
- # case MessageTopic.PING:
168
- # peer_pub_key = self.addresses.get(addr)
169
- # if peer_pub_key in self.peers:
170
- # self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
171
- # continue
172
-
173
- # is_validator_flag = decode(message.body)
174
-
175
- # if peer_pub_key not in self.peers:
176
- # self._send_ping(addr)
177
-
178
- # peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
179
- # self.peers[peer.sender] = peer
180
- # self.peer_route.add_peer(peer_pub_key)
181
- # if is_validator_flag == [1]:
182
- # self.validation_route.add_peer(peer_pub_key)
183
-
184
- # if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
185
- # self._send_ping(addr)
186
-
187
- # case MessageTopic.OBJECT_REQUEST:
188
- # try:
189
- # object_request = ObjectRequest.from_bytes(message.body)
190
-
191
- # match object_request.type:
192
- # # -------------- OBJECT_GET --------------
193
- # case ObjectRequestType.OBJECT_GET:
194
- # object_hash = object_request.hash
195
-
196
- # # 1. If we already have the object, return it.
197
- # local_data = self._local_object_get(object_hash)
198
- # if local_data is not None:
199
- # resp = ObjectResponse(
200
- # type=ObjectResponseType.OBJECT_FOUND,
201
- # data=local_data,
202
- # hash=object_hash
203
- # )
204
- # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
205
- # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
206
- # return # done
207
-
208
- # # 2. If we know a provider, tell the requester.
209
- # if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
210
- # self.storage_index = {}
211
- # if object_hash in self.storage_index:
212
- # provider_bytes = self.storage_index[object_hash]
213
- # resp = ObjectResponse(
214
- # type=ObjectResponseType.OBJECT_PROVIDER,
215
- # data=provider_bytes,
216
- # hash=object_hash
217
- # )
218
- # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
219
- # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
220
- # return # done
221
-
222
- # # 3. Otherwise, direct the requester to a peer nearer to the hash.
223
- # nearest = self._get_closest_local_peer(object_hash)
224
- # if nearest:
225
- # nearest_key, nearest_peer = nearest
226
- # peer_info = encode([
227
- # nearest_key.public_bytes(
228
- # encoding=serialization.Encoding.Raw,
229
- # format=serialization.PublicFormat.Raw
230
- # ),
231
- # encode_ip_address(*nearest_peer.address)
232
- # ])
233
- # resp = ObjectResponse(
234
- # type=ObjectResponseType.OBJECT_NEAREST_PEER,
235
- # data=peer_info,
236
- # hash=object_hash
237
- # )
238
- # obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
239
- # self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
240
-
241
- # # -------------- OBJECT_PUT --------------
242
- # case ObjectRequestType.OBJECT_PUT:
243
- # # Ensure the hash is present / correct.
244
- # obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
245
-
246
- # nearest = self._get_closest_local_peer(obj_hash)
247
- # # If a strictly nearer peer exists, forward the PUT.
248
- # if nearest and self._is_closer_than_local_peers(obj_hash, nearest[0]):
249
- # fwd_req = ObjectRequest(
250
- # type=ObjectRequestType.OBJECT_PUT,
251
- # data=object_request.data,
252
- # hash=obj_hash
253
- # )
254
- # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
255
- # self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
256
- # else:
257
- # # We are closest → remember who can provide the object.
258
- # peer_pub_key = self.addresses.get(addr)
259
- # provider_record = encode([
260
- # peer_pub_key.public_bytes(),
261
- # encode_ip_address(*addr)
262
- # ])
263
- # if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
264
- # self.storage_index = {}
265
- # self.storage_index[obj_hash] = provider_record
266
-
267
- # except Exception as e:
268
- # print(f"Error processing OBJECT_REQUEST: {e}")
269
-
270
- # case MessageTopic.OBJECT_RESPONSE:
271
- # try:
272
- # object_response = ObjectResponse.from_bytes(message.body)
273
- # if object_response.hash not in self.object_request_queue:
274
- # continue
275
-
276
- # match object_response.type:
277
- # case ObjectResponseType.OBJECT_FOUND:
278
- # if object_response.hash != blake3.blake3(object_response.data).digest():
279
- # continue
280
- # self.object_request_queue.remove(object_response.hash)
281
- # self._local_object_put(object_response.hash, object_response.data)
282
-
283
- # case ObjectResponseType.OBJECT_PROVIDER:
284
- # _provider_public_key, provider_address = decode(object_response.data)
285
- # provider_ip, provider_port = decode_ip_address(provider_address)
286
- # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
287
- # self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
288
-
289
- # case ObjectResponseType.OBJECT_NEAREST_PEER:
290
- # # -- decode the peer info sent back
291
- # nearest_peer_public_key_bytes, nearest_peer_address = (
292
- # decode(object_response.data)
293
- # )
294
- # nearest_peer_public_key = X25519PublicKey.from_public_bytes(
295
- # nearest_peer_public_key_bytes
296
- # )
297
-
298
- # # -- XOR-distance between the object hash and the candidate peer
299
- # peer_bytes = nearest_peer_public_key.public_bytes(
300
- # encoding=serialization.Encoding.Raw,
301
- # format=serialization.PublicFormat.Raw,
302
- # )
303
- # object_response_xor = sum(
304
- # a ^ b for a, b in zip(object_response.hash, peer_bytes)
305
- # )
306
-
307
- # # -- forward only if that peer is strictly nearer than any local peer
308
- # if self._is_closer_than_local_peers(
309
- # object_response.hash, nearest_peer_public_key
310
- # ):
311
- # nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
312
- # obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
313
- # self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
314
- # )
315
-
316
-
317
- # except Exception as e:
318
- # print(f"Error processing OBJECT_RESPONSE: {e}")
319
-
320
- # except Exception as e:
321
- # print(f"Error processing message: {e}")
322
-
323
- # def _relay_outgoing_queue_processor(self):
324
- # while True:
325
- # try:
326
- # data, addr = self.outgoing_queue.get()
327
- # self.outgoing_socket.sendto(data, addr)
328
- # except Exception as e:
329
- # print(f"Error sending message: {e}")
330
-
331
- # def _relay_peer_manager(self):
332
- # while True:
333
- # try:
334
- # time.sleep(60)
335
- # for peer in self.peers.values():
336
- # if (datetime.now(timezone.utc) - peer.timestamp).total_seconds() > 900:
337
- # del self.peers[peer.sender]
338
- # self.peer_route.remove_peer(peer.sender)
339
- # if peer.sender in self.validation_route.buckets:
340
- # self.validation_route.remove_peer(peer.sender)
341
- # except Exception as e:
342
- # print(f"Error in _peer_manager_thread: {e}")
343
-
344
- # def _send_ping(self, addr: Tuple[str, int]):
345
- # is_validator_flag = encode([1] if self.validation_secret_key else [0])
346
- # ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
347
- # self.outgoing_queue.put((ping_message.to_bytes(), addr))
348
-
349
- # def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
350
- # # Find the globally closest peer using XOR distance
351
- # closest_peer = None
352
- # closest_distance = None
353
-
354
- # # Check all peers
355
- # for peer_key, peer in self.peers.items():
356
- # # Calculate XOR distance between hash and peer's public key
357
- # peer_bytes = peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
358
- # # XOR each byte and sum them to get a total distance
359
- # distance = sum(a ^ b for a, b in zip(hash, peer_bytes))
360
- # # Update the closest peer if the distance is smaller
361
- # if closest_distance is None or distance < closest_distance:
362
- # closest_distance = distance
363
- # closest_peer = (peer_key, peer)
364
-
365
- # return closest_peer
366
-
367
- # def _is_closer_than_local_peers(self, hash: bytes, foreign_peer_public_key: X25519PublicKey) -> bool:
368
-
369
- # # Get the closest local peer
370
- # closest_local_peer = self._get_closest_local_peer(hash)
371
-
372
- # # If we have no local peers, the foreign peer is closer by default
373
- # if closest_local_peer is None:
374
- # return True
375
-
376
- # # Calculate XOR distance for the foreign peer
377
- # foreign_peer_bytes = foreign_peer_public_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
378
- # foreign_distance = sum(a ^ b for a, b in zip(hash, foreign_peer_bytes))
379
-
380
- # # Get the closest local peer key and calculate its distance
381
- # closest_peer_key, _ = closest_local_peer
382
- # closest_peer_bytes = closest_peer_key.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
383
- # local_distance = sum(a ^ b for a, b in zip(hash, closest_peer_bytes))
384
-
385
- # # Return True if the foreign peer is closer (has smaller XOR distance)
386
- # return foreign_distance < local_distance
387
-
388
- # # MACHINE
389
- # def _machine_setup(self):
390
- # self.environments: Dict[uuid.UUID, Env] = {}
391
- # self.machine_environments_lock = threading.Lock()
392
-
393
- # def machine_create_environment(self, parent_id: Optional[uuid.UUID] = None) -> uuid.UUID:
394
- # env_id = uuid.uuid4()
395
- # with self.machine_environments_lock:
396
- # while env_id in self.environments:
397
- # env_id = uuid.uuid4()
398
- # self.environments[env_id] = Env(parent_id=parent_id)
399
- # return env_id
400
-
401
- # def machine_get_or_create_environment(
402
- # self,
403
- # env_id: Optional[uuid.UUID] = None,
404
- # parent_id: Optional[uuid.UUID] = None,
405
- # max_exprs: Optional[int] = None
406
- # ) -> uuid.UUID:
407
- # with self.machine_environments_lock:
408
- # if env_id is not None and env_id in self.environments:
409
- # return env_id
410
- # new_id = env_id if env_id is not None else uuid.uuid4()
411
- # while new_id in self.environments:
412
- # new_id = uuid.uuid4()
413
- # self.environments[new_id] = Env(parent_id=parent_id, max_exprs=max_exprs)
414
- # return new_id
415
-
416
- # def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
417
- # with self.machine_environments_lock:
418
- # removed = self.environments.pop(env_id, None)
419
- # return removed is not None
420
-
421
- # def machine_expr_get(self, env_id: uuid.UUID, name: str) -> Optional[Expr]:
422
- # with self.machine_environments_lock:
423
- # cur = self.environments.get(env_id)
424
- # while cur is not None:
425
- # if name in cur.data:
426
- # return cur.data[name]
427
- # if cur.parent_id:
428
- # cur = self.environments.get(cur.parent_id)
429
- # else:
430
- # cur = None
431
- # return None
432
-
433
- # def machine_expr_put(self, env_id: uuid.UUID, name: str, expr: Expr):
434
- # with self.machine_environments_lock:
435
- # env = self.environments.get(env_id)
436
- # if env is None:
437
- # return False
438
- # env.put(name, expr)
439
- # return True
440
-
441
- # def machine_expr_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
442
- # if isinstance(expr, Expr.Boolean) or isinstance(expr, Expr.Integer) or isinstance(expr, Expr.String) or isinstance(expr, Expr.Error):
443
- # return expr
444
-
445
- # elif isinstance(expr, Expr.Symbol):
446
- # value = self.machine_expr_get(env_id=env_id, name=expr.value)
447
- # if value:
448
- # return value
449
- # else:
450
- # return Expr.Error(message=f"unbound symbol '{expr.value}'", origin=expr)
451
-
452
- # elif isinstance(expr, Expr.ListExpr):
453
- # if len(expr.elements) == 0:
454
- # return expr
455
- # if len(expr.elements) == 1:
456
- # return self.machine_expr_eval(expr=expr.elements[0], env_id=env_id)
457
- # first = expr.elements[0]
458
- # if isinstance(first, Expr.Symbol):
459
- # first_symbol_value = self.machine_expr_get(env_id=env_id, name=first.value)
460
-
461
- # if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
462
- # evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
463
- # return Expr.ListExpr(evaluated_elements)
464
-
465
- # elif first.value == "def":
466
- # args = expr.elements[1:]
467
- # if len(args) != 2:
468
- # return Expr.Error("def expects key value", origin=expr)
469
- # if not isinstance(args[0], Expr.Symbol):
470
- # return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
471
- # result = self.machine_expr_eval(env_id=env_id, expr=args[1])
472
- # if isinstance(result, Expr.Error):
473
- # return result
474
-
475
- # self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
476
- # return result
477
-
478
-
479
- # ## DEF: (def x 1) -> ()
480
-
481
- # ## GET: (x) -> 1
482
-
483
- # ## ADD: (+ 1 2) -> (+ 3) -> 3
484
-
485
- # ## NAND: (~& 1 1) -> (~& 0) -> 0
486
-
487
- # ##
488
-
489
-
490
- # ## List: ints -> (1 2)
491
- # # push: (list.push 3 ints) -> (1 2 3) / (list.push 0 0 ints) -> (0 1 2)
492
- # elif first.value == "list.push":
493
- # args = expr.elements[1:]
494
- # if len(args) == 2:
495
- # val_expr, list_expr = args
496
- # idx = None
497
- # elif len(args) == 3:
498
- # idx_expr, val_expr, list_expr = args
499
- # idx = self.machine_expr_eval(env_id, idx_expr)
500
- # if isinstance(idx, Expr.Error): return idx
501
- # if not isinstance(idx, Expr.IntExpr):
502
- # return Expr.Error("index must be int", origin=idx_expr)
503
- # idx = idx.value
504
- # else:
505
- # return Expr.Error("list.push expects (value list) or (index value list)", origin=expr)
506
-
507
- # lst = self.machine_expr_eval(env_id, list_expr)
508
- # if isinstance(lst, Expr.Error): return lst
509
- # if not isinstance(lst, Expr.ListExpr):
510
- # return Expr.Error("last arg to list.push must be a list", origin=list_expr)
511
-
512
- # val = self.machine_expr_eval(env_id, val_expr)
513
- # if isinstance(val, Expr.Error): return val
514
-
515
- # elems = list(lst.elements)
516
- # if idx is None:
517
- # elems.append(val)
518
- # else:
519
- # if idx < 0 or idx > len(elems):
520
- # return Expr.Error("index out of range", origin=idx_expr)
521
- # elems.insert(idx, val)
522
- # return Expr.ListExpr(elems)
523
-
524
- # # pop: (list.pop 1 ints) -> 2
525
- # elif first.value == "list.pop":
526
- # if len(expr.elements) < 3:
527
- # return Expr.Error("list.pop expects index list", origin=expr)
528
-
529
- # idx_expr, list_expr = expr.elements[1], expr.elements[2]
530
- # idx = self.machine_expr_eval(env_id, idx_expr)
531
- # if isinstance(idx, Expr.Error): return idx
532
- # if not isinstance(idx, Expr.IntExpr):
533
- # return Expr.Error("index must be int", origin=idx_expr)
534
- # idx = idx.value
535
-
536
- # lst = self.machine_expr_eval(env_id, list_expr)
537
- # if isinstance(lst, Expr.Error): return lst
538
- # if not isinstance(lst, Expr.ListExpr):
539
- # return Expr.Error("second arg to list.pop must be a list", origin=list_expr)
540
-
541
- # elems = list(lst.elements)
542
- # if idx < 0 or idx >= len(elems):
543
- # return Expr.Error("index out of range", origin=idx_expr)
544
- # del elems[idx]
545
- # return Expr.ListExpr(elems)
546
-
547
- # # get: (list.get 1 ints) -> 2
548
- # elif first.value == "list.get":
549
- # if len(expr.elements) < 3:
550
- # return Expr.Error("list.get expects index list", origin=expr)
551
-
552
- # idx_expr, list_expr = expr.elements[1], expr.elements[2]
553
- # idx = self.machine_expr_eval(env_id, idx_expr)
554
- # if isinstance(idx, Expr.Error): return idx
555
- # if not isinstance(idx, Expr.IntExpr):
556
- # return Expr.Error("index must be int", origin=idx_expr)
557
- # idx = idx.value
558
-
559
- # lst = self.machine_expr_eval(env_id, list_expr)
560
- # if isinstance(lst, Expr.Error): return lst
561
- # if not isinstance(lst, Expr.ListExpr):
562
- # return Expr.Error("second arg to list.get must be a list", origin=list_expr)
563
-
564
- # if idx < 0 or idx >= len(lst.elements):
565
- # return Expr.Error("index out of range", origin=idx_expr)
566
- # return lst.elements[idx]
567
-
568
- # # set: (list.set 1 3 ints) -> (1 3)
569
- # elif first.value == "list.set":
570
- # if len(expr.elements) < 4:
571
- # return Expr.Error("list.set expects index value list", origin=expr)
572
- # idx_expr, val_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
573
- # idx = self.machine_expr_eval(env_id, idx_expr)
574
- # if isinstance(idx, Expr.Error): return idx
575
- # if not isinstance(idx, Expr.IntExpr):
576
- # return Expr.Error("index must be int", origin=idx_expr)
577
- # idx = idx.value
578
-
579
- # val = self.machine_expr_eval(env_id, val_expr)
580
- # if isinstance(val, Expr.Error): return val
581
-
582
- # lst = self.machine_expr_eval(env_id, list_expr)
583
- # if isinstance(lst, Expr.Error): return lst
584
- # if not isinstance(lst, Expr.ListExpr):
585
- # return Expr.Error("third arg to list.set must be a list", origin=list_expr)
586
-
587
- # elems = list(lst.elements)
588
- # if idx < 0 or idx >= len(elems):
589
- # return Expr.Error("index out of range", origin=idx_expr)
590
- # elems[idx] = val
591
- # return Expr.ListExpr(elems)
592
-
593
- # ### each: (list.each fn list) -> ()
594
- # elif first.value == "list.each":
595
- # if len(expr.elements) < 3:
596
- # return Expr.Error("list.each expects fn list", origin=expr)
597
- # fn_expr, list_expr = expr.elements[1], expr.elements[2]
598
- # lst = self.machine_expr_eval(env_id, list_expr)
599
- # if isinstance(lst, Expr.Error):
600
- # return lst
601
- # if not isinstance(lst, Expr.ListExpr):
602
- # return Expr.Error("second arg to list.each must be a list", origin=list_expr)
603
-
604
- # for el in lst.elements:
605
- # res = self.machine_expr_eval(env_id, Expr.ListExpr([fn_expr, el]))
606
- # if isinstance(res, Expr.Error):
607
- # return res
608
- # return Expr.ListExpr([])
609
-
610
- # ### fold: (list.fold fn init list) / (list.fold + 0 ints) -> 3
611
- # elif first.value == "list.fold":
612
- # fn_expr, init_expr, list_expr = expr.elements[1], expr.elements[2], expr.elements[3]
613
- # acc = self.machine_expr_eval(env_id, init_expr)
614
- # if isinstance(acc, Expr.Error):
615
- # return acc
616
-
617
- # lst = self.machine_expr_eval(env_id, list_expr)
618
- # if isinstance(lst, Expr.Error):
619
- # return lst
620
- # if not isinstance(lst, Expr.ListExpr):
621
- # return Expr.Error("third arg to list.fold must be a list", origin=list_expr)
622
-
623
- # for el in lst.elements:
624
- # call = Expr.ListExpr([fn_expr, acc, el])
625
- # res = self.machine_expr_eval(env_id, call)
626
- # if isinstance(res, Expr.Error):
627
- # return res
628
- # acc = res
629
-
630
- # return acc
631
-
632
- # ### sort: (list.sort fn list) / (list.sort (fn (a b) (a < b)) ints) -> (2 1)
633
- # elif first.value == "list.sort":
634
- # if len(expr.elements) < 3:
635
- # return Expr.Error("list.sort fn list", origin=expr)
636
- # fn_e, lst_e = expr.elements[1], expr.elements[2]
637
-
638
- # lst = self.machine_expr_eval(env_id, lst_e)
639
- # if isinstance(lst, Expr.Error): return lst
640
- # if not isinstance(lst, Expr.ListExpr):
641
- # return Expr.Error("second arg must be list", origin=lst_e)
642
-
643
- # elems = list(lst.elements)
644
- # for i in range(1, len(elems)):
645
- # j = i
646
- # while j > 0:
647
- # cmp_res = self.machine_expr_eval(
648
- # env_id,
649
- # Expr.ListExpr([fn_e, elems[j-1], elems[j]])
650
- # )
651
- # if isinstance(cmp_res, Expr.Error): return cmp_res
652
- # if not isinstance(cmp_res, Expr.BoolExpr):
653
- # return Expr.Error("comparator must return bool", origin=fn_e)
654
-
655
- # if cmp_res.value:
656
- # elems[j-1], elems[j] = elems[j], elems[j-1]
657
- # j -= 1
658
- # else:
659
- # break
660
- # return Expr.ListExpr(elems)
661
-
662
- # ### len: (list.len list) -> Int / (list.len ints) -> Integer(2)
663
- # elif first.value == "list.len":
664
- # if len(expr.elements) < 2:
665
- # return Expr.Error("list.len list", origin=expr)
666
- # lst_e = expr.elements[1]
667
- # lst = self.machine_expr_eval(env_id, lst_e)
668
- # if isinstance(lst, Expr.Error): return lst
669
- # if not isinstance(lst, Expr.ListExpr):
670
- # return Expr.Error("arg must be list", origin=lst_e)
671
- # return Expr.Integer(len(lst.elements))
672
-
673
- # ## Integer
674
- # ### add
675
- # elif first.value == "+":
676
- # args = expr.elements[1:]
677
- # if not args:
678
- # return Expr.Error("'+' expects at least 1 argument", origin=expr)
679
- # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
680
- # for v in vals:
681
- # if isinstance(v, Expr.Error): return v
682
- # if not isinstance(v, Expr.Integer):
683
- # return Expr.Error("'+' only accepts integer operands", origin=v)
684
- # return Expr.Integer(abs(vals[0].value) if len(vals) == 1
685
- # else sum(v.value for v in vals))
686
-
687
- # elif first.value == "-":
688
- # args = expr.elements[1:]
689
- # if not args:
690
- # return Expr.Error("'-' expects at least 1 argument", origin=expr)
691
- # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
692
- # for v in vals:
693
- # if isinstance(v, Expr.Error): return v
694
- # if not isinstance(v, Expr.Integer):
695
- # return Expr.Error("'-' only accepts integer operands", origin=v)
696
- # if len(vals) == 1:
697
- # return Expr.Integer(-vals[0].value)
698
- # result = vals[0].value
699
- # for v in vals[1:]:
700
- # result -= v.value
701
- # return Expr.Integer(result)
702
-
703
- # elif first.value == "/":
704
- # args = expr.elements[1:]
705
- # if len(args) < 2:
706
- # return Expr.Error("'/' expects at least 2 arguments", origin=expr)
707
- # vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
708
- # for v in vals:
709
- # if isinstance(v, Expr.Error): return v
710
- # if not isinstance(v, Expr.Integer):
711
- # return Expr.Error("'/' only accepts integer operands", origin=v)
712
- # result = vals[0].value
713
- # for v in vals[1:]:
714
- # if v.value == 0:
715
- # return Expr.Error("division by zero", origin=v)
716
- # if result % v.value:
717
- # return Expr.Error("non-exact division", origin=expr)
718
- # result //= v.value
719
- # return Expr.Integer(result)
720
-
721
- # elif first.value == "%":
722
- # if len(expr.elements) != 3:
723
- # return Expr.Error("'%' expects exactly 2 arguments", origin=expr)
724
- # a = self.machine_expr_eval(env_id=env_id, expr=expr.elements[1])
725
- # b = self.machine_expr_eval(env_id=env_id, expr=expr.elements[2])
726
- # for v in (a, b):
727
- # if isinstance(v, Expr.Error): return v
728
- # if not isinstance(v, Expr.Integer):
729
- # return Expr.Error("'%' only accepts integer operands", origin=v)
730
- # if b.value == 0:
731
- # return Expr.Error("division by zero", origin=expr.elements[2])
732
- # return Expr.Integer(a.value % b.value)
733
-
734
- # elif first.value in ("=", "!=", ">", "<", ">=", "<="):
735
- # args = expr.elements[1:]
736
- # if len(args) != 2:
737
- # return Expr.Error(f"'{first.value}' expects exactly 2 arguments", origin=expr)
738
-
739
- # left = self.machine_expr_eval(env_id=env_id, expr=args[0])
740
- # right = self.machine_expr_eval(env_id=env_id, expr=args[1])
741
-
742
- # for v in (left, right):
743
- # if isinstance(v, Expr.Error):
744
- # return v
745
- # if not isinstance(v, Expr.Integer):
746
- # return Expr.Error(f"'{first.value}' only accepts integer operands", origin=v)
747
-
748
- # a, b = left.value, right.value
749
- # match first.value:
750
- # case "=": res = a == b
751
- # case "!=": res = a != b
752
- # case ">": res = a > b
753
- # case "<": res = a < b
754
- # case ">=": res = a >= b
755
- # case "<=": res = a <= b
756
-
757
- # return Expr.Boolean(res)
758
-
759
- # if isinstance(first, Expr.Function):
760
- # arg_exprs = expr.elements[1:]
761
- # if len(arg_exprs) != len(first.params):
762
- # return Expr.Error(f"arity mismatch: expected {len(first.params)}, got {len(arg_exprs)}", origin=expr)
763
-
764
- # call_env = self.machine_create_environment(parent_id=env_id)
765
- # for name, aexpr in zip(first.params, arg_exprs):
766
- # val = self.machine_expr_eval(env_id, aexpr)
767
- # if isinstance(val, Expr.Error): return val
768
- # self.machine_expr_put(call_env, name, val)
769
-
770
- # return self.machine_expr_eval(env_id=call_env, expr=first.body)
771
-
772
- # else:
773
- # evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
774
- # return Expr.ListExpr(evaluated_elements)
775
-
776
- # elif isinstance(expr, Expr.Function):
777
- # return expr
778
-
779
- # else:
780
- # raise ValueError(f"Unknown expression type: {type(expr)}")
781
-
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.consensus.start import process_blocks_and_transactions
11
+ from astreum.machine import Expr, high_eval, low_eval, script_eval
12
+ from astreum.machine.models.environment import Env, env_get, env_set
13
+ from astreum.machine.models.expression import get_expr_list_from_storage
14
+ from astreum.storage.models.atom import get_atom_list_from_storage
15
+ from astreum.storage.actions.get import (
16
+ _hot_storage_get,
17
+ _cold_storage_get,
18
+ _network_get,
19
+ storage_get,
20
+ )
21
+ from astreum.storage.actions.set import (
22
+ _hot_storage_set,
23
+ _cold_storage_set,
24
+ _network_set,
25
+ )
26
+ from astreum.storage.setup import storage_setup
27
+ from astreum.utils.logging import logging_setup
28
+
29
+
30
+ class Node:
31
+ def __init__(self, config: dict = {}):
32
+ self.config = config
33
+ self.logger = logging_setup(config)
34
+
35
+ self.logger.info("Starting Astreum Node")
36
+
37
+ # Chain Configuration
38
+ chain_str = config.get("chain", "test")
39
+ self.chain = 1 if chain_str == "main" else 0
40
+ self.logger.info(f"Chain configured as: {chain_str} ({self.chain})")
41
+
42
+ # Storage Setup
43
+ storage_setup(self, config=config)
44
+
45
+ # Machine Setup
46
+ self.environments: Dict[uuid.UUID, Env] = {}
47
+ self.machine_environments_lock = threading.RLock()
48
+
49
+ connect = connect_to_network_and_verify
50
+ validate = process_blocks_and_transactions
51
+
52
+ low_eval = low_eval
53
+ high_eval = high_eval
54
+ script_eval = script_eval
55
+
56
+ env_get = env_get
57
+ env_set = env_set
58
+
59
+ # Storage
60
+ ## Get
61
+ _hot_storage_get = _hot_storage_get
62
+ _cold_storage_get = _cold_storage_get
63
+ _network_get = _network_get
64
+
65
+ ## Set
66
+ _hot_storage_set = _hot_storage_set
67
+ _cold_storage_set = _cold_storage_set
68
+ _network_set = _network_set
69
+
70
+ storage_get = storage_get
71
+
72
+ get_expr_list_from_storage = get_expr_list_from_storage
73
+ get_atom_list_from_storage = get_atom_list_from_storage