astreum 0.2.24__py3-none-any.whl → 0.2.26__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.

Potentially problematic release.


This version of astreum might be problematic. Click here for more details.

@@ -109,7 +109,7 @@ def deserialize(data: bytes, D: int) -> QuadraticForm:
109
109
 
110
110
  # --- Public VDF API -----------------------------------------------------
111
111
 
112
- def generate(
112
+ def vdf_generate(
113
113
  old_output: bytes,
114
114
  T: int,
115
115
  D: int
@@ -136,7 +136,7 @@ def generate(
136
136
  return y_bytes, proof_bytes
137
137
 
138
138
 
139
- def verify(
139
+ def vdf_verify(
140
140
  old_output: bytes,
141
141
  new_output: bytes,
142
142
  proof: bytes,
astreum/models/block.py CHANGED
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from threading import Thread
3
4
  from typing import List, Dict, Any, Optional, Union
4
5
 
6
+ from astreum.crypto.wesolowski import vdf_generate, vdf_verify
5
7
  from astreum.models.account import Account
6
8
  from astreum.models.accounts import Accounts
7
9
  from astreum.models.patricia import PatriciaTrie
@@ -84,123 +86,162 @@ class Block:
84
86
  return int.from_bytes(leaf_bytes, "big")
85
87
  return leaf_bytes
86
88
 
87
- def verify_block_signature(self) -> bool:
88
- """Verify the block's Ed25519 signature against its body root."""
89
- pub = ed25519.Ed25519PublicKey.from_public_bytes(
90
- self.get_field("validator_pk")
91
- )
92
- try:
93
- pub.verify(self.get_signature(), self.get_body_hash())
94
- return True
95
- except Exception:
96
- return False
97
-
98
89
  @classmethod
99
90
  def genesis(cls, validator_addr: bytes) -> "Block":
100
- # 1. validator-stakes sub-trie
91
+ # 1. validator-stakes sub-trie
101
92
  stake_trie = PatriciaTrie()
102
93
  stake_trie.put(validator_addr, (1).to_bytes(32, "big"))
103
94
  stake_root = stake_trie.root_hash
104
95
 
105
- # 2. build the two Account bodies
106
- validator_acct = Account.create(balance=0, data=b"", nonce=0)
96
+ # 2. three Account bodies
97
+ validator_acct = Account.create(balance=0, data=b"", nonce=0)
107
98
  treasury_acct = Account.create(balance=1, data=stake_root, nonce=0)
99
+ burn_acct = Account.create(balance=0, data=b"", nonce=0)
108
100
 
109
- # 3. global Accounts structure
101
+ # 3. global Accounts structure
110
102
  accts = Accounts()
111
103
  accts.set_account(validator_addr, validator_acct)
112
104
  accts.set_account(b"\x11" * 32, treasury_acct)
105
+ accts.set_account(b"\x00" * 32, burn_acct)
113
106
  accounts_hash = accts.root_hash
114
107
 
115
- # 4. constant body fields for genesis
108
+ # 4. constant body fields for genesis
116
109
  body_kwargs = dict(
110
+ block_hash = b"",
117
111
  number = 0,
118
112
  prev_block_hash = b"\x00" * 32,
119
113
  timestamp = 0,
114
+ block_time = 0,
120
115
  accounts_hash = accounts_hash,
116
+ accounts = accts,
121
117
  transactions_total_fees = 0,
122
- transaction_limit = 0,
118
+ transaction_limit = 1,
123
119
  transactions_root_hash = b"\x00" * 32,
124
- delay_difficulty = 0,
120
+ transactions_count = 0,
121
+ delay_difficulty = 1,
125
122
  delay_output = b"",
126
123
  delay_proof = b"",
127
124
  validator_pk = validator_addr,
128
125
  signature = b"",
129
126
  )
130
127
 
131
- # 5. build and return the block
128
+ # 5. build and return the block
132
129
  return cls.create(**body_kwargs)
133
130
 
134
131
  @classmethod
135
132
  def build(
136
133
  cls,
137
134
  previous_block: "Block",
138
- transactions: list[Transaction],
135
+ transactions: List[Transaction],
139
136
  *,
140
- validator_pk: bytes,
137
+ validator_sk,
141
138
  natural_rate: float = 0.618,
142
139
  ) -> "Block":
143
140
  BURN = b"\x00" * 32
144
141
 
145
- # --- 0. create an empty block-in-progress, seeded with parent fields ----
146
142
  blk = cls(
147
- block_hash=b"", # placeholder; set at the end
143
+ block_hash=b"",
148
144
  number=previous_block.number + 1,
149
145
  prev_block_hash=previous_block.hash,
150
146
  timestamp=previous_block.timestamp + 1,
151
147
  accounts_hash=previous_block.accounts_hash,
152
148
  transaction_limit=previous_block.transaction_limit,
153
149
  transactions_count=0,
150
+ validator_pk=validator_sk.public_key().public_bytes(),
154
151
  )
155
152
 
156
- # --- 1. apply up to transaction_limit txs -------------------------------
153
+ # ------------------ difficulty via natural_rate -----------------------
154
+ prev_bt = previous_block.block_time or 0
155
+ prev_diff = previous_block.delay_difficulty or 1
156
+ if prev_bt <= 1:
157
+ blk.delay_difficulty = max(1, int(prev_diff / natural_rate)) # increase
158
+ else:
159
+ blk.delay_difficulty = max(1, int(prev_diff * natural_rate)) # decrease
160
+
161
+ # ------------------ launch VDF in background --------------------------
162
+ vdf_result: dict[str, bytes] = {}
163
+
164
+ def _vdf_worker():
165
+ y, p = vdf_generate(previous_block.delay_output, blk.delay_difficulty, -4)
166
+ vdf_result["y"] = y
167
+ vdf_result["p"] = p
168
+
169
+ Thread(target=_vdf_worker, daemon=True).start()
170
+
171
+ # ------------------ process transactions -----------------------------
157
172
  for tx in transactions:
158
173
  try:
159
- blk.apply_tx(tx) # ← NEW single-line call
174
+ blk.apply_tx(tx)
160
175
  except ValueError:
161
- break # stop at first invalid or cap reached
176
+ break
162
177
 
163
- # --- 2. split fees after all txs ----------------------------------------
178
+ # ------------------ split fees --------------------------------------
164
179
  burn_amt = blk.total_fees // 2
165
180
  reward_amt = blk.total_fees - burn_amt
181
+
182
+ def _credit(addr: bytes, amt: int):
183
+ acc = blk.accounts.get_account(addr) or Account.create(0, b"", 0)
184
+ blk.accounts.set_account(addr, Account.create(acc.balance() + amt, acc.data(), acc.nonce()))
185
+
166
186
  if burn_amt:
167
- blk.accounts.set_account(
168
- BURN,
169
- Account.create(
170
- balance=(blk.accounts.get_account(BURN) or Account.create(0, b"", 0)).balance() + burn_amt,
171
- data=b"",
172
- nonce=0,
173
- ),
174
- )
187
+ _credit(BURN, burn_amt)
175
188
  if reward_amt:
176
- blk.accounts.set_account(
177
- validator_pk,
178
- Account.create(
179
- balance=(blk.accounts.get_account(validator_pk) or Account.create(0, b"", 0)).balance() + reward_amt,
180
- data=b"",
181
- nonce=0,
182
- ),
183
- )
189
+ _credit(blk.validator_pk, reward_amt)
184
190
 
185
- # --- 3. recalc tx-limit via prev metrics -------------------------------
191
+ # ------------------ update tx limit with natural_rate ---------------
186
192
  prev_limit = previous_block.transaction_limit
187
193
  prev_tx_count = previous_block.transactions_count
188
- threshold = prev_limit * natural_rate
189
- if prev_tx_count > threshold:
194
+ grow_thr = prev_limit * natural_rate
195
+ shrink_thr = prev_tx_count * natural_rate
196
+
197
+ if prev_tx_count > grow_thr:
190
198
  blk.transaction_limit = prev_tx_count
191
- elif prev_tx_count < threshold:
199
+ elif prev_tx_count < shrink_thr:
192
200
  blk.transaction_limit = max(1, int(prev_limit * natural_rate))
193
201
  else:
194
202
  blk.transaction_limit = prev_limit
195
203
 
196
- # --- 4. finalise block hash & header roots ------------------------------
204
+ # ------------------ wait for VDF ------------------------------------
205
+ while "y" not in vdf_result:
206
+ pass
207
+ blk.delay_output = vdf_result["y"]
208
+ blk.delay_proof = vdf_result["p"]
209
+
210
+ # ------------------ timing & roots ----------------------------------
211
+ blk.block_time = blk.timestamp - previous_block.timestamp
197
212
  blk.accounts_hash = blk.accounts.root_hash
198
213
  blk.transactions_root_hash = MerkleTree.from_leaves(blk.tx_hashes).root_hash
199
- blk.hash = MerkleTree.from_leaves([
200
- blk.transactions_root_hash,
201
- blk.accounts_hash,
202
- blk.total_fees.to_bytes(8, "big"),
203
- ]).root_hash # or your existing body-root/signing scheme
214
+ blk.transactions_total_fees = blk.total_fees
215
+
216
+ # ------------------ build full body root ----------------------------
217
+ body_fields = {
218
+ "accounts_hash": blk.accounts_hash,
219
+ "block_time": blk.block_time,
220
+ "delay_difficulty": blk.delay_difficulty,
221
+ "delay_output": blk.delay_output,
222
+ "delay_proof": blk.delay_proof,
223
+ "number": blk.number,
224
+ "prev_block_hash": blk.prev_block_hash,
225
+ "timestamp": blk.timestamp,
226
+ "transaction_limit": blk.transaction_limit,
227
+ "transactions_count": blk.transactions_count,
228
+ "transactions_root_hash": blk.transactions_root_hash,
229
+ "transactions_total_fees": blk.transactions_total_fees,
230
+ "validator_pk": blk.validator_pk,
231
+ }
232
+
233
+ leaves: List[bytes] = []
234
+ for k in sorted(body_fields):
235
+ v = body_fields[k]
236
+ if isinstance(v, bytes):
237
+ leaves.append(v)
238
+ else:
239
+ leaves.append(int(v).to_bytes((v.bit_length() + 7) // 8 or 1, "big"))
240
+
241
+ body_root = MerkleTree.from_leaves(leaves).root_hash
242
+ blk.body_tree = MerkleTree.from_leaves([body_root])
243
+ blk.signature = validator_sk.sign(body_root)
244
+ blk.hash = MerkleTree.from_leaves([body_root, blk.signature]).root_hash
204
245
 
205
246
  return blk
206
247
 
@@ -298,3 +339,94 @@ class Block:
298
339
  self.total_fees += fee
299
340
  self.tx_hashes.append(tx.hash)
300
341
  self.transactions_count += 1
342
+
343
+ def validate_block(self, remote_get_fn) -> bool:
344
+ NAT = 0.618
345
+ _i2b = lambda i: i.to_bytes((i.bit_length() + 7) // 8 or 1, "big")
346
+
347
+ # ---------- 1. block-hash & signature -----------------------------
348
+ blk_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.hash)
349
+ body_root = blk_mt.get(0); sig = blk_mt.get(1)
350
+ ed25519.verify_signature(public_key=self.validator_pk, message=body_root, signature=sig)
351
+
352
+ # ---------- 2. rebuild body_root from fields ----------------------
353
+ f_names = (
354
+ "accounts_hash","block_time","delay_difficulty","delay_output","delay_proof",
355
+ "number","prev_block_hash","timestamp","transaction_limit",
356
+ "transactions_count","transactions_root_hash","transactions_total_fees",
357
+ "validator_pk",
358
+ )
359
+ leaves = [
360
+ v if isinstance(v := self.get_field(n), bytes) else _i2b(v)
361
+ for n in sorted(f_names)
362
+ ]
363
+ if MerkleTree.from_leaves(leaves).root_hash != body_root:
364
+ raise ValueError("body root mismatch")
365
+
366
+ # ---------- 3. previous block header & VDF ------------------------
367
+ prev_mt = MerkleTree(node_get=remote_get_fn, root_hash=self.prev_block_hash)
368
+ prev_body_root, prev_sig = prev_mt.get(0), prev_mt.get(1)
369
+ prev_body_mt = MerkleTree(node_get=remote_get_fn, root_hash=prev_body_root)
370
+ prev_blk = Block(block_hash=self.prev_block_hash,
371
+ body_tree=prev_body_mt, signature=prev_sig)
372
+ prev_out = prev_blk.get_field("delay_output")
373
+ prev_diff = prev_blk.get_field("delay_difficulty")
374
+ prev_bt = prev_blk.get_field("block_time")
375
+ prev_limit = prev_blk.get_field("transaction_limit")
376
+ prev_cnt = prev_blk.get_field("transactions_count")
377
+
378
+ if not vdf_verify(prev_out, self.delay_output, self.delay_proof,
379
+ T=self.delay_difficulty, D=-4):
380
+ raise ValueError("bad VDF proof")
381
+
382
+ # ---------- 4. replay all txs -------------------------------------
383
+ accs = Accounts(root_hash=prev_blk.get_field("accounts_hash"),
384
+ node_get=remote_get_fn)
385
+ tx_mt = MerkleTree(node_get=remote_get_fn,
386
+ root_hash=self.transactions_root_hash)
387
+ if tx_mt.leaf_count() != self.transactions_count:
388
+ raise ValueError("transactions_count mismatch")
389
+
390
+ dummy = Block(block_hash=b"", accounts=accs,
391
+ accounts_hash=accs.root_hash,
392
+ transaction_limit=prev_limit)
393
+ for i in range(self.transactions_count):
394
+ h = tx_mt.get(i)
395
+ tm = MerkleTree(node_get=remote_get_fn, root_hash=h)
396
+ tx = Transaction(h, tree=tm, node_get=remote_get_fn)
397
+ dummy.apply_tx(tx)
398
+
399
+ # fee split identical to build()
400
+ burn = dummy.total_fees // 2
401
+ rew = dummy.total_fees - burn
402
+ if burn:
403
+ dummy.accounts.set_account(
404
+ b"\x00"*32,
405
+ Account.create(burn, b"", 0)
406
+ )
407
+ if rew:
408
+ v_acct = dummy.accounts.get_account(self.validator_pk) or Account.create(0,b"",0)
409
+ dummy.accounts.set_account(
410
+ self.validator_pk,
411
+ Account.create(v_acct.balance()+rew, v_acct.data(), v_acct.nonce())
412
+ )
413
+
414
+ if dummy.accounts.root_hash != self.accounts_hash:
415
+ raise ValueError("accounts_hash mismatch")
416
+
417
+ # ---------- 5. natural-rate rules --------------------------------
418
+ grow_thr = prev_limit * NAT
419
+ shrink_thr = prev_cnt * NAT
420
+ expect_lim = prev_cnt if prev_cnt > grow_thr \
421
+ else max(1, int(prev_limit * NAT)) if prev_cnt < shrink_thr \
422
+ else prev_limit
423
+ if self.transaction_limit != expect_lim:
424
+ raise ValueError("tx-limit rule")
425
+
426
+ expect_diff = max(1, int(prev_diff / NAT)) if prev_bt <= 1 \
427
+ else max(1, int(prev_diff * NAT))
428
+ if self.delay_difficulty != expect_diff:
429
+ raise ValueError("difficulty rule")
430
+
431
+ return True
432
+
astreum/models/merkle.py CHANGED
@@ -41,11 +41,11 @@ class MerkleNode:
41
41
  class MerkleTree:
42
42
  def __init__(
43
43
  self,
44
- node_get: Callable[[bytes], Optional[bytes]],
44
+ global_get_fn: Callable[[bytes], Optional[bytes]],
45
45
  root_hash: Optional[bytes] = None,
46
46
  height: Optional[int] = None,
47
47
  ) -> None:
48
- self._node_get = node_get
48
+ self._global_get_fn = global_get_fn
49
49
  self.nodes: Dict[bytes, MerkleNode] = {}
50
50
  self.root_hash = root_hash
51
51
  self._height: Optional[int] = height
@@ -54,13 +54,13 @@ class MerkleTree:
54
54
  def from_leaves(
55
55
  cls,
56
56
  leaves: List[bytes],
57
- node_get: Callable[[bytes], Optional[bytes]] | None = None,
57
+ global_get_fn: Callable[[bytes], Optional[bytes]] | None = None,
58
58
  ) -> "MerkleTree":
59
59
  if not leaves:
60
60
  raise ValueError("must supply at least one leaf")
61
61
 
62
- node_get = node_get or (lambda _h: None)
63
- tree = cls(node_get=node_get)
62
+ global_get_fn = global_get_fn or (lambda _h: None)
63
+ tree = cls(global_get_fn=global_get_fn)
64
64
 
65
65
  # Step 1 – create leaf nodes list[bytes]
66
66
  level_hashes: List[bytes] = []
@@ -97,7 +97,7 @@ class MerkleTree:
97
97
  return None
98
98
  node = self.nodes.get(h)
99
99
  if node is None:
100
- raw = self._node_get(h)
100
+ raw = self._global_get_fn(h)
101
101
  if raw is None:
102
102
  return None
103
103
  node = MerkleNode.from_bytes(raw)
@@ -27,14 +27,14 @@ class Transaction:
27
27
  tx_hash: bytes,
28
28
  *,
29
29
  tree: Optional[MerkleTree] = None,
30
- get_node_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
30
+ global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
31
31
  ) -> None:
32
32
  self._hash = tx_hash
33
33
  self._tree = tree
34
34
  self._field_cache: Dict[str, Union[int, bytes]] = {}
35
35
 
36
- if self._tree and get_node_fn:
37
- self._tree.set_external_node_fetcher(get_node_fn)
36
+ if self._tree and global_get_fn:
37
+ self._tree.global_get_fn = global_get_fn
38
38
 
39
39
  @classmethod
40
40
  def create(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.24
3
+ Version: 0.2.26
4
4
  Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
5
5
  Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
6
6
  Project-URL: Homepage, https://github.com/astreum/lib
@@ -4,7 +4,7 @@ astreum/node.py,sha256=dPloCXuDyIn3-KDqxlgl3jxsonJlFMLi_quwJRsoLC8,46259
4
4
  astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
6
6
  astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
7
- astreum/crypto/wesolowski.py,sha256=FDAX82L5cceR6DGTtUO57ZhcxpBNiskGrnLWnd_3BSw,4084
7
+ astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
8
8
  astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
9
9
  astreum/lispeum/__init__.py,sha256=K-NDzIjtIsXzC9X7lnYvlvIaVxjFcY7WNsgLIE3DH3U,58
10
10
  astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
@@ -12,12 +12,12 @@ astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,
12
12
  astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  astreum/models/account.py,sha256=sHujGSwtV13rvOGJ5LZXuMrJ4F9XUdvyuWKz-zJ9lkE,2986
14
14
  astreum/models/accounts.py,sha256=aFSEWlq6zRf65-KGAdNGqEJyNVY3fpKhx8y1vU6sgSc,1164
15
- astreum/models/block.py,sha256=mqHpsN4-aDyW9Q9emFBVIEyNvLAqRJZbqCN-4vS4Hw0,11795
16
- astreum/models/merkle.py,sha256=merV3rx2iRfzvglV6gNusrJf7OMbcVV854T-DUWCC64,6733
15
+ astreum/models/block.py,sha256=-5j7uO0woVtNi0h52__e7AxpDQSVhzKUhr6Qc-2xZsE,17870
16
+ astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
17
17
  astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
18
- astreum/models/transaction.py,sha256=yBarvRK2ybMAHHEQPZpubGO7gms4U9k093xQGNQHQ4Q,3043
19
- astreum-0.2.24.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
20
- astreum-0.2.24.dist-info/METADATA,sha256=bcrWSsFCFwM884WECJ9THr12tMUS8MAG3VwLyaCEosg,5478
21
- astreum-0.2.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- astreum-0.2.24.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
23
- astreum-0.2.24.dist-info/RECORD,,
18
+ astreum/models/transaction.py,sha256=MkLL5YX18kIf9-O4LBaZ4eWjkXDAaYIrDcDehbDZoqg,3038
19
+ astreum-0.2.26.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
20
+ astreum-0.2.26.dist-info/METADATA,sha256=fUG5PepefY7Cjfp5Gj5GwtbYF369bARQAcl0A4OJiQ0,5478
21
+ astreum-0.2.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ astreum-0.2.26.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
23
+ astreum-0.2.26.dist-info/RECORD,,