astreum 0.3.9__py3-none-any.whl → 0.3.46__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astreum/__init__.py +5 -4
- astreum/communication/__init__.py +15 -11
- astreum/communication/difficulty.py +39 -0
- astreum/communication/disconnect.py +57 -0
- astreum/communication/handlers/handshake.py +105 -89
- astreum/communication/handlers/object_request.py +179 -149
- astreum/communication/handlers/object_response.py +7 -1
- astreum/communication/handlers/ping.py +9 -0
- astreum/communication/handlers/route_request.py +7 -1
- astreum/communication/handlers/route_response.py +7 -1
- astreum/communication/incoming_queue.py +96 -0
- astreum/communication/message_pow.py +36 -0
- astreum/communication/models/peer.py +4 -0
- astreum/communication/models/ping.py +27 -6
- astreum/communication/models/route.py +4 -0
- astreum/communication/{start.py → node.py} +10 -11
- astreum/communication/outgoing_queue.py +108 -0
- astreum/communication/processors/incoming.py +110 -37
- astreum/communication/processors/outgoing.py +35 -2
- astreum/communication/processors/peer.py +134 -0
- astreum/communication/setup.py +273 -112
- astreum/communication/util.py +14 -0
- astreum/node.py +99 -89
- astreum/storage/actions/get.py +79 -48
- astreum/storage/actions/set.py +171 -156
- astreum/storage/providers.py +24 -0
- astreum/storage/setup.py +23 -22
- astreum/utils/config.py +247 -30
- astreum/utils/logging.py +1 -1
- astreum/{consensus → validation}/__init__.py +0 -4
- astreum/validation/constants.py +2 -0
- astreum/{consensus → validation}/genesis.py +4 -6
- astreum/validation/models/block.py +544 -0
- astreum/validation/models/fork.py +511 -0
- astreum/{consensus → validation}/models/receipt.py +17 -4
- astreum/{consensus → validation}/models/transaction.py +45 -3
- astreum/validation/node.py +190 -0
- astreum/{consensus → validation}/validator.py +18 -9
- astreum/validation/workers/__init__.py +8 -0
- astreum/{consensus → validation}/workers/validation.py +361 -307
- astreum/verification/__init__.py +4 -0
- astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
- astreum/verification/node.py +61 -0
- astreum/verification/worker.py +183 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/METADATA +43 -9
- astreum-0.3.46.dist-info/RECORD +79 -0
- astreum/consensus/models/block.py +0 -364
- astreum/consensus/models/chain.py +0 -66
- astreum/consensus/models/fork.py +0 -100
- astreum/consensus/setup.py +0 -83
- astreum/consensus/start.py +0 -67
- astreum/consensus/workers/__init__.py +0 -9
- astreum/consensus/workers/verify.py +0 -90
- astreum-0.3.9.dist-info/RECORD +0 -71
- /astreum/{consensus → validation}/models/__init__.py +0 -0
- /astreum/{consensus → validation}/models/account.py +0 -0
- /astreum/{consensus → validation}/models/accounts.py +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/WHEEL +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, List, Optional, Tuple, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from ...storage.models.atom import Atom, AtomKind, ZERO32, bytes_list_to_atoms
|
|
5
|
+
from .accounts import Accounts
|
|
6
|
+
from .receipt import Receipt
|
|
7
|
+
from .transaction import apply_transaction
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ...storage.models.trie import Trie
|
|
11
|
+
from .transaction import Transaction
|
|
12
|
+
from .receipt import Receipt
|
|
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--> version_atom --next--> signature_atom --next--> body_list_atom --next--> ZERO32
|
|
36
|
+
where: type_atom = Atom(kind=AtomKind.SYMBOL, data=b"block")
|
|
37
|
+
version_atom = Atom(kind=AtomKind.BYTES, data=b"\x01")
|
|
38
|
+
signature_atom = Atom(kind=AtomKind.BYTES, data=<signature-bytes>)
|
|
39
|
+
body_list_atom = Atom(kind=AtomKind.LIST, data=<body_head_id>)
|
|
40
|
+
|
|
41
|
+
Details order in body_list:
|
|
42
|
+
0: chain (byte)
|
|
43
|
+
1: previous_block_hash (bytes)
|
|
44
|
+
2: number (int -> big-endian bytes)
|
|
45
|
+
3: timestamp (int -> big-endian bytes)
|
|
46
|
+
4: accounts_hash (bytes)
|
|
47
|
+
5: transactions_total_fees (int -> big-endian bytes)
|
|
48
|
+
6: transactions_hash (bytes)
|
|
49
|
+
7: receipts_hash (bytes)
|
|
50
|
+
8: delay_difficulty (int -> big-endian bytes)
|
|
51
|
+
9: validator_public_key_bytes (bytes)
|
|
52
|
+
10: nonce (int -> big-endian bytes)
|
|
53
|
+
|
|
54
|
+
Notes:
|
|
55
|
+
- "body tree" is represented here by the body_list id (self.body_hash), not
|
|
56
|
+
embedded again as a field to avoid circular references.
|
|
57
|
+
- "signature" is a field on the class but is not required for validation
|
|
58
|
+
navigation; include it in the instance but it is not encoded in atoms
|
|
59
|
+
unless explicitly provided via details extension in the future.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# essential identifiers
|
|
63
|
+
version: int
|
|
64
|
+
atom_hash: Optional[bytes]
|
|
65
|
+
chain_id: int
|
|
66
|
+
previous_block_hash: bytes
|
|
67
|
+
previous_block: Optional["Block"]
|
|
68
|
+
|
|
69
|
+
# block details
|
|
70
|
+
number: int
|
|
71
|
+
timestamp: Optional[int]
|
|
72
|
+
accounts_hash: Optional[bytes]
|
|
73
|
+
transactions_total_fees: Optional[int]
|
|
74
|
+
transactions_hash: Optional[bytes]
|
|
75
|
+
receipts_hash: Optional[bytes]
|
|
76
|
+
delay_difficulty: Optional[int]
|
|
77
|
+
validator_public_key_bytes: Optional[bytes]
|
|
78
|
+
nonce: Optional[int]
|
|
79
|
+
|
|
80
|
+
# additional
|
|
81
|
+
body_hash: Optional[bytes]
|
|
82
|
+
signature: Optional[bytes]
|
|
83
|
+
|
|
84
|
+
# structures
|
|
85
|
+
accounts: Optional["Trie"]
|
|
86
|
+
transactions: Optional[List["Transaction"]]
|
|
87
|
+
receipts: Optional[List["Receipt"]]
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
*,
|
|
92
|
+
chain_id: int,
|
|
93
|
+
previous_block_hash: bytes,
|
|
94
|
+
previous_block: Optional["Block"],
|
|
95
|
+
number: int,
|
|
96
|
+
timestamp: Optional[int],
|
|
97
|
+
accounts_hash: Optional[bytes],
|
|
98
|
+
transactions_total_fees: Optional[int],
|
|
99
|
+
transactions_hash: Optional[bytes],
|
|
100
|
+
receipts_hash: Optional[bytes],
|
|
101
|
+
delay_difficulty: Optional[int],
|
|
102
|
+
validator_public_key_bytes: Optional[bytes],
|
|
103
|
+
version: int = 1,
|
|
104
|
+
nonce: Optional[int] = None,
|
|
105
|
+
signature: Optional[bytes] = None,
|
|
106
|
+
atom_hash: Optional[bytes] = None,
|
|
107
|
+
body_hash: Optional[bytes] = None,
|
|
108
|
+
accounts: Optional["Trie"] = None,
|
|
109
|
+
transactions: Optional[List["Transaction"]] = None,
|
|
110
|
+
receipts: Optional[List["Receipt"]] = None,
|
|
111
|
+
) -> None:
|
|
112
|
+
self.version = int(version)
|
|
113
|
+
self.atom_hash = atom_hash
|
|
114
|
+
self.chain_id = chain_id
|
|
115
|
+
self.previous_block_hash = previous_block_hash
|
|
116
|
+
self.previous_block = previous_block
|
|
117
|
+
self.number = number
|
|
118
|
+
self.timestamp = timestamp
|
|
119
|
+
self.accounts_hash = accounts_hash
|
|
120
|
+
self.transactions_total_fees = transactions_total_fees
|
|
121
|
+
self.transactions_hash = transactions_hash
|
|
122
|
+
self.receipts_hash = receipts_hash
|
|
123
|
+
self.delay_difficulty = delay_difficulty
|
|
124
|
+
self.validator_public_key_bytes = (
|
|
125
|
+
bytes(validator_public_key_bytes) if validator_public_key_bytes else None
|
|
126
|
+
)
|
|
127
|
+
self.nonce = nonce
|
|
128
|
+
self.body_hash = body_hash
|
|
129
|
+
self.signature = signature
|
|
130
|
+
self.accounts = accounts
|
|
131
|
+
self.transactions = transactions
|
|
132
|
+
self.receipts = receipts
|
|
133
|
+
|
|
134
|
+
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
135
|
+
# Build body details as direct byte atoms, in defined order
|
|
136
|
+
detail_payloads: List[bytes] = []
|
|
137
|
+
block_atoms: List[Atom] = []
|
|
138
|
+
|
|
139
|
+
def _emit(detail_bytes: bytes) -> None:
|
|
140
|
+
detail_payloads.append(detail_bytes)
|
|
141
|
+
|
|
142
|
+
# 0: chain
|
|
143
|
+
_emit(_int_to_be_bytes(self.chain_id))
|
|
144
|
+
# 1: previous_block_hash
|
|
145
|
+
_emit(self.previous_block_hash)
|
|
146
|
+
# 2: number
|
|
147
|
+
_emit(_int_to_be_bytes(self.number))
|
|
148
|
+
# 3: timestamp
|
|
149
|
+
_emit(_int_to_be_bytes(self.timestamp))
|
|
150
|
+
# 4: accounts_hash
|
|
151
|
+
_emit(self.accounts_hash or b"")
|
|
152
|
+
# 5: transactions_total_fees
|
|
153
|
+
_emit(_int_to_be_bytes(self.transactions_total_fees))
|
|
154
|
+
# 6: transactions_hash
|
|
155
|
+
_emit(self.transactions_hash or b"")
|
|
156
|
+
# 7: receipts_hash
|
|
157
|
+
_emit(self.receipts_hash or b"")
|
|
158
|
+
# 8: delay_difficulty
|
|
159
|
+
_emit(_int_to_be_bytes(self.delay_difficulty))
|
|
160
|
+
# 9: validator_public_key_bytes
|
|
161
|
+
_emit(self.validator_public_key_bytes or b"")
|
|
162
|
+
# 10: nonce
|
|
163
|
+
_emit(_int_to_be_bytes(self.nonce))
|
|
164
|
+
|
|
165
|
+
# Build body list chain directly from detail atoms
|
|
166
|
+
body_head = ZERO32
|
|
167
|
+
detail_atoms: List[Atom] = []
|
|
168
|
+
for payload in reversed(detail_payloads):
|
|
169
|
+
atom = Atom(data=payload, next_id=body_head, kind=AtomKind.BYTES)
|
|
170
|
+
detail_atoms.append(atom)
|
|
171
|
+
body_head = atom.object_id()
|
|
172
|
+
detail_atoms.reverse()
|
|
173
|
+
|
|
174
|
+
block_atoms.extend(detail_atoms)
|
|
175
|
+
|
|
176
|
+
body_list_atom = Atom(data=body_head, kind=AtomKind.LIST)
|
|
177
|
+
self.body_hash = body_list_atom.object_id()
|
|
178
|
+
|
|
179
|
+
# Signature atom links to body list atom; type atom links to signature atom
|
|
180
|
+
sig_atom = Atom(
|
|
181
|
+
data=bytes(self.signature or b""),
|
|
182
|
+
next_id=self.body_hash,
|
|
183
|
+
kind=AtomKind.BYTES,
|
|
184
|
+
)
|
|
185
|
+
version_atom = Atom(
|
|
186
|
+
data=_int_to_be_bytes(self.version),
|
|
187
|
+
next_id=sig_atom.object_id(),
|
|
188
|
+
kind=AtomKind.BYTES,
|
|
189
|
+
)
|
|
190
|
+
type_atom = Atom(
|
|
191
|
+
data=b"block",
|
|
192
|
+
next_id=version_atom.object_id(),
|
|
193
|
+
kind=AtomKind.SYMBOL,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
block_atoms.append(body_list_atom)
|
|
197
|
+
block_atoms.append(sig_atom)
|
|
198
|
+
block_atoms.append(version_atom)
|
|
199
|
+
block_atoms.append(type_atom)
|
|
200
|
+
|
|
201
|
+
self.atom_hash = type_atom.object_id()
|
|
202
|
+
return self.atom_hash, block_atoms
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_atom(cls, node: Any, block_id: bytes) -> "Block":
|
|
206
|
+
|
|
207
|
+
block_header = node.get_atom_list_from_storage(block_id)
|
|
208
|
+
if block_header is None or len(block_header) != 4:
|
|
209
|
+
raise ValueError("malformed block atom chain")
|
|
210
|
+
type_atom, version_atom, sig_atom, body_list_atom = block_header
|
|
211
|
+
|
|
212
|
+
if type_atom.kind is not AtomKind.SYMBOL or type_atom.data != b"block":
|
|
213
|
+
raise ValueError("not a block (type atom payload)")
|
|
214
|
+
if version_atom.kind is not AtomKind.BYTES:
|
|
215
|
+
raise ValueError("malformed block (version atom kind)")
|
|
216
|
+
version = _be_bytes_to_int(version_atom.data)
|
|
217
|
+
if version != 1:
|
|
218
|
+
raise ValueError("unsupported block version")
|
|
219
|
+
if sig_atom.kind is not AtomKind.BYTES:
|
|
220
|
+
raise ValueError("malformed block (signature atom kind)")
|
|
221
|
+
if body_list_atom.kind is not AtomKind.LIST:
|
|
222
|
+
raise ValueError("malformed block (body list atom kind)")
|
|
223
|
+
if body_list_atom.next_id != ZERO32:
|
|
224
|
+
raise ValueError("malformed block (body list tail)")
|
|
225
|
+
|
|
226
|
+
detail_atoms = node.get_atom_list_from_storage(body_list_atom.data)
|
|
227
|
+
if detail_atoms is None:
|
|
228
|
+
raise ValueError("missing block body list nodes")
|
|
229
|
+
|
|
230
|
+
if len(detail_atoms) != 11:
|
|
231
|
+
raise ValueError("block body must contain exactly 11 detail entries")
|
|
232
|
+
|
|
233
|
+
detail_values: List[bytes] = []
|
|
234
|
+
for detail_atom in detail_atoms:
|
|
235
|
+
if detail_atom.kind is not AtomKind.BYTES:
|
|
236
|
+
raise ValueError("block body detail atoms must be bytes")
|
|
237
|
+
detail_values.append(detail_atom.data)
|
|
238
|
+
|
|
239
|
+
(
|
|
240
|
+
chain_bytes,
|
|
241
|
+
prev_bytes,
|
|
242
|
+
number_bytes,
|
|
243
|
+
timestamp_bytes,
|
|
244
|
+
accounts_bytes,
|
|
245
|
+
fees_bytes,
|
|
246
|
+
transactions_bytes,
|
|
247
|
+
receipts_bytes,
|
|
248
|
+
delay_diff_bytes,
|
|
249
|
+
validator_bytes,
|
|
250
|
+
nonce_bytes,
|
|
251
|
+
) = detail_values
|
|
252
|
+
|
|
253
|
+
return cls(
|
|
254
|
+
version=version,
|
|
255
|
+
chain_id=_be_bytes_to_int(chain_bytes),
|
|
256
|
+
previous_block_hash=prev_bytes or ZERO32,
|
|
257
|
+
previous_block=None,
|
|
258
|
+
number=_be_bytes_to_int(number_bytes),
|
|
259
|
+
timestamp=_be_bytes_to_int(timestamp_bytes),
|
|
260
|
+
accounts_hash=accounts_bytes or None,
|
|
261
|
+
transactions_total_fees=_be_bytes_to_int(fees_bytes),
|
|
262
|
+
transactions_hash=transactions_bytes or None,
|
|
263
|
+
receipts_hash=receipts_bytes or None,
|
|
264
|
+
delay_difficulty=_be_bytes_to_int(delay_diff_bytes),
|
|
265
|
+
validator_public_key_bytes=validator_bytes or None,
|
|
266
|
+
nonce=_be_bytes_to_int(nonce_bytes),
|
|
267
|
+
signature=sig_atom.data if sig_atom is not None else None,
|
|
268
|
+
atom_hash=block_id,
|
|
269
|
+
body_hash=body_list_atom.object_id(),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def verify(self, node: Any) -> bool:
|
|
273
|
+
"""Verify receipts, transactions, and accounts hashes for this block."""
|
|
274
|
+
if node is None:
|
|
275
|
+
raise ValueError("node required for block verification")
|
|
276
|
+
|
|
277
|
+
logger = getattr(node, "logger", None)
|
|
278
|
+
|
|
279
|
+
def _hex(value: Optional[bytes]) -> str:
|
|
280
|
+
if isinstance(value, (bytes, bytearray)):
|
|
281
|
+
return value.hex()
|
|
282
|
+
return str(value)
|
|
283
|
+
|
|
284
|
+
def _log_debug(message: str, *args: object) -> None:
|
|
285
|
+
if logger:
|
|
286
|
+
logger.debug(message, *args)
|
|
287
|
+
|
|
288
|
+
def _log_warning(message: str, *args: object) -> None:
|
|
289
|
+
if logger:
|
|
290
|
+
logger.warning(message, *args)
|
|
291
|
+
|
|
292
|
+
_log_debug("Block verify start block=%s", _hex(self.atom_hash))
|
|
293
|
+
|
|
294
|
+
if self.transactions_hash is None:
|
|
295
|
+
_log_warning("Block verify missing transactions_hash block=%s", _hex(self.atom_hash))
|
|
296
|
+
return False
|
|
297
|
+
if self.receipts_hash is None:
|
|
298
|
+
_log_warning("Block verify missing receipts_hash block=%s", _hex(self.atom_hash))
|
|
299
|
+
return False
|
|
300
|
+
if self.accounts_hash is None:
|
|
301
|
+
_log_warning("Block verify missing accounts_hash block=%s", _hex(self.atom_hash))
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
def _load_hash_list(head: bytes) -> Optional[List[bytes]]:
|
|
305
|
+
if head == ZERO32:
|
|
306
|
+
return []
|
|
307
|
+
atoms = node.get_atom_list_from_storage(head)
|
|
308
|
+
if atoms is None:
|
|
309
|
+
_log_warning("Block verify missing list atoms head=%s block=%s", _hex(head), _hex(self.atom_hash))
|
|
310
|
+
return None
|
|
311
|
+
for atom in atoms:
|
|
312
|
+
if atom.kind is not AtomKind.BYTES:
|
|
313
|
+
_log_warning("Block verify list atom kind mismatch head=%s block=%s", _hex(head), _hex(self.atom_hash))
|
|
314
|
+
return None
|
|
315
|
+
return [bytes(atom.data) for atom in atoms]
|
|
316
|
+
|
|
317
|
+
prev_hash = self.previous_block_hash or ZERO32
|
|
318
|
+
if prev_hash == ZERO32:
|
|
319
|
+
if self.transactions_hash != ZERO32:
|
|
320
|
+
_log_warning("Block verify genesis tx hash mismatch block=%s", _hex(self.atom_hash))
|
|
321
|
+
return False
|
|
322
|
+
if self.receipts_hash != ZERO32:
|
|
323
|
+
_log_warning("Block verify genesis receipts hash mismatch block=%s", _hex(self.atom_hash))
|
|
324
|
+
return False
|
|
325
|
+
if self.transactions_total_fees not in (0, None):
|
|
326
|
+
_log_warning("Block verify genesis fees mismatch block=%s", _hex(self.atom_hash))
|
|
327
|
+
return False
|
|
328
|
+
_log_debug("Block verify genesis passed block=%s", _hex(self.atom_hash))
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
prev_block = self.previous_block or Block.from_atom(node, prev_hash)
|
|
333
|
+
except Exception:
|
|
334
|
+
_log_warning("Block verify failed loading parent block=%s prev=%s", _hex(self.atom_hash), _hex(prev_hash))
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
prev_accounts_hash = getattr(prev_block, "accounts_hash", None)
|
|
338
|
+
if not prev_accounts_hash:
|
|
339
|
+
_log_warning("Block verify missing parent accounts hash block=%s", _hex(self.atom_hash))
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
tx_hashes = _load_hash_list(self.transactions_hash)
|
|
343
|
+
if tx_hashes is None:
|
|
344
|
+
_log_warning("Block verify failed loading tx list block=%s", _hex(self.atom_hash))
|
|
345
|
+
return False
|
|
346
|
+
|
|
347
|
+
expected_tx_head, _ = bytes_list_to_atoms(tx_hashes)
|
|
348
|
+
if expected_tx_head != (self.transactions_hash or ZERO32):
|
|
349
|
+
_log_warning(
|
|
350
|
+
"Block verify tx head mismatch block=%s expected=%s actual=%s",
|
|
351
|
+
_hex(self.atom_hash),
|
|
352
|
+
_hex(expected_tx_head),
|
|
353
|
+
_hex(self.transactions_hash),
|
|
354
|
+
)
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
accounts_snapshot = Accounts(root_hash=prev_accounts_hash)
|
|
358
|
+
work_block = type("_WorkBlock", (), {})()
|
|
359
|
+
work_block.chain_id = self.chain_id
|
|
360
|
+
work_block.accounts = accounts_snapshot
|
|
361
|
+
work_block.transactions = []
|
|
362
|
+
work_block.receipts = []
|
|
363
|
+
|
|
364
|
+
total_fees = 0
|
|
365
|
+
for tx_hash in tx_hashes:
|
|
366
|
+
try:
|
|
367
|
+
total_fees += apply_transaction(node, work_block, tx_hash)
|
|
368
|
+
except Exception:
|
|
369
|
+
_log_warning(
|
|
370
|
+
"Block verify failed applying tx=%s block=%s",
|
|
371
|
+
_hex(tx_hash),
|
|
372
|
+
_hex(self.atom_hash),
|
|
373
|
+
)
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
if self.transactions_total_fees is None:
|
|
377
|
+
_log_warning("Block verify missing total fees block=%s", _hex(self.atom_hash))
|
|
378
|
+
return False
|
|
379
|
+
if int(self.transactions_total_fees) != int(total_fees):
|
|
380
|
+
_log_warning(
|
|
381
|
+
"Block verify fees mismatch block=%s expected=%s actual=%s",
|
|
382
|
+
_hex(self.atom_hash),
|
|
383
|
+
total_fees,
|
|
384
|
+
self.transactions_total_fees,
|
|
385
|
+
)
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
applied_transactions = list(work_block.transactions or [])
|
|
389
|
+
if len(applied_transactions) != len(tx_hashes):
|
|
390
|
+
_log_warning(
|
|
391
|
+
"Block verify tx count mismatch block=%s expected=%s actual=%s",
|
|
392
|
+
_hex(self.atom_hash),
|
|
393
|
+
len(tx_hashes),
|
|
394
|
+
len(applied_transactions),
|
|
395
|
+
)
|
|
396
|
+
return False
|
|
397
|
+
|
|
398
|
+
expected_receipts: List[Receipt] = list(work_block.receipts or [])
|
|
399
|
+
if len(expected_receipts) != len(applied_transactions):
|
|
400
|
+
_log_warning(
|
|
401
|
+
"Block verify receipt count mismatch block=%s expected=%s actual=%s",
|
|
402
|
+
_hex(self.atom_hash),
|
|
403
|
+
len(applied_transactions),
|
|
404
|
+
len(expected_receipts),
|
|
405
|
+
)
|
|
406
|
+
return False
|
|
407
|
+
expected_receipt_ids: List[bytes] = []
|
|
408
|
+
for receipt in expected_receipts:
|
|
409
|
+
receipt_id, _ = receipt.to_atom()
|
|
410
|
+
expected_receipt_ids.append(receipt_id)
|
|
411
|
+
|
|
412
|
+
expected_receipts_head, _ = bytes_list_to_atoms(expected_receipt_ids)
|
|
413
|
+
if expected_receipts_head != (self.receipts_hash or ZERO32):
|
|
414
|
+
_log_warning(
|
|
415
|
+
"Block verify receipts head mismatch block=%s expected=%s actual=%s",
|
|
416
|
+
_hex(self.atom_hash),
|
|
417
|
+
_hex(expected_receipts_head),
|
|
418
|
+
_hex(self.receipts_hash),
|
|
419
|
+
)
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
stored_receipt_ids = _load_hash_list(self.receipts_hash)
|
|
423
|
+
if stored_receipt_ids is None:
|
|
424
|
+
_log_warning("Block verify failed loading receipts list block=%s", _hex(self.atom_hash))
|
|
425
|
+
return False
|
|
426
|
+
if stored_receipt_ids != expected_receipt_ids:
|
|
427
|
+
_log_warning("Block verify receipts list mismatch block=%s", _hex(self.atom_hash))
|
|
428
|
+
return False
|
|
429
|
+
for expected, stored_id in zip(expected_receipts, stored_receipt_ids):
|
|
430
|
+
try:
|
|
431
|
+
stored = Receipt.from_atom(node, stored_id)
|
|
432
|
+
except Exception:
|
|
433
|
+
_log_warning(
|
|
434
|
+
"Block verify failed loading receipt=%s block=%s",
|
|
435
|
+
_hex(stored_id),
|
|
436
|
+
_hex(self.atom_hash),
|
|
437
|
+
)
|
|
438
|
+
return False
|
|
439
|
+
if stored.transaction_hash != expected.transaction_hash:
|
|
440
|
+
_log_warning(
|
|
441
|
+
"Block verify receipt tx mismatch receipt=%s block=%s",
|
|
442
|
+
_hex(stored_id),
|
|
443
|
+
_hex(self.atom_hash),
|
|
444
|
+
)
|
|
445
|
+
return False
|
|
446
|
+
if stored.status != expected.status:
|
|
447
|
+
_log_warning(
|
|
448
|
+
"Block verify receipt status mismatch receipt=%s block=%s",
|
|
449
|
+
_hex(stored_id),
|
|
450
|
+
_hex(self.atom_hash),
|
|
451
|
+
)
|
|
452
|
+
return False
|
|
453
|
+
if stored.cost != expected.cost:
|
|
454
|
+
_log_warning(
|
|
455
|
+
"Block verify receipt cost mismatch receipt=%s block=%s",
|
|
456
|
+
_hex(stored_id),
|
|
457
|
+
_hex(self.atom_hash),
|
|
458
|
+
)
|
|
459
|
+
return False
|
|
460
|
+
if stored.logs_hash != expected.logs_hash:
|
|
461
|
+
_log_warning(
|
|
462
|
+
"Block verify receipt logs hash mismatch receipt=%s block=%s",
|
|
463
|
+
_hex(stored_id),
|
|
464
|
+
_hex(self.atom_hash),
|
|
465
|
+
)
|
|
466
|
+
return False
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
accounts_snapshot.update_trie(node)
|
|
470
|
+
except Exception:
|
|
471
|
+
_log_warning("Block verify failed updating accounts trie block=%s", _hex(self.atom_hash))
|
|
472
|
+
return False
|
|
473
|
+
if accounts_snapshot.root_hash != self.accounts_hash:
|
|
474
|
+
_log_warning(
|
|
475
|
+
"Block verify accounts hash mismatch block=%s expected=%s actual=%s",
|
|
476
|
+
_hex(self.atom_hash),
|
|
477
|
+
_hex(accounts_snapshot.root_hash),
|
|
478
|
+
_hex(self.accounts_hash),
|
|
479
|
+
)
|
|
480
|
+
return False
|
|
481
|
+
|
|
482
|
+
_log_debug("Block verify success block=%s", _hex(self.atom_hash))
|
|
483
|
+
return True
|
|
484
|
+
|
|
485
|
+
@staticmethod
|
|
486
|
+
def _leading_zero_bits(buf: bytes) -> int:
|
|
487
|
+
"""Return the number of leading zero bits in the provided buffer."""
|
|
488
|
+
zeros = 0
|
|
489
|
+
for byte in buf:
|
|
490
|
+
if byte == 0:
|
|
491
|
+
zeros += 8
|
|
492
|
+
continue
|
|
493
|
+
zeros += 8 - int(byte).bit_length()
|
|
494
|
+
break
|
|
495
|
+
return zeros
|
|
496
|
+
|
|
497
|
+
@staticmethod
|
|
498
|
+
def calculate_delay_difficulty(
|
|
499
|
+
*,
|
|
500
|
+
previous_timestamp: Optional[int],
|
|
501
|
+
current_timestamp: Optional[int],
|
|
502
|
+
previous_difficulty: Optional[int],
|
|
503
|
+
target_spacing: int = 2,
|
|
504
|
+
) -> int:
|
|
505
|
+
"""
|
|
506
|
+
Adjust the delay difficulty with linear steps relative to block spacing.
|
|
507
|
+
|
|
508
|
+
If blocks arrive too quickly (spacing <= 1), difficulty increases by one.
|
|
509
|
+
If blocks are slower than the target spacing, difficulty decreases by one,
|
|
510
|
+
and otherwise remains unchanged.
|
|
511
|
+
"""
|
|
512
|
+
base_difficulty = max(1, int(previous_difficulty or 1))
|
|
513
|
+
if previous_timestamp is None or current_timestamp is None:
|
|
514
|
+
return base_difficulty
|
|
515
|
+
|
|
516
|
+
spacing = max(0, int(current_timestamp) - int(previous_timestamp))
|
|
517
|
+
if spacing <= 1:
|
|
518
|
+
return base_difficulty + 1
|
|
519
|
+
if spacing > target_spacing:
|
|
520
|
+
return max(1, base_difficulty - 1)
|
|
521
|
+
return base_difficulty
|
|
522
|
+
|
|
523
|
+
def generate_nonce(
|
|
524
|
+
self,
|
|
525
|
+
*,
|
|
526
|
+
difficulty: int,
|
|
527
|
+
) -> int:
|
|
528
|
+
"""
|
|
529
|
+
Find a nonce that yields a block hash with the required leading zero bits.
|
|
530
|
+
|
|
531
|
+
The search starts from the current nonce and iterates until the target
|
|
532
|
+
difficulty is met.
|
|
533
|
+
"""
|
|
534
|
+
target = max(1, int(difficulty))
|
|
535
|
+
start = int(self.nonce or 0)
|
|
536
|
+
nonce = start
|
|
537
|
+
while True:
|
|
538
|
+
self.nonce = nonce
|
|
539
|
+
block_hash, _ = self.to_atom()
|
|
540
|
+
leading_zeros = self._leading_zero_bits(block_hash)
|
|
541
|
+
if leading_zeros >= target:
|
|
542
|
+
self.atom_hash = block_hash
|
|
543
|
+
return nonce
|
|
544
|
+
nonce += 1
|