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.

Files changed (109) hide show
  1. astreum-0.3.21/PKG-INFO +184 -0
  2. astreum-0.3.21/README.md +166 -0
  3. {astreum-0.1.13 → astreum-0.3.21}/pyproject.toml +5 -4
  4. astreum-0.3.21/src/astreum/__init__.py +20 -0
  5. astreum-0.3.21/src/astreum/communication/__init__.py +11 -0
  6. astreum-0.3.21/src/astreum/communication/disconnect.py +57 -0
  7. astreum-0.3.21/src/astreum/communication/handlers/handshake.py +62 -0
  8. astreum-0.3.21/src/astreum/communication/handlers/object_request.py +176 -0
  9. astreum-0.3.21/src/astreum/communication/handlers/object_response.py +115 -0
  10. astreum-0.3.21/src/astreum/communication/handlers/ping.py +34 -0
  11. astreum-0.3.21/src/astreum/communication/handlers/route_request.py +76 -0
  12. astreum-0.3.21/src/astreum/communication/handlers/route_response.py +53 -0
  13. astreum-0.3.21/src/astreum/communication/models/message.py +124 -0
  14. astreum-0.3.21/src/astreum/communication/models/peer.py +51 -0
  15. astreum-0.3.21/src/astreum/communication/models/ping.py +33 -0
  16. astreum-0.3.21/src/astreum/communication/models/route.py +90 -0
  17. astreum-0.3.21/src/astreum/communication/node.py +36 -0
  18. astreum-0.3.21/src/astreum/communication/processors/incoming.py +117 -0
  19. astreum-0.3.21/src/astreum/communication/processors/outgoing.py +29 -0
  20. astreum-0.3.21/src/astreum/communication/processors/peer.py +63 -0
  21. astreum-0.3.21/src/astreum/communication/setup.py +171 -0
  22. astreum-0.3.21/src/astreum/communication/util.py +49 -0
  23. astreum-0.3.21/src/astreum/crypto/chacha20poly1305.py +74 -0
  24. astreum-0.3.21/src/astreum/crypto/ed25519.py +50 -0
  25. astreum-0.3.21/src/astreum/crypto/quadratic_form.py +123 -0
  26. astreum-0.3.21/src/astreum/crypto/wesolowski.py +154 -0
  27. astreum-0.3.21/src/astreum/crypto/x25519.py +31 -0
  28. astreum-0.3.21/src/astreum/machine/__init__.py +20 -0
  29. astreum-0.3.21/src/astreum/machine/evaluations/high_evaluation.py +237 -0
  30. astreum-0.3.21/src/astreum/machine/evaluations/low_evaluation.py +281 -0
  31. astreum-0.3.21/src/astreum/machine/evaluations/script_evaluation.py +27 -0
  32. astreum-0.3.21/src/astreum/machine/models/environment.py +31 -0
  33. astreum-0.3.21/src/astreum/machine/models/expression.py +218 -0
  34. astreum-0.3.21/src/astreum/machine/models/meter.py +18 -0
  35. astreum-0.3.21/src/astreum/machine/parser.py +51 -0
  36. astreum-0.3.21/src/astreum/machine/tokenizer.py +90 -0
  37. astreum-0.3.21/src/astreum/node.py +100 -0
  38. astreum-0.3.21/src/astreum/storage/__init__.py +7 -0
  39. astreum-0.3.21/src/astreum/storage/actions/get.py +183 -0
  40. astreum-0.3.21/src/astreum/storage/actions/set.py +176 -0
  41. astreum-0.3.21/src/astreum/storage/models/atom.py +107 -0
  42. astreum-0.3.21/src/astreum/storage/models/trie.py +502 -0
  43. astreum-0.3.21/src/astreum/storage/requests.py +28 -0
  44. astreum-0.3.21/src/astreum/storage/setup.py +22 -0
  45. astreum-0.3.21/src/astreum/utils/bytes.py +24 -0
  46. astreum-0.3.21/src/astreum/utils/config.py +109 -0
  47. astreum-0.3.21/src/astreum/utils/integer.py +25 -0
  48. astreum-0.3.21/src/astreum/utils/logging.py +219 -0
  49. astreum-0.3.21/src/astreum/validation/__init__.py +18 -0
  50. astreum-0.3.21/src/astreum/validation/genesis.py +67 -0
  51. astreum-0.3.21/src/astreum/validation/models/__init__.py +0 -0
  52. astreum-0.3.21/src/astreum/validation/models/account.py +84 -0
  53. astreum-0.3.21/src/astreum/validation/models/accounts.py +72 -0
  54. astreum-0.3.21/src/astreum/validation/models/block.py +384 -0
  55. astreum-0.3.21/src/astreum/validation/models/chain.py +66 -0
  56. astreum-0.3.21/src/astreum/validation/models/fork.py +100 -0
  57. astreum-0.3.21/src/astreum/validation/models/receipt.py +111 -0
  58. astreum-0.3.21/src/astreum/validation/models/transaction.py +255 -0
  59. astreum-0.3.21/src/astreum/validation/node.py +127 -0
  60. astreum-0.3.21/src/astreum/validation/validator.py +104 -0
  61. astreum-0.3.21/src/astreum/validation/workers/__init__.py +8 -0
  62. astreum-0.3.21/src/astreum/validation/workers/validation.py +355 -0
  63. astreum-0.3.21/src/astreum/verification/__init__.py +4 -0
  64. astreum-0.3.21/src/astreum/verification/discover.py +66 -0
  65. astreum-0.3.21/src/astreum/verification/node.py +61 -0
  66. astreum-0.3.21/src/astreum/verification/worker.py +104 -0
  67. astreum-0.3.21/src/astreum.egg-info/PKG-INFO +184 -0
  68. astreum-0.3.21/src/astreum.egg-info/SOURCES.txt +77 -0
  69. {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/requires.txt +1 -0
  70. astreum-0.1.13/PKG-INFO +0 -88
  71. astreum-0.1.13/README.md +0 -72
  72. astreum-0.1.13/src/astreum/__init__.py +0 -2
  73. astreum-0.1.13/src/astreum/lispeum/expression.py +0 -95
  74. astreum-0.1.13/src/astreum/lispeum/parser.py +0 -40
  75. astreum-0.1.13/src/astreum/lispeum/special/definition.py +0 -27
  76. astreum-0.1.13/src/astreum/lispeum/special/list/all.py +0 -32
  77. astreum-0.1.13/src/astreum/lispeum/special/list/any.py +0 -32
  78. astreum-0.1.13/src/astreum/lispeum/special/list/fold.py +0 -29
  79. astreum-0.1.13/src/astreum/lispeum/special/list/get.py +0 -20
  80. astreum-0.1.13/src/astreum/lispeum/special/list/insert.py +0 -23
  81. astreum-0.1.13/src/astreum/lispeum/special/list/map.py +0 -30
  82. astreum-0.1.13/src/astreum/lispeum/special/list/position.py +0 -33
  83. astreum-0.1.13/src/astreum/lispeum/special/list/remove.py +0 -22
  84. astreum-0.1.13/src/astreum/lispeum/storage.py +0 -410
  85. astreum-0.1.13/src/astreum/lispeum/tokenizer.py +0 -52
  86. astreum-0.1.13/src/astreum/machine/__init__.py +0 -352
  87. astreum-0.1.13/src/astreum/machine/environment.py +0 -29
  88. astreum-0.1.13/src/astreum/machine/error.py +0 -2
  89. astreum-0.1.13/src/astreum/node/__init__.py +0 -592
  90. astreum-0.1.13/src/astreum/node/models.py +0 -309
  91. astreum-0.1.13/src/astreum/node/relay/__init__.py +0 -326
  92. astreum-0.1.13/src/astreum/node/relay/bucket.py +0 -90
  93. astreum-0.1.13/src/astreum/node/relay/envelope.py +0 -280
  94. astreum-0.1.13/src/astreum/node/relay/message.py +0 -110
  95. astreum-0.1.13/src/astreum/node/relay/peer.py +0 -174
  96. astreum-0.1.13/src/astreum/node/relay/route.py +0 -161
  97. astreum-0.1.13/src/astreum/utils/bytes_format.py +0 -75
  98. astreum-0.1.13/src/astreum.egg-info/PKG-INFO +0 -88
  99. astreum-0.1.13/src/astreum.egg-info/SOURCES.txt +0 -40
  100. {astreum-0.1.13 → astreum-0.3.21}/LICENSE +0 -0
  101. {astreum-0.1.13 → astreum-0.3.21}/setup.cfg +0 -0
  102. {astreum-0.1.13/src/astreum/lispeum → astreum-0.3.21/src/astreum/communication/handlers}/__init__.py +0 -0
  103. {astreum-0.1.13/src/astreum/lispeum/special → astreum-0.3.21/src/astreum/communication/models}/__init__.py +0 -0
  104. {astreum-0.1.13/src/astreum/lispeum/special/list → astreum-0.3.21/src/astreum/communication/processors}/__init__.py +0 -0
  105. {astreum-0.1.13/src/astreum/lispeum/special/number → astreum-0.3.21/src/astreum/crypto}/__init__.py +0 -0
  106. {astreum-0.1.13/src/astreum/utils → astreum-0.3.21/src/astreum/machine/evaluations}/__init__.py +0 -0
  107. /astreum-0.1.13/src/astreum/lispeum/special/number/addition.py → /astreum-0.3.21/src/astreum/machine/models/__init__.py +0 -0
  108. {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/dependency_links.txt +0 -0
  109. {astreum-0.1.13 → astreum-0.3.21}/src/astreum.egg-info/top_level.txt +0 -0
@@ -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
+ ```
@@ -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.1.13"
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 Lispeum virtual machine."
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,11 @@
1
+ from .models.message import Message
2
+ from .models.peer import Peer
3
+ from .models.route import Route
4
+ from .setup import communication_setup
5
+
6
+ __all__ = [
7
+ "Message",
8
+ "Peer",
9
+ "Route",
10
+ "communication_setup",
11
+ ]
@@ -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