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