astreum 0.2.23__tar.gz → 0.2.24__tar.gz

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.

Files changed (30) hide show
  1. {astreum-0.2.23/src/astreum.egg-info → astreum-0.2.24}/PKG-INFO +1 -1
  2. {astreum-0.2.23 → astreum-0.2.24}/pyproject.toml +1 -1
  3. astreum-0.2.24/src/astreum/models/block.py +300 -0
  4. {astreum-0.2.23 → astreum-0.2.24/src/astreum.egg-info}/PKG-INFO +1 -1
  5. astreum-0.2.23/src/astreum/models/block.py +0 -170
  6. {astreum-0.2.23 → astreum-0.2.24}/LICENSE +0 -0
  7. {astreum-0.2.23 → astreum-0.2.24}/README.md +0 -0
  8. {astreum-0.2.23 → astreum-0.2.24}/setup.cfg +0 -0
  9. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/__init__.py +0 -0
  10. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/crypto/__init__.py +0 -0
  11. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/crypto/ed25519.py +0 -0
  12. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/crypto/quadratic_form.py +0 -0
  13. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/crypto/wesolowski.py +0 -0
  14. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/crypto/x25519.py +0 -0
  15. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/format.py +0 -0
  16. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/lispeum/__init__.py +0 -0
  17. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/lispeum/parser.py +0 -0
  18. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/lispeum/tokenizer.py +0 -0
  19. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/__init__.py +0 -0
  20. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/account.py +0 -0
  21. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/accounts.py +0 -0
  22. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/merkle.py +0 -0
  23. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/patricia.py +0 -0
  24. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/models/transaction.py +0 -0
  25. {astreum-0.2.23 → astreum-0.2.24}/src/astreum/node.py +0 -0
  26. {astreum-0.2.23 → astreum-0.2.24}/src/astreum.egg-info/SOURCES.txt +0 -0
  27. {astreum-0.2.23 → astreum-0.2.24}/src/astreum.egg-info/dependency_links.txt +0 -0
  28. {astreum-0.2.23 → astreum-0.2.24}/src/astreum.egg-info/requires.txt +0 -0
  29. {astreum-0.2.23 → astreum-0.2.24}/src/astreum.egg-info/top_level.txt +0 -0
  30. {astreum-0.2.23 → astreum-0.2.24}/tests/test_node_machine.py +0 -0
@@ -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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.23"
3
+ version = "0.2.24"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,300 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Dict, Any, Optional, Union
4
+
5
+ from astreum.models.account import Account
6
+ from astreum.models.accounts import Accounts
7
+ from astreum.models.patricia import PatriciaTrie
8
+ from astreum.models.transaction import Transaction
9
+ from ..crypto import ed25519
10
+ from .merkle import MerkleTree
11
+
12
+ # Constants for integer field names
13
+ _INT_FIELDS = {
14
+ "delay_difficulty",
15
+ "number",
16
+ "timestamp",
17
+ "transaction_limit",
18
+ "transactions_total_fees",
19
+ }
20
+
21
+ class Block:
22
+ def __init__(
23
+ self,
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,
39
+ body_tree: Optional[MerkleTree] = None,
40
+ signature: Optional[bytes] = None,
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
58
+
59
+ @property
60
+ def hash(self) -> bytes:
61
+ return self._block_hash
62
+
63
+ def get_body_hash(self) -> bytes:
64
+ """Return the Merkle root of the body fields."""
65
+ if not self._body_tree:
66
+ raise ValueError("Body tree not available for this block instance.")
67
+ return self._body_tree.root_hash
68
+
69
+ def get_signature(self) -> bytes:
70
+ """Return the block's signature leaf."""
71
+ if self._signature is None:
72
+ raise ValueError("Signature not available for this block instance.")
73
+ return self._signature
74
+
75
+ def get_field(self, name: str) -> Union[int, bytes]:
76
+ """Query a single body field by name, returning an int or bytes."""
77
+ if name not in self._field_names:
78
+ raise KeyError(f"Unknown field: {name}")
79
+ if not self._body_tree:
80
+ raise ValueError("Body tree not available for field queries.")
81
+ idx = self._field_names.index(name)
82
+ leaf_bytes = self._body_tree.leaves[idx]
83
+ if name in _INT_FIELDS:
84
+ return int.from_bytes(leaf_bytes, "big")
85
+ return leaf_bytes
86
+
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
+ @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 . build the two Account bodies
106
+ validator_acct = Account.create(balance=0, data=b"", nonce=0)
107
+ treasury_acct = Account.create(balance=1, data=stake_root, nonce=0)
108
+
109
+ # 3 . global Accounts structure
110
+ accts = Accounts()
111
+ accts.set_account(validator_addr, validator_acct)
112
+ accts.set_account(b"\x11" * 32, treasury_acct)
113
+ accounts_hash = accts.root_hash
114
+
115
+ # 4 . constant body fields for genesis
116
+ body_kwargs = dict(
117
+ number = 0,
118
+ prev_block_hash = b"\x00" * 32,
119
+ timestamp = 0,
120
+ accounts_hash = accounts_hash,
121
+ transactions_total_fees = 0,
122
+ transaction_limit = 0,
123
+ transactions_root_hash = b"\x00" * 32,
124
+ delay_difficulty = 0,
125
+ delay_output = b"",
126
+ delay_proof = b"",
127
+ validator_pk = validator_addr,
128
+ signature = b"",
129
+ )
130
+
131
+ # 5 . build and return the block
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
@@ -1,170 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import List, Dict, Any, Optional, Union
4
-
5
- from astreum.models.account import Account
6
- from astreum.models.accounts import Accounts
7
- from astreum.models.patricia import PatriciaTrie
8
- from ..crypto import ed25519
9
- from .merkle import MerkleTree
10
-
11
- # Constants for integer field names
12
- _INT_FIELDS = {
13
- "delay_difficulty",
14
- "number",
15
- "timestamp",
16
- "transaction_limit",
17
- "transactions_total_fees",
18
- }
19
-
20
- class Block:
21
- def __init__(
22
- self,
23
- block_hash: bytes,
24
- body_tree: Optional[MerkleTree] = None,
25
- 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
- ]
44
-
45
- @property
46
- def hash(self) -> bytes:
47
- """Return the block hash (Merkle root of body_root || signature)."""
48
- return self._block_hash
49
-
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
- def get_body_hash(self) -> bytes:
102
- """Return the Merkle root of the body fields."""
103
- if not self._body_tree:
104
- raise ValueError("Body tree not available for this block instance.")
105
- return self._body_tree.root_hash
106
-
107
- def get_signature(self) -> bytes:
108
- """Return the block's signature leaf."""
109
- if self._signature is None:
110
- raise ValueError("Signature not available for this block instance.")
111
- return self._signature
112
-
113
- def get_field(self, name: str) -> Union[int, bytes]:
114
- """Query a single body field by name, returning an int or bytes."""
115
- if name not in self._field_names:
116
- raise KeyError(f"Unknown field: {name}")
117
- if not self._body_tree:
118
- raise ValueError("Body tree not available for field queries.")
119
- idx = self._field_names.index(name)
120
- leaf_bytes = self._body_tree.leaves[idx]
121
- if name in _INT_FIELDS:
122
- return int.from_bytes(leaf_bytes, "big")
123
- return leaf_bytes
124
-
125
- def verify_block_signature(self) -> bool:
126
- """Verify the block's Ed25519 signature against its body root."""
127
- pub = ed25519.Ed25519PublicKey.from_public_bytes(
128
- self.get_field("validator_pk")
129
- )
130
- try:
131
- pub.verify(self.get_signature(), self.get_body_hash())
132
- return True
133
- except Exception:
134
- return False
135
-
136
- @classmethod
137
- def genesis(cls, validator_addr: bytes) -> "Block":
138
- # 1 . validator-stakes sub-trie
139
- stake_trie = PatriciaTrie()
140
- stake_trie.put(validator_addr, (1).to_bytes(32, "big"))
141
- stake_root = stake_trie.root_hash
142
-
143
- # 2 . build the two Account bodies
144
- validator_acct = Account.create(balance=0, data=b"", nonce=0)
145
- treasury_acct = Account.create(balance=1, data=stake_root, nonce=0)
146
-
147
- # 3 . global Accounts structure
148
- accts = Accounts()
149
- accts.set_account(validator_addr, validator_acct)
150
- accts.set_account(b"\x11" * 32, treasury_acct)
151
- accounts_hash = accts.root_hash
152
-
153
- # 4 . constant body fields for genesis
154
- body_kwargs = dict(
155
- number = 0,
156
- prev_block_hash = b"\x00" * 32,
157
- timestamp = 0,
158
- accounts_hash = accounts_hash,
159
- transactions_total_fees = 0,
160
- transaction_limit = 0,
161
- transactions_root_hash = b"\x00" * 32,
162
- delay_difficulty = 0,
163
- delay_output = b"",
164
- delay_proof = b"",
165
- validator_pk = validator_addr,
166
- signature = b"",
167
- )
168
-
169
- # 5 . build and return the block
170
- return cls.create(**body_kwargs)
File without changes
File without changes
File without changes
File without changes
File without changes