astreum 0.2.44__py3-none-any.whl → 0.2.45__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()
astreum/_node.py CHANGED
@@ -13,24 +13,26 @@ 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()
22
22
  self.low_eval = low_eval
23
23
  # Communication and Validation Setup (import lazily to avoid heavy deps during parsing tests)
24
- try:
25
- from astreum._communication import communication_setup # type: ignore
26
- communication_setup(node=self, config=config)
27
- except Exception:
28
- pass
29
- try:
30
- from astreum._consensus import consensus_setup # type: ignore
31
- consensus_setup(node=self)
32
- except Exception:
33
- pass
24
+ try:
25
+ from astreum._communication import communication_setup # type: ignore
26
+ communication_setup(node=self, config=config)
27
+ except Exception:
28
+ pass
29
+ try:
30
+ from astreum._consensus import consensus_setup # type: ignore
31
+ consensus_setup(node=self, config=config)
32
+ except Exception:
33
+ pass
34
+
35
+
34
36
 
35
37
  # ---- Env helpers ----
36
38
  def env_get(self, env_id: uuid.UUID, key: bytes) -> Optional[Expr]:
@@ -78,25 +80,25 @@ class Node:
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
88
90
 
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
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
101
+
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.45
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=KC_TSaOq7zmOE8baS4gNuoQAI8NxEMsz5rlH29nLN20,3819
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
@@ -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.45.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
52
+ astreum-0.2.45.dist-info/METADATA,sha256=poHQQVqPXQLxXZ-j_4EAtw5R6QA2s_Ps9URrpFvmWDg,6181
53
+ astreum-0.2.45.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ astreum-0.2.45.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
55
+ astreum-0.2.45.dist-info/RECORD,,