astreum 0.2.44__tar.gz → 0.2.46__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.44/src/astreum.egg-info → astreum-0.2.46}/PKG-INFO +1 -1
  2. {astreum-0.2.44 → astreum-0.2.46}/pyproject.toml +1 -1
  3. astreum-0.2.46/src/astreum/_consensus/genesis.py +72 -0
  4. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/setup.py +50 -3
  5. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/parser.py +25 -25
  6. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_node.py +28 -26
  7. astreum-0.2.46/src/astreum/utils/bytes.py +24 -0
  8. {astreum-0.2.44 → astreum-0.2.46/src/astreum.egg-info}/PKG-INFO +1 -1
  9. {astreum-0.2.44 → astreum-0.2.46}/src/astreum.egg-info/SOURCES.txt +1 -0
  10. astreum-0.2.44/src/astreum/_consensus/genesis.py +0 -141
  11. {astreum-0.2.44 → astreum-0.2.46}/LICENSE +0 -0
  12. {astreum-0.2.44 → astreum-0.2.46}/README.md +0 -0
  13. {astreum-0.2.44 → astreum-0.2.46}/setup.cfg +0 -0
  14. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/__init__.py +0 -0
  15. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/__init__.py +0 -0
  16. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/message.py +0 -0
  17. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/peer.py +0 -0
  18. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/ping.py +0 -0
  19. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/route.py +0 -0
  20. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/setup.py +0 -0
  21. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_communication/util.py +0 -0
  22. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/__init__.py +0 -0
  23. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/account.py +0 -0
  24. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/accounts.py +0 -0
  25. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/block.py +0 -0
  26. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/chain.py +0 -0
  27. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/fork.py +0 -0
  28. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/receipt.py +0 -0
  29. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/transaction.py +0 -0
  30. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/workers/__init__.py +0 -0
  31. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/workers/discovery.py +0 -0
  32. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/workers/validation.py +0 -0
  33. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_consensus/workers/verify.py +0 -0
  34. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/__init__.py +0 -0
  35. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/environment.py +0 -0
  36. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/expression.py +0 -0
  37. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/high_evaluation.py +0 -0
  38. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/low_evaluation.py +0 -0
  39. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/meter.py +0 -0
  40. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_lispeum/tokenizer.py +0 -0
  41. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_storage/__init__.py +0 -0
  42. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_storage/atom.py +0 -0
  43. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/_storage/patricia.py +0 -0
  44. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/crypto/__init__.py +0 -0
  45. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/crypto/ed25519.py +0 -0
  46. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/crypto/quadratic_form.py +0 -0
  47. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/crypto/wesolowski.py +0 -0
  48. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/crypto/x25519.py +0 -0
  49. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/format.py +0 -0
  50. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/models/__init__.py +0 -0
  51. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/models/block.py +0 -0
  52. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/models/merkle.py +0 -0
  53. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/models/patricia.py +0 -0
  54. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/node.py +0 -0
  55. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/storage/__init__.py +0 -0
  56. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/storage/object.py +0 -0
  57. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/storage/setup.py +0 -0
  58. {astreum-0.2.44 → astreum-0.2.46}/src/astreum/utils/integer.py +0 -0
  59. {astreum-0.2.44 → astreum-0.2.46}/src/astreum.egg-info/dependency_links.txt +0 -0
  60. {astreum-0.2.44 → astreum-0.2.46}/src/astreum.egg-info/requires.txt +0 -0
  61. {astreum-0.2.44 → astreum-0.2.46}/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.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,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.44"
3
+ version = "0.2.46"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,72 @@
1
+
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, List
5
+
6
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
7
+
8
+ from .account import Account
9
+ from .block import Block
10
+ from .._storage.atom import ZERO32
11
+ from .._storage.patricia import PatriciaTrie
12
+ from ..utils.integer import int_to_bytes
13
+
14
+ TREASURY_ADDRESS = b"\x01" * 32
15
+ BURN_ADDRESS = b"\x00" * 32
16
+ def create_genesis_block(node: Any, validator_public_key: bytes, validator_secret_key: bytes) -> Block:
17
+ validator_pk = bytes(validator_public_key)
18
+
19
+ if len(validator_pk) != 32:
20
+ raise ValueError("validator_public_key must be 32 bytes")
21
+
22
+ # 1. Stake trie with single validator stake of 1 (encoded on 32 bytes).
23
+ stake_trie = PatriciaTrie()
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
27
+
28
+ # 2. Account trie with treasury, burn, and validator accounts.
29
+ accounts_trie = PatriciaTrie()
30
+
31
+ treasury_account = Account.create(balance=1, data=stake_root, counter=0)
32
+ accounts_trie.put(storage_node=node, key=TREASURY_ADDRESS, value=treasury_account.hash)
33
+
34
+ burn_account = Account.create(balance=0, data=b"", counter=0)
35
+ accounts_trie.put(storage_node=node, key=BURN_ADDRESS, value=burn_account.hash)
36
+
37
+ validator_account = Account.create(balance=0, data=b"", counter=0)
38
+ accounts_trie.put(storage_node=node, key=validator_pk, value=validator_account.hash)
39
+
40
+ accounts_root = accounts_trie.root_hash
41
+ if accounts_root is None:
42
+ raise ValueError("genesis accounts trie is empty")
43
+
44
+ # 3. Assemble block metadata.
45
+ block = Block()
46
+ block.previous_block_hash = ZERO32
47
+ block.number = 0
48
+ block.timestamp = 0
49
+ block.accounts_hash = accounts_root
50
+ block.accounts = accounts_trie
51
+ block.transactions_total_fees = 0
52
+ block.transactions_hash = ZERO32
53
+ block.receipts_hash = ZERO32
54
+ block.delay_difficulty = 0
55
+ block.delay_output = b""
56
+ block.validator_public_key = validator_pk
57
+ block.transactions = []
58
+ block.receipts = []
59
+
60
+ # 4. Sign the block body with the validator secret key.
61
+ block.signature = b""
62
+ block.to_atom()
63
+
64
+ if block.body_hash is None:
65
+ raise ValueError("failed to materialise genesis block body")
66
+
67
+ secret = Ed25519PrivateKey.from_private_bytes(validator_secret_key)
68
+ block.signature = secret.sign(block.body_hash)
69
+ block_hash, _ = block.to_atom()
70
+
71
+ block.hash = block_hash
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:]
@@ -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))
@@ -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
@@ -54,4 +54,5 @@ src/astreum/models/patricia.py
54
54
  src/astreum/storage/__init__.py
55
55
  src/astreum/storage/object.py
56
56
  src/astreum/storage/setup.py
57
+ src/astreum/utils/bytes.py
57
58
  src/astreum/utils/integer.py
@@ -1,141 +0,0 @@
1
-
2
- from __future__ import annotations
3
-
4
- from typing import Any, Iterable, List, Optional, Tuple
5
-
6
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
7
-
8
- from .account import Account
9
- from .block import Block
10
- from .._storage.atom import Atom, ZERO32
11
- from .._storage.patricia import PatriciaTrie, PatriciaNode
12
-
13
- TREASURY_ADDRESS = b"\x01" * 32
14
- 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
- def create_genesis_block(node: Any, validator_public_key: bytes, validator_secret_key: bytes) -> Block:
76
- validator_pk = bytes(validator_public_key)
77
-
78
- if len(validator_pk) != 32:
79
- raise ValueError("validator_public_key must be 32 bytes")
80
-
81
- # 1. Stake trie with single validator stake of 1 (encoded on 32 bytes).
82
- 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
87
-
88
- # 2. Account trie with treasury, burn, and validator accounts.
89
- accounts_trie = PatriciaTrie()
90
-
91
- 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)
95
-
96
- 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)
100
-
101
- 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)
107
-
108
- accounts_root = accounts_trie.root_hash
109
- if accounts_root is None:
110
- raise ValueError("genesis accounts trie is empty")
111
-
112
- # 3. Assemble block metadata.
113
- block = Block()
114
- block.previous_block_hash = ZERO32
115
- block.number = 0
116
- block.timestamp = 0
117
- block.accounts_hash = accounts_root
118
- block.accounts = accounts_trie
119
- block.transactions_total_fees = 0
120
- block.transactions_hash = ZERO32
121
- block.receipts_hash = ZERO32
122
- block.delay_difficulty = 0
123
- block.delay_output = b""
124
- block.validator_public_key = validator_pk
125
- block.transactions = []
126
- block.receipts = []
127
-
128
- # 4. Sign the block body with the validator secret key.
129
- block.signature = b""
130
- block.to_atom()
131
-
132
- if block.body_hash is None:
133
- raise ValueError("failed to materialise genesis block body")
134
-
135
- secret = Ed25519PrivateKey.from_private_bytes(validator_secret_key)
136
- block.signature = secret.sign(block.body_hash)
137
- block_hash, block_atoms = block.to_atom()
138
- _store_atoms(node, block_atoms)
139
-
140
- block.hash = block_hash
141
- return block
File without changes
File without changes
File without changes
File without changes
File without changes