astreum 0.2.26__tar.gz → 0.2.27__tar.gz

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.

Files changed (30) hide show
  1. {astreum-0.2.26/src/astreum.egg-info → astreum-0.2.27}/PKG-INFO +1 -1
  2. {astreum-0.2.26 → astreum-0.2.27}/pyproject.toml +1 -1
  3. astreum-0.2.27/src/astreum/models/message.py +64 -0
  4. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/node.py +40 -151
  5. {astreum-0.2.26 → astreum-0.2.27/src/astreum.egg-info}/PKG-INFO +1 -1
  6. {astreum-0.2.26 → astreum-0.2.27}/src/astreum.egg-info/SOURCES.txt +1 -0
  7. {astreum-0.2.26 → astreum-0.2.27}/LICENSE +0 -0
  8. {astreum-0.2.26 → astreum-0.2.27}/README.md +0 -0
  9. {astreum-0.2.26 → astreum-0.2.27}/setup.cfg +0 -0
  10. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/__init__.py +0 -0
  11. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/crypto/__init__.py +0 -0
  12. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/crypto/ed25519.py +0 -0
  13. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/crypto/quadratic_form.py +0 -0
  14. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/crypto/wesolowski.py +0 -0
  15. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/crypto/x25519.py +0 -0
  16. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/format.py +0 -0
  17. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/lispeum/__init__.py +0 -0
  18. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/lispeum/parser.py +0 -0
  19. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/lispeum/tokenizer.py +0 -0
  20. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/__init__.py +0 -0
  21. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/account.py +0 -0
  22. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/accounts.py +0 -0
  23. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/block.py +0 -0
  24. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/merkle.py +0 -0
  25. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/patricia.py +0 -0
  26. {astreum-0.2.26 → astreum-0.2.27}/src/astreum/models/transaction.py +0 -0
  27. {astreum-0.2.26 → astreum-0.2.27}/src/astreum.egg-info/dependency_links.txt +0 -0
  28. {astreum-0.2.26 → astreum-0.2.27}/src/astreum.egg-info/requires.txt +0 -0
  29. {astreum-0.2.26 → astreum-0.2.27}/src/astreum.egg-info/top_level.txt +0 -0
  30. {astreum-0.2.26 → astreum-0.2.27}/tests/test_node_machine.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.26
3
+ Version: 0.2.27
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.26"
3
+ version = "0.2.27"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -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
@@ -15,6 +15,7 @@ from .crypto import ed25519, x25519
15
15
  from enum import IntEnum
16
16
  import blake3
17
17
  import struct
18
+ from .models.message import Message, MessageTopic
18
19
 
19
20
  class ObjectRequestType(IntEnum):
20
21
  OBJECT_GET = 0
@@ -61,106 +62,11 @@ class ObjectResponse:
61
62
  type_val, data_val, hash_val = decode(data)
62
63
  return cls(type=ObjectResponseType(type_val[0]), data=data_val, hash=hash_val)
63
64
 
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
-
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
-
160
65
  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
66
+ shared_key: bytes
67
+ timestamp: datetime
68
+ def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
69
+ self.shared_key = my_sec_key.exchange(peer_pub_key)
164
70
  self.timestamp = datetime.now(timezone.utc)
165
71
 
166
72
  class Route:
@@ -419,6 +325,7 @@ class Node:
419
325
  self.peer_manager_thread.start()
420
326
 
421
327
  self.peers = Dict[X25519PublicKey, Peer]
328
+ self.addresses = Dict[Tuple[str, int], X25519PublicKey]
422
329
 
423
330
  if 'bootstrap' in config:
424
331
  for addr in config['bootstrap']:
@@ -451,9 +358,8 @@ class Node:
451
358
  # find the nearest peer route node to the hash and send an object request
452
359
  closest_peer = self._get_closest_local_peer(hash)
453
360
  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))
361
+ object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, content=hash)
362
+ self.outgoing_queue.put((object_request_message.to_bytes(), self.peers[closest_peer].address))
457
363
 
458
364
  # wait for upto self.storage_get_relay_timeout seconds for the object to be stored/until local_object_get returns something
459
365
  start_time = time.time()
@@ -481,30 +387,31 @@ class Node:
481
387
  while True:
482
388
  try:
483
389
  data, addr = self.incoming_queue.get()
484
- envelope = Envelope.from_bytes(data)
485
- match envelope.message.topic:
390
+ message = Message.from_bytes(data)
391
+ match message.topic:
486
392
  case MessageTopic.PING:
487
- if envelope.sender in self.peers:
488
- self.peers[envelope.sender].timestamp = datetime.now(timezone.utc)
393
+ peer_pub_key = self.addresses.get(addr)
394
+ if peer_pub_key in self.peers:
395
+ self.peers[peer_pub_key].timestamp = datetime.now(timezone.utc)
489
396
  continue
490
397
 
491
- is_validator_flag = decode(envelope.message.body)
398
+ is_validator_flag = decode(message.body)
492
399
 
493
- if envelope.sender not in self.peers:
400
+ if peer_pub_key not in self.peers:
494
401
  self._send_ping(addr)
495
402
 
496
- peer = Peer(self.relay_secret_key, envelope.sender, addr)
403
+ peer = Peer(my_sec_key=self.relay_secret_key, peer_pub_key=peer_pub_key)
497
404
  self.peers[peer.sender] = peer
498
- self.peer_route.add_peer(envelope.sender)
405
+ self.peer_route.add_peer(peer_pub_key)
499
406
  if is_validator_flag == [1]:
500
- self.validation_route.add_peer(envelope.sender)
407
+ self.validation_route.add_peer(peer_pub_key)
501
408
 
502
409
  if peer.timestamp < datetime.now(timezone.utc) - timedelta(minutes=5.0):
503
410
  self._send_ping(addr)
504
411
 
505
412
  case MessageTopic.OBJECT_REQUEST:
506
413
  try:
507
- object_request = ObjectRequest.from_bytes(envelope.message.body)
414
+ object_request = ObjectRequest.from_bytes(message.body)
508
415
 
509
416
  match object_request.type:
510
417
  # -------------- OBJECT_GET --------------
@@ -519,9 +426,8 @@ class Node:
519
426
  data=local_data,
520
427
  hash=object_hash
521
428
  )
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))
429
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
430
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
525
431
  return # done
526
432
 
527
433
  # 2. If we know a provider, tell the requester.
@@ -534,9 +440,8 @@ class Node:
534
440
  data=provider_bytes,
535
441
  hash=object_hash
536
442
  )
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))
443
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
444
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
540
445
  return # done
541
446
 
542
447
  # 3. Otherwise, direct the requester to a peer nearer to the hash.
@@ -555,14 +460,13 @@ class Node:
555
460
  data=peer_info,
556
461
  hash=object_hash
557
462
  )
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))
463
+ obj_res_msg = Message(topic=MessageTopic.OBJECT_RESPONSE, body=resp.to_bytes())
464
+ self.outgoing_queue.put((obj_res_msg.to_bytes(), addr))
561
465
 
562
466
  # -------------- OBJECT_PUT --------------
563
467
  case ObjectRequestType.OBJECT_PUT:
564
468
  # Ensure the hash is present / correct.
565
- obj_hash = object_request.hash or blake30.blake3(object_request.data).digest()
469
+ obj_hash = object_request.hash or blake3.blake3(object_request.data).digest()
566
470
 
567
471
  nearest = self._get_closest_local_peer(obj_hash)
568
472
  # If a strictly nearer peer exists, forward the PUT.
@@ -572,13 +476,13 @@ class Node:
572
476
  data=object_request.data,
573
477
  hash=obj_hash
574
478
  )
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))
479
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=fwd_req.to_bytes())
480
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), nearest[1].address))
578
481
  else:
579
482
  # We are closest → remember who can provide the object.
483
+ peer_pub_key = self.addresses.get(addr)
580
484
  provider_record = encode([
581
- envelope.sender.public_bytes(),
485
+ peer_pub_key.public_bytes(),
582
486
  encode_ip_address(*addr)
583
487
  ])
584
488
  if not hasattr(self, "storage_index") or not isinstance(self.storage_index, dict):
@@ -590,13 +494,13 @@ class Node:
590
494
 
591
495
  case MessageTopic.OBJECT_RESPONSE:
592
496
  try:
593
- object_response = ObjectResponse.from_bytes(envelope.message.body)
497
+ object_response = ObjectResponse.from_bytes(message.body)
594
498
  if object_response.hash not in self.object_request_queue:
595
499
  continue
596
500
 
597
501
  match object_response.type:
598
502
  case ObjectResponseType.OBJECT_FOUND:
599
- if object_response.hash != blake30.blake3(object_response.data).digest():
503
+ if object_response.hash != blake3.blake3(object_response.data).digest():
600
504
  continue
601
505
  self.object_request_queue.remove(object_response.hash)
602
506
  self._local_object_put(object_response.hash, object_response.data)
@@ -604,9 +508,8 @@ class Node:
604
508
  case ObjectResponseType.OBJECT_PROVIDER:
605
509
  _provider_public_key, provider_address = decode(object_response.data)
606
510
  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)))
511
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
512
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_ip, provider_port)))
610
513
 
611
514
  case ObjectResponseType.OBJECT_NEAREST_PEER:
612
515
  # -- decode the peer info sent back
@@ -630,23 +533,10 @@ class Node:
630
533
  if self._is_closer_than_local_peers(
631
534
  object_response.hash, nearest_peer_public_key
632
535
  ):
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
- )
536
+ nearest_peer_ip, nearest_peer_port = decode_ip_address(nearest_peer_address)
537
+ obj_req_msg = Message(topic=MessageTopic.OBJECT_REQUEST, content=object_response.hash)
538
+ self.outgoing_queue.put((obj_req_msg.to_bytes(), (nearest_peer_ip, nearest_peer_port),)
539
+ )
650
540
 
651
541
 
652
542
  except Exception as e:
@@ -678,9 +568,8 @@ class Node:
678
568
 
679
569
  def _send_ping(self, addr: Tuple[str, int]):
680
570
  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))
571
+ ping_message = Message(topic=MessageTopic.PING, content=is_validator_flag)
572
+ self.outgoing_queue.put((ping_message.to_bytes(), addr))
684
573
 
685
574
  def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
686
575
  # Find the globally closest peer using XOR distance
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.26
3
+ Version: 0.2.27
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
@@ -22,6 +22,7 @@ src/astreum/models/account.py
22
22
  src/astreum/models/accounts.py
23
23
  src/astreum/models/block.py
24
24
  src/astreum/models/merkle.py
25
+ src/astreum/models/message.py
25
26
  src/astreum/models/patricia.py
26
27
  src/astreum/models/transaction.py
27
28
  tests/test_node_machine.py
File without changes
File without changes
File without changes
File without changes