astreum 0.2.26__py3-none-any.whl → 0.2.28__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.

Potentially problematic release.


This version of astreum might be problematic. Click here for more details.

@@ -0,0 +1,40 @@
1
+ from typing import Dict, Optional
2
+ import uuid
3
+
4
+ from astreum.lispeum.expression import Expr
5
+
6
+
7
+ class Env:
8
+ def __init__(
9
+ self,
10
+ data: Optional[Dict[str, Expr]] = None,
11
+ parent_id: Optional[uuid.UUID] = None,
12
+ max_exprs: Optional[int] = 8,
13
+ ):
14
+ self.data: Dict[str, Expr] = data if data is not None else {}
15
+ self.parent_id: Optional[uuid.UUID] = parent_id
16
+ self.max_exprs: Optional[int] = max_exprs
17
+
18
+ def put(self, name: str, value: Expr) -> None:
19
+ if (
20
+ self.max_exprs is not None
21
+ and name not in self.data
22
+ and len(self.data) >= self.max_exprs
23
+ ):
24
+ raise RuntimeError(
25
+ f"environment full: {len(self.data)} ≥ max_exprs={self.max_exprs}"
26
+ )
27
+ self.data[name] = value
28
+
29
+ def get(self, name: str) -> Optional[Expr]:
30
+ return self.data.get(name)
31
+
32
+ def pop(self, name: str) -> Optional[Expr]:
33
+ return self.data.pop(name, None)
34
+
35
+ def __repr__(self) -> str:
36
+ return (
37
+ f"Env(size={len(self.data)}, "
38
+ f"max_exprs={self.max_exprs}, "
39
+ f"parent_id={self.parent_id})"
40
+ )
@@ -0,0 +1,86 @@
1
+
2
+ from typing import List, Optional, Union
3
+
4
+
5
+ class Expr:
6
+ class ListExpr:
7
+ def __init__(self, elements: List['Expr']):
8
+ self.elements = elements
9
+
10
+ def __eq__(self, other):
11
+ if not isinstance(other, Expr.ListExpr):
12
+ return NotImplemented
13
+ return self.elements == other.elements
14
+
15
+ def __ne__(self, other):
16
+ return not self.__eq__(other)
17
+
18
+ @property
19
+ def value(self):
20
+ inner = " ".join(str(e) for e in self.elements)
21
+ return f"({inner})"
22
+
23
+
24
+ def __repr__(self):
25
+ if not self.elements:
26
+ return "()"
27
+
28
+ inner = " ".join(str(e) for e in self.elements)
29
+ return f"({inner})"
30
+
31
+ def __iter__(self):
32
+ return iter(self.elements)
33
+
34
+ def __getitem__(self, index: Union[int, slice]):
35
+ return self.elements[index]
36
+
37
+ def __len__(self):
38
+ return len(self.elements)
39
+
40
+ class Symbol:
41
+ def __init__(self, value: str):
42
+ self.value = value
43
+
44
+ def __repr__(self):
45
+ return self.value
46
+
47
+ class Integer:
48
+ def __init__(self, value: int):
49
+ self.value = value
50
+
51
+ def __repr__(self):
52
+ return str(self.value)
53
+
54
+ class String:
55
+ def __init__(self, value: str):
56
+ self.value = value
57
+
58
+ def __repr__(self):
59
+ return f'"{self.value}"'
60
+
61
+ class Boolean:
62
+ def __init__(self, value: bool):
63
+ self.value = value
64
+
65
+ def __repr__(self):
66
+ return "true" if self.value else "false"
67
+
68
+ class Function:
69
+ def __init__(self, params: List[str], body: 'Expr'):
70
+ self.params = params
71
+ self.body = body
72
+
73
+ def __repr__(self):
74
+ params_str = " ".join(self.params)
75
+ body_str = str(self.body)
76
+ return f"(fn ({params_str}) {body_str})"
77
+
78
+ class Error:
79
+ def __init__(self, message: str, origin: Optional['Expr'] = None):
80
+ self.message = message
81
+ self.origin = origin
82
+
83
+ def __repr__(self):
84
+ if self.origin is None:
85
+ return f'(error "{self.message}")'
86
+ return f'(error "{self.message}" in {self.origin})'
@@ -0,0 +1,64 @@
1
+ from enum import IntEnum
2
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
3
+
4
+ class MessageTopic(IntEnum):
5
+ PING = 0
6
+ OBJECT_REQUEST = 1
7
+ OBJECT_RESPONSE = 2
8
+ ROUTE_REQUEST = 3
9
+ ROUTE_RESPONSE = 4
10
+
11
+ class Message:
12
+ handshake: bool
13
+ sender: X25519PublicKey
14
+
15
+ topic: MessageTopic
16
+ content: bytes
17
+
18
+ def to_bytes(self):
19
+ if self.handshake:
20
+ # handshake byte (1) + raw public key bytes
21
+ return bytes([1]) + self.sender.public_bytes(
22
+ encoding=serialization.Encoding.Raw,
23
+ format=serialization.PublicFormat.Raw
24
+ )
25
+ else:
26
+ # normal message: 0 + topic + content
27
+ return bytes([0, self.topic.value]) + self.content
28
+
29
+ @classmethod
30
+ def from_bytes(cls, data: bytes) -> "Message":
31
+ if len(data) < 1:
32
+ raise ValueError("Cannot parse Message: no data")
33
+ flag = data[0]
34
+ # create empty instance
35
+ msg = cls.__new__(cls)
36
+
37
+ if flag == 1:
38
+ # handshake message: the rest is the peer’s public key
39
+ key_bytes = data[1:]
40
+ try:
41
+ sender = X25519PublicKey.from_public_bytes(key_bytes)
42
+ except ValueError:
43
+ raise ValueError("Invalid public key bytes")
44
+ msg.handshake = True
45
+ msg.sender = sender
46
+ msg.topic = None
47
+ msg.content = b''
48
+ elif flag == 0:
49
+ # normal message: next byte is topic, rest is content
50
+ if len(data) < 2:
51
+ raise ValueError("Cannot parse Message: missing topic byte")
52
+ topic_val = data[1]
53
+ try:
54
+ topic = MessageTopic(topic_val)
55
+ except ValueError:
56
+ raise ValueError(f"Unknown MessageTopic: {topic_val}")
57
+ msg.handshake = False
58
+ msg.sender = None
59
+ msg.topic = topic
60
+ msg.content = data[2:]
61
+ else:
62
+ raise ValueError(f"Invalid handshake flag: {flag}")
63
+
64
+ return msg
astreum/node.py CHANGED
@@ -7,160 +7,27 @@ from typing import Tuple, Dict, Union, Optional, List
7
7
  from datetime import datetime, timedelta, timezone
8
8
  import uuid
9
9
 
10
+ from astreum.lispeum.environment import Env
11
+ from astreum.lispeum.expression import Expr
12
+ from astreum.storage.object import ObjectRequest, ObjectRequestType, ObjectResponse, ObjectResponseType
13
+ from astreum.storage.setup import storage_setup
14
+
10
15
  from .models.transaction import Transaction
11
16
  from .format import encode, decode
12
17
  from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
13
18
  from cryptography.hazmat.primitives import serialization
14
19
  from .crypto import ed25519, x25519
15
- from enum import IntEnum
16
20
  import blake3
17
21
  import struct
22
+ from .models.message import Message, MessageTopic
18
23
 
19
- class ObjectRequestType(IntEnum):
20
- OBJECT_GET = 0
21
- OBJECT_PUT = 1
22
-
23
- class ObjectRequest:
24
- type: ObjectRequestType
25
- data: bytes
26
- hash: bytes
27
-
28
- def __init__(self, type: ObjectRequestType, data: bytes, hash: bytes = None):
29
- self.type = type
30
- self.data = data
31
- self.hash = hash
32
-
33
- def to_bytes(self):
34
- return encode([self.type.value, self.data, self.hash])
35
-
36
- @classmethod
37
- def from_bytes(cls, data: bytes):
38
- type_val, data_val, hash_val = decode(data)
39
- return cls(type=ObjectRequestType(type_val[0]), data=data_val, hash=hash_val)
40
-
41
- class ObjectResponseType(IntEnum):
42
- OBJECT_FOUND = 0
43
- OBJECT_PROVIDER = 1
44
- OBJECT_NEAREST_PEER = 2
45
-
46
- class ObjectResponse:
47
- type: ObjectResponseType
48
- data: bytes
49
- hash: bytes
50
-
51
- def __init__(self, type: ObjectResponseType, data: bytes, hash: bytes = None):
52
- self.type = type
53
- self.data = data
54
- self.hash = hash
55
-
56
- def to_bytes(self):
57
- return encode([self.type.value, self.data, self.hash])
58
-
59
- @classmethod
60
- def from_bytes(cls, data: bytes):
61
- type_val, data_val, hash_val = decode(data)
62
- return cls(type=ObjectResponseType(type_val[0]), data=data_val, hash=hash_val)
63
-
64
- class MessageTopic(IntEnum):
65
- PING = 0
66
- OBJECT_REQUEST = 1
67
- OBJECT_RESPONSE = 2
68
- ROUTE_REQUEST = 3
69
- ROUTE_RESPONSE = 4
70
-
71
- class Message:
72
- body: bytes
73
- topic: MessageTopic
74
-
75
- def to_bytes(self):
76
- return encode([self.body, [self.topic.value]])
77
-
78
- @classmethod
79
- def from_bytes(cls, data: bytes):
80
- body, topic = decode(data)
81
- return cls(body=body, topic=MessageTopic(topic[0]))
82
-
83
- class Envelope:
84
- encrypted: bool
85
- message: Message
86
- nonce: int
87
- sender: X25519PublicKey
88
- timestamp: datetime
89
-
90
- def __init__(self, message: Message, sender: X25519PublicKey, encrypted: bool = False, nonce: int = 0, timestamp: Union[int, datetime, None] = None, difficulty: int = 1):
91
- self.encrypted = encrypted
92
- encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
93
-
94
- self.message = message
95
- message_bytes = message.to_bytes()
96
-
97
- self.sender = sender
98
- self.sender_bytes = sender.public_bytes()
99
-
100
- self.nonce = nonce
101
-
102
- if timestamp is None:
103
- self.timestamp = datetime.now(timezone.utc)
104
- timestamp_int = int(self.timestamp.timestamp())
105
- elif isinstance(timestamp, int):
106
- self.timestamp = datetime.fromtimestamp(timestamp, timezone.utc)
107
- timestamp_int = timestamp
108
- elif isinstance(timestamp, datetime):
109
- self.timestamp = timestamp
110
- timestamp_int = int(timestamp.timestamp())
111
- else:
112
- raise TypeError("Timestamp must be an int (Unix timestamp), datetime object, or None")
113
-
114
- def count_leading_zero_bits(data: bytes) -> int:
115
- count = 0
116
- for b in data:
117
- if b == 0:
118
- count += 8
119
- else:
120
- count += 8 - b.bit_length()
121
- break
122
- return count
123
24
 
124
- while True:
125
- envelope_bytes = encode([
126
- encrypted_bytes,
127
- message_bytes,
128
- self.nonce,
129
- self.sender_bytes,
130
- timestamp_int
131
- ])
132
- envelope_hash = blake3.blake3(envelope_bytes).digest()
133
- if count_leading_zero_bits(envelope_hash) >= difficulty:
134
- self.hash = envelope_hash
135
- break
136
- self.nonce += 1
137
-
138
- def to_bytes(self):
139
- encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
140
-
141
- return encode([
142
- encrypted_bytes,
143
- self.message.to_bytes(),
144
- self.nonce,
145
- self.sender.public_bytes(),
146
- int(self.timestamp.timestamp())
147
- ])
148
-
149
- @classmethod
150
- def from_bytes(cls, data: bytes):
151
- encrypted_bytes, message_bytes, nonce, sender_bytes, timestamp_int = decode(data)
152
- return cls(
153
- encrypted=(encrypted_bytes == b'\x01'),
154
- message=Message.from_bytes(message_bytes),
155
- nonce=nonce,
156
- sender=X25519PublicKey.from_public_bytes(sender_bytes),
157
- timestamp=datetime.fromtimestamp(timestamp_int, timezone.utc)
158
- )
159
25
 
160
26
  class Peer:
161
- def __init__(self, node_secret_key: X25519PrivateKey, peer_public_key: X25519PublicKey, address: Tuple[str, int]):
162
- self.shared_key = x25519.generate_shared_key(node_secret_key, peer_public_key)
163
- self.address = address
27
+ shared_key: bytes
28
+ timestamp: datetime
29
+ def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
30
+ self.shared_key = my_sec_key.exchange(peer_pub_key)
164
31
  self.timestamp = datetime.now(timezone.utc)
165
32
 
166
33
  class Route:
@@ -203,135 +70,18 @@ def decode_ip_address(data: bytes) -> tuple[str, int]:
203
70
  raise ValueError("Invalid address byte format")
204
71
  return ip, port
205
72
 
206
- # =========
207
- # MACHINE
208
- # =========
209
-
210
- class Expr:
211
- class ListExpr:
212
- def __init__(self, elements: List['Expr']):
213
- self.elements = elements
214
-
215
- def __eq__(self, other):
216
- if not isinstance(other, Expr.ListExpr):
217
- return NotImplemented
218
- return self.elements == other.elements
219
-
220
- def __ne__(self, other):
221
- return not self.__eq__(other)
222
-
223
- @property
224
- def value(self):
225
- inner = " ".join(str(e) for e in self.elements)
226
- return f"({inner})"
227
-
228
-
229
- def __repr__(self):
230
- if not self.elements:
231
- return "()"
232
-
233
- inner = " ".join(str(e) for e in self.elements)
234
- return f"({inner})"
235
-
236
- def __iter__(self):
237
- return iter(self.elements)
238
-
239
- def __getitem__(self, index: Union[int, slice]):
240
- return self.elements[index]
241
-
242
- def __len__(self):
243
- return len(self.elements)
244
-
245
- class Symbol:
246
- def __init__(self, value: str):
247
- self.value = value
248
-
249
- def __repr__(self):
250
- return self.value
251
-
252
- class Integer:
253
- def __init__(self, value: int):
254
- self.value = value
255
-
256
- def __repr__(self):
257
- return str(self.value)
258
-
259
- class String:
260
- def __init__(self, value: str):
261
- self.value = value
262
-
263
- def __repr__(self):
264
- return f'"{self.value}"'
265
-
266
- class Boolean:
267
- def __init__(self, value: bool):
268
- self.value = value
269
-
270
- def __repr__(self):
271
- return "true" if self.value else "false"
272
-
273
- class Function:
274
- def __init__(self, params: List[str], body: 'Expr'):
275
- self.params = params
276
- self.body = body
277
-
278
- def __repr__(self):
279
- params_str = " ".join(self.params)
280
- body_str = str(self.body)
281
- return f"(fn ({params_str}) {body_str})"
282
-
283
- class Error:
284
- def __init__(self, message: str, origin: Optional['Expr'] = None):
285
- self.message = message
286
- self.origin = origin
287
-
288
- def __repr__(self):
289
- if self.origin is None:
290
- return f'(error "{self.message}")'
291
- return f'(error "{self.message}" in {self.origin})'
292
-
293
- class Env:
294
- def __init__(
295
- self,
296
- data: Optional[Dict[str, Expr]] = None,
297
- parent_id: Optional[uuid.UUID] = None,
298
- max_exprs: Optional[int] = 8,
299
- ):
300
- self.data: Dict[str, Expr] = data if data is not None else {}
301
- self.parent_id: Optional[uuid.UUID] = parent_id
302
- self.max_exprs: Optional[int] = max_exprs
303
-
304
- def put(self, name: str, value: Expr) -> None:
305
- if (
306
- self.max_exprs is not None
307
- and name not in self.data
308
- and len(self.data) >= self.max_exprs
309
- ):
310
- raise RuntimeError(
311
- f"environment full: {len(self.data)} ≥ max_exprs={self.max_exprs}"
312
- )
313
- self.data[name] = value
314
-
315
- def get(self, name: str) -> Optional[Expr]:
316
- return self.data.get(name)
317
-
318
- def pop(self, name: str) -> Optional[Expr]:
319
- return self.data.pop(name, None)
320
-
321
- def __repr__(self) -> str:
322
- return (
323
- f"Env(size={len(self.data)}, "
324
- f"max_exprs={self.max_exprs}, "
325
- f"parent_id={self.parent_id})"
326
- )
327
-
328
-
329
73
  class Node:
330
74
  def __init__(self, config: dict = {}):
331
75
  self._machine_setup()
332
76
  machine_only = bool(config.get('machine-only', True))
333
77
  if not machine_only:
334
- self._storage_setup(config=config)
78
+ (
79
+ self.storage_path,
80
+ self.memory_storage,
81
+ self.storage_get_relay_timeout,
82
+ self.storage_index
83
+ ) = storage_setup(config)
84
+
335
85
  self._relay_setup(config=config)
336
86
  self._validation_setup(config=config)
337
87
 
@@ -344,21 +94,6 @@ class Node:
344
94
  def _create_block(self):
345
95
  pass
346
96
 
347
- # STORAGE METHODS
348
- def _storage_setup(self, config: dict):
349
- storage_path_str = config.get('storage_path')
350
- if storage_path_str is None:
351
- self.storage_path = None
352
- self.memory_storage = {}
353
- else:
354
- self.storage_path = Path(storage_path_str)
355
- self.storage_path.mkdir(parents=True, exist_ok=True)
356
- self.memory_storage = None
357
-
358
- self.storage_get_relay_timeout = config.get('storage_get_relay_timeout', 5)
359
- # STORAGE INDEX: (object_hash, encoded (provider_public_key, provider_address))
360
- self.storage_index = Dict[bytes, bytes]
361
-
362
97
  def _relay_setup(self, config: dict):
363
98
  self.use_ipv6 = config.get('use_ipv6', False)
364
99
  incoming_port = config.get('incoming_port', 7373)
@@ -419,6 +154,7 @@ class Node:
419
154
  self.peer_manager_thread.start()
420
155
 
421
156
  self.peers = Dict[X25519PublicKey, Peer]
157
+ self.addresses = Dict[Tuple[str, int], X25519PublicKey]
422
158
 
423
159
  if 'bootstrap' in config:
424
160
  for addr in config['bootstrap']:
@@ -451,9 +187,8 @@ class Node:
451
187
  # find the nearest peer route node to the hash and send an object request
452
188
  closest_peer = self._get_closest_local_peer(hash)
453
189
  if closest_peer:
454
- object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, body=hash)
455
- object_request_envelope = Envelope(message=object_request_message, sender=self.relay_public_key)
456
- self.outgoing_queue.put((object_request_envelope.to_bytes(), self.peers[closest_peer].address))
190
+ object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
191
+ self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
457
192
 
458
193
  # wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
459
194
  start_time = time.time()
@@ -481,30 +216,31 @@ class Node:
481
216
  while True:
482
217
  try:
483
218
  data, addr = self.incoming_queue.get()
484
- envelope = Envelope.from_bytes(data)
485
- match envelope.message.topic:
219
+ message = Message.from_bytes(data)
220
+ match message.topic:
486
221
  case MessageTopic.PING:
487
- if envelope.sender in self.peers:
488
- self.peers[envelope.sender].timestamp = datetime.now(timezone.utc)
222
+ peer_pub_key = self.addresses.get(addr)
223
+ if peer_pub_key in self.peers:
224
+ self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
489
225
  continue
490
226
 
491
- is_validator_flag = decode(envelope.message.body)
227
+ is_validator_flag = decode(message.body)
492
228
 
493
- if envelope.sender not in self.peers:
229
+ if peer_pub_key not in self.peers:
494
230
  self._send_ping(addr)
495
231
 
496
- peer = Peer(self.relay_secret_key, envelope.sender, addr)
232
+ peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
497
233
  self.peers[peer.sender] = peer
498
- self.peer_route.add_peer(envelope.sender)
234
+ self.peer_route.add_peer(peer_pub_key)
499
235
  if is_validator_flag == [1]:
500
- self.validation_route.add_peer(envelope.sender)
236
+ self.validation_route.add_peer(peer_pub_key)
501
237
 
502
238
  if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
503
239
  self._send_ping(addr)
504
240
 
505
241
  case MessageTopic.OBJECT_REQUEST:
506
242
  try:
507
- object_request = ObjectRequest.from_bytes(envelope.message.body)
243
+ object_request = ObjectRequest.from_bytes(message.body)
508
244
 
509
245
  match object_request.type:
510
246
  # -------------- OBJECT_GET --------------
@@ -519,9 +255,8 @@ class Node:
519
255
  data=local_data,
520
256
  hash=object_hash
521
257
  )
522
- msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
523
- env = Envelope(message=msg, sender=self.relay_public_key)
524
- self.outgoing_queue.put((env.to_bytes(), addr))
258
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
259
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
525
260
  return # done
526
261
 
527
262
  # 2. If we know a provider, tell the requester.
@@ -534,9 +269,8 @@ class Node:
534
269
  data=provider_bytes,
535
270
  hash=object_hash
536
271
  )
537
- msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
538
- env = Envelope(message=msg, sender=self.relay_public_key)
539
- self.outgoing_queue.put((env.to_bytes(), addr))
272
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
273
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
540
274
  return # done
541
275
 
542
276
  # 3. Otherwise, direct the requester to a peer nearer to the hash.
@@ -555,14 +289,13 @@ class Node:
555
289
  data=peer_info,
556
290
  hash=object_hash
557
291
  )
558
- msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
559
- env = Envelope(message=msg, sender=self.relay_public_key)
560
- self.outgoing_queue.put((env.to_bytes(), addr))
292
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
293
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
561
294
 
562
295
  # -------------- OBJECT_PUT --------------
563
296
  case ObjectRequestType.OBJECT_PUT:
564
297
  # Ensure the hash is present / correct.
565
- obj_hash = object_request.hash or blake30.blake3(object_request.data).digest()
298
+ obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
566
299
 
567
300
  nearest = self._get_closest_local_peer(obj_hash)
568
301
  # If a strictly nearer peer exists, forward the PUT.
@@ -572,13 +305,13 @@ class Node:
572
305
  data=object_request.data,
573
306
  hash=obj_hash
574
307
  )
575
- fwd_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
576
- fwd_env = Envelope(message=fwd_msg, sender=self.relay_public_key)
577
- self.outgoing_queue.put((fwd_env.to_bytes(), nearest[1].address))
308
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
309
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
578
310
  else:
579
311
  # We are closest → remember who can provide the object.
312
+ peer_pub_key = self.addresses.get(addr)
580
313
  provider_record = encode([
581
- envelope.sender.public_bytes(),
314
+ peer_pub_key.public_bytes(),
582
315
  encode_ip_address(*addr)
583
316
  ])
584
317
  if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
@@ -590,13 +323,13 @@ class Node:
590
323
 
591
324
  case MessageTopic.OBJECT_RESPONSE:
592
325
  try:
593
- object_response = ObjectResponse.from_bytes(envelope.message.body)
326
+ object_response = ObjectResponse.from_bytes(message.body)
594
327
  if object_response.hash not in self.object_request_queue:
595
328
  continue
596
329
 
597
330
  match object_response.type:
598
331
  case ObjectResponseType.OBJECT_FOUND:
599
- if object_response.hash != blake30.blake3(object_response.data).digest():
332
+ if object_response.hash != blake3.blake3(object_response.data).digest():
600
333
  continue
601
334
  self.object_request_queue.remove(object_response.hash)
602
335
  self._local_object_put(object_response.hash, object_response.data)
@@ -604,9 +337,8 @@ class Node:
604
337
  case ObjectResponseType.OBJECT_PROVIDER:
605
338
  _provider_public_key, provider_address = decode(object_response.data)
606
339
  provider_ip, provider_port = decode_ip_address(provider_address)
607
- object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
608
- object_request_envelope = Envelope(message=object_request_message, sender=self.relay_public_key)
609
- self.outgoing_queue.put((object_request_envelope.to_bytes(), (provider_ip, provider_port)))
340
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
341
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
610
342
 
611
343
  case ObjectResponseType.OBJECT_NEAREST_PEER:
612
344
  # -- decode the peer info sent back
@@ -630,23 +362,10 @@ class Node:
630
362
  if self._is_closer_than_local_peers(
631
363
  object_response.hash, nearest_peer_public_key
632
364
  ):
633
- nearest_peer_ip, nearest_peer_port = decode_ip_address(
634
- nearest_peer_address
635
- )
636
- object_request_message = Message(
637
- topic=MessageTopic.OBJECT_REQUEST,
638
- body=object_response.hash,
639
- )
640
- object_request_envelope = Envelope(
641
- message=object_request_message,
642
- sender=self.relay_public_key,
643
- )
644
- self.outgoing_queue.put(
645
- (
646
- object_request_envelope.to_bytes(),
647
- (nearest_peer_ip, nearest_peer_port),
648
- )
649
- )
365
+ nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
366
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
367
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
368
+ )
650
369
 
651
370
 
652
371
  except Exception as e:
@@ -678,9 +397,8 @@ class Node:
678
397
 
679
398
  def _send_ping(self, addr: Tuple[str, int]):
680
399
  is_validator_flag = encode([1] if self.validation_secret_key else [0])
681
- ping_message = Message(topic=MessageTopic.PING, body=is_validator_flag)
682
- ping_envelope = Envelope(message=ping_message, sender=self.relay_public_key)
683
- self.outgoing_queue.put((ping_envelope.to_bytes(), addr))
400
+ ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
401
+ self.outgoing_queue.put((ping_message.to_bytes(), addr))
684
402
 
685
403
  def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
686
404
  # Find the globally closest peer using XOR distance
File without changes
@@ -0,0 +1,68 @@
1
+ from enum import IntEnum
2
+
3
+ class ObjectRequestType(IntEnum):
4
+ OBJECT_GET = 0
5
+ OBJECT_PUT = 1
6
+
7
+ class ObjectRequest:
8
+ type: ObjectRequestType
9
+ data: bytes
10
+ hash: bytes
11
+
12
+ def __init__(self, type: ObjectRequestType, data: bytes, hash: bytes = None):
13
+ self.type = type
14
+ self.data = data
15
+ self.hash = hash
16
+
17
+ def to_bytes(self):
18
+ return [self.type.value] + self.hash + self.data
19
+
20
+ @classmethod
21
+ def from_bytes(cls, data: bytes) -> "ObjectRequest":
22
+ # need at least 1 byte for type + 32 bytes for hash
23
+ if len(data) < 1 + 32:
24
+ raise ValueError(f"Too short for ObjectRequest ({len(data)} bytes)")
25
+
26
+ type_val = data[0]
27
+ try:
28
+ req_type = ObjectRequestType(type_val)
29
+ except ValueError:
30
+ raise ValueError(f"Unknown ObjectRequestType: {type_val!r}")
31
+
32
+ hash_bytes = data[1:33]
33
+ payload = data[33:]
34
+ return cls(req_type, payload, hash_bytes)
35
+
36
+ class ObjectResponseType(IntEnum):
37
+ OBJECT_FOUND = 0
38
+ OBJECT_PROVIDER = 1
39
+ OBJECT_NEAREST_PEER = 2
40
+
41
+ class ObjectResponse:
42
+ type: ObjectResponseType
43
+ data: bytes
44
+ hash: bytes
45
+
46
+ def __init__(self, type: ObjectResponseType, data: bytes, hash: bytes = None):
47
+ self.type = type
48
+ self.data = data
49
+ self.hash = hash
50
+
51
+ def to_bytes(self):
52
+ return [self.type.value] + self.hash + self.data
53
+
54
+ @classmethod
55
+ def from_bytes(cls, data: bytes) -> "ObjectResponse":
56
+ # need at least 1 byte for type + 32 bytes for hash
57
+ if len(data) < 1 + 32:
58
+ raise ValueError(f"Too short to be a valid ObjectResponse ({len(data)} bytes)")
59
+
60
+ type_val = data[0]
61
+ try:
62
+ resp_type = ObjectResponseType(type_val)
63
+ except ValueError:
64
+ raise ValueError(f"Unknown ObjectResponseType: {type_val}")
65
+
66
+ hash_bytes = data[1:33]
67
+ payload = data[33:]
68
+ return cls(resp_type, payload, hash_bytes)
@@ -0,0 +1,16 @@
1
+ from pathlib import Path
2
+ from typing import Optional, Dict, Tuple, Any
3
+
4
+ def storage_setup(config: dict
5
+ ) -> Tuple[Optional[Path], Dict[bytes, Any], int, Dict[bytes, bytes]]:
6
+ storage_path_str = config.get('storage_path')
7
+ if storage_path_str is None:
8
+ storage_path, memory_storage = None, {}
9
+ else:
10
+ storage_path = Path(storage_path_str)
11
+ storage_path.mkdir(parents=True, exist_ok=True)
12
+ memory_storage = None
13
+
14
+ timeout = config.get('storage_get_relay_timeout', 5)
15
+ storage_index: Dict[bytes, bytes] = {}
16
+ return storage_path, memory_storage, timeout, storage_index
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.26
3
+ Version: 0.2.28
4
4
  Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
6
  Project-URL: Homepage, https://github.com/astreum/lib
@@ -1,12 +1,14 @@
1
1
  astreum/__init__.py,sha256=y2Ok3EY_FstcmlVASr80lGR_0w-dH-SXDCCQFmL6uwA,28
2
2
  astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
3
- astreum/node.py,sha256=dPloCXuDyIn3-KDqxlgl3jxsonJlFMLi_quwJRsoLC8,46259
3
+ astreum/node.py,sha256=L2YpIxyhf0f-y85_vBGBaGiItgFGzLgXYhyabDetaKM,37116
4
4
  astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
6
6
  astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
7
7
  astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
8
8
  astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
9
9
  astreum/lispeum/__init__.py,sha256=K-NDzIjtIsXzC9X7lnYvlvIaVxjFcY7WNsgLIE3DH3U,58
10
+ astreum/lispeum/environment.py,sha256=wolwt9psDl62scgjaVG0G59xlBs1AM4NPgryUbxzG_4,1220
11
+ astreum/lispeum/expression.py,sha256=K8gFifDaHu394bs9qnpvP8tjeiymFGQpnDC_iW9nU4E,2379
10
12
  astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
11
13
  astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
12
14
  astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -14,10 +16,14 @@ astreum/models/account.py,sha256=sHujGSwtV13rvOGJ5LZXuMrJ4F9XUdvyuWKz-zJ9lkE,298
14
16
  astreum/models/accounts.py,sha256=aFSEWlq6zRf65-KGAdNGqEJyNVY3fpKhx8y1vU6sgSc,1164
15
17
  astreum/models/block.py,sha256=-5j7uO0woVtNi0h52__e7AxpDQSVhzKUhr6Qc-2xZsE,17870
16
18
  astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
19
+ astreum/models/message.py,sha256=vv8yx-ndVYjCmPM4gXRVMToCTlKY_mflPu0uKsb9iiE,2117
17
20
  astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
18
21
  astreum/models/transaction.py,sha256=MkLL5YX18kIf9-O4LBaZ4eWjkXDAaYIrDcDehbDZoqg,3038
19
- astreum-0.2.26.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
20
- astreum-0.2.26.dist-info/METADATA,sha256=fUG5PepefY7Cjfp5Gj5GwtbYF369bARQAcl0A4OJiQ0,5478
21
- astreum-0.2.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- astreum-0.2.26.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
23
- astreum-0.2.26.dist-info/RECORD,,
22
+ astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
24
+ astreum/storage/setup.py,sha256=EhMKr4wnBaESbzF8AlE2R-8YPIzxWx3afSv5BL8X6Lo,632
25
+ astreum-0.2.28.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
26
+ astreum-0.2.28.dist-info/METADATA,sha256=dB1O5if24FmM7erOlVfX-WAa923EO8KgzCl4GpaHS7o,5478
27
+ astreum-0.2.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
+ astreum-0.2.28.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
29
+ astreum-0.2.28.dist-info/RECORD,,