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.
Files changed (82) hide show
  1. astreum/__init__.py +16 -7
  2. astreum/{_communication → communication}/__init__.py +3 -3
  3. astreum/communication/handlers/handshake.py +83 -0
  4. astreum/communication/handlers/ping.py +48 -0
  5. astreum/communication/handlers/storage_request.py +81 -0
  6. astreum/communication/models/__init__.py +0 -0
  7. astreum/{_communication → communication/models}/message.py +1 -0
  8. astreum/communication/models/peer.py +23 -0
  9. astreum/{_communication → communication/models}/route.py +45 -8
  10. astreum/{_communication → communication}/setup.py +46 -95
  11. astreum/communication/start.py +38 -0
  12. astreum/consensus/__init__.py +20 -0
  13. astreum/consensus/genesis.py +66 -0
  14. astreum/consensus/models/__init__.py +0 -0
  15. astreum/consensus/models/account.py +84 -0
  16. astreum/consensus/models/accounts.py +72 -0
  17. astreum/consensus/models/block.py +364 -0
  18. astreum/{_consensus → consensus/models}/chain.py +7 -7
  19. astreum/{_consensus → consensus/models}/fork.py +8 -8
  20. astreum/consensus/models/receipt.py +98 -0
  21. astreum/consensus/models/transaction.py +213 -0
  22. astreum/{_consensus → consensus}/setup.py +26 -11
  23. astreum/consensus/start.py +68 -0
  24. astreum/consensus/validator.py +95 -0
  25. astreum/{_consensus → consensus}/workers/discovery.py +20 -1
  26. astreum/consensus/workers/validation.py +291 -0
  27. astreum/{_consensus → consensus}/workers/verify.py +32 -3
  28. astreum/machine/__init__.py +20 -0
  29. astreum/machine/evaluations/__init__.py +0 -0
  30. astreum/machine/evaluations/high_evaluation.py +237 -0
  31. astreum/machine/evaluations/low_evaluation.py +281 -0
  32. astreum/machine/evaluations/script_evaluation.py +27 -0
  33. astreum/machine/models/__init__.py +0 -0
  34. astreum/machine/models/environment.py +31 -0
  35. astreum/machine/models/expression.py +218 -0
  36. astreum/{_lispeum → machine}/parser.py +26 -31
  37. astreum/machine/tokenizer.py +90 -0
  38. astreum/node.py +73 -781
  39. astreum/storage/__init__.py +7 -0
  40. astreum/storage/actions/get.py +69 -0
  41. astreum/storage/actions/set.py +132 -0
  42. astreum/storage/models/atom.py +107 -0
  43. astreum/{_storage/patricia.py → storage/models/trie.py} +236 -177
  44. astreum/storage/setup.py +44 -15
  45. astreum/utils/bytes.py +24 -0
  46. astreum/utils/integer.py +25 -0
  47. astreum/utils/logging.py +219 -0
  48. astreum-0.3.1.dist-info/METADATA +160 -0
  49. astreum-0.3.1.dist-info/RECORD +62 -0
  50. astreum/_communication/peer.py +0 -11
  51. astreum/_consensus/__init__.py +0 -20
  52. astreum/_consensus/account.py +0 -170
  53. astreum/_consensus/accounts.py +0 -67
  54. astreum/_consensus/block.py +0 -328
  55. astreum/_consensus/genesis.py +0 -141
  56. astreum/_consensus/receipt.py +0 -177
  57. astreum/_consensus/transaction.py +0 -192
  58. astreum/_consensus/workers/validation.py +0 -122
  59. astreum/_lispeum/__init__.py +0 -16
  60. astreum/_lispeum/environment.py +0 -13
  61. astreum/_lispeum/expression.py +0 -37
  62. astreum/_lispeum/high_evaluation.py +0 -177
  63. astreum/_lispeum/low_evaluation.py +0 -123
  64. astreum/_lispeum/tokenizer.py +0 -22
  65. astreum/_node.py +0 -58
  66. astreum/_storage/__init__.py +0 -5
  67. astreum/_storage/atom.py +0 -117
  68. astreum/format.py +0 -75
  69. astreum/models/block.py +0 -441
  70. astreum/models/merkle.py +0 -205
  71. astreum/models/patricia.py +0 -393
  72. astreum/storage/object.py +0 -68
  73. astreum-0.2.41.dist-info/METADATA +0 -146
  74. astreum-0.2.41.dist-info/RECORD +0 -53
  75. /astreum/{models → communication/handlers}/__init__.py +0 -0
  76. /astreum/{_communication → communication/models}/ping.py +0 -0
  77. /astreum/{_communication → communication}/util.py +0 -0
  78. /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
  79. /astreum/{_lispeum → machine/models}/meter.py +0 -0
  80. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
  81. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
  82. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
@@ -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
- ```
@@ -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