astreum 0.3.9__py3-none-any.whl → 0.3.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.
Files changed (60) hide show
  1. astreum/__init__.py +5 -4
  2. astreum/communication/__init__.py +15 -11
  3. astreum/communication/difficulty.py +39 -0
  4. astreum/communication/disconnect.py +57 -0
  5. astreum/communication/handlers/handshake.py +105 -89
  6. astreum/communication/handlers/object_request.py +179 -149
  7. astreum/communication/handlers/object_response.py +7 -1
  8. astreum/communication/handlers/ping.py +9 -0
  9. astreum/communication/handlers/route_request.py +7 -1
  10. astreum/communication/handlers/route_response.py +7 -1
  11. astreum/communication/incoming_queue.py +96 -0
  12. astreum/communication/message_pow.py +36 -0
  13. astreum/communication/models/peer.py +4 -0
  14. astreum/communication/models/ping.py +27 -6
  15. astreum/communication/models/route.py +4 -0
  16. astreum/communication/{start.py → node.py} +10 -11
  17. astreum/communication/outgoing_queue.py +108 -0
  18. astreum/communication/processors/incoming.py +110 -37
  19. astreum/communication/processors/outgoing.py +35 -2
  20. astreum/communication/processors/peer.py +134 -0
  21. astreum/communication/setup.py +273 -112
  22. astreum/communication/util.py +14 -0
  23. astreum/node.py +99 -89
  24. astreum/storage/actions/get.py +79 -48
  25. astreum/storage/actions/set.py +171 -156
  26. astreum/storage/providers.py +24 -0
  27. astreum/storage/setup.py +23 -22
  28. astreum/utils/config.py +247 -30
  29. astreum/utils/logging.py +1 -1
  30. astreum/{consensus → validation}/__init__.py +0 -4
  31. astreum/validation/constants.py +2 -0
  32. astreum/{consensus → validation}/genesis.py +4 -6
  33. astreum/validation/models/block.py +544 -0
  34. astreum/validation/models/fork.py +511 -0
  35. astreum/{consensus → validation}/models/receipt.py +17 -4
  36. astreum/{consensus → validation}/models/transaction.py +45 -3
  37. astreum/validation/node.py +190 -0
  38. astreum/{consensus → validation}/validator.py +18 -9
  39. astreum/validation/workers/__init__.py +8 -0
  40. astreum/{consensus → validation}/workers/validation.py +361 -307
  41. astreum/verification/__init__.py +4 -0
  42. astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
  43. astreum/verification/node.py +61 -0
  44. astreum/verification/worker.py +183 -0
  45. {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/METADATA +43 -9
  46. astreum-0.3.46.dist-info/RECORD +79 -0
  47. astreum/consensus/models/block.py +0 -364
  48. astreum/consensus/models/chain.py +0 -66
  49. astreum/consensus/models/fork.py +0 -100
  50. astreum/consensus/setup.py +0 -83
  51. astreum/consensus/start.py +0 -67
  52. astreum/consensus/workers/__init__.py +0 -9
  53. astreum/consensus/workers/verify.py +0 -90
  54. astreum-0.3.9.dist-info/RECORD +0 -71
  55. /astreum/{consensus → validation}/models/__init__.py +0 -0
  56. /astreum/{consensus → validation}/models/account.py +0 -0
  57. /astreum/{consensus → validation}/models/accounts.py +0 -0
  58. {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/WHEEL +0 -0
  59. {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/licenses/LICENSE +0 -0
  60. {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,190 @@
1
+ import threading
2
+ from queue import Queue
3
+
4
+ from astreum.communication.node import connect_node
5
+ from cryptography.hazmat.primitives import serialization
6
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
7
+
8
+ from astreum.utils.bytes import hex_to_bytes
9
+ from astreum.communication.models.message import Message, MessageTopic
10
+ from astreum.communication.models.ping import Ping
11
+ from astreum.communication.difficulty import message_difficulty
12
+ from astreum.communication.outgoing_queue import enqueue_outgoing
13
+ from astreum.validation.genesis import create_genesis_block
14
+ from astreum.validation.workers import make_validation_worker
15
+ from astreum.verification.node import verify_blockchain
16
+
17
+
18
+ def validate_blockchain(self, validator_secret_key: Ed25519PrivateKey):
19
+ """Initialize validator keys, ensure genesis exists, then start validation thread."""
20
+ connect_node(self)
21
+
22
+ default_seed = self.config.get("default_seed")
23
+ if not default_seed:
24
+ verify_blockchain(self)
25
+ else:
26
+ self.logger.info(
27
+ "Skipping verification; default_seed configured as trusted head provider"
28
+ )
29
+
30
+ self.logger.info("Setting up node consensus")
31
+
32
+ latest_block_hex = self.config.get("latest_block_hash")
33
+ if latest_block_hex is not None:
34
+ self.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
35
+
36
+ self.nonce_time_ms = 0
37
+
38
+ self.logger.info(
39
+ "Consensus latest_block_hash preset: %s",
40
+ self.latest_block_hash,
41
+ )
42
+
43
+ self._validation_transaction_queue = Queue()
44
+ self._validation_stop_event = threading.Event()
45
+
46
+ def enqueue_transaction_hash(tx_hash: bytes) -> None:
47
+ """Schedule a transaction hash for validation processing."""
48
+ if not isinstance(tx_hash, (bytes, bytearray)):
49
+ raise TypeError("transaction hash must be bytes-like")
50
+ self._validation_transaction_queue.put(bytes(tx_hash))
51
+
52
+ self.enqueue_transaction_hash = enqueue_transaction_hash
53
+
54
+ validation_worker = make_validation_worker(self)
55
+
56
+ self.consensus_validation_thread = threading.Thread(
57
+ target=validation_worker, daemon=True, name="consensus-validation"
58
+ )
59
+ self.logger.info(
60
+ "Consensus validation worker prepared (%s)",
61
+ self.consensus_validation_thread.name,
62
+ )
63
+
64
+ self.logger.info(
65
+ "Initializing block and transaction processing for chain %s",
66
+ self.config["chain"],
67
+ )
68
+
69
+ self.validation_secret_key = validator_secret_key
70
+ validator_public_key_obj = self.validation_secret_key.public_key()
71
+ validator_public_key_bytes = validator_public_key_obj.public_bytes(
72
+ encoding=serialization.Encoding.Raw,
73
+ format=serialization.PublicFormat.Raw,
74
+ )
75
+ self.validation_public_key = validator_public_key_bytes
76
+ self.logger.debug(
77
+ "Derived validator public key %s", validator_public_key_bytes.hex()
78
+ )
79
+
80
+ if self.latest_block_hash is None:
81
+ genesis_block = create_genesis_block(
82
+ self,
83
+ validator_public_key=validator_public_key_bytes,
84
+ chain_id=self.config["chain_id"],
85
+ )
86
+ account_atoms = genesis_block.accounts.update_trie(self) if genesis_block.accounts else []
87
+
88
+ genesis_hash, genesis_atoms = genesis_block.to_atom()
89
+ self.logger.debug(
90
+ "Genesis block created with %s atoms (%s account atoms)",
91
+ len(genesis_atoms),
92
+ len(account_atoms),
93
+ )
94
+
95
+ for atom in account_atoms + genesis_atoms:
96
+ try:
97
+ self._hot_storage_set(key=atom.object_id(), value=atom)
98
+ except Exception as exc:
99
+ self.logger.warning(
100
+ "Unable to persist genesis atom %s: %s",
101
+ atom.object_id(),
102
+ exc,
103
+ )
104
+ try:
105
+ self._cold_storage_set(atom.object_id(), atom)
106
+ except Exception as exc:
107
+ self.logger.warning(
108
+ "Unable to persist genesis atom %s to cold storage: %s",
109
+ atom.object_id(),
110
+ exc,
111
+ )
112
+
113
+ self.latest_block_hash = genesis_hash
114
+ self.latest_block = genesis_block
115
+ self.logger.info("Genesis block stored with hash %s", genesis_hash.hex())
116
+ else:
117
+ self.logger.debug(
118
+ "latest_block_hash already set to %s; skipping genesis creation",
119
+ self.latest_block_hash.hex()
120
+ if isinstance(self.latest_block_hash, (bytes, bytearray))
121
+ else self.latest_block_hash,
122
+ )
123
+
124
+ validation_thread = self.consensus_validation_thread
125
+ if validation_thread.is_alive():
126
+ self.logger.debug("Consensus validation thread already running")
127
+ else:
128
+ self.logger.info(
129
+ "Starting consensus validation thread (%s)",
130
+ validation_thread.name,
131
+ )
132
+ validation_thread.start()
133
+
134
+ # ping all peers to announce validation capability
135
+ try:
136
+ ping_payload = Ping(
137
+ is_validator=bool(self.validation_public_key),
138
+ difficulty=message_difficulty(self),
139
+ latest_block=self.latest_block_hash,
140
+ ).to_bytes()
141
+ except Exception as exc:
142
+ self.logger.debug("Failed to build validation ping payload: %s", exc)
143
+ return
144
+
145
+ if self.outgoing_queue and self.peers:
146
+ with self.peers_lock:
147
+ peers = list(self.peers.items())
148
+ for peer_key, peer in peers:
149
+ peer_hex = (
150
+ peer_key.hex()
151
+ if isinstance(peer_key, (bytes, bytearray))
152
+ else peer_key
153
+ )
154
+ address = peer.address
155
+ if not address:
156
+ self.logger.debug(
157
+ "Skipping validation ping to %s; address missing",
158
+ peer_hex,
159
+ )
160
+ continue
161
+ try:
162
+ ping_msg = Message(
163
+ topic=MessageTopic.PING,
164
+ content=ping_payload,
165
+ sender=self.relay_public_key,
166
+ )
167
+ ping_msg.encrypt(peer.shared_key_bytes)
168
+ queued = enqueue_outgoing(
169
+ self,
170
+ address,
171
+ message=ping_msg,
172
+ difficulty=peer.difficulty,
173
+ )
174
+ if queued:
175
+ self.logger.debug(
176
+ "Queued validation ping to %s (%s)",
177
+ address,
178
+ peer_hex,
179
+ )
180
+ else:
181
+ self.logger.debug(
182
+ "Dropped validation ping to %s (%s)",
183
+ address,
184
+ peer_hex,
185
+ )
186
+ except Exception:
187
+ self.logger.exception(
188
+ "Failed queueing validation ping to %s",
189
+ address,
190
+ )
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import random
4
4
  from typing import Any, Dict, Optional, Tuple
5
5
 
6
- from .genesis import TREASURY_ADDRESS
6
+ from .constants import TREASURY_ADDRESS
7
7
  from .models.account import Account
8
8
  from .models.accounts import Accounts
9
9
  from .models.block import Block
@@ -11,15 +11,19 @@ from ..storage.models.atom import ZERO32
11
11
  from ..utils.integer import bytes_to_int, int_to_bytes
12
12
 
13
13
 
14
+ SLOT_DURATION_SECONDS = 2
15
+
16
+
14
17
  def current_validator(
15
18
  node: Any,
16
19
  block_hash: bytes,
17
20
  target_time: Optional[int] = None,
18
21
  ) -> Tuple[bytes, Accounts]:
19
22
  """
20
- Determine the validator for the requested target_time, halving stakes each second
21
- between the referenced block and the target time. Returns the validator key and
22
- the updated accounts snapshot reflecting stake and balance adjustments.
23
+ Determine the validator for the requested target_time, halving stakes once per
24
+ slot (currently 2 seconds) between the referenced block and the target time.
25
+ Returns the validator key and the updated accounts snapshot reflecting stake and
26
+ balance adjustments.
23
27
  """
24
28
 
25
29
  block = Block.from_atom(node, block_hash)
@@ -74,6 +78,8 @@ def current_validator(
74
78
  if current_amount <= 0:
75
79
  raise ValueError("validator stake must be positive")
76
80
  new_amount = current_amount // 2
81
+ if new_amount < 1:
82
+ new_amount = 1
77
83
  returned_amount = current_amount - new_amount
78
84
  stakes[validator_key] = new_amount
79
85
  stake_trie.put(node, validator_key, int_to_bytes(new_amount))
@@ -86,10 +92,13 @@ def current_validator(
86
92
  accounts.set_account(validator_key, validator_account)
87
93
  accounts.set_account(TREASURY_ADDRESS, treasury_account)
88
94
 
89
- iteration_target = block_timestamp + 1
90
- while True:
95
+ delta = target_timestamp - block_timestamp
96
+ slots_to_process = max(1, (delta + SLOT_DURATION_SECONDS - 1) // SLOT_DURATION_SECONDS)
97
+
98
+ selected_validator = pick_validator()
99
+ halve_stake(selected_validator)
100
+ for _ in range(1, slots_to_process):
91
101
  selected_validator = pick_validator()
92
102
  halve_stake(selected_validator)
93
- if iteration_target == target_timestamp:
94
- return selected_validator, accounts
95
- iteration_target += 1
103
+
104
+ return selected_validator, accounts
@@ -0,0 +1,8 @@
1
+ """
2
+ Worker thread factories for the consensus subsystem.
3
+ """
4
+
5
+ from .validation import make_validation_worker
6
+ from astreum.verification.worker import make_verify_worker
7
+
8
+ __all__ = ["make_verify_worker", "make_validation_worker"]