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

astreum/models/block.py CHANGED
@@ -5,6 +5,7 @@ from typing import List, Dict, Any, Optional, Union
5
5
  from astreum.models.account import Account
6
6
  from astreum.models.accounts import Accounts
7
7
  from astreum.models.patricia import PatriciaTrie
8
+ from astreum.models.transaction import Transaction
8
9
  from ..crypto import ed25519
9
10
  from .merkle import MerkleTree
10
11
 
@@ -21,83 +22,44 @@ class Block:
21
22
  def __init__(
22
23
  self,
23
24
  block_hash: bytes,
25
+ *,
26
+ number: Optional[int] = None,
27
+ prev_block_hash: Optional[bytes] = None,
28
+ timestamp: Optional[int] = None,
29
+ accounts_hash: Optional[bytes] = None,
30
+ accounts: Optional[Accounts] = None,
31
+ transaction_limit: Optional[int] = None,
32
+ transactions_total_fees: Optional[int] = None,
33
+ transactions_root_hash: Optional[bytes] = None,
34
+ transactions_count: Optional[int] = None,
35
+ delay_difficulty: Optional[int] = None,
36
+ delay_output: Optional[bytes] = None,
37
+ delay_proof: Optional[bytes] = None,
38
+ validator_pk: Optional[bytes] = None,
24
39
  body_tree: Optional[MerkleTree] = None,
25
40
  signature: Optional[bytes] = None,
26
- ) -> None:
27
- self._block_hash = block_hash
28
- self._body_tree = body_tree
29
- self._signature = signature
30
- # store field names in alphabetical order for consistent indexing
31
- self._field_names = [
32
- "accounts_hash",
33
- "delay_difficulty",
34
- "delay_output",
35
- "delay_proof",
36
- "number",
37
- "prev_block_hash",
38
- "timestamp",
39
- "transaction_limit",
40
- "transactions_root_hash",
41
- "transactions_total_fees",
42
- "validator_pk",
43
- ]
41
+ ):
42
+ self.hash = block_hash
43
+ self.number = number
44
+ self.prev_block_hash = prev_block_hash
45
+ self.timestamp = timestamp
46
+ self.accounts_hash = accounts_hash
47
+ self.accounts = accounts
48
+ self.transaction_limit = transaction_limit
49
+ self.transactions_total_fees = transactions_total_fees
50
+ self.transactions_root_hash = transactions_root_hash
51
+ self.transactions_count = transactions_count
52
+ self.delay_difficulty = delay_difficulty
53
+ self.delay_output = delay_output
54
+ self.delay_proof = delay_proof
55
+ self.validator_pk = validator_pk
56
+ self.body_tree = body_tree
57
+ self.signature = signature
44
58
 
45
59
  @property
46
60
  def hash(self) -> bytes:
47
- """Return the block hash (Merkle root of body_root || signature)."""
48
61
  return self._block_hash
49
62
 
50
- @classmethod
51
- def create(
52
- cls,
53
- number: int,
54
- prev_block_hash: bytes,
55
- timestamp: int,
56
- accounts_hash: bytes,
57
- transactions_total_fees: int,
58
- transaction_limit: int,
59
- transactions_root_hash: bytes,
60
- delay_difficulty: int,
61
- delay_output: bytes,
62
- delay_proof: bytes,
63
- validator_pk: bytes,
64
- signature: bytes,
65
- ) -> Block:
66
- """Build a new block by hashing the provided fields into Merkle trees."""
67
- # map fields by name
68
- field_map: Dict[str, Any] = {
69
- "accounts_hash": accounts_hash,
70
- "delay_difficulty": delay_difficulty,
71
- "delay_output": delay_output,
72
- "delay_proof": delay_proof,
73
- "number": number,
74
- "prev_block_hash": prev_block_hash,
75
- "timestamp": timestamp,
76
- "transaction_limit": transaction_limit,
77
- "transactions_root_hash": transactions_root_hash,
78
- "transactions_total_fees": transactions_total_fees,
79
- "validator_pk": validator_pk,
80
- }
81
-
82
- leaves: List[bytes] = []
83
- for name in sorted(field_map):
84
- v = field_map[name]
85
- if isinstance(v, bytes):
86
- leaf_bytes = v
87
- elif isinstance(v, int):
88
- length = (v.bit_length() + 7) // 8 or 1
89
- leaf_bytes = v.to_bytes(length, "big")
90
- else:
91
- raise TypeError(f"Unsupported field type for '{name}': {type(v)}")
92
- leaves.append(leaf_bytes)
93
-
94
- body_tree = MerkleTree.from_leaves(leaves)
95
- body_root = body_tree.root_hash
96
- top_tree = MerkleTree.from_leaves([body_root, signature])
97
- block_hash = top_tree.root_hash
98
-
99
- return cls(block_hash, body_tree, signature)
100
-
101
63
  def get_body_hash(self) -> bytes:
102
64
  """Return the Merkle root of the body fields."""
103
65
  if not self._body_tree:
@@ -168,3 +130,171 @@ class Block:
168
130
 
169
131
  # 5 . build and return the block
170
132
  return cls.create(**body_kwargs)
133
+
134
+ @classmethod
135
+ def build(
136
+ cls,
137
+ previous_block: "Block",
138
+ transactions: list[Transaction],
139
+ *,
140
+ validator_pk: bytes,
141
+ natural_rate: float = 0.618,
142
+ ) -> "Block":
143
+ BURN = b"\x00" * 32
144
+
145
+ # --- 0. create an empty block-in-progress, seeded with parent fields ----
146
+ blk = cls(
147
+ block_hash=b"", # placeholder; set at the end
148
+ number=previous_block.number + 1,
149
+ prev_block_hash=previous_block.hash,
150
+ timestamp=previous_block.timestamp + 1,
151
+ accounts_hash=previous_block.accounts_hash,
152
+ transaction_limit=previous_block.transaction_limit,
153
+ transactions_count=0,
154
+ )
155
+
156
+ # --- 1. apply up to transaction_limit txs -------------------------------
157
+ for tx in transactions:
158
+ try:
159
+ blk.apply_tx(tx) # ← NEW single-line call
160
+ except ValueError:
161
+ break # stop at first invalid or cap reached
162
+
163
+ # --- 2. split fees after all txs ----------------------------------------
164
+ burn_amt = blk.total_fees // 2
165
+ reward_amt = blk.total_fees - burn_amt
166
+ 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
+ )
175
+ 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
+ )
184
+
185
+ # --- 3. recalc tx-limit via prev metrics -------------------------------
186
+ prev_limit = previous_block.transaction_limit
187
+ prev_tx_count = previous_block.transactions_count
188
+ threshold = prev_limit * natural_rate
189
+ if prev_tx_count > threshold:
190
+ blk.transaction_limit = prev_tx_count
191
+ elif prev_tx_count < threshold:
192
+ blk.transaction_limit = max(1, int(prev_limit * natural_rate))
193
+ else:
194
+ blk.transaction_limit = prev_limit
195
+
196
+ # --- 4. finalise block hash & header roots ------------------------------
197
+ blk.accounts_hash = blk.accounts.root_hash
198
+ 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
204
+
205
+ return blk
206
+
207
+
208
+ def apply_tx(self, tx: Transaction) -> None:
209
+ # --- lazy state ----------------------------------------------------
210
+ if not hasattr(self, "accounts") or self.accounts is None:
211
+ self.accounts = Accounts(root_hash=self.accounts_hash)
212
+ if not hasattr(self, "total_fees"):
213
+ self.total_fees = 0
214
+ self.tx_hashes = []
215
+ self.transactions_count = 0
216
+
217
+ TREASURY = b"\x11" * 32
218
+ BURN = b"\x00" * 32
219
+
220
+ # --- cap check -----------------------------------------------------
221
+ if self.transactions_count >= self.transaction_limit:
222
+ raise ValueError("block transaction limit reached")
223
+
224
+ # --- unpack tx -----------------------------------------------------
225
+ sender_pk = tx.get_sender_pk()
226
+ recip_pk = tx.get_recipient_pk()
227
+ amount = tx.get_amount()
228
+ fee = tx.get_fee()
229
+ nonce = tx.get_nonce()
230
+
231
+ sender_acct = self.accounts.get_account(sender_pk)
232
+ if (sender_acct is None
233
+ or sender_acct.nonce() != nonce
234
+ or sender_acct.balance() < amount + fee):
235
+ raise ValueError("invalid or unaffordable transaction")
236
+
237
+ # --- debit sender --------------------------------------------------
238
+ self.accounts.set_account(
239
+ sender_pk,
240
+ Account.create(
241
+ balance=sender_acct.balance() - amount - fee,
242
+ data=sender_acct.data(),
243
+ nonce=sender_acct.nonce() + 1,
244
+ )
245
+ )
246
+
247
+ # --- destination handling -----------------------------------------
248
+ if recip_pk == TREASURY:
249
+ treasury = self.accounts.get_account(TREASURY)
250
+
251
+ trie = PatriciaTrie(node_get=None, root_hash=treasury.data())
252
+ stake_bytes = trie.get(sender_pk) or b""
253
+ current_stake = int.from_bytes(stake_bytes, "big") if stake_bytes else 0
254
+
255
+ if amount > 0:
256
+ # stake **deposit**
257
+ trie.put(sender_pk, (current_stake + amount).to_bytes(32, "big"))
258
+ new_treas_bal = treasury.balance() + amount
259
+ else:
260
+ # stake **withdrawal**
261
+ if current_stake == 0:
262
+ raise ValueError("no stake to withdraw")
263
+ # move stake back to sender balance
264
+ sender_after = self.accounts.get_account(sender_pk)
265
+ self.accounts.set_account(
266
+ sender_pk,
267
+ Account.create(
268
+ balance=sender_after.balance() + current_stake,
269
+ data=sender_after.data(),
270
+ nonce=sender_after.nonce(),
271
+ )
272
+ )
273
+ trie.delete(sender_pk)
274
+ new_treas_bal = treasury.balance() # treasury balance unchanged
275
+
276
+ # write back treasury with new trie root
277
+ self.accounts.set_account(
278
+ TREASURY,
279
+ Account.create(
280
+ balance=new_treas_bal,
281
+ data=trie.root_hash,
282
+ nonce=treasury.nonce(),
283
+ )
284
+ )
285
+
286
+ else:
287
+ recip_acct = self.accounts.get_account(recip_pk) or Account.create(0, b"", 0)
288
+ self.accounts.set_account(
289
+ recip_pk,
290
+ Account.create(
291
+ balance=recip_acct.balance() + amount,
292
+ data=recip_acct.data(),
293
+ nonce=recip_acct.nonce(),
294
+ )
295
+ )
296
+
297
+ # --- accumulate fee & record --------------------------------------
298
+ self.total_fees += fee
299
+ self.tx_hashes.append(tx.hash)
300
+ self.transactions_count += 1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.23
3
+ Version: 0.2.24
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
@@ -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=3OyAAqw853aLnnWogYdkxOg_WXNEsfn2g8jrFEip2aY,6141
15
+ astreum/models/block.py,sha256=mqHpsN4-aDyW9Q9emFBVIEyNvLAqRJZbqCN-4vS4Hw0,11795
16
16
  astreum/models/merkle.py,sha256=merV3rx2iRfzvglV6gNusrJf7OMbcVV854T-DUWCC64,6733
17
17
  astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
18
18
  astreum/models/transaction.py,sha256=yBarvRK2ybMAHHEQPZpubGO7gms4U9k093xQGNQHQ4Q,3043
19
- astreum-0.2.23.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
20
- astreum-0.2.23.dist-info/METADATA,sha256=itKxfzkJrO8JcpMVCW3nDA_8NdtdndEj9zKJaoT5GGc,5478
21
- astreum-0.2.23.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- astreum-0.2.23.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
23
- astreum-0.2.23.dist-info/RECORD,,
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,,