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

astreum/models/merkle.py CHANGED
@@ -2,18 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Callable, Dict, List, Optional, Tuple
4
4
 
5
- import blake3 # type: ignore
5
+ import blake3
6
6
  from ..format import encode, decode
7
7
 
8
8
  class MerkleNode:
9
- """A node in a binary Merkle tree.
10
-
11
- *Leaf* : ``value`` is **not** ``None``.
12
- *Interior*: ``value`` is ``None``.
13
- """
14
-
15
- __slots__ = ("left", "right", "value", "_hash")
16
-
17
9
  def __init__(
18
10
  self,
19
11
  left: Optional[bytes],
@@ -25,9 +17,6 @@ class MerkleNode:
25
17
  self.value = value
26
18
  self._hash: Optional[bytes] = None
27
19
 
28
- # ------------------------------------------------------------------
29
- # serialisation helpers
30
- # ------------------------------------------------------------------
31
20
  def to_bytes(self) -> bytes:
32
21
  return encode([self.left, self.right, self.value])
33
22
 
@@ -36,11 +25,8 @@ class MerkleNode:
36
25
  left, right, value = decode(blob)
37
26
  return cls(left, right, value)
38
27
 
39
- # ------------------------------------------------------------------
40
- # content hash (blake3)
41
- # ------------------------------------------------------------------
42
28
  def _compute_hash(self) -> bytes:
43
- if self.value is not None: # leaf
29
+ if self.value is not None:
44
30
  return blake3.blake3(self.value).digest()
45
31
  left = self.left or b""
46
32
  right = self.right or b""
@@ -52,21 +38,7 @@ class MerkleNode:
52
38
  return self._hash
53
39
 
54
40
 
55
- # ─────────────────────────────────────────────────────────────────────────────
56
- # Merkle tree – fixed‑height, no dynamic growth
57
- # ─────────────────────────────────────────────────────────────────────────────
58
-
59
-
60
41
  class MerkleTree:
61
- """Binary Merkle tree addressed by *leaf position* (0‑based).
62
-
63
- * The number of levels is fixed once the tree is built (``_height``).
64
- * ``put`` can **only update** existing leaves; it never adds capacity.
65
- """
66
-
67
- # ------------------------------------------------------------------
68
- # construction helpers
69
- # ------------------------------------------------------------------
70
42
  def __init__(
71
43
  self,
72
44
  node_get: Callable[[bytes], Optional[bytes]],
@@ -84,11 +56,6 @@ class MerkleTree:
84
56
  leaves: List[bytes],
85
57
  node_get: Callable[[bytes], Optional[bytes]] | None = None,
86
58
  ) -> "MerkleTree":
87
- """Build a complete tree from *leaves* (left‑to‑right order).
88
-
89
- Missing right siblings in the top levels are allowed; they are encoded
90
- as interior nodes with a single child.
91
- """
92
59
  if not leaves:
93
60
  raise ValueError("must supply at least one leaf")
94
61
 
@@ -125,9 +92,6 @@ class MerkleTree:
125
92
  tree._height = height
126
93
  return tree
127
94
 
128
- # ------------------------------------------------------------------
129
- # internal helpers
130
- # ------------------------------------------------------------------
131
95
  def _fetch(self, h: bytes | None) -> Optional[MerkleNode]:
132
96
  if h is None:
133
97
  return None
@@ -141,11 +105,10 @@ class MerkleTree:
141
105
  return node
142
106
 
143
107
  def _invalidate(self, node: MerkleNode) -> None:
144
- node._hash = None # type: ignore[attr-defined]
108
+ node._hash = None
145
109
 
146
110
  def _ensure_height(self) -> None:
147
111
  if self._height is None:
148
- # Recompute by traversing leftmost branch
149
112
  h = 0
150
113
  nh = self.root_hash
151
114
  while nh is not None:
@@ -186,11 +149,7 @@ class MerkleTree:
186
149
  return leaf.value if leaf else None
187
150
 
188
151
  def put(self, index: int, value: bytes) -> None:
189
- """Overwrite *value* at existing leaf *index*.
190
-
191
- Raises ``IndexError`` if *index* is outside current capacity **or** if
192
- the path to that leaf is missing in the stored structure.
193
- """
152
+ # 1 . input validation
194
153
  if index < 0:
195
154
  raise IndexError("negative index")
196
155
  if self.root_hash is None:
@@ -198,6 +157,7 @@ class MerkleTree:
198
157
  if index >= self._capacity():
199
158
  raise IndexError("index beyond tree capacity")
200
159
 
160
+ # 2 . walk down to the target leaf
201
161
  node_hash = self.root_hash
202
162
  stack: List[Tuple[MerkleNode, bytes, bool]] = []
203
163
  for bit in self._path_bits(index):
@@ -207,28 +167,39 @@ class MerkleTree:
207
167
  went_right = bool(bit)
208
168
  child_hash = node.right if went_right else node.left
209
169
  if child_hash is None:
210
- raise IndexError("path leads into nonexistent branch")
170
+ raise IndexError("path leads into non-existent branch")
211
171
  stack.append((node, node.hash(), went_right))
212
172
  node_hash = child_hash
213
173
 
174
+ # 3 . update the leaf
214
175
  leaf = self._fetch(node_hash)
215
176
  if leaf is None or leaf.value is None:
216
177
  raise IndexError("target leaf missing")
178
+
179
+ old_hash = leaf.hash()
217
180
  leaf.value = value
218
181
  self._invalidate(leaf)
219
182
  new_hash = leaf.hash()
183
+
184
+ if new_hash != old_hash:
185
+ self.nodes.pop(old_hash, None)
220
186
  self.nodes[new_hash] = leaf
221
-
222
- # bubble updated hashes
187
+
188
+ # 4 . bubble the change up
223
189
  for parent, old_hash, went_right in reversed(stack):
224
190
  if went_right:
225
191
  parent.right = new_hash
226
192
  else:
227
193
  parent.left = new_hash
194
+
228
195
  self._invalidate(parent)
229
196
  new_hash = parent.hash()
197
+
230
198
  if new_hash != old_hash:
231
- del self.nodes[old_hash]
232
- self.nodes[new_hash] = parent
199
+ self.nodes.pop(old_hash, None)
200
+ self.nodes[new_hash] = parent
201
+
202
+ # 5 . finalise the new root
233
203
  self.root_hash = new_hash
234
204
 
205
+
@@ -195,9 +195,12 @@ class PatriciaTrie:
195
195
 
196
196
  # 4.3 – matched entire key → update value
197
197
  if key_pos == total_bits:
198
- self._invalidate_hash(node)
198
+ old_hash = node.hash()
199
199
  node.value = value
200
+ self._invalidate_hash(node)
200
201
  new_hash = node.hash()
202
+ if new_hash != old_hash:
203
+ self.nodes.pop(old_hash, None)
201
204
  self.nodes[new_hash] = node
202
205
  self._bubble(stack, new_hash)
203
206
  return
@@ -233,12 +236,12 @@ class PatriciaTrie:
233
236
  value: bytes,
234
237
  stack: List[Tuple[PatriciaNode, bytes, int]],
235
238
  ) -> None:
236
- # key_pos points to routing bit; leaf stores the rest after that bit
237
239
  tail_len = len(key) * 8 - (key_pos + 1)
238
240
  tail_bits, tail_len = self._bit_slice(key, key_pos + 1, tail_len)
239
241
  leaf = self._make_node(tail_bits, tail_len, value, None, None)
240
242
 
241
- # attach to parent
243
+ old_parent_hash = parent.hash()
244
+
242
245
  if dir_bit:
243
246
  parent.child_1 = leaf.hash()
244
247
  else:
@@ -246,9 +249,12 @@ class PatriciaTrie:
246
249
 
247
250
  self._invalidate_hash(parent)
248
251
  new_parent_hash = parent.hash()
252
+ if new_parent_hash != old_parent_hash:
253
+ self.nodes.pop(old_parent_hash, None)
249
254
  self.nodes[new_parent_hash] = parent
250
255
  self._bubble(stack, new_parent_hash)
251
256
 
257
+
252
258
  def _split_and_insert(
253
259
  self,
254
260
  node: PatriciaNode,
@@ -277,10 +283,14 @@ class PatriciaTrie:
277
283
  lcp + 1, # start *after* divergence bit
278
284
  node.key_len - lcp - 1 # may be zero
279
285
  )
286
+ old_node_hash = node.hash()
287
+
280
288
  node.key = old_suffix_bits
281
289
  node.key_len = old_suffix_len
282
290
  self._invalidate_hash(node)
283
291
  new_node_hash = node.hash()
292
+ if new_node_hash != old_node_hash:
293
+ self.nodes.pop(old_node_hash, None)
284
294
  self.nodes[new_node_hash] = node
285
295
 
286
296
  # ➍—new leaf for the key being inserted (unchanged)
@@ -341,15 +351,21 @@ class PatriciaTrie:
341
351
  """
342
352
  while stack:
343
353
  parent, old_hash, dir_bit = stack.pop()
354
+
344
355
  if dir_bit == 0:
345
356
  parent.child_0 = new_hash
346
357
  else:
347
358
  parent.child_1 = new_hash
359
+
348
360
  self._invalidate_hash(parent)
349
361
  new_hash = parent.hash()
362
+ if new_hash != old_hash:
363
+ self.nodes.pop(old_hash, None)
350
364
  self.nodes[new_hash] = parent
365
+
351
366
  self.root_hash = new_hash
352
367
 
368
+
353
369
  def _bit_slice(
354
370
  self,
355
371
  buf: bytes,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.18
3
+ Version: 0.2.20
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
@@ -12,11 +12,11 @@ astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,
12
12
  astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  astreum/models/account.py,sha256=sHujGSwtV13rvOGJ5LZXuMrJ4F9XUdvyuWKz-zJ9lkE,2986
14
14
  astreum/models/block.py,sha256=YIY3qLneFM7OooY29khLDEs3NIEA7y8VhlXitflkxTo,4564
15
- astreum/models/merkle.py,sha256=E850dITZmuQS2LmQt2yA_luy0a3QjHgSbL2ibQAmYJc,8627
16
- astreum/models/patricia.py,sha256=WtfwVufZazFTOjVDnSDI0I3ghetm3GBzAwqNQevOlJ8,12974
15
+ astreum/models/merkle.py,sha256=merV3rx2iRfzvglV6gNusrJf7OMbcVV854T-DUWCC64,6733
16
+ astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
17
17
  astreum/models/transaction.py,sha256=yBarvRK2ybMAHHEQPZpubGO7gms4U9k093xQGNQHQ4Q,3043
18
- astreum-0.2.18.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
19
- astreum-0.2.18.dist-info/METADATA,sha256=FlvqUo_qpvlxbOzKHONdWYS00SwvCUopk73M1A4AyRA,5478
20
- astreum-0.2.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- astreum-0.2.18.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
22
- astreum-0.2.18.dist-info/RECORD,,
18
+ astreum-0.2.20.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
19
+ astreum-0.2.20.dist-info/METADATA,sha256=rX7NJZZEC1nqxcg84iHqzFg1wY-PB7aF3eePbLXUu9Q,5478
20
+ astreum-0.2.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ astreum-0.2.20.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
22
+ astreum-0.2.20.dist-info/RECORD,,