astreum 0.2.41__py3-none-any.whl → 0.3.1__py3-none-any.whl

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 (82) hide show
  1. astreum/__init__.py +16 -7
  2. astreum/{_communication → communication}/__init__.py +3 -3
  3. astreum/communication/handlers/handshake.py +83 -0
  4. astreum/communication/handlers/ping.py +48 -0
  5. astreum/communication/handlers/storage_request.py +81 -0
  6. astreum/communication/models/__init__.py +0 -0
  7. astreum/{_communication → communication/models}/message.py +1 -0
  8. astreum/communication/models/peer.py +23 -0
  9. astreum/{_communication → communication/models}/route.py +45 -8
  10. astreum/{_communication → communication}/setup.py +46 -95
  11. astreum/communication/start.py +38 -0
  12. astreum/consensus/__init__.py +20 -0
  13. astreum/consensus/genesis.py +66 -0
  14. astreum/consensus/models/__init__.py +0 -0
  15. astreum/consensus/models/account.py +84 -0
  16. astreum/consensus/models/accounts.py +72 -0
  17. astreum/consensus/models/block.py +364 -0
  18. astreum/{_consensus → consensus/models}/chain.py +7 -7
  19. astreum/{_consensus → consensus/models}/fork.py +8 -8
  20. astreum/consensus/models/receipt.py +98 -0
  21. astreum/consensus/models/transaction.py +213 -0
  22. astreum/{_consensus → consensus}/setup.py +26 -11
  23. astreum/consensus/start.py +68 -0
  24. astreum/consensus/validator.py +95 -0
  25. astreum/{_consensus → consensus}/workers/discovery.py +20 -1
  26. astreum/consensus/workers/validation.py +291 -0
  27. astreum/{_consensus → consensus}/workers/verify.py +32 -3
  28. astreum/machine/__init__.py +20 -0
  29. astreum/machine/evaluations/__init__.py +0 -0
  30. astreum/machine/evaluations/high_evaluation.py +237 -0
  31. astreum/machine/evaluations/low_evaluation.py +281 -0
  32. astreum/machine/evaluations/script_evaluation.py +27 -0
  33. astreum/machine/models/__init__.py +0 -0
  34. astreum/machine/models/environment.py +31 -0
  35. astreum/machine/models/expression.py +218 -0
  36. astreum/{_lispeum → machine}/parser.py +26 -31
  37. astreum/machine/tokenizer.py +90 -0
  38. astreum/node.py +73 -781
  39. astreum/storage/__init__.py +7 -0
  40. astreum/storage/actions/get.py +69 -0
  41. astreum/storage/actions/set.py +132 -0
  42. astreum/storage/models/atom.py +107 -0
  43. astreum/{_storage/patricia.py → storage/models/trie.py} +236 -177
  44. astreum/storage/setup.py +44 -15
  45. astreum/utils/bytes.py +24 -0
  46. astreum/utils/integer.py +25 -0
  47. astreum/utils/logging.py +219 -0
  48. astreum-0.3.1.dist-info/METADATA +160 -0
  49. astreum-0.3.1.dist-info/RECORD +62 -0
  50. astreum/_communication/peer.py +0 -11
  51. astreum/_consensus/__init__.py +0 -20
  52. astreum/_consensus/account.py +0 -170
  53. astreum/_consensus/accounts.py +0 -67
  54. astreum/_consensus/block.py +0 -328
  55. astreum/_consensus/genesis.py +0 -141
  56. astreum/_consensus/receipt.py +0 -177
  57. astreum/_consensus/transaction.py +0 -192
  58. astreum/_consensus/workers/validation.py +0 -122
  59. astreum/_lispeum/__init__.py +0 -16
  60. astreum/_lispeum/environment.py +0 -13
  61. astreum/_lispeum/expression.py +0 -37
  62. astreum/_lispeum/high_evaluation.py +0 -177
  63. astreum/_lispeum/low_evaluation.py +0 -123
  64. astreum/_lispeum/tokenizer.py +0 -22
  65. astreum/_node.py +0 -58
  66. astreum/_storage/__init__.py +0 -5
  67. astreum/_storage/atom.py +0 -117
  68. astreum/format.py +0 -75
  69. astreum/models/block.py +0 -441
  70. astreum/models/merkle.py +0 -205
  71. astreum/models/patricia.py +0 -393
  72. astreum/storage/object.py +0 -68
  73. astreum-0.2.41.dist-info/METADATA +0 -146
  74. astreum-0.2.41.dist-info/RECORD +0 -53
  75. /astreum/{models → communication/handlers}/__init__.py +0 -0
  76. /astreum/{_communication → communication/models}/ping.py +0 -0
  77. /astreum/{_communication → communication}/util.py +0 -0
  78. /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
  79. /astreum/{_lispeum → machine/models}/meter.py +0 -0
  80. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
  81. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
  82. {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
1
+ from __future__ import annotations
2
+
3
+ import atexit
4
+ import inspect
5
+ import gzip
6
+ import json
7
+ import logging
8
+ import logging.handlers
9
+ import os
10
+ import pathlib
11
+ import platform
12
+ import queue
13
+ import shutil
14
+ from datetime import datetime, timezone
15
+ from typing import Any, Dict, Optional
16
+
17
+ from blake3 import blake3
18
+
19
+ # Fixed identity for all loggers in this library
20
+ _ORG_NAME = "Astreum"
21
+ _PRODUCT_NAME = "lib-py"
22
+
23
+
24
+ def _safe_path(path_str: str) -> Optional[pathlib.Path]:
25
+ try:
26
+ return pathlib.Path(path_str).resolve()
27
+ except Exception:
28
+ try:
29
+ return pathlib.Path(path_str).absolute()
30
+ except Exception:
31
+ return None
32
+
33
+
34
+ def _hash_path(path: pathlib.Path) -> str:
35
+ try:
36
+ data = str(path).encode("utf-8", errors="ignore")
37
+ except Exception:
38
+ data = repr(path).encode("utf-8", errors="ignore")
39
+ return blake3(data).hexdigest()
40
+
41
+
42
+ def _find_caller_path() -> pathlib.Path:
43
+ stack = inspect.stack()
44
+ candidates: list[pathlib.Path] = []
45
+ for frame_info in stack[2:]:
46
+ filename = frame_info.filename
47
+ if not filename:
48
+ continue
49
+ path = _safe_path(filename)
50
+ if path is None:
51
+ continue
52
+ candidates.append(path)
53
+ if "astreum" not in path.parts:
54
+ return path
55
+
56
+ if candidates:
57
+ return candidates[0]
58
+ return pathlib.Path.cwd()
59
+
60
+
61
+ def _derive_instance_id() -> str:
62
+ return _hash_path(_find_caller_path())[:16]
63
+
64
+
65
+ def _log_root(org: str, product: str, instance_id: str) -> pathlib.Path:
66
+ """Resolve the base directory for logs using platform defaults."""
67
+ if platform.system() == "Windows":
68
+ base = os.getenv("LOCALAPPDATA") or str(pathlib.Path.home())
69
+ return pathlib.Path(base) / org / product / "logs" / instance_id
70
+
71
+ xdg_state = os.getenv("XDG_STATE_HOME")
72
+ base_path = pathlib.Path(xdg_state) if xdg_state else pathlib.Path.home() / ".local" / "state"
73
+ return base_path / org / product / "logs" / instance_id
74
+
75
+
76
+ class JSONFormatter(logging.Formatter):
77
+ """Log record formatter that emits JSON objects per line."""
78
+
79
+ def format(self, record: logging.LogRecord) -> str: # type: ignore[override]
80
+ payload: Dict[str, Any] = {
81
+ "ts": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(),
82
+ "level": record.levelname,
83
+ "logger": record.name,
84
+ "msg": record.getMessage(),
85
+ "pid": record.process,
86
+ "thread": record.threadName,
87
+ "module": record.module,
88
+ "func": record.funcName,
89
+ "instance_id": getattr(record, "instance_id", None),
90
+ }
91
+
92
+ for key, value in record.__dict__.items():
93
+ if key in payload or key.startswith(("_", "msecs", "relativeCreated")):
94
+ continue
95
+ try:
96
+ json.dumps(value)
97
+ except Exception:
98
+ continue
99
+ payload[key] = value
100
+
101
+ return json.dumps(payload, ensure_ascii=False)
102
+
103
+
104
+ def _gzip_rotator(src: str, dst: str) -> None:
105
+ """Rotate the log file by gzipping it and removing the original."""
106
+ with open(src, "rb") as source, gzip.open(f"{dst}.gz", "wb") as target:
107
+ shutil.copyfileobj(source, target)
108
+ os.remove(src)
109
+
110
+
111
+ def _namer(default_name: str) -> str:
112
+ """Custom name for rotated logs: node-YYYY-MM-DD.log."""
113
+ path = pathlib.Path(default_name)
114
+ parent = path.parent
115
+ name = path.name
116
+ fragments = name.split(".log.")
117
+ if len(fragments) != 2:
118
+ return default_name
119
+ stem, date_part = fragments
120
+ return str(parent / f"{stem}-{date_part}.log")
121
+
122
+
123
+ def _human_line(record: logging.LogRecord) -> str:
124
+ """Format a record as a concise human-readable line."""
125
+ dt = datetime.fromtimestamp(record.created, tz=timezone.utc)
126
+ stamp = f"{dt:%Y-%m-%d}-{dt:%S}-{dt:%M}"
127
+ return f"[{stamp}] [{record.levelname.lower()}] {record.getMessage()}"
128
+
129
+
130
+ class HumanFormatter(logging.Formatter):
131
+ """Simple formatter for optional verbose console output."""
132
+
133
+ def format(self, record: logging.LogRecord) -> str: # type: ignore[override]
134
+ return _human_line(record)
135
+
136
+
137
+ def _shutdown_listener(listener: logging.handlers.QueueListener, handlers: list[logging.Handler]) -> None:
138
+ """Stop the queue listener and close handlers on interpreter exit."""
139
+ try:
140
+ listener.stop()
141
+ except Exception:
142
+ pass
143
+ finally:
144
+ for handler in handlers:
145
+ try:
146
+ handler.close()
147
+ except Exception:
148
+ pass
149
+
150
+
151
+ def logging_setup(config: dict) -> logging.LoggerAdapter:
152
+ """Configure logging according to the runtime config and return an adapter."""
153
+ if config is None:
154
+ config = {}
155
+ elif not isinstance(config, dict):
156
+ config = dict(config)
157
+
158
+ org = _ORG_NAME
159
+ product = _PRODUCT_NAME
160
+ instance_id = _derive_instance_id()
161
+
162
+ retention_value = config.get("retention_days")
163
+ retention_days = int(retention_value) if retention_value is not None else 90
164
+
165
+ verbose = bool(config.get("verbose", False))
166
+
167
+ log_dir = _log_root(org, product, instance_id)
168
+ log_dir.mkdir(parents=True, exist_ok=True)
169
+
170
+ base_file = log_dir / "node.log"
171
+ file_handler = logging.handlers.TimedRotatingFileHandler(
172
+ filename=str(base_file),
173
+ when="midnight",
174
+ interval=1,
175
+ backupCount=max(retention_days, 0),
176
+ utc=True,
177
+ encoding="utf-8",
178
+ delay=True,
179
+ )
180
+ file_handler.setFormatter(JSONFormatter())
181
+ file_handler.rotator = _gzip_rotator
182
+ file_handler.namer = _namer
183
+
184
+ handler_list: list[logging.Handler] = [file_handler]
185
+
186
+ if verbose:
187
+ console_handler = logging.StreamHandler()
188
+ console_handler.setLevel(logging.INFO)
189
+ console_handler.setFormatter(HumanFormatter())
190
+ handler_list.append(console_handler)
191
+
192
+ log_queue: queue.Queue[logging.LogRecord] = queue.Queue(-1)
193
+ queue_handler = logging.handlers.QueueHandler(log_queue)
194
+
195
+ base_logger = logging.getLogger(f"{product}.{instance_id}")
196
+ base_logger.setLevel(logging.INFO)
197
+ base_logger.handlers.clear()
198
+ base_logger.propagate = False
199
+ base_logger.addHandler(queue_handler)
200
+
201
+ listener = logging.handlers.QueueListener(
202
+ log_queue, *handler_list, respect_handler_level=True
203
+ )
204
+ listener.daemon = True
205
+ listener.start()
206
+ atexit.register(_shutdown_listener, listener, handler_list)
207
+
208
+ adapter = logging.LoggerAdapter(base_logger, {"instance_id": instance_id})
209
+ setattr(adapter, "_queue_listener", listener)
210
+ setattr(adapter, "_handlers", handler_list)
211
+
212
+ return adapter
213
+
214
+
215
+ __all__ = [
216
+ "HumanFormatter",
217
+ "JSONFormatter",
218
+ "logging_setup",
219
+ ]
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: astreum
3
+ Version: 0.3.1
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` | int | `90` | Number of days to keep rotated log files (daily gzip). |
37
+ | `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
38
+
39
+ ### Networking
40
+
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
+
49
+ > **Note**
50
+ > The peer‑to‑peer *route* used for object discovery is always enabled.
51
+ > If `validation_secret_key` is provided the node automatically joins the validation route too.
52
+
53
+ ### Example
54
+
55
+ ```python
56
+ from astreum.node import Node
57
+
58
+ config = {
59
+ "relay_secret_key": "ab…cd", # optional – hex encoded
60
+ "validation_secret_key": "12…34", # optional – validator
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
+ "incoming_port": 7373,
65
+ "use_ipv6": False,
66
+ "bootstrap": [
67
+ "bootstrap.astreum.org:7373",
68
+ "127.0.0.1:7374"
69
+ ]
70
+ }
71
+
72
+ node = Node(config)
73
+ # … your code …
74
+ ```
75
+
76
+
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.
80
+
81
+ ```python
82
+ # Define a named function int.add (stack body) and call it with bytes 1 and 2
83
+
84
+ import uuid
85
+ from astreum import Node, Env, Expr
86
+
87
+ # 1) Spin‑up a stand‑alone VM
88
+ node = Node()
89
+
90
+ # 2) Create an environment (simple manual setup)
91
+ env_id = uuid.uuid4()
92
+ node.environments[env_id] = Env()
93
+
94
+ # 3) Build a function value using a low‑level stack body via `sk`.
95
+ # Body does: $0 $1 add (i.e., a + b)
96
+ low_body = Expr.ListExpr([
97
+ Expr.Symbol("$0"), # a (first arg)
98
+ Expr.Symbol("$1"), # b (second arg)
99
+ Expr.Symbol("add"),
100
+ ])
101
+
102
+ fn_body = Expr.ListExpr([
103
+ Expr.Symbol("a"),
104
+ Expr.Symbol("b"),
105
+ Expr.ListExpr([low_body, Expr.Symbol("sk")]),
106
+ ])
107
+
108
+ params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
109
+ int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
110
+
111
+ # 4) Store under the name "int.add"
112
+ node.env_set(env_id, "int.add", int_add_fn)
113
+
114
+ # 5) Retrieve the function and call it with bytes 1 and 2
115
+ bound = node.env_get(env_id, "int.add")
116
+ call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
117
+ res = node.high_eval(env_id, call)
118
+
119
+ # sk returns a list of bytes; for 1+2 expect a single byte with value 3
120
+ print([b.value for b in res.elements]) # [3]
121
+ ```
122
+
123
+ ### Handling errors
124
+
125
+ Both helpers raise `ParseError` (from `astreum.machine.error`) when something goes wrong:
126
+
127
+ * Unterminated string literals are caught by `tokenize`.
128
+ * Unexpected or missing parentheses are caught by `parse`.
129
+
130
+ Catch the exception to provide developer‑friendly diagnostics:
131
+
132
+ ```python
133
+ try:
134
+ tokens = tokenize(bad_source)
135
+ expr, _ = parse(tokens)
136
+ except ParseError as e:
137
+ print("Parse failed:", e)
138
+ ```
139
+
140
+ ---
141
+
142
+
143
+ ## Logging
144
+
145
+ Every `Node` instance wires up structured logging automatically:
146
+
147
+ - 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.
148
+ - 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"]`.
149
+ - Each event is a single JSON line containing timestamp, level, logger, message, process/thread info, module/function, and the derived `instance_id`.
150
+ - Set `config["verbose"] = True` to mirror logs to stdout in a human-friendly format like `[2025-04-13-42-59] [info] Starting Astreum Node`.
151
+ - The very first entry emitted is the banner `Starting Astreum Node`, signalling that the logging pipeline is live before other subsystems spin up.
152
+
153
+ ## Testing
154
+
155
+ ```bash
156
+ python3 -m venv venv
157
+ source venv/bin/activate
158
+ pip install -e .
159
+ python3 -m unittest discover -s tests
160
+ ```
@@ -0,0 +1,62 @@
1
+ astreum/__init__.py,sha256=GkEW_ReYore8_0nEOvPnZLUa3lO7CgMWu6LeEjrGXEk,325
2
+ astreum/node.py,sha256=cHZyq9ImhCB9PSROKR5lFsUau6VLCjRIfiJSZhCPFzI,2103
3
+ astreum/communication/__init__.py,sha256=wNxzsAk8Fol9cGMPuVvY4etrrMqn3SjZq1dE82kFrxw,228
4
+ astreum/communication/setup.py,sha256=qliXCj2uHvzullCPSVtUuEG9zdqHewKgYfQLfsM8tao,7236
5
+ astreum/communication/start.py,sha256=lfud8VvLeKFbkF_TwHFODg20RVpClUa4a_zsHB7ynxk,1853
6
+ astreum/communication/util.py,sha256=bJ3td3naDzmCelAJQpLwiDMoRBkijQl9YLROjsWyOrI,1256
7
+ astreum/communication/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ astreum/communication/handlers/handshake.py,sha256=twd18nnfYcyC8hLXZ0EDwUw-2mPQGRf1RYdW21x9CHM,2378
9
+ astreum/communication/handlers/ping.py,sha256=xY-QT0IoeNPKR1hyruRwJa2N8_op7aPOCZUk9X-kZWk,1258
10
+ astreum/communication/handlers/storage_request.py,sha256=rUWhoeOxVZHcvEMxi74hN9XF9SFHe9Uw-9q4pBP-KwE,2406
11
+ astreum/communication/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ astreum/communication/models/message.py,sha256=Wl1IITj7eY9_q0IOT4J7c5gsjS1bF51CH7GcSSuu5OM,3327
13
+ astreum/communication/models/peer.py,sha256=CbqkyCwhFCiC2spd1-KjNdeVGNjjt2ECVs8uHot-ETI,875
14
+ astreum/communication/models/ping.py,sha256=u_DQTZJsbMdYiDDqjdZDsLaN5na2m9WZjVeEM3zq9_Y,955
15
+ astreum/communication/models/route.py,sha256=LRHx0R1MSIln92GQbyDrZpE_hfiHDiSG_3z1Ssq_1n4,4032
16
+ astreum/consensus/__init__.py,sha256=VZR_NyGSD5VvZp3toD2zpdYwFDLBIcckeVZXFPlruuU,425
17
+ astreum/consensus/genesis.py,sha256=RI9AzQFmDTgNFuiiTmW2dDiGcURIUGmThdRpxWrUOBk,1962
18
+ astreum/consensus/setup.py,sha256=lrEapfpJXKqw4iwST11-tqPAI2VW2h3H6Ue4JDAtrP4,3142
19
+ astreum/consensus/start.py,sha256=ZUa77cINmj5AzGR8dnZ1KS0OeDIyesSmrEOx0zo4HBI,2581
20
+ astreum/consensus/validator.py,sha256=cqcmw1WEB8DkznNX_Mn8tmE956rVSNCPv1FicdL8EAQ,3647
21
+ astreum/consensus/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ astreum/consensus/models/account.py,sha256=3QcT59QUZynysLSbiywidFYVzYJ3LR6qia7JwXOwn4I,2690
23
+ astreum/consensus/models/accounts.py,sha256=iUMs6LvmMea-gxd6-ujkFjqhWmuW1cl9XTWGXQkpLys,2388
24
+ astreum/consensus/models/block.py,sha256=nNtw9TbEAF1mIEfgJr1fuswcZ0B63SVnuBANqJ5Zaac,13531
25
+ astreum/consensus/models/chain.py,sha256=SIIDFSYbag76kTUNwnuJ2_zyuhFsvT7n5HgrVTxBrvE,2797
26
+ astreum/consensus/models/fork.py,sha256=IbXRB93bUg2k3q3oQ9dOPzozV-rY-TEDFjYrw-WBymE,3859
27
+ astreum/consensus/models/receipt.py,sha256=KjKKjYp_LnP2zkX1FLIwD_4hqKV1b2TPfp43tY701q4,3336
28
+ astreum/consensus/models/transaction.py,sha256=AYa1Q-BaYW3mkOv1e3WbvDFEsYamKMiFrja-eO2zU_Y,7475
29
+ astreum/consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
30
+ astreum/consensus/workers/discovery.py,sha256=ckko9286WaK0qAaUpk_pHmQe_N3F87iGZu67OhCdtY8,2487
31
+ astreum/consensus/workers/validation.py,sha256=1jwFUL1zztuzLiYAmi92-KTUq97yraFAhuvhNhFJeLs,12223
32
+ astreum/consensus/workers/verify.py,sha256=eadF27iXOnMife_Pwz65lVwUyTEU8LGIcdGkCT_nzo0,3487
33
+ astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
35
+ astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
36
+ astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
37
+ astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
38
+ astreum/machine/__init__.py,sha256=TjWf9RlGuOGbCqdjJKidh8W4pCzUoLpi3FgutssEGoQ,479
39
+ astreum/machine/parser.py,sha256=Z_Y0Sax0rPh8JcIo19-iNDQoc5GTdGQkmfFyLpCB4bw,1757
40
+ astreum/machine/tokenizer.py,sha256=6wPqR_D3h5BEvR78XKtD45ouy77RZBbz4Yh4jHSmN4o,2394
41
+ astreum/machine/evaluations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ astreum/machine/evaluations/high_evaluation.py,sha256=0tKOvW8T7EEHrL5pZtMUSnUszYTPWSP2xnEocr1eIOk,9778
43
+ astreum/machine/evaluations/low_evaluation.py,sha256=n3LwHDD889PAoj1XW7D2Eu4WCalx5nl0mKoLrgdoLpo,10337
44
+ astreum/machine/evaluations/script_evaluation.py,sha256=eWouYUwTYzaqUyXqEe-lAJFIluW0gMeCDdXqle88oWw,864
45
+ astreum/machine/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ astreum/machine/models/environment.py,sha256=WjP6GRX_8e0-BAhzRLvQ6fYtKQEVR0LZi7DZNZS0TSE,1019
47
+ astreum/machine/models/expression.py,sha256=yYr9ktk-NWPL4EXwHz7ePvr9eNvfSBQe3yzRUz06yas,7675
48
+ astreum/machine/models/meter.py,sha256=5q2PFW7_jmgKVM1-vwE4RRjMfPEthUA4iu1CwR-Axws,505
49
+ astreum/storage/__init__.py,sha256=Flk6WXT2xGFHWWJiZHK3O5OpjoLTOFMqqIiJTtD58kY,111
50
+ astreum/storage/setup.py,sha256=udwLpSCFpneKH9DlxUB40EVjmhjqQQ2hS4dePwQKkL8,1508
51
+ astreum/storage/actions/get.py,sha256=XRNOUzD3OjMpfFPyhQQt2rE5dpS_Hdp9Yf5SYELjm30,2572
52
+ astreum/storage/actions/set.py,sha256=-eyHJW5xPRbkDV8YvPQsp_SEFkCt4HEQ0VK2soYRXvg,4210
53
+ astreum/storage/models/atom.py,sha256=FY_bgtoju59Yo7TL1DTFTr9_pRMNBuH6-u59D6bz2fc,3163
54
+ astreum/storage/models/trie.py,sha256=Bn3ssPGI7YGS4iUH5ESvpG1NE6Ljx2Xo7wkEpQhjKUY,17587
55
+ astreum/utils/bytes.py,sha256=9QTWC2JCdwWLB5R2mPtmjPro0IUzE58DL3uEul4AheE,846
56
+ astreum/utils/integer.py,sha256=iQt-klWOYVghu_NOT341MmHbOle4FDT3by4PNKNXscg,736
57
+ astreum/utils/logging.py,sha256=mRDtWSCj8vKt58WGKLNSkK9Oa0graNVSoS8URby4Q9g,6684
58
+ astreum-0.3.1.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
59
+ astreum-0.3.1.dist-info/METADATA,sha256=_fPZGHAf0_YTfkErbQwTEovrBE0x_MbUPp8uXJ04JUE,7716
60
+ astreum-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
61
+ astreum-0.3.1.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
62
+ astreum-0.3.1.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
2
- from datetime import datetime, timezone
3
-
4
- class Peer:
5
- shared_key: bytes
6
- timestamp: datetime
7
- latest_block: bytes
8
-
9
- def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
10
- self.shared_key = my_sec_key.exchange(peer_pub_key)
11
- self.timestamp = datetime.now(timezone.utc)
@@ -1,20 +0,0 @@
1
- from .account import Account
2
- from .accounts import Accounts
3
- from .block import Block
4
- from .chain import Chain
5
- from .fork import Fork
6
- from .receipt import Receipt
7
- from .transaction import Transaction
8
- from .setup import consensus_setup
9
-
10
-
11
- __all__ = [
12
- "Block",
13
- "Chain",
14
- "Fork",
15
- "Receipt",
16
- "Transaction",
17
- "Account",
18
- "Accounts",
19
- "consensus_setup",
20
- ]
@@ -1,170 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from typing import Any, Callable, List, Optional, Tuple
5
-
6
- from .._storage.atom import Atom, ZERO32
7
-
8
-
9
- def _int_to_be_bytes(value: int) -> bytes:
10
- value = int(value)
11
- if value < 0:
12
- raise ValueError("account integers must be non-negative")
13
- if value == 0:
14
- return b"\x00"
15
- size = (value.bit_length() + 7) // 8
16
- return value.to_bytes(size, "big")
17
-
18
-
19
- def _be_bytes_to_int(data: Optional[bytes]) -> int:
20
- if not data:
21
- return 0
22
- return int.from_bytes(data, "big")
23
-
24
-
25
- def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
26
- next_hash = ZERO32
27
- elements: List[Atom] = []
28
- for child_id in reversed(child_ids):
29
- elem = Atom.from_data(data=child_id, next_hash=next_hash)
30
- next_hash = elem.object_id()
31
- elements.append(elem)
32
- elements.reverse()
33
- value_atom = Atom.from_data(
34
- data=len(child_ids).to_bytes(8, "little"),
35
- next_hash=next_hash,
36
- )
37
- type_atom = Atom.from_data(data=b"list", next_hash=value_atom.object_id())
38
- atoms = elements + [value_atom, type_atom]
39
- return type_atom.object_id(), atoms
40
-
41
-
42
- def _resolve_storage_get(source: Any) -> Callable[[bytes], Optional[Atom]]:
43
- if callable(source):
44
- return source
45
- getter = getattr(source, "_local_get", None)
46
- if callable(getter):
47
- return getter
48
- raise TypeError("Account.from_atom needs a callable storage getter or node with '_local_get'")
49
-
50
-
51
- def _read_list_entries(
52
- storage_get: Callable[[bytes], Optional[Atom]],
53
- start: bytes,
54
- ) -> List[bytes]:
55
- entries: List[bytes] = []
56
- current = start if start and start != ZERO32 else b""
57
- while current:
58
- elem = storage_get(current)
59
- if elem is None:
60
- break
61
- entries.append(elem.data)
62
- nxt = elem.next
63
- current = nxt if nxt and nxt != ZERO32 else b""
64
- return entries
65
-
66
-
67
- @dataclass
68
- class Account:
69
- _balance: int
70
- _data: bytes
71
- _nonce: int
72
- hash: bytes = ZERO32
73
- atoms: List[Atom] = field(default_factory=list)
74
-
75
- @staticmethod
76
- def _encode(balance: int, data: bytes, nonce: int) -> Tuple[bytes, List[Atom]]:
77
- balance_atom = Atom.from_data(data=_int_to_be_bytes(balance))
78
- data_atom = Atom.from_data(data=bytes(data))
79
- nonce_atom = Atom.from_data(data=_int_to_be_bytes(nonce))
80
-
81
- field_atoms = [balance_atom, data_atom, nonce_atom]
82
- field_ids = [a.object_id() for a in field_atoms]
83
-
84
- body_id, body_atoms = _make_list(field_ids)
85
- type_atom = Atom.from_data(data=b"account", next_hash=body_id)
86
- top_id, top_atoms = _make_list([type_atom.object_id(), body_id])
87
-
88
- atoms = field_atoms + body_atoms + [type_atom] + top_atoms
89
- return top_id, atoms
90
-
91
- @classmethod
92
- def create(cls, balance: int, data: bytes, nonce: int) -> "Account":
93
- account_hash, atoms = cls._encode(balance, data, nonce)
94
- return cls(
95
- _balance=int(balance),
96
- _data=bytes(data),
97
- _nonce=int(nonce),
98
- hash=account_hash,
99
- atoms=atoms,
100
- )
101
-
102
- @classmethod
103
- def from_atom(cls, source: Any, account_id: bytes) -> "Account":
104
- storage_get = _resolve_storage_get(source)
105
-
106
- outer_list = storage_get(account_id)
107
- if outer_list is None or outer_list.data != b"list":
108
- raise ValueError("not an account (outer list missing)")
109
-
110
- outer_value = storage_get(outer_list.next)
111
- if outer_value is None:
112
- raise ValueError("malformed account (outer value missing)")
113
-
114
- entries = _read_list_entries(storage_get, outer_value.next)
115
- if len(entries) < 2:
116
- raise ValueError("malformed account (type/body missing)")
117
-
118
- type_atom_id, body_id = entries[0], entries[1]
119
- type_atom = storage_get(type_atom_id)
120
- if type_atom is None or type_atom.data != b"account":
121
- raise ValueError("not an account (type mismatch)")
122
-
123
- body_list = storage_get(body_id)
124
- if body_list is None or body_list.data != b"list":
125
- raise ValueError("malformed account body (type)")
126
-
127
- body_value = storage_get(body_list.next)
128
- if body_value is None:
129
- raise ValueError("malformed account body (value)")
130
-
131
- field_ids = _read_list_entries(storage_get, body_value.next)
132
- if len(field_ids) < 3:
133
- field_ids.extend([ZERO32] * (3 - len(field_ids)))
134
-
135
- def _read_field(field_id: bytes) -> bytes:
136
- if not field_id or field_id == ZERO32:
137
- return b""
138
- atom = storage_get(field_id)
139
- return atom.data if atom is not None else b""
140
-
141
- balance_bytes = _read_field(field_ids[0])
142
- data_bytes = _read_field(field_ids[1])
143
- nonce_bytes = _read_field(field_ids[2])
144
-
145
- account = cls.create(
146
- balance=_be_bytes_to_int(balance_bytes),
147
- data=data_bytes,
148
- nonce=_be_bytes_to_int(nonce_bytes),
149
- )
150
- if account.hash != account_id:
151
- raise ValueError("account hash mismatch while decoding")
152
- return account
153
-
154
- def balance(self) -> int:
155
- return self._balance
156
-
157
- def data(self) -> bytes:
158
- return self._data
159
-
160
- def nonce(self) -> int:
161
- return self._nonce
162
-
163
- def body_hash(self) -> bytes:
164
- return self.hash
165
-
166
- def to_atom(self) -> Tuple[bytes, List[Atom]]:
167
- account_hash, atoms = self._encode(self._balance, self._data, self._nonce)
168
- self.hash = account_hash
169
- self.atoms = atoms
170
- return account_hash, list(atoms)