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.
- astreum/__init__.py +16 -7
- astreum/{_communication → communication}/__init__.py +3 -3
- astreum/communication/handlers/handshake.py +89 -0
- astreum/communication/handlers/object_request.py +176 -0
- astreum/communication/handlers/object_response.py +115 -0
- astreum/communication/handlers/ping.py +34 -0
- astreum/communication/handlers/route_request.py +76 -0
- astreum/communication/handlers/route_response.py +53 -0
- astreum/communication/models/__init__.py +0 -0
- astreum/communication/models/message.py +124 -0
- astreum/communication/models/peer.py +51 -0
- astreum/{_communication → communication/models}/route.py +7 -12
- astreum/communication/processors/__init__.py +0 -0
- astreum/communication/processors/incoming.py +98 -0
- astreum/communication/processors/outgoing.py +20 -0
- astreum/communication/setup.py +166 -0
- astreum/communication/start.py +37 -0
- astreum/{_communication → communication}/util.py +7 -0
- astreum/consensus/__init__.py +20 -0
- astreum/consensus/genesis.py +66 -0
- astreum/consensus/models/__init__.py +0 -0
- astreum/consensus/models/account.py +84 -0
- astreum/consensus/models/accounts.py +72 -0
- astreum/consensus/models/block.py +364 -0
- astreum/{_consensus → consensus/models}/chain.py +7 -7
- astreum/{_consensus → consensus/models}/fork.py +8 -8
- astreum/consensus/models/receipt.py +98 -0
- astreum/{_consensus → consensus/models}/transaction.py +76 -78
- astreum/{_consensus → consensus}/setup.py +18 -50
- astreum/consensus/start.py +67 -0
- astreum/consensus/validator.py +95 -0
- astreum/{_consensus → consensus}/workers/discovery.py +19 -1
- astreum/consensus/workers/validation.py +307 -0
- astreum/{_consensus → consensus}/workers/verify.py +29 -2
- astreum/crypto/chacha20poly1305.py +74 -0
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/{_lispeum → machine/evaluations}/high_evaluation.py +237 -236
- astreum/machine/evaluations/low_evaluation.py +281 -0
- astreum/machine/evaluations/script_evaluation.py +27 -0
- astreum/machine/models/__init__.py +0 -0
- astreum/machine/models/environment.py +31 -0
- astreum/{_lispeum → machine/models}/expression.py +36 -8
- astreum/machine/tokenizer.py +90 -0
- astreum/node.py +78 -767
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +183 -0
- astreum/storage/actions/set.py +178 -0
- astreum/{_storage → storage/models}/atom.py +55 -57
- astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
- astreum/storage/requests.py +28 -0
- astreum/storage/setup.py +22 -15
- astreum/utils/config.py +48 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/METADATA +27 -26
- astreum-0.3.9.dist-info/RECORD +71 -0
- astreum/_communication/message.py +0 -101
- astreum/_communication/peer.py +0 -23
- astreum/_communication/setup.py +0 -322
- astreum/_consensus/__init__.py +0 -20
- astreum/_consensus/account.py +0 -95
- astreum/_consensus/accounts.py +0 -38
- astreum/_consensus/block.py +0 -311
- astreum/_consensus/genesis.py +0 -72
- astreum/_consensus/receipt.py +0 -136
- astreum/_consensus/workers/validation.py +0 -125
- astreum/_lispeum/__init__.py +0 -16
- astreum/_lispeum/environment.py +0 -13
- astreum/_lispeum/low_evaluation.py +0 -123
- astreum/_lispeum/tokenizer.py +0 -22
- astreum/_node.py +0 -198
- astreum/_storage/__init__.py +0 -7
- astreum/_storage/setup.py +0 -35
- astreum/format.py +0 -75
- astreum/models/block.py +0 -441
- astreum/models/merkle.py +0 -205
- astreum/models/patricia.py +0 -393
- astreum/storage/object.py +0 -68
- astreum-0.2.61.dist-info/RECORD +0 -57
- /astreum/{models → communication/handlers}/__init__.py +0 -0
- /astreum/{_communication → communication/models}/ping.py +0 -0
- /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
- /astreum/{_lispeum → machine/models}/meter.py +0 -0
- /astreum/{_lispeum → machine}/parser.py +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/WHEEL +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/top_level.txt +0 -0
astreum/models/block.py
DELETED
|
@@ -1,441 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from threading import Thread
|
|
4
|
-
from typing import List, Dict, Any, Optional, Union
|
|
5
|
-
|
|
6
|
-
from astreum.crypto.wesolowski import vdf_generate, vdf_verify
|
|
7
|
-
from astreum._consensus.account import Account
|
|
8
|
-
from astreum._consensus.accounts import Accounts
|
|
9
|
-
from astreum.models.patricia import PatriciaTrie
|
|
10
|
-
from astreum.models.transaction import Transaction
|
|
11
|
-
from ..crypto import ed25519
|
|
12
|
-
from .merkle import MerkleTree
|
|
13
|
-
|
|
14
|
-
# Constants for integer field names
|
|
15
|
-
_INT_FIELDS = {
|
|
16
|
-
"delay_difficulty",
|
|
17
|
-
"number",
|
|
18
|
-
"timestamp",
|
|
19
|
-
"transaction_limit",
|
|
20
|
-
"transactions_total_fees",
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class Block:
|
|
24
|
-
def __init__(
|
|
25
|
-
self,
|
|
26
|
-
block_hash: bytes,
|
|
27
|
-
*,
|
|
28
|
-
number: Optional[int] = None,
|
|
29
|
-
prev_block_hash: Optional[bytes] = None,
|
|
30
|
-
timestamp: Optional[int] = None,
|
|
31
|
-
accounts_hash: Optional[bytes] = None,
|
|
32
|
-
accounts: Optional[Accounts] = None,
|
|
33
|
-
transaction_limit: Optional[int] = None,
|
|
34
|
-
transactions_total_fees: Optional[int] = None,
|
|
35
|
-
transactions_hash: Optional[bytes] = None,
|
|
36
|
-
transactions_count: Optional[int] = None,
|
|
37
|
-
delay_difficulty: Optional[int] = None,
|
|
38
|
-
delay_output: Optional[bytes] = None,
|
|
39
|
-
delay_proof: Optional[bytes] = None,
|
|
40
|
-
validator_pk: Optional[bytes] = None,
|
|
41
|
-
body_tree: Optional[MerkleTree] = None,
|
|
42
|
-
signature: Optional[bytes] = None,
|
|
43
|
-
):
|
|
44
|
-
self.hash = block_hash
|
|
45
|
-
self.number = number
|
|
46
|
-
self.prev_block_hash = prev_block_hash
|
|
47
|
-
self.timestamp = timestamp
|
|
48
|
-
self.accounts_hash = accounts_hash
|
|
49
|
-
self.accounts = accounts
|
|
50
|
-
self.transaction_limit = transaction_limit
|
|
51
|
-
self.transactions_total_fees = transactions_total_fees
|
|
52
|
-
self.transactions_hash = transactions_hash
|
|
53
|
-
self.transactions_count = transactions_count
|
|
54
|
-
self.delay_difficulty = delay_difficulty
|
|
55
|
-
self.delay_output = delay_output
|
|
56
|
-
self.delay_proof = delay_proof
|
|
57
|
-
self.validator_pk = validator_pk
|
|
58
|
-
self.body_tree = body_tree
|
|
59
|
-
self.signature = signature
|
|
60
|
-
|
|
61
|
-
@property
|
|
62
|
-
def hash(self) -> bytes:
|
|
63
|
-
return self._block_hash
|
|
64
|
-
|
|
65
|
-
def get_body_hash(self) -> bytes:
|
|
66
|
-
"""Return the Merkle root of the body fields."""
|
|
67
|
-
if not self._body_tree:
|
|
68
|
-
raise ValueError("Body tree not available for this block instance.")
|
|
69
|
-
return self._body_tree.root_hash
|
|
70
|
-
|
|
71
|
-
def get_signature(self) -> bytes:
|
|
72
|
-
"""Return the block's signature leaf."""
|
|
73
|
-
if self._signature is None:
|
|
74
|
-
raise ValueError("Signature not available for this block instance.")
|
|
75
|
-
return self._signature
|
|
76
|
-
|
|
77
|
-
# Backwards/forwards alias for clarity with external specs
|
|
78
|
-
@property
|
|
79
|
-
def validator_public_key(self) -> Optional[bytes]:
|
|
80
|
-
return self.validator_pk
|
|
81
|
-
|
|
82
|
-
@validator_public_key.setter
|
|
83
|
-
def validator_public_key(self, value: Optional[bytes]) -> None:
|
|
84
|
-
self.validator_pk = value
|
|
85
|
-
|
|
86
|
-
def get_field(self, name: str) -> Union[int, bytes]:
|
|
87
|
-
"""Query a single body field by name, returning an int or bytes."""
|
|
88
|
-
if name not in self._field_names:
|
|
89
|
-
raise KeyError(f"Unknown field: {name}")
|
|
90
|
-
if not self._body_tree:
|
|
91
|
-
raise ValueError("Body tree not available for field queries.")
|
|
92
|
-
idx = self._field_names.index(name)
|
|
93
|
-
leaf_bytes = self._body_tree.leaves[idx]
|
|
94
|
-
if name in _INT_FIELDS:
|
|
95
|
-
return int.from_bytes(leaf_bytes, "big")
|
|
96
|
-
return leaf_bytes
|
|
97
|
-
|
|
98
|
-
@classmethod
|
|
99
|
-
def genesis(cls, validator_addr: bytes) -> "Block":
|
|
100
|
-
# 1. validator-stakes sub-trie
|
|
101
|
-
stake_trie = PatriciaTrie()
|
|
102
|
-
stake_trie.put(validator_addr, (1).to_bytes(32, "big"))
|
|
103
|
-
stake_root = stake_trie.root_hash
|
|
104
|
-
|
|
105
|
-
# 2. three Account bodies
|
|
106
|
-
validator_acct = Account.create(balance=0, data=b"", counter=0)
|
|
107
|
-
treasury_acct = Account.create(balance=1, data=stake_root, counter=0)
|
|
108
|
-
burn_acct = Account.create(balance=0, data=b"", counter=0)
|
|
109
|
-
|
|
110
|
-
# 3. global Accounts structure
|
|
111
|
-
accts = Accounts()
|
|
112
|
-
accts.set_account(validator_addr, validator_acct)
|
|
113
|
-
accts.set_account(b"\x11" * 32, treasury_acct)
|
|
114
|
-
accts.set_account(b"\x00" * 32, burn_acct)
|
|
115
|
-
accounts_hash = accts.root_hash
|
|
116
|
-
|
|
117
|
-
# 4. constant body fields for genesis
|
|
118
|
-
body_kwargs = dict(
|
|
119
|
-
block_hash = b"",
|
|
120
|
-
number = 0,
|
|
121
|
-
prev_block_hash = b"\x00" * 32,
|
|
122
|
-
timestamp = 0,
|
|
123
|
-
block_time = 0,
|
|
124
|
-
accounts_hash = accounts_hash,
|
|
125
|
-
accounts = accts,
|
|
126
|
-
transactions_total_fees = 0,
|
|
127
|
-
transaction_limit = 1,
|
|
128
|
-
transactions_hash = b"\x00" * 32,
|
|
129
|
-
transactions_count = 0,
|
|
130
|
-
delay_difficulty = 1,
|
|
131
|
-
delay_output = b"",
|
|
132
|
-
delay_proof = b"",
|
|
133
|
-
validator_pk = validator_addr,
|
|
134
|
-
signature = b"",
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
# 5. build and return the block
|
|
138
|
-
return cls.create(**body_kwargs)
|
|
139
|
-
|
|
140
|
-
@classmethod
|
|
141
|
-
def build(
|
|
142
|
-
cls,
|
|
143
|
-
previous_block: "Block",
|
|
144
|
-
transactions: List[Transaction],
|
|
145
|
-
*,
|
|
146
|
-
validator_sk,
|
|
147
|
-
natural_rate: float = 0.618,
|
|
148
|
-
) -> "Block":
|
|
149
|
-
BURN = b"\x00" * 32
|
|
150
|
-
|
|
151
|
-
blk = cls(
|
|
152
|
-
block_hash=b"",
|
|
153
|
-
number=previous_block.number + 1,
|
|
154
|
-
prev_block_hash=previous_block.hash,
|
|
155
|
-
timestamp=previous_block.timestamp + 1,
|
|
156
|
-
accounts_hash=previous_block.accounts_hash,
|
|
157
|
-
transaction_limit=previous_block.transaction_limit,
|
|
158
|
-
transactions_count=0,
|
|
159
|
-
validator_pk=validator_sk.public_key().public_bytes(),
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
# ------------------ difficulty via natural_rate -----------------------
|
|
163
|
-
prev_bt = previous_block.block_time or 0
|
|
164
|
-
prev_diff = previous_block.delay_difficulty or 1
|
|
165
|
-
if prev_bt <= 1:
|
|
166
|
-
blk.delay_difficulty = max(1, int(prev_diff / natural_rate)) # increase
|
|
167
|
-
else:
|
|
168
|
-
blk.delay_difficulty = max(1, int(prev_diff * natural_rate)) # decrease
|
|
169
|
-
|
|
170
|
-
# ------------------ launch VDF in background --------------------------
|
|
171
|
-
vdf_result: dict[str, bytes] = {}
|
|
172
|
-
|
|
173
|
-
def _vdf_worker():
|
|
174
|
-
y, p = vdf_generate(previous_block.delay_output, blk.delay_difficulty, -4)
|
|
175
|
-
vdf_result["y"] = y
|
|
176
|
-
vdf_result["p"] = p
|
|
177
|
-
|
|
178
|
-
Thread(target=_vdf_worker, daemon=True).start()
|
|
179
|
-
|
|
180
|
-
# ------------------ process transactions -----------------------------
|
|
181
|
-
for tx in transactions:
|
|
182
|
-
try:
|
|
183
|
-
blk.apply_tx(tx)
|
|
184
|
-
except ValueError:
|
|
185
|
-
break
|
|
186
|
-
|
|
187
|
-
# ------------------ split fees --------------------------------------
|
|
188
|
-
burn_amt = blk.total_fees // 2
|
|
189
|
-
reward_amt = blk.total_fees - burn_amt
|
|
190
|
-
|
|
191
|
-
def _credit(addr: bytes, amt: int):
|
|
192
|
-
acc = blk.accounts.get_account(addr) or Account.create(0, b"", 0)
|
|
193
|
-
blk.accounts.set_account(addr, Account.create(acc.balance + amt, acc.data, acc.counter))
|
|
194
|
-
|
|
195
|
-
if burn_amt:
|
|
196
|
-
_credit(BURN, burn_amt)
|
|
197
|
-
if reward_amt:
|
|
198
|
-
_credit(blk.validator_pk, reward_amt)
|
|
199
|
-
|
|
200
|
-
# ------------------ update tx limit with natural_rate ---------------
|
|
201
|
-
prev_limit = previous_block.transaction_limit
|
|
202
|
-
prev_tx_count = previous_block.transactions_count
|
|
203
|
-
grow_thr = prev_limit * natural_rate
|
|
204
|
-
shrink_thr = prev_tx_count * natural_rate
|
|
205
|
-
|
|
206
|
-
if prev_tx_count > grow_thr:
|
|
207
|
-
blk.transaction_limit = prev_tx_count
|
|
208
|
-
elif prev_tx_count < shrink_thr:
|
|
209
|
-
blk.transaction_limit = max(1, int(prev_limit * natural_rate))
|
|
210
|
-
else:
|
|
211
|
-
blk.transaction_limit = prev_limit
|
|
212
|
-
|
|
213
|
-
# ------------------ wait for VDF ------------------------------------
|
|
214
|
-
while "y" not in vdf_result:
|
|
215
|
-
pass
|
|
216
|
-
blk.delay_output = vdf_result["y"]
|
|
217
|
-
blk.delay_proof = vdf_result["p"]
|
|
218
|
-
|
|
219
|
-
# ------------------ timing & roots ----------------------------------
|
|
220
|
-
blk.block_time = blk.timestamp - previous_block.timestamp
|
|
221
|
-
blk.accounts_hash = blk.accounts.root_hash
|
|
222
|
-
blk.transactions_hash = MerkleTree.from_leaves(blk.tx_hashes).root_hash
|
|
223
|
-
blk.transactions_total_fees = blk.total_fees
|
|
224
|
-
|
|
225
|
-
# ------------------ build full body root ----------------------------
|
|
226
|
-
body_fields = {
|
|
227
|
-
"accounts_hash": blk.accounts_hash,
|
|
228
|
-
"block_time": blk.block_time,
|
|
229
|
-
"delay_difficulty": blk.delay_difficulty,
|
|
230
|
-
"delay_output": blk.delay_output,
|
|
231
|
-
"delay_proof": blk.delay_proof,
|
|
232
|
-
"number": blk.number,
|
|
233
|
-
"prev_block_hash": blk.prev_block_hash,
|
|
234
|
-
"timestamp": blk.timestamp,
|
|
235
|
-
"transaction_limit": blk.transaction_limit,
|
|
236
|
-
"transactions_count": blk.transactions_count,
|
|
237
|
-
"transactions_hash": blk.transactions_hash,
|
|
238
|
-
"transactions_total_fees": blk.transactions_total_fees,
|
|
239
|
-
"validator_pk": blk.validator_pk,
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
leaves: List[bytes] = []
|
|
243
|
-
for k in sorted(body_fields):
|
|
244
|
-
v = body_fields[k]
|
|
245
|
-
if isinstance(v, bytes):
|
|
246
|
-
leaves.append(v)
|
|
247
|
-
else:
|
|
248
|
-
leaves.append(int(v).to_bytes((v.bit_length() + 7) // 8 or 1, "big"))
|
|
249
|
-
|
|
250
|
-
body_root = MerkleTree.from_leaves(leaves).root_hash
|
|
251
|
-
blk.body_tree = MerkleTree.from_leaves([body_root])
|
|
252
|
-
blk.signature = validator_sk.sign(body_root)
|
|
253
|
-
blk.hash = MerkleTree.from_leaves([body_root, blk.signature]).root_hash
|
|
254
|
-
|
|
255
|
-
return blk
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def apply_tx(self, tx: Transaction) -> None:
|
|
259
|
-
# --- lazy state ----------------------------------------------------
|
|
260
|
-
if not hasattr(self, "accounts") or self.accounts is None:
|
|
261
|
-
self.accounts = Accounts(root_hash=self.accounts_hash)
|
|
262
|
-
if not hasattr(self, "total_fees"):
|
|
263
|
-
self.total_fees = 0
|
|
264
|
-
self.tx_hashes = []
|
|
265
|
-
self.transactions_count = 0
|
|
266
|
-
|
|
267
|
-
TREASURY = b"\x11" * 32
|
|
268
|
-
BURN = b"\x00" * 32
|
|
269
|
-
|
|
270
|
-
# --- cap check -----------------------------------------------------
|
|
271
|
-
if self.transactions_count >= self.transaction_limit:
|
|
272
|
-
raise ValueError("block transaction limit reached")
|
|
273
|
-
|
|
274
|
-
# --- unpack tx -----------------------------------------------------
|
|
275
|
-
sender_pk = tx.get_sender_pk()
|
|
276
|
-
recip_pk = tx.get_recipient_pk()
|
|
277
|
-
amount = tx.get_amount()
|
|
278
|
-
fee = tx.get_fee()
|
|
279
|
-
nonce = tx.get_nonce()
|
|
280
|
-
|
|
281
|
-
sender_acct = self.accounts.get_account(sender_pk)
|
|
282
|
-
if (sender_acct is None
|
|
283
|
-
or sender_acct.counter != nonce
|
|
284
|
-
or sender_acct.balance < amount + fee):
|
|
285
|
-
raise ValueError("invalid or unaffordable transaction")
|
|
286
|
-
|
|
287
|
-
# --- debit sender --------------------------------------------------
|
|
288
|
-
self.accounts.set_account(
|
|
289
|
-
sender_pk,
|
|
290
|
-
Account.create(
|
|
291
|
-
balance=sender_acct.balance - amount - fee,
|
|
292
|
-
data=sender_acct.data,
|
|
293
|
-
counter=sender_acct.counter + 1,
|
|
294
|
-
)
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
# --- destination handling -----------------------------------------
|
|
298
|
-
if recip_pk == TREASURY:
|
|
299
|
-
treasury = self.accounts.get_account(TREASURY)
|
|
300
|
-
|
|
301
|
-
trie = PatriciaTrie(node_get=None, root_hash=treasury.data)
|
|
302
|
-
stake_bytes = trie.get(sender_pk) or b""
|
|
303
|
-
current_stake = int.from_bytes(stake_bytes, "big") if stake_bytes else 0
|
|
304
|
-
|
|
305
|
-
if amount > 0:
|
|
306
|
-
# stake **deposit**
|
|
307
|
-
trie.put(sender_pk, (current_stake + amount).to_bytes(32, "big"))
|
|
308
|
-
new_treas_bal = treasury.balance + amount
|
|
309
|
-
else:
|
|
310
|
-
# stake **withdrawal**
|
|
311
|
-
if current_stake == 0:
|
|
312
|
-
raise ValueError("no stake to withdraw")
|
|
313
|
-
# move stake back to sender balance
|
|
314
|
-
sender_after = self.accounts.get_account(sender_pk)
|
|
315
|
-
self.accounts.set_account(
|
|
316
|
-
sender_pk,
|
|
317
|
-
Account.create(
|
|
318
|
-
balance=sender_after.balance + current_stake,
|
|
319
|
-
data=sender_after.data,
|
|
320
|
-
counter=sender_after.counter,
|
|
321
|
-
)
|
|
322
|
-
)
|
|
323
|
-
trie.delete(sender_pk)
|
|
324
|
-
new_treas_bal = treasury.balance # treasury balance unchanged
|
|
325
|
-
|
|
326
|
-
# write back treasury with new trie root
|
|
327
|
-
self.accounts.set_account(
|
|
328
|
-
TREASURY,
|
|
329
|
-
Account.create(
|
|
330
|
-
balance=new_treas_bal,
|
|
331
|
-
data=trie.root_hash,
|
|
332
|
-
counter=treasury.counter,
|
|
333
|
-
)
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
else:
|
|
337
|
-
recip_acct = self.accounts.get_account(recip_pk) or Account.create(0, b"", 0)
|
|
338
|
-
self.accounts.set_account(
|
|
339
|
-
recip_pk,
|
|
340
|
-
Account.create(
|
|
341
|
-
balance=recip_acct.balance + amount,
|
|
342
|
-
data=recip_acct.data,
|
|
343
|
-
counter=recip_acct.counter,
|
|
344
|
-
)
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
# --- accumulate fee & record --------------------------------------
|
|
348
|
-
self.total_fees += fee
|
|
349
|
-
self.tx_hashes.append(tx.hash)
|
|
350
|
-
self.transactions_count += 1
|
|
351
|
-
|
|
352
|
-
def validate_block(self, remote_get_fn) -> bool:
|
|
353
|
-
NAT = 0.618
|
|
354
|
-
_i2b = lambda i: i.to_bytes((i.bit_length() + 7) // 8 or 1, "big")
|
|
355
|
-
|
|
356
|
-
# ---------- 1. block-hash & signature -----------------------------
|
|
357
|
-
blk_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.hash)
|
|
358
|
-
body_root = blk_mt.get(0); sig = blk_mt.get(1)
|
|
359
|
-
ed25519.verify_signature(public_key=self.validator_pk, message=body_root, signature=sig)
|
|
360
|
-
|
|
361
|
-
# ---------- 2. rebuild body_root from fields ----------------------
|
|
362
|
-
f_names = (
|
|
363
|
-
"accounts_hash","block_time","delay_difficulty","delay_output","delay_proof",
|
|
364
|
-
"number","prev_block_hash","timestamp","transaction_limit",
|
|
365
|
-
"transactions_count","transactions_hash","transactions_total_fees",
|
|
366
|
-
"validator_pk",
|
|
367
|
-
)
|
|
368
|
-
leaves = [
|
|
369
|
-
v if isinstance(v := self.get_field(n), bytes) else _i2b(v)
|
|
370
|
-
for n in sorted(f_names)
|
|
371
|
-
]
|
|
372
|
-
if MerkleTree.from_leaves(leaves).root_hash != body_root:
|
|
373
|
-
raise ValueError("body root mismatch")
|
|
374
|
-
|
|
375
|
-
# ---------- 3. previous block header & VDF ------------------------
|
|
376
|
-
prev_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.prev_block_hash)
|
|
377
|
-
prev_body_root, prev_sig = prev_mt.get(0), prev_mt.get(1)
|
|
378
|
-
prev_body_mt = MerkleTree(node_get=remote_get_fn, root_hash=prev_body_root)
|
|
379
|
-
prev_blk = Block(block_hash=self.prev_block_hash,
|
|
380
|
-
body_tree=prev_body_mt, signature=prev_sig)
|
|
381
|
-
prev_out = prev_blk.get_field("delay_output")
|
|
382
|
-
prev_diff = prev_blk.get_field("delay_difficulty")
|
|
383
|
-
prev_bt = prev_blk.get_field("block_time")
|
|
384
|
-
prev_limit = prev_blk.get_field("transaction_limit")
|
|
385
|
-
prev_cnt = prev_blk.get_field("transactions_count")
|
|
386
|
-
|
|
387
|
-
if not vdf_verify(prev_out, self.delay_output, self.delay_proof,
|
|
388
|
-
T=self.delay_difficulty, D=-4):
|
|
389
|
-
raise ValueError("bad VDF proof")
|
|
390
|
-
|
|
391
|
-
# ---------- 4. replay all txs -------------------------------------
|
|
392
|
-
accs = Accounts(root_hash=prev_blk.get_field("accounts_hash"),
|
|
393
|
-
node_get=remote_get_fn)
|
|
394
|
-
tx_mt = MerkleTree(node_get=remote_get_fn,
|
|
395
|
-
root_hash=self.transactions_hash)
|
|
396
|
-
if tx_mt.leaf_count() != self.transactions_count:
|
|
397
|
-
raise ValueError("transactions_count mismatch")
|
|
398
|
-
|
|
399
|
-
dummy = Block(block_hash=b"", accounts=accs,
|
|
400
|
-
accounts_hash=accs.root_hash,
|
|
401
|
-
transaction_limit=prev_limit)
|
|
402
|
-
for i in range(self.transactions_count):
|
|
403
|
-
h = tx_mt.get(i)
|
|
404
|
-
tm = MerkleTree(node_get=remote_get_fn, root_hash=h)
|
|
405
|
-
tx = Transaction(h, tree=tm, node_get=remote_get_fn)
|
|
406
|
-
dummy.apply_tx(tx)
|
|
407
|
-
|
|
408
|
-
# fee split identical to build()
|
|
409
|
-
burn = dummy.total_fees // 2
|
|
410
|
-
rew = dummy.total_fees - burn
|
|
411
|
-
if burn:
|
|
412
|
-
dummy.accounts.set_account(
|
|
413
|
-
b"\x00"*32,
|
|
414
|
-
Account.create(burn, b"", 0)
|
|
415
|
-
)
|
|
416
|
-
if rew:
|
|
417
|
-
v_acct = dummy.accounts.get_account(self.validator_pk) or Account.create(0,b"",0)
|
|
418
|
-
dummy.accounts.set_account(
|
|
419
|
-
self.validator_pk,
|
|
420
|
-
Account.create(v_acct.balance+rew, v_acct.data, v_acct.counter)
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
if dummy.accounts.root_hash != self.accounts_hash:
|
|
424
|
-
raise ValueError("accounts_hash mismatch")
|
|
425
|
-
|
|
426
|
-
# ---------- 5. natural-rate rules --------------------------------
|
|
427
|
-
grow_thr = prev_limit * NAT
|
|
428
|
-
shrink_thr = prev_cnt * NAT
|
|
429
|
-
expect_lim = prev_cnt if prev_cnt > grow_thr \
|
|
430
|
-
else max(1, int(prev_limit * NAT)) if prev_cnt < shrink_thr \
|
|
431
|
-
else prev_limit
|
|
432
|
-
if self.transaction_limit != expect_lim:
|
|
433
|
-
raise ValueError("tx-limit rule")
|
|
434
|
-
|
|
435
|
-
expect_diff = max(1, int(prev_diff / NAT)) if prev_bt <= 1 \
|
|
436
|
-
else max(1, int(prev_diff * NAT))
|
|
437
|
-
if self.delay_difficulty != expect_diff:
|
|
438
|
-
raise ValueError("difficulty rule")
|
|
439
|
-
|
|
440
|
-
return True
|
|
441
|
-
|
astreum/models/merkle.py
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Dict, List, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import blake3
|
|
6
|
-
from ..format import encode, decode
|
|
7
|
-
|
|
8
|
-
class MerkleNode:
|
|
9
|
-
def __init__(
|
|
10
|
-
self,
|
|
11
|
-
left: Optional[bytes],
|
|
12
|
-
right: Optional[bytes],
|
|
13
|
-
value: Optional[bytes],
|
|
14
|
-
) -> None:
|
|
15
|
-
self.left = left
|
|
16
|
-
self.right = right
|
|
17
|
-
self.value = value
|
|
18
|
-
self._hash: Optional[bytes] = None
|
|
19
|
-
|
|
20
|
-
def to_bytes(self) -> bytes:
|
|
21
|
-
return encode([self.left, self.right, self.value])
|
|
22
|
-
|
|
23
|
-
@classmethod
|
|
24
|
-
def from_bytes(cls, blob: bytes) -> "MerkleNode":
|
|
25
|
-
left, right, value = decode(blob)
|
|
26
|
-
return cls(left, right, value)
|
|
27
|
-
|
|
28
|
-
def _compute_hash(self) -> bytes:
|
|
29
|
-
if self.value is not None:
|
|
30
|
-
return blake3.blake3(self.value).digest()
|
|
31
|
-
left = self.left or b""
|
|
32
|
-
right = self.right or b""
|
|
33
|
-
return blake3.blake3(left + right).digest()
|
|
34
|
-
|
|
35
|
-
def hash(self) -> bytes:
|
|
36
|
-
if self._hash is None:
|
|
37
|
-
self._hash = self._compute_hash()
|
|
38
|
-
return self._hash
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class MerkleTree:
|
|
42
|
-
def __init__(
|
|
43
|
-
self,
|
|
44
|
-
global_get_fn: Callable[[bytes], Optional[bytes]],
|
|
45
|
-
root_hash: Optional[bytes] = None,
|
|
46
|
-
height: Optional[int] = None,
|
|
47
|
-
) -> None:
|
|
48
|
-
self._global_get_fn = global_get_fn
|
|
49
|
-
self.nodes: Dict[bytes, MerkleNode] = {}
|
|
50
|
-
self.root_hash = root_hash
|
|
51
|
-
self._height: Optional[int] = height
|
|
52
|
-
|
|
53
|
-
@classmethod
|
|
54
|
-
def from_leaves(
|
|
55
|
-
cls,
|
|
56
|
-
leaves: List[bytes],
|
|
57
|
-
global_get_fn: Callable[[bytes], Optional[bytes]] | None = None,
|
|
58
|
-
) -> "MerkleTree":
|
|
59
|
-
if not leaves:
|
|
60
|
-
raise ValueError("must supply at least one leaf")
|
|
61
|
-
|
|
62
|
-
global_get_fn = global_get_fn or (lambda _h: None)
|
|
63
|
-
tree = cls(global_get_fn=global_get_fn)
|
|
64
|
-
|
|
65
|
-
# Step 1 – create leaf nodes list[bytes]
|
|
66
|
-
level_hashes: List[bytes] = []
|
|
67
|
-
for val in leaves:
|
|
68
|
-
leaf = MerkleNode(None, None, val)
|
|
69
|
-
h = leaf.hash()
|
|
70
|
-
tree.nodes[h] = leaf
|
|
71
|
-
level_hashes.append(h)
|
|
72
|
-
|
|
73
|
-
height = 1 # current level (leaves)
|
|
74
|
-
|
|
75
|
-
# Step 2 – build upper levels until single root remains
|
|
76
|
-
while len(level_hashes) > 1:
|
|
77
|
-
next_level: List[bytes] = []
|
|
78
|
-
it = iter(level_hashes)
|
|
79
|
-
for left_hash in it:
|
|
80
|
-
try:
|
|
81
|
-
right_hash = next(it)
|
|
82
|
-
except StopIteration:
|
|
83
|
-
right_hash = None
|
|
84
|
-
parent = MerkleNode(left_hash, right_hash, None)
|
|
85
|
-
ph = parent.hash()
|
|
86
|
-
tree.nodes[ph] = parent
|
|
87
|
-
next_level.append(ph)
|
|
88
|
-
level_hashes = next_level
|
|
89
|
-
height += 1
|
|
90
|
-
|
|
91
|
-
tree.root_hash = level_hashes[0]
|
|
92
|
-
tree._height = height
|
|
93
|
-
return tree
|
|
94
|
-
|
|
95
|
-
def _fetch(self, h: bytes | None) -> Optional[MerkleNode]:
|
|
96
|
-
if h is None:
|
|
97
|
-
return None
|
|
98
|
-
node = self.nodes.get(h)
|
|
99
|
-
if node is None:
|
|
100
|
-
raw = self._global_get_fn(h)
|
|
101
|
-
if raw is None:
|
|
102
|
-
return None
|
|
103
|
-
node = MerkleNode.from_bytes(raw)
|
|
104
|
-
self.nodes[h] = node
|
|
105
|
-
return node
|
|
106
|
-
|
|
107
|
-
def _invalidate(self, node: MerkleNode) -> None:
|
|
108
|
-
node._hash = None
|
|
109
|
-
|
|
110
|
-
def _ensure_height(self) -> None:
|
|
111
|
-
if self._height is None:
|
|
112
|
-
h = 0
|
|
113
|
-
nh = self.root_hash
|
|
114
|
-
while nh is not None:
|
|
115
|
-
node = self._fetch(nh)
|
|
116
|
-
nh = node.left if node and node.value is None else None
|
|
117
|
-
h += 1
|
|
118
|
-
self._height = h or 1
|
|
119
|
-
|
|
120
|
-
def _capacity(self) -> int:
|
|
121
|
-
self._ensure_height()
|
|
122
|
-
assert self._height is not None
|
|
123
|
-
return 1 << (self._height - 1)
|
|
124
|
-
|
|
125
|
-
def _path_bits(self, index: int) -> List[int]:
|
|
126
|
-
self._ensure_height()
|
|
127
|
-
assert self._height is not None
|
|
128
|
-
bits = []
|
|
129
|
-
for shift in range(self._height - 2, -1, -1):
|
|
130
|
-
bits.append((index >> shift) & 1)
|
|
131
|
-
return bits
|
|
132
|
-
|
|
133
|
-
# ------------------------------------------------------------------
|
|
134
|
-
# get / put
|
|
135
|
-
# ------------------------------------------------------------------
|
|
136
|
-
def get(self, index: int) -> Optional[bytes]:
|
|
137
|
-
if index < 0 or self.root_hash is None or index >= self._capacity():
|
|
138
|
-
return None
|
|
139
|
-
|
|
140
|
-
node_hash = self.root_hash
|
|
141
|
-
for bit in self._path_bits(index):
|
|
142
|
-
node = self._fetch(node_hash)
|
|
143
|
-
if node is None:
|
|
144
|
-
return None
|
|
145
|
-
node_hash = node.right if bit else node.left
|
|
146
|
-
if node_hash is None:
|
|
147
|
-
return None
|
|
148
|
-
leaf = self._fetch(node_hash)
|
|
149
|
-
return leaf.value if leaf else None
|
|
150
|
-
|
|
151
|
-
def put(self, index: int, value: bytes) -> None:
|
|
152
|
-
# 1 . input validation
|
|
153
|
-
if index < 0:
|
|
154
|
-
raise IndexError("negative index")
|
|
155
|
-
if self.root_hash is None:
|
|
156
|
-
raise IndexError("tree is empty – build it first with from_leaves()")
|
|
157
|
-
if index >= self._capacity():
|
|
158
|
-
raise IndexError("index beyond tree capacity")
|
|
159
|
-
|
|
160
|
-
# 2 . walk down to the target leaf
|
|
161
|
-
node_hash = self.root_hash
|
|
162
|
-
stack: List[Tuple[MerkleNode, bytes, bool]] = []
|
|
163
|
-
for bit in self._path_bits(index):
|
|
164
|
-
node = self._fetch(node_hash)
|
|
165
|
-
if node is None:
|
|
166
|
-
raise IndexError("missing node along path")
|
|
167
|
-
went_right = bool(bit)
|
|
168
|
-
child_hash = node.right if went_right else node.left
|
|
169
|
-
if child_hash is None:
|
|
170
|
-
raise IndexError("path leads into non-existent branch")
|
|
171
|
-
stack.append((node, node.hash(), went_right))
|
|
172
|
-
node_hash = child_hash
|
|
173
|
-
|
|
174
|
-
# 3 . update the leaf
|
|
175
|
-
leaf = self._fetch(node_hash)
|
|
176
|
-
if leaf is None or leaf.value is None:
|
|
177
|
-
raise IndexError("target leaf missing")
|
|
178
|
-
|
|
179
|
-
old_hash = leaf.hash()
|
|
180
|
-
leaf.value = value
|
|
181
|
-
self._invalidate(leaf)
|
|
182
|
-
new_hash = leaf.hash()
|
|
183
|
-
|
|
184
|
-
if new_hash != old_hash:
|
|
185
|
-
self.nodes.pop(old_hash, None)
|
|
186
|
-
self.nodes[new_hash] = leaf
|
|
187
|
-
|
|
188
|
-
# 4 . bubble the change up
|
|
189
|
-
for parent, old_hash, went_right in reversed(stack):
|
|
190
|
-
if went_right:
|
|
191
|
-
parent.right = new_hash
|
|
192
|
-
else:
|
|
193
|
-
parent.left = new_hash
|
|
194
|
-
|
|
195
|
-
self._invalidate(parent)
|
|
196
|
-
new_hash = parent.hash()
|
|
197
|
-
|
|
198
|
-
if new_hash != old_hash:
|
|
199
|
-
self.nodes.pop(old_hash, None)
|
|
200
|
-
self.nodes[new_hash] = parent
|
|
201
|
-
|
|
202
|
-
# 5 . finalise the new root
|
|
203
|
-
self.root_hash = new_hash
|
|
204
|
-
|
|
205
|
-
|