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 +200 -70
- {astreum-0.2.23.dist-info → astreum-0.2.24.dist-info}/METADATA +1 -1
- {astreum-0.2.23.dist-info → astreum-0.2.24.dist-info}/RECORD +6 -6
- {astreum-0.2.23.dist-info → astreum-0.2.24.dist-info}/WHEEL +0 -0
- {astreum-0.2.23.dist-info → astreum-0.2.24.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.23.dist-info → astreum-0.2.24.dist-info}/top_level.txt +0 -0
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
|
-
)
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
29
|
-
self.
|
|
30
|
-
|
|
31
|
-
self.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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=
|
|
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.
|
|
20
|
-
astreum-0.2.
|
|
21
|
-
astreum-0.2.
|
|
22
|
-
astreum-0.2.
|
|
23
|
-
astreum-0.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|