astreum 0.2.44__py3-none-any.whl → 0.2.46__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.

Potentially problematic release.


This version of astreum might be problematic. Click here for more details.

@@ -1,77 +1,18 @@
1
-
1
+
2
2
  from __future__ import annotations
3
3
 
4
- from typing import Any, Iterable, List, Optional, Tuple
4
+ from typing import Any, List
5
5
 
6
6
  from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
7
7
 
8
8
  from .account import Account
9
9
  from .block import Block
10
- from .._storage.atom import Atom, ZERO32
11
- from .._storage.patricia import PatriciaTrie, PatriciaNode
10
+ from .._storage.atom import ZERO32
11
+ from .._storage.patricia import PatriciaTrie
12
+ from ..utils.integer import int_to_bytes
12
13
 
13
14
  TREASURY_ADDRESS = b"\x01" * 32
14
15
  BURN_ADDRESS = b"\x00" * 32
15
-
16
-
17
- def _int_to_be_bytes(value: int) -> bytes:
18
- if value < 0:
19
- raise ValueError("integer fields in genesis must be non-negative")
20
- if value == 0:
21
- return b"\x00"
22
- length = (value.bit_length() + 7) // 8
23
- return value.to_bytes(length, "big")
24
-
25
-
26
- def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
27
- next_hash = ZERO32
28
- chain: List[Atom] = []
29
- for child_id in reversed(child_ids):
30
- elem = Atom.from_data(data=child_id, next_hash=next_hash)
31
- next_hash = elem.object_id()
32
- chain.append(elem)
33
- chain.reverse()
34
-
35
- value_atom = Atom.from_data(
36
- data=len(child_ids).to_bytes(8, "little"),
37
- next_hash=next_hash,
38
- )
39
- type_atom = Atom.from_data(data=b"list", next_hash=value_atom.object_id())
40
- atoms = chain + [value_atom, type_atom]
41
- return type_atom.object_id(), atoms
42
-
43
-
44
- def _store_atoms(node: Any, atoms: Iterable[Atom]) -> None:
45
- setter = getattr(node, "_local_set", None)
46
- if not callable(setter):
47
- raise TypeError("node must expose '_local_set(object_id, atom)'")
48
- for atom in atoms:
49
- setter(atom.object_id(), atom)
50
-
51
-
52
- def _persist_trie(trie: PatriciaTrie, node: Any) -> None:
53
- for patricia_node in trie.nodes.values():
54
- _, atoms = patricia_node.to_atoms()
55
- _store_atoms(node, atoms)
56
-
57
-
58
- if not hasattr(PatriciaNode, "to_bytes"):
59
- def _patricia_node_to_bytes(self: PatriciaNode) -> bytes: # type: ignore[no-redef]
60
- fields = [
61
- bytes([self.key_len]) + self.key,
62
- self.child_0 or ZERO32,
63
- self.child_1 or ZERO32,
64
- self.value or b"",
65
- ]
66
- encoded: List[bytes] = []
67
- for field in fields:
68
- encoded.append(len(field).to_bytes(4, "big"))
69
- encoded.append(field)
70
- return b"".join(encoded)
71
-
72
- PatriciaNode.to_bytes = _patricia_node_to_bytes # type: ignore[attr-defined]
73
-
74
-
75
16
  def create_genesis_block(node: Any, validator_public_key: bytes, validator_secret_key: bytes) -> Block:
76
17
  validator_pk = bytes(validator_public_key)
77
18
 
@@ -80,30 +21,21 @@ def create_genesis_block(node: Any, validator_public_key: bytes, validator_secre
80
21
 
81
22
  # 1. Stake trie with single validator stake of 1 (encoded on 32 bytes).
82
23
  stake_trie = PatriciaTrie()
83
- stake_amount = (1).to_bytes(32, "big")
84
- stake_trie.put(node, validator_pk, stake_amount)
85
- _persist_trie(stake_trie, node)
86
- stake_root = stake_trie.root_hash or ZERO32
24
+ stake_amount = int_to_bytes(1)
25
+ stake_trie.put(storage_node=node, key=validator_pk, value=stake_amount)
26
+ stake_root = stake_trie.root_hash
87
27
 
88
28
  # 2. Account trie with treasury, burn, and validator accounts.
89
29
  accounts_trie = PatriciaTrie()
90
30
 
91
31
  treasury_account = Account.create(balance=1, data=stake_root, counter=0)
92
- treasury_account_id, treasury_atoms = treasury_account.to_atom()
93
- _store_atoms(node, treasury_atoms)
94
- accounts_trie.put(node, TREASURY_ADDRESS, treasury_account_id)
32
+ accounts_trie.put(storage_node=node, key=TREASURY_ADDRESS, value=treasury_account.hash)
95
33
 
96
34
  burn_account = Account.create(balance=0, data=b"", counter=0)
97
- burn_account_id, burn_atoms = burn_account.to_atom()
98
- _store_atoms(node, burn_atoms)
99
- accounts_trie.put(node, BURN_ADDRESS, burn_account_id)
35
+ accounts_trie.put(storage_node=node, key=BURN_ADDRESS, value=burn_account.hash)
100
36
 
101
37
  validator_account = Account.create(balance=0, data=b"", counter=0)
102
- validator_account_id, validator_atoms = validator_account.to_atom()
103
- _store_atoms(node, validator_atoms)
104
- accounts_trie.put(node, validator_pk, validator_account_id)
105
-
106
- _persist_trie(accounts_trie, node)
38
+ accounts_trie.put(storage_node=node, key=validator_pk, value=validator_account.hash)
107
39
 
108
40
  accounts_root = accounts_trie.root_hash
109
41
  if accounts_root is None:
@@ -134,8 +66,7 @@ def create_genesis_block(node: Any, validator_public_key: bytes, validator_secre
134
66
 
135
67
  secret = Ed25519PrivateKey.from_private_bytes(validator_secret_key)
136
68
  block.signature = secret.sign(block.body_hash)
137
- block_hash, block_atoms = block.to_atom()
138
- _store_atoms(node, block_atoms)
69
+ block_hash, _ = block.to_atom()
139
70
 
140
71
  block.hash = block_hash
141
72
  return block
@@ -2,13 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import threading
4
4
  from queue import Queue
5
- from typing import Any
5
+ from typing import Any, Optional
6
6
 
7
7
  from .workers import (
8
8
  make_discovery_worker,
9
9
  make_validation_worker,
10
10
  make_verify_worker,
11
11
  )
12
+ from .genesis import create_genesis_block
13
+ from ..utils.bytes import hex_to_bytes
12
14
 
13
15
 
14
16
  def current_validator(node: Any) -> bytes:
@@ -16,7 +18,9 @@ def current_validator(node: Any) -> bytes:
16
18
  raise NotImplementedError("current_validator must be implemented by the host node")
17
19
 
18
20
 
19
- def consensus_setup(node: Any) -> None:
21
+ def consensus_setup(node: Any, config: Optional[dict] = None) -> None:
22
+ config = config or {}
23
+
20
24
  # Shared state
21
25
  node.validation_lock = getattr(node, "validation_lock", threading.RLock())
22
26
 
@@ -26,6 +30,12 @@ def consensus_setup(node: Any) -> None:
26
30
  node.chains = getattr(node, "chains", {})
27
31
  node.forks = getattr(node, "forks", {})
28
32
 
33
+ node.latest_block_hash = None
34
+ latest_block_hex = config.get("latest_block_hash")
35
+ if latest_block_hex is not None:
36
+ node.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
37
+ node.latest_block = None
38
+
29
39
  # Pending transactions queue (hash-only entries)
30
40
  node._validation_transaction_queue = getattr(
31
41
  node, "_validation_transaction_queue", Queue()
@@ -64,5 +74,42 @@ def consensus_setup(node: Any) -> None:
64
74
  )
65
75
  node.consensus_discovery_thread.start()
66
76
  node.consensus_verify_thread.start()
67
- if getattr(node, "validation_secret_key", None):
77
+
78
+ validator_secret_hex = config.get("validation_secret_key")
79
+ if validator_secret_hex:
80
+ validator_secret_bytes = hex_to_bytes(validator_secret_hex, expected_length=32)
81
+ try:
82
+ from cryptography.hazmat.primitives import serialization
83
+ from cryptography.hazmat.primitives.asymmetric import ed25519
84
+
85
+ validator_private = ed25519.Ed25519PrivateKey.from_private_bytes(
86
+ validator_secret_bytes
87
+ )
88
+ except Exception as exc:
89
+ raise ValueError("invalid validation_secret_key") from exc
90
+
91
+ validator_public_bytes = validator_private.public_key().public_bytes(
92
+ encoding=serialization.Encoding.Raw,
93
+ format=serialization.PublicFormat.Raw,
94
+ )
95
+
96
+ node.validation_secret_key = validator_private
97
+ node.validation_public_key = validator_public_bytes
98
+
99
+ if node.latest_block_hash is None:
100
+ genesis_block = create_genesis_block(
101
+ node,
102
+ validator_public_key=validator_public_bytes,
103
+ validator_secret_key=validator_secret_bytes,
104
+ )
105
+ genesis_hash, genesis_atoms = genesis_block.to_atom()
106
+ if hasattr(node, "_local_set"):
107
+ for atom in genesis_atoms:
108
+ try:
109
+ node._local_set(atom.object_id(), atom)
110
+ except Exception:
111
+ pass
112
+ node.latest_block_hash = genesis_hash
113
+ node.latest_block = genesis_block
114
+
68
115
  node.consensus_validation_thread.start()
@@ -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 . import Expr
3
3
 
4
4
  class ParseError(Exception):
5
5
  pass
@@ -27,30 +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 → 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
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
52
52
 
53
53
  def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
54
54
  """Parse tokens into an Expr and return (expr, remaining_tokens)."""
55
55
  expr, next_pos = _parse_one(tokens, 0)
56
- return expr, tokens[next_pos:]
56
+ return expr, tokens[next_pos:]
astreum/_node.py CHANGED
@@ -3,8 +3,8 @@ from typing import Dict, Optional
3
3
  import uuid
4
4
  import threading
5
5
 
6
- from src.astreum._storage.atom import Atom
7
- from src.astreum._lispeum import Env, Expr, low_eval, parse, tokenize, ParseError
6
+ from ._storage.atom import Atom
7
+ from ._lispeum import Env, Expr, low_eval, parse, tokenize, ParseError
8
8
 
9
9
  def bytes_touched(*vals: bytes) -> int:
10
10
  """For metering: how many bytes were manipulated (max of operands)."""
@@ -13,9 +13,9 @@ def bytes_touched(*vals: bytes) -> int:
13
13
  class Node:
14
14
  def __init__(self, config: dict):
15
15
  # Storage Setup
16
- self.in_memory_storage: Dict[bytes, Atom] = {}
17
- self.in_memory_storage_lock = threading.RLock()
18
- self.storage_index: Dict[bytes, str] = {}
16
+ self.in_memory_storage: Dict[bytes, Atom] = {}
17
+ self.in_memory_storage_lock = threading.RLock()
18
+ self.storage_index: Dict[bytes, str] = {}
19
19
  # Lispeum Setup
20
20
  self.environments: Dict[uuid.UUID, Env] = {}
21
21
  self.machine_environments_lock = threading.RLock()
@@ -28,9 +28,11 @@ class Node:
28
28
  pass
29
29
  try:
30
30
  from astreum._consensus import consensus_setup # type: ignore
31
- consensus_setup(node=self)
31
+ consensus_setup(node=self, config=config)
32
32
  except Exception:
33
33
  pass
34
+
35
+
34
36
 
35
37
  # ---- Env helpers ----
36
38
  def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
@@ -73,30 +75,30 @@ class Node:
73
75
  def _network_set(self, atom: Atom) -> None:
74
76
  """Advertise an atom to the closest known peer so they can fetch it from us."""
75
77
  try:
76
- from src.astreum._communication.message import Message, MessageTopic
78
+ from ._communication.message import Message, MessageTopic
77
79
  except Exception:
78
80
  return
79
81
 
80
82
  atom_id = atom.object_id()
81
- try:
82
- closest_peer = self.peer_route.closest_peer_for_hash(atom_id)
83
- except Exception:
84
- return
85
- if closest_peer is None or closest_peer.address is None:
86
- return
87
- target_addr = closest_peer.address
83
+ try:
84
+ closest_peer = self.peer_route.closest_peer_for_hash(atom_id)
85
+ except Exception:
86
+ return
87
+ if closest_peer is None or closest_peer.address is None:
88
+ return
89
+ target_addr = closest_peer.address
90
+
91
+ try:
92
+ provider_ip, provider_port = self.incoming_socket.getsockname()[:2]
93
+ except Exception:
94
+ return
95
+
96
+ provider_str = f"{provider_ip}:{int(provider_port)}"
97
+ try:
98
+ provider_bytes = provider_str.encode("utf-8")
99
+ except Exception:
100
+ return
88
101
 
89
- try:
90
- provider_ip, provider_port = self.incoming_socket.getsockname()[:2]
91
- except Exception:
92
- return
93
-
94
- provider_str = f"{provider_ip}:{int(provider_port)}"
95
- try:
96
- provider_bytes = provider_str.encode("utf-8")
97
- except Exception:
98
- return
99
-
100
- payload = atom_id + provider_bytes
102
+ payload = atom_id + provider_bytes
101
103
  message = Message(topic=MessageTopic.STORAGE_REQUEST, content=payload)
102
104
  self.outgoing_queue.put((message.to_bytes(), target_addr))
astreum/utils/bytes.py ADDED
@@ -0,0 +1,24 @@
1
+ from typing import Optional
2
+
3
+
4
+ def hex_to_bytes(value: str, *, expected_length: Optional[int] = None) -> bytes:
5
+ """Convert a 0x-prefixed hex string into raw bytes."""
6
+ if not isinstance(value, str):
7
+ raise TypeError("hex value must be provided as a string")
8
+
9
+ if not value.startswith(("0x", "0X")):
10
+ raise ValueError("hex value must start with '0x'")
11
+
12
+ hex_digits = value[2:]
13
+ if len(hex_digits) % 2:
14
+ raise ValueError("hex value must have an even number of digits")
15
+
16
+ try:
17
+ result = bytes.fromhex(hex_digits)
18
+ except ValueError as exc:
19
+ raise ValueError("hex value contains non-hexadecimal characters") from exc
20
+
21
+ if expected_length is not None and len(result) != expected_length:
22
+ raise ValueError(f"hex value must decode to exactly {expected_length} bytes")
23
+
24
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.44
3
+ Version: 0.2.46
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,5 +1,5 @@
1
1
  astreum/__init__.py,sha256=9tzA27B_eG5wRF1SAWJIV7xTmCcR1QFc123b_cvFOa4,345
2
- astreum/_node.py,sha256=AUpLjqbHno-Oh2X47nEfoU4I9i_Aj5vjXM1mHQH0gfM,3780
2
+ astreum/_node.py,sha256=dTwXjnCJZHjUw8dPmH7neaL1-M4rso77Aogo0935vsY,3796
3
3
  astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
4
4
  astreum/node.py,sha256=MmlK3jaANTMB3ZAxR8IaSc82OS9meJmVawYIVURADbg,39689
5
5
  astreum/_communication/__init__.py,sha256=XJui0yOcfAur4HKt-8sSRlwB-MSU1rchkuOAY-nKDOE,207
@@ -15,9 +15,9 @@ astreum/_consensus/accounts.py,sha256=zGq2BCMJPtD_lzcj4jqMzB2Foc3ptxXSPhc9zypB1T
15
15
  astreum/_consensus/block.py,sha256=HybdVcV1D9f1wUE9XeFRnCGF9HH8YI1bgo-y1h-RS5o,12342
16
16
  astreum/_consensus/chain.py,sha256=WwUeLrdg7uj--ZsUxse6xFlzO2QeQQggyKSL49KhfU0,2768
17
17
  astreum/_consensus/fork.py,sha256=dK5DtT9hWCj_rQs6MS1c1bcGBbkgVTBPIOudYbqS9vw,3825
18
- astreum/_consensus/genesis.py,sha256=oED7AHw0fPJgMu0OmhbbikJcbItw_bxVBVU8RlB2rJM,4766
18
+ astreum/_consensus/genesis.py,sha256=YOzEEcpH5lQUdRNz1P8cfeddh-IJzjTbOiVKrEPHXPg,2506
19
19
  astreum/_consensus/receipt.py,sha256=GPLspqVJHnyBr1cKmBoClsJyeEUecYmg3_acz4L4rRo,6031
20
- astreum/_consensus/setup.py,sha256=agwqfOemdLqE0Z1zrPV2XHVmZ9sAxp8Bh4k6e2u-t8w,2385
20
+ astreum/_consensus/setup.py,sha256=KHW2KzHRgCMLWaJNV2utosKdk7nxjI5aF9ie7jctgcU,4257
21
21
  astreum/_consensus/transaction.py,sha256=q9r0nQzB_67dV-9cIThCzpiJkTojP--8QENPOKxN5yw,7481
22
22
  astreum/_consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
23
23
  astreum/_consensus/workers/discovery.py,sha256=X1yjKGjLSApMJ9mgWbnc7N21ALD09khDf-in-M45Mis,1683
@@ -29,7 +29,7 @@ astreum/_lispeum/expression.py,sha256=io8tbCer_1TJee77yRbcNI5q-DPFGa8xZiC80tGvRR
29
29
  astreum/_lispeum/high_evaluation.py,sha256=7MwIeVZMPumYvKXn6Lkn-GrZhRNB6MjnUUWdiYT7Ei0,7952
30
30
  astreum/_lispeum/low_evaluation.py,sha256=HgyCSAmL5K5SKq3Xw_BtbTZZUJbMg4-bVZW-A12glQ0,4373
31
31
  astreum/_lispeum/meter.py,sha256=5q2PFW7_jmgKVM1-vwE4RRjMfPEthUA4iu1CwR-Axws,505
32
- astreum/_lispeum/parser.py,sha256=WOW3sSZWkIzPEy3fYTyl0lrtkMxHL9zRrNSYztPDemQ,2271
32
+ astreum/_lispeum/parser.py,sha256=rpxznlILbGruCS8wS_SQajYs7MlNPoMPbQ48XEjLeRo,2277
33
33
  astreum/_lispeum/tokenizer.py,sha256=P68uIj4aPKzjuCJ85jfzRi67QztpuXIOC1vvLQueBI4,552
34
34
  astreum/_storage/__init__.py,sha256=EmKZNAZmo3UVE3ekOOuckwFnBVjpa0Sy8Oxg72Lgdxc,53
35
35
  astreum/_storage/atom.py,sha256=YFjvfMhniInch13iaKGpw4CCxxgyWonniryb-Rfse4A,4177
@@ -46,9 +46,10 @@ astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13
46
46
  astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
48
48
  astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
49
+ astreum/utils/bytes.py,sha256=9QTWC2JCdwWLB5R2mPtmjPro0IUzE58DL3uEul4AheE,846
49
50
  astreum/utils/integer.py,sha256=iQt-klWOYVghu_NOT341MmHbOle4FDT3by4PNKNXscg,736
50
- astreum-0.2.44.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
51
- astreum-0.2.44.dist-info/METADATA,sha256=BfQ0S0XtFjxl4evYAAWk9buUhQAVAq7L37patJVYDDU,6181
52
- astreum-0.2.44.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- astreum-0.2.44.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
54
- astreum-0.2.44.dist-info/RECORD,,
51
+ astreum-0.2.46.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
52
+ astreum-0.2.46.dist-info/METADATA,sha256=o454zFl45Clnos8EwXm0PCSivnHw8fkoOC3C2mCFXYo,6181
53
+ astreum-0.2.46.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ astreum-0.2.46.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
55
+ astreum-0.2.46.dist-info/RECORD,,