astreum 0.2.37__tar.gz → 0.2.39__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 (60) hide show
  1. {astreum-0.2.37/src/astreum.egg-info → astreum-0.2.39}/PKG-INFO +1 -1
  2. {astreum-0.2.37 → astreum-0.2.39}/pyproject.toml +1 -1
  3. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_communication/setup.py +14 -5
  4. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/__init__.py +6 -4
  5. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/setup.py +70 -2
  6. astreum-0.2.39/src/astreum/_consensus/transaction.py +172 -0
  7. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_node.py +3 -3
  8. {astreum-0.2.37 → astreum-0.2.39/src/astreum.egg-info}/PKG-INFO +1 -1
  9. {astreum-0.2.37 → astreum-0.2.39}/src/astreum.egg-info/SOURCES.txt +7 -6
  10. {astreum-0.2.37 → astreum-0.2.39}/LICENSE +0 -0
  11. {astreum-0.2.37 → astreum-0.2.39}/README.md +0 -0
  12. {astreum-0.2.37 → astreum-0.2.39}/setup.cfg +0 -0
  13. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/__init__.py +0 -0
  14. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_communication/__init__.py +0 -0
  15. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_communication/peer.py +0 -0
  16. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_communication/route.py +0 -0
  17. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/block.py +0 -0
  18. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/chain.py +0 -0
  19. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/fork.py +0 -0
  20. {astreum-0.2.37/src/astreum/_validation → astreum-0.2.39/src/astreum/_consensus}/genesis.py +0 -0
  21. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/__init__.py +0 -0
  22. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/environment.py +0 -0
  23. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/expression.py +0 -0
  24. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/high_evaluation.py +0 -0
  25. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/low_evaluation.py +0 -0
  26. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/meter.py +0 -0
  27. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/parser.py +0 -0
  28. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_lispeum/tokenizer.py +0 -0
  29. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_storage/__init__.py +0 -0
  30. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/_storage/atom.py +0 -0
  31. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/crypto/__init__.py +0 -0
  32. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/crypto/ed25519.py +0 -0
  33. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/crypto/quadratic_form.py +0 -0
  34. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/crypto/wesolowski.py +0 -0
  35. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/crypto/x25519.py +0 -0
  36. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/format.py +0 -0
  37. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/lispeum/__init__.py +0 -0
  38. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/lispeum/environment.py +0 -0
  39. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/lispeum/expression.py +0 -0
  40. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/lispeum/parser.py +0 -0
  41. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/lispeum/tokenizer.py +0 -0
  42. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/__init__.py +0 -0
  43. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/account.py +0 -0
  44. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/accounts.py +0 -0
  45. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/block.py +0 -0
  46. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/merkle.py +0 -0
  47. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/message.py +0 -0
  48. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/patricia.py +0 -0
  49. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/models/transaction.py +0 -0
  50. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/node.py +0 -0
  51. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/relay/__init__.py +0 -0
  52. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/relay/peer.py +0 -0
  53. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/relay/route.py +0 -0
  54. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/relay/setup.py +0 -0
  55. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/storage/__init__.py +0 -0
  56. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/storage/object.py +0 -0
  57. {astreum-0.2.37 → astreum-0.2.39}/src/astreum/storage/setup.py +0 -0
  58. {astreum-0.2.37 → astreum-0.2.39}/src/astreum.egg-info/dependency_links.txt +0 -0
  59. {astreum-0.2.37 → astreum-0.2.39}/src/astreum.egg-info/requires.txt +0 -0
  60. {astreum-0.2.37 → astreum-0.2.39}/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.37
3
+ Version: 0.2.39
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.37"
3
+ version = "0.2.39"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -1,7 +1,8 @@
1
1
  import socket, threading
2
2
  from queue import Queue
3
3
  from typing import Tuple, Optional
4
- from cryptography.hazmat.primitives.asymmetric import ed25519
4
+ from cryptography.hazmat.primitives.asymmetric import ed25519
5
+ from cryptography.hazmat.primitives import serialization
5
6
  from cryptography.hazmat.primitives.asymmetric.x25519 import (
6
7
  X25519PrivateKey,
7
8
  X25519PublicKey,
@@ -67,10 +68,18 @@ def communication_setup(node: "Node", config: dict):
67
68
 
68
69
  # key loading
69
70
  node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
70
- node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
71
-
72
- # derive pubs + routes
73
- node.relay_public_key = node.relay_secret_key.public_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
+ )
74
83
  node.peer_route, node.validation_route = make_routes(
75
84
  node.relay_public_key,
76
85
  node.validation_secret_key
@@ -1,12 +1,14 @@
1
1
  from .block import Block
2
2
  from .chain import Chain
3
3
  from .fork import Fork
4
- from .setup import validation_setup
5
-
6
-
4
+ from .transaction import Transaction
5
+ from .setup import consensus_setup
6
+
7
+
7
8
  __all__ = [
8
9
  "Block",
9
10
  "Chain",
10
11
  "Fork",
11
- "validation_setup",
12
+ "Transaction",
13
+ "consensus_setup",
12
14
  ]
@@ -2,16 +2,27 @@ from __future__ import annotations
2
2
 
3
3
  import threading
4
4
  import time
5
- from queue import Queue, Empty
5
+ from queue import Empty, Queue
6
6
  from typing import Any, Dict, Optional, Tuple
7
7
 
8
8
  from .block import Block
9
9
  from .chain import Chain
10
10
  from .fork import Fork
11
+ from .transaction import Transaction
11
12
  from .._storage.atom import ZERO32, Atom
12
13
 
13
14
 
14
- def validation_setup(node: Any) -> None:
15
+ def current_validator(node: Any) -> bytes:
16
+ """Return the current validator identifier. Override downstream."""
17
+ raise NotImplementedError("current_validator must be implemented by the host node")
18
+
19
+
20
+ def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None:
21
+ """Apply transaction to the candidate block. Override downstream."""
22
+ pass
23
+
24
+
25
+ def consensus_setup(node: Any) -> None:
15
26
  # Shared state
16
27
  node.validation_lock = getattr(node, "validation_lock", threading.RLock())
17
28
 
@@ -21,6 +32,10 @@ def validation_setup(node: Any) -> None:
21
32
  node.chains = getattr(node, "chains", {})
22
33
  node.forks = getattr(node, "forks", {})
23
34
 
35
+ # Pending transactions queue (hash-only entries)
36
+ node._validation_transaction_queue = getattr(
37
+ node, "_validation_transaction_queue", Queue()
38
+ )
24
39
  # Single work queue of grouped items: (latest_block_hash, set(peer_ids))
25
40
  node._validation_verify_queue = getattr(
26
41
  node, "_validation_verify_queue", Queue()
@@ -29,6 +44,14 @@ def validation_setup(node: Any) -> None:
29
44
  node, "_validation_stop_event", threading.Event()
30
45
  )
31
46
 
47
+ def enqueue_transaction_hash(tx_hash: bytes) -> None:
48
+ """Schedule a transaction hash for validation processing."""
49
+ if not isinstance(tx_hash, (bytes, bytearray)):
50
+ raise TypeError("transaction hash must be bytes-like")
51
+ node._validation_transaction_queue.put(bytes(tx_hash))
52
+
53
+ node.enqueue_transaction_hash = enqueue_transaction_hash
54
+
32
55
  def _process_peers_latest_block(latest_block_hash: bytes, peer_ids: set[Any]) -> None:
33
56
  """Assign a peer to a fork for its latest block without merging forks.
34
57
 
@@ -130,6 +153,46 @@ def validation_setup(node: Any) -> None:
130
153
  except Exception:
131
154
  pass
132
155
 
156
+ def _validation_worker() -> None:
157
+ """Consume pending transactions when scheduled to validate."""
158
+ stop = node._validation_stop_event
159
+ while not stop.is_set():
160
+ validation_public_key = getattr(node, "validation_public_key", None)
161
+ if not validation_public_key:
162
+ time.sleep(0.5)
163
+ continue
164
+
165
+ scheduled_validator = current_validator(node)
166
+
167
+ if scheduled_validator != validation_public_key:
168
+ time.sleep(0.5)
169
+ continue
170
+
171
+ try:
172
+ current_hash = node._validation_transaction_queue.get_nowait()
173
+ except Empty:
174
+ time.sleep(0.1)
175
+ continue
176
+
177
+ new_block = Block()
178
+ new_block.validator_public_key = getattr(node, "validation_public_key", None)
179
+
180
+ while True:
181
+ try:
182
+ apply_transaction(node, new_block, current_hash)
183
+ except NotImplementedError:
184
+ node._validation_transaction_queue.put(current_hash)
185
+ time.sleep(0.5)
186
+ break
187
+ except Exception:
188
+ # Skip problematic transaction; leave block as-is.
189
+ pass
190
+
191
+ try:
192
+ current_hash = node._validation_transaction_queue.get_nowait()
193
+ except Empty:
194
+ break
195
+
133
196
  # Start workers as daemons
134
197
  node.validation_discovery_thread = threading.Thread(
135
198
  target=_discovery_worker, daemon=True, name="validation-discovery"
@@ -137,5 +200,10 @@ def validation_setup(node: Any) -> None:
137
200
  node.validation_verify_thread = threading.Thread(
138
201
  target=_verify_worker, daemon=True, name="validation-verify"
139
202
  )
203
+ node.validation_worker_thread = threading.Thread(
204
+ target=_validation_worker, daemon=True, name="validation-worker"
205
+ )
140
206
  node.validation_discovery_thread.start()
141
207
  node.validation_verify_thread.start()
208
+ if getattr(node, "validation_secret_key", None):
209
+ node.validation_worker_thread.start()
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Callable, List, Optional, Tuple
5
+
6
+ from .._storage.atom import Atom, ZERO32
7
+
8
+
9
+ def _int_to_be_bytes(value: Optional[int]) -> bytes:
10
+ if value is None:
11
+ return b""
12
+ value = int(value)
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_typed_bytes(payload: bytes) -> Tuple[bytes, List[Atom]]:
26
+ value_atom = Atom.from_data(data=payload)
27
+ type_atom = Atom.from_data(data=b"byte", next_hash=value_atom.object_id())
28
+ return type_atom.object_id(), [value_atom, type_atom]
29
+
30
+
31
+ def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
32
+ atoms: List[Atom] = []
33
+ next_hash = ZERO32
34
+ chain: List[Atom] = []
35
+ for child_id in reversed(child_ids):
36
+ elem = Atom.from_data(data=child_id, next_hash=next_hash)
37
+ next_hash = elem.object_id()
38
+ chain.append(elem)
39
+
40
+ chain.reverse()
41
+ list_value = Atom.from_data(data=len(child_ids).to_bytes(8, "little"), next_hash=next_hash)
42
+ list_type = Atom.from_data(data=b"list", next_hash=list_value.object_id())
43
+ atoms.extend(chain)
44
+ atoms.append(list_value)
45
+ atoms.append(list_type)
46
+ return list_type.object_id(), atoms
47
+
48
+
49
+ @dataclass
50
+ class Transaction:
51
+ amount: int
52
+ counter: int
53
+ data: bytes = b""
54
+ recipient: bytes = b""
55
+ sender: bytes = b""
56
+ signature: bytes = b""
57
+
58
+ def to_atom(self) -> Tuple[bytes, List[Atom]]:
59
+ """Serialise the transaction, returning (object_id, atoms)."""
60
+ body_child_ids: List[bytes] = []
61
+ acc: List[Atom] = []
62
+
63
+ def emit(payload: bytes) -> None:
64
+ oid, atoms = _make_typed_bytes(payload)
65
+ body_child_ids.append(oid)
66
+ acc.extend(atoms)
67
+
68
+ emit(_int_to_be_bytes(self.amount))
69
+ emit(_int_to_be_bytes(self.counter))
70
+ emit(bytes(self.data))
71
+ emit(bytes(self.recipient))
72
+ emit(bytes(self.sender))
73
+
74
+ body_id, body_atoms = _make_list(body_child_ids)
75
+ acc.extend(body_atoms)
76
+
77
+ type_atom = Atom.from_data(data=b"transaction", next_hash=body_id)
78
+ signature_atom = Atom.from_data(data=bytes(self.signature))
79
+
80
+ top_list_id, top_atoms = _make_list(
81
+ [type_atom.object_id(), body_id, signature_atom.object_id()]
82
+ )
83
+
84
+ atoms: List[Atom] = acc
85
+ atoms.append(type_atom)
86
+ atoms.append(signature_atom)
87
+ atoms.extend(top_atoms)
88
+ return top_list_id, atoms
89
+
90
+ @classmethod
91
+ def from_atom(
92
+ cls,
93
+ storage_get: Callable[[bytes], Optional[Atom]],
94
+ transaction_id: bytes,
95
+ ) -> Transaction:
96
+ top_type_atom = storage_get(transaction_id)
97
+ if top_type_atom is None or top_type_atom.data != b"list":
98
+ raise ValueError("not a transaction (outer list missing)")
99
+
100
+ top_value_atom = storage_get(top_type_atom.next)
101
+ if top_value_atom is None:
102
+ raise ValueError("malformed transaction (outer value missing)")
103
+
104
+ head = top_value_atom.next
105
+ first_elem = storage_get(head)
106
+ if first_elem is None:
107
+ raise ValueError("malformed transaction (type element missing)")
108
+
109
+ type_atom_id = first_elem.data
110
+ type_atom = storage_get(type_atom_id)
111
+ if type_atom is None or type_atom.data != b"transaction":
112
+ raise ValueError("not a transaction (type mismatch)")
113
+
114
+ def read_list_entries(start: bytes) -> List[bytes]:
115
+ entries: List[bytes] = []
116
+ current = start if start != ZERO32 else b""
117
+ while current:
118
+ elem = storage_get(current)
119
+ if elem is None:
120
+ break
121
+ entries.append(elem.data)
122
+ nxt = elem.next
123
+ current = nxt if nxt != ZERO32 else b""
124
+ return entries
125
+
126
+ remainder_entries = read_list_entries(first_elem.next)
127
+ if len(remainder_entries) < 2:
128
+ raise ValueError("malformed transaction (body/signature missing)")
129
+
130
+ body_id, signature_atom_id = remainder_entries[0], remainder_entries[1]
131
+
132
+ body_type_atom = storage_get(body_id)
133
+ if body_type_atom is None or body_type_atom.data != b"list":
134
+ raise ValueError("malformed transaction body (type)")
135
+
136
+ body_value_atom = storage_get(body_type_atom.next)
137
+ if body_value_atom is None:
138
+ raise ValueError("malformed transaction body (value)")
139
+
140
+ body_entries = read_list_entries(body_value_atom.next)
141
+ if len(body_entries) < 5:
142
+ body_entries.extend([ZERO32] * (5 - len(body_entries)))
143
+
144
+ def read_typed_bytes(entry_id: bytes) -> bytes:
145
+ if not entry_id or entry_id == ZERO32:
146
+ return b""
147
+ elem = storage_get(entry_id)
148
+ if elem is None:
149
+ return b""
150
+ type_atom = storage_get(elem.data)
151
+ if type_atom is None or type_atom.data != b"byte":
152
+ return b""
153
+ value_atom = storage_get(type_atom.next)
154
+ return value_atom.data if value_atom is not None else b""
155
+
156
+ amount_bytes = read_typed_bytes(body_entries[0])
157
+ counter_bytes = read_typed_bytes(body_entries[1])
158
+ data_bytes = read_typed_bytes(body_entries[2])
159
+ recipient_bytes = read_typed_bytes(body_entries[3])
160
+ sender_bytes = read_typed_bytes(body_entries[4])
161
+
162
+ signature_atom = storage_get(signature_atom_id)
163
+ signature_bytes = signature_atom.data if signature_atom is not None else b""
164
+
165
+ return cls(
166
+ amount=_be_bytes_to_int(amount_bytes),
167
+ counter=_be_bytes_to_int(counter_bytes),
168
+ data=data_bytes,
169
+ recipient=recipient_bytes,
170
+ sender=sender_bytes,
171
+ signature=signature_bytes,
172
+ )
@@ -25,9 +25,9 @@ class Node:
25
25
  communication_setup(node=self, config=config)
26
26
  except Exception:
27
27
  pass
28
- try:
29
- from astreum._validation import validation_setup # type: ignore
30
- validation_setup(node=self)
28
+ try:
29
+ from astreum._consensus import consensus_setup # type: ignore
30
+ consensus_setup(node=self)
31
31
  except Exception:
32
32
  pass
33
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.37
3
+ Version: 0.2.39
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
@@ -14,6 +14,13 @@ src/astreum/_communication/__init__.py
14
14
  src/astreum/_communication/peer.py
15
15
  src/astreum/_communication/route.py
16
16
  src/astreum/_communication/setup.py
17
+ src/astreum/_consensus/__init__.py
18
+ src/astreum/_consensus/block.py
19
+ src/astreum/_consensus/chain.py
20
+ src/astreum/_consensus/fork.py
21
+ src/astreum/_consensus/genesis.py
22
+ src/astreum/_consensus/setup.py
23
+ src/astreum/_consensus/transaction.py
17
24
  src/astreum/_lispeum/__init__.py
18
25
  src/astreum/_lispeum/environment.py
19
26
  src/astreum/_lispeum/expression.py
@@ -24,12 +31,6 @@ src/astreum/_lispeum/parser.py
24
31
  src/astreum/_lispeum/tokenizer.py
25
32
  src/astreum/_storage/__init__.py
26
33
  src/astreum/_storage/atom.py
27
- src/astreum/_validation/__init__.py
28
- src/astreum/_validation/block.py
29
- src/astreum/_validation/chain.py
30
- src/astreum/_validation/fork.py
31
- src/astreum/_validation/genesis.py
32
- src/astreum/_validation/setup.py
33
34
  src/astreum/crypto/__init__.py
34
35
  src/astreum/crypto/ed25519.py
35
36
  src/astreum/crypto/quadratic_form.py
File without changes
File without changes
File without changes
File without changes
File without changes