astreum 0.1.13__tar.gz → 0.3.21__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.
- astreum-0.3.21/PKG-INFO +184 -0
- astreum-0.3.21/README.md +166 -0
- {astreum-0.1.13 → astreum-0.3.21}/pyproject.toml +5 -4
- astreum-0.3.21/src/astreum/__init__.py +20 -0
- astreum-0.3.21/src/astreum/communication/__init__.py +11 -0
- astreum-0.3.21/src/astreum/communication/disconnect.py +57 -0
- astreum-0.3.21/src/astreum/communication/handlers/handshake.py +62 -0
- astreum-0.3.21/src/astreum/communication/handlers/object_request.py +176 -0
- astreum-0.3.21/src/astreum/communication/handlers/object_response.py +115 -0
- astreum-0.3.21/src/astreum/communication/handlers/ping.py +34 -0
- astreum-0.3.21/src/astreum/communication/handlers/route_request.py +76 -0
- astreum-0.3.21/src/astreum/communication/handlers/route_response.py +53 -0
- astreum-0.3.21/src/astreum/communication/models/message.py +124 -0
- astreum-0.3.21/src/astreum/communication/models/peer.py +51 -0
- astreum-0.3.21/src/astreum/communication/models/ping.py +33 -0
- astreum-0.3.21/src/astreum/communication/models/route.py +90 -0
- astreum-0.3.21/src/astreum/communication/node.py +36 -0
- astreum-0.3.21/src/astreum/communication/processors/incoming.py +117 -0
- astreum-0.3.21/src/astreum/communication/processors/outgoing.py +29 -0
- astreum-0.3.21/src/astreum/communication/processors/peer.py +63 -0
- astreum-0.3.21/src/astreum/communication/setup.py +171 -0
- astreum-0.3.21/src/astreum/communication/util.py +49 -0
- astreum-0.3.21/src/astreum/crypto/chacha20poly1305.py +74 -0
- astreum-0.3.21/src/astreum/crypto/ed25519.py +50 -0
- astreum-0.3.21/src/astreum/crypto/quadratic_form.py +123 -0
- astreum-0.3.21/src/astreum/crypto/wesolowski.py +154 -0
- astreum-0.3.21/src/astreum/crypto/x25519.py +31 -0
- astreum-0.3.21/src/astreum/machine/__init__.py +20 -0
- astreum-0.3.21/src/astreum/machine/evaluations/high_evaluation.py +237 -0
- astreum-0.3.21/src/astreum/machine/evaluations/low_evaluation.py +281 -0
- astreum-0.3.21/src/astreum/machine/evaluations/script_evaluation.py +27 -0
- astreum-0.3.21/src/astreum/machine/models/environment.py +31 -0
- astreum-0.3.21/src/astreum/machine/models/expression.py +218 -0
- astreum-0.3.21/src/astreum/machine/models/meter.py +18 -0
- astreum-0.3.21/src/astreum/machine/parser.py +51 -0
- astreum-0.3.21/src/astreum/machine/tokenizer.py +90 -0
- astreum-0.3.21/src/astreum/node.py +100 -0
- astreum-0.3.21/src/astreum/storage/__init__.py +7 -0
- astreum-0.3.21/src/astreum/storage/actions/get.py +183 -0
- astreum-0.3.21/src/astreum/storage/actions/set.py +176 -0
- astreum-0.3.21/src/astreum/storage/models/atom.py +107 -0
- astreum-0.3.21/src/astreum/storage/models/trie.py +502 -0
- astreum-0.3.21/src/astreum/storage/requests.py +28 -0
- astreum-0.3.21/src/astreum/storage/setup.py +22 -0
- astreum-0.3.21/src/astreum/utils/bytes.py +24 -0
- astreum-0.3.21/src/astreum/utils/config.py +109 -0
- astreum-0.3.21/src/astreum/utils/integer.py +25 -0
- astreum-0.3.21/src/astreum/utils/logging.py +219 -0
- astreum-0.3.21/src/astreum/validation/__init__.py +18 -0
- astreum-0.3.21/src/astreum/validation/genesis.py +67 -0
- astreum-0.3.21/src/astreum/validation/models/__init__.py +0 -0
- astreum-0.3.21/src/astreum/validation/models/account.py +84 -0
- astreum-0.3.21/src/astreum/validation/models/accounts.py +72 -0
- astreum-0.3.21/src/astreum/validation/models/block.py +384 -0
- astreum-0.3.21/src/astreum/validation/models/chain.py +66 -0
- astreum-0.3.21/src/astreum/validation/models/fork.py +100 -0
- astreum-0.3.21/src/astreum/validation/models/receipt.py +111 -0
- astreum-0.3.21/src/astreum/validation/models/transaction.py +255 -0
- astreum-0.3.21/src/astreum/validation/node.py +127 -0
- astreum-0.3.21/src/astreum/validation/validator.py +104 -0
- astreum-0.3.21/src/astreum/validation/workers/__init__.py +8 -0
- astreum-0.3.21/src/astreum/validation/workers/validation.py +355 -0
- astreum-0.3.21/src/astreum/verification/__init__.py +4 -0
- astreum-0.3.21/src/astreum/verification/discover.py +66 -0
- astreum-0.3.21/src/astreum/verification/node.py +61 -0
- astreum-0.3.21/src/astreum/verification/worker.py +104 -0
- astreum-0.3.21/src/astreum.egg-info/PKG-INFO +184 -0
- astreum-0.3.21/src/astreum.egg-info/SOURCES.txt +77 -0
- {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/requires.txt +1 -0
- astreum-0.1.13/PKG-INFO +0 -88
- astreum-0.1.13/README.md +0 -72
- astreum-0.1.13/src/astreum/__init__.py +0 -2
- astreum-0.1.13/src/astreum/lispeum/expression.py +0 -95
- astreum-0.1.13/src/astreum/lispeum/parser.py +0 -40
- astreum-0.1.13/src/astreum/lispeum/special/definition.py +0 -27
- astreum-0.1.13/src/astreum/lispeum/special/list/all.py +0 -32
- astreum-0.1.13/src/astreum/lispeum/special/list/any.py +0 -32
- astreum-0.1.13/src/astreum/lispeum/special/list/fold.py +0 -29
- astreum-0.1.13/src/astreum/lispeum/special/list/get.py +0 -20
- astreum-0.1.13/src/astreum/lispeum/special/list/insert.py +0 -23
- astreum-0.1.13/src/astreum/lispeum/special/list/map.py +0 -30
- astreum-0.1.13/src/astreum/lispeum/special/list/position.py +0 -33
- astreum-0.1.13/src/astreum/lispeum/special/list/remove.py +0 -22
- astreum-0.1.13/src/astreum/lispeum/storage.py +0 -410
- astreum-0.1.13/src/astreum/lispeum/tokenizer.py +0 -52
- astreum-0.1.13/src/astreum/machine/__init__.py +0 -352
- astreum-0.1.13/src/astreum/machine/environment.py +0 -29
- astreum-0.1.13/src/astreum/machine/error.py +0 -2
- astreum-0.1.13/src/astreum/node/__init__.py +0 -592
- astreum-0.1.13/src/astreum/node/models.py +0 -309
- astreum-0.1.13/src/astreum/node/relay/__init__.py +0 -326
- astreum-0.1.13/src/astreum/node/relay/bucket.py +0 -90
- astreum-0.1.13/src/astreum/node/relay/envelope.py +0 -280
- astreum-0.1.13/src/astreum/node/relay/message.py +0 -110
- astreum-0.1.13/src/astreum/node/relay/peer.py +0 -174
- astreum-0.1.13/src/astreum/node/relay/route.py +0 -161
- astreum-0.1.13/src/astreum/utils/bytes_format.py +0 -75
- astreum-0.1.13/src/astreum.egg-info/PKG-INFO +0 -88
- astreum-0.1.13/src/astreum.egg-info/SOURCES.txt +0 -40
- {astreum-0.1.13 → astreum-0.3.21}/LICENSE +0 -0
- {astreum-0.1.13 → astreum-0.3.21}/setup.cfg +0 -0
- {astreum-0.1.13/src/astreum/lispeum → astreum-0.3.21/src/astreum/communication/handlers}/__init__.py +0 -0
- {astreum-0.1.13/src/astreum/lispeum/special → astreum-0.3.21/src/astreum/communication/models}/__init__.py +0 -0
- {astreum-0.1.13/src/astreum/lispeum/special/list → astreum-0.3.21/src/astreum/communication/processors}/__init__.py +0 -0
- {astreum-0.1.13/src/astreum/lispeum/special/number → astreum-0.3.21/src/astreum/crypto}/__init__.py +0 -0
- {astreum-0.1.13/src/astreum/utils → astreum-0.3.21/src/astreum/machine/evaluations}/__init__.py +0 -0
- /astreum-0.1.13/src/astreum/lispeum/special/number/addition.py → /astreum-0.3.21/src/astreum/machine/models/__init__.py +0 -0
- {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/top_level.txt +0 -0
astreum-0.3.21/PKG-INFO
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astreum
|
|
3
|
+
Version: 0.3.21
|
|
4
|
+
Summary: Python library to interact with the Astreum blockchain and its virtual machine.
|
|
5
|
+
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
|
+
Project-URL: Homepage, https://github.com/astreum/lib-py
|
|
7
|
+
Project-URL: Issues, https://github.com/astreum/lib-py/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pycryptodomex==3.21.0
|
|
15
|
+
Requires-Dist: cryptography==44.0.2
|
|
16
|
+
Requires-Dist: blake3==1.0.4
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# lib
|
|
20
|
+
|
|
21
|
+
Python library to interact with the Astreum blockchain and its virtual machine.
|
|
22
|
+
|
|
23
|
+
[View on PyPI](https://pypi.org/project/astreum/)
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
When initializing an `astreum.Node`, pass a dictionary with any of the options below. Only the parameters you want to override need to be present – everything else falls back to its default.
|
|
28
|
+
|
|
29
|
+
### Core Configuration
|
|
30
|
+
|
|
31
|
+
| Parameter | Type | Default | Description |
|
|
32
|
+
| --------------------------- | ---------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `hot_storage_limit` | int | `1073741824` | Maximum bytes kept in the hot cache before new atoms are skipped (1 GiB). |
|
|
34
|
+
| `cold_storage_limit` | int | `10737418240` | Cold storage write threshold (10 GiB by default); set to `0` to skip the limit. |
|
|
35
|
+
| `cold_storage_path` | string | `None` | Directory where persisted atoms live; Astreum creates it on startup and skips cold storage when unset. |
|
|
36
|
+
| `logging_retention_days` | int | `90` | Number of days to keep rotated log files (daily gzip). |
|
|
37
|
+
| `chain_id` | int | `0` | Chain identifier used for validation (0 = test, 1 = main). |
|
|
38
|
+
| `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
|
|
39
|
+
|
|
40
|
+
### Communication
|
|
41
|
+
|
|
42
|
+
| Parameter | Type | Default | Description |
|
|
43
|
+
| ------------------------ | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `relay_secret_key` | hex string | Auto-generated | X25519 private key used for the relay route; a new keypair is created when this field is omitted. |
|
|
45
|
+
| `validation_secret_key` | hex string | `None` | Optional Ed25519 key that lets the node join the validation route; leave blank to opt out of validation. |
|
|
46
|
+
| `use_ipv6` | bool | `False` | Bind the incoming/outgoing sockets on IPv6 (the OS still listens on IPv4 if a peer speaks both). |
|
|
47
|
+
| `incoming_port` | int | `52780` | UDP port the relay binds to; pass `0` or omit to let the OS pick an ephemeral port. |
|
|
48
|
+
| `bootstrap` | list\[str\] | `[]` | Addresses to ping with a handshake before joining; each must look like `host:port` or `[ipv6]:port`. |
|
|
49
|
+
| `peer_timeout` | int | `900` | Evict peers that have not been seen within this many seconds (15 minutes). |
|
|
50
|
+
| `peer_timeout_interval` | int | `10` | How often (seconds) the peer manager checks for stale peers. |
|
|
51
|
+
|
|
52
|
+
> **Note**
|
|
53
|
+
> The peer‑to‑peer *route* used for object discovery is always enabled.
|
|
54
|
+
> If `validation_secret_key` is provided the node automatically joins the validation route too.
|
|
55
|
+
|
|
56
|
+
### Example
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from astreum.node import Node
|
|
60
|
+
|
|
61
|
+
config = {
|
|
62
|
+
"relay_secret_key": "ab…cd", # optional – hex encoded
|
|
63
|
+
"validation_secret_key": "12…34", # optional – validator
|
|
64
|
+
"hot_storage_limit": 1073741824, # cap hot cache at 1 GiB
|
|
65
|
+
"cold_storage_limit": 10737418240, # cap cold storage at 10 GiB
|
|
66
|
+
"cold_storage_path": "./data/node1",
|
|
67
|
+
"incoming_port": 52780,
|
|
68
|
+
"use_ipv6": False,
|
|
69
|
+
"bootstrap": [
|
|
70
|
+
"bootstrap.astreum.org:52780",
|
|
71
|
+
"127.0.0.1:7374"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
node = Node(config)
|
|
76
|
+
# … your code …
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Astreum Machine Quickstart
|
|
81
|
+
|
|
82
|
+
The Astreum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Astreum script, and the node tokenizes, parses, and evaluates.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
86
|
+
|
|
87
|
+
import uuid
|
|
88
|
+
from astreum import Node, Env, Expr
|
|
89
|
+
|
|
90
|
+
# 1) Spin‑up a stand‑alone VM
|
|
91
|
+
node = Node()
|
|
92
|
+
|
|
93
|
+
# 2) Create an environment (simple manual setup)
|
|
94
|
+
env_id = uuid.uuid4()
|
|
95
|
+
node.environments[env_id] = Env()
|
|
96
|
+
|
|
97
|
+
# 3) Build a function value using a low‑level stack body via `sk`.
|
|
98
|
+
# Body does: $0 $1 add (i.e., a + b)
|
|
99
|
+
low_body = Expr.ListExpr([
|
|
100
|
+
Expr.Symbol("$0"), # a (first arg)
|
|
101
|
+
Expr.Symbol("$1"), # b (second arg)
|
|
102
|
+
Expr.Symbol("add"),
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
fn_body = Expr.ListExpr([
|
|
106
|
+
Expr.Symbol("a"),
|
|
107
|
+
Expr.Symbol("b"),
|
|
108
|
+
Expr.ListExpr([low_body, Expr.Symbol("sk")]),
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
112
|
+
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
113
|
+
|
|
114
|
+
# 4) Store under the name "int.add"
|
|
115
|
+
node.env_set(env_id, "int.add", int_add_fn)
|
|
116
|
+
|
|
117
|
+
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
118
|
+
bound = node.env_get(env_id, "int.add")
|
|
119
|
+
call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
|
|
120
|
+
res = node.high_eval(env_id, call)
|
|
121
|
+
|
|
122
|
+
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
123
|
+
print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Handling errors
|
|
127
|
+
|
|
128
|
+
Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
|
|
129
|
+
|
|
130
|
+
* Unterminated string literals are caught by `tokenize`.
|
|
131
|
+
* Unexpected or missing parentheses are caught by `parse`.
|
|
132
|
+
|
|
133
|
+
Catch the exception to provide developer‑friendly diagnostics:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
try:
|
|
137
|
+
tokens = tokenize(bad_source)
|
|
138
|
+
expr, _ = parse(tokens)
|
|
139
|
+
except ParseError as e:
|
|
140
|
+
print("Parse failed:", e)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## Logging
|
|
147
|
+
|
|
148
|
+
Every `Node` instance wires up structured logging automatically:
|
|
149
|
+
|
|
150
|
+
- Logs land in per-instance files named `node.log` under `%LOCALAPPDATA%\Astreum\lib-py\logs/<instance_id>` on Windows and `$XDG_STATE_HOME` (or `~/.local/state`)/`Astreum/lib-py/logs/<instance_id>` on other platforms. The `<instance_id>` is the first 16 hex characters of a BLAKE3 hash of the caller's file path, so running the node from different entry points keeps their logs isolated.
|
|
151
|
+
- Files rotate at midnight UTC with gzip compression (`node-YYYY-MM-DD.log.gz`) and retain 90 days by default. Override via `config["logging_retention_days"]`.
|
|
152
|
+
- Each event is a single JSON line containing timestamp, level, logger, message, process/thread info, module/function, and the derived `instance_id`.
|
|
153
|
+
- Set `config["verbose"] = True` to mirror logs to stdout in a human-friendly format like `[2025-04-13-42-59] [info] Starting Astreum Node`.
|
|
154
|
+
- The very first entry emitted is the banner `Starting Astreum Node`, signalling that the logging pipeline is live before other subsystems spin up.
|
|
155
|
+
|
|
156
|
+
## Testing
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python3 -m venv venv
|
|
160
|
+
source venv/bin/activate
|
|
161
|
+
pip install -e .
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
for all tests
|
|
165
|
+
```
|
|
166
|
+
python3 -m unittest discover -s tests
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
for individual tests
|
|
170
|
+
```
|
|
171
|
+
python3 -m unittest tests.node.test_atom_get
|
|
172
|
+
python3 -m unittest tests.node.test_current_validator
|
|
173
|
+
python3 -m unittest tests.node.test_node_connection
|
|
174
|
+
python3 -m unittest tests.node.test_node_init
|
|
175
|
+
python3 -m unittest tests.node.test_node_validation
|
|
176
|
+
python3 -m unittest tests.node.tokenize
|
|
177
|
+
python3 -m unittest tests.node.parse
|
|
178
|
+
python3 -m unittest tests.node.function
|
|
179
|
+
python3 -m unittest tests.node.stack
|
|
180
|
+
python3 -m unittest tests.models.test_merkle
|
|
181
|
+
python3 -m unittest tests.models.test_patricia
|
|
182
|
+
python3 -m unittest tests.block.atom
|
|
183
|
+
python3 -m unittest tests.block.nonce
|
|
184
|
+
```
|
astreum-0.3.21/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# lib
|
|
2
|
+
|
|
3
|
+
Python library to interact with the Astreum blockchain and its 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
|
+
| `hot_storage_limit` | int | `1073741824` | Maximum bytes kept in the hot cache before new atoms are skipped (1 GiB). |
|
|
16
|
+
| `cold_storage_limit` | int | `10737418240` | Cold storage write threshold (10 GiB by default); set to `0` to skip the limit. |
|
|
17
|
+
| `cold_storage_path` | string | `None` | Directory where persisted atoms live; Astreum creates it on startup and skips cold storage when unset. |
|
|
18
|
+
| `logging_retention_days` | int | `90` | Number of days to keep rotated log files (daily gzip). |
|
|
19
|
+
| `chain_id` | int | `0` | Chain identifier used for validation (0 = test, 1 = main). |
|
|
20
|
+
| `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
|
|
21
|
+
|
|
22
|
+
### Communication
|
|
23
|
+
|
|
24
|
+
| Parameter | Type | Default | Description |
|
|
25
|
+
| ------------------------ | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `relay_secret_key` | hex string | Auto-generated | X25519 private key used for the relay route; a new keypair is created when this field is omitted. |
|
|
27
|
+
| `validation_secret_key` | hex string | `None` | Optional Ed25519 key that lets the node join the validation route; leave blank to opt out of validation. |
|
|
28
|
+
| `use_ipv6` | bool | `False` | Bind the incoming/outgoing sockets on IPv6 (the OS still listens on IPv4 if a peer speaks both). |
|
|
29
|
+
| `incoming_port` | int | `52780` | UDP port the relay binds to; pass `0` or omit to let the OS pick an ephemeral port. |
|
|
30
|
+
| `bootstrap` | list\[str\] | `[]` | Addresses to ping with a handshake before joining; each must look like `host:port` or `[ipv6]:port`. |
|
|
31
|
+
| `peer_timeout` | int | `900` | Evict peers that have not been seen within this many seconds (15 minutes). |
|
|
32
|
+
| `peer_timeout_interval` | int | `10` | How often (seconds) the peer manager checks for stale peers. |
|
|
33
|
+
|
|
34
|
+
> **Note**
|
|
35
|
+
> The peer‑to‑peer *route* used for object discovery is always enabled.
|
|
36
|
+
> If `validation_secret_key` is provided the node automatically joins the validation route too.
|
|
37
|
+
|
|
38
|
+
### Example
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from astreum.node import Node
|
|
42
|
+
|
|
43
|
+
config = {
|
|
44
|
+
"relay_secret_key": "ab…cd", # optional – hex encoded
|
|
45
|
+
"validation_secret_key": "12…34", # optional – validator
|
|
46
|
+
"hot_storage_limit": 1073741824, # cap hot cache at 1 GiB
|
|
47
|
+
"cold_storage_limit": 10737418240, # cap cold storage at 10 GiB
|
|
48
|
+
"cold_storage_path": "./data/node1",
|
|
49
|
+
"incoming_port": 52780,
|
|
50
|
+
"use_ipv6": False,
|
|
51
|
+
"bootstrap": [
|
|
52
|
+
"bootstrap.astreum.org:52780",
|
|
53
|
+
"127.0.0.1:7374"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
node = Node(config)
|
|
58
|
+
# … your code …
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Astreum Machine Quickstart
|
|
63
|
+
|
|
64
|
+
The Astreum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Astreum script, and the node tokenizes, parses, and evaluates.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
68
|
+
|
|
69
|
+
import uuid
|
|
70
|
+
from astreum import Node, Env, Expr
|
|
71
|
+
|
|
72
|
+
# 1) Spin‑up a stand‑alone VM
|
|
73
|
+
node = Node()
|
|
74
|
+
|
|
75
|
+
# 2) Create an environment (simple manual setup)
|
|
76
|
+
env_id = uuid.uuid4()
|
|
77
|
+
node.environments[env_id] = Env()
|
|
78
|
+
|
|
79
|
+
# 3) Build a function value using a low‑level stack body via `sk`.
|
|
80
|
+
# Body does: $0 $1 add (i.e., a + b)
|
|
81
|
+
low_body = Expr.ListExpr([
|
|
82
|
+
Expr.Symbol("$0"), # a (first arg)
|
|
83
|
+
Expr.Symbol("$1"), # b (second arg)
|
|
84
|
+
Expr.Symbol("add"),
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
fn_body = Expr.ListExpr([
|
|
88
|
+
Expr.Symbol("a"),
|
|
89
|
+
Expr.Symbol("b"),
|
|
90
|
+
Expr.ListExpr([low_body, Expr.Symbol("sk")]),
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
94
|
+
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
95
|
+
|
|
96
|
+
# 4) Store under the name "int.add"
|
|
97
|
+
node.env_set(env_id, "int.add", int_add_fn)
|
|
98
|
+
|
|
99
|
+
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
100
|
+
bound = node.env_get(env_id, "int.add")
|
|
101
|
+
call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
|
|
102
|
+
res = node.high_eval(env_id, call)
|
|
103
|
+
|
|
104
|
+
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
105
|
+
print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Handling errors
|
|
109
|
+
|
|
110
|
+
Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
|
|
111
|
+
|
|
112
|
+
* Unterminated string literals are caught by `tokenize`.
|
|
113
|
+
* Unexpected or missing parentheses are caught by `parse`.
|
|
114
|
+
|
|
115
|
+
Catch the exception to provide developer‑friendly diagnostics:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
try:
|
|
119
|
+
tokens = tokenize(bad_source)
|
|
120
|
+
expr, _ = parse(tokens)
|
|
121
|
+
except ParseError as e:
|
|
122
|
+
print("Parse failed:", e)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## Logging
|
|
129
|
+
|
|
130
|
+
Every `Node` instance wires up structured logging automatically:
|
|
131
|
+
|
|
132
|
+
- Logs land in per-instance files named `node.log` under `%LOCALAPPDATA%\Astreum\lib-py\logs/<instance_id>` on Windows and `$XDG_STATE_HOME` (or `~/.local/state`)/`Astreum/lib-py/logs/<instance_id>` on other platforms. The `<instance_id>` is the first 16 hex characters of a BLAKE3 hash of the caller's file path, so running the node from different entry points keeps their logs isolated.
|
|
133
|
+
- Files rotate at midnight UTC with gzip compression (`node-YYYY-MM-DD.log.gz`) and retain 90 days by default. Override via `config["logging_retention_days"]`.
|
|
134
|
+
- Each event is a single JSON line containing timestamp, level, logger, message, process/thread info, module/function, and the derived `instance_id`.
|
|
135
|
+
- Set `config["verbose"] = True` to mirror logs to stdout in a human-friendly format like `[2025-04-13-42-59] [info] Starting Astreum Node`.
|
|
136
|
+
- The very first entry emitted is the banner `Starting Astreum Node`, signalling that the logging pipeline is live before other subsystems spin up.
|
|
137
|
+
|
|
138
|
+
## Testing
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
python3 -m venv venv
|
|
142
|
+
source venv/bin/activate
|
|
143
|
+
pip install -e .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
for all tests
|
|
147
|
+
```
|
|
148
|
+
python3 -m unittest discover -s tests
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
for individual tests
|
|
152
|
+
```
|
|
153
|
+
python3 -m unittest tests.node.test_atom_get
|
|
154
|
+
python3 -m unittest tests.node.test_current_validator
|
|
155
|
+
python3 -m unittest tests.node.test_node_connection
|
|
156
|
+
python3 -m unittest tests.node.test_node_init
|
|
157
|
+
python3 -m unittest tests.node.test_node_validation
|
|
158
|
+
python3 -m unittest tests.node.tokenize
|
|
159
|
+
python3 -m unittest tests.node.parse
|
|
160
|
+
python3 -m unittest tests.node.function
|
|
161
|
+
python3 -m unittest tests.node.stack
|
|
162
|
+
python3 -m unittest tests.models.test_merkle
|
|
163
|
+
python3 -m unittest tests.models.test_patricia
|
|
164
|
+
python3 -m unittest tests.block.atom
|
|
165
|
+
python3 -m unittest tests.block.nonce
|
|
166
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "astreum"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.21"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="Roy R. O. Okello", email="roy@stelar.xyz" },
|
|
6
6
|
]
|
|
7
|
-
description = "Python library to interact with the Astreum blockchain and its
|
|
7
|
+
description = "Python library to interact with the Astreum blockchain and its virtual machine."
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.8"
|
|
10
10
|
classifiers = [
|
|
@@ -15,8 +15,9 @@ classifiers = [
|
|
|
15
15
|
dependencies = [
|
|
16
16
|
"pycryptodomex==3.21.0",
|
|
17
17
|
"cryptography==44.0.2",
|
|
18
|
+
"blake3==1.0.4"
|
|
18
19
|
]
|
|
19
20
|
|
|
20
21
|
[project.urls]
|
|
21
|
-
Homepage = "https://github.com/astreum/lib"
|
|
22
|
-
Issues = "https://github.com/astreum/lib/issues"
|
|
22
|
+
Homepage = "https://github.com/astreum/lib-py"
|
|
23
|
+
Issues = "https://github.com/astreum/lib-py/issues"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
from astreum.validation import Account, Accounts, Block, Chain, Fork, Receipt, Transaction
|
|
3
|
+
from astreum.machine import Env, Expr, parse, tokenize
|
|
4
|
+
from astreum.node import Node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__: list[str] = [
|
|
8
|
+
"Node",
|
|
9
|
+
"Env",
|
|
10
|
+
"Expr",
|
|
11
|
+
"Block",
|
|
12
|
+
"Chain",
|
|
13
|
+
"Fork",
|
|
14
|
+
"Receipt",
|
|
15
|
+
"Transaction",
|
|
16
|
+
"Account",
|
|
17
|
+
"Accounts",
|
|
18
|
+
"parse",
|
|
19
|
+
"tokenize",
|
|
20
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Helpers related to disconnecting communication components."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from astreum.node import Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_SOCKET_ATTRS: tuple[str, ...] = ("incoming_socket", "outgoing_socket")
|
|
12
|
+
_THREAD_ATTRS: tuple[str, ...] = (
|
|
13
|
+
"incoming_populate_thread",
|
|
14
|
+
"incoming_process_thread",
|
|
15
|
+
"outgoing_thread",
|
|
16
|
+
"peer_manager_thread",
|
|
17
|
+
"latest_block_discovery_thread",
|
|
18
|
+
"verify_thread",
|
|
19
|
+
"consensus_validation_thread",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _set_event(node: "Node", attr_name: str) -> None:
|
|
24
|
+
event = getattr(node, attr_name, None)
|
|
25
|
+
if event is not None:
|
|
26
|
+
event.set()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _close_socket(node: "Node", attr_name: str) -> None:
|
|
30
|
+
sock = getattr(node, attr_name, None)
|
|
31
|
+
if sock is None:
|
|
32
|
+
return
|
|
33
|
+
try:
|
|
34
|
+
sock.close()
|
|
35
|
+
except Exception as exc: # pragma: no cover - defensive logging path
|
|
36
|
+
node.logger.warning("Error closing %s: %s", attr_name, exc)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def disconnect_node(node: "Node") -> None:
|
|
40
|
+
"""Gracefully stop worker threads and close communication sockets."""
|
|
41
|
+
node.logger.info("Disconnecting Astreum Node")
|
|
42
|
+
|
|
43
|
+
_set_event(node, "communication_stop_event")
|
|
44
|
+
_set_event(node, "_validation_stop_event")
|
|
45
|
+
_set_event(node, "_verify_stop_event")
|
|
46
|
+
|
|
47
|
+
for sock_name in _SOCKET_ATTRS:
|
|
48
|
+
_close_socket(node, sock_name)
|
|
49
|
+
|
|
50
|
+
for thread_name in _THREAD_ATTRS:
|
|
51
|
+
thread = getattr(node, thread_name, None)
|
|
52
|
+
if thread is None or not thread.is_alive():
|
|
53
|
+
continue
|
|
54
|
+
thread.join(timeout=1.0)
|
|
55
|
+
|
|
56
|
+
node.is_connected = False
|
|
57
|
+
node.logger.info("Node disconnected")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Sequence
|
|
4
|
+
|
|
5
|
+
from cryptography.hazmat.primitives import serialization
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
|
|
7
|
+
|
|
8
|
+
from ..models.peer import Peer
|
|
9
|
+
from ..models.message import Message
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .... import Node
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_handshake(node: "Node", addr: Sequence[object], message: Message) -> bool:
|
|
16
|
+
"""Handle incoming handshake messages.
|
|
17
|
+
|
|
18
|
+
Returns True if the outer loop should `continue`, False otherwise.
|
|
19
|
+
"""
|
|
20
|
+
sender_public_key_bytes = message.sender_bytes
|
|
21
|
+
try:
|
|
22
|
+
sender_key = X25519PublicKey.from_public_bytes(sender_public_key_bytes)
|
|
23
|
+
except Exception as exc:
|
|
24
|
+
node.logger.warning("Error extracting sender key bytes: %s", exc)
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
host = addr[0]
|
|
29
|
+
port = int.from_bytes(message.content[:2], "big", signed=False)
|
|
30
|
+
except Exception:
|
|
31
|
+
return True
|
|
32
|
+
peer_address = (host, port)
|
|
33
|
+
|
|
34
|
+
existing_peer = node.get_peer(sender_public_key_bytes)
|
|
35
|
+
if existing_peer is not None:
|
|
36
|
+
existing_peer.address = peer_address
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
peer = Peer(
|
|
41
|
+
node_secret_key=node.relay_secret_key,
|
|
42
|
+
peer_public_key=sender_key,
|
|
43
|
+
address=peer_address,
|
|
44
|
+
)
|
|
45
|
+
except Exception:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
node.add_peer(sender_public_key_bytes, peer)
|
|
49
|
+
node.peer_route.add_peer(sender_public_key_bytes, peer)
|
|
50
|
+
|
|
51
|
+
node.logger.info(
|
|
52
|
+
"Handshake accepted from %s:%s; peer added",
|
|
53
|
+
peer_address[0],
|
|
54
|
+
peer_address[1],
|
|
55
|
+
)
|
|
56
|
+
response = Message(
|
|
57
|
+
handshake=True,
|
|
58
|
+
sender=node.relay_public_key,
|
|
59
|
+
content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
|
|
60
|
+
)
|
|
61
|
+
node.outgoing_queue.put((response.to_bytes(), peer_address))
|
|
62
|
+
return True
|