astreum 0.2.61__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 (86) hide show
  1. astreum/__init__.py +16 -7
  2. astreum/{_communication → communication}/__init__.py +3 -3
  3. astreum/communication/handlers/handshake.py +89 -0
  4. astreum/communication/handlers/object_request.py +176 -0
  5. astreum/communication/handlers/object_response.py +115 -0
  6. astreum/communication/handlers/ping.py +34 -0
  7. astreum/communication/handlers/route_request.py +76 -0
  8. astreum/communication/handlers/route_response.py +53 -0
  9. astreum/communication/models/__init__.py +0 -0
  10. astreum/communication/models/message.py +124 -0
  11. astreum/communication/models/peer.py +51 -0
  12. astreum/{_communication → communication/models}/route.py +7 -12
  13. astreum/communication/processors/__init__.py +0 -0
  14. astreum/communication/processors/incoming.py +98 -0
  15. astreum/communication/processors/outgoing.py +20 -0
  16. astreum/communication/setup.py +166 -0
  17. astreum/communication/start.py +37 -0
  18. astreum/{_communication → communication}/util.py +7 -0
  19. astreum/consensus/__init__.py +20 -0
  20. astreum/consensus/genesis.py +66 -0
  21. astreum/consensus/models/__init__.py +0 -0
  22. astreum/consensus/models/account.py +84 -0
  23. astreum/consensus/models/accounts.py +72 -0
  24. astreum/consensus/models/block.py +364 -0
  25. astreum/{_consensus → consensus/models}/chain.py +7 -7
  26. astreum/{_consensus → consensus/models}/fork.py +8 -8
  27. astreum/consensus/models/receipt.py +98 -0
  28. astreum/{_consensus → consensus/models}/transaction.py +76 -78
  29. astreum/{_consensus → consensus}/setup.py +18 -50
  30. astreum/consensus/start.py +67 -0
  31. astreum/consensus/validator.py +95 -0
  32. astreum/{_consensus → consensus}/workers/discovery.py +19 -1
  33. astreum/consensus/workers/validation.py +307 -0
  34. astreum/{_consensus → consensus}/workers/verify.py +29 -2
  35. astreum/crypto/chacha20poly1305.py +74 -0
  36. astreum/machine/__init__.py +20 -0
  37. astreum/machine/evaluations/__init__.py +0 -0
  38. astreum/{_lispeum → machine/evaluations}/high_evaluation.py +237 -236
  39. astreum/machine/evaluations/low_evaluation.py +281 -0
  40. astreum/machine/evaluations/script_evaluation.py +27 -0
  41. astreum/machine/models/__init__.py +0 -0
  42. astreum/machine/models/environment.py +31 -0
  43. astreum/{_lispeum → machine/models}/expression.py +36 -8
  44. astreum/machine/tokenizer.py +90 -0
  45. astreum/node.py +78 -767
  46. astreum/storage/__init__.py +7 -0
  47. astreum/storage/actions/get.py +183 -0
  48. astreum/storage/actions/set.py +178 -0
  49. astreum/{_storage → storage/models}/atom.py +55 -57
  50. astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
  51. astreum/storage/requests.py +28 -0
  52. astreum/storage/setup.py +22 -15
  53. astreum/utils/config.py +48 -0
  54. {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/METADATA +27 -26
  55. astreum-0.3.9.dist-info/RECORD +71 -0
  56. astreum/_communication/message.py +0 -101
  57. astreum/_communication/peer.py +0 -23
  58. astreum/_communication/setup.py +0 -322
  59. astreum/_consensus/__init__.py +0 -20
  60. astreum/_consensus/account.py +0 -95
  61. astreum/_consensus/accounts.py +0 -38
  62. astreum/_consensus/block.py +0 -311
  63. astreum/_consensus/genesis.py +0 -72
  64. astreum/_consensus/receipt.py +0 -136
  65. astreum/_consensus/workers/validation.py +0 -125
  66. astreum/_lispeum/__init__.py +0 -16
  67. astreum/_lispeum/environment.py +0 -13
  68. astreum/_lispeum/low_evaluation.py +0 -123
  69. astreum/_lispeum/tokenizer.py +0 -22
  70. astreum/_node.py +0 -198
  71. astreum/_storage/__init__.py +0 -7
  72. astreum/_storage/setup.py +0 -35
  73. astreum/format.py +0 -75
  74. astreum/models/block.py +0 -441
  75. astreum/models/merkle.py +0 -205
  76. astreum/models/patricia.py +0 -393
  77. astreum/storage/object.py +0 -68
  78. astreum-0.2.61.dist-info/RECORD +0 -57
  79. /astreum/{models → communication/handlers}/__init__.py +0 -0
  80. /astreum/{_communication → communication/models}/ping.py +0 -0
  81. /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
  82. /astreum/{_lispeum → machine/models}/meter.py +0 -0
  83. /astreum/{_lispeum → machine}/parser.py +0 -0
  84. {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/WHEEL +0 -0
  85. {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/licenses/LICENSE +0 -0
  86. {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, List, Tuple
5
+
6
+ from ...storage.models.atom import Atom, ZERO32, AtomKind
7
+ from ...storage.models.trie import Trie
8
+ from ...utils.integer import bytes_to_int, int_to_bytes
9
+
10
+
11
+ @dataclass
12
+ class Account:
13
+ balance: int
14
+ code_hash: bytes
15
+ counter: int
16
+ data_hash: bytes
17
+ data: Trie
18
+ atom_hash: bytes = ZERO32
19
+ atoms: List[Atom] = field(default_factory=list)
20
+
21
+ @classmethod
22
+ def create(cls, balance: int = 0, data_hash: bytes = ZERO32, code_hash: bytes = ZERO32, counter: int = 0) -> "Account":
23
+ account = cls(
24
+ balance=int(balance),
25
+ code_hash=bytes(code_hash),
26
+ counter=int(counter),
27
+ data_hash=bytes(data_hash),
28
+ data=Trie(root_hash=bytes(data_hash)),
29
+ )
30
+ atom_hash, atoms = account.to_atom()
31
+ account.atom_hash = atom_hash
32
+ account.atoms = atoms
33
+ return account
34
+
35
+ @classmethod
36
+ def from_atom(cls, node: Any, root_id: bytes) -> "Account":
37
+
38
+ account_atoms = node.get_atom_list_from_storage(root_hash=root_id)
39
+
40
+ if account_atoms is None or len(account_atoms) != 5:
41
+ raise ValueError("malformed account atom list")
42
+
43
+ type_atom, balance_atom, code_atom, counter_atom, data_atom = account_atoms
44
+
45
+ if type_atom.data != b"account":
46
+ raise ValueError("not an account (type mismatch)")
47
+
48
+ account = cls.create(
49
+ balance=bytes_to_int(balance_atom.data),
50
+ data_hash=data_atom.data,
51
+ counter=bytes_to_int(counter_atom.data),
52
+ code_hash=code_atom.data,
53
+ )
54
+
55
+ return account
56
+
57
+ def to_atom(self) -> Tuple[bytes, List[Atom]]:
58
+ data_atom = Atom(
59
+ data=bytes(self.data_hash),
60
+ kind=AtomKind.LIST,
61
+ )
62
+ counter_atom = Atom(
63
+ data=int_to_bytes(self.counter),
64
+ next_id=data_atom.object_id(),
65
+ kind=AtomKind.BYTES,
66
+ )
67
+ code_atom = Atom(
68
+ data=bytes(self.code_hash),
69
+ next_id=counter_atom.object_id(),
70
+ kind=AtomKind.LIST,
71
+ )
72
+ balance_atom = Atom(
73
+ data=int_to_bytes(self.balance),
74
+ next_id=code_atom.object_id(),
75
+ kind=AtomKind.BYTES,
76
+ )
77
+ type_atom = Atom(
78
+ data=b"account",
79
+ next_id=balance_atom.object_id(),
80
+ kind=AtomKind.SYMBOL,
81
+ )
82
+
83
+ atoms = [data_atom, counter_atom, code_atom, balance_atom, type_atom]
84
+ return type_atom.object_id(), list(atoms)
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from ...storage.models.atom import Atom, ZERO32
6
+ from ...storage.models.trie import Trie
7
+ from .account import Account
8
+
9
+
10
+ class Accounts:
11
+ def __init__(
12
+ self,
13
+ root_hash: Optional[bytes] = None,
14
+ ) -> None:
15
+ self._trie = Trie(root_hash=root_hash)
16
+ self._cache: Dict[bytes, Account] = {}
17
+
18
+ @property
19
+ def root_hash(self) -> Optional[bytes]:
20
+ return self._trie.root_hash
21
+
22
+ def get_account(self, address: bytes, node: Optional[Any] = None) -> Optional[Account]:
23
+ cached = self._cache.get(address)
24
+ if cached is not None:
25
+ return cached
26
+
27
+ if node is None:
28
+ raise ValueError("Accounts requires a node reference for trie access")
29
+
30
+ account_id: Optional[bytes] = self._trie.get(node, address)
31
+ if account_id is None:
32
+ return None
33
+
34
+ account = Account.from_atom(node, account_id)
35
+ self._cache[address] = account
36
+ return account
37
+
38
+ def set_account(self, address: bytes, account: Account) -> None:
39
+ self._cache[address] = account
40
+
41
+ def update_trie(self, node: Any) -> List[Atom]:
42
+ """
43
+ Serialise cached accounts, ensure their associated data tries are materialised,
44
+ and return all atoms that must be stored (data tries, account records, and the
45
+ accounts trie nodes themselves).
46
+ """
47
+
48
+ def _node_atoms(trie: Trie) -> List[Atom]:
49
+ emitted: List[Atom] = []
50
+ if not trie.nodes:
51
+ return emitted
52
+ for node_hash in sorted(trie.nodes.keys()):
53
+ trie_node = trie.nodes[node_hash]
54
+ head_hash, atoms = trie_node.to_atoms()
55
+ if head_hash != node_hash:
56
+ continue
57
+ emitted.extend(atoms)
58
+ return emitted
59
+
60
+ data_atoms: List[Atom] = []
61
+ account_atoms: List[Atom] = []
62
+
63
+ for address, account in self._cache.items():
64
+ account.data_hash = account.data.root_hash or ZERO32
65
+ data_atoms.extend(_node_atoms(account.data))
66
+
67
+ account_id, atoms = account.to_atom()
68
+ self._trie.put(node, address, account_id)
69
+ account_atoms.extend(atoms)
70
+
71
+ trie_atoms = _node_atoms(self._trie)
72
+ return data_atoms + account_atoms + trie_atoms
@@ -0,0 +1,364 @@
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,7 +1,7 @@
1
- # chain.py
1
+ # chain.py
2
2
  from typing import Callable, Dict, Optional
3
3
  from .block import Block
4
- from .._storage.atom import ZERO32, Atom
4
+ from ...storage.models.atom import ZERO32, Atom
5
5
 
6
6
  class Chain:
7
7
  def __init__(self, head_block: Block):
@@ -10,7 +10,7 @@ class Chain:
10
10
  # Root (genesis) hash for this chain; set by validation setup when known
11
11
  self.root: Optional[bytes] = None
12
12
  # Fork position: the head hash of the default/current fork for this chain
13
- self.fork_position: Optional[bytes] = getattr(head_block, "hash", None)
13
+ self.fork_position: Optional[bytes] = getattr(head_block, "atom_hash", None)
14
14
  # Mark the first malicious block encountered during validation; None means not found
15
15
  self.malicious_block_hash: Optional[bytes] = None
16
16
 
@@ -35,14 +35,14 @@ class Chain:
35
35
  def load_block(bid: bytes) -> Block:
36
36
  if bid in block_cache:
37
37
  return block_cache[bid]
38
- b = Block.from_atom(get_cached, bid)
38
+ b = Block.from_atom(get_cached, bid)
39
39
  block_cache[bid] = b
40
40
  return b
41
41
 
42
42
  blk = self.head_block
43
43
  # Ensure head is in cache if it has a hash
44
- if getattr(blk, "hash", None):
45
- block_cache[blk.hash] = blk # type: ignore[attr-defined]
44
+ if getattr(blk, "atom_hash", None):
45
+ block_cache[blk.atom_hash] = blk # type: ignore[attr-defined]
46
46
 
47
47
  # Walk back, validating each block
48
48
  while True:
@@ -51,7 +51,7 @@ class Chain:
51
51
  blk.validate(get_cached) # may decode previous but uses cached atoms
52
52
  except Exception:
53
53
  # record first failure point then propagate
54
- self.malicious_block_hash = getattr(blk, "hash", None)
54
+ self.malicious_block_hash = getattr(blk, "atom_hash", None)
55
55
  raise
56
56
 
57
57
  prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional, Set, Any, Callable, Dict
3
+ from typing import Optional, Set, Any, Callable, Dict
4
4
  from .block import Block
5
- from .._storage.atom import ZERO32, Atom
5
+ from ...storage.models.atom import ZERO32, Atom
6
6
 
7
7
 
8
8
  class Fork:
@@ -64,7 +64,7 @@ class Fork:
64
64
  if bid in block_cache:
65
65
  return block_cache[bid]
66
66
  try:
67
- b = Block.from_atom(get_cached, bid)
67
+ b = Block.from_atom(get_cached, bid)
68
68
  except Exception:
69
69
  return None
70
70
  block_cache[bid] = b
@@ -80,16 +80,16 @@ class Fork:
80
80
  blk.validate(get_cached) # type: ignore[arg-type]
81
81
  except Exception:
82
82
  # mark the first failure point
83
- self.malicious_block_hash = blk.hash
83
+ self.malicious_block_hash = blk.atom_hash
84
84
  return False
85
85
 
86
86
  # Early-exit if we met another known fork head
87
- if stop_heads and blk.hash in stop_heads:
88
- self.validated_upto = blk.hash
87
+ if stop_heads and blk.atom_hash in stop_heads:
88
+ self.validated_upto = blk.atom_hash
89
89
  return True
90
90
 
91
- if blk.hash == self.chain_fork_position:
92
- self.validated_upto = blk.hash
91
+ if blk.atom_hash == self.chain_fork_position:
92
+ self.validated_upto = blk.atom_hash
93
93
  return True
94
94
 
95
95
  prev_hash = blk.previous_block_hash if hasattr(blk, "previous_block_hash") else ZERO32