astreum 0.2.42__tar.gz → 0.2.44__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 (60) hide show
  1. {astreum-0.2.42/src/astreum.egg-info → astreum-0.2.44}/PKG-INFO +1 -1
  2. {astreum-0.2.42 → astreum-0.2.44}/pyproject.toml +1 -1
  3. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/message.py +1 -0
  4. astreum-0.2.44/src/astreum/_communication/peer.py +23 -0
  5. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/route.py +40 -3
  6. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/setup.py +72 -4
  7. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/account.py +1 -1
  8. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/block.py +2 -2
  9. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/transaction.py +2 -2
  10. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/workers/validation.py +5 -2
  11. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/workers/verify.py +1 -1
  12. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_node.py +49 -5
  13. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_storage/patricia.py +2 -2
  14. {astreum-0.2.42 → astreum-0.2.44/src/astreum.egg-info}/PKG-INFO +1 -1
  15. astreum-0.2.42/src/astreum/_communication/peer.py +0 -11
  16. {astreum-0.2.42 → astreum-0.2.44}/LICENSE +0 -0
  17. {astreum-0.2.42 → astreum-0.2.44}/README.md +0 -0
  18. {astreum-0.2.42 → astreum-0.2.44}/setup.cfg +0 -0
  19. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/__init__.py +0 -0
  20. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/__init__.py +0 -0
  21. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/ping.py +0 -0
  22. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_communication/util.py +0 -0
  23. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/__init__.py +0 -0
  24. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/accounts.py +0 -0
  25. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/chain.py +0 -0
  26. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/fork.py +0 -0
  27. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/genesis.py +0 -0
  28. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/receipt.py +0 -0
  29. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/setup.py +0 -0
  30. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/workers/__init__.py +0 -0
  31. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_consensus/workers/discovery.py +0 -0
  32. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/__init__.py +0 -0
  33. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/environment.py +0 -0
  34. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/expression.py +0 -0
  35. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/high_evaluation.py +0 -0
  36. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/low_evaluation.py +0 -0
  37. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/meter.py +0 -0
  38. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/parser.py +0 -0
  39. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_lispeum/tokenizer.py +0 -0
  40. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_storage/__init__.py +0 -0
  41. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/_storage/atom.py +0 -0
  42. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/crypto/__init__.py +0 -0
  43. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/crypto/ed25519.py +0 -0
  44. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/crypto/quadratic_form.py +0 -0
  45. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/crypto/wesolowski.py +0 -0
  46. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/crypto/x25519.py +0 -0
  47. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/format.py +0 -0
  48. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/models/__init__.py +0 -0
  49. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/models/block.py +0 -0
  50. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/models/merkle.py +0 -0
  51. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/models/patricia.py +0 -0
  52. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/node.py +0 -0
  53. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/storage/__init__.py +0 -0
  54. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/storage/object.py +0 -0
  55. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/storage/setup.py +0 -0
  56. {astreum-0.2.42 → astreum-0.2.44}/src/astreum/utils/integer.py +0 -0
  57. {astreum-0.2.42 → astreum-0.2.44}/src/astreum.egg-info/SOURCES.txt +0 -0
  58. {astreum-0.2.42 → astreum-0.2.44}/src/astreum.egg-info/dependency_links.txt +0 -0
  59. {astreum-0.2.42 → astreum-0.2.44}/src/astreum.egg-info/requires.txt +0 -0
  60. {astreum-0.2.42 → astreum-0.2.44}/src/astreum.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.42
3
+ Version: 0.2.44
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.42"
3
+ version = "0.2.44"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -10,6 +10,7 @@ class MessageTopic(IntEnum):
10
10
  ROUTE_REQUEST = 3
11
11
  ROUTE_RESPONSE = 4
12
12
  TRANSACTION = 5
13
+ STORAGE_REQUEST = 6
13
14
 
14
15
 
15
16
  class Message:
@@ -0,0 +1,23 @@
1
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
2
+ from cryptography.hazmat.primitives import serialization
3
+ from datetime import datetime, timezone
4
+ from typing import Optional, Tuple
5
+
6
+ class Peer:
7
+ shared_key: bytes
8
+ timestamp: datetime
9
+ latest_block: bytes
10
+ address: Optional[Tuple[str, int]]
11
+ public_key: X25519PublicKey
12
+ public_key_bytes: bytes
13
+
14
+ def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
15
+ self.shared_key = my_sec_key.exchange(peer_pub_key)
16
+ self.timestamp = datetime.now(timezone.utc)
17
+ self.latest_block = b""
18
+ self.address = None
19
+ self.public_key = peer_pub_key
20
+ self.public_key_bytes = peer_pub_key.public_bytes(
21
+ encoding=serialization.Encoding.Raw,
22
+ format=serialization.PublicFormat.Raw,
23
+ )
@@ -1,6 +1,7 @@
1
- from typing import Dict, List, Union
1
+ from typing import Dict, List, Optional, Union
2
2
  from cryptography.hazmat.primitives import serialization
3
3
  from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
4
+ from .peer import Peer
4
5
 
5
6
  PeerKey = Union[X25519PublicKey, bytes, bytearray]
6
7
 
@@ -15,7 +16,7 @@ class Route:
15
16
  self.buckets: Dict[int, List[bytes]] = {
16
17
  i: [] for i in range(len(self.relay_public_key_bytes) * 8)
17
18
  }
18
- self.peers = {}
19
+ self.peers: Dict[bytes, Peer] = {}
19
20
 
20
21
  @staticmethod
21
22
  def _matching_leading_bits(a: bytes, b: bytes) -> int:
@@ -38,13 +39,21 @@ class Route:
38
39
  return key_bytes
39
40
  raise TypeError("peer_public_key must be raw bytes or X25519PublicKey")
40
41
 
41
- def add_peer(self, peer_public_key: PeerKey):
42
+ @staticmethod
43
+ def _xor_distance(a: bytes, b: bytes) -> int:
44
+ if len(a) != len(b):
45
+ raise ValueError("xor distance requires equal-length operands")
46
+ return int.from_bytes(bytes(x ^ y for x, y in zip(a, b)), "big", signed=False)
47
+
48
+ def add_peer(self, peer_public_key: PeerKey, peer: Optional[Peer] = None):
42
49
  peer_public_key_bytes = self._normalize_peer_key(peer_public_key)
43
50
  bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
44
51
  if len(self.buckets[bucket_idx]) < self.bucket_size:
45
52
  bucket = self.buckets[bucket_idx]
46
53
  if peer_public_key_bytes not in bucket:
47
54
  bucket.append(peer_public_key_bytes)
55
+ if peer is not None:
56
+ self.peers[peer_public_key_bytes] = peer
48
57
 
49
58
  def remove_peer(self, peer_public_key: PeerKey):
50
59
  peer_public_key_bytes = self._normalize_peer_key(peer_public_key)
@@ -56,3 +65,31 @@ class Route:
56
65
  bucket.remove(peer_public_key_bytes)
57
66
  except ValueError:
58
67
  pass
68
+ self.peers.pop(peer_public_key_bytes, None)
69
+
70
+ def closest_peer_for_hash(self, target_hash: bytes) -> Optional[Peer]:
71
+ """Return the peer with the minimal XOR distance to ``target_hash``."""
72
+ if not isinstance(target_hash, (bytes, bytearray)):
73
+ raise TypeError("target_hash must be bytes-like")
74
+
75
+ target = bytes(target_hash)
76
+ if len(target) != len(self.relay_public_key_bytes):
77
+ raise ValueError("target_hash must match peer key length (32 bytes)")
78
+
79
+ closest_key: Optional[bytes] = None
80
+ closest_distance: Optional[int] = None
81
+
82
+ for bucket in self.buckets.values():
83
+ for peer_key in bucket:
84
+ try:
85
+ distance = self._xor_distance(target, peer_key)
86
+ except ValueError:
87
+ continue
88
+ if closest_distance is None or distance < closest_distance:
89
+ closest_distance = distance
90
+ closest_key = peer_key
91
+
92
+ if closest_key is None:
93
+ return None
94
+ peer = self.peers.get(closest_key)
95
+ return peer
@@ -95,9 +95,10 @@ def process_incoming_messages(node: "Node") -> None:
95
95
  peer = Peer(node.relay_secret_key, sender_key)
96
96
  except Exception:
97
97
  continue
98
-
98
+ peer.address = address_key
99
+
99
100
  node.peers[sender_public_key_bytes] = peer
100
- node.peer_route.add_peer(sender_public_key_bytes)
101
+ node.peer_route.add_peer(sender_public_key_bytes, peer)
101
102
 
102
103
  response = Message(handshake=True, sender=node.relay_public_key)
103
104
  node.outgoing_queue.put((response.to_bytes(), address_key))
@@ -105,17 +106,25 @@ def process_incoming_messages(node: "Node") -> None:
105
106
 
106
107
  elif old_key_bytes == sender_public_key_bytes:
107
108
  # existing mapping with same key -> nothing to change
108
- pass
109
+ peer = node.peers.get(sender_public_key_bytes)
110
+ if peer is not None:
111
+ peer.address = address_key
109
112
 
110
113
  else:
111
114
  # address reused with a different key -> replace peer
112
115
  node.peers.pop(old_key_bytes, None)
116
+ try:
117
+ node.peer_route.remove_peer(old_key_bytes)
118
+ except Exception:
119
+ pass
113
120
  try:
114
121
  peer = Peer(node.relay_secret_key, sender_key)
115
122
  except Exception:
116
123
  continue
117
-
124
+ peer.address = address_key
125
+
118
126
  node.peers[sender_public_key_bytes] = peer
127
+ node.peer_route.add_peer(sender_public_key_bytes, peer)
119
128
 
120
129
  match message.topic:
121
130
  case MessageTopic.PING:
@@ -164,6 +173,65 @@ def process_incoming_messages(node: "Node") -> None:
164
173
  if node.validation_secret_key is None:
165
174
  continue
166
175
  node._validation_transaction_queue.put(message.content)
176
+
177
+ case MessageTopic.STORAGE_REQUEST:
178
+ payload = message.content
179
+ if len(payload) < 32:
180
+ continue
181
+
182
+ atom_id = payload[:32]
183
+ provider_bytes = payload[32:]
184
+ if not provider_bytes:
185
+ continue
186
+
187
+ try:
188
+ provider_str = provider_bytes.decode("utf-8")
189
+ except UnicodeDecodeError:
190
+ continue
191
+
192
+ try:
193
+ host, port = addr[0], int(addr[1])
194
+ except Exception:
195
+ continue
196
+ address_key = (host, port)
197
+ sender_key_bytes = node.addresses.get(address_key)
198
+ if sender_key_bytes is None:
199
+ continue
200
+
201
+ try:
202
+ local_key_bytes = node.relay_public_key.public_bytes(
203
+ encoding=serialization.Encoding.Raw,
204
+ format=serialization.PublicFormat.Raw,
205
+ )
206
+ except Exception:
207
+ continue
208
+
209
+ def xor_distance(target: bytes, key: bytes) -> int:
210
+ return int.from_bytes(
211
+ bytes(a ^ b for a, b in zip(target, key)),
212
+ byteorder="big",
213
+ signed=False,
214
+ )
215
+
216
+ self_distance = xor_distance(atom_id, local_key_bytes)
217
+
218
+ try:
219
+ closest_peer = node.peer_route.closest_peer_for_hash(atom_id)
220
+ except Exception:
221
+ closest_peer = None
222
+
223
+ if (
224
+ closest_peer is not None
225
+ and closest_peer.public_key_bytes != sender_key_bytes
226
+ ):
227
+ closest_distance = xor_distance(atom_id, closest_peer.public_key_bytes)
228
+ if closest_distance < self_distance:
229
+ target_addr = closest_peer.address
230
+ if target_addr is not None and target_addr != addr:
231
+ node.outgoing_queue.put((message.to_bytes(), target_addr))
232
+ continue
233
+
234
+ node.storage_index[atom_id] = provider_str.strip()
167
235
 
168
236
  case _:
169
237
  continue
@@ -33,7 +33,7 @@ class Account:
33
33
 
34
34
  @classmethod
35
35
  def from_atom(cls, node: Any, account_id: bytes) -> "Account":
36
- storage_get = node._local_get
36
+ storage_get = node.storage_get
37
37
 
38
38
  type_atom = storage_get(account_id)
39
39
  if type_atom is None or type_atom.data != b"account":
@@ -186,9 +186,9 @@ class Block:
186
186
  if callable(source):
187
187
  storage_get = source
188
188
  else:
189
- storage_get = getattr(source, "_local_get", None)
189
+ storage_get = source.storage_get
190
190
  if not callable(storage_get):
191
- raise TypeError("Block.from_atom requires a node with '_local_get' or a callable storage getter")
191
+ raise TypeError("Block.from_atom requires a node with 'storage_get' or a callable storage getter")
192
192
  # 1) Expect main list
193
193
  main_typ = storage_get(block_id)
194
194
  if main_typ is None or main_typ.data != b"list":
@@ -76,7 +76,7 @@ class Transaction:
76
76
  node: Any,
77
77
  transaction_id: bytes,
78
78
  ) -> Transaction:
79
- storage_get = node._local_get
79
+ storage_get = node.storage_get
80
80
  if not callable(storage_get):
81
81
  raise NotImplementedError("node does not expose a storage getter")
82
82
 
@@ -204,7 +204,7 @@ def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None
204
204
 
205
205
  block.accounts.set_account(address=recipient_account)
206
206
 
207
- block.transactions.append(transaction)
207
+ block.transactions.append(transaction_hash)
208
208
 
209
209
  receipt = Receipt(
210
210
  transaction_hash=bytes(transaction_hash),
@@ -88,7 +88,7 @@ def make_validation_worker(
88
88
  new_block.timestamp = max(int(now), min_allowed)
89
89
 
90
90
  # atomize block
91
- new_block_hash, _ = new_block.to_atom()
91
+ new_block_hash, new_block_atoms = new_block.to_atom()
92
92
  # put as own latest block hash
93
93
  node.latest_block_hash = new_block_hash
94
94
 
@@ -117,6 +117,9 @@ def make_validation_worker(
117
117
  except Exception:
118
118
  pass
119
119
 
120
- # store the new block and receipts
120
+ # upload block atoms
121
+
122
+ # upload receipt atoms
123
+ # upload account atoms
121
124
 
122
125
  return _validation_worker
@@ -17,7 +17,7 @@ def _process_peers_latest_block(
17
17
  fk.head for fk in node.forks.values() if fk.head != latest_block_hash
18
18
  }
19
19
 
20
- new_fork.validate(storage_get=node._local_get, stop_heads=current_fork_heads)
20
+ new_fork.validate(storage_get=node.storage_get, stop_heads=current_fork_heads)
21
21
 
22
22
  if new_fork.validated_upto and new_fork.validated_upto in node.forks:
23
23
  ref = node.forks[new_fork.validated_upto]
@@ -13,8 +13,9 @@ def bytes_touched(*vals: bytes) -> int:
13
13
  class Node:
14
14
  def __init__(self, config: dict):
15
15
  # Storage Setup
16
- self.in_memory_storage: Dict[bytes, Atom] = {}
17
- self.in_memory_storage_lock = threading.RLock()
16
+ self.in_memory_storage: Dict[bytes, Atom] = {}
17
+ self.in_memory_storage_lock = threading.RLock()
18
+ self.storage_index: Dict[bytes, str] = {}
18
19
  # Lispeum Setup
19
20
  self.environments: Dict[uuid.UUID, Env] = {}
20
21
  self.machine_environments_lock = threading.RLock()
@@ -25,9 +26,9 @@ class Node:
25
26
  communication_setup(node=self, config=config)
26
27
  except Exception:
27
28
  pass
28
- try:
29
- from astreum._consensus import consensus_setup # type: ignore
30
- consensus_setup(node=self)
29
+ try:
30
+ from astreum._consensus import consensus_setup # type: ignore
31
+ consensus_setup(node=self)
31
32
  except Exception:
32
33
  pass
33
34
 
@@ -56,3 +57,46 @@ class Node:
56
57
  def _local_set(self, key: bytes, value: Atom) -> None:
57
58
  with self.in_memory_storage_lock:
58
59
  self.in_memory_storage[key] = value
60
+
61
+ def _network_get(self, key: bytes) -> Optional[Atom]:
62
+ # locate storage provider
63
+ # query storage provider
64
+ return None
65
+
66
+ def storage_get(self, key: bytes) -> Optional[Atom]:
67
+ """Retrieve an Atom by checking local storage first, then the network."""
68
+ atom = self._local_get(key)
69
+ if atom is not None:
70
+ return atom
71
+ return self._network_get(key)
72
+
73
+ def _network_set(self, atom: Atom) -> None:
74
+ """Advertise an atom to the closest known peer so they can fetch it from us."""
75
+ try:
76
+ from src.astreum._communication.message import Message, MessageTopic
77
+ except Exception:
78
+ return
79
+
80
+ atom_id = atom.object_id()
81
+ try:
82
+ closest_peer = self.peer_route.closest_peer_for_hash(atom_id)
83
+ except Exception:
84
+ return
85
+ if closest_peer is None or closest_peer.address is None:
86
+ return
87
+ target_addr = closest_peer.address
88
+
89
+ try:
90
+ provider_ip, provider_port = self.incoming_socket.getsockname()[:2]
91
+ except Exception:
92
+ return
93
+
94
+ provider_str = f"{provider_ip}:{int(provider_port)}"
95
+ try:
96
+ provider_bytes = provider_str.encode("utf-8")
97
+ except Exception:
98
+ return
99
+
100
+ payload = atom_id + provider_bytes
101
+ message = Message(topic=MessageTopic.STORAGE_REQUEST, content=payload)
102
+ self.outgoing_queue.put((message.to_bytes(), target_addr))
@@ -87,7 +87,7 @@ class PatriciaNode:
87
87
  hops = 0
88
88
 
89
89
  while current != ZERO32 and hops < 4:
90
- atom = node._local_get(current)
90
+ atom = node.storage_get(current)
91
91
  if atom is None:
92
92
  raise ValueError("missing atom while decoding Patricia node")
93
93
  entries.append(atom.data)
@@ -163,7 +163,7 @@ class PatriciaTrie:
163
163
  if cached is not None:
164
164
  return cached
165
165
 
166
- if storage_node._local_get(h) is None:
166
+ if storage_node.storage_get(h) is None:
167
167
  return None
168
168
 
169
169
  pat_node = PatriciaNode.from_atoms(storage_node, h)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.42
3
+ Version: 0.2.44
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,11 +0,0 @@
1
- from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
2
- from datetime import datetime, timezone
3
-
4
- class Peer:
5
- shared_key: bytes
6
- timestamp: datetime
7
- latest_block: bytes
8
-
9
- def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
10
- self.shared_key = my_sec_key.exchange(peer_pub_key)
11
- self.timestamp = datetime.now(timezone.utc)
File without changes
File without changes
File without changes
File without changes
File without changes