astreum 0.2.29__py3-none-any.whl → 0.2.61__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.
- astreum/__init__.py +9 -1
- astreum/_communication/__init__.py +11 -0
- astreum/{models → _communication}/message.py +101 -64
- astreum/_communication/peer.py +23 -0
- astreum/_communication/ping.py +33 -0
- astreum/_communication/route.py +95 -0
- astreum/_communication/setup.py +322 -0
- astreum/_communication/util.py +42 -0
- astreum/_consensus/__init__.py +20 -0
- astreum/_consensus/account.py +95 -0
- astreum/_consensus/accounts.py +38 -0
- astreum/_consensus/block.py +311 -0
- astreum/_consensus/chain.py +66 -0
- astreum/_consensus/fork.py +100 -0
- astreum/_consensus/genesis.py +72 -0
- astreum/_consensus/receipt.py +136 -0
- astreum/_consensus/setup.py +115 -0
- astreum/_consensus/transaction.py +215 -0
- astreum/_consensus/workers/__init__.py +9 -0
- astreum/_consensus/workers/discovery.py +48 -0
- astreum/_consensus/workers/validation.py +125 -0
- astreum/_consensus/workers/verify.py +63 -0
- astreum/_lispeum/__init__.py +16 -0
- astreum/_lispeum/environment.py +13 -0
- astreum/_lispeum/expression.py +190 -0
- astreum/_lispeum/high_evaluation.py +236 -0
- astreum/_lispeum/low_evaluation.py +123 -0
- astreum/_lispeum/meter.py +18 -0
- astreum/_lispeum/parser.py +51 -0
- astreum/_lispeum/tokenizer.py +22 -0
- astreum/_node.py +198 -0
- astreum/_storage/__init__.py +7 -0
- astreum/_storage/atom.py +109 -0
- astreum/_storage/patricia.py +478 -0
- astreum/_storage/setup.py +35 -0
- astreum/models/block.py +48 -39
- astreum/node.py +755 -563
- astreum/utils/bytes.py +24 -0
- astreum/utils/integer.py +25 -0
- astreum/utils/logging.py +219 -0
- {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/METADATA +50 -14
- astreum-0.2.61.dist-info/RECORD +57 -0
- astreum/lispeum/__init__.py +0 -2
- astreum/lispeum/environment.py +0 -40
- astreum/lispeum/expression.py +0 -86
- astreum/lispeum/parser.py +0 -41
- astreum/lispeum/tokenizer.py +0 -52
- astreum/models/account.py +0 -91
- astreum/models/accounts.py +0 -34
- astreum/models/transaction.py +0 -106
- astreum/relay/__init__.py +0 -0
- astreum/relay/peer.py +0 -9
- astreum/relay/route.py +0 -25
- astreum/relay/setup.py +0 -58
- astreum-0.2.29.dist-info/RECORD +0 -33
- {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/WHEEL +0 -0
- {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.29.dist-info → astreum-0.2.61.dist-info}/top_level.txt +0 -0
astreum/node.py
CHANGED
|
@@ -1,589 +1,781 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
from astreum.
|
|
13
|
-
from astreum.
|
|
14
|
-
from astreum.relay.
|
|
15
|
-
from astreum.
|
|
16
|
-
from astreum.
|
|
17
|
-
|
|
18
|
-
from .
|
|
19
|
-
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from .
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
self.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
|
146
148
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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)
|
|
184
186
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
273
275
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
# )
|
|
313
315
|
|
|
314
316
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
+
# except Exception as e:
|
|
318
|
+
# print(f"Error processing OBJECT_RESPONSE: {e}")
|
|
317
319
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
+
# except Exception as e:
|
|
321
|
+
# print(f"Error processing message: {e}")
|
|
320
322
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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}")
|
|
328
330
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
|
351
353
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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)
|
|
362
364
|
|
|
363
|
-
|
|
365
|
+
# return closest_peer
|
|
364
366
|
|
|
365
|
-
|
|
367
|
+
# def _is_closer_than_local_peers(self, hash: bytes, foreign_peer_public_key: X25519PublicKey) -> bool:
|
|
366
368
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
+
# # Get the closest local peer
|
|
370
|
+
# closest_local_peer = self._get_closest_local_peer(hash)
|
|
369
371
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
372
|
+
# # If we have no local peers, the foreign peer is closer by default
|
|
373
|
+
# if closest_local_peer is None:
|
|
374
|
+
# return True
|
|
373
375
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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))
|
|
377
379
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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))
|
|
382
384
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
398
400
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
430
432
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
|
442
444
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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)
|
|
449
451
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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)
|
|
458
460
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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)
|
|
462
464
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|
472
474
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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)
|
|
583
775
|
|
|
584
|
-
|
|
585
|
-
|
|
776
|
+
# elif isinstance(expr, Expr.Function):
|
|
777
|
+
# return expr
|
|
586
778
|
|
|
587
|
-
|
|
588
|
-
|
|
779
|
+
# else:
|
|
780
|
+
# raise ValueError(f"Unknown expression type: {type(expr)}")
|
|
589
781
|
|