astreum 0.2.0__tar.gz → 0.2.3__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 (67) hide show
  1. {astreum-0.2.0/src/astreum.egg-info → astreum-0.2.3}/PKG-INFO +17 -38
  2. astreum-0.2.3/README.md +105 -0
  3. {astreum-0.2.0 → astreum-0.2.3}/pyproject.toml +1 -1
  4. astreum-0.2.3/src/astreum/__init__.py +1 -0
  5. astreum-0.2.3/src/astreum/lispeum/__init__.py +2 -0
  6. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/parser.py +3 -2
  7. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/storage.py +1 -1
  8. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/tokenizer.py +2 -2
  9. astreum-0.2.3/src/astreum/machine/error.py +0 -0
  10. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/node.py +66 -59
  11. {astreum-0.2.0 → astreum-0.2.3/src/astreum.egg-info}/PKG-INFO +17 -38
  12. {astreum-0.2.0 → astreum-0.2.3}/src/astreum.egg-info/SOURCES.txt +2 -2
  13. astreum-0.2.3/tests/test_node_machine.py +56 -0
  14. astreum-0.2.0/README.md +0 -126
  15. astreum-0.2.0/src/astreum/__init__.py +0 -1
  16. astreum-0.2.0/src/astreum/lispeum/__init__.py +0 -2
  17. astreum-0.2.0/src/astreum/lispeum/utils.py +0 -17
  18. astreum-0.2.0/src/astreum/machine/error.py +0 -2
  19. {astreum-0.2.0 → astreum-0.2.3}/LICENSE +0 -0
  20. {astreum-0.2.0 → astreum-0.2.3}/setup.cfg +0 -0
  21. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/__init__.py +0 -0
  22. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/__init__.py +0 -0
  23. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/bucket.py +0 -0
  24. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/envelope.py +0 -0
  25. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/message.py +0 -0
  26. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/peer.py +0 -0
  27. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/relay/route.py +0 -0
  28. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/storage/__init__.py +0 -0
  29. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/storage/merkle.py +0 -0
  30. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/storage/patricia.py +0 -0
  31. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/storage/storage.py +0 -0
  32. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/storage/utils.py +0 -0
  33. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/utils.py +0 -0
  34. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/__init__.py +0 -0
  35. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/_block/__init__.py +0 -0
  36. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/_block/create.py +0 -0
  37. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/_block/model.py +0 -0
  38. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/_block/validate.py +0 -0
  39. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/account.py +0 -0
  40. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/block.py +0 -0
  41. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/constants.py +0 -0
  42. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/stake.py +0 -0
  43. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/transaction.py +0 -0
  44. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/_node/validation/vdf.py +0 -0
  45. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/crypto/__init__.py +0 -0
  46. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/crypto/ed25519.py +0 -0
  47. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/crypto/x25519.py +0 -0
  48. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/format.py +0 -0
  49. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/expression.py +0 -0
  50. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/__init__.py +0 -0
  51. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/definition.py +0 -0
  52. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/__init__.py +0 -0
  53. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/all.py +0 -0
  54. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/any.py +0 -0
  55. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/fold.py +0 -0
  56. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/get.py +0 -0
  57. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/insert.py +0 -0
  58. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/map.py +0 -0
  59. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/position.py +0 -0
  60. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/list/remove.py +0 -0
  61. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/number/__init__.py +0 -0
  62. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/lispeum/special/number/addition.py +0 -0
  63. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/machine/__init__.py +0 -0
  64. {astreum-0.2.0 → astreum-0.2.3}/src/astreum/machine/environment.py +0 -0
  65. {astreum-0.2.0 → astreum-0.2.3}/src/astreum.egg-info/dependency_links.txt +0 -0
  66. {astreum-0.2.0 → astreum-0.2.3}/src/astreum.egg-info/requires.txt +0 -0
  67. {astreum-0.2.0 → astreum-0.2.3}/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.0
3
+ Version: 0.2.3
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
@@ -16,7 +16,7 @@ Requires-Dist: cryptography==44.0.2
16
16
  Requires-Dist: blake3==1.0.4
17
17
  Dynamic: license-file
18
18
 
19
- # Astreum Python Library
19
+ # lib
20
20
 
21
21
  Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
22
22
 
@@ -28,19 +28,13 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
28
28
 
29
29
  ### Core Configuration
30
30
 
31
- | Parameter | Type | Default | Description |
32
- | -------------- | ---- | ------- | ----------- |
33
- | `machine-only` | bool | `True` | When |
34
-
35
- | |
36
- | - |
37
-
38
- | **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. | | | |
39
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
40
- | `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. |
41
- | `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. |
42
- | `storage_path` | string | `None` | Directory where objects are persisted. If *None* the node uses an in‑memory store. |
43
- | `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
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. |
44
38
 
45
39
  ### Networking
46
40
 
@@ -79,7 +73,7 @@ node = Node(config)
79
73
 
80
74
  ## Lispeum Machine Quickstart
81
75
 
82
- 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 *session* (lexical environment).
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.
83
77
 
84
78
  ```python
85
79
  from astreum.node import Node
@@ -89,16 +83,15 @@ from astreum.machine.parser import parse
89
83
  # 1. Spin‑up a stand‑alone VM (machine‑only node).
90
84
  node = Node({"machine-only": True})
91
85
 
92
- # 2. Create a fresh session (environment).
93
- session_id = node.machine_session_create()
86
+ # 2. Create an environment.
87
+ env_id = node.machine_create_environment()
94
88
 
95
89
  # 3. Convert Lispeum source → Expr AST.
96
90
  source = '(+ 1 (* 2 3))'
97
91
  expr, _ = parse(tokenize(source))
98
92
 
99
- # 4. Evaluate inside that session.
100
- env = node.sessions[session_id] # fetch the Env
101
- 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)
102
95
 
103
96
  print(result.value) # 7
104
97
  ```
@@ -107,23 +100,8 @@ print(result.value) # 7
107
100
 
108
101
  Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
109
102
 
110
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
111
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
112
-
113
- Catch the exception to provide developer‑friendly diagnostics:
114
-
115
- ```python
116
- try:
117
- tokens = tokenize(bad_source)
118
- expr, _ = parse(tokens)
119
- except ParseError as e:
120
- print("Parse failed:", e)
121
- ```
122
-
123
- Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
124
-
125
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
126
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
103
+ * Unterminated string literals are caught by `tokenize`.
104
+ * Unexpected or missing parentheses are caught by `parse`.
127
105
 
128
106
  Catch the exception to provide developer‑friendly diagnostics:
129
107
 
@@ -140,5 +118,6 @@ except ParseError as e:
140
118
  ## Testing
141
119
 
142
120
  ```bash
121
+
143
122
  python3 -m unittest discover -s tests
144
123
  ```
@@ -0,0 +1,105 @@
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`, 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.
10
+
11
+ ### Core Configuration
12
+
13
+ | Parameter | Type | Default | Description |
14
+ | --------------------------- | ---------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `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. |
16
+ | `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. |
17
+ | `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. |
18
+ | `storage_path` | string | `None` | Directory where objects are persisted. If *None*, the node uses an in‑memory store. |
19
+ | `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
20
+
21
+ ### Networking
22
+
23
+ | Parameter | Type | Default | Description |
24
+ | --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
25
+ | `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
26
+ | `incoming_port` | int | `7373` | UDP port the relay binds to. |
27
+ | `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
28
+
29
+ > **Note**
30
+ > The peer‑to‑peer *route* used for object discovery is always enabled.
31
+ > If `validation_secret_key` is provided the node automatically joins the validation route too.
32
+
33
+ ### Example
34
+
35
+ ```python
36
+ from astreum.node import Node
37
+
38
+ config = {
39
+ "machine-only": False, # run full node
40
+ "relay_secret_key": "ab…cd", # optional – hex encoded
41
+ "validation_secret_key": "12…34", # optional – validator
42
+ "storage_path": "./data/node1",
43
+ "storage_get_relay_timeout": 5,
44
+ "incoming_port": 7373,
45
+ "use_ipv6": False,
46
+ "bootstrap": [
47
+ ("bootstrap.astreum.org", 7373),
48
+ ("127.0.0.1", 7374)
49
+ ]
50
+ }
51
+
52
+ node = Node(config)
53
+ # … your code …
54
+ ```
55
+
56
+ ## Lispeum Machine Quickstart
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 environment.
59
+
60
+ ```python
61
+ from astreum.node import Node
62
+ from astreum.machine.tokenizer import tokenize
63
+ from astreum.machine.parser import parse
64
+
65
+ # 1. Spin‑up a stand‑alone VM (machine‑only node).
66
+ node = Node({"machine-only": True})
67
+
68
+ # 2. Create an environment.
69
+ env_id = node.machine_create_environment()
70
+
71
+ # 3. Convert Lispeum source → Expr AST.
72
+ source = '(+ 1 (* 2 3))'
73
+ expr, _ = parse(tokenize(source))
74
+
75
+ # 4. Evaluate
76
+ result = node.machine_expr_eval(env_id=env_id, expr=expr) # -> Expr.Integer(7)
77
+
78
+ print(result.value) # 7
79
+ ```
80
+
81
+ ### Handling errors
82
+
83
+ Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
84
+
85
+ * Unterminated string literals are caught by `tokenize`.
86
+ * Unexpected or missing parentheses are caught by `parse`.
87
+
88
+ Catch the exception to provide developer‑friendly diagnostics:
89
+
90
+ ```python
91
+ try:
92
+ tokens = tokenize(bad_source)
93
+ expr, _ = parse(tokens)
94
+ except ParseError as e:
95
+ print("Parse failed:", e)
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Testing
101
+
102
+ ```bash
103
+
104
+ python3 -m unittest discover -s tests
105
+ ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1 @@
1
+ from .node import Node, Expr
@@ -0,0 +1,2 @@
1
+ from .parser import parse
2
+ from .tokenizer import tokenize
@@ -1,7 +1,8 @@
1
1
  from typing import List, Tuple
2
- from astreum.machine.error import ParseError
3
- from astreum.node import Expr
2
+ from ..node import Expr
4
3
 
4
+ class ParseError(Exception):
5
+ pass
5
6
 
6
7
  def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
7
8
  if not tokens:
@@ -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 .utils import hash_data
12
+ from ..crypto.blake30 import hash_data
13
13
 
14
14
 
15
15
  def expr_to_objects(expr: Any) -> Tuple[bytes, Dict[bytes, bytes]]:
@@ -3,8 +3,8 @@
3
3
  # Tokenizer function
4
4
  from typing import List
5
5
 
6
- from astreum.machine.error import ParseError
7
-
6
+ class ParseError(Exception):
7
+ pass
8
8
 
9
9
  def tokenize(input: str) -> List[str]:
10
10
  tokens = []
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 astreum import format
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 astreum.crypto import ed25519, x25519
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 format.encode([self.type.value, self.data, self.hash])
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 = format.decode(data)
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 format.encode([self.type.value, self.data, self.hash])
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 = format.decode(data)
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 format.encode([self.body, [self.topic.value]])
74
+ return encode([self.body, [self.topic.value]])
75
75
 
76
76
  @classmethod
77
77
  def from_bytes(cls, data: bytes):
78
- body, topic = format.decode(data)
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 = format.encode([
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 = utils.blake3_hash(envelope_bytes)
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 format.encode([
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 = format.decode(data)
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' | None = None):
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, parent: 'Env' = None):
292
+ def __init__(self, parent_id: uuid.UUID = None):
292
293
  self.data: Dict[str, Expr] = {}
293
- self.parent = parent
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 = format.decode(envelope.message.body)
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 = format.encode([
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 blake3.blake3(object_request.data).digest()
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 = format.encode([
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 != blake3.blake3(object_response.data).digest():
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 = format.decode(object_response.data)
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
- format.decode(object_response.data)
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 = format.encode([1] if self.validation_secret_key else [0])
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[(X25519PublicKey, Peer)]:
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,48 @@ class Node:
694
695
 
695
696
  # MACHINE
696
697
  def _machine_setup(self):
697
- self.sessions: Dict[uuid.UUID, Env] = {}
698
- self.lock = threading.Lock()
699
-
700
- def machine_session_create(self) -> uuid.UUID:
701
- session_id = uuid.uuid4()
702
- with self.lock:
703
- self.sessions[session_id] = Env()
704
- return session_id
705
-
706
- def machine_session_delete(self, session_id: str) -> bool:
707
- with self.lock:
708
- if session_id in self.sessions:
709
- del self.sessions[session_id]
710
- return True
711
- else:
712
- return False
713
-
714
- def machine_expr_get(self, session_id: uuid.UUID, name: str) -> Optional[Expr]:
715
- with self.lock:
716
- env = self.sessions.get(session_id)
717
- if env is None:
718
- return None
719
- return env.get(name)
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
708
+
709
+ def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
710
+ with self.machine_environments_lock:
711
+ removed = self.environments.pop(env_id, None)
712
+ return removed is not None
713
+
714
+ def machine_expr_get(self, env_id: uuid.UUID, name: str) -> Optional[Expr]:
715
+ with self.machine_environments_lock:
716
+ cur = self.environments.get(env_id)
717
+ while cur is not None:
718
+ if name in cur.data:
719
+ return cur.data[name]
720
+ if cur.parent_id:
721
+ cur = self.environments.get(cur.parent_id)
722
+ else:
723
+ cur = None
724
+ return None
720
725
 
721
- def machine_expr_put(self, session_id: uuid.UUID, name: str, expr: Expr):
722
- with self.lock:
723
- env = self.sessions.get(session_id)
726
+ def machine_expr_put(self, env_id: uuid.UUID, name: str, expr: Expr):
727
+ with self.machine_environments_lock:
728
+ env = self.environments.get(env_id)
724
729
  if env is None:
725
730
  return False
726
731
  env.put(name, expr)
727
732
  return True
728
733
 
729
- def machine_expr_eval(self, env: Env, expr: Expr) -> Expr:
734
+ def machine_expr_eval(self, env_id: uuid.UUID, expr: Expr) -> Expr:
730
735
  if isinstance(expr, Expr.Boolean) or isinstance(expr, Expr.Integer) or isinstance(expr, Expr.String) or isinstance(expr, Expr.Error):
731
736
  return expr
732
737
 
733
738
  elif isinstance(expr, Expr.Symbol):
734
- value = env.get(expr.value)
739
+ value = self.machine_expr_get(env_id=env_id, name=expr.value)
735
740
  if value:
736
741
  return value
737
742
  else:
@@ -741,24 +746,26 @@ class Node:
741
746
  if len(expr.elements) == 0:
742
747
  return expr
743
748
  if len(expr.elements) == 1:
744
- return self.machine_expr_eval(expr=expr.elements[0], env=env)
749
+ return self.machine_expr_eval(expr=expr.elements[0], env_id=env_id)
745
750
  first = expr.elements[0]
746
751
  if isinstance(first, Expr.Symbol):
747
- first_symbol_value = env.get(first.value)
752
+ first_symbol_value = self.machine_expr_get(env_id=env_id, name=first.value)
748
753
 
749
754
  if first_symbol_value and not isinstance(first_symbol_value, Expr.Function):
750
- evaluated_elements = [self.evaluate_expression(e, env) for e in expr.elements]
755
+ evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
751
756
  return Expr.ListExpr(evaluated_elements)
752
757
 
753
758
  elif first.value == "def":
759
+ args = expr.elements[1:]
754
760
  if len(args) != 2:
755
761
  return Expr.Error(message=f"'def' expects exactly 2 arguments, got {len(args)}", origin=expr)
756
762
  if not isinstance(args[0], Expr.Symbol):
757
763
  return Expr.Error(message="first argument to 'def' must be a symbol", origin=args[0])
758
- result = self.machine_expr_eval(env=env, expr=args[1])
764
+ result = self.machine_expr_eval(env_id=env_id, expr=args[1])
759
765
  if isinstance(result, Expr.Error):
760
766
  return result
761
- env.put(name=args[0].value, value=result)
767
+
768
+ self.machine_expr_put(env_id=env_id, name=args[0].value, expr=result)
762
769
  return result
763
770
 
764
771
  # # List
@@ -901,7 +908,7 @@ class Node:
901
908
  return Expr.Error(message="'+' expects at least 1 argument", origin=expr)
902
909
  evaluated_args = []
903
910
  for arg in args:
904
- val = self.evaluate_expression(arg, env)
911
+ val = self.machine_expr_eval(env_id==env_id, expr=arg)
905
912
  if isinstance(val, Expr.Error):
906
913
  return val
907
914
  evaluated_args.append(val)
@@ -1010,7 +1017,7 @@ class Node:
1010
1017
  # return Expr.Integer(dividend % divisor)
1011
1018
 
1012
1019
  else:
1013
- evaluated_elements = [self.evaluate_expression(e, env) for e in expr.elements]
1020
+ evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
1014
1021
  return Expr.ListExpr(evaluated_elements)
1015
1022
 
1016
1023
  elif isinstance(expr, Expr.Function):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.0
3
+ Version: 0.2.3
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
@@ -16,7 +16,7 @@ Requires-Dist: cryptography==44.0.2
16
16
  Requires-Dist: blake3==1.0.4
17
17
  Dynamic: license-file
18
18
 
19
- # Astreum Python Library
19
+ # lib
20
20
 
21
21
  Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
22
22
 
@@ -28,19 +28,13 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
28
28
 
29
29
  ### Core Configuration
30
30
 
31
- | Parameter | Type | Default | Description |
32
- | -------------- | ---- | ------- | ----------- |
33
- | `machine-only` | bool | `True` | When |
34
-
35
- | |
36
- | - |
37
-
38
- | **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. | | | |
39
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
40
- | `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. |
41
- | `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. |
42
- | `storage_path` | string | `None` | Directory where objects are persisted. If *None* the node uses an in‑memory store. |
43
- | `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
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. |
44
38
 
45
39
  ### Networking
46
40
 
@@ -79,7 +73,7 @@ node = Node(config)
79
73
 
80
74
  ## Lispeum Machine Quickstart
81
75
 
82
- 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 *session* (lexical environment).
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.
83
77
 
84
78
  ```python
85
79
  from astreum.node import Node
@@ -89,16 +83,15 @@ from astreum.machine.parser import parse
89
83
  # 1. Spin‑up a stand‑alone VM (machine‑only node).
90
84
  node = Node({"machine-only": True})
91
85
 
92
- # 2. Create a fresh session (environment).
93
- session_id = node.machine_session_create()
86
+ # 2. Create an environment.
87
+ env_id = node.machine_create_environment()
94
88
 
95
89
  # 3. Convert Lispeum source → Expr AST.
96
90
  source = '(+ 1 (* 2 3))'
97
91
  expr, _ = parse(tokenize(source))
98
92
 
99
- # 4. Evaluate inside that session.
100
- env = node.sessions[session_id] # fetch the Env
101
- 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)
102
95
 
103
96
  print(result.value) # 7
104
97
  ```
@@ -107,23 +100,8 @@ print(result.value) # 7
107
100
 
108
101
  Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
109
102
 
110
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
111
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
112
-
113
- Catch the exception to provide developer‑friendly diagnostics:
114
-
115
- ```python
116
- try:
117
- tokens = tokenize(bad_source)
118
- expr, _ = parse(tokens)
119
- except ParseError as e:
120
- print("Parse failed:", e)
121
- ```
122
-
123
- Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
124
-
125
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
126
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
103
+ * Unterminated string literals are caught by `tokenize`.
104
+ * Unexpected or missing parentheses are caught by `parse`.
127
105
 
128
106
  Catch the exception to provide developer‑friendly diagnostics:
129
107
 
@@ -140,5 +118,6 @@ except ParseError as e:
140
118
  ## Testing
141
119
 
142
120
  ```bash
121
+
143
122
  python3 -m unittest discover -s tests
144
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()
astreum-0.2.0/README.md DELETED
@@ -1,126 +0,0 @@
1
- # Astreum Python Library
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`, 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.
10
-
11
- ### Core Configuration
12
-
13
- | Parameter | Type | Default | Description |
14
- | -------------- | ---- | ------- | ----------- |
15
- | `machine-only` | bool | `True` | When |
16
-
17
- | |
18
- | - |
19
-
20
- | **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. | | | |
21
- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
22
- | `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. |
23
- | `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. |
24
- | `storage_path` | string | `None` | Directory where objects are persisted. If *None* the node uses an in‑memory store. |
25
- | `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
26
-
27
- ### Networking
28
-
29
- | Parameter | Type | Default | Description |
30
- | --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
31
- | `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
32
- | `incoming_port` | int | `7373` | UDP port the relay binds to. |
33
- | `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
34
-
35
- > **Note**
36
- > The peer‑to‑peer *route* used for object discovery is always enabled.
37
- > If `validation_secret_key` is provided the node automatically joins the validation route too.
38
-
39
- ### Example
40
-
41
- ```python
42
- from astreum.node import Node
43
-
44
- config = {
45
- "machine-only": False, # run full node
46
- "relay_secret_key": "ab…cd", # optional – hex encoded
47
- "validation_secret_key": "12…34", # optional – validator
48
- "storage_path": "./data/node1",
49
- "storage_get_relay_timeout": 5,
50
- "incoming_port": 7373,
51
- "use_ipv6": False,
52
- "bootstrap": [
53
- ("bootstrap.astreum.org", 7373),
54
- ("127.0.0.1", 7374)
55
- ]
56
- }
57
-
58
- node = Node(config)
59
- # … your code …
60
- ```
61
-
62
- ## Lispeum Machine Quickstart
63
-
64
- 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 *session* (lexical environment).
65
-
66
- ```python
67
- from astreum.node import Node
68
- from astreum.machine.tokenizer import tokenize
69
- from astreum.machine.parser import parse
70
-
71
- # 1. Spin‑up a stand‑alone VM (machine‑only node).
72
- node = Node({"machine-only": True})
73
-
74
- # 2. Create a fresh session (environment).
75
- session_id = node.machine_session_create()
76
-
77
- # 3. Convert Lispeum source → Expr AST.
78
- source = '(+ 1 (* 2 3))'
79
- expr, _ = parse(tokenize(source))
80
-
81
- # 4. Evaluate inside that session.
82
- env = node.sessions[session_id] # fetch the Env
83
- result = node.machine_expr_eval(env, expr) # -> Expr.Integer(7)
84
-
85
- print(result.value) # 7
86
- ```
87
-
88
- ### Handling errors
89
-
90
- Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
91
-
92
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
93
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
94
-
95
- Catch the exception to provide developer‑friendly diagnostics:
96
-
97
- ```python
98
- try:
99
- tokens = tokenize(bad_source)
100
- expr, _ = parse(tokens)
101
- except ParseError as e:
102
- print("Parse failed:", e)
103
- ```
104
-
105
- Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
106
-
107
- * Unterminated string literals are caught by `tokenize` fileciteturn1file1
108
- * Unexpected or missing parentheses are caught by `parse` fileciteturn1file0
109
-
110
- Catch the exception to provide developer‑friendly diagnostics:
111
-
112
- ```python
113
- try:
114
- tokens = tokenize(bad_source)
115
- expr, _ = parse(tokens)
116
- except ParseError as e:
117
- print("Parse failed:", e)
118
- ```
119
-
120
- ---
121
-
122
- ## Testing
123
-
124
- ```bash
125
- python3 -m unittest discover -s tests
126
- ```
@@ -1 +0,0 @@
1
- from node import Node
@@ -1,2 +0,0 @@
1
- from parser import parse
2
- from tokenizer import tokenize
@@ -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()
@@ -1,2 +0,0 @@
1
- class ParseError(Exception):
2
- pass
File without changes
File without changes
File without changes