astreum 0.3.1__py3-none-any.whl → 0.3.9__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 (34) hide show
  1. astreum/communication/handlers/handshake.py +89 -83
  2. astreum/communication/handlers/object_request.py +176 -0
  3. astreum/communication/handlers/object_response.py +115 -0
  4. astreum/communication/handlers/ping.py +6 -20
  5. astreum/communication/handlers/route_request.py +76 -0
  6. astreum/communication/handlers/route_response.py +53 -0
  7. astreum/communication/models/message.py +81 -58
  8. astreum/communication/models/peer.py +42 -14
  9. astreum/communication/models/route.py +2 -7
  10. astreum/communication/processors/__init__.py +0 -0
  11. astreum/communication/processors/incoming.py +98 -0
  12. astreum/communication/processors/outgoing.py +20 -0
  13. astreum/communication/setup.py +36 -75
  14. astreum/communication/start.py +9 -10
  15. astreum/communication/util.py +7 -0
  16. astreum/consensus/start.py +9 -10
  17. astreum/consensus/workers/discovery.py +6 -7
  18. astreum/consensus/workers/validation.py +307 -291
  19. astreum/consensus/workers/verify.py +8 -10
  20. astreum/crypto/chacha20poly1305.py +74 -0
  21. astreum/machine/evaluations/high_evaluation.py +237 -237
  22. astreum/machine/evaluations/low_evaluation.py +18 -18
  23. astreum/node.py +25 -6
  24. astreum/storage/actions/get.py +183 -69
  25. astreum/storage/actions/set.py +66 -20
  26. astreum/storage/requests.py +28 -0
  27. astreum/storage/setup.py +3 -25
  28. astreum/utils/config.py +48 -0
  29. {astreum-0.3.1.dist-info → astreum-0.3.9.dist-info}/METADATA +3 -3
  30. {astreum-0.3.1.dist-info → astreum-0.3.9.dist-info}/RECORD +33 -24
  31. astreum/communication/handlers/storage_request.py +0 -81
  32. {astreum-0.3.1.dist-info → astreum-0.3.9.dist-info}/WHEEL +0 -0
  33. {astreum-0.3.1.dist-info → astreum-0.3.9.dist-info}/licenses/LICENSE +0 -0
  34. {astreum-0.3.1.dist-info → astreum-0.3.9.dist-info}/top_level.txt +0 -0
@@ -1,291 +1,307 @@
1
- from __future__ import annotations
2
-
3
- import time
4
- from queue import Empty
5
- from typing import Any, Callable
6
-
7
- from ..models.account import Account
8
- from ..models.accounts import Accounts
9
- from ..models.block import Block
10
- from ..models.transaction import apply_transaction
11
- from ..validator import current_validator
12
- from ...storage.models.atom import bytes_list_to_atoms
13
- from ...communication.models.message import Message, MessageTopic
14
- from ...communication.models.ping import Ping
15
-
16
-
17
- def make_validation_worker(
18
- node: Any,
19
- ) -> Callable[[], None]:
20
- """Build the validation worker bound to the given node."""
21
-
22
- def _validation_worker() -> None:
23
- node_logger = node.logger
24
- node_logger.info("Validation worker started")
25
- stop = node._validation_stop_event
26
-
27
- def _award_validator_reward(block: Block, reward_amount: int) -> None:
28
- """Credit the validator account with the provided reward."""
29
- if reward_amount <= 0:
30
- return
31
- accounts = getattr(block, "accounts", None)
32
- validator_key = getattr(block, "validator_public_key", None)
33
- if accounts is None or not validator_key:
34
- node_logger.debug(
35
- "Skipping validator reward; accounts snapshot or key missing"
36
- )
37
- return
38
- try:
39
- validator_account = accounts.get_account(
40
- address=validator_key, node=node
41
- )
42
- except Exception:
43
- node_logger.exception("Unable to load validator account for reward")
44
- return
45
- if validator_account is None:
46
- validator_account = Account.create()
47
- validator_account.balance += reward_amount
48
- accounts.set_account(validator_key, validator_account)
49
-
50
- while not stop.is_set():
51
- validation_public_key = getattr(node, "validation_public_key", None)
52
- if not validation_public_key:
53
- node_logger.debug("Validation public key unavailable; sleeping")
54
- time.sleep(0.5)
55
- continue
56
-
57
- latest_block_hash = getattr(node, "latest_block_hash", None)
58
- if not isinstance(latest_block_hash, (bytes, bytearray)):
59
- node_logger.warning("Missing latest_block_hash; retrying")
60
- time.sleep(0.5)
61
- continue
62
-
63
- node_logger.debug(
64
- "Querying current validator for block %s",
65
- latest_block_hash.hex()
66
- if isinstance(latest_block_hash, (bytes, bytearray))
67
- else latest_block_hash,
68
- )
69
- try:
70
- scheduled_validator, _ = current_validator(node, latest_block_hash)
71
- except Exception as exc:
72
- node_logger.exception("Unable to determine current validator: %s", exc)
73
- time.sleep(0.5)
74
- continue
75
-
76
- if scheduled_validator != validation_public_key:
77
- expected_hex = (
78
- scheduled_validator.hex()
79
- if isinstance(scheduled_validator, (bytes, bytearray))
80
- else scheduled_validator
81
- )
82
- node_logger.debug("Current validator mismatch; expected %s", expected_hex)
83
- time.sleep(0.5)
84
- continue
85
-
86
- try:
87
- previous_block = Block.from_atom(node, latest_block_hash)
88
- except Exception:
89
- node_logger.exception("Unable to load previous block for validation")
90
- time.sleep(0.5)
91
- continue
92
-
93
- try:
94
- current_hash = node._validation_transaction_queue.get_nowait()
95
- queue_empty = False
96
- except Empty:
97
- current_hash = None
98
- queue_empty = True
99
- node_logger.debug(
100
- "No pending validation transactions; generating empty block"
101
- )
102
-
103
- try:
104
- accounts_snapshot = Accounts(root_hash=previous_block.accounts_hash)
105
- except Exception:
106
- accounts_snapshot = None
107
- node_logger.warning("Unable to initialise accounts snapshot for block")
108
-
109
- new_block = Block(
110
- chain_id=getattr(node, "chain", 0),
111
- previous_block_hash=latest_block_hash,
112
- previous_block=previous_block,
113
- number=(previous_block.number or 0) + 1,
114
- timestamp=None,
115
- accounts_hash=previous_block.accounts_hash,
116
- transactions_total_fees=0,
117
- transactions_hash=None,
118
- receipts_hash=None,
119
- delay_difficulty=None,
120
- validator_public_key=validation_public_key,
121
- nonce=0,
122
- signature=None,
123
- accounts=accounts_snapshot,
124
- transactions=[],
125
- receipts=[],
126
- )
127
- node_logger.debug(
128
- "Creating block #%s extending %s",
129
- new_block.number,
130
- (
131
- node.latest_block_hash.hex()
132
- if isinstance(node.latest_block_hash, (bytes, bytearray))
133
- else node.latest_block_hash
134
- ),
135
- )
136
-
137
- # we may want to add a timer to process part of the txs only on a slow computer
138
- total_fees = 0
139
- while current_hash is not None:
140
- try:
141
- total_fees += apply_transaction(node, new_block, current_hash)
142
- except NotImplementedError:
143
- tx_hex = (
144
- current_hash.hex()
145
- if isinstance(current_hash, (bytes, bytearray))
146
- else current_hash
147
- )
148
- node_logger.warning("Transaction %s unsupported; re-queued", tx_hex)
149
- node._validation_transaction_queue.put(current_hash)
150
- time.sleep(0.5)
151
- break
152
- except Exception:
153
- tx_hex = (
154
- current_hash.hex()
155
- if isinstance(current_hash, (bytes, bytearray))
156
- else current_hash
157
- )
158
- node_logger.exception("Failed applying transaction %s", tx_hex)
159
-
160
- try:
161
- current_hash = node._validation_transaction_queue.get_nowait()
162
- except Empty:
163
- current_hash = None
164
-
165
- new_block.transactions_total_fees = total_fees
166
- reward_amount = total_fees if total_fees > 0 else 1
167
- if total_fees == 0 and queue_empty:
168
- node_logger.debug("Awarding base validator reward of 1 aster")
169
- elif total_fees > 0:
170
- node_logger.debug(
171
- "Collected %d aster in transaction fees for this block", total_fees
172
- )
173
- _award_validator_reward(new_block, reward_amount)
174
-
175
- # create an atom list of transactions, save the list head hash as the block's transactions_hash
176
- transactions = new_block.transactions or []
177
- tx_hashes = [bytes(tx.hash) for tx in transactions if tx.hash]
178
- head_hash, _ = bytes_list_to_atoms(tx_hashes)
179
- new_block.transactions_hash = head_hash
180
- node_logger.debug("Block includes %d transactions", len(transactions))
181
-
182
- receipts = new_block.receipts or []
183
- receipt_atoms = []
184
- receipt_hashes = []
185
- for rcpt in receipts:
186
- receipt_id, atoms = rcpt.to_atom()
187
- receipt_atoms.extend(atoms)
188
- receipt_hashes.append(bytes(receipt_id))
189
- receipts_head, _ = bytes_list_to_atoms(receipt_hashes)
190
- new_block.receipts_hash = receipts_head
191
- node_logger.debug("Block includes %d receipts", len(receipts))
192
-
193
- account_atoms = []
194
- if new_block.accounts is not None:
195
- try:
196
- account_atoms = new_block.accounts.update_trie(node)
197
- new_block.accounts_hash = new_block.accounts.root_hash
198
- node_logger.debug(
199
- "Updated trie for %d cached accounts",
200
- len(new_block.accounts._cache),
201
- )
202
- except Exception:
203
- node_logger.exception("Failed to update accounts trie for block")
204
-
205
- now = time.time()
206
- min_allowed = new_block.previous_block.timestamp + 1
207
- new_block.timestamp = max(int(now), min_allowed)
208
-
209
- new_block.delay_difficulty = Block.calculate_delay_difficulty(
210
- previous_timestamp=previous_block.timestamp,
211
- current_timestamp=new_block.timestamp,
212
- previous_difficulty=previous_block.delay_difficulty,
213
- )
214
-
215
- try:
216
- new_block.generate_nonce(difficulty=previous_block.delay_difficulty)
217
- node_logger.debug(
218
- "Found nonce %s for block #%s at difficulty %s",
219
- new_block.nonce,
220
- new_block.number,
221
- new_block.delay_difficulty,
222
- )
223
- except Exception:
224
- node_logger.exception("Failed while searching for block nonce")
225
- time.sleep(0.5)
226
- continue
227
-
228
- # atomize block
229
- new_block_hash, new_block_atoms = new_block.to_atom()
230
- # put as own latest block hash
231
- node.latest_block_hash = new_block_hash
232
- node.latest_block = new_block
233
- node_logger.info(
234
- "Validated block #%s with hash %s (%d atoms)",
235
- new_block.number,
236
- new_block_hash.hex(),
237
- len(new_block_atoms),
238
- )
239
-
240
- # ping peers in the validation route to update their records
241
- if node.validation_route and node.outgoing_queue and node.addresses:
242
- route_peers = {
243
- peer_key
244
- for bucket in getattr(node.validation_route, "buckets", {}).values()
245
- for peer_key in bucket
246
- }
247
- if route_peers:
248
- ping_payload = Ping(
249
- is_validator=True,
250
- latest_block=new_block_hash,
251
- ).to_bytes()
252
-
253
- message_bytes = Message(
254
- topic=MessageTopic.PING,
255
- content=ping_payload,
256
- ).to_bytes()
257
-
258
- for address, peer_key in node.addresses.items():
259
- if peer_key in route_peers:
260
- try:
261
- node.outgoing_queue.put((message_bytes, address))
262
- node_logger.debug(
263
- "Queued validator ping to %s (%s)",
264
- address,
265
- peer_key.hex()
266
- if isinstance(peer_key, (bytes, bytearray))
267
- else peer_key,
268
- )
269
- except Exception:
270
- node_logger.exception(
271
- "Failed queueing validator ping to %s", address
272
- )
273
-
274
- # upload block atoms
275
- for block_atom in new_block_atoms:
276
- atom_id = block_atom.object_id()
277
- node._hot_storage_set(key=atom_id, value=block_atom)
278
-
279
- # upload receipt atoms
280
- for receipt_atom in receipt_atoms:
281
- atom_id = receipt_atom.object_id()
282
- node._hot_storage_set(key=atom_id, value=receipt_atom)
283
-
284
- # upload account atoms
285
- for account_atom in account_atoms:
286
- atom_id = account_atom.object_id()
287
- node._hot_storage_set(key=atom_id, value=account_atom)
288
-
289
- node_logger.info("Validation worker stopped")
290
-
291
- return _validation_worker
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from queue import Empty
5
+ from typing import Any, Callable
6
+
7
+ from ..models.account import Account
8
+ from ..models.accounts import Accounts
9
+ from ..models.block import Block
10
+ from ..models.transaction import apply_transaction
11
+ from ..validator import current_validator
12
+ from ...storage.models.atom import bytes_list_to_atoms
13
+ from ...communication.models.message import Message, MessageTopic
14
+ from ...communication.models.ping import Ping
15
+
16
+
17
+ def make_validation_worker(
18
+ node: Any,
19
+ ) -> Callable[[], None]:
20
+ """Build the validation worker bound to the given node."""
21
+
22
+ def _validation_worker() -> None:
23
+ node.logger.info("Validation worker started")
24
+ stop = node._validation_stop_event
25
+
26
+ def _award_validator_reward(block: Block, reward_amount: int) -> None:
27
+ """Credit the validator account with the provided reward."""
28
+ if reward_amount <= 0:
29
+ return
30
+ accounts = getattr(block, "accounts", None)
31
+ validator_key = getattr(block, "validator_public_key", None)
32
+ if accounts is None or not validator_key:
33
+ node.logger.debug(
34
+ "Skipping validator reward; accounts snapshot or key missing"
35
+ )
36
+ return
37
+ try:
38
+ validator_account = accounts.get_account(
39
+ address=validator_key, node=node
40
+ )
41
+ except Exception:
42
+ node.logger.exception("Unable to load validator account for reward")
43
+ return
44
+ if validator_account is None:
45
+ validator_account = Account.create()
46
+ validator_account.balance += reward_amount
47
+ accounts.set_account(validator_key, validator_account)
48
+
49
+ while not stop.is_set():
50
+ validation_public_key = getattr(node, "validation_public_key", None)
51
+ if not validation_public_key:
52
+ node.logger.debug("Validation public key unavailable; sleeping")
53
+ time.sleep(0.5)
54
+ continue
55
+
56
+ latest_block_hash = getattr(node, "latest_block_hash", None)
57
+ if not isinstance(latest_block_hash, (bytes, bytearray)):
58
+ node.logger.warning("Missing latest_block_hash; retrying")
59
+ time.sleep(0.5)
60
+ continue
61
+
62
+ node.logger.debug(
63
+ "Querying current validator for block %s",
64
+ latest_block_hash.hex()
65
+ if isinstance(latest_block_hash, (bytes, bytearray))
66
+ else latest_block_hash,
67
+ )
68
+ try:
69
+ scheduled_validator, _ = current_validator(node, latest_block_hash)
70
+ except Exception as exc:
71
+ node.logger.exception("Unable to determine current validator: %s", exc)
72
+ time.sleep(0.5)
73
+ continue
74
+
75
+ if scheduled_validator != validation_public_key:
76
+ expected_hex = (
77
+ scheduled_validator.hex()
78
+ if isinstance(scheduled_validator, (bytes, bytearray))
79
+ else scheduled_validator
80
+ )
81
+ node.logger.debug("Current validator mismatch; expected %s", expected_hex)
82
+ time.sleep(0.5)
83
+ continue
84
+
85
+ try:
86
+ previous_block = Block.from_atom(node, latest_block_hash)
87
+ except Exception:
88
+ node.logger.exception("Unable to load previous block for validation")
89
+ time.sleep(0.5)
90
+ continue
91
+
92
+ try:
93
+ current_hash = node._validation_transaction_queue.get_nowait()
94
+ queue_empty = False
95
+ except Empty:
96
+ current_hash = None
97
+ queue_empty = True
98
+ node.logger.debug(
99
+ "No pending validation transactions; generating empty block"
100
+ )
101
+
102
+ try:
103
+ accounts_snapshot = Accounts(root_hash=previous_block.accounts_hash)
104
+ except Exception:
105
+ accounts_snapshot = None
106
+ node.logger.warning("Unable to initialise accounts snapshot for block")
107
+
108
+ new_block = Block(
109
+ chain_id=getattr(node, "chain", 0),
110
+ previous_block_hash=latest_block_hash,
111
+ previous_block=previous_block,
112
+ number=(previous_block.number or 0) + 1,
113
+ timestamp=None,
114
+ accounts_hash=previous_block.accounts_hash,
115
+ transactions_total_fees=0,
116
+ transactions_hash=None,
117
+ receipts_hash=None,
118
+ delay_difficulty=None,
119
+ validator_public_key=validation_public_key,
120
+ nonce=0,
121
+ signature=None,
122
+ accounts=accounts_snapshot,
123
+ transactions=[],
124
+ receipts=[],
125
+ )
126
+ node.logger.debug(
127
+ "Creating block #%s extending %s",
128
+ new_block.number,
129
+ (
130
+ node.latest_block_hash.hex()
131
+ if isinstance(node.latest_block_hash, (bytes, bytearray))
132
+ else node.latest_block_hash
133
+ ),
134
+ )
135
+
136
+ # we may want to add a timer to process part of the txs only on a slow computer
137
+ total_fees = 0
138
+ while current_hash is not None:
139
+ try:
140
+ total_fees += apply_transaction(node, new_block, current_hash)
141
+ except NotImplementedError:
142
+ tx_hex = (
143
+ current_hash.hex()
144
+ if isinstance(current_hash, (bytes, bytearray))
145
+ else current_hash
146
+ )
147
+ node.logger.warning("Transaction %s unsupported; re-queued", tx_hex)
148
+ node._validation_transaction_queue.put(current_hash)
149
+ time.sleep(0.5)
150
+ break
151
+ except Exception:
152
+ tx_hex = (
153
+ current_hash.hex()
154
+ if isinstance(current_hash, (bytes, bytearray))
155
+ else current_hash
156
+ )
157
+ node.logger.exception("Failed applying transaction %s", tx_hex)
158
+
159
+ try:
160
+ current_hash = node._validation_transaction_queue.get_nowait()
161
+ except Empty:
162
+ current_hash = None
163
+
164
+ new_block.transactions_total_fees = total_fees
165
+ reward_amount = total_fees if total_fees > 0 else 1
166
+ if total_fees == 0 and queue_empty:
167
+ node.logger.debug("Awarding base validator reward of 1 aster")
168
+ elif total_fees > 0:
169
+ node.logger.debug(
170
+ "Collected %d aster in transaction fees for this block", total_fees
171
+ )
172
+ _award_validator_reward(new_block, reward_amount)
173
+
174
+ # create an atom list of transactions, save the list head hash as the block's transactions_hash
175
+ transactions = new_block.transactions or []
176
+ tx_hashes = [bytes(tx.hash) for tx in transactions if tx.hash]
177
+ head_hash, _ = bytes_list_to_atoms(tx_hashes)
178
+ new_block.transactions_hash = head_hash
179
+ node.logger.debug("Block includes %d transactions", len(transactions))
180
+
181
+ receipts = new_block.receipts or []
182
+ receipt_atoms = []
183
+ receipt_hashes = []
184
+ for rcpt in receipts:
185
+ receipt_id, atoms = rcpt.to_atom()
186
+ receipt_atoms.extend(atoms)
187
+ receipt_hashes.append(bytes(receipt_id))
188
+ receipts_head, _ = bytes_list_to_atoms(receipt_hashes)
189
+ new_block.receipts_hash = receipts_head
190
+ node.logger.debug("Block includes %d receipts", len(receipts))
191
+
192
+ account_atoms = []
193
+ if new_block.accounts is not None:
194
+ try:
195
+ account_atoms = new_block.accounts.update_trie(node)
196
+ new_block.accounts_hash = new_block.accounts.root_hash
197
+ node.logger.debug(
198
+ "Updated trie for %d cached accounts",
199
+ len(new_block.accounts._cache),
200
+ )
201
+ except Exception:
202
+ node.logger.exception("Failed to update accounts trie for block")
203
+
204
+ now = time.time()
205
+ min_allowed = new_block.previous_block.timestamp + 1
206
+ new_block.timestamp = max(int(now), min_allowed)
207
+
208
+ new_block.delay_difficulty = Block.calculate_delay_difficulty(
209
+ previous_timestamp=previous_block.timestamp,
210
+ current_timestamp=new_block.timestamp,
211
+ previous_difficulty=previous_block.delay_difficulty,
212
+ )
213
+
214
+ try:
215
+ new_block.generate_nonce(difficulty=previous_block.delay_difficulty)
216
+ node.logger.debug(
217
+ "Found nonce %s for block #%s at difficulty %s",
218
+ new_block.nonce,
219
+ new_block.number,
220
+ new_block.delay_difficulty,
221
+ )
222
+ except Exception:
223
+ node.logger.exception("Failed while searching for block nonce")
224
+ time.sleep(0.5)
225
+ continue
226
+
227
+ # atomize block
228
+ new_block_hash, new_block_atoms = new_block.to_atom()
229
+ # put as own latest block hash
230
+ node.latest_block_hash = new_block_hash
231
+ node.latest_block = new_block
232
+ node.logger.info(
233
+ "Validated block #%s with hash %s (%d atoms)",
234
+ new_block.number,
235
+ new_block_hash.hex(),
236
+ len(new_block_atoms),
237
+ )
238
+
239
+ # ping peers in the validation route to update their records
240
+ if node.validation_route and node.outgoing_queue and node.peers:
241
+ route_peers = {
242
+ peer_key
243
+ for bucket in getattr(node.validation_route, "buckets", {}).values()
244
+ for peer_key in bucket
245
+ }
246
+ if route_peers:
247
+ ping_payload = Ping(
248
+ is_validator=True,
249
+ latest_block=new_block_hash,
250
+ ).to_bytes()
251
+
252
+ for peer_key in route_peers:
253
+ peer_hex = (
254
+ peer_key.hex()
255
+ if isinstance(peer_key, (bytes, bytearray))
256
+ else peer_key
257
+ )
258
+ peer = node.get_peer(peer_key)
259
+ if peer is None:
260
+ node.logger.debug(
261
+ "Skipping validator ping to peer %s; peer not found",
262
+ peer_hex,
263
+ )
264
+ continue
265
+ address = getattr(peer, "address", None)
266
+ if not address:
267
+ node.logger.debug(
268
+ "Skipping validator ping to %s; address missing",
269
+ peer_hex,
270
+ )
271
+ continue
272
+ try:
273
+ ping_msg = Message(
274
+ topic=MessageTopic.PING,
275
+ content=ping_payload,
276
+ sender=node.relay_public_key,
277
+ )
278
+ ping_msg.encrypt(peer.shared_key_bytes)
279
+ node.outgoing_queue.put((ping_msg.to_bytes(), address))
280
+ node.logger.debug(
281
+ "Queued validator ping to %s (%s)",
282
+ address,
283
+ peer_key.hex()
284
+ if isinstance(peer_key, (bytes, bytearray))
285
+ else peer_key,
286
+ )
287
+ except Exception:
288
+ node.logger.exception("Failed queueing validator ping to %s", address)
289
+
290
+ # upload block atoms
291
+ for block_atom in new_block_atoms:
292
+ atom_id = block_atom.object_id()
293
+ node._hot_storage_set(key=atom_id, value=block_atom)
294
+
295
+ # upload receipt atoms
296
+ for receipt_atom in receipt_atoms:
297
+ atom_id = receipt_atom.object_id()
298
+ node._hot_storage_set(key=atom_id, value=receipt_atom)
299
+
300
+ # upload account atoms
301
+ for account_atom in account_atoms:
302
+ atom_id = account_atom.object_id()
303
+ node._hot_storage_set(key=atom_id, value=account_atom)
304
+
305
+ node.logger.info("Validation worker stopped")
306
+
307
+ return _validation_worker