astreum 0.2.41__py3-none-any.whl → 0.3.1__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.
- astreum/__init__.py +16 -7
- astreum/{_communication → communication}/__init__.py +3 -3
- astreum/communication/handlers/handshake.py +83 -0
- astreum/communication/handlers/ping.py +48 -0
- astreum/communication/handlers/storage_request.py +81 -0
- astreum/communication/models/__init__.py +0 -0
- astreum/{_communication → communication/models}/message.py +1 -0
- astreum/communication/models/peer.py +23 -0
- astreum/{_communication → communication/models}/route.py +45 -8
- astreum/{_communication → communication}/setup.py +46 -95
- astreum/communication/start.py +38 -0
- astreum/consensus/__init__.py +20 -0
- astreum/consensus/genesis.py +66 -0
- astreum/consensus/models/__init__.py +0 -0
- astreum/consensus/models/account.py +84 -0
- astreum/consensus/models/accounts.py +72 -0
- astreum/consensus/models/block.py +364 -0
- astreum/{_consensus → consensus/models}/chain.py +7 -7
- astreum/{_consensus → consensus/models}/fork.py +8 -8
- astreum/consensus/models/receipt.py +98 -0
- astreum/consensus/models/transaction.py +213 -0
- astreum/{_consensus → consensus}/setup.py +26 -11
- astreum/consensus/start.py +68 -0
- astreum/consensus/validator.py +95 -0
- astreum/{_consensus → consensus}/workers/discovery.py +20 -1
- astreum/consensus/workers/validation.py +291 -0
- astreum/{_consensus → consensus}/workers/verify.py +32 -3
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/machine/evaluations/high_evaluation.py +237 -0
- astreum/machine/evaluations/low_evaluation.py +281 -0
- astreum/machine/evaluations/script_evaluation.py +27 -0
- astreum/machine/models/__init__.py +0 -0
- astreum/machine/models/environment.py +31 -0
- astreum/machine/models/expression.py +218 -0
- astreum/{_lispeum → machine}/parser.py +26 -31
- astreum/machine/tokenizer.py +90 -0
- astreum/node.py +73 -781
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +69 -0
- astreum/storage/actions/set.py +132 -0
- astreum/storage/models/atom.py +107 -0
- astreum/{_storage/patricia.py → storage/models/trie.py} +236 -177
- astreum/storage/setup.py +44 -15
- astreum/utils/bytes.py +24 -0
- astreum/utils/integer.py +25 -0
- astreum/utils/logging.py +219 -0
- astreum-0.3.1.dist-info/METADATA +160 -0
- astreum-0.3.1.dist-info/RECORD +62 -0
- astreum/_communication/peer.py +0 -11
- astreum/_consensus/__init__.py +0 -20
- astreum/_consensus/account.py +0 -170
- astreum/_consensus/accounts.py +0 -67
- astreum/_consensus/block.py +0 -328
- astreum/_consensus/genesis.py +0 -141
- astreum/_consensus/receipt.py +0 -177
- astreum/_consensus/transaction.py +0 -192
- astreum/_consensus/workers/validation.py +0 -122
- astreum/_lispeum/__init__.py +0 -16
- astreum/_lispeum/environment.py +0 -13
- astreum/_lispeum/expression.py +0 -37
- astreum/_lispeum/high_evaluation.py +0 -177
- astreum/_lispeum/low_evaluation.py +0 -123
- astreum/_lispeum/tokenizer.py +0 -22
- astreum/_node.py +0 -58
- astreum/_storage/__init__.py +0 -5
- astreum/_storage/atom.py +0 -117
- astreum/format.py +0 -75
- astreum/models/block.py +0 -441
- astreum/models/merkle.py +0 -205
- astreum/models/patricia.py +0 -393
- astreum/storage/object.py +0 -68
- astreum-0.2.41.dist-info/METADATA +0 -146
- astreum-0.2.41.dist-info/RECORD +0 -53
- /astreum/{models → communication/handlers}/__init__.py +0 -0
- /astreum/{_communication → communication/models}/ping.py +0 -0
- /astreum/{_communication → communication}/util.py +0 -0
- /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
- /astreum/{_lispeum → machine/models}/meter.py +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
astreum/models/patricia.py
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import blake3
|
|
2
|
-
from typing import Callable, Dict, List, Optional, Tuple
|
|
3
|
-
from ..format import encode, decode
|
|
4
|
-
|
|
5
|
-
class PatriciaNode:
|
|
6
|
-
"""
|
|
7
|
-
A node in a compressed-key Patricia trie.
|
|
8
|
-
|
|
9
|
-
Attributes:
|
|
10
|
-
key_len (int): Number of bits in the `key` prefix that are meaningful.
|
|
11
|
-
key (bytes): The MSB-aligned bit prefix (zero-padded in last byte).
|
|
12
|
-
value (Optional[bytes]): Stored payload (None for internal nodes).
|
|
13
|
-
child_0 (Optional[bytes]): Hash pointer for next-bit == 0.
|
|
14
|
-
child_1 (Optional[bytes]): Hash pointer for next-bit == 1.
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
def __init__(
|
|
18
|
-
self,
|
|
19
|
-
key_len: int,
|
|
20
|
-
key: bytes,
|
|
21
|
-
value: Optional[bytes],
|
|
22
|
-
child_0: Optional[bytes],
|
|
23
|
-
child_1: Optional[bytes]
|
|
24
|
-
):
|
|
25
|
-
self.key_len = key_len
|
|
26
|
-
self.key = key
|
|
27
|
-
self.value = value
|
|
28
|
-
self.child_0 = child_0
|
|
29
|
-
self.child_1 = child_1
|
|
30
|
-
self._hash: Optional[bytes] = None
|
|
31
|
-
|
|
32
|
-
def to_bytes(self) -> bytes:
|
|
33
|
-
"""
|
|
34
|
-
Serialize node fields to bytes using the shared encode format.
|
|
35
|
-
- key_len in a single byte.
|
|
36
|
-
- None pointers/values as empty bytes.
|
|
37
|
-
"""
|
|
38
|
-
key_len_b = self.key_len.to_bytes(1, "big")
|
|
39
|
-
val_b = self.value if self.value is not None else b""
|
|
40
|
-
c0_b = self.child_0 if self.child_0 is not None else b""
|
|
41
|
-
c1_b = self.child_1 if self.child_1 is not None else b""
|
|
42
|
-
return encode([key_len_b, self.key, val_b, c0_b, c1_b])
|
|
43
|
-
|
|
44
|
-
@classmethod
|
|
45
|
-
def from_bytes(cls, blob: bytes) -> "PatriciaNode":
|
|
46
|
-
"""
|
|
47
|
-
Deserialize a blob produced by to_bytes() back into a PatriciaNode.
|
|
48
|
-
Empty bytes are converted back to None for value/children.
|
|
49
|
-
"""
|
|
50
|
-
key_len_b, key, val_b, c0_b, c1_b = decode(blob)
|
|
51
|
-
key_len = key_len_b[0]
|
|
52
|
-
value = val_b if val_b else None
|
|
53
|
-
child_0 = c0_b if c0_b else None
|
|
54
|
-
child_1 = c1_b if c1_b else None
|
|
55
|
-
return cls(key_len, key, value, child_0, child_1)
|
|
56
|
-
|
|
57
|
-
def hash(self) -> bytes:
|
|
58
|
-
"""
|
|
59
|
-
Compute and cache the BLAKE3 hash of this node's serialized form.
|
|
60
|
-
"""
|
|
61
|
-
if self._hash is None:
|
|
62
|
-
self._hash = blake3.blake3(self.to_bytes()).digest()
|
|
63
|
-
return self._hash
|
|
64
|
-
|
|
65
|
-
class PatriciaTrie:
|
|
66
|
-
"""
|
|
67
|
-
A compressed-key Patricia trie supporting get and put.
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
def __init__(
|
|
71
|
-
self,
|
|
72
|
-
node_get: Callable[[bytes], Optional[bytes]],
|
|
73
|
-
root_hash: Optional[bytes] = None,
|
|
74
|
-
) -> None:
|
|
75
|
-
"""
|
|
76
|
-
:param node_get: function mapping node-hash -> serialized node bytes (or None)
|
|
77
|
-
:param root_hash: optional hash of existing root node
|
|
78
|
-
"""
|
|
79
|
-
self._node_get = node_get
|
|
80
|
-
self.nodes: Dict[bytes, PatriciaNode] = {}
|
|
81
|
-
self.root_hash = root_hash
|
|
82
|
-
|
|
83
|
-
@staticmethod
|
|
84
|
-
def _bit(buf: bytes, idx: int) -> bool:
|
|
85
|
-
"""
|
|
86
|
-
Return the bit at position `idx` (MSB-first) from `buf`.
|
|
87
|
-
"""
|
|
88
|
-
byte_i, offset = divmod(idx, 8)
|
|
89
|
-
return ((buf[byte_i] >> (7 - offset)) & 1) == 1
|
|
90
|
-
|
|
91
|
-
@classmethod
|
|
92
|
-
def _match_prefix(
|
|
93
|
-
cls,
|
|
94
|
-
prefix: bytes,
|
|
95
|
-
prefix_len: int,
|
|
96
|
-
key: bytes,
|
|
97
|
-
key_bit_offset: int,
|
|
98
|
-
) -> bool:
|
|
99
|
-
"""
|
|
100
|
-
Check whether the `prefix_len` bits of `prefix` match
|
|
101
|
-
bits in `key` starting at `key_bit_offset`.
|
|
102
|
-
"""
|
|
103
|
-
total_bits = len(key) * 8
|
|
104
|
-
if key_bit_offset + prefix_len > total_bits:
|
|
105
|
-
return False
|
|
106
|
-
for i in range(prefix_len):
|
|
107
|
-
if cls._bit(prefix, i) != cls._bit(key, key_bit_offset + i):
|
|
108
|
-
return False
|
|
109
|
-
return True
|
|
110
|
-
|
|
111
|
-
def _fetch(self, h: bytes) -> Optional[PatriciaNode]:
|
|
112
|
-
"""
|
|
113
|
-
Fetch a node by hash, using in-memory cache then external node_get.
|
|
114
|
-
"""
|
|
115
|
-
node = self.nodes.get(h)
|
|
116
|
-
if node is None:
|
|
117
|
-
raw = self._node_get(h)
|
|
118
|
-
if raw is None:
|
|
119
|
-
return None
|
|
120
|
-
node = PatriciaNode.from_bytes(raw)
|
|
121
|
-
self.nodes[h] = node
|
|
122
|
-
return node
|
|
123
|
-
|
|
124
|
-
def get(self, key: bytes) -> Optional[bytes]:
|
|
125
|
-
"""
|
|
126
|
-
Return the stored value for `key`, or None if absent.
|
|
127
|
-
"""
|
|
128
|
-
# Empty trie?
|
|
129
|
-
if self.root_hash is None:
|
|
130
|
-
return None
|
|
131
|
-
|
|
132
|
-
node = self._fetch(self.root_hash)
|
|
133
|
-
if node is None:
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
key_pos = 0 # bit offset into key
|
|
137
|
-
|
|
138
|
-
while node is not None:
|
|
139
|
-
# 1) Check that this node's prefix matches the key here
|
|
140
|
-
if not self._match_prefix(node.key, node.key_len, key, key_pos):
|
|
141
|
-
return None
|
|
142
|
-
key_pos += node.key_len
|
|
143
|
-
|
|
144
|
-
# 2) If we've consumed all bits of the search key:
|
|
145
|
-
if key_pos == len(key) * 8:
|
|
146
|
-
# Return value only if this node actually stores one
|
|
147
|
-
return node.value
|
|
148
|
-
|
|
149
|
-
# 3) Decide which branch to follow via next bit
|
|
150
|
-
try:
|
|
151
|
-
next_bit = self._bit(key, key_pos)
|
|
152
|
-
except IndexError:
|
|
153
|
-
return None
|
|
154
|
-
|
|
155
|
-
child_hash = node.child_1 if next_bit else node.child_0
|
|
156
|
-
if child_hash is None:
|
|
157
|
-
return None # dead end
|
|
158
|
-
|
|
159
|
-
# 4) Fetch child and continue descent
|
|
160
|
-
node = self._fetch(child_hash)
|
|
161
|
-
if node is None:
|
|
162
|
-
return None # dangling pointer
|
|
163
|
-
|
|
164
|
-
key_pos += 1 # consumed routing bit
|
|
165
|
-
|
|
166
|
-
return None
|
|
167
|
-
|
|
168
|
-
def put(self, key: bytes, value: bytes) -> None:
|
|
169
|
-
"""
|
|
170
|
-
Insert or update `key` with `value` in-place.
|
|
171
|
-
"""
|
|
172
|
-
total_bits = len(key) * 8
|
|
173
|
-
|
|
174
|
-
# S1 – Empty trie → create root leaf
|
|
175
|
-
if self.root_hash is None:
|
|
176
|
-
leaf = self._make_node(key, total_bits, value, None, None)
|
|
177
|
-
self.root_hash = leaf.hash()
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
# S2 – traversal bookkeeping
|
|
181
|
-
stack: List[Tuple[PatriciaNode, bytes, int]] = [] # (parent, parent_hash, dir_bit)
|
|
182
|
-
node = self._fetch(self.root_hash)
|
|
183
|
-
assert node is not None
|
|
184
|
-
key_pos = 0
|
|
185
|
-
|
|
186
|
-
# S4 – main descent loop
|
|
187
|
-
while True:
|
|
188
|
-
# 4.1 – prefix mismatch? → split
|
|
189
|
-
if not self._match_prefix(node.key, node.key_len, key, key_pos):
|
|
190
|
-
self._split_and_insert(node, stack, key, key_pos, value)
|
|
191
|
-
return
|
|
192
|
-
|
|
193
|
-
# 4.2 – consume this prefix
|
|
194
|
-
key_pos += node.key_len
|
|
195
|
-
|
|
196
|
-
# 4.3 – matched entire key → update value
|
|
197
|
-
if key_pos == total_bits:
|
|
198
|
-
old_hash = node.hash()
|
|
199
|
-
node.value = value
|
|
200
|
-
self._invalidate_hash(node)
|
|
201
|
-
new_hash = node.hash()
|
|
202
|
-
if new_hash != old_hash:
|
|
203
|
-
self.nodes.pop(old_hash, None)
|
|
204
|
-
self.nodes[new_hash] = node
|
|
205
|
-
self._bubble(stack, new_hash)
|
|
206
|
-
return
|
|
207
|
-
|
|
208
|
-
# 4.4 – routing bit
|
|
209
|
-
next_bit = self._bit(key, key_pos)
|
|
210
|
-
child_hash = node.child_1 if next_bit else node.child_0
|
|
211
|
-
|
|
212
|
-
# 4.6 – no child → easy append leaf
|
|
213
|
-
if child_hash is None:
|
|
214
|
-
self._append_leaf(node, next_bit, key, key_pos, value, stack)
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
# 4.7 – push current node onto stack
|
|
218
|
-
stack.append((node, node.hash(), int(next_bit)))
|
|
219
|
-
|
|
220
|
-
# 4.8 – fetch child and continue
|
|
221
|
-
node = self._fetch(child_hash)
|
|
222
|
-
if node is None:
|
|
223
|
-
# Dangling pointer: treat as missing child
|
|
224
|
-
parent, _, _ = stack[-1]
|
|
225
|
-
self._append_leaf(parent, next_bit, key, key_pos, value, stack[:-1])
|
|
226
|
-
return
|
|
227
|
-
|
|
228
|
-
key_pos += 1 # consumed routing bit
|
|
229
|
-
|
|
230
|
-
def _append_leaf(
|
|
231
|
-
self,
|
|
232
|
-
parent: PatriciaNode,
|
|
233
|
-
dir_bit: bool,
|
|
234
|
-
key: bytes,
|
|
235
|
-
key_pos: int,
|
|
236
|
-
value: bytes,
|
|
237
|
-
stack: List[Tuple[PatriciaNode, bytes, int]],
|
|
238
|
-
) -> None:
|
|
239
|
-
tail_len = len(key) * 8 - (key_pos + 1)
|
|
240
|
-
tail_bits, tail_len = self._bit_slice(key, key_pos + 1, tail_len)
|
|
241
|
-
leaf = self._make_node(tail_bits, tail_len, value, None, None)
|
|
242
|
-
|
|
243
|
-
old_parent_hash = parent.hash()
|
|
244
|
-
|
|
245
|
-
if dir_bit:
|
|
246
|
-
parent.child_1 = leaf.hash()
|
|
247
|
-
else:
|
|
248
|
-
parent.child_0 = leaf.hash()
|
|
249
|
-
|
|
250
|
-
self._invalidate_hash(parent)
|
|
251
|
-
new_parent_hash = parent.hash()
|
|
252
|
-
if new_parent_hash != old_parent_hash:
|
|
253
|
-
self.nodes.pop(old_parent_hash, None)
|
|
254
|
-
self.nodes[new_parent_hash] = parent
|
|
255
|
-
self._bubble(stack, new_parent_hash)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def _split_and_insert(
|
|
259
|
-
self,
|
|
260
|
-
node: PatriciaNode,
|
|
261
|
-
stack: List[Tuple[PatriciaNode, bytes, int]],
|
|
262
|
-
key: bytes,
|
|
263
|
-
key_pos: int,
|
|
264
|
-
value: bytes,
|
|
265
|
-
) -> None:
|
|
266
|
-
# ➊—find longest-common-prefix (lcp) as before …
|
|
267
|
-
max_lcp = min(node.key_len, len(key) * 8 - key_pos)
|
|
268
|
-
lcp = 0
|
|
269
|
-
while lcp < max_lcp and self._bit(node.key, lcp) == self._bit(key, key_pos + lcp):
|
|
270
|
-
lcp += 1
|
|
271
|
-
|
|
272
|
-
# divergence bit values (taken **before** we mutate node.key)
|
|
273
|
-
old_div_bit = self._bit(node.key, lcp)
|
|
274
|
-
new_div_bit = self._bit(key, key_pos + lcp)
|
|
275
|
-
|
|
276
|
-
# ➋—internal node that holds the common prefix
|
|
277
|
-
common_bits, common_len = self._bit_slice(node.key, 0, lcp)
|
|
278
|
-
internal = self._make_node(common_bits, common_len, None, None, None)
|
|
279
|
-
|
|
280
|
-
# ➌—trim the *existing* node’s prefix **after** the divergence bit
|
|
281
|
-
old_suffix_bits, old_suffix_len = self._bit_slice(
|
|
282
|
-
node.key,
|
|
283
|
-
lcp + 1, # start *after* divergence bit
|
|
284
|
-
node.key_len - lcp - 1 # may be zero
|
|
285
|
-
)
|
|
286
|
-
old_node_hash = node.hash()
|
|
287
|
-
|
|
288
|
-
node.key = old_suffix_bits
|
|
289
|
-
node.key_len = old_suffix_len
|
|
290
|
-
self._invalidate_hash(node)
|
|
291
|
-
new_node_hash = node.hash()
|
|
292
|
-
if new_node_hash != old_node_hash:
|
|
293
|
-
self.nodes.pop(old_node_hash, None)
|
|
294
|
-
self.nodes[new_node_hash] = node
|
|
295
|
-
|
|
296
|
-
# ➍—new leaf for the key being inserted (unchanged)
|
|
297
|
-
new_tail_len = len(key) * 8 - (key_pos + lcp + 1)
|
|
298
|
-
new_tail_bits, _ = self._bit_slice(key, key_pos + lcp + 1, new_tail_len)
|
|
299
|
-
leaf = self._make_node(new_tail_bits, new_tail_len, value, None, None)
|
|
300
|
-
|
|
301
|
-
# ➎—hang the two children off the internal node
|
|
302
|
-
if old_div_bit:
|
|
303
|
-
internal.child_1 = new_node_hash
|
|
304
|
-
internal.child_0 = leaf.hash()
|
|
305
|
-
else:
|
|
306
|
-
internal.child_0 = new_node_hash
|
|
307
|
-
internal.child_1 = leaf.hash()
|
|
308
|
-
|
|
309
|
-
# ➏—rehash up to the root (unchanged)
|
|
310
|
-
self._invalidate_hash(internal)
|
|
311
|
-
internal_hash = internal.hash()
|
|
312
|
-
self.nodes[internal_hash] = internal
|
|
313
|
-
|
|
314
|
-
if not stack:
|
|
315
|
-
self.root_hash = internal_hash
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
parent, _, dir_bit = stack.pop()
|
|
319
|
-
if dir_bit == 0:
|
|
320
|
-
parent.child_0 = internal_hash
|
|
321
|
-
else:
|
|
322
|
-
parent.child_1 = internal_hash
|
|
323
|
-
self._invalidate_hash(parent)
|
|
324
|
-
self._bubble(stack, parent.hash())
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
def _make_node(
|
|
328
|
-
self,
|
|
329
|
-
prefix_bits: bytes,
|
|
330
|
-
prefix_len: int,
|
|
331
|
-
value: Optional[bytes],
|
|
332
|
-
child0: Optional[bytes],
|
|
333
|
-
child1: Optional[bytes],
|
|
334
|
-
) -> PatriciaNode:
|
|
335
|
-
node = PatriciaNode(prefix_len, prefix_bits, value, child0, child1)
|
|
336
|
-
self.nodes[node.hash()] = node
|
|
337
|
-
return node
|
|
338
|
-
|
|
339
|
-
def _invalidate_hash(self, node: PatriciaNode) -> None:
|
|
340
|
-
"""Clear cached hash so next .hash() recomputes."""
|
|
341
|
-
node._hash = None # type: ignore
|
|
342
|
-
|
|
343
|
-
def _bubble(
|
|
344
|
-
self,
|
|
345
|
-
stack: List[Tuple[PatriciaNode, bytes, int]],
|
|
346
|
-
new_hash: bytes
|
|
347
|
-
) -> None:
|
|
348
|
-
"""
|
|
349
|
-
Propagate updated child-hash `new_hash` up the ancestor stack,
|
|
350
|
-
rebasing each parent's pointer, invalidating and re-hashing.
|
|
351
|
-
"""
|
|
352
|
-
while stack:
|
|
353
|
-
parent, old_hash, dir_bit = stack.pop()
|
|
354
|
-
|
|
355
|
-
if dir_bit == 0:
|
|
356
|
-
parent.child_0 = new_hash
|
|
357
|
-
else:
|
|
358
|
-
parent.child_1 = new_hash
|
|
359
|
-
|
|
360
|
-
self._invalidate_hash(parent)
|
|
361
|
-
new_hash = parent.hash()
|
|
362
|
-
if new_hash != old_hash:
|
|
363
|
-
self.nodes.pop(old_hash, None)
|
|
364
|
-
self.nodes[new_hash] = parent
|
|
365
|
-
|
|
366
|
-
self.root_hash = new_hash
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
def _bit_slice(
|
|
370
|
-
self,
|
|
371
|
-
buf: bytes,
|
|
372
|
-
start_bit: int,
|
|
373
|
-
length: int
|
|
374
|
-
) -> tuple[bytes, int]:
|
|
375
|
-
"""
|
|
376
|
-
Extract `length` bits from `buf` starting at `start_bit` (MSB-first),
|
|
377
|
-
returning (bytes, bit_len) with zero-padding.
|
|
378
|
-
"""
|
|
379
|
-
if length == 0:
|
|
380
|
-
return b"", 0
|
|
381
|
-
|
|
382
|
-
total = int.from_bytes(buf, "big")
|
|
383
|
-
bits_in_buf = len(buf) * 8
|
|
384
|
-
|
|
385
|
-
# shift so slice ends at LSB
|
|
386
|
-
shift = bits_in_buf - (start_bit + length)
|
|
387
|
-
slice_int = (total >> shift) & ((1 << length) - 1)
|
|
388
|
-
|
|
389
|
-
# left-align to MSB of first byte
|
|
390
|
-
pad = (8 - (length % 8)) % 8
|
|
391
|
-
slice_int <<= pad
|
|
392
|
-
byte_len = (length + 7) // 8
|
|
393
|
-
return slice_int.to_bytes(byte_len, "big"), length
|
astreum/storage/object.py
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
from enum import IntEnum
|
|
2
|
-
|
|
3
|
-
class ObjectRequestType(IntEnum):
|
|
4
|
-
OBJECT_GET = 0
|
|
5
|
-
OBJECT_PUT = 1
|
|
6
|
-
|
|
7
|
-
class ObjectRequest:
|
|
8
|
-
type: ObjectRequestType
|
|
9
|
-
data: bytes
|
|
10
|
-
hash: bytes
|
|
11
|
-
|
|
12
|
-
def __init__(self, type: ObjectRequestType, data: bytes, hash: bytes = None):
|
|
13
|
-
self.type = type
|
|
14
|
-
self.data = data
|
|
15
|
-
self.hash = hash
|
|
16
|
-
|
|
17
|
-
def to_bytes(self):
|
|
18
|
-
return [self.type.value] + self.hash + self.data
|
|
19
|
-
|
|
20
|
-
@classmethod
|
|
21
|
-
def from_bytes(cls, data: bytes) -> "ObjectRequest":
|
|
22
|
-
# need at least 1 byte for type + 32 bytes for hash
|
|
23
|
-
if len(data) < 1 + 32:
|
|
24
|
-
raise ValueError(f"Too short for ObjectRequest ({len(data)} bytes)")
|
|
25
|
-
|
|
26
|
-
type_val = data[0]
|
|
27
|
-
try:
|
|
28
|
-
req_type = ObjectRequestType(type_val)
|
|
29
|
-
except ValueError:
|
|
30
|
-
raise ValueError(f"Unknown ObjectRequestType: {type_val!r}")
|
|
31
|
-
|
|
32
|
-
hash_bytes = data[1:33]
|
|
33
|
-
payload = data[33:]
|
|
34
|
-
return cls(req_type, payload, hash_bytes)
|
|
35
|
-
|
|
36
|
-
class ObjectResponseType(IntEnum):
|
|
37
|
-
OBJECT_FOUND = 0
|
|
38
|
-
OBJECT_PROVIDER = 1
|
|
39
|
-
OBJECT_NEAREST_PEER = 2
|
|
40
|
-
|
|
41
|
-
class ObjectResponse:
|
|
42
|
-
type: ObjectResponseType
|
|
43
|
-
data: bytes
|
|
44
|
-
hash: bytes
|
|
45
|
-
|
|
46
|
-
def __init__(self, type: ObjectResponseType, data: bytes, hash: bytes = None):
|
|
47
|
-
self.type = type
|
|
48
|
-
self.data = data
|
|
49
|
-
self.hash = hash
|
|
50
|
-
|
|
51
|
-
def to_bytes(self):
|
|
52
|
-
return [self.type.value] + self.hash + self.data
|
|
53
|
-
|
|
54
|
-
@classmethod
|
|
55
|
-
def from_bytes(cls, data: bytes) -> "ObjectResponse":
|
|
56
|
-
# need at least 1 byte for type + 32 bytes for hash
|
|
57
|
-
if len(data) < 1 + 32:
|
|
58
|
-
raise ValueError(f"Too short to be a valid ObjectResponse ({len(data)} bytes)")
|
|
59
|
-
|
|
60
|
-
type_val = data[0]
|
|
61
|
-
try:
|
|
62
|
-
resp_type = ObjectResponseType(type_val)
|
|
63
|
-
except ValueError:
|
|
64
|
-
raise ValueError(f"Unknown ObjectResponseType: {type_val}")
|
|
65
|
-
|
|
66
|
-
hash_bytes = data[1:33]
|
|
67
|
-
payload = data[33:]
|
|
68
|
-
return cls(resp_type, payload, hash_bytes)
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: astreum
|
|
3
|
-
Version: 0.2.41
|
|
4
|
-
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
|
-
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
|
-
Project-URL: Homepage, https://github.com/astreum/lib
|
|
7
|
-
Project-URL: Issues, https://github.com/astreum/lib/issues
|
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.8
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
License-File: LICENSE
|
|
14
|
-
Requires-Dist: pycryptodomex==3.21.0
|
|
15
|
-
Requires-Dist: cryptography==44.0.2
|
|
16
|
-
Requires-Dist: blake3==1.0.4
|
|
17
|
-
Dynamic: license-file
|
|
18
|
-
|
|
19
|
-
# lib
|
|
20
|
-
|
|
21
|
-
Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
22
|
-
|
|
23
|
-
[View on PyPI](https://pypi.org/project/astreum/)
|
|
24
|
-
|
|
25
|
-
## Configuration
|
|
26
|
-
|
|
27
|
-
When initializing an `astreum.Node`, pass a dictionary with any of the options below. Only the parameters you want to override need to be present – everything else falls back to its default.
|
|
28
|
-
|
|
29
|
-
### Core Configuration
|
|
30
|
-
|
|
31
|
-
| Parameter | Type | Default | Description |
|
|
32
|
-
| --------------------------- | ---------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
-
| `machine-only` | bool | `True` | When **True** the node starts in *machine‑only* mode: no storage subsystem and no relay networking – only the Lispeum VM. Set to **False** to enable storage and relay features. |
|
|
34
|
-
| `relay_secret_key` | hex string | Auto‑generated | Ed25519 private key that identifies the node on the network. If omitted, a fresh keypair is generated and kept in‑memory. |
|
|
35
|
-
| `validation_secret_key` | hex string | `None` | X25519 private key that lets the node participate in the validation route. Leave unset for a non‑validator node. |
|
|
36
|
-
| `storage_path` | string | `None` | Directory where objects are persisted. If *None*, the node uses an in‑memory store. |
|
|
37
|
-
| `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
|
|
38
|
-
|
|
39
|
-
### Networking
|
|
40
|
-
|
|
41
|
-
| Parameter | Type | Default | Description |
|
|
42
|
-
| --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
|
|
43
|
-
| `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
|
|
44
|
-
| `incoming_port` | int | `7373` | UDP port the relay binds to. |
|
|
45
|
-
| `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
|
|
46
|
-
|
|
47
|
-
> **Note**
|
|
48
|
-
> The peer‑to‑peer *route* used for object discovery is always enabled.
|
|
49
|
-
> If `validation_secret_key` is provided the node automatically joins the validation route too.
|
|
50
|
-
|
|
51
|
-
### Example
|
|
52
|
-
|
|
53
|
-
```python
|
|
54
|
-
from astreum.node import Node
|
|
55
|
-
|
|
56
|
-
config = {
|
|
57
|
-
"machine-only": False, # run full node
|
|
58
|
-
"relay_secret_key": "ab…cd", # optional – hex encoded
|
|
59
|
-
"validation_secret_key": "12…34", # optional – validator
|
|
60
|
-
"storage_path": "./data/node1",
|
|
61
|
-
"storage_get_relay_timeout": 5,
|
|
62
|
-
"incoming_port": 7373,
|
|
63
|
-
"use_ipv6": False,
|
|
64
|
-
"bootstrap": [
|
|
65
|
-
("bootstrap.astreum.org", 7373),
|
|
66
|
-
("127.0.0.1", 7374)
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
node = Node(config)
|
|
71
|
-
# … your code …
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Lispeum Machine Quickstart
|
|
75
|
-
|
|
76
|
-
The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Lispeum source text, and the node tokenizes, parses, and **evaluates** the resulting AST inside an isolated environment.
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
80
|
-
|
|
81
|
-
import uuid
|
|
82
|
-
from astreum import Node, Env, Expr
|
|
83
|
-
|
|
84
|
-
# 1) Spin‑up a stand‑alone VM
|
|
85
|
-
node = Node()
|
|
86
|
-
|
|
87
|
-
# 2) Create an environment (simple manual setup)
|
|
88
|
-
env_id = uuid.uuid4()
|
|
89
|
-
node.environments[env_id] = Env()
|
|
90
|
-
|
|
91
|
-
# 3) Build a function value using a low‑level stack body via `sk`.
|
|
92
|
-
# Body does: $0 $1 add (i.e., a + b)
|
|
93
|
-
low_body = Expr.ListExpr([
|
|
94
|
-
Expr.Symbol("$0"), # a (first arg)
|
|
95
|
-
Expr.Symbol("$1"), # b (second arg)
|
|
96
|
-
Expr.Symbol("add"),
|
|
97
|
-
])
|
|
98
|
-
|
|
99
|
-
fn_body = Expr.ListExpr([
|
|
100
|
-
Expr.Symbol("a"),
|
|
101
|
-
Expr.Symbol("b"),
|
|
102
|
-
Expr.ListExpr([low_body, Expr.Symbol("sk")]),
|
|
103
|
-
])
|
|
104
|
-
|
|
105
|
-
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
106
|
-
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
107
|
-
|
|
108
|
-
# 4) Store under the name "int.add"
|
|
109
|
-
node.env_set(env_id, b"int.add", int_add_fn)
|
|
110
|
-
|
|
111
|
-
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
112
|
-
bound = node.env_get(env_id, b"int.add")
|
|
113
|
-
call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
|
|
114
|
-
res = node.high_eval(env_id, call)
|
|
115
|
-
|
|
116
|
-
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
117
|
-
print([b.value for b in res.elements]) # [3]
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Handling errors
|
|
121
|
-
|
|
122
|
-
Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
|
|
123
|
-
|
|
124
|
-
* Unterminated string literals are caught by `tokenize`.
|
|
125
|
-
* Unexpected or missing parentheses are caught by `parse`.
|
|
126
|
-
|
|
127
|
-
Catch the exception to provide developer‑friendly diagnostics:
|
|
128
|
-
|
|
129
|
-
```python
|
|
130
|
-
try:
|
|
131
|
-
tokens = tokenize(bad_source)
|
|
132
|
-
expr, _ = parse(tokens)
|
|
133
|
-
except ParseError as e:
|
|
134
|
-
print("Parse failed:", e)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Testing
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
python3 -m venv venv
|
|
143
|
-
source venv/bin/activate
|
|
144
|
-
pip install -e .
|
|
145
|
-
python3 -m unittest discover -s tests
|
|
146
|
-
```
|
astreum-0.2.41.dist-info/RECORD
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
astreum/__init__.py,sha256=9tzA27B_eG5wRF1SAWJIV7xTmCcR1QFc123b_cvFOa4,345
|
|
2
|
-
astreum/_node.py,sha256=3fpfULVs3MrPBR-ymlvPyuZMi8lv0a8JBnxPb2oFOTU,2214
|
|
3
|
-
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
4
|
-
astreum/node.py,sha256=MmlK3jaANTMB3ZAxR8IaSc82OS9meJmVawYIVURADbg,39689
|
|
5
|
-
astreum/_communication/__init__.py,sha256=XJui0yOcfAur4HKt-8sSRlwB-MSU1rchkuOAY-nKDOE,207
|
|
6
|
-
astreum/_communication/message.py,sha256=zNjUnt1roJRHMEoiLkcA7VK_TsHXok8PYEF7KTCqKnQ,3303
|
|
7
|
-
astreum/_communication/peer.py,sha256=DT2vJOzeLyyOj7vTDQ1u1lF5Vc7Epj0Hhie-YfDrzfA,425
|
|
8
|
-
astreum/_communication/ping.py,sha256=u_DQTZJsbMdYiDDqjdZDsLaN5na2m9WZjVeEM3zq9_Y,955
|
|
9
|
-
astreum/_communication/route.py,sha256=fbVXY0xF3O-k7dY9TuCsr6_XxD3m7Cb9ugVacQZ6GSk,2490
|
|
10
|
-
astreum/_communication/setup.py,sha256=TzXpc8dajV31BJhHQMMcb5UdaFrxb1xlYSj58vc_5mg,9072
|
|
11
|
-
astreum/_communication/util.py,sha256=bJ3td3naDzmCelAJQpLwiDMoRBkijQl9YLROjsWyOrI,1256
|
|
12
|
-
astreum/_consensus/__init__.py,sha256=gOCpvnIeO17CGjUGr0odaKNvGEggmDRXfT5IuyrYtcM,376
|
|
13
|
-
astreum/_consensus/account.py,sha256=d95h5c8kvmLD4CCyGRvaTJRQK7AESKrqDG8bCdbZOKg,5614
|
|
14
|
-
astreum/_consensus/accounts.py,sha256=NaLKDafsfq9SgFMtrYQpvAO7o0XbEwAIRhFl0dkY_Dk,2277
|
|
15
|
-
astreum/_consensus/block.py,sha256=v6U2y8br9PVKGn0Q8fLVOtEODlXayPtEd_Wqprem_TU,12358
|
|
16
|
-
astreum/_consensus/chain.py,sha256=WwUeLrdg7uj--ZsUxse6xFlzO2QeQQggyKSL49KhfU0,2768
|
|
17
|
-
astreum/_consensus/fork.py,sha256=dK5DtT9hWCj_rQs6MS1c1bcGBbkgVTBPIOudYbqS9vw,3825
|
|
18
|
-
astreum/_consensus/genesis.py,sha256=Xe9LA58cBz_IoutSJrtjeGDuFo0W-MVrWMkSdEwAVog,4760
|
|
19
|
-
astreum/_consensus/receipt.py,sha256=GPLspqVJHnyBr1cKmBoClsJyeEUecYmg3_acz4L4rRo,6031
|
|
20
|
-
astreum/_consensus/setup.py,sha256=agwqfOemdLqE0Z1zrPV2XHVmZ9sAxp8Bh4k6e2u-t8w,2385
|
|
21
|
-
astreum/_consensus/transaction.py,sha256=LK4oDe9EepOfzLxeXc19fEFDBoxY-aj_v-nsuRyET8g,6490
|
|
22
|
-
astreum/_consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
|
|
23
|
-
astreum/_consensus/workers/discovery.py,sha256=X1yjKGjLSApMJ9mgWbnc7N21ALD09khDf-in-M45Mis,1683
|
|
24
|
-
astreum/_consensus/workers/validation.py,sha256=WcHLOs2NLMTrJpErghBXWeW2aqtX3-xGPo-HcySdEZ8,4814
|
|
25
|
-
astreum/_consensus/workers/verify.py,sha256=uqmf2UICj_eBFeGDpGyzKQLtz9qeCbOtz5euwNqkZmw,1924
|
|
26
|
-
astreum/_lispeum/__init__.py,sha256=LAy2Z-gBBQlByBHRGUKaaQrOB7QzFEEyGRtInmwTpfU,304
|
|
27
|
-
astreum/_lispeum/environment.py,sha256=pJ0rjp9GoQxHhDiPIVei0jP7dZ_Pznso2O_tpp94-Ik,328
|
|
28
|
-
astreum/_lispeum/expression.py,sha256=io8tbCer_1TJee77yRbcNI5q-DPFGa8xZiC80tGvRRQ,1063
|
|
29
|
-
astreum/_lispeum/high_evaluation.py,sha256=7MwIeVZMPumYvKXn6Lkn-GrZhRNB6MjnUUWdiYT7Ei0,7952
|
|
30
|
-
astreum/_lispeum/low_evaluation.py,sha256=HgyCSAmL5K5SKq3Xw_BtbTZZUJbMg4-bVZW-A12glQ0,4373
|
|
31
|
-
astreum/_lispeum/meter.py,sha256=5q2PFW7_jmgKVM1-vwE4RRjMfPEthUA4iu1CwR-Axws,505
|
|
32
|
-
astreum/_lispeum/parser.py,sha256=WOW3sSZWkIzPEy3fYTyl0lrtkMxHL9zRrNSYztPDemQ,2271
|
|
33
|
-
astreum/_lispeum/tokenizer.py,sha256=P68uIj4aPKzjuCJ85jfzRi67QztpuXIOC1vvLQueBI4,552
|
|
34
|
-
astreum/_storage/__init__.py,sha256=EmKZNAZmo3UVE3ekOOuckwFnBVjpa0Sy8Oxg72Lgdxc,53
|
|
35
|
-
astreum/_storage/atom.py,sha256=YFjvfMhniInch13iaKGpw4CCxxgyWonniryb-Rfse4A,4177
|
|
36
|
-
astreum/_storage/patricia.py,sha256=Eh8AO2_J8WCai4kyuML_0Jb_zntgGlq-VPljnSjss10,14851
|
|
37
|
-
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
-
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
39
|
-
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
40
|
-
astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
|
|
41
|
-
astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
|
|
42
|
-
astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
-
astreum/models/block.py,sha256=7qKUhOpL26NiiLDq_ff7mJCNrbxWb_9jPTtMtjemy3Y,18126
|
|
44
|
-
astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
|
|
45
|
-
astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
|
|
46
|
-
astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
-
astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
|
|
48
|
-
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
49
|
-
astreum-0.2.41.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
50
|
-
astreum-0.2.41.dist-info/METADATA,sha256=03v5FuUofHssnDaviuCFvqWB1p3hz6lqvhM1t9HFhas,6181
|
|
51
|
-
astreum-0.2.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
52
|
-
astreum-0.2.41.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
53
|
-
astreum-0.2.41.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|