astreum 0.2.53__tar.gz → 0.3.10__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.
Files changed (101) hide show
  1. {astreum-0.2.53/src/astreum.egg-info → astreum-0.3.10}/PKG-INFO +27 -26
  2. {astreum-0.2.53 → astreum-0.3.10}/README.md +142 -141
  3. {astreum-0.2.53 → astreum-0.3.10}/pyproject.toml +4 -4
  4. astreum-0.3.10/src/astreum/__init__.py +20 -0
  5. {astreum-0.2.53/src/astreum/_communication → astreum-0.3.10/src/astreum/communication}/__init__.py +3 -3
  6. astreum-0.3.10/src/astreum/communication/handlers/handshake.py +89 -0
  7. astreum-0.3.10/src/astreum/communication/handlers/object_request.py +176 -0
  8. astreum-0.3.10/src/astreum/communication/handlers/object_response.py +115 -0
  9. astreum-0.3.10/src/astreum/communication/handlers/ping.py +34 -0
  10. astreum-0.3.10/src/astreum/communication/handlers/route_request.py +76 -0
  11. astreum-0.3.10/src/astreum/communication/handlers/route_response.py +53 -0
  12. astreum-0.3.10/src/astreum/communication/models/message.py +124 -0
  13. astreum-0.3.10/src/astreum/communication/models/peer.py +51 -0
  14. {astreum-0.2.53/src/astreum/_communication → astreum-0.3.10/src/astreum/communication/models}/route.py +7 -12
  15. astreum-0.3.10/src/astreum/communication/processors/incoming.py +98 -0
  16. astreum-0.3.10/src/astreum/communication/processors/outgoing.py +20 -0
  17. astreum-0.3.10/src/astreum/communication/setup.py +166 -0
  18. astreum-0.3.10/src/astreum/communication/start.py +37 -0
  19. {astreum-0.2.53/src/astreum/_communication → astreum-0.3.10/src/astreum/communication}/util.py +7 -0
  20. astreum-0.3.10/src/astreum/consensus/__init__.py +20 -0
  21. astreum-0.3.10/src/astreum/consensus/genesis.py +66 -0
  22. astreum-0.3.10/src/astreum/consensus/models/__init__.py +0 -0
  23. astreum-0.3.10/src/astreum/consensus/models/account.py +84 -0
  24. astreum-0.3.10/src/astreum/consensus/models/accounts.py +72 -0
  25. astreum-0.3.10/src/astreum/consensus/models/block.py +364 -0
  26. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus/models}/chain.py +7 -7
  27. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus/models}/fork.py +8 -8
  28. astreum-0.3.10/src/astreum/consensus/models/receipt.py +98 -0
  29. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus/models}/transaction.py +76 -78
  30. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus}/setup.py +18 -50
  31. astreum-0.3.10/src/astreum/consensus/start.py +67 -0
  32. astreum-0.3.10/src/astreum/consensus/validator.py +95 -0
  33. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus}/workers/discovery.py +19 -1
  34. astreum-0.3.10/src/astreum/consensus/workers/validation.py +307 -0
  35. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus}/workers/verify.py +29 -2
  36. astreum-0.3.10/src/astreum/crypto/__init__.py +0 -0
  37. astreum-0.3.10/src/astreum/crypto/chacha20poly1305.py +74 -0
  38. astreum-0.3.10/src/astreum/machine/__init__.py +20 -0
  39. astreum-0.3.10/src/astreum/machine/evaluations/__init__.py +0 -0
  40. {astreum-0.2.53/src/astreum/_lispeum → astreum-0.3.10/src/astreum/machine/evaluations}/high_evaluation.py +93 -46
  41. astreum-0.3.10/src/astreum/machine/evaluations/low_evaluation.py +281 -0
  42. astreum-0.3.10/src/astreum/machine/evaluations/script_evaluation.py +27 -0
  43. astreum-0.3.10/src/astreum/machine/models/__init__.py +0 -0
  44. astreum-0.3.10/src/astreum/machine/models/environment.py +31 -0
  45. {astreum-0.2.53/src/astreum/_lispeum → astreum-0.3.10/src/astreum/machine/models}/expression.py +45 -8
  46. astreum-0.3.10/src/astreum/machine/tokenizer.py +90 -0
  47. astreum-0.3.10/src/astreum/node.py +94 -0
  48. {astreum-0.2.53/src/astreum/_storage → astreum-0.3.10/src/astreum/storage}/__init__.py +1 -1
  49. astreum-0.3.10/src/astreum/storage/actions/get.py +183 -0
  50. astreum-0.3.10/src/astreum/storage/actions/set.py +178 -0
  51. {astreum-0.2.53/src/astreum/_storage → astreum-0.3.10/src/astreum/storage/models}/atom.py +55 -57
  52. astreum-0.2.53/src/astreum/_storage/patricia.py → astreum-0.3.10/src/astreum/storage/models/trie.py +236 -177
  53. astreum-0.3.10/src/astreum/storage/requests.py +28 -0
  54. astreum-0.3.10/src/astreum/storage/setup.py +22 -0
  55. astreum-0.3.10/src/astreum/utils/config.py +48 -0
  56. {astreum-0.2.53 → astreum-0.3.10/src/astreum.egg-info}/PKG-INFO +27 -26
  57. astreum-0.3.10/src/astreum.egg-info/SOURCES.txt +74 -0
  58. astreum-0.2.53/src/astreum/__init__.py +0 -9
  59. astreum-0.2.53/src/astreum/_communication/message.py +0 -101
  60. astreum-0.2.53/src/astreum/_communication/peer.py +0 -23
  61. astreum-0.2.53/src/astreum/_communication/setup.py +0 -322
  62. astreum-0.2.53/src/astreum/_consensus/__init__.py +0 -20
  63. astreum-0.2.53/src/astreum/_consensus/account.py +0 -95
  64. astreum-0.2.53/src/astreum/_consensus/accounts.py +0 -38
  65. astreum-0.2.53/src/astreum/_consensus/block.py +0 -311
  66. astreum-0.2.53/src/astreum/_consensus/genesis.py +0 -72
  67. astreum-0.2.53/src/astreum/_consensus/receipt.py +0 -177
  68. astreum-0.2.53/src/astreum/_consensus/workers/validation.py +0 -125
  69. astreum-0.2.53/src/astreum/_lispeum/__init__.py +0 -16
  70. astreum-0.2.53/src/astreum/_lispeum/environment.py +0 -13
  71. astreum-0.2.53/src/astreum/_lispeum/low_evaluation.py +0 -123
  72. astreum-0.2.53/src/astreum/_lispeum/tokenizer.py +0 -22
  73. astreum-0.2.53/src/astreum/_node.py +0 -163
  74. astreum-0.2.53/src/astreum/_storage/setup.py +0 -35
  75. astreum-0.2.53/src/astreum/format.py +0 -75
  76. astreum-0.2.53/src/astreum/models/block.py +0 -441
  77. astreum-0.2.53/src/astreum/models/merkle.py +0 -205
  78. astreum-0.2.53/src/astreum/models/patricia.py +0 -393
  79. astreum-0.2.53/src/astreum/node.py +0 -781
  80. astreum-0.2.53/src/astreum/storage/object.py +0 -68
  81. astreum-0.2.53/src/astreum/storage/setup.py +0 -15
  82. astreum-0.2.53/src/astreum.egg-info/SOURCES.txt +0 -60
  83. {astreum-0.2.53 → astreum-0.3.10}/LICENSE +0 -0
  84. {astreum-0.2.53 → astreum-0.3.10}/setup.cfg +0 -0
  85. {astreum-0.2.53/src/astreum/crypto → astreum-0.3.10/src/astreum/communication/handlers}/__init__.py +0 -0
  86. {astreum-0.2.53/src/astreum → astreum-0.3.10/src/astreum/communication}/models/__init__.py +0 -0
  87. {astreum-0.2.53/src/astreum/_communication → astreum-0.3.10/src/astreum/communication/models}/ping.py +0 -0
  88. {astreum-0.2.53/src/astreum/storage → astreum-0.3.10/src/astreum/communication/processors}/__init__.py +0 -0
  89. {astreum-0.2.53/src/astreum/_consensus → astreum-0.3.10/src/astreum/consensus}/workers/__init__.py +0 -0
  90. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/crypto/ed25519.py +0 -0
  91. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/crypto/quadratic_form.py +0 -0
  92. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/crypto/wesolowski.py +0 -0
  93. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/crypto/x25519.py +0 -0
  94. {astreum-0.2.53/src/astreum/_lispeum → astreum-0.3.10/src/astreum/machine/models}/meter.py +0 -0
  95. {astreum-0.2.53/src/astreum/_lispeum → astreum-0.3.10/src/astreum/machine}/parser.py +0 -0
  96. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/utils/bytes.py +0 -0
  97. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/utils/integer.py +0 -0
  98. {astreum-0.2.53 → astreum-0.3.10}/src/astreum/utils/logging.py +0 -0
  99. {astreum-0.2.53 → astreum-0.3.10}/src/astreum.egg-info/dependency_links.txt +0 -0
  100. {astreum-0.2.53 → astreum-0.3.10}/src/astreum.egg-info/requires.txt +0 -0
  101. {astreum-0.2.53 → astreum-0.3.10}/src/astreum.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.53
4
- Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
3
+ Version: 0.3.10
4
+ Summary: Python library to interact with the Astreum blockchain and its virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
- Project-URL: Homepage, https://github.com/astreum/lib
7
- Project-URL: Issues, https://github.com/astreum/lib/issues
6
+ Project-URL: Homepage, https://github.com/astreum/lib-py
7
+ Project-URL: Issues, https://github.com/astreum/lib-py/issues
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
@@ -18,7 +18,7 @@ Dynamic: license-file
18
18
 
19
19
  # lib
20
20
 
21
- Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
21
+ Python library to interact with the Astreum blockchain and its virtual machine.
22
22
 
23
23
  [View on PyPI](https://pypi.org/project/astreum/)
24
24
 
@@ -30,21 +30,21 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
30
30
 
31
31
  | Parameter | Type | Default | Description |
32
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. |
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. |
38
36
  | `logging_retention` | int | `90` | Number of days to keep rotated log files (daily gzip). |
39
37
  | `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
40
38
 
41
39
  ### Networking
42
40
 
43
- | Parameter | Type | Default | Description |
44
- | --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
45
- | `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
46
- | `incoming_port` | int | `7373` | UDP port the relay binds to. |
47
- | `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
41
+ | Parameter | Type | Default | Description |
42
+ | ------------------------ | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
43
+ | `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. |
44
+ | `validation_secret_key` | hex string | `None` | Optional Ed25519 key that lets the node join the validation route; leave blank to opt out of validation. |
45
+ | `use_ipv6` | bool | `False` | Bind the incoming/outgoing sockets on IPv6 (the OS still listens on IPv4 if a peer speaks both). |
46
+ | `incoming_port` | int | `7373` | UDP port the relay binds to; pass `0` or omit to let the OS pick an ephemeral port. |
47
+ | `bootstrap` | list\[str\] | `[]` | Addresses to ping with a handshake before joining; each must look like `host:port` or `[ipv6]:port`. |
48
48
 
49
49
  > **Note**
50
50
  > The peer‑to‑peer *route* used for object discovery is always enabled.
@@ -56,16 +56,16 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
56
56
  from astreum.node import Node
57
57
 
58
58
  config = {
59
- "machine-only": False, # run full node
60
59
  "relay_secret_key": "ab…cd", # optional – hex encoded
61
60
  "validation_secret_key": "12…34", # optional – validator
62
- "storage_path": "./data/node1",
63
- "storage_get_relay_timeout": 5,
61
+ "hot_storage_limit": 1073741824, # cap hot cache at 1 GiB
62
+ "cold_storage_limit": 10737418240, # cap cold storage at 10 GiB
63
+ "cold_storage_path": "./data/node1",
64
64
  "incoming_port": 7373,
65
65
  "use_ipv6": False,
66
66
  "bootstrap": [
67
- ("bootstrap.astreum.org", 7373),
68
- ("127.0.0.1", 7374)
67
+ "bootstrap.astreum.org:7373",
68
+ "127.0.0.1:7374"
69
69
  ]
70
70
  }
71
71
 
@@ -73,9 +73,10 @@ node = Node(config)
73
73
  # … your code …
74
74
  ```
75
75
 
76
- ## Lispeum Machine Quickstart
77
76
 
78
- The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Lispeum source text, and the node tokenizes, parses, and **evaluates** the resulting AST inside an isolated environment.
77
+ ## Astreum Machine Quickstart
78
+
79
+ The Astreum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Astreum script, and the node tokenizes, parses, and evaluates.
79
80
 
80
81
  ```python
81
82
  # Define a named function int.add (stack body) and call it with bytes 1 and 2
@@ -108,15 +109,15 @@ params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
108
109
  int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
109
110
 
110
111
  # 4) Store under the name "int.add"
111
- node.env_set(env_id, b"int.add", int_add_fn)
112
+ node.env_set(env_id, "int.add", int_add_fn)
112
113
 
113
114
  # 5) Retrieve the function and call it with bytes 1 and 2
114
- bound = node.env_get(env_id, b"int.add")
115
- call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
115
+ bound = node.env_get(env_id, "int.add")
116
+ call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
116
117
  res = node.high_eval(env_id, call)
117
118
 
118
119
  # sk returns a list of bytes; for 1+2 expect a single byte with value 3
119
- print([b.value for b in res.elements]) # [3]
120
+ print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
120
121
  ```
121
122
 
122
123
  ### Handling errors
@@ -1,141 +1,142 @@
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
- | `logging_retention` | int | `90` | Number of days to keep rotated log files (daily gzip). |
21
- | `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
22
-
23
- ### Networking
24
-
25
- | Parameter | Type | Default | Description |
26
- | --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
27
- | `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
28
- | `incoming_port` | int | `7373` | UDP port the relay binds to. |
29
- | `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
30
-
31
- > **Note**
32
- > The peer‑to‑peer *route* used for object discovery is always enabled.
33
- > If `validation_secret_key` is provided the node automatically joins the validation route too.
34
-
35
- ### Example
36
-
37
- ```python
38
- from astreum.node import Node
39
-
40
- config = {
41
- "machine-only": False, # run full node
42
- "relay_secret_key": "abcd", # optional – hex encoded
43
- "validation_secret_key": "12…34", # optional validator
44
- "storage_path": "./data/node1",
45
- "storage_get_relay_timeout": 5,
46
- "incoming_port": 7373,
47
- "use_ipv6": False,
48
- "bootstrap": [
49
- ("bootstrap.astreum.org", 7373),
50
- ("127.0.0.1", 7374)
51
- ]
52
- }
53
-
54
- node = Node(config)
55
- # … your code …
56
- ```
57
-
58
- ## Lispeum Machine Quickstart
59
-
60
- 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.
61
-
62
- ```python
63
- # Define a named function int.add (stack body) and call it with bytes 1 and 2
64
-
65
- import uuid
66
- from astreum import Node, Env, Expr
67
-
68
- # 1) Spin‑up a stand‑alone VM
69
- node = Node()
70
-
71
- # 2) Create an environment (simple manual setup)
72
- env_id = uuid.uuid4()
73
- node.environments[env_id] = Env()
74
-
75
- # 3) Build a function value using a low‑level stack body via `sk`.
76
- # Body does: $0 $1 add (i.e., a + b)
77
- low_body = Expr.ListExpr([
78
- Expr.Symbol("$0"), # a (first arg)
79
- Expr.Symbol("$1"), # b (second arg)
80
- Expr.Symbol("add"),
81
- ])
82
-
83
- fn_body = Expr.ListExpr([
84
- Expr.Symbol("a"),
85
- Expr.Symbol("b"),
86
- Expr.ListExpr([low_body, Expr.Symbol("sk")]),
87
- ])
88
-
89
- params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
90
- int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
91
-
92
- # 4) Store under the name "int.add"
93
- node.env_set(env_id, b"int.add", int_add_fn)
94
-
95
- # 5) Retrieve the function and call it with bytes 1 and 2
96
- bound = node.env_get(env_id, b"int.add")
97
- call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
98
- res = node.high_eval(env_id, call)
99
-
100
- # sk returns a list of bytes; for 1+2 expect a single byte with value 3
101
- print([b.value for b in res.elements]) # [3]
102
- ```
103
-
104
- ### Handling errors
105
-
106
- Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
107
-
108
- * Unterminated string literals are caught by `tokenize`.
109
- * Unexpected or missing parentheses are caught by `parse`.
110
-
111
- Catch the exception to provide developer‑friendly diagnostics:
112
-
113
- ```python
114
- try:
115
- tokens = tokenize(bad_source)
116
- expr, _ = parse(tokens)
117
- except ParseError as e:
118
- print("Parse failed:", e)
119
- ```
120
-
121
- ---
122
-
123
-
124
- ## Logging
125
-
126
- Every `Node` instance wires up structured logging automatically:
127
-
128
- - 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.
129
- - 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"]`.
130
- - Each event is a single JSON line containing timestamp, level, logger, message, process/thread info, module/function, and the derived `instance_id`.
131
- - Set `config["verbose"] = True` to mirror logs to stdout in a human-friendly format like `[2025-04-13-42-59] [info] Starting Astreum Node`.
132
- - The very first entry emitted is the banner `Starting Astreum Node`, signalling that the logging pipeline is live before other subsystems spin up.
133
-
134
- ## Testing
135
-
136
- ```bash
137
- python3 -m venv venv
138
- source venv/bin/activate
139
- pip install -e .
140
- python3 -m unittest discover -s tests
141
- ```
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` | int | `90` | Number of days to keep rotated log files (daily gzip). |
19
+ | `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
20
+
21
+ ### Networking
22
+
23
+ | Parameter | Type | Default | Description |
24
+ | ------------------------ | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
25
+ | `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. |
26
+ | `validation_secret_key` | hex string | `None` | Optional Ed25519 key that lets the node join the validation route; leave blank to opt out of validation. |
27
+ | `use_ipv6` | bool | `False` | Bind the incoming/outgoing sockets on IPv6 (the OS still listens on IPv4 if a peer speaks both). |
28
+ | `incoming_port` | int | `7373` | UDP port the relay binds to; pass `0` or omit to let the OS pick an ephemeral port. |
29
+ | `bootstrap` | list\[str\] | `[]` | Addresses to ping with a handshake before joining; each must look like `host:port` or `[ipv6]:port`. |
30
+
31
+ > **Note**
32
+ > The peer‑to‑peer *route* used for object discovery is always enabled.
33
+ > If `validation_secret_key` is provided the node automatically joins the validation route too.
34
+
35
+ ### Example
36
+
37
+ ```python
38
+ from astreum.node import Node
39
+
40
+ config = {
41
+ "relay_secret_key": "ab…cd", # optional hex encoded
42
+ "validation_secret_key": "1234", # optional – validator
43
+ "hot_storage_limit": 1073741824, # cap hot cache at 1 GiB
44
+ "cold_storage_limit": 10737418240, # cap cold storage at 10 GiB
45
+ "cold_storage_path": "./data/node1",
46
+ "incoming_port": 7373,
47
+ "use_ipv6": False,
48
+ "bootstrap": [
49
+ "bootstrap.astreum.org:7373",
50
+ "127.0.0.1:7374"
51
+ ]
52
+ }
53
+
54
+ node = Node(config)
55
+ # … your code …
56
+ ```
57
+
58
+
59
+ ## Astreum Machine Quickstart
60
+
61
+ The Astreum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Astreum script, and the node tokenizes, parses, and evaluates.
62
+
63
+ ```python
64
+ # Define a named function int.add (stack body) and call it with bytes 1 and 2
65
+
66
+ import uuid
67
+ from astreum import Node, Env, Expr
68
+
69
+ # 1) Spin‑up a stand‑alone VM
70
+ node = Node()
71
+
72
+ # 2) Create an environment (simple manual setup)
73
+ env_id = uuid.uuid4()
74
+ node.environments[env_id] = Env()
75
+
76
+ # 3) Build a function value using a low‑level stack body via `sk`.
77
+ # Body does: $0 $1 add (i.e., a + b)
78
+ low_body = Expr.ListExpr([
79
+ Expr.Symbol("$0"), # a (first arg)
80
+ Expr.Symbol("$1"), # b (second arg)
81
+ Expr.Symbol("add"),
82
+ ])
83
+
84
+ fn_body = Expr.ListExpr([
85
+ Expr.Symbol("a"),
86
+ Expr.Symbol("b"),
87
+ Expr.ListExpr([low_body, Expr.Symbol("sk")]),
88
+ ])
89
+
90
+ params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
91
+ int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
92
+
93
+ # 4) Store under the name "int.add"
94
+ node.env_set(env_id, "int.add", int_add_fn)
95
+
96
+ # 5) Retrieve the function and call it with bytes 1 and 2
97
+ bound = node.env_get(env_id, "int.add")
98
+ call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
99
+ res = node.high_eval(env_id, call)
100
+
101
+ # sk returns a list of bytes; for 1+2 expect a single byte with value 3
102
+ print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
103
+ ```
104
+
105
+ ### Handling errors
106
+
107
+ Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
108
+
109
+ * Unterminated string literals are caught by `tokenize`.
110
+ * Unexpected or missing parentheses are caught by `parse`.
111
+
112
+ Catch the exception to provide developer‑friendly diagnostics:
113
+
114
+ ```python
115
+ try:
116
+ tokens = tokenize(bad_source)
117
+ expr, _ = parse(tokens)
118
+ except ParseError as e:
119
+ print("Parse failed:", e)
120
+ ```
121
+
122
+ ---
123
+
124
+
125
+ ## Logging
126
+
127
+ Every `Node` instance wires up structured logging automatically:
128
+
129
+ - 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.
130
+ - 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"]`.
131
+ - Each event is a single JSON line containing timestamp, level, logger, message, process/thread info, module/function, and the derived `instance_id`.
132
+ - Set `config["verbose"] = True` to mirror logs to stdout in a human-friendly format like `[2025-04-13-42-59] [info] Starting Astreum Node`.
133
+ - The very first entry emitted is the banner `Starting Astreum Node`, signalling that the logging pipeline is live before other subsystems spin up.
134
+
135
+ ## Testing
136
+
137
+ ```bash
138
+ python3 -m venv venv
139
+ source venv/bin/activate
140
+ pip install -e .
141
+ python3 -m unittest discover -s tests
142
+ ```
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.53"
3
+ version = "0.3.10"
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 = [
@@ -19,5 +19,5 @@ dependencies = [
19
19
  ]
20
20
 
21
21
  [project.urls]
22
- Homepage = "https://github.com/astreum/lib"
23
- 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.consensus 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
+ ]
@@ -1,6 +1,6 @@
1
- from .message import Message
2
- from .peer import Peer
3
- from .route import Route
1
+ from .models.message import Message
2
+ from .models.peer import Peer
3
+ from .models.route import Route
4
4
  from .setup import communication_setup
5
5
 
6
6
  __all__ = [
@@ -0,0 +1,89 @@
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
+ old_key_bytes = node.addresses.get(peer_address)
35
+ node.addresses[peer_address] = sender_public_key_bytes
36
+
37
+ if old_key_bytes is None:
38
+ try:
39
+ peer = Peer(
40
+ node_secret_key=node.relay_secret_key,
41
+ peer_public_key=sender_key,
42
+ address=peer_address,
43
+ )
44
+ except Exception:
45
+ return True
46
+
47
+ node.add_peer(sender_public_key_bytes, peer)
48
+ node.peer_route.add_peer(sender_public_key_bytes, peer)
49
+
50
+ node.logger.info(
51
+ "Handshake accepted from %s:%s; peer added",
52
+ peer_address[0],
53
+ peer_address[1],
54
+ )
55
+ response = Message(
56
+ handshake=True,
57
+ sender=node.relay_public_key,
58
+ content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
59
+ )
60
+ node.outgoing_queue.put((response.to_bytes(), peer_address))
61
+ return True
62
+
63
+ if old_key_bytes == sender_public_key_bytes:
64
+ peer = node.get_peer(sender_public_key_bytes)
65
+ if peer is not None:
66
+ peer.address = peer_address
67
+ return False
68
+
69
+ try:
70
+ node.peer_route.remove_peer(old_key_bytes)
71
+ except Exception:
72
+ pass
73
+ try:
74
+ peer = Peer(
75
+ node_secret_key=node.relay_secret_key,
76
+ peer_public_key=sender_key,
77
+ address=peer_address,
78
+ )
79
+ except Exception:
80
+ return True
81
+
82
+ node.replace_peer(old_key_bytes, sender_public_key_bytes, peer)
83
+ node.peer_route.add_peer(sender_public_key_bytes, peer)
84
+ node.logger.info(
85
+ "Peer at %s:%s replaced due to key change",
86
+ peer_address[0],
87
+ peer_address[1],
88
+ )
89
+ return False