astreum 0.2.10__tar.gz → 0.2.12__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 (28) hide show
  1. {astreum-0.2.10/src/astreum.egg-info → astreum-0.2.12}/PKG-INFO +1 -1
  2. {astreum-0.2.10 → astreum-0.2.12}/pyproject.toml +1 -1
  3. astreum-0.2.12/src/astreum/utils/__init__.py +0 -0
  4. astreum-0.2.12/src/astreum/utils/patricia.py +249 -0
  5. {astreum-0.2.10 → astreum-0.2.12/src/astreum.egg-info}/PKG-INFO +1 -1
  6. {astreum-0.2.10 → astreum-0.2.12}/src/astreum.egg-info/SOURCES.txt +2 -0
  7. {astreum-0.2.10 → astreum-0.2.12}/LICENSE +0 -0
  8. {astreum-0.2.10 → astreum-0.2.12}/README.md +0 -0
  9. {astreum-0.2.10 → astreum-0.2.12}/setup.cfg +0 -0
  10. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/__init__.py +0 -0
  11. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/_node/__init__.py +0 -0
  12. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/_node/storage/__init__.py +0 -0
  13. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/_node/storage/merkle.py +0 -0
  14. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/_node/storage/patricia.py +0 -0
  15. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/crypto/__init__.py +0 -0
  16. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/crypto/ed25519.py +0 -0
  17. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/crypto/quadratic_form.py +0 -0
  18. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/crypto/wesolowski.py +0 -0
  19. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/crypto/x25519.py +0 -0
  20. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/format.py +0 -0
  21. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/lispeum/__init__.py +0 -0
  22. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/lispeum/parser.py +0 -0
  23. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/lispeum/tokenizer.py +0 -0
  24. {astreum-0.2.10 → astreum-0.2.12}/src/astreum/node.py +0 -0
  25. {astreum-0.2.10 → astreum-0.2.12}/src/astreum.egg-info/dependency_links.txt +0 -0
  26. {astreum-0.2.10 → astreum-0.2.12}/src/astreum.egg-info/requires.txt +0 -0
  27. {astreum-0.2.10 → astreum-0.2.12}/src/astreum.egg-info/top_level.txt +0 -0
  28. {astreum-0.2.10 → astreum-0.2.12}/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.10
3
+ Version: 0.2.12
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.10"
3
+ version = "0.2.12"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
File without changes
@@ -0,0 +1,249 @@
1
+ import blake3
2
+ from typing import Callable, Dict, List, Optional, Tuple
3
+ from astreum import format
4
+
5
+ class PatriciaNode:
6
+ def __init__(
7
+ self,
8
+ key_len: int,
9
+ key: bytes,
10
+ value: Optional[bytes],
11
+ child_0: Optional[bytes],
12
+ child_1: Optional[bytes]
13
+ ):
14
+ self.key_len = key_len
15
+ self.key = key
16
+ self.value = value
17
+ self.child_0 = child_0
18
+ self.child_1 = child_1
19
+ self._hash: bytes | None = None
20
+
21
+ def to_bytes(self) -> bytes:
22
+ return format.encode([self.key_len, self.key, self.value, self.child_0, self.child_1])
23
+
24
+ @classmethod
25
+ def from_bytes(cls, blob: bytes) -> "PatriciaNode":
26
+ key_len, key, value, child_0, child_1 = format.decode(blob)
27
+ return cls(key_len, key, value, child_0, child_1)
28
+
29
+ def hash(self) -> bytes:
30
+ if self._hash is None:
31
+ self._hash = blake3.blake3(self.to_bytes()).digest()
32
+ return self._hash
33
+
34
+ class PatriciaTrie:
35
+ def __init__(
36
+ self,
37
+ node_get: Callable[[bytes], Optional[bytes]],
38
+ root_hash: Optional[bytes] = None,
39
+ ) -> None:
40
+ self._node_get = node_get
41
+ self.nodes: Dict[bytes, PatriciaNode] = {}
42
+ self.root_hash: Optional[bytes] = root_hash
43
+
44
+ @staticmethod
45
+ def _bit(buf: bytes, idx: int) -> bool:
46
+ byte_i, offset = divmod(idx, 8)
47
+ return ((buf[byte_i] >> (7 - offset)) & 1) == 1
48
+
49
+ @classmethod
50
+ def _match_prefix(
51
+ cls,
52
+ prefix: bytes,
53
+ prefix_len: int,
54
+ key: bytes,
55
+ key_bit_offset: int,
56
+ ) -> bool:
57
+ if key_bit_offset + prefix_len > len(key) * 8:
58
+ return False
59
+
60
+ for i in range(prefix_len):
61
+ if cls._bit(prefix, i) != cls._bit(key, key_bit_offset + i):
62
+ return False
63
+ return True
64
+
65
+ def _fetch(self, h: bytes) -> Optional[PatriciaNode]:
66
+ node = self.nodes.get(h)
67
+ if node is None:
68
+ raw = self._node_get(h)
69
+ if raw is None:
70
+ return None
71
+ node = PatriciaNode.from_bytes(raw)
72
+ self.nodes[h] = node
73
+ return node
74
+
75
+ def get(self, key: bytes) -> Optional["PatriciaNode"]:
76
+ """Return the node that stores *key*, or ``None`` if absent."""
77
+ if self.root_hash is None:
78
+ return None
79
+
80
+ node = self._fetch(self.root_hash)
81
+ if node is None:
82
+ return None
83
+
84
+ key_pos = 0
85
+
86
+ while node is not None:
87
+ # 1️⃣ Verify that this node's (possibly sub‑byte) prefix matches.
88
+ if not self._match_prefix(node.key, node.key_len, key, key_pos):
89
+ return None
90
+ key_pos += node.key_len
91
+
92
+ # 2️⃣ If every bit of *key* has been matched, success only if the
93
+ # node actually stores a value.
94
+ if key_pos == len(key) * 8:
95
+ return node if node.value is not None else None
96
+
97
+ # 3️⃣ Decide which branch to follow using the next bit of *key*.
98
+ try:
99
+ next_bit = self._bit(key, key_pos)
100
+ except IndexError: # key ended prematurely
101
+ return None
102
+
103
+ child_hash = node.child_1 if next_bit else node.child_0
104
+ if child_hash is None: # dead end – key not present
105
+ return None
106
+
107
+ # 4️⃣ Fetch the child node via unified helper.
108
+ node = self._fetch(child_hash)
109
+ if node is None: # dangling pointer
110
+ return None
111
+
112
+ key_pos += 1 # we just consumed one routing bit
113
+
114
+ return None
115
+
116
+ def put(self, key: bytes, value: bytes) -> None:
117
+ """Insert or update ``key`` with ``value`` in‑place."""
118
+ total_bits = len(key) * 8
119
+
120
+ # S1 – Empty trie → create root leaf
121
+ if self.root_hash is None:
122
+ leaf = self._make_node(key, total_bits, value, None, None)
123
+ self.root_hash = leaf.hash()
124
+ return
125
+
126
+ # S2 – traversal bookkeeping
127
+ stack: List[Tuple[PatriciaNode, bytes, int]] = [] # (parent, parent_hash, dir_bit)
128
+ node = self._fetch(self.root_hash)
129
+ assert node is not None # root must exist now
130
+ key_pos = 0
131
+
132
+ # S4 – main descent loop
133
+ while True:
134
+ # 4.1 – prefix mismatch? → split
135
+ if not self._match_prefix(node.key, node.key_len, key, key_pos):
136
+ self._split_and_insert(node, stack, key, key_pos, value)
137
+ return
138
+
139
+ # 4.2 – consume this prefix
140
+ key_pos += node.key_len
141
+
142
+ # 4.3 – matched entire key → update value
143
+ if key_pos == total_bits:
144
+ self._invalidate_hash(node)
145
+ node.value = value
146
+ new_hash = node.hash()
147
+ self._bubble(stack, new_hash)
148
+ return
149
+
150
+ # 4.4 – routing bit
151
+ next_bit = self._bit(key, key_pos)
152
+ child_hash = node.child_1 if next_bit else node.child_0
153
+
154
+ # 4.6 – no child → easy append leaf
155
+ if child_hash is None:
156
+ self._append_leaf(node, next_bit, key, key_pos, value, stack)
157
+ return
158
+
159
+ # 4.7 – push current node onto stack
160
+ stack.append((node, node.hash(), int(next_bit)))
161
+
162
+ # 4.8 – fetch child and continue
163
+ node = self._fetch(child_hash)
164
+ if node is None:
165
+ # Dangling pointer – treat as append missing leaf
166
+ self._append_leaf(stack[-1][0], next_bit, key, key_pos, value, stack[:-1])
167
+ return
168
+ key_pos += 1 # consumed routing bit
169
+
170
+ def _append_leaf(
171
+ self,
172
+ parent: PatriciaNode,
173
+ dir_bit: bool,
174
+ key: bytes,
175
+ key_pos: int,
176
+ value: bytes,
177
+ stack: List[Tuple[PatriciaNode, bytes, int]],
178
+ ) -> None:
179
+ # key_pos points to routing bit; leaf stores the *rest* after that bit
180
+ tail_len = len(key) * 8 - (key_pos + 1)
181
+ tail_bits, tail_len = self._bit_slice(key, key_pos + 1, tail_len)
182
+ leaf = self._make_node(tail_bits, tail_len, value, None, None)
183
+
184
+ # attach
185
+ if dir_bit:
186
+ parent.child_1 = leaf.hash()
187
+ else:
188
+ parent.child_0 = leaf.hash()
189
+ self._invalidate_hash(parent)
190
+ new_parent_hash = parent.hash()
191
+ self._bubble(stack, new_parent_hash)
192
+
193
+ def _split_and_insert(
194
+ self,
195
+ node: PatriciaNode,
196
+ stack: List[Tuple[PatriciaNode, bytes, int]],
197
+ key: bytes,
198
+ key_pos: int,
199
+ value: bytes,
200
+ ) -> None:
201
+ """Split ``node`` at first divergent bit and insert new leaf for *key*."""
202
+ # Compute LCP between node.key and remaining key bits
203
+ max_lcp = min(node.key_len, len(key) * 8 - key_pos)
204
+ lcp = 0
205
+ while lcp < max_lcp and self._bit(node.key, lcp) == self._bit(key, key_pos + lcp):
206
+ lcp += 1
207
+
208
+ # Common prefix bits → new internal node
209
+ common_bits, common_len = self._bit_slice(node.key, 0, lcp)
210
+ internal = self._make_node(common_bits, common_len, None, None, None)
211
+
212
+ # Trim old node prefix
213
+ old_suffix_bits, old_suffix_len = self._bit_slice(node.key, lcp, node.key_len - lcp)
214
+ node.key = old_suffix_bits
215
+ node.key_len = old_suffix_len
216
+ self._invalidate_hash(node) # will be re‑hashed when attached
217
+ old_div_bit = self._bit(node.key, 0) if old_suffix_len > 0 else False
218
+
219
+ # New key leaf
220
+ new_key_tail_len = len(key) * 8 - (key_pos + lcp + 1)
221
+ new_tail_bits, new_tail_len = self._bit_slice(key, key_pos + lcp + 1, new_key_tail_len)
222
+ leaf = self._make_node(new_tail_bits, new_tail_len, value, None, None)
223
+ new_div_bit = self._bit(key, key_pos + lcp)
224
+
225
+ # Attach children to internal
226
+ if old_div_bit:
227
+ internal.child_1 = node.hash()
228
+ internal.child_0 = leaf.hash() if not new_div_bit else internal.child_0
229
+ else:
230
+ internal.child_0 = node.hash()
231
+ internal.child_1 = leaf.hash() if new_div_bit else internal.child_1
232
+ self._invalidate_hash(internal)
233
+ internal_hash = internal.hash()
234
+
235
+ # Rewire parent link or set as root
236
+ if not stack:
237
+ self.root_hash = internal_hash
238
+ return
239
+
240
+ parent, parent_old_hash, dir_bit = stack.pop()
241
+ if dir_bit == 0:
242
+ parent.child_0 = internal_hash
243
+ else:
244
+ parent.child_1 = internal_hash
245
+ self._invalidate_hash(parent)
246
+ parent_new_hash = parent.hash()
247
+ self._bubble(stack, parent_new_hash)
248
+
249
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.10
3
+ Version: 0.2.12
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
@@ -21,4 +21,6 @@ src/astreum/crypto/x25519.py
21
21
  src/astreum/lispeum/__init__.py
22
22
  src/astreum/lispeum/parser.py
23
23
  src/astreum/lispeum/tokenizer.py
24
+ src/astreum/utils/__init__.py
25
+ src/astreum/utils/patricia.py
24
26
  tests/test_node_machine.py
File without changes
File without changes
File without changes
File without changes
File without changes