astreum 0.2.13__py3-none-any.whl → 0.2.15__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,98 @@
1
+ from ..format import encode, decode
2
+ from ..crypto import ed25519
3
+ import blake3
4
+
5
+ class Block:
6
+ def __init__(
7
+ self,
8
+ number: int,
9
+ prev_block_hash: bytes,
10
+ timestamp: int,
11
+ accounts_hash: bytes,
12
+ transactions_total_fees: int,
13
+ transaction_limit: int,
14
+ transactions_root_hash: bytes,
15
+ vdf_difficulty: int,
16
+ vdf_output: bytes,
17
+ vdf_proof: bytes,
18
+ validator_pk: bytes,
19
+ signature: bytes,
20
+ ) -> None:
21
+ self.accounts_hash = accounts_hash
22
+ self.number = int(number)
23
+ self.prev_block_hash = prev_block_hash
24
+ self.timestamp = int(timestamp)
25
+ self.transactions_total_fees = int(transactions_total_fees)
26
+ self.transaction_limit = int(transaction_limit)
27
+ self.transactions_root_hash = transactions_root_hash
28
+ self.validator_pk = validator_pk
29
+ self.vdf_difficulty = int(vdf_difficulty)
30
+ self.vdf_output = vdf_output
31
+ self.vdf_proof = vdf_proof
32
+ self.signature = signature
33
+ self.body_hash = self._compute_body_hash()
34
+
35
+ def _body_fields_without_sig(self) -> list:
36
+ return [
37
+ self.accounts_hash,
38
+ self.number,
39
+ self.prev_block_hash,
40
+ self.timestamp,
41
+ self.transactions_total_fees,
42
+ self.transaction_limit,
43
+ self.transactions_root_hash,
44
+ self.validator_pk,
45
+ self.vdf_difficulty,
46
+ self.vdf_output,
47
+ self.vdf_proof,
48
+ ]
49
+
50
+ def _compute_body_hash(self) -> bytes:
51
+ return blake3.blake3(encode(self._body_fields_without_sig())).digest()
52
+
53
+ def to_bytes(self) -> bytes:
54
+ return encode(self._body_fields_without_sig() + [self.signature])
55
+
56
+ @classmethod
57
+ def from_bytes(cls, blob: bytes) -> "Block":
58
+ (
59
+ accounts_hash,
60
+ number,
61
+ prev_block_hash,
62
+ timestamp,
63
+ transactions_total_fees,
64
+ transaction_limit,
65
+ transactions_root_hash,
66
+ validator_pk,
67
+ vdf_difficulty,
68
+ vdf_output,
69
+ vdf_proof,
70
+ signature
71
+ ) = decode(blob)
72
+ return cls(
73
+ number=int(number),
74
+ prev_block_hash=prev_block_hash,
75
+ timestamp=int(timestamp),
76
+ accounts_hash=accounts_hash,
77
+ transactions_total_fees=int(transactions_total_fees),
78
+ transaction_limit=int(transaction_limit),
79
+ transactions_root_hash=transactions_root_hash,
80
+ vdf_difficulty=int(vdf_difficulty),
81
+ vdf_output=vdf_output,
82
+ vdf_proof=vdf_proof,
83
+ validator_pk=validator_pk,
84
+ signature=signature,
85
+ )
86
+
87
+ @property
88
+ def hash(self) -> bytes:
89
+ return blake3.blake3(self.body_hash + self.signature).digest()
90
+
91
+ def verify_block_signature(self) -> bool:
92
+ try:
93
+ pub = ed25519.Ed25519PublicKey.from_public_bytes(self.validator_pk)
94
+ pub.verify(self.signature, self.body_hash)
95
+ return True
96
+ except Exception:
97
+ return False
98
+
@@ -0,0 +1,233 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable, Dict, List, Optional, Tuple
4
+
5
+ import blake3 # type: ignore
6
+ from ..format import encode, decode
7
+
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
+ def __init__(
18
+ self,
19
+ left: Optional[bytes],
20
+ right: Optional[bytes],
21
+ value: Optional[bytes],
22
+ ) -> None:
23
+ self.left = left
24
+ self.right = right
25
+ self.value = value
26
+ self._hash: Optional[bytes] = None
27
+
28
+ # ------------------------------------------------------------------
29
+ # serialisation helpers
30
+ # ------------------------------------------------------------------
31
+ def to_bytes(self) -> bytes:
32
+ return encode([self.left, self.right, self.value])
33
+
34
+ @classmethod
35
+ def from_bytes(cls, blob: bytes) -> "MerkleNode":
36
+ left, right, value = decode(blob)
37
+ return cls(left, right, value)
38
+
39
+ # ------------------------------------------------------------------
40
+ # content hash (blake3)
41
+ # ------------------------------------------------------------------
42
+ def _compute_hash(self) -> bytes:
43
+ if self.value is not None: # leaf
44
+ return blake3.blake3(self.value).digest()
45
+ left = self.left or b""
46
+ right = self.right or b""
47
+ return blake3.blake3(left + right).digest()
48
+
49
+ def hash(self) -> bytes:
50
+ if self._hash is None:
51
+ self._hash = self._compute_hash()
52
+ return self._hash
53
+
54
+
55
+ # ─────────────────────────────────────────────────────────────────────────────
56
+ # Merkle tree – fixed‑height, no dynamic growth
57
+ # ─────────────────────────────────────────────────────────────────────────────
58
+
59
+
60
+ 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
+ def __init__(
71
+ self,
72
+ node_get: Callable[[bytes], Optional[bytes]],
73
+ root_hash: Optional[bytes] = None,
74
+ height: Optional[int] = None,
75
+ ) -> None:
76
+ self._node_get = node_get
77
+ self.nodes: Dict[bytes, MerkleNode] = {}
78
+ self.root_hash = root_hash
79
+ self._height: Optional[int] = height
80
+
81
+ @classmethod
82
+ def from_leaves(
83
+ cls,
84
+ leaves: List[bytes],
85
+ node_get: Callable[[bytes], Optional[bytes]] | None = None,
86
+ ) -> "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
+ if not leaves:
93
+ raise ValueError("must supply at least one leaf")
94
+
95
+ node_get = node_get or (lambda _h: None)
96
+ tree = cls(node_get=node_get)
97
+
98
+ # Step 1 – create leaf nodes list[bytes]
99
+ level_hashes: List[bytes] = []
100
+ for val in leaves:
101
+ leaf = MerkleNode(None, None, val)
102
+ h = leaf.hash()
103
+ tree.nodes[h] = leaf
104
+ level_hashes.append(h)
105
+
106
+ height = 1 # current level (leaves)
107
+
108
+ # Step 2 – build upper levels until single root remains
109
+ while len(level_hashes) > 1:
110
+ next_level: List[bytes] = []
111
+ it = iter(level_hashes)
112
+ for left_hash in it:
113
+ try:
114
+ right_hash = next(it)
115
+ except StopIteration:
116
+ right_hash = None
117
+ parent = MerkleNode(left_hash, right_hash, None)
118
+ ph = parent.hash()
119
+ tree.nodes[ph] = parent
120
+ next_level.append(ph)
121
+ level_hashes = next_level
122
+ height += 1
123
+
124
+ tree.root_hash = level_hashes[0]
125
+ tree._height = height
126
+ return tree
127
+
128
+ # ------------------------------------------------------------------
129
+ # internal helpers
130
+ # ------------------------------------------------------------------
131
+ def _fetch(self, h: bytes | None) -> Optional[MerkleNode]:
132
+ if h is None:
133
+ return None
134
+ node = self.nodes.get(h)
135
+ if node is None:
136
+ raw = self._node_get(h)
137
+ if raw is None:
138
+ return None
139
+ node = MerkleNode.from_bytes(raw)
140
+ self.nodes[h] = node
141
+ return node
142
+
143
+ def _invalidate(self, node: MerkleNode) -> None:
144
+ node._hash = None # type: ignore[attr-defined]
145
+
146
+ def _ensure_height(self) -> None:
147
+ if self._height is None:
148
+ # Recompute by traversing leftmost branch
149
+ h = 0
150
+ nh = self.root_hash
151
+ while nh is not None:
152
+ node = self._fetch(nh)
153
+ nh = node.left if node and node.value is None else None
154
+ h += 1
155
+ self._height = h or 1
156
+
157
+ def _capacity(self) -> int:
158
+ self._ensure_height()
159
+ assert self._height is not None
160
+ return 1 << (self._height - 1)
161
+
162
+ def _path_bits(self, index: int) -> List[int]:
163
+ self._ensure_height()
164
+ assert self._height is not None
165
+ bits = []
166
+ for shift in range(self._height - 2, -1, -1):
167
+ bits.append((index >> shift) & 1)
168
+ return bits
169
+
170
+ # ------------------------------------------------------------------
171
+ # get / put
172
+ # ------------------------------------------------------------------
173
+ def get(self, index: int) -> Optional[bytes]:
174
+ if index < 0 or self.root_hash is None or index >= self._capacity():
175
+ return None
176
+
177
+ node_hash = self.root_hash
178
+ for bit in self._path_bits(index):
179
+ node = self._fetch(node_hash)
180
+ if node is None:
181
+ return None
182
+ node_hash = node.right if bit else node.left
183
+ if node_hash is None:
184
+ return None
185
+ leaf = self._fetch(node_hash)
186
+ return leaf.value if leaf else None
187
+
188
+ 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
+ """
194
+ if index < 0:
195
+ raise IndexError("negative index")
196
+ if self.root_hash is None:
197
+ raise IndexError("tree is empty – build it first with from_leaves()")
198
+ if index >= self._capacity():
199
+ raise IndexError("index beyond tree capacity")
200
+
201
+ node_hash = self.root_hash
202
+ stack: List[Tuple[MerkleNode, bytes, bool]] = []
203
+ for bit in self._path_bits(index):
204
+ node = self._fetch(node_hash)
205
+ if node is None:
206
+ raise IndexError("missing node along path")
207
+ went_right = bool(bit)
208
+ child_hash = node.right if went_right else node.left
209
+ if child_hash is None:
210
+ raise IndexError("path leads into non‑existent branch")
211
+ stack.append((node, node.hash(), went_right))
212
+ node_hash = child_hash
213
+
214
+ leaf = self._fetch(node_hash)
215
+ if leaf is None or leaf.value is None:
216
+ raise IndexError("target leaf missing")
217
+ leaf.value = value
218
+ self._invalidate(leaf)
219
+ new_hash = leaf.hash()
220
+
221
+ # bubble updated hashes
222
+ for parent, old_hash, went_right in reversed(stack):
223
+ if went_right:
224
+ parent.right = new_hash
225
+ else:
226
+ parent.left = new_hash
227
+ self._invalidate(parent)
228
+ new_hash = parent.hash()
229
+ if new_hash != old_hash:
230
+ del self.nodes[old_hash]
231
+ self.nodes[new_hash] = parent
232
+ self.root_hash = new_hash
233
+
astreum/node.py CHANGED
@@ -325,101 +325,7 @@ class Env:
325
325
  f"parent_id={self.parent_id})"
326
326
  )
327
327
 
328
- class Block:
329
- def __init__(
330
- self,
331
- *,
332
- number: int,
333
- prev_block_hash: bytes,
334
- timestamp: int,
335
- accounts_hash: bytes,
336
- total_astre_burned: int,
337
- tx_limit: int,
338
- tx_root: bytes,
339
- vdf_difficulty: int,
340
- vdf_output: bytes,
341
- vdf_proof: bytes,
342
- validator_pk: bytes,
343
- signature: bytes,
344
- ) -> None:
345
- self.accounts_hash = accounts_hash
346
- self.number = int(number)
347
- self.prev_block_hash = prev_block_hash
348
- self.timestamp = int(timestamp)
349
- self.total_astre_burned = int(total_astre_burned)
350
- self.tx_limit = int(tx_limit)
351
- self.tx_root = tx_root
352
- self.validator_pk = validator_pk
353
- self.vdf_difficulty = int(vdf_difficulty)
354
- self.vdf_output = vdf_output
355
- self.vdf_proof = vdf_proof
356
- self.signature = signature
357
- self.body_hash = self._compute_body_hash()
358
-
359
- def _body_fields_without_sig(self) -> list:
360
- return [
361
- self.accounts_hash,
362
- self.number,
363
- self.prev_block_hash,
364
- self.timestamp,
365
- self.total_astre_burned,
366
- self.tx_limit,
367
- self.tx_root,
368
- self.validator_pk,
369
- self.vdf_difficulty,
370
- self.vdf_output,
371
- self.vdf_proof,
372
- ]
373
-
374
- def _compute_body_hash(self) -> bytes:
375
- return blake3.blake3(encode(self._body_fields_without_sig())).digest()
376
-
377
- def to_bytes(self) -> bytes:
378
- return encode(self._body_fields_without_sig() + [self.signature])
379
-
380
- @classmethod
381
- def from_bytes(cls, blob: bytes) -> "Block":
382
- (
383
- accounts_hash,
384
- number,
385
- prev_block_hash,
386
- timestamp,
387
- total_astre_burned,
388
- tx_limit,
389
- tx_root,
390
- validator_pk,
391
- vdf_difficulty,
392
- vdf_output,
393
- vdf_proof,
394
- signature
395
- ) = decode(blob)
396
- return cls(
397
- number=int(number),
398
- prev_block_hash=prev_block_hash,
399
- timestamp=int(timestamp),
400
- accounts_hash=accounts_hash,
401
- total_astre_burned=int(total_astre_burned),
402
- tx_limit=int(tx_limit),
403
- tx_root=tx_root,
404
- vdf_difficulty=int(vdf_difficulty),
405
- vdf_output=vdf_output,
406
- vdf_proof=vdf_proof,
407
- validator_pk=validator_pk,
408
- signature=signature,
409
- )
410
-
411
- @property
412
- def hash(self) -> bytes:
413
- return blake3.blake3(self.body_hash + self.signature).digest()
414
328
 
415
- def verify_block_signature(self) -> bool:
416
- try:
417
- pub = ed25519.Ed25519PublicKey.from_public_bytes(self.validator_pk)
418
- pub.verify(self.signature, self.body_hash)
419
- return True
420
- except Exception:
421
- return False
422
-
423
329
  class Node:
424
330
  def __init__(self, config: dict = {}):
425
331
  self._machine_setup()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.13
3
+ Version: 0.2.15
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,10 +1,6 @@
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=B0v9KIoL16TmzQloDdUkPk7e-3_aDc2FS3-ggu1HtB0,49116
4
- astreum/_node/__init__.py,sha256=7yz1YHo0DCUgUQvJf75qdUo_ocl5-XZRU-Vc2NhcvJs,18639
5
- astreum/_node/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- astreum/_node/storage/merkle.py,sha256=XCQBrHbwI0FuPTCUwHOy-Kva3uWbvCdw_-13hRPf1UI,10219
7
- astreum/_node/storage/patricia.py,sha256=tynxn_qETCU9X7yJdeh_0GHpC8Pzcoq4CWrSZlMUeRc,11546
3
+ astreum/node.py,sha256=dPloCXuDyIn3-KDqxlgl3jxsonJlFMLi_quwJRsoLC8,46259
8
4
  astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
5
  astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
10
6
  astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
@@ -14,10 +10,12 @@ astreum/lispeum/__init__.py,sha256=K-NDzIjtIsXzC9X7lnYvlvIaVxjFcY7WNsgLIE3DH3U,5
14
10
  astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
15
11
  astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
16
12
  astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ astreum/models/block.py,sha256=NKYyxL6_BrtXRgcgIrnkYsobX0Z_bGqQT-fQ_09zEOo,3226
14
+ astreum/models/merkle.py,sha256=ceH4yJlt82XDTXe46hyoU88QIaGVOsVDsBZeDnOJYv8,8590
17
15
  astreum/models/patricia.py,sha256=D7UVU4b6Yvn2_McI35VoMEbpqwR8OmZon5LGoUSRADo,8913
18
16
  astreum/models/transaction.py,sha256=Vu0cfmh80S31nEbxyJfv1dk9_zqtgGNyMdhlM0uQF4E,2611
19
- astreum-0.2.13.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
20
- astreum-0.2.13.dist-info/METADATA,sha256=ZSKRGYPgPEaAgzNzKgYlEk2hWQP7nOZX3hNl5H76JFs,5478
21
- astreum-0.2.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- astreum-0.2.13.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
23
- astreum-0.2.13.dist-info/RECORD,,
17
+ astreum-0.2.15.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
18
+ astreum-0.2.15.dist-info/METADATA,sha256=7Nv0BCooioBjPEUhtTB1SdaNceBCgGf2iPZY2BLSx1E,5478
19
+ astreum-0.2.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ astreum-0.2.15.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
21
+ astreum-0.2.15.dist-info/RECORD,,