astreum 0.2.36__tar.gz → 0.2.38__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 (61) hide show
  1. {astreum-0.2.36/src/astreum.egg-info → astreum-0.2.38}/PKG-INFO +4 -2
  2. {astreum-0.2.36 → astreum-0.2.38}/README.md +3 -1
  3. {astreum-0.2.36 → astreum-0.2.38}/pyproject.toml +1 -1
  4. astreum-0.2.38/src/astreum/__init__.py +9 -0
  5. astreum-0.2.38/src/astreum/_communication/__init__.py +9 -0
  6. astreum-0.2.38/src/astreum/_communication/peer.py +11 -0
  7. astreum-0.2.38/src/astreum/_communication/setup.py +113 -0
  8. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/__init__.py +3 -3
  9. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/environment.py +5 -2
  10. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/expression.py +7 -7
  11. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/high_evaluation.py +8 -8
  12. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/low_evaluation.py +10 -8
  13. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/parser.py +25 -9
  14. astreum-0.2.38/src/astreum/_node.py +58 -0
  15. astreum-0.2.38/src/astreum/_storage/__init__.py +5 -0
  16. astreum-0.2.36/src/astreum/_node.py → astreum-0.2.38/src/astreum/_storage/atom.py +17 -64
  17. astreum-0.2.38/src/astreum/_validation/__init__.py +12 -0
  18. astreum-0.2.38/src/astreum/_validation/block.py +296 -0
  19. astreum-0.2.38/src/astreum/_validation/chain.py +63 -0
  20. astreum-0.2.38/src/astreum/_validation/fork.py +98 -0
  21. astreum-0.2.38/src/astreum/_validation/setup.py +221 -0
  22. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/block.py +18 -9
  23. astreum-0.2.38/src/astreum/relay/route.py +25 -0
  24. astreum-0.2.38/src/astreum/storage/__init__.py +0 -0
  25. {astreum-0.2.36 → astreum-0.2.38/src/astreum.egg-info}/PKG-INFO +4 -2
  26. {astreum-0.2.36 → astreum-0.2.38}/src/astreum.egg-info/SOURCES.txt +13 -2
  27. astreum-0.2.36/src/astreum/__init__.py +0 -1
  28. astreum-0.2.36/tests/test_node_machine.py +0 -56
  29. {astreum-0.2.36 → astreum-0.2.38}/LICENSE +0 -0
  30. {astreum-0.2.36 → astreum-0.2.38}/setup.cfg +0 -0
  31. {astreum-0.2.36/src/astreum/relay → astreum-0.2.38/src/astreum/_communication}/route.py +0 -0
  32. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/meter.py +0 -0
  33. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/_lispeum/tokenizer.py +0 -0
  34. /astreum-0.2.36/src/astreum/crypto/__init__.py → /astreum-0.2.38/src/astreum/_validation/genesis.py +0 -0
  35. {astreum-0.2.36/src/astreum/lispeum → astreum-0.2.38/src/astreum/crypto}/__init__.py +0 -0
  36. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/crypto/ed25519.py +0 -0
  37. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/crypto/quadratic_form.py +0 -0
  38. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/crypto/wesolowski.py +0 -0
  39. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/crypto/x25519.py +0 -0
  40. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/format.py +0 -0
  41. {astreum-0.2.36/src/astreum/models → astreum-0.2.38/src/astreum/lispeum}/__init__.py +0 -0
  42. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/lispeum/environment.py +0 -0
  43. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/lispeum/expression.py +0 -0
  44. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/lispeum/parser.py +0 -0
  45. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/lispeum/tokenizer.py +0 -0
  46. {astreum-0.2.36/src/astreum/relay → astreum-0.2.38/src/astreum/models}/__init__.py +0 -0
  47. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/account.py +0 -0
  48. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/accounts.py +0 -0
  49. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/merkle.py +0 -0
  50. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/message.py +0 -0
  51. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/patricia.py +0 -0
  52. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/models/transaction.py +0 -0
  53. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/node.py +0 -0
  54. {astreum-0.2.36/src/astreum/storage → astreum-0.2.38/src/astreum/relay}/__init__.py +0 -0
  55. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/relay/peer.py +0 -0
  56. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/relay/setup.py +0 -0
  57. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/storage/object.py +0 -0
  58. {astreum-0.2.36 → astreum-0.2.38}/src/astreum/storage/setup.py +0 -0
  59. {astreum-0.2.36 → astreum-0.2.38}/src/astreum.egg-info/dependency_links.txt +0 -0
  60. {astreum-0.2.36 → astreum-0.2.38}/src/astreum.egg-info/requires.txt +0 -0
  61. {astreum-0.2.36 → astreum-0.2.38}/src/astreum.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.36
3
+ Version: 0.2.38
4
4
  Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
6
  Project-URL: Homepage, https://github.com/astreum/lib
@@ -79,7 +79,7 @@ The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it
79
79
  # Define a named function int.add (stack body) and call it with bytes 1 and 2
80
80
 
81
81
  import uuid
82
- from astreum._node import Node, Env, Expr
82
+ from astreum import Node, Env, Expr
83
83
 
84
84
  # 1) Spin‑up a stand‑alone VM
85
85
  node = Node()
@@ -139,6 +139,8 @@ except ParseError as e:
139
139
  ## Testing
140
140
 
141
141
  ```bash
142
+ python3 -m venv venv
142
143
  source venv/bin/activate
144
+ pip install -e .
143
145
  python3 -m unittest discover -s tests
144
146
  ```
@@ -61,7 +61,7 @@ The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it
61
61
  # Define a named function int.add (stack body) and call it with bytes 1 and 2
62
62
 
63
63
  import uuid
64
- from astreum._node import Node, Env, Expr
64
+ from astreum import Node, Env, Expr
65
65
 
66
66
  # 1) Spin‑up a stand‑alone VM
67
67
  node = Node()
@@ -121,6 +121,8 @@ except ParseError as e:
121
121
  ## Testing
122
122
 
123
123
  ```bash
124
+ python3 -m venv venv
124
125
  source venv/bin/activate
126
+ pip install -e .
125
127
  python3 -m unittest discover -s tests
126
128
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.36"
3
+ version = "0.2.38"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,9 @@
1
+ """Lightweight package initializer to avoid circular imports during tests.
2
+
3
+ Exports are intentionally minimal; import submodules directly as needed:
4
+ - Node, Expr, Env, tokenize, parse -> from astreum._node or astreum.lispeum
5
+ - Validation types -> from astreum._validation
6
+ - Storage types -> from astreum._storage
7
+ """
8
+
9
+ __all__: list[str] = []
@@ -0,0 +1,9 @@
1
+ from .peer import Peer
2
+ from .route import Route
3
+ from .setup import communication_setup
4
+
5
+ __all__ = [
6
+ "Peer",
7
+ "Route",
8
+ "communication_setup",
9
+ ]
@@ -0,0 +1,11 @@
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)
@@ -0,0 +1,113 @@
1
+ import socket, threading
2
+ from queue import Queue
3
+ from typing import Tuple, Optional
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric.x25519 import (
7
+ X25519PrivateKey,
8
+ X25519PublicKey,
9
+ )
10
+
11
+ from typing import TYPE_CHECKING
12
+ if TYPE_CHECKING:
13
+ from .. import Node
14
+
15
+ from .import Route
16
+
17
+ def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
18
+ """DH key for relaying (always X25519)."""
19
+ return
20
+
21
+ def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
22
+ """Signing key for validation (Ed25519), or None if absent."""
23
+ return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
24
+ if hex_key else None
25
+
26
+ def make_routes(
27
+ relay_pk: X25519PublicKey,
28
+ val_sk: Optional[ed25519.Ed25519PrivateKey]
29
+ ) -> Tuple[Route, Optional[Route]]:
30
+ """Peer route (DH pubkey) + optional validation route (ed pubkey)."""
31
+ peer_rt = Route(relay_pk)
32
+ val_rt = Route(val_sk.public_key()) if val_sk else None
33
+ return peer_rt, val_rt
34
+
35
+ def setup_udp(
36
+ bind_port: int,
37
+ use_ipv6: bool
38
+ ) -> Tuple[socket.socket, int, Queue, threading.Thread, threading.Thread]:
39
+ fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
40
+ sock = socket.socket(fam, socket.SOCK_DGRAM)
41
+ if use_ipv6:
42
+ sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
43
+ sock.bind(("::" if use_ipv6 else "0.0.0.0", bind_port or 0))
44
+ port = sock.getsockname()[1]
45
+
46
+ q = Queue()
47
+ pop = threading.Thread(target=lambda: None, daemon=True)
48
+ proc = threading.Thread(target=lambda: None, daemon=True)
49
+ pop.start(); proc.start()
50
+ return sock, port, q, pop, proc
51
+
52
+ def setup_outgoing(
53
+ use_ipv6: bool
54
+ ) -> Tuple[socket.socket, Queue, threading.Thread]:
55
+ fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
56
+ sock = socket.socket(fam, socket.SOCK_DGRAM)
57
+ q = Queue()
58
+ thr = threading.Thread(target=lambda: None, daemon=True)
59
+ thr.start()
60
+ return sock, q, thr
61
+
62
+ def make_maps():
63
+ """Empty lookup maps: peers and addresses."""
64
+ return
65
+
66
+ def communication_setup(node: "Node", config: dict):
67
+ node.use_ipv6 = config.get('use_ipv6', False)
68
+
69
+ # key loading
70
+ node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
71
+ node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
72
+
73
+ # derive pubs + routes
74
+ node.relay_public_key = node.relay_secret_key.public_key()
75
+ node.validation_public_key = (
76
+ node.validation_secret_key.public_key().public_bytes(
77
+ encoding=serialization.Encoding.Raw,
78
+ format=serialization.PublicFormat.Raw,
79
+ )
80
+ if node.validation_secret_key
81
+ else None
82
+ )
83
+ node.peer_route, node.validation_route = make_routes(
84
+ node.relay_public_key,
85
+ node.validation_secret_key
86
+ )
87
+
88
+ # sockets + queues + threads
89
+ (node.incoming_socket,
90
+ node.incoming_port,
91
+ node.incoming_queue,
92
+ node.incoming_populate_thread,
93
+ node.incoming_process_thread
94
+ ) = setup_udp(config.get('incoming_port', 7373), node.use_ipv6)
95
+
96
+ (node.outgoing_socket,
97
+ node.outgoing_queue,
98
+ node.outgoing_thread
99
+ ) = setup_outgoing(node.use_ipv6)
100
+
101
+ # other workers & maps
102
+ node.object_request_queue = Queue()
103
+ node.peer_manager_thread = threading.Thread(
104
+ target=node._relay_peer_manager,
105
+ daemon=True
106
+ )
107
+ node.peer_manager_thread.start()
108
+
109
+ node.peers, node.addresses = {}, {} # peers: Dict[X25519PublicKey,Peer], addresses: Dict[(str,int),X25519PublicKey]
110
+
111
+ # bootstrap pings
112
+ for addr in config.get('bootstrap', []):
113
+ node._send_ping(addr)
@@ -2,7 +2,7 @@ from .expression import Expr
2
2
  from .environment import Env
3
3
  from .low_evaluation import low_eval
4
4
  from .meter import Meter
5
- from .parser import parse
5
+ from .parser import parse, ParseError
6
6
  from .tokenizer import tokenize
7
7
 
8
8
  __all__ = [
@@ -11,6 +11,6 @@ __all__ = [
11
11
  "low_eval",
12
12
  "Meter",
13
13
  "parse",
14
- "tokenize"
14
+ "tokenize",
15
+ "ParseError",
15
16
  ]
16
-
@@ -1,10 +1,13 @@
1
1
  from ast import Expr
2
2
  from typing import Dict, Optional
3
+ import uuid
3
4
 
4
5
 
5
6
  class Env:
6
7
  def __init__(
7
8
  self,
8
- data: Optional[Dict[str, Expr]] = None
9
+ data: Optional[Dict[str, Expr]] = None,
10
+ parent_id: Optional[uuid.UUID] = None,
9
11
  ):
10
- self.data: Dict[bytes, Expr] = {} if data is None else data
12
+ self.data: Dict[bytes, Expr] = {} if data is None else data
13
+ self.parent_id = parent_id
@@ -10,21 +10,21 @@ class Expr:
10
10
  if not self.elements:
11
11
  return "()"
12
12
  inner = " ".join(str(e) for e in self.elements)
13
- return f"({inner})"
13
+ return f"({inner} list)"
14
14
 
15
15
  class Symbol:
16
16
  def __init__(self, value: str):
17
17
  self.value = value
18
18
 
19
19
  def __repr__(self):
20
- return self.value
20
+ return f"({self.value} symbol)"
21
21
 
22
- class Byte:
23
- def __init__(self, value: int):
22
+ class Bytes:
23
+ def __init__(self, value: bytes):
24
24
  self.value = value
25
25
 
26
26
  def __repr__(self):
27
- return self.value
27
+ return f"({self.value} bytes)"
28
28
 
29
29
  class Error:
30
30
  def __init__(self, topic: str, origin: Optional['Expr'] = None):
@@ -33,5 +33,5 @@ class Expr:
33
33
 
34
34
  def __repr__(self):
35
35
  if self.origin is None:
36
- return f'({self.topic} err)'
37
- return f'({self.origin} {self.topic} err)'
36
+ return f'({self.topic} error)'
37
+ return f'({self.origin} {self.topic} error)'
@@ -1,7 +1,9 @@
1
- from typing import List, Union
2
- import uuid
3
-
4
- from src.astreum._lispeum import Env, Expr, Meter
1
+ from typing import List, Union
2
+ import uuid
3
+
4
+ from .environment import Env
5
+ from .expression import Expr
6
+ from .meter import Meter
5
7
 
6
8
 
7
9
  def high_eval(self, env_id: uuid.UUID, expr: Expr, meter = None) -> Expr:
@@ -125,9 +127,7 @@ def high_eval(self, env_id: uuid.UUID, expr: Expr, meter = None) -> Expr:
125
127
 
126
128
  # Execute low-level code built from sk-body using the caller's meter
127
129
  res = self.low_eval(code, meter=meter)
128
- if isinstance(res, Expr.Error):
129
- return res
130
- return Expr.ListExpr([Expr.Byte(b) for b in res])
130
+ return res
131
131
 
132
132
  # ---------- (... (body params fn)) HIGH-LEVEL CALL ----------
133
133
  if isinstance(tail, Expr.ListExpr):
@@ -174,4 +174,4 @@ def high_eval(self, env_id: uuid.UUID, expr: Expr, meter = None) -> Expr:
174
174
 
175
175
  # ---------- default: resolve each element and return list ----------
176
176
  resolved: List[Expr] = [self.high_eval(env_id, e, meter=meter) for e in expr.elements]
177
- return Expr.ListExpr(resolved)
177
+ return Expr.ListExpr(resolved)
@@ -1,5 +1,6 @@
1
- from typing import Dict, List, Union
2
- from src.astreum._lispeum import Expr, Meter
1
+ from typing import Dict, List, Union
2
+ from .expression import Expr
3
+ from .meter import Meter
3
4
 
4
5
  def tc_to_int(b: bytes) -> int:
5
6
  """bytes -> int using two's complement (width = len(b)*8)."""
@@ -34,18 +35,19 @@ def nand_bytes(a: bytes, b: bytes) -> bytes:
34
35
  resu = (~(au & bu)) & mask
35
36
  return resu.to_bytes(w, "big", signed=False)
36
37
 
37
- def low_eval(self, code: List[bytes], meter: Meter) -> Union[bytes, Expr.Error]:
38
+ def low_eval(self, code: List[bytes], meter: Meter) -> Expr:
38
39
 
39
40
  heap: Dict[bytes, bytes] = {}
40
41
 
41
42
  stack: List[bytes] = []
42
43
  pc = 0
43
44
 
44
- while True:
45
- if pc >= len(code):
46
- if len(stack) != 1:
47
- return Expr.Error("bad stack")
48
- return stack.pop()
45
+ while True:
46
+ if pc >= len(code):
47
+ if len(stack) != 1:
48
+ return Expr.Error("bad stack")
49
+ # wrap successful result as an Expr.Bytes
50
+ return Expr.Bytes(stack.pop())
49
51
 
50
52
  tok = code[pc]
51
53
  pc += 1
@@ -1,5 +1,5 @@
1
- from typing import List, Tuple
2
- from src.astreum._lispeum import Expr
1
+ from typing import List, Tuple
2
+ from src.astreum._lispeum import Expr
3
3
 
4
4
  class ParseError(Exception):
5
5
  pass
@@ -27,14 +27,30 @@ def _parse_one(tokens: List[str], pos: int = 0) -> Tuple[Expr, int]:
27
27
  if tok == ')':
28
28
  raise ParseError("unexpected ')'")
29
29
 
30
- # try integer → Byte
31
- try:
32
- n = int(tok)
33
- return Expr.Byte(n), pos + 1
34
- except ValueError:
35
- return Expr.Symbol(tok), pos + 1
30
+ # try integer → Bytes (variable-length two's complement)
31
+ try:
32
+ n = int(tok)
33
+ # encode as minimal-width signed two's complement, big-endian
34
+ def int_to_min_tc(v: int) -> bytes:
35
+ """Return the minimal-width signed two's complement big-endian
36
+ byte encoding of integer v. Width expands just enough so that
37
+ decoding with signed=True yields the same value and sign.
38
+ Example: 0 -> b"\x00", 127 -> b"\x7f", 128 -> b"\x00\x80".
39
+ """
40
+ if v == 0:
41
+ return b"\x00"
42
+ w = 1
43
+ while True:
44
+ try:
45
+ return v.to_bytes(w, "big", signed=True)
46
+ except OverflowError:
47
+ w += 1
48
+
49
+ return Expr.Bytes(int_to_min_tc(n)), pos + 1
50
+ except ValueError:
51
+ return Expr.Symbol(tok), pos + 1
36
52
 
37
53
  def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
38
54
  """Parse tokens into an Expr and return (expr, remaining_tokens)."""
39
55
  expr, next_pos = _parse_one(tokens, 0)
40
- return expr, tokens[next_pos:]
56
+ return expr, tokens[next_pos:]
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+ from typing import Dict, Optional
3
+ import uuid
4
+ import threading
5
+
6
+ from src.astreum._storage.atom import Atom
7
+ from src.astreum._lispeum import Env, Expr, low_eval, parse, tokenize, ParseError
8
+
9
+ def bytes_touched(*vals: bytes) -> int:
10
+ """For metering: how many bytes were manipulated (max of operands)."""
11
+ return max((len(v) for v in vals), default=1)
12
+
13
+ class Node:
14
+ def __init__(self, config: dict):
15
+ # Storage Setup
16
+ self.in_memory_storage: Dict[bytes, Atom] = {}
17
+ self.in_memory_storage_lock = threading.RLock()
18
+ # Lispeum Setup
19
+ self.environments: Dict[uuid.UUID, Env] = {}
20
+ self.machine_environments_lock = threading.RLock()
21
+ self.low_eval = low_eval
22
+ # Communication and Validation Setup (import lazily to avoid heavy deps during parsing tests)
23
+ try:
24
+ from astreum._communication import communication_setup # type: ignore
25
+ communication_setup(node=self, config=config)
26
+ except Exception:
27
+ pass
28
+ try:
29
+ from astreum._validation import validation_setup # type: ignore
30
+ validation_setup(node=self)
31
+ except Exception:
32
+ pass
33
+
34
+ # ---- Env helpers ----
35
+ def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
36
+ cur = self.environments.get(env_id)
37
+ while cur is not None:
38
+ if key in cur.data:
39
+ return cur.data[key]
40
+ cur = self.environments.get(cur.parent_id) if cur.parent_id else None
41
+ return None
42
+
43
+ def env_set(self, env_id: uuid.UUID, key: bytes, value: Expr) -> bool:
44
+ with self.machine_environments_lock:
45
+ env = self.environments.get(env_id)
46
+ if env is None:
47
+ return False
48
+ env.data[key] = value
49
+ return True
50
+
51
+ # Storage
52
+ def _local_get(self, key: bytes) -> Optional[Atom]:
53
+ with self.in_memory_storage_lock:
54
+ return self.in_memory_storage.get(key)
55
+
56
+ def _local_set(self, key: bytes, value: Atom) -> None:
57
+ with self.in_memory_storage_lock:
58
+ self.in_memory_storage[key] = value
@@ -0,0 +1,5 @@
1
+ from .atom import Atom
2
+
3
+ __all__ = [
4
+ "Atom",
5
+ ]
@@ -1,14 +1,8 @@
1
- from __future__ import annotations
2
- from typing import Dict, List, Optional, Tuple, Union
3
- import uuid
4
- import threading
5
1
 
6
- from src.astreum._lispeum import Env, Expr, Meter, low_eval
7
2
 
8
- def bytes_touched(*vals: bytes) -> int:
9
- """For metering: how many bytes were manipulated (max of operands)."""
10
- return max((len(v) for v in vals), default=1)
3
+ from typing import List, Optional, Tuple
11
4
 
5
+ from .._lispeum.expression import Expr
12
6
  from blake3 import blake3
13
7
 
14
8
  ZERO32 = b"\x00"*32
@@ -48,8 +42,6 @@ class Atom:
48
42
  return object_id == blake3(data_hash + next_hash + u64_le(size)).digest()
49
43
 
50
44
  def to_bytes(self) -> bytes:
51
- if len(self.next) != len(ZERO32):
52
- raise ValueError("next must be 32 bytes")
53
45
  return self.next + self.data
54
46
 
55
47
  @staticmethod
@@ -60,29 +52,23 @@ class Atom:
60
52
  data = buf[len(ZERO32):]
61
53
  return Atom(data=data, next=next_hash, size=len(data))
62
54
 
63
- def u32_le(n: int) -> bytes:
64
- return int(n).to_bytes(4, "little", signed=False)
65
-
66
55
  def expr_to_atoms(e: Expr) -> Tuple[bytes, List[Atom]]:
67
- def sym(v: str) -> Tuple[bytes, List[Atom]]:
68
- val = v.encode("utf-8")
69
- val_atom = Atom.from_data(u32_le(len(val)) + val)
56
+ def symbol(value: str) -> Tuple[bytes, List[Atom]]:
57
+ val = value.encode("utf-8")
58
+ val_atom = Atom.from_data(data=val)
70
59
  typ_atom = Atom.from_data(b"symbol", val_atom.object_id())
71
60
  return typ_atom.object_id(), [val_atom, typ_atom]
72
61
 
73
- def byt(v: int) -> Tuple[bytes, List[Atom]]:
74
- val_atom = Atom.from_data(bytes([v & 0xFF]))
62
+ def bytes(data: bytes) -> Tuple[bytes, List[Atom]]:
63
+ val_atom = Atom.from_data(data=data)
75
64
  typ_atom = Atom.from_data(b"byte", val_atom.object_id())
76
65
  return typ_atom.object_id(), [val_atom, typ_atom]
77
66
 
78
67
  def err(topic: str, origin: Optional[Expr]) -> Tuple[bytes, List[Atom]]:
79
- t = topic.encode("utf-8")
80
- origin_hash, acc = (ZERO32, [])
81
- if origin is not None:
82
- origin_hash, acc = expr_to_atoms(origin)
83
- val_atom = Atom.from_data(u32_le(len(t)) + t + origin_hash)
84
- typ_atom = Atom.from_data(b"error", val_atom.object_id())
85
- return typ_atom.object_id(), acc + [val_atom, typ_atom]
68
+ topic_bytes = topic.encode("utf-8")
69
+ topic_atom = Atom.from_data(data=topic_bytes)
70
+ typ_atom = Atom.from_data(data=b"error", next_hash=topic_atom.object_id())
71
+ return typ_atom.object_id(), [topic_atom, typ_atom]
86
72
 
87
73
  def lst(items: List[Expr]) -> Tuple[bytes, List[Atom]]:
88
74
  acc: List[Atom] = []
@@ -99,49 +85,16 @@ def expr_to_atoms(e: Expr) -> Tuple[bytes, List[Atom]]:
99
85
  elem_atoms.append(a)
100
86
  elem_atoms.reverse()
101
87
  head = next_hash
102
- val_atom = Atom.from_data(u64_le(len(items)), head)
103
- typ_atom = Atom.from_data(b"list", val_atom.object_id())
88
+ val_atom = Atom.from_data(data=u64_le(len(items)), next_hash=head)
89
+ typ_atom = Atom.from_data(data=b"list", next_hash=val_atom.object_id())
104
90
  return typ_atom.object_id(), acc + elem_atoms + [val_atom, typ_atom]
105
91
 
106
92
  if isinstance(e, Expr.Symbol):
107
- return sym(e.value)
108
- if isinstance(e, Expr.Byte):
109
- return byt(e.value)
93
+ return symbol(e.value)
94
+ if isinstance(e, Expr.Bytes):
95
+ return bytes(e.value)
110
96
  if isinstance(e, Expr.Error):
111
97
  return err(e.topic, e.origin)
112
98
  if isinstance(e, Expr.ListExpr):
113
99
  return lst(e.elements)
114
- raise TypeError("unknown Expr variant")
115
-
116
- class Node:
117
- def __init__(self):
118
- # Storage Setup
119
- self.in_memory_storage: Dict[bytes, bytes] = {}
120
- # Lispeum Setup
121
- self.environments: Dict[uuid.UUID, Env] = {}
122
- self.machine_environments_lock = threading.RLock()
123
- self.low_eval = low_eval
124
-
125
- # ---- Env helpers ----
126
- def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
127
- cur = self.environments.get(env_id)
128
- while cur is not None:
129
- if key in cur.data:
130
- return cur.data[key]
131
- cur = self.environments.get(cur.parent_id) if cur.parent_id else None
132
- return None
133
-
134
- def env_set(self, env_id: uuid.UUID, key: bytes, value: Expr) -> bool:
135
- with self.machine_environments_lock:
136
- env = self.environments.get(env_id)
137
- if env is None:
138
- return False
139
- env.data[key] = value
140
- return True
141
-
142
- # ---- Storage (persistent) ----
143
- def _local_get(self, key: bytes) -> Optional[bytes]:
144
- return self.in_memory_storage.get(key)
145
-
146
- def _local_set(self, key: bytes, value: bytes) -> None:
147
- self.in_memory_storage[key] = value
100
+ raise TypeError("unknown Expr variant")
@@ -0,0 +1,12 @@
1
+ from .block import Block
2
+ from .chain import Chain
3
+ from .fork import Fork
4
+ from .setup import validation_setup
5
+
6
+
7
+ __all__ = [
8
+ "Block",
9
+ "Chain",
10
+ "Fork",
11
+ "validation_setup",
12
+ ]