astreum 0.1.6__tar.gz → 0.1.8__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 (44) hide show
  1. astreum-0.1.8/PKG-INFO +84 -0
  2. astreum-0.1.8/README.md +70 -0
  3. {astreum-0.1.6 → astreum-0.1.8}/pyproject.toml +1 -1
  4. astreum-0.1.8/src/astreum/lispeum/storage.py +457 -0
  5. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/__init__.py +49 -4
  6. astreum-0.1.8/src/astreum.egg-info/PKG-INFO +84 -0
  7. {astreum-0.1.6 → astreum-0.1.8}/src/astreum.egg-info/SOURCES.txt +1 -0
  8. astreum-0.1.6/PKG-INFO +0 -24
  9. astreum-0.1.6/README.md +0 -10
  10. astreum-0.1.6/src/astreum.egg-info/PKG-INFO +0 -24
  11. {astreum-0.1.6 → astreum-0.1.8}/LICENSE +0 -0
  12. {astreum-0.1.6 → astreum-0.1.8}/setup.cfg +0 -0
  13. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/__init__.py +0 -0
  14. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/__init__.py +0 -0
  15. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/expression.py +0 -0
  16. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/parser.py +0 -0
  17. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/__init__.py +0 -0
  18. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/definition.py +0 -0
  19. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/__init__.py +0 -0
  20. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/all.py +0 -0
  21. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/any.py +0 -0
  22. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/fold.py +0 -0
  23. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/get.py +0 -0
  24. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/insert.py +0 -0
  25. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/map.py +0 -0
  26. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/position.py +0 -0
  27. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/list/remove.py +0 -0
  28. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/number/__init__.py +0 -0
  29. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/special/number/addition.py +0 -0
  30. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/lispeum/tokenizer.py +0 -0
  31. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/machine/__init__.py +0 -0
  32. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/machine/environment.py +0 -0
  33. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/machine/error.py +0 -0
  34. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/models.py +0 -0
  35. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/__init__.py +0 -0
  36. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/bucket.py +0 -0
  37. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/envelope.py +0 -0
  38. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/message.py +0 -0
  39. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/peer.py +0 -0
  40. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/node/relay/route.py +0 -0
  41. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/utils/__init__.py +0 -0
  42. {astreum-0.1.6 → astreum-0.1.8}/src/astreum/utils/bytes_format.py +0 -0
  43. {astreum-0.1.6 → astreum-0.1.8}/src/astreum.egg-info/dependency_links.txt +0 -0
  44. {astreum-0.1.6 → astreum-0.1.8}/src/astreum.egg-info/top_level.txt +0 -0
astreum-0.1.8/PKG-INFO ADDED
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.2
2
+ Name: astreum
3
+ Version: 0.1.8
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
+
15
+ # lib
16
+
17
+ Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
18
+
19
+ [View on PyPI](https://pypi.org/project/astreum/)
20
+
21
+ ## Configuration
22
+
23
+ When initializing an Astreum Node, you need to provide a configuration dictionary. Below are the available configuration parameters:
24
+
25
+ ### Node Configuration
26
+
27
+ | Parameter | Type | Default | Description |
28
+ |-----------|------|---------|-------------|
29
+ | `node_id` | bytes | Random 32 bytes | Unique identifier for the node |
30
+ | `followed_chain_id` | bytes | None | ID of the blockchain that this node follows |
31
+ | `storage_path` | str | "./storage" | Directory path where node data will be stored |
32
+
33
+ ### Network Configuration
34
+
35
+ | Parameter | Type | Default | Description |
36
+ |-----------|------|---------|-------------|
37
+ | `use_ipv6` | bool | False | Whether to use IPv6 (True) or IPv4 (False) |
38
+ | `incoming_port` | int | 7373 | Port to listen for incoming messages |
39
+ | `max_message_size` | int | 65536 | Maximum size of UDP datagrams in bytes |
40
+ | `num_workers` | int | 4 | Number of worker threads for message processing |
41
+
42
+ ### Route Configuration
43
+
44
+ | Parameter | Type | Default | Description |
45
+ |-----------|------|---------|-------------|
46
+ | `peer_route` | bool | False | Whether to participate in the peer discovery route |
47
+ | `validation_route` | bool | False | Whether to participate in the block validation route |
48
+ | `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
49
+
50
+ ### Example Usage
51
+
52
+ ```python
53
+ from astreum.node import Node
54
+
55
+ # Configuration dictionary
56
+ config = {
57
+ "node_id": b"my-unique-node-id-goes-here-exactly-32", # 32 bytes
58
+ "followed_chain_id": b"main-chain-id-goes-here",
59
+ "storage_path": "./data/node1",
60
+ "incoming_port": 7373,
61
+ "use_ipv6": False,
62
+ "peer_route": True,
63
+ "validation_route": True,
64
+ "bootstrap_peers": [
65
+ ("bootstrap.astreum.org", 7373),
66
+ ("127.0.0.1", 7374)
67
+ ]
68
+ }
69
+
70
+ # Initialize the node with config
71
+ node = Node(config)
72
+
73
+ # Start the node
74
+ node.start()
75
+
76
+ # ... use the node ...
77
+
78
+ # Stop the node when done
79
+ node.stop()
80
+ ```
81
+
82
+ ## Testing
83
+
84
+ python3 -m unittest discover -s tests
@@ -0,0 +1,70 @@
1
+ # lib
2
+
3
+ Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
4
+
5
+ [View on PyPI](https://pypi.org/project/astreum/)
6
+
7
+ ## Configuration
8
+
9
+ When initializing an Astreum Node, you need to provide a configuration dictionary. Below are the available configuration parameters:
10
+
11
+ ### Node Configuration
12
+
13
+ | Parameter | Type | Default | Description |
14
+ |-----------|------|---------|-------------|
15
+ | `node_id` | bytes | Random 32 bytes | Unique identifier for the node |
16
+ | `followed_chain_id` | bytes | None | ID of the blockchain that this node follows |
17
+ | `storage_path` | str | "./storage" | Directory path where node data will be stored |
18
+
19
+ ### Network Configuration
20
+
21
+ | Parameter | Type | Default | Description |
22
+ |-----------|------|---------|-------------|
23
+ | `use_ipv6` | bool | False | Whether to use IPv6 (True) or IPv4 (False) |
24
+ | `incoming_port` | int | 7373 | Port to listen for incoming messages |
25
+ | `max_message_size` | int | 65536 | Maximum size of UDP datagrams in bytes |
26
+ | `num_workers` | int | 4 | Number of worker threads for message processing |
27
+
28
+ ### Route Configuration
29
+
30
+ | Parameter | Type | Default | Description |
31
+ |-----------|------|---------|-------------|
32
+ | `peer_route` | bool | False | Whether to participate in the peer discovery route |
33
+ | `validation_route` | bool | False | Whether to participate in the block validation route |
34
+ | `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
35
+
36
+ ### Example Usage
37
+
38
+ ```python
39
+ from astreum.node import Node
40
+
41
+ # Configuration dictionary
42
+ config = {
43
+ "node_id": b"my-unique-node-id-goes-here-exactly-32", # 32 bytes
44
+ "followed_chain_id": b"main-chain-id-goes-here",
45
+ "storage_path": "./data/node1",
46
+ "incoming_port": 7373,
47
+ "use_ipv6": False,
48
+ "peer_route": True,
49
+ "validation_route": True,
50
+ "bootstrap_peers": [
51
+ ("bootstrap.astreum.org", 7373),
52
+ ("127.0.0.1", 7374)
53
+ ]
54
+ }
55
+
56
+ # Initialize the node with config
57
+ node = Node(config)
58
+
59
+ # Start the node
60
+ node.start()
61
+
62
+ # ... use the node ...
63
+
64
+ # Stop the node when done
65
+ node.stop()
66
+ ```
67
+
68
+ ## Testing
69
+
70
+ python3 -m unittest discover -s tests
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.1.6"
3
+ version = "0.1.8"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,457 @@
1
+ """
2
+ Storage utilities for Lispeum expressions.
3
+
4
+ This module provides functions to convert Lispeum expressions to an
5
+ object-based Merkle tree representation for storage and retrieval.
6
+ """
7
+
8
+ import hashlib
9
+ import struct
10
+ from typing import Dict, Tuple, Any, List, Optional
11
+
12
+ from astreum.lispeum.expression import Expr
13
+
14
+
15
+ def expr_to_objects(expr: Any) -> Tuple[bytes, Dict[bytes, bytes]]:
16
+ """
17
+ Convert a Lispeum expression to a collection of objects in a Merkle tree structure.
18
+
19
+ Args:
20
+ expr: The expression to convert
21
+
22
+ Returns:
23
+ A tuple containing (root_hash, objects_dict) where:
24
+ - root_hash is the hash of the root object
25
+ - objects_dict is a dictionary mapping object hashes to their serialized representations
26
+ """
27
+ objects = {}
28
+ root_hash = _serialize_expr(expr, objects)
29
+ return root_hash, objects
30
+
31
+
32
+ def objects_to_expr(root_hash: bytes, objects: Dict[bytes, bytes]) -> Any:
33
+ """
34
+ Convert a collection of objects back to a Lispeum expression.
35
+
36
+ Args:
37
+ root_hash: The hash of the root object
38
+ objects: A dictionary mapping object hashes to their serialized representations
39
+
40
+ Returns:
41
+ The reconstructed Lispeum expression
42
+ """
43
+ return _deserialize_expr(root_hash, objects)
44
+
45
+
46
+ def _serialize_expr(expr: Any, objects: Dict[bytes, bytes]) -> bytes:
47
+ """
48
+ Serialize a Lispeum expression to bytes and add it to the objects dictionary.
49
+
50
+ Args:
51
+ expr: The expression to serialize
52
+ objects: Dictionary to store serialized objects
53
+
54
+ Returns:
55
+ The hash of the serialized expression
56
+ """
57
+ if expr is None:
58
+ # None type
59
+ type_bytes = b'N' # N for None
60
+ type_hash = hashlib.sha256(type_bytes).digest()
61
+ objects[type_hash] = type_bytes
62
+
63
+ # None values don't need a value leaf, just return the type hash
64
+ return type_hash
65
+
66
+ elif isinstance(expr, Expr.ListExpr):
67
+ # Create type leaf
68
+ type_bytes = b'L' # L for List
69
+ type_hash = hashlib.sha256(type_bytes).digest()
70
+ objects[type_hash] = type_bytes
71
+
72
+ # Serialize each element and collect their hashes
73
+ element_hashes = []
74
+ for elem in expr.elements:
75
+ elem_hash = _serialize_expr(elem, objects)
76
+ element_hashes.append(elem_hash)
77
+
78
+ # Create value leaf with all element hashes
79
+ value_bytes = b''.join(element_hashes)
80
+ value_hash = hashlib.sha256(value_bytes).digest()
81
+ objects[value_hash] = value_bytes
82
+
83
+ # Create the tree node with type and value
84
+ tree_bytes = type_hash + value_hash
85
+ tree_hash = hashlib.sha256(tree_bytes).digest()
86
+ objects[tree_hash] = tree_bytes
87
+
88
+ return tree_hash
89
+
90
+ elif isinstance(expr, Expr.Symbol):
91
+ # Create type leaf
92
+ type_bytes = b'S' # S for Symbol
93
+ type_hash = hashlib.sha256(type_bytes).digest()
94
+ objects[type_hash] = type_bytes
95
+
96
+ # Create value leaf
97
+ value_bytes = expr.value.encode('utf-8')
98
+ value_hash = hashlib.sha256(value_bytes).digest()
99
+ objects[value_hash] = value_bytes
100
+
101
+ # Create the tree node with type and value
102
+ tree_bytes = type_hash + value_hash
103
+ tree_hash = hashlib.sha256(tree_bytes).digest()
104
+ objects[tree_hash] = tree_bytes
105
+
106
+ return tree_hash
107
+
108
+ elif isinstance(expr, Expr.Integer):
109
+ # Create type leaf
110
+ type_bytes = b'I' # I for Integer
111
+ type_hash = hashlib.sha256(type_bytes).digest()
112
+ objects[type_hash] = type_bytes
113
+
114
+ # Create value leaf - use 2's complement little endian for integers
115
+ value_bytes = struct.pack("<q", expr.value) # 8-byte little endian
116
+ value_hash = hashlib.sha256(value_bytes).digest()
117
+ objects[value_hash] = value_bytes
118
+
119
+ # Create the tree node with type and value
120
+ tree_bytes = type_hash + value_hash
121
+ tree_hash = hashlib.sha256(tree_bytes).digest()
122
+ objects[tree_hash] = tree_bytes
123
+
124
+ return tree_hash
125
+
126
+ elif isinstance(expr, Expr.String):
127
+ # Create type leaf
128
+ type_bytes = b'T' # T for Text/String
129
+ type_hash = hashlib.sha256(type_bytes).digest()
130
+ objects[type_hash] = type_bytes
131
+
132
+ # Create value leaf
133
+ value_bytes = expr.value.encode('utf-8')
134
+ value_hash = hashlib.sha256(value_bytes).digest()
135
+ objects[value_hash] = value_bytes
136
+
137
+ # Create the tree node with type and value
138
+ tree_bytes = type_hash + value_hash
139
+ tree_hash = hashlib.sha256(tree_bytes).digest()
140
+ objects[tree_hash] = tree_bytes
141
+
142
+ return tree_hash
143
+
144
+ elif isinstance(expr, Expr.Boolean):
145
+ # Create type leaf
146
+ type_bytes = b'B' # B for Boolean
147
+ type_hash = hashlib.sha256(type_bytes).digest()
148
+ objects[type_hash] = type_bytes
149
+
150
+ # Create value leaf
151
+ value_bytes = b'1' if expr.value else b'0'
152
+ value_hash = hashlib.sha256(value_bytes).digest()
153
+ objects[value_hash] = value_bytes
154
+
155
+ # Create the tree node with type and value
156
+ tree_bytes = type_hash + value_hash
157
+ tree_hash = hashlib.sha256(tree_bytes).digest()
158
+ objects[tree_hash] = tree_bytes
159
+
160
+ return tree_hash
161
+
162
+ elif isinstance(expr, Expr.Function):
163
+ # Create type leaf
164
+ type_bytes = b'F' # F for Function
165
+ type_hash = hashlib.sha256(type_bytes).digest()
166
+ objects[type_hash] = type_bytes
167
+
168
+ # Serialize params
169
+ params_list = []
170
+ for param in expr.params:
171
+ params_list.append(param.encode('utf-8'))
172
+ params_bytes = b','.join(params_list)
173
+ params_hash = hashlib.sha256(params_bytes).digest()
174
+ objects[params_hash] = params_bytes
175
+
176
+ # Serialize body recursively
177
+ body_hash = _serialize_expr(expr.body, objects)
178
+
179
+ # Combine params and body hashes for the value
180
+ value_bytes = params_hash + body_hash
181
+ value_hash = hashlib.sha256(value_bytes).digest()
182
+ objects[value_hash] = value_bytes
183
+
184
+ # Create the tree node with type and value
185
+ tree_bytes = type_hash + value_hash
186
+ tree_hash = hashlib.sha256(tree_bytes).digest()
187
+ objects[tree_hash] = tree_bytes
188
+
189
+ return tree_hash
190
+
191
+ elif isinstance(expr, Expr.Error):
192
+ # Create type leaf
193
+ type_bytes = b'E' # E for Error
194
+ type_hash = hashlib.sha256(type_bytes).digest()
195
+ objects[type_hash] = type_bytes
196
+
197
+ # Serialize error components
198
+ category_bytes = expr.category.encode('utf-8')
199
+ category_hash = hashlib.sha256(category_bytes).digest()
200
+ objects[category_hash] = category_bytes
201
+
202
+ message_bytes = expr.message.encode('utf-8')
203
+ message_hash = hashlib.sha256(message_bytes).digest()
204
+ objects[message_hash] = message_bytes
205
+
206
+ if expr.details:
207
+ details_bytes = expr.details.encode('utf-8')
208
+ details_hash = hashlib.sha256(details_bytes).digest()
209
+ objects[details_hash] = details_bytes
210
+
211
+ # Combine all three components
212
+ value_bytes = category_hash + message_hash + details_hash
213
+ else:
214
+ # Just combine category and message
215
+ value_bytes = category_hash + message_hash
216
+
217
+ value_hash = hashlib.sha256(value_bytes).digest()
218
+ objects[value_hash] = value_bytes
219
+
220
+ # Create the tree node with type and value
221
+ tree_bytes = type_hash + value_hash
222
+ tree_hash = hashlib.sha256(tree_bytes).digest()
223
+ objects[tree_hash] = tree_bytes
224
+
225
+ return tree_hash
226
+
227
+ else:
228
+ # Unknown type - serialize as string
229
+ type_bytes = b'U' # U for Unknown
230
+ type_hash = hashlib.sha256(type_bytes).digest()
231
+ objects[type_hash] = type_bytes
232
+
233
+ # Create value leaf with string representation
234
+ value_bytes = str(expr).encode('utf-8')
235
+ value_hash = hashlib.sha256(value_bytes).digest()
236
+ objects[value_hash] = value_bytes
237
+
238
+ # Create the tree node with type and value
239
+ tree_bytes = type_hash + value_hash
240
+ tree_hash = hashlib.sha256(tree_bytes).digest()
241
+ objects[tree_hash] = tree_bytes
242
+
243
+ return tree_hash
244
+
245
+
246
+ def _deserialize_expr(obj_hash: bytes, objects: Dict[bytes, bytes]) -> Any:
247
+ """
248
+ Deserialize a Lispeum expression from its hash.
249
+
250
+ Args:
251
+ obj_hash: The hash of the object to deserialize
252
+ objects: Dictionary containing serialized objects
253
+
254
+ Returns:
255
+ The deserialized Lispeum expression
256
+ """
257
+ if obj_hash not in objects:
258
+ return None
259
+
260
+ obj_data = objects[obj_hash]
261
+
262
+ # Check if this is a type-only node (for None)
263
+ if len(obj_data) == 1:
264
+ if obj_data == b'N':
265
+ return None
266
+ return None # Unrecognized single-byte type
267
+
268
+ # For regular nodes, expect 64 bytes (two 32-byte hashes)
269
+ if len(obj_data) == 64:
270
+ type_hash = obj_data[:32]
271
+ value_hash = obj_data[32:]
272
+
273
+ if type_hash not in objects or value_hash not in objects:
274
+ return None
275
+
276
+ type_data = objects[type_hash]
277
+ value_data = objects[value_hash]
278
+
279
+ # Switch based on the type marker
280
+ if type_data == b'L': # List
281
+ elements = []
282
+ # Each hash is 32 bytes
283
+ hash_size = 32
284
+ for i in range(0, len(value_data), hash_size):
285
+ elem_hash = value_data[i:i+hash_size]
286
+ if elem_hash:
287
+ elem = _deserialize_expr(elem_hash, objects)
288
+ elements.append(elem)
289
+ return Expr.ListExpr(elements)
290
+
291
+ elif type_data == b'S': # Symbol
292
+ return Expr.Symbol(value_data.decode('utf-8'))
293
+
294
+ elif type_data == b'I': # Integer
295
+ int_value = struct.unpack("<q", value_data)[0]
296
+ return Expr.Integer(int_value)
297
+
298
+ elif type_data == b'T': # String (Text)
299
+ return Expr.String(value_data.decode('utf-8'))
300
+
301
+ elif type_data == b'B': # Boolean
302
+ return Expr.Boolean(value_data == b'1')
303
+
304
+ elif type_data == b'F': # Function
305
+ # Value contains params_hash and body_hash
306
+ params_hash = value_data[:32]
307
+ body_hash = value_data[32:]
308
+
309
+ if params_hash not in objects:
310
+ return None
311
+
312
+ params_data = objects[params_hash]
313
+ params = [p.decode('utf-8') for p in params_data.split(b',') if p]
314
+
315
+ body = _deserialize_expr(body_hash, objects)
316
+ return Expr.Function(params, body)
317
+
318
+ elif type_data == b'E': # Error
319
+ # Check if we have details or just category and message
320
+ if len(value_data) == 64: # No details
321
+ category_hash = value_data[:32]
322
+ message_hash = value_data[32:]
323
+ details_hash = None
324
+ elif len(value_data) == 96: # With details
325
+ category_hash = value_data[:32]
326
+ message_hash = value_data[32:64]
327
+ details_hash = value_data[64:]
328
+ else:
329
+ return None
330
+
331
+ if category_hash not in objects or message_hash not in objects:
332
+ return None
333
+
334
+ category = objects[category_hash].decode('utf-8')
335
+ message = objects[message_hash].decode('utf-8')
336
+
337
+ details = None
338
+ if details_hash and details_hash in objects:
339
+ details = objects[details_hash].decode('utf-8')
340
+
341
+ return Expr.Error(category, message, details)
342
+
343
+ elif type_data == b'U': # Unknown
344
+ return Expr.String(value_data.decode('utf-8'))
345
+
346
+ return None # Unrecognized format
347
+
348
+
349
+ def store_expr(expr: Any, storage) -> bytes:
350
+ """
351
+ Store a Lispeum expression in the provided storage.
352
+
353
+ Args:
354
+ expr: The expression to store
355
+ storage: Storage interface with put(key, value) method
356
+
357
+ Returns:
358
+ The hash of the root object
359
+ """
360
+ root_hash, objects = expr_to_objects(expr)
361
+
362
+ # Store all objects in the storage
363
+ for obj_hash, obj_data in objects.items():
364
+ storage.put(obj_hash, obj_data)
365
+
366
+ return root_hash
367
+
368
+
369
+ def get_expr_from_storage(root_hash: bytes, storage) -> Any:
370
+ """
371
+ Load a Lispeum expression from storage.
372
+
373
+ Args:
374
+ root_hash: The hash of the root object
375
+ storage: Storage interface with get(key) method
376
+
377
+ Returns:
378
+ The loaded Lispeum expression, or None if not found
379
+ """
380
+ if not root_hash:
381
+ return None
382
+
383
+ # Build the objects dictionary from storage
384
+ objects = {}
385
+ queue = [root_hash]
386
+ visited = set()
387
+
388
+ while queue:
389
+ current_hash = queue.pop(0)
390
+ if current_hash in visited:
391
+ continue
392
+
393
+ visited.add(current_hash)
394
+ obj_data = storage.get(current_hash)
395
+
396
+ if not obj_data:
397
+ # Can't find an object, return None
398
+ return None
399
+
400
+ objects[current_hash] = obj_data
401
+
402
+ # For single-byte nodes (e.g., None), no further processing needed
403
+ if len(obj_data) == 1:
404
+ continue
405
+
406
+ # For regular tree nodes (type + value)
407
+ if len(obj_data) == 64:
408
+ # Add both hashes to the queue
409
+ type_hash = obj_data[:32]
410
+ value_hash = obj_data[32:]
411
+
412
+ if type_hash not in visited:
413
+ queue.append(type_hash)
414
+
415
+ if value_hash not in visited:
416
+ queue.append(value_hash)
417
+
418
+ # For function and error types, we need to check the value fields
419
+ # which might contain additional hashes
420
+ if type_hash in objects:
421
+ type_data = objects[type_hash]
422
+
423
+ # For Function type, the value contains params_hash + body_hash
424
+ if type_data == b'F' and value_hash in objects:
425
+ value_data = objects[value_hash]
426
+ if len(value_data) == 64:
427
+ params_hash = value_data[:32]
428
+ body_hash = value_data[32:]
429
+
430
+ if params_hash not in visited:
431
+ queue.append(params_hash)
432
+
433
+ if body_hash not in visited:
434
+ queue.append(body_hash)
435
+
436
+ # For Error type, the value contains category_hash + message_hash + [details_hash]
437
+ elif type_data == b'E' and value_hash in objects:
438
+ value_data = objects[value_hash]
439
+ hash_size = 32
440
+
441
+ for i in range(0, len(value_data), hash_size):
442
+ component_hash = value_data[i:i+hash_size]
443
+ if component_hash and component_hash not in visited:
444
+ queue.append(component_hash)
445
+
446
+ # For List type, the value contains all element hashes
447
+ elif type_data == b'L' and value_hash in objects:
448
+ value_data = objects[value_hash]
449
+ hash_size = 32
450
+
451
+ for i in range(0, len(value_data), hash_size):
452
+ elem_hash = value_data[i:i+hash_size]
453
+ if elem_hash and elem_hash not in visited:
454
+ queue.append(elem_hash)
455
+
456
+ # Reconstruct the expression from objects
457
+ return objects_to_expr(root_hash, objects)
@@ -10,6 +10,7 @@ from .route_table import RouteTable
10
10
  from .machine import AstreumMachine
11
11
  from .utils import encode, decode
12
12
  from .models import Block, Transaction
13
+ from astreum.lispeum.storage import store_expr, get_expr_from_storage
13
14
 
14
15
  class Node:
15
16
  def __init__(self, config: dict):
@@ -17,8 +18,6 @@ class Node:
17
18
  self.node_id = config.get('node_id', os.urandom(32)) # Default to random ID if not provided
18
19
  self.relay = Relay(config)
19
20
  self.storage = Storage(config)
20
- self.machine = AstreumMachine(config)
21
- self.route_table = RouteTable(config, self.node_id)
22
21
 
23
22
  # Latest block of the chain this node is following
24
23
  self.latest_block = None
@@ -27,12 +26,19 @@ class Node:
27
26
  # Candidate chains that might be adopted
28
27
  self.candidate_chains = {} # chain_id -> {'latest_block': block, 'timestamp': time.time()}
29
28
 
29
+ # Initialize route table with our node ID
30
+ self.route_table = RouteTable(config, self.node_id)
31
+
32
+ # Initialize machine after storage so it can use it
33
+ # Pass self to machine so it can access the storage
34
+ self.machine = AstreumMachine(node=self)
35
+
30
36
  # Register message handlers
31
37
  self._register_message_handlers()
32
38
 
33
39
  # Initialize latest block from storage if available
34
40
  self._initialize_latest_block()
35
-
41
+
36
42
  def _register_message_handlers(self):
37
43
  """Register handlers for different message topics."""
38
44
  self.relay.register_message_handler(Topic.PING, self._handle_ping)
@@ -413,4 +419,43 @@ class Node:
413
419
  verify the chain and potentially switch to it if it's valid and better.
414
420
  """
415
421
  # TODO: Implement chain evaluation logic
416
- pass
422
+ pass
423
+
424
+ def post_global_storage(self, name: str, value):
425
+ """
426
+ Store a global variable in node storage.
427
+
428
+ Args:
429
+ name: Name of the variable
430
+ value: Value to store
431
+ """
432
+ # Store the expression directly in node storage using DAG representation
433
+ root_hash = store_expr(value, self.storage)
434
+
435
+ # Create a key for this variable name (without special prefixes)
436
+ key = hashlib.sha256(name.encode()).digest()
437
+
438
+ # Store the root hash reference
439
+ self.storage.put(key, root_hash)
440
+
441
+ def query_global_storage(self, name: str):
442
+ """
443
+ Retrieve a global variable from node storage.
444
+
445
+ Args:
446
+ name: Name of the variable to retrieve
447
+
448
+ Returns:
449
+ The stored expression, or None if not found
450
+ """
451
+ # Create the key for this variable name
452
+ key = hashlib.sha256(name.encode()).digest()
453
+
454
+ # Try to retrieve the root hash
455
+ root_hash = self.storage.get(key)
456
+
457
+ if root_hash:
458
+ # Load the expression using its root hash
459
+ return get_expr_from_storage(root_hash, self.storage)
460
+
461
+ return None
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.2
2
+ Name: astreum
3
+ Version: 0.1.8
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
+
15
+ # lib
16
+
17
+ Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
18
+
19
+ [View on PyPI](https://pypi.org/project/astreum/)
20
+
21
+ ## Configuration
22
+
23
+ When initializing an Astreum Node, you need to provide a configuration dictionary. Below are the available configuration parameters:
24
+
25
+ ### Node Configuration
26
+
27
+ | Parameter | Type | Default | Description |
28
+ |-----------|------|---------|-------------|
29
+ | `node_id` | bytes | Random 32 bytes | Unique identifier for the node |
30
+ | `followed_chain_id` | bytes | None | ID of the blockchain that this node follows |
31
+ | `storage_path` | str | "./storage" | Directory path where node data will be stored |
32
+
33
+ ### Network Configuration
34
+
35
+ | Parameter | Type | Default | Description |
36
+ |-----------|------|---------|-------------|
37
+ | `use_ipv6` | bool | False | Whether to use IPv6 (True) or IPv4 (False) |
38
+ | `incoming_port` | int | 7373 | Port to listen for incoming messages |
39
+ | `max_message_size` | int | 65536 | Maximum size of UDP datagrams in bytes |
40
+ | `num_workers` | int | 4 | Number of worker threads for message processing |
41
+
42
+ ### Route Configuration
43
+
44
+ | Parameter | Type | Default | Description |
45
+ |-----------|------|---------|-------------|
46
+ | `peer_route` | bool | False | Whether to participate in the peer discovery route |
47
+ | `validation_route` | bool | False | Whether to participate in the block validation route |
48
+ | `bootstrap_peers` | list | [] | List of bootstrap peers in the format `[("hostname", port), ...]` |
49
+
50
+ ### Example Usage
51
+
52
+ ```python
53
+ from astreum.node import Node
54
+
55
+ # Configuration dictionary
56
+ config = {
57
+ "node_id": b"my-unique-node-id-goes-here-exactly-32", # 32 bytes
58
+ "followed_chain_id": b"main-chain-id-goes-here",
59
+ "storage_path": "./data/node1",
60
+ "incoming_port": 7373,
61
+ "use_ipv6": False,
62
+ "peer_route": True,
63
+ "validation_route": True,
64
+ "bootstrap_peers": [
65
+ ("bootstrap.astreum.org", 7373),
66
+ ("127.0.0.1", 7374)
67
+ ]
68
+ }
69
+
70
+ # Initialize the node with config
71
+ node = Node(config)
72
+
73
+ # Start the node
74
+ node.start()
75
+
76
+ # ... use the node ...
77
+
78
+ # Stop the node when done
79
+ node.stop()
80
+ ```
81
+
82
+ ## Testing
83
+
84
+ python3 -m unittest discover -s tests
@@ -9,6 +9,7 @@ src/astreum.egg-info/top_level.txt
9
9
  src/astreum/lispeum/__init__.py
10
10
  src/astreum/lispeum/expression.py
11
11
  src/astreum/lispeum/parser.py
12
+ src/astreum/lispeum/storage.py
12
13
  src/astreum/lispeum/tokenizer.py
13
14
  src/astreum/lispeum/special/__init__.py
14
15
  src/astreum/lispeum/special/definition.py
astreum-0.1.6/PKG-INFO DELETED
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: astreum
3
- Version: 0.1.6
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
-
15
- # lib
16
-
17
- Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
18
-
19
- [View on PyPI](https://pypi.org/project/astreum/)
20
-
21
- ## Testing
22
-
23
- python3 -m unittest discover -s tests
24
-
astreum-0.1.6/README.md DELETED
@@ -1,10 +0,0 @@
1
- # lib
2
-
3
- Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
4
-
5
- [View on PyPI](https://pypi.org/project/astreum/)
6
-
7
- ## Testing
8
-
9
- python3 -m unittest discover -s tests
10
-
@@ -1,24 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: astreum
3
- Version: 0.1.6
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
-
15
- # lib
16
-
17
- Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
18
-
19
- [View on PyPI](https://pypi.org/project/astreum/)
20
-
21
- ## Testing
22
-
23
- python3 -m unittest discover -s tests
24
-
File without changes
File without changes
File without changes