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.
- astreum/__init__.py +5 -4
- astreum/communication/__init__.py +15 -11
- astreum/communication/difficulty.py +39 -0
- astreum/communication/disconnect.py +57 -0
- astreum/communication/handlers/handshake.py +105 -89
- astreum/communication/handlers/object_request.py +179 -149
- astreum/communication/handlers/object_response.py +7 -1
- astreum/communication/handlers/ping.py +9 -0
- astreum/communication/handlers/route_request.py +7 -1
- astreum/communication/handlers/route_response.py +7 -1
- astreum/communication/incoming_queue.py +96 -0
- astreum/communication/message_pow.py +36 -0
- astreum/communication/models/peer.py +4 -0
- astreum/communication/models/ping.py +27 -6
- astreum/communication/models/route.py +4 -0
- astreum/communication/{start.py → node.py} +10 -11
- astreum/communication/outgoing_queue.py +108 -0
- astreum/communication/processors/incoming.py +110 -37
- astreum/communication/processors/outgoing.py +35 -2
- astreum/communication/processors/peer.py +134 -0
- astreum/communication/setup.py +273 -112
- astreum/communication/util.py +14 -0
- astreum/node.py +99 -89
- astreum/storage/actions/get.py +79 -48
- astreum/storage/actions/set.py +171 -156
- astreum/storage/providers.py +24 -0
- astreum/storage/setup.py +23 -22
- astreum/utils/config.py +247 -30
- astreum/utils/logging.py +1 -1
- astreum/{consensus → validation}/__init__.py +0 -4
- astreum/validation/constants.py +2 -0
- astreum/{consensus → validation}/genesis.py +4 -6
- astreum/validation/models/block.py +544 -0
- astreum/validation/models/fork.py +511 -0
- astreum/{consensus → validation}/models/receipt.py +17 -4
- astreum/{consensus → validation}/models/transaction.py +45 -3
- astreum/validation/node.py +190 -0
- astreum/{consensus → validation}/validator.py +18 -9
- astreum/validation/workers/__init__.py +8 -0
- astreum/{consensus → validation}/workers/validation.py +361 -307
- astreum/verification/__init__.py +4 -0
- astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
- astreum/verification/node.py +61 -0
- astreum/verification/worker.py +183 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/METADATA +43 -9
- astreum-0.3.46.dist-info/RECORD +79 -0
- astreum/consensus/models/block.py +0 -364
- astreum/consensus/models/chain.py +0 -66
- astreum/consensus/models/fork.py +0 -100
- astreum/consensus/setup.py +0 -83
- astreum/consensus/start.py +0 -67
- astreum/consensus/workers/__init__.py +0 -9
- astreum/consensus/workers/verify.py +0 -90
- astreum-0.3.9.dist-info/RECORD +0 -71
- /astreum/{consensus → validation}/models/__init__.py +0 -0
- /astreum/{consensus → validation}/models/account.py +0 -0
- /astreum/{consensus → validation}/models/accounts.py +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/WHEEL +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/licenses/LICENSE +0 -0
- {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 .
|
|
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
|
|
21
|
-
between the referenced block and the target time.
|
|
22
|
-
the updated accounts snapshot reflecting stake and
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
iteration_target += 1
|
|
103
|
+
|
|
104
|
+
return selected_validator, accounts
|