astreum 0.2.1__tar.gz → 0.2.4__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.
- {astreum-0.2.1/src/astreum.egg-info → astreum-0.2.4}/PKG-INFO +7 -7
- {astreum-0.2.1 → astreum-0.2.4}/README.md +6 -6
- {astreum-0.2.1 → astreum-0.2.4}/pyproject.toml +1 -1
- astreum-0.2.4/src/astreum/__init__.py +1 -0
- astreum-0.2.4/src/astreum/lispeum/__init__.py +2 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/parser.py +3 -2
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/storage.py +1 -1
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/tokenizer.py +2 -2
- astreum-0.2.4/src/astreum/machine/error.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/node.py +75 -58
- {astreum-0.2.1 → astreum-0.2.4/src/astreum.egg-info}/PKG-INFO +7 -7
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum.egg-info/SOURCES.txt +2 -2
- astreum-0.2.4/tests/test_node_machine.py +56 -0
- astreum-0.2.1/src/astreum/__init__.py +0 -1
- astreum-0.2.1/src/astreum/lispeum/__init__.py +0 -2
- astreum-0.2.1/src/astreum/lispeum/utils.py +0 -17
- astreum-0.2.1/src/astreum/machine/error.py +0 -2
- {astreum-0.2.1 → astreum-0.2.4}/LICENSE +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/setup.cfg +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/bucket.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/envelope.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/message.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/peer.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/relay/route.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/storage/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/storage/merkle.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/storage/patricia.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/storage/storage.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/storage/utils.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/utils.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/_block/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/_block/create.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/_block/model.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/_block/validate.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/account.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/block.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/constants.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/stake.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/transaction.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/_node/validation/vdf.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/format.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/expression.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/definition.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/all.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/any.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/fold.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/get.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/insert.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/map.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/position.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/list/remove.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/number/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/lispeum/special/number/addition.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/machine/__init__.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum/machine/environment.py +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.1 → astreum-0.2.4}/src/astreum.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -73,7 +73,7 @@ node = Node(config)
|
|
|
73
73
|
|
|
74
74
|
## Lispeum Machine Quickstart
|
|
75
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
|
|
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
77
|
|
|
78
78
|
```python
|
|
79
79
|
from astreum.node import Node
|
|
@@ -83,16 +83,15 @@ from astreum.machine.parser import parse
|
|
|
83
83
|
# 1. Spin‑up a stand‑alone VM (machine‑only node).
|
|
84
84
|
node = Node({"machine-only": True})
|
|
85
85
|
|
|
86
|
-
# 2. Create
|
|
87
|
-
|
|
86
|
+
# 2. Create an environment.
|
|
87
|
+
env_id = node.machine_create_environment()
|
|
88
88
|
|
|
89
89
|
# 3. Convert Lispeum source → Expr AST.
|
|
90
90
|
source = '(+ 1 (* 2 3))'
|
|
91
91
|
expr, _ = parse(tokenize(source))
|
|
92
92
|
|
|
93
|
-
# 4. Evaluate
|
|
94
|
-
|
|
95
|
-
result = node.machine_expr_eval(env, expr) # -> Expr.Integer(7)
|
|
93
|
+
# 4. Evaluate
|
|
94
|
+
result = node.machine_expr_eval(env_id=env_id, expr=expr) # -> Expr.Integer(7)
|
|
96
95
|
|
|
97
96
|
print(result.value) # 7
|
|
98
97
|
```
|
|
@@ -119,5 +118,6 @@ except ParseError as e:
|
|
|
119
118
|
## Testing
|
|
120
119
|
|
|
121
120
|
```bash
|
|
121
|
+
|
|
122
122
|
python3 -m unittest discover -s tests
|
|
123
123
|
```
|
|
@@ -55,7 +55,7 @@ node = Node(config)
|
|
|
55
55
|
|
|
56
56
|
## Lispeum Machine Quickstart
|
|
57
57
|
|
|
58
|
-
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
|
|
58
|
+
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.
|
|
59
59
|
|
|
60
60
|
```python
|
|
61
61
|
from astreum.node import Node
|
|
@@ -65,16 +65,15 @@ from astreum.machine.parser import parse
|
|
|
65
65
|
# 1. Spin‑up a stand‑alone VM (machine‑only node).
|
|
66
66
|
node = Node({"machine-only": True})
|
|
67
67
|
|
|
68
|
-
# 2. Create
|
|
69
|
-
|
|
68
|
+
# 2. Create an environment.
|
|
69
|
+
env_id = node.machine_create_environment()
|
|
70
70
|
|
|
71
71
|
# 3. Convert Lispeum source → Expr AST.
|
|
72
72
|
source = '(+ 1 (* 2 3))'
|
|
73
73
|
expr, _ = parse(tokenize(source))
|
|
74
74
|
|
|
75
|
-
# 4. Evaluate
|
|
76
|
-
|
|
77
|
-
result = node.machine_expr_eval(env, expr) # -> Expr.Integer(7)
|
|
75
|
+
# 4. Evaluate
|
|
76
|
+
result = node.machine_expr_eval(env_id=env_id, expr=expr) # -> Expr.Integer(7)
|
|
78
77
|
|
|
79
78
|
print(result.value) # 7
|
|
80
79
|
```
|
|
@@ -101,5 +100,6 @@ except ParseError as e:
|
|
|
101
100
|
## Testing
|
|
102
101
|
|
|
103
102
|
```bash
|
|
103
|
+
|
|
104
104
|
python3 -m unittest discover -s tests
|
|
105
105
|
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .node import Node, Expr
|
|
@@ -9,7 +9,7 @@ import struct
|
|
|
9
9
|
from typing import Dict, Tuple, Any, List, Optional
|
|
10
10
|
|
|
11
11
|
from astreum.lispeum.expression import Expr
|
|
12
|
-
from .
|
|
12
|
+
from ..crypto.blake30 import hash_data
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def expr_to_objects(expr: Any) -> Tuple[bytes, Dict[bytes, bytes]]:
|
|
File without changes
|
|
@@ -6,10 +6,10 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Tuple, Dict, Union, Optional, List
|
|
7
7
|
from datetime import datetime, timedelta, timezone
|
|
8
8
|
import uuid
|
|
9
|
-
from
|
|
9
|
+
from .format import encode, decode
|
|
10
10
|
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
|
|
11
11
|
from cryptography.hazmat.primitives import serialization
|
|
12
|
-
from
|
|
12
|
+
from .crypto import ed25519, x25519
|
|
13
13
|
from enum import IntEnum
|
|
14
14
|
import blake3
|
|
15
15
|
import struct
|
|
@@ -29,11 +29,11 @@ class ObjectRequest:
|
|
|
29
29
|
self.hash = hash
|
|
30
30
|
|
|
31
31
|
def to_bytes(self):
|
|
32
|
-
return
|
|
32
|
+
return encode([self.type.value, self.data, self.hash])
|
|
33
33
|
|
|
34
34
|
@classmethod
|
|
35
35
|
def from_bytes(cls, data: bytes):
|
|
36
|
-
type_val, data_val, hash_val =
|
|
36
|
+
type_val, data_val, hash_val = decode(data)
|
|
37
37
|
return cls(type=ObjectRequestType(type_val[0]), data=data_val, hash=hash_val)
|
|
38
38
|
|
|
39
39
|
class ObjectResponseType(IntEnum):
|
|
@@ -52,11 +52,11 @@ class ObjectResponse:
|
|
|
52
52
|
self.hash = hash
|
|
53
53
|
|
|
54
54
|
def to_bytes(self):
|
|
55
|
-
return
|
|
55
|
+
return encode([self.type.value, self.data, self.hash])
|
|
56
56
|
|
|
57
57
|
@classmethod
|
|
58
58
|
def from_bytes(cls, data: bytes):
|
|
59
|
-
type_val, data_val, hash_val =
|
|
59
|
+
type_val, data_val, hash_val = decode(data)
|
|
60
60
|
return cls(type=ObjectResponseType(type_val[0]), data=data_val, hash=hash_val)
|
|
61
61
|
|
|
62
62
|
class MessageTopic(IntEnum):
|
|
@@ -71,11 +71,11 @@ class Message:
|
|
|
71
71
|
topic: MessageTopic
|
|
72
72
|
|
|
73
73
|
def to_bytes(self):
|
|
74
|
-
return
|
|
74
|
+
return encode([self.body, [self.topic.value]])
|
|
75
75
|
|
|
76
76
|
@classmethod
|
|
77
77
|
def from_bytes(cls, data: bytes):
|
|
78
|
-
body, topic =
|
|
78
|
+
body, topic = decode(data)
|
|
79
79
|
return cls(body=body, topic=MessageTopic(topic[0]))
|
|
80
80
|
|
|
81
81
|
class Envelope:
|
|
@@ -90,6 +90,7 @@ class Envelope:
|
|
|
90
90
|
encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
|
|
91
91
|
|
|
92
92
|
self.message = message
|
|
93
|
+
message_bytes = message.to_bytes()
|
|
93
94
|
|
|
94
95
|
self.sender = sender
|
|
95
96
|
self.sender_bytes = sender.public_bytes()
|
|
@@ -119,14 +120,14 @@ class Envelope:
|
|
|
119
120
|
return count
|
|
120
121
|
|
|
121
122
|
while True:
|
|
122
|
-
envelope_bytes =
|
|
123
|
+
envelope_bytes = encode([
|
|
123
124
|
encrypted_bytes,
|
|
124
125
|
message_bytes,
|
|
125
126
|
self.nonce,
|
|
126
127
|
self.sender_bytes,
|
|
127
128
|
timestamp_int
|
|
128
129
|
])
|
|
129
|
-
envelope_hash =
|
|
130
|
+
envelope_hash = blake3.blake3(envelope_bytes).digest()
|
|
130
131
|
if count_leading_zero_bits(envelope_hash) >= difficulty:
|
|
131
132
|
self.hash = envelope_hash
|
|
132
133
|
break
|
|
@@ -135,7 +136,7 @@ class Envelope:
|
|
|
135
136
|
def to_bytes(self):
|
|
136
137
|
encrypted_bytes = b'\x01' if self.encrypted else b'\x00'
|
|
137
138
|
|
|
138
|
-
return
|
|
139
|
+
return encode([
|
|
139
140
|
encrypted_bytes,
|
|
140
141
|
self.message.to_bytes(),
|
|
141
142
|
self.nonce,
|
|
@@ -145,7 +146,7 @@ class Envelope:
|
|
|
145
146
|
|
|
146
147
|
@classmethod
|
|
147
148
|
def from_bytes(cls, data: bytes):
|
|
148
|
-
encrypted_bytes, message_bytes, nonce, sender_bytes, timestamp_int =
|
|
149
|
+
encrypted_bytes, message_bytes, nonce, sender_bytes, timestamp_int = decode(data)
|
|
149
150
|
return cls(
|
|
150
151
|
encrypted=(encrypted_bytes == b'\x01'),
|
|
151
152
|
message=Message.from_bytes(message_bytes),
|
|
@@ -278,7 +279,7 @@ class Expr:
|
|
|
278
279
|
return f"(fn ({params_str}) {body_str})"
|
|
279
280
|
|
|
280
281
|
class Error:
|
|
281
|
-
def __init__(self, message: str, origin: 'Expr'
|
|
282
|
+
def __init__(self, message: str, origin: Optional['Expr'] = None):
|
|
282
283
|
self.message = message
|
|
283
284
|
self.origin = origin
|
|
284
285
|
|
|
@@ -288,9 +289,9 @@ class Expr:
|
|
|
288
289
|
return f'(error "{self.message}" in {self.origin})'
|
|
289
290
|
|
|
290
291
|
class Env:
|
|
291
|
-
def __init__(self,
|
|
292
|
+
def __init__(self, parent_id: uuid.UUID = None):
|
|
292
293
|
self.data: Dict[str, Expr] = {}
|
|
293
|
-
self.
|
|
294
|
+
self.parent_id = parent_id
|
|
294
295
|
|
|
295
296
|
def put(self, name: str, value: Expr):
|
|
296
297
|
self.data[name] = value
|
|
@@ -459,7 +460,7 @@ class Node:
|
|
|
459
460
|
self.peers[envelope.sender].timestamp = datetime.now(timezone.utc)
|
|
460
461
|
continue
|
|
461
462
|
|
|
462
|
-
is_validator_flag =
|
|
463
|
+
is_validator_flag = decode(envelope.message.body)
|
|
463
464
|
|
|
464
465
|
if envelope.sender not in self.peers:
|
|
465
466
|
self._send_ping(addr)
|
|
@@ -514,7 +515,7 @@ class Node:
|
|
|
514
515
|
nearest = self._get_closest_local_peer(object_hash)
|
|
515
516
|
if nearest:
|
|
516
517
|
nearest_key, nearest_peer = nearest
|
|
517
|
-
peer_info =
|
|
518
|
+
peer_info = encode([
|
|
518
519
|
nearest_key.public_bytes(
|
|
519
520
|
encoding=serialization.Encoding.Raw,
|
|
520
521
|
format=serialization.PublicFormat.Raw
|
|
@@ -533,7 +534,7 @@ class Node:
|
|
|
533
534
|
# -------------- OBJECT_PUT --------------
|
|
534
535
|
case ObjectRequestType.OBJECT_PUT:
|
|
535
536
|
# Ensure the hash is present / correct.
|
|
536
|
-
obj_hash = object_request.hash or
|
|
537
|
+
obj_hash = object_request.hash or blake30.blake3(object_request.data).digest()
|
|
537
538
|
|
|
538
539
|
nearest = self._get_closest_local_peer(obj_hash)
|
|
539
540
|
# If a strictly nearer peer exists, forward the PUT.
|
|
@@ -548,7 +549,7 @@ class Node:
|
|
|
548
549
|
self.outgoing_queue.put((fwd_env.to_bytes(), nearest[1].address))
|
|
549
550
|
else:
|
|
550
551
|
# We are closest → remember who can provide the object.
|
|
551
|
-
provider_record =
|
|
552
|
+
provider_record = encode([
|
|
552
553
|
envelope.sender.public_bytes(),
|
|
553
554
|
encode_ip_address(*addr)
|
|
554
555
|
])
|
|
@@ -567,13 +568,13 @@ class Node:
|
|
|
567
568
|
|
|
568
569
|
match object_response.type:
|
|
569
570
|
case ObjectResponseType.OBJECT_FOUND:
|
|
570
|
-
if object_response.hash !=
|
|
571
|
+
if object_response.hash != blake30.blake3(object_response.data).digest():
|
|
571
572
|
continue
|
|
572
573
|
self.object_request_queue.remove(object_response.hash)
|
|
573
574
|
self._local_object_put(object_response.hash, object_response.data)
|
|
574
575
|
|
|
575
576
|
case ObjectResponseType.OBJECT_PROVIDER:
|
|
576
|
-
_provider_public_key, provider_address =
|
|
577
|
+
_provider_public_key, provider_address = decode(object_response.data)
|
|
577
578
|
provider_ip, provider_port = decode_ip_address(provider_address)
|
|
578
579
|
object_request_message = Message(topic=MessageTopic.OBJECT_REQUEST, body=object_hash)
|
|
579
580
|
object_request_envelope = Envelope(message=object_request_message, sender=self.relay_public_key)
|
|
@@ -582,7 +583,7 @@ class Node:
|
|
|
582
583
|
case ObjectResponseType.OBJECT_NEAREST_PEER:
|
|
583
584
|
# -- decode the peer info sent back
|
|
584
585
|
nearest_peer_public_key_bytes, nearest_peer_address = (
|
|
585
|
-
|
|
586
|
+
decode(object_response.data)
|
|
586
587
|
)
|
|
587
588
|
nearest_peer_public_key = X25519PublicKey.from_public_bytes(
|
|
588
589
|
nearest_peer_public_key_bytes
|
|
@@ -648,12 +649,12 @@ class Node:
|
|
|
648
649
|
print(f"Error in _peer_manager_thread: {e}")
|
|
649
650
|
|
|
650
651
|
def _send_ping(self, addr: Tuple[str, int]):
|
|
651
|
-
is_validator_flag =
|
|
652
|
+
is_validator_flag = encode([1] if self.validation_secret_key else [0])
|
|
652
653
|
ping_message = Message(topic=MessageTopic.PING, body=is_validator_flag)
|
|
653
654
|
ping_envelope = Envelope(message=ping_message, sender=self.relay_public_key)
|
|
654
655
|
self.outgoing_queue.put((ping_envelope.to_bytes(), addr))
|
|
655
656
|
|
|
656
|
-
def _get_closest_local_peer(self, hash: bytes) -> Optional[
|
|
657
|
+
def _get_closest_local_peer(self, hash: bytes) -> Optional[Tuple[X25519PublicKey, Peer]]:
|
|
657
658
|
# Find the globally closest peer using XOR distance
|
|
658
659
|
closest_peer = None
|
|
659
660
|
closest_distance = None
|
|
@@ -694,44 +695,58 @@ class Node:
|
|
|
694
695
|
|
|
695
696
|
# MACHINE
|
|
696
697
|
def _machine_setup(self):
|
|
697
|
-
self.
|
|
698
|
-
self.
|
|
699
|
-
|
|
700
|
-
def
|
|
701
|
-
|
|
702
|
-
with self.
|
|
703
|
-
self.
|
|
704
|
-
|
|
698
|
+
self.environments: Dict[uuid.UUID, Env] = {}
|
|
699
|
+
self.machine_environments_lock = threading.Lock()
|
|
700
|
+
|
|
701
|
+
def machine_create_environment(self, parent_id: Optional[uuid.UUID] = None) -> uuid.UUID:
|
|
702
|
+
env_id = uuid.uuid4()
|
|
703
|
+
with self.machine_environments_lock:
|
|
704
|
+
while env_id in self.environments:
|
|
705
|
+
env_id = uuid.uuid4()
|
|
706
|
+
self.environments[env_id] = Env(parent_id=parent_id)
|
|
707
|
+
return env_id
|
|
705
708
|
|
|
706
|
-
def
|
|
707
|
-
with self.
|
|
708
|
-
if
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return
|
|
709
|
+
def machine_get_or_create_environment(self, env_id: Optional[uuid.UUID] = None, parent_id: Optional[uuid.UUID] = None) -> uuid.UUID:
|
|
710
|
+
with self.machine_environments_lock:
|
|
711
|
+
if env_id is not None and env_id in self.environments:
|
|
712
|
+
return env_id
|
|
713
|
+
new_id = env_id if env_id is not None else uuid.uuid4()
|
|
714
|
+
while new_id in self.environments:
|
|
715
|
+
new_id = uuid.uuid4()
|
|
716
|
+
self.environments[new_id] = Env(parent_id=parent_id)
|
|
717
|
+
return new_id
|
|
718
|
+
|
|
719
|
+
def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
|
|
720
|
+
with self.machine_environments_lock:
|
|
721
|
+
removed = self.environments.pop(env_id, None)
|
|
722
|
+
return removed is not None
|
|
723
|
+
|
|
724
|
+
def machine_expr_get(self, env_id: uuid.UUID, name: str) -> Optional[Expr]:
|
|
725
|
+
with self.machine_environments_lock:
|
|
726
|
+
cur = self.environments.get(env_id)
|
|
727
|
+
while cur is not None:
|
|
728
|
+
if name in cur.data:
|
|
729
|
+
return cur.data[name]
|
|
730
|
+
if cur.parent_id:
|
|
731
|
+
cur = self.environments.get(cur.parent_id)
|
|
732
|
+
else:
|
|
733
|
+
cur = None
|
|
734
|
+
return None
|
|
720
735
|
|
|
721
|
-
def machine_expr_put(self,
|
|
722
|
-
with self.
|
|
723
|
-
env = self.
|
|
736
|
+
def machine_expr_put(self, env_id: uuid.UUID, name: str, expr: Expr):
|
|
737
|
+
with self.machine_environments_lock:
|
|
738
|
+
env = self.environments.get(env_id)
|
|
724
739
|
if env is None:
|
|
725
740
|
return False
|
|
726
741
|
env.put(name, expr)
|
|
727
742
|
return True
|
|
728
743
|
|
|
729
|
-
def machine_expr_eval(self,
|
|
744
|
+
def machine_expr_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
|
|
730
745
|
if isinstance(expr, Expr.Boolean) or isinstance(expr, Expr.Integer) or isinstance(expr, Expr.String) or isinstance(expr, Expr.Error):
|
|
731
746
|
return expr
|
|
732
747
|
|
|
733
748
|
elif isinstance(expr, Expr.Symbol):
|
|
734
|
-
value =
|
|
749
|
+
value = self.machine_expr_get(env_id=env_id, name=expr.value)
|
|
735
750
|
if value:
|
|
736
751
|
return value
|
|
737
752
|
else:
|
|
@@ -741,24 +756,26 @@ class Node:
|
|
|
741
756
|
if len(expr.elements) == 0:
|
|
742
757
|
return expr
|
|
743
758
|
if len(expr.elements) == 1:
|
|
744
|
-
return self.machine_expr_eval(expr=expr.elements[0],
|
|
759
|
+
return self.machine_expr_eval(expr=expr.elements[0], env_id=env_id)
|
|
745
760
|
first = expr.elements[0]
|
|
746
761
|
if isinstance(first, Expr.Symbol):
|
|
747
|
-
first_symbol_value =
|
|
762
|
+
first_symbol_value = self.machine_expr_get(env_id=env_id, name=first.value)
|
|
748
763
|
|
|
749
764
|
if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
|
|
750
|
-
evaluated_elements = [self.
|
|
765
|
+
evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
|
|
751
766
|
return Expr.ListExpr(evaluated_elements)
|
|
752
767
|
|
|
753
768
|
elif first.value == "def":
|
|
769
|
+
args = expr.elements[1:]
|
|
754
770
|
if len(args) != 2:
|
|
755
771
|
return Expr.Error(message=f"'def' expects exactly 2 arguments, got {len(args)}", origin=expr)
|
|
756
772
|
if not isinstance(args[0], Expr.Symbol):
|
|
757
773
|
return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
|
|
758
|
-
result = self.machine_expr_eval(
|
|
774
|
+
result = self.machine_expr_eval(env_id=env_id, expr=args[1])
|
|
759
775
|
if isinstance(result, Expr.Error):
|
|
760
776
|
return result
|
|
761
|
-
|
|
777
|
+
|
|
778
|
+
self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
|
|
762
779
|
return result
|
|
763
780
|
|
|
764
781
|
# # List
|
|
@@ -901,7 +918,7 @@ class Node:
|
|
|
901
918
|
return Expr.Error(message="'+' expects at least 1 argument", origin=expr)
|
|
902
919
|
evaluated_args = []
|
|
903
920
|
for arg in args:
|
|
904
|
-
val = self.
|
|
921
|
+
val = self.machine_expr_eval(env_id==env_id, expr=arg)
|
|
905
922
|
if isinstance(val, Expr.Error):
|
|
906
923
|
return val
|
|
907
924
|
evaluated_args.append(val)
|
|
@@ -1010,7 +1027,7 @@ class Node:
|
|
|
1010
1027
|
# return Expr.Integer(dividend % divisor)
|
|
1011
1028
|
|
|
1012
1029
|
else:
|
|
1013
|
-
evaluated_elements = [self.
|
|
1030
|
+
evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
|
|
1014
1031
|
return Expr.ListExpr(evaluated_elements)
|
|
1015
1032
|
|
|
1016
1033
|
elif isinstance(expr, Expr.Function):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -73,7 +73,7 @@ node = Node(config)
|
|
|
73
73
|
|
|
74
74
|
## Lispeum Machine Quickstart
|
|
75
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
|
|
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
77
|
|
|
78
78
|
```python
|
|
79
79
|
from astreum.node import Node
|
|
@@ -83,16 +83,15 @@ from astreum.machine.parser import parse
|
|
|
83
83
|
# 1. Spin‑up a stand‑alone VM (machine‑only node).
|
|
84
84
|
node = Node({"machine-only": True})
|
|
85
85
|
|
|
86
|
-
# 2. Create
|
|
87
|
-
|
|
86
|
+
# 2. Create an environment.
|
|
87
|
+
env_id = node.machine_create_environment()
|
|
88
88
|
|
|
89
89
|
# 3. Convert Lispeum source → Expr AST.
|
|
90
90
|
source = '(+ 1 (* 2 3))'
|
|
91
91
|
expr, _ = parse(tokenize(source))
|
|
92
92
|
|
|
93
|
-
# 4. Evaluate
|
|
94
|
-
|
|
95
|
-
result = node.machine_expr_eval(env, expr) # -> Expr.Integer(7)
|
|
93
|
+
# 4. Evaluate
|
|
94
|
+
result = node.machine_expr_eval(env_id=env_id, expr=expr) # -> Expr.Integer(7)
|
|
96
95
|
|
|
97
96
|
print(result.value) # 7
|
|
98
97
|
```
|
|
@@ -119,5 +118,6 @@ except ParseError as e:
|
|
|
119
118
|
## Testing
|
|
120
119
|
|
|
121
120
|
```bash
|
|
121
|
+
|
|
122
122
|
python3 -m unittest discover -s tests
|
|
123
123
|
```
|
|
@@ -41,7 +41,6 @@ src/astreum/lispeum/expression.py
|
|
|
41
41
|
src/astreum/lispeum/parser.py
|
|
42
42
|
src/astreum/lispeum/storage.py
|
|
43
43
|
src/astreum/lispeum/tokenizer.py
|
|
44
|
-
src/astreum/lispeum/utils.py
|
|
45
44
|
src/astreum/lispeum/special/__init__.py
|
|
46
45
|
src/astreum/lispeum/special/definition.py
|
|
47
46
|
src/astreum/lispeum/special/list/__init__.py
|
|
@@ -57,4 +56,5 @@ src/astreum/lispeum/special/number/__init__.py
|
|
|
57
56
|
src/astreum/lispeum/special/number/addition.py
|
|
58
57
|
src/astreum/machine/__init__.py
|
|
59
58
|
src/astreum/machine/environment.py
|
|
60
|
-
src/astreum/machine/error.py
|
|
59
|
+
src/astreum/machine/error.py
|
|
60
|
+
tests/test_node_machine.py
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from src.astreum.node import Node, Expr
|
|
3
|
+
from src.astreum.lispeum import tokenize, parse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestNodeMachine(unittest.TestCase):
|
|
7
|
+
"""Integration tests for the Lispeum VM embedded in astreum.Node."""
|
|
8
|
+
|
|
9
|
+
def setUp(self):
|
|
10
|
+
# Spin‑up a stand‑alone VM
|
|
11
|
+
self.node = Node({"machine-only": True})
|
|
12
|
+
self.env_id = self.node.machine_create_environment()
|
|
13
|
+
self.env = self.node.environments[self.env_id]
|
|
14
|
+
|
|
15
|
+
# ---------- helpers --------------------------------------------------
|
|
16
|
+
def _eval(self, source: str) -> Expr:
|
|
17
|
+
"""Tokenize → parse → eval a Lispeum snippet inside the current env."""
|
|
18
|
+
tokens = tokenize(source)
|
|
19
|
+
expr, _ = parse(tokens)
|
|
20
|
+
return self.node.machine_expr_eval(env_id=self.env_id, expr=expr)
|
|
21
|
+
|
|
22
|
+
# ---------- core tests ----------------------------------------------
|
|
23
|
+
def test_int_addition(self):
|
|
24
|
+
"""(+ 2 3) ⇒ 5"""
|
|
25
|
+
result = self._eval("(+ 2 3)")
|
|
26
|
+
self.assertEqual(result.value, 5)
|
|
27
|
+
|
|
28
|
+
def test_variable_definition_and_lookup(self):
|
|
29
|
+
"""(def numero 42) then numero ⇒ 42"""
|
|
30
|
+
self._eval("(def numero 42)")
|
|
31
|
+
lookup_result = self._eval("numero")
|
|
32
|
+
self.assertEqual(lookup_result.value, 42)
|
|
33
|
+
|
|
34
|
+
def test_session_isolation(self):
|
|
35
|
+
"""Variables defined in one session must not leak into another."""
|
|
36
|
+
# Define in first env
|
|
37
|
+
self._eval("(def a 1)")
|
|
38
|
+
|
|
39
|
+
# Create second session
|
|
40
|
+
other_env = self.node.machine_create_environment()
|
|
41
|
+
|
|
42
|
+
tokens = tokenize("a")
|
|
43
|
+
expr, _ = parse(tokens)
|
|
44
|
+
result_other = self.node.machine_expr_eval(env_id=other_env, expr=expr)
|
|
45
|
+
|
|
46
|
+
# Expect an Expr.Error or any non‑1 value
|
|
47
|
+
self.assertTrue(
|
|
48
|
+
isinstance(result_other, Expr.Error) or getattr(result_other, "value", None) != 1,
|
|
49
|
+
"Variable 'a' leaked across sessions"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.node.machine_delete_environment(other_env)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
unittest.main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from node import Node
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Utility functions for the Lispeum module.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import blake3
|
|
6
|
-
|
|
7
|
-
def hash_data(data: bytes) -> bytes:
|
|
8
|
-
"""
|
|
9
|
-
Hash data using BLAKE3.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
data: Data to hash
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
32-byte BLAKE3 hash
|
|
16
|
-
"""
|
|
17
|
-
return blake3.blake3(data).digest()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|