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,364 +0,0 @@
1
-
2
- from typing import Any, Callable, List, Optional, Tuple, TYPE_CHECKING
3
-
4
- from ...storage.models.atom import Atom, AtomKind, ZERO32, hash_bytes
5
-
6
- if TYPE_CHECKING:
7
- from ...storage.models.trie import Trie
8
- from .transaction import Transaction
9
- from .receipt import Receipt
10
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
11
- from cryptography.exceptions import InvalidSignature
12
-
13
-
14
- def _int_to_be_bytes(n: Optional[int]) -> bytes:
15
- if n is None:
16
- return b""
17
- n = int(n)
18
- if n == 0:
19
- return b"\x00"
20
- size = (n.bit_length() + 7) // 8
21
- return n.to_bytes(size, "big")
22
-
23
-
24
- def _be_bytes_to_int(b: Optional[bytes]) -> int:
25
- if not b:
26
- return 0
27
- return int.from_bytes(b, "big")
28
-
29
-
30
- class Block:
31
- """Validation Block representation using Atom storage.
32
-
33
- Top-level encoding:
34
- block_id = type_atom.object_id()
35
- chain: type_atom --next--> signature_atom --next--> body_list_atom --next--> ZERO32
36
- where: type_atom = Atom(kind=AtomKind.SYMBOL, data=b"block")
37
- signature_atom = Atom(kind=AtomKind.BYTES, data=<signature-bytes>)
38
- body_list_atom = Atom(kind=AtomKind.LIST, data=<body_head_id>)
39
-
40
- Details order in body_list:
41
- 0: chain (byte)
42
- 1: previous_block_hash (bytes)
43
- 2: number (int -> big-endian bytes)
44
- 3: timestamp (int -> big-endian bytes)
45
- 4: accounts_hash (bytes)
46
- 5: transactions_total_fees (int -> big-endian bytes)
47
- 6: transactions_hash (bytes)
48
- 7: receipts_hash (bytes)
49
- 8: delay_difficulty (int -> big-endian bytes)
50
- 9: validator_public_key (bytes)
51
- 10: nonce (int -> big-endian bytes)
52
-
53
- Notes:
54
- - "body tree" is represented here by the body_list id (self.body_hash), not
55
- embedded again as a field to avoid circular references.
56
- - "signature" is a field on the class but is not required for validation
57
- navigation; include it in the instance but it is not encoded in atoms
58
- unless explicitly provided via details extension in the future.
59
- """
60
-
61
- # essential identifiers
62
- atom_hash: Optional[bytes]
63
- chain_id: int
64
- previous_block_hash: bytes
65
- previous_block: Optional["Block"]
66
-
67
- # block details
68
- number: int
69
- timestamp: Optional[int]
70
- accounts_hash: Optional[bytes]
71
- transactions_total_fees: Optional[int]
72
- transactions_hash: Optional[bytes]
73
- receipts_hash: Optional[bytes]
74
- delay_difficulty: Optional[int]
75
- validator_public_key: Optional[bytes]
76
- nonce: Optional[int]
77
-
78
- # additional
79
- body_hash: Optional[bytes]
80
- signature: Optional[bytes]
81
-
82
- # structures
83
- accounts: Optional["Trie"]
84
- transactions: Optional[List["Transaction"]]
85
- receipts: Optional[List["Receipt"]]
86
-
87
- def __init__(
88
- self,
89
- *,
90
- chain_id: int,
91
- previous_block_hash: bytes,
92
- previous_block: Optional["Block"],
93
- number: int,
94
- timestamp: Optional[int],
95
- accounts_hash: Optional[bytes],
96
- transactions_total_fees: Optional[int],
97
- transactions_hash: Optional[bytes],
98
- receipts_hash: Optional[bytes],
99
- delay_difficulty: Optional[int],
100
- validator_public_key: Optional[bytes],
101
- nonce: Optional[int] = None,
102
- signature: Optional[bytes] = None,
103
- atom_hash: Optional[bytes] = None,
104
- body_hash: Optional[bytes] = None,
105
- accounts: Optional["Trie"] = None,
106
- transactions: Optional[List["Transaction"]] = None,
107
- receipts: Optional[List["Receipt"]] = None,
108
- ) -> None:
109
- self.atom_hash = atom_hash
110
- self.chain_id = chain_id
111
- self.previous_block_hash = previous_block_hash
112
- self.previous_block = previous_block
113
- self.number = number
114
- self.timestamp = timestamp
115
- self.accounts_hash = accounts_hash
116
- self.transactions_total_fees = transactions_total_fees
117
- self.transactions_hash = transactions_hash
118
- self.receipts_hash = receipts_hash
119
- self.delay_difficulty = delay_difficulty
120
- self.validator_public_key = validator_public_key
121
- self.nonce = nonce
122
- self.body_hash = body_hash
123
- self.signature = signature
124
- self.accounts = accounts
125
- self.transactions = transactions
126
- self.receipts = receipts
127
-
128
- def to_atom(self) -> Tuple[bytes, List[Atom]]:
129
- # Build body details as direct byte atoms, in defined order
130
- detail_payloads: List[bytes] = []
131
- block_atoms: List[Atom] = []
132
-
133
- def _emit(detail_bytes: bytes) -> None:
134
- detail_payloads.append(detail_bytes)
135
-
136
- # 0: chain
137
- _emit(_int_to_be_bytes(self.chain_id))
138
- # 1: previous_block_hash
139
- _emit(self.previous_block_hash)
140
- # 2: number
141
- _emit(_int_to_be_bytes(self.number))
142
- # 3: timestamp
143
- _emit(_int_to_be_bytes(self.timestamp))
144
- # 4: accounts_hash
145
- _emit(self.accounts_hash or b"")
146
- # 5: transactions_total_fees
147
- _emit(_int_to_be_bytes(self.transactions_total_fees))
148
- # 6: transactions_hash
149
- _emit(self.transactions_hash or b"")
150
- # 7: receipts_hash
151
- _emit(self.receipts_hash or b"")
152
- # 8: delay_difficulty
153
- _emit(_int_to_be_bytes(self.delay_difficulty))
154
- # 9: validator_public_key
155
- _emit(self.validator_public_key or b"")
156
- # 10: nonce
157
- _emit(_int_to_be_bytes(self.nonce))
158
-
159
- # Build body list chain directly from detail atoms
160
- body_head = ZERO32
161
- detail_atoms: List[Atom] = []
162
- for payload in reversed(detail_payloads):
163
- atom = Atom(data=payload, next_id=body_head, kind=AtomKind.BYTES)
164
- detail_atoms.append(atom)
165
- body_head = atom.object_id()
166
- detail_atoms.reverse()
167
-
168
- block_atoms.extend(detail_atoms)
169
-
170
- body_list_atom = Atom(data=body_head, kind=AtomKind.LIST)
171
- self.body_hash = body_list_atom.object_id()
172
-
173
- # Signature atom links to body list atom; type atom links to signature atom
174
- sig_atom = Atom(
175
- data=bytes(self.signature or b""),
176
- next_id=self.body_hash,
177
- kind=AtomKind.BYTES,
178
- )
179
- type_atom = Atom(data=b"block", next_id=sig_atom.object_id(), kind=AtomKind.SYMBOL)
180
-
181
- block_atoms.append(body_list_atom)
182
- block_atoms.append(sig_atom)
183
- block_atoms.append(type_atom)
184
-
185
- self.atom_hash = type_atom.object_id()
186
- return self.atom_hash, block_atoms
187
-
188
- @classmethod
189
- def from_atom(cls, node: Any, block_id: bytes) -> "Block":
190
-
191
- block_header = node.get_atom_list_from_storage(block_id)
192
- if block_header is None or len(block_header) != 3:
193
- raise ValueError("malformed block atom chain")
194
- type_atom, sig_atom, body_list_atom = block_header
195
-
196
- if type_atom.kind is not AtomKind.SYMBOL or type_atom.data != b"block":
197
- raise ValueError("not a block (type atom payload)")
198
- if sig_atom.kind is not AtomKind.BYTES:
199
- raise ValueError("malformed block (signature atom kind)")
200
- if body_list_atom.kind is not AtomKind.LIST:
201
- raise ValueError("malformed block (body list atom kind)")
202
- if body_list_atom.next_id != ZERO32:
203
- raise ValueError("malformed block (body list tail)")
204
-
205
- detail_atoms = node.get_atom_list_from_storage(body_list_atom.data)
206
- if detail_atoms is None:
207
- raise ValueError("missing block body list nodes")
208
-
209
- if len(detail_atoms) != 11:
210
- raise ValueError("block body must contain exactly 11 detail entries")
211
-
212
- detail_values: List[bytes] = []
213
- for detail_atom in detail_atoms:
214
- if detail_atom.kind is not AtomKind.BYTES:
215
- raise ValueError("block body detail atoms must be bytes")
216
- detail_values.append(detail_atom.data)
217
-
218
- (
219
- chain_bytes,
220
- prev_bytes,
221
- number_bytes,
222
- timestamp_bytes,
223
- accounts_bytes,
224
- fees_bytes,
225
- transactions_bytes,
226
- receipts_bytes,
227
- delay_diff_bytes,
228
- validator_bytes,
229
- nonce_bytes,
230
- ) = detail_values
231
-
232
- return cls(
233
- chain_id=_be_bytes_to_int(chain_bytes),
234
- previous_block_hash=prev_bytes or ZERO32,
235
- previous_block=None,
236
- number=_be_bytes_to_int(number_bytes),
237
- timestamp=_be_bytes_to_int(timestamp_bytes),
238
- accounts_hash=accounts_bytes or None,
239
- transactions_total_fees=_be_bytes_to_int(fees_bytes),
240
- transactions_hash=transactions_bytes or None,
241
- receipts_hash=receipts_bytes or None,
242
- delay_difficulty=_be_bytes_to_int(delay_diff_bytes),
243
- validator_public_key=validator_bytes or None,
244
- nonce=_be_bytes_to_int(nonce_bytes),
245
- signature=sig_atom.data if sig_atom is not None else None,
246
- atom_hash=block_id,
247
- body_hash=body_list_atom.object_id(),
248
- )
249
-
250
- def validate(self, storage_get: Callable[[bytes], Optional[Atom]]) -> bool:
251
- """Validate this block against storage.
252
-
253
- Checks:
254
- - Signature: signature must verify over the body list id using the
255
- validator's public key.
256
- - Timestamp monotonicity: if previous block exists (not ZERO32), this
257
- block's timestamp must be >= previous.timestamp + 1.
258
- """
259
- # Unverifiable if critical fields are missing
260
- if not self.body_hash:
261
- return False
262
- if not self.signature:
263
- return False
264
- if not self.validator_public_key:
265
- return False
266
- if self.timestamp is None:
267
- return False
268
-
269
- # 1) Signature check over body hash
270
- try:
271
- pub = Ed25519PublicKey.from_public_bytes(bytes(self.validator_public_key))
272
- pub.verify(self.signature, self.body_hash)
273
- except InvalidSignature as e:
274
- raise ValueError("invalid signature") from e
275
-
276
- # 2) Timestamp monotonicity against previous block
277
- prev_ts: Optional[int] = None
278
- prev_hash = self.previous_block_hash or ZERO32
279
-
280
- if self.previous_block is not None:
281
- prev_ts = int(self.previous_block.timestamp or 0)
282
- prev_hash = self.previous_block.atom_hash or prev_hash or ZERO32
283
-
284
- if prev_hash and prev_hash != ZERO32 and prev_ts is None:
285
- # If previous block cannot be loaded, treat as unverifiable, not malicious
286
- try:
287
- prev = Block.from_atom(storage_get, prev_hash)
288
- except Exception:
289
- return False
290
- prev_ts = int(prev.timestamp or 0)
291
-
292
- if prev_hash and prev_hash != ZERO32:
293
- if prev_ts is None:
294
- return False
295
- cur_ts = int(self.timestamp or 0)
296
- if cur_ts < prev_ts + 1:
297
- raise ValueError("timestamp must be at least prev+1")
298
-
299
- return True
300
-
301
- @staticmethod
302
- def _leading_zero_bits(buf: bytes) -> int:
303
- """Return the number of leading zero bits in the provided buffer."""
304
- zeros = 0
305
- for byte in buf:
306
- if byte == 0:
307
- zeros += 8
308
- continue
309
- zeros += 8 - int(byte).bit_length()
310
- break
311
- return zeros
312
-
313
- @staticmethod
314
- def calculate_delay_difficulty(
315
- *,
316
- previous_timestamp: Optional[int],
317
- current_timestamp: Optional[int],
318
- previous_difficulty: Optional[int],
319
- target_spacing: int = 2,
320
- ) -> int:
321
- """
322
- Adjust the delay difficulty based on how quickly the previous block was produced.
323
-
324
- The previous block difficulty is increased if the spacing is below the target,
325
- decreased if above, and returned unchanged when the target spacing is met.
326
- """
327
- base_difficulty = max(1, int(previous_difficulty or 1))
328
- if previous_timestamp is None or current_timestamp is None:
329
- return base_difficulty
330
-
331
- spacing = max(0, int(current_timestamp) - int(previous_timestamp))
332
- if spacing <= 1:
333
- adjusted = base_difficulty * 1.618
334
- elif spacing == target_spacing:
335
- adjusted = float(base_difficulty)
336
- elif spacing > target_spacing:
337
- adjusted = base_difficulty * 0.618
338
- else:
339
- adjusted = float(base_difficulty)
340
-
341
- return max(1, int(round(adjusted)))
342
-
343
- def generate_nonce(
344
- self,
345
- *,
346
- difficulty: int,
347
- ) -> int:
348
- """
349
- Find a nonce that yields a block hash with the required leading zero bits.
350
-
351
- The search starts from the current nonce and iterates until the target
352
- difficulty is met.
353
- """
354
- target = max(1, int(difficulty))
355
- start = int(self.nonce or 0)
356
- nonce = start
357
- while True:
358
- self.nonce = nonce
359
- block_hash, _ = self.to_atom()
360
- leading_zeros = self._leading_zero_bits(block_hash)
361
- if leading_zeros >= target:
362
- self.atom_hash = block_hash
363
- return nonce
364
- nonce += 1
@@ -1,66 +0,0 @@
1
- # chain.py
2
- from typing import Callable, Dict, Optional
3
- from .block import Block
4
- from ...storage.models.atom import ZERO32, Atom
5
-
6
- class Chain:
7
- def __init__(self, head_block: Block):
8
- self.head_block = head_block
9
- self.validated_upto_block = None
10
- # Root (genesis) hash for this chain; set by validation setup when known
11
- self.root: Optional[bytes] = None
12
- # Fork position: the head hash of the default/current fork for this chain
13
- self.fork_position: Optional[bytes] = getattr(head_block, "atom_hash", None)
14
- # Mark the first malicious block encountered during validation; None means not found
15
- self.malicious_block_hash: Optional[bytes] = None
16
-
17
- def validate(self, storage_get: Callable[[bytes], Atom]) -> Block:
18
- """Validate the chain from head to genesis and return the root block.
19
-
20
- Incorporates per-block validation (signature on body and timestamp
21
- monotonicity). Uses a simple cache to avoid duplicate Atom fetches and
22
- duplicate block decoding during the backward walk.
23
- """
24
- # Atom and Block caches for this validation pass
25
- atom_cache: Dict[bytes, Optional[Atom]] = {}
26
- block_cache: Dict[bytes, Block] = {}
27
-
28
- def get_cached(k: bytes) -> Optional[Atom]:
29
- if k in atom_cache:
30
- return atom_cache[k]
31
- a = storage_get(k)
32
- atom_cache[k] = a
33
- return a
34
-
35
- def load_block(bid: bytes) -> Block:
36
- if bid in block_cache:
37
- return block_cache[bid]
38
- b = Block.from_atom(get_cached, bid)
39
- block_cache[bid] = b
40
- return b
41
-
42
- blk = self.head_block
43
- # Ensure head is in cache if it has a hash
44
- if getattr(blk, "atom_hash", None):
45
- block_cache[blk.atom_hash] = blk # type: ignore[attr-defined]
46
-
47
- # Walk back, validating each block
48
- while True:
49
- # Validate current block (signature over body, timestamp rule)
50
- try:
51
- blk.validate(get_cached) # may decode previous but uses cached atoms
52
- except Exception:
53
- # record first failure point then propagate
54
- self.malicious_block_hash = getattr(blk, "atom_hash", None)
55
- raise
56
-
57
- prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32
58
- if prev_hash == ZERO32:
59
- break
60
- # Move to previous block using cache-aware loader
61
- prev_blk = load_block(prev_hash)
62
- blk.previous_block = prev_blk # cache the object for any downstream use
63
- blk = prev_blk
64
-
65
- self.validated_upto_block = blk
66
- return blk
@@ -1,100 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Optional, Set, Any, Callable, Dict
4
- from .block import Block
5
- from ...storage.models.atom import ZERO32, Atom
6
-
7
-
8
- class Fork:
9
- """A branch head within a Chain (same root).
10
-
11
- - head: current tip block id (bytes)
12
- - peers: identifiers (e.g., peer pubkey objects) following this head
13
- - root: genesis block id for this chain (optional)
14
- - validated_upto: earliest verified ancestor (optional)
15
- - chain_fork_position: the chain's fork anchor relevant to this fork
16
- """
17
-
18
- def __init__(
19
- self,
20
- head: bytes,
21
- ) -> None:
22
- self.head: bytes = head
23
- self.peers: Set[Any] = set()
24
- self.root: Optional[bytes] = None
25
- self.validated_upto: Optional[bytes] = None
26
- self.chain_fork_position: Optional[bytes] = None
27
- # Mark the first block found malicious during validation; None means not found
28
- self.malicious_block_hash: Optional[bytes] = None
29
-
30
- def add_peer(self, peer_id: Any) -> None:
31
- self.peers.add(peer_id)
32
-
33
- def remove_peer(self, peer_id: Any) -> None:
34
- self.peers.discard(peer_id)
35
-
36
- def validate(
37
- self,
38
- storage_get: Callable[[bytes], Optional[object]],
39
- stop_heads: Optional[Set[bytes]] = None,
40
- ) -> bool:
41
- """Validate only up to the chain fork position, not genesis.
42
-
43
- Returns True if self.head descends from self.chain_fork_position (or if
44
- chain_fork_position is None/equals head), and updates validated_upto to
45
- that anchor. If stop_heads is provided, returns True early if ancestry
46
- reaches any of those heads, setting validated_upto to the matched head.
47
- Returns False if ancestry cannot be confirmed.
48
- """
49
- if self.chain_fork_position is None or self.chain_fork_position == self.head:
50
- self.validated_upto = self.head
51
- return True
52
- # Caches to avoid double fetching/decoding
53
- atom_cache: Dict[bytes, Optional[Atom]] = {}
54
- block_cache: Dict[bytes, Block] = {}
55
-
56
- def get_cached(k: bytes) -> Optional[Atom]:
57
- if k in atom_cache:
58
- return atom_cache[k]
59
- a = storage_get(k) # type: ignore[call-arg]
60
- atom_cache[k] = a # may be None if missing
61
- return a
62
-
63
- def load_block(bid: bytes) -> Optional[Block]:
64
- if bid in block_cache:
65
- return block_cache[bid]
66
- try:
67
- b = Block.from_atom(get_cached, bid)
68
- except Exception:
69
- return None
70
- block_cache[bid] = b
71
- return b
72
-
73
- blk = load_block(self.head)
74
- if blk is None:
75
- # Missing head data: unverifiable, not malicious
76
- return False
77
- # Walk up to fork anchor, validating each block signature + timestamp
78
- while True:
79
- try:
80
- blk.validate(get_cached) # type: ignore[arg-type]
81
- except Exception:
82
- # mark the first failure point
83
- self.malicious_block_hash = blk.atom_hash
84
- return False
85
-
86
- # Early-exit if we met another known fork head
87
- if stop_heads and blk.atom_hash in stop_heads:
88
- self.validated_upto = blk.atom_hash
89
- return True
90
-
91
- if blk.atom_hash == self.chain_fork_position:
92
- self.validated_upto = blk.atom_hash
93
- return True
94
-
95
- prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32
96
- nxt = load_block(prev_hash)
97
- if nxt is None:
98
- return False
99
- blk.previous_block = nxt # cache for future use
100
- blk = nxt
@@ -1,83 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import threading
4
- from queue import Queue
5
- from typing import Any, Optional
6
-
7
- from .validator import current_validator # re-exported for compatibility
8
- from .workers import (
9
- make_discovery_worker,
10
- make_validation_worker,
11
- make_verify_worker,
12
- )
13
- from ..utils.bytes import hex_to_bytes
14
-
15
-
16
- def consensus_setup(node: Any, config: Optional[dict] = None) -> None:
17
- config = config or {}
18
- node.logger.info("Setting up node consensus")
19
-
20
- # Shared state
21
- node.validation_lock = getattr(node, "validation_lock", threading.RLock())
22
-
23
- # Public maps per your spec
24
- # - chains: Dict[root, Chain]
25
- # - forks: Dict[head, Fork]
26
- node.chains = getattr(node, "chains", {})
27
- node.forks = getattr(node, "forks", {})
28
- node.logger.info(
29
- "Consensus maps initialized (chains=%s, forks=%s)",
30
- len(node.chains),
31
- len(node.forks),
32
- )
33
-
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
-
38
- node.latest_block_hash = getattr(node, "latest_block_hash", None)
39
- node.latest_block = getattr(node, "latest_block", None)
40
- node.logger.info(
41
- "Consensus latest_block_hash preset: %s",
42
- node.latest_block_hash.hex() if isinstance(node.latest_block_hash, bytes) else node.latest_block_hash,
43
- )
44
-
45
- # Pending transactions queue (hash-only entries)
46
- node._validation_transaction_queue = getattr(
47
- node, "_validation_transaction_queue", Queue()
48
- )
49
- # Single work queue of grouped items: (latest_block_hash, set(peer_ids))
50
- node._validation_verify_queue = getattr(
51
- node, "_validation_verify_queue", Queue()
52
- )
53
- node._validation_stop_event = getattr(
54
- node, "_validation_stop_event", threading.Event()
55
- )
56
-
57
- def enqueue_transaction_hash(tx_hash: bytes) -> None:
58
- """Schedule a transaction hash for validation processing."""
59
- if not isinstance(tx_hash, (bytes, bytearray)):
60
- raise TypeError("transaction hash must be bytes-like")
61
- node._validation_transaction_queue.put(bytes(tx_hash))
62
-
63
- node.enqueue_transaction_hash = enqueue_transaction_hash
64
-
65
- verify_worker = make_verify_worker(node)
66
- validation_worker = make_validation_worker(node)
67
-
68
- # Start workers as daemons
69
- discovery_worker = make_discovery_worker(node)
70
- node.consensus_discovery_thread = threading.Thread(
71
- target=discovery_worker, daemon=True, name="consensus-discovery"
72
- )
73
- node.consensus_verify_thread = threading.Thread(
74
- target=verify_worker, daemon=True, name="consensus-verify"
75
- )
76
- node.consensus_validation_thread = threading.Thread(
77
- target=validation_worker, daemon=True, name="consensus-validation"
78
- )
79
- node.consensus_discovery_thread.start()
80
- node.logger.info("Started consensus discovery thread (%s)", node.consensus_discovery_thread.name)
81
- node.consensus_verify_thread.start()
82
- node.logger.info("Started consensus verify thread (%s)", node.consensus_verify_thread.name)
83
- node.logger.info("Consensus setup ready")
@@ -1,67 +0,0 @@
1
- from cryptography.hazmat.primitives import serialization
2
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
3
-
4
- from astreum.consensus.genesis import create_genesis_block
5
-
6
-
7
- def process_blocks_and_transactions(self, validator_secret_key: Ed25519PrivateKey):
8
- """Initialize validator keys, ensure genesis exists, then start validation thread."""
9
- self.logger.info(
10
- "Initializing block and transaction processing for chain %s",
11
- self.config["chain"],
12
- )
13
-
14
- self.validation_secret_key = validator_secret_key
15
- validator_public_key_obj = self.validation_secret_key.public_key()
16
- validator_public_key_bytes = validator_public_key_obj.public_bytes(
17
- encoding=serialization.Encoding.Raw,
18
- format=serialization.PublicFormat.Raw,
19
- )
20
- self.validation_public_key = validator_public_key_bytes
21
- self.logger.debug(
22
- "Derived validator public key %s", validator_public_key_bytes.hex()
23
- )
24
-
25
- if self.latest_block_hash is None:
26
- genesis_block = create_genesis_block(
27
- self,
28
- validator_public_key=validator_public_key_bytes,
29
- chain_id=self.config["chain_id"],
30
- )
31
- account_atoms = genesis_block.accounts.update_trie(self) if genesis_block.accounts else []
32
-
33
- genesis_hash, genesis_atoms = genesis_block.to_atom()
34
- self.logger.debug(
35
- "Genesis block created with %s atoms (%s account atoms)",
36
- len(genesis_atoms),
37
- len(account_atoms),
38
- )
39
-
40
- for atom in account_atoms + genesis_atoms:
41
- try:
42
- self._hot_storage_set(key=atom.object_id(), value=atom)
43
- except Exception as exc:
44
- self.logger.warning(
45
- "Unable to persist genesis atom %s: %s",
46
- atom.object_id(),
47
- exc,
48
- )
49
-
50
- self.latest_block_hash = genesis_hash
51
- self.latest_block = genesis_block
52
- self.logger.info("Genesis block stored with hash %s", genesis_hash.hex())
53
- else:
54
- self.logger.debug(
55
- "latest_block_hash already set to %s; skipping genesis creation",
56
- self.latest_block_hash.hex()
57
- if isinstance(self.latest_block_hash, (bytes, bytearray))
58
- else self.latest_block_hash,
59
- )
60
-
61
- self.logger.info(
62
- "Starting consensus validation thread (%s)",
63
- self.consensus_validation_thread.name,
64
- )
65
- self.consensus_validation_thread.start()
66
-
67
- # ping all peers to announce validation capability
@@ -1,9 +0,0 @@
1
- """
2
- Worker thread factories for the consensus subsystem.
3
- """
4
-
5
- from .discovery import make_discovery_worker
6
- from .validation import make_validation_worker
7
- from .verify import make_verify_worker
8
-
9
- __all__ = ["make_discovery_worker", "make_verify_worker", "make_validation_worker"]