astreum 0.2.41__tar.gz → 0.2.42__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.
Files changed (61) hide show
  1. {astreum-0.2.41/src/astreum.egg-info → astreum-0.2.42}/PKG-INFO +1 -1
  2. {astreum-0.2.41 → astreum-0.2.42}/pyproject.toml +1 -1
  3. astreum-0.2.42/src/astreum/_consensus/account.py +95 -0
  4. astreum-0.2.42/src/astreum/_consensus/accounts.py +38 -0
  5. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/genesis.py +3 -3
  6. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/transaction.py +52 -28
  7. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/block.py +20 -20
  8. astreum-0.2.42/src/astreum/utils/integer.py +25 -0
  9. {astreum-0.2.41 → astreum-0.2.42/src/astreum.egg-info}/PKG-INFO +1 -1
  10. {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/SOURCES.txt +2 -1
  11. astreum-0.2.41/src/astreum/_consensus/account.py +0 -170
  12. astreum-0.2.41/src/astreum/_consensus/accounts.py +0 -67
  13. {astreum-0.2.41 → astreum-0.2.42}/LICENSE +0 -0
  14. {astreum-0.2.41 → astreum-0.2.42}/README.md +0 -0
  15. {astreum-0.2.41 → astreum-0.2.42}/setup.cfg +0 -0
  16. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/__init__.py +0 -0
  17. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/__init__.py +0 -0
  18. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/message.py +0 -0
  19. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/peer.py +0 -0
  20. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/ping.py +0 -0
  21. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/route.py +0 -0
  22. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/setup.py +0 -0
  23. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/util.py +0 -0
  24. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/__init__.py +0 -0
  25. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/block.py +0 -0
  26. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/chain.py +0 -0
  27. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/fork.py +0 -0
  28. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/receipt.py +0 -0
  29. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/setup.py +0 -0
  30. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/__init__.py +0 -0
  31. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/discovery.py +0 -0
  32. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/validation.py +0 -0
  33. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/verify.py +0 -0
  34. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/__init__.py +0 -0
  35. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/environment.py +0 -0
  36. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/expression.py +0 -0
  37. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/high_evaluation.py +0 -0
  38. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/low_evaluation.py +0 -0
  39. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/meter.py +0 -0
  40. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/parser.py +0 -0
  41. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/tokenizer.py +0 -0
  42. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_node.py +0 -0
  43. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/__init__.py +0 -0
  44. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/atom.py +0 -0
  45. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/patricia.py +0 -0
  46. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/__init__.py +0 -0
  47. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/ed25519.py +0 -0
  48. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/quadratic_form.py +0 -0
  49. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/wesolowski.py +0 -0
  50. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/x25519.py +0 -0
  51. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/format.py +0 -0
  52. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/__init__.py +0 -0
  53. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/merkle.py +0 -0
  54. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/patricia.py +0 -0
  55. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/node.py +0 -0
  56. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/__init__.py +0 -0
  57. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/object.py +0 -0
  58. {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/setup.py +0 -0
  59. {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/dependency_links.txt +0 -0
  60. {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/requires.txt +0 -0
  61. {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.41
3
+ Version: 0.2.42
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.41"
3
+ version = "0.2.42"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, List, Optional, Tuple
5
+
6
+ from .._storage.atom import Atom, ZERO32
7
+ from .._storage.patricia import PatriciaTrie
8
+ from ..utils.integer import bytes_to_int, int_to_bytes
9
+
10
+
11
+ @dataclass
12
+ class Account:
13
+ balance: int
14
+ code: bytes
15
+ counter: int
16
+ data_hash: bytes
17
+ data: PatriciaTrie
18
+ hash: bytes = ZERO32
19
+ body_hash: bytes = ZERO32
20
+ atoms: List[Atom] = field(default_factory=list)
21
+
22
+ @classmethod
23
+ def create(cls, balance: int = 0, data_hash: bytes = ZERO32, code: bytes = ZERO32, counter: int = 0) -> "Account":
24
+ account = cls(
25
+ balance=int(balance),
26
+ code=bytes(code),
27
+ counter=int(counter),
28
+ data_hash=bytes(data_hash),
29
+ data=PatriciaTrie(root_hash=bytes(data_hash)),
30
+ )
31
+ account.to_atom()
32
+ return account
33
+
34
+ @classmethod
35
+ def from_atom(cls, node: Any, account_id: bytes) -> "Account":
36
+ storage_get = node._local_get
37
+
38
+ type_atom = storage_get(account_id)
39
+ if type_atom is None or type_atom.data != b"account":
40
+ raise ValueError("not an account (type mismatch)")
41
+
42
+ def _read_atom(atom_id: Optional[bytes]) -> Optional[Atom]:
43
+ if not atom_id or atom_id == ZERO32:
44
+ return None
45
+ return storage_get(atom_id)
46
+
47
+ balance_atom = _read_atom(type_atom.next)
48
+ if balance_atom is None:
49
+ raise ValueError("malformed account (balance missing)")
50
+
51
+ code_atom = _read_atom(balance_atom.next)
52
+ if code_atom is None:
53
+ raise ValueError("malformed account (code missing)")
54
+
55
+ counter_atom = _read_atom(code_atom.next)
56
+ if counter_atom is None:
57
+ raise ValueError("malformed account (counter missing)")
58
+
59
+ data_atom = _read_atom(counter_atom.next)
60
+ if data_atom is None:
61
+ raise ValueError("malformed account (data missing)")
62
+
63
+ account = cls.create(
64
+ balance=bytes_to_int(balance_atom.data),
65
+ data_hash=data_atom.data,
66
+ counter=bytes_to_int(counter_atom.data),
67
+ code=code_atom.data,
68
+ )
69
+ if account.hash != account_id:
70
+ raise ValueError("account hash mismatch while decoding")
71
+ return account
72
+
73
+ def to_atom(self) -> Tuple[bytes, List[Atom]]:
74
+ # Build a single forward chain: account -> balance -> code -> counter -> data.
75
+ data_atom = Atom.from_data(data=bytes(self.data_hash))
76
+ counter_atom = Atom.from_data(
77
+ data=int_to_bytes(self.counter),
78
+ next_hash=data_atom.object_id(),
79
+ )
80
+ code_atom = Atom.from_data(
81
+ data=bytes(self.code),
82
+ next_hash=counter_atom.object_id(),
83
+ )
84
+ balance_atom = Atom.from_data(
85
+ data=int_to_bytes(self.balance),
86
+ next_hash=code_atom.object_id(),
87
+ )
88
+ type_atom = Atom.from_data(data=b"account", next_hash=balance_atom.object_id())
89
+
90
+ atoms = [data_atom, counter_atom, code_atom, balance_atom, type_atom]
91
+ account_hash = type_atom.object_id()
92
+ self.hash = account_hash
93
+ self.body_hash = account_hash
94
+ self.atoms = atoms
95
+ return account_hash, list(atoms)
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from .._storage.patricia import PatriciaTrie
6
+ from .account import Account
7
+
8
+
9
+ class Accounts:
10
+ def __init__(
11
+ self,
12
+ root_hash: Optional[bytes] = None,
13
+ ) -> None:
14
+ self._trie = PatriciaTrie(root_hash=root_hash)
15
+ self._cache: Dict[bytes, Account] = {}
16
+
17
+ @property
18
+ def root_hash(self) -> Optional[bytes]:
19
+ return self._trie.root_hash
20
+
21
+ def get_account(self, address: bytes, node: Optional[Any] = None) -> Optional[Account]:
22
+ cached = self._cache.get(address)
23
+ if cached is not None:
24
+ return cached
25
+
26
+ if node is None:
27
+ raise ValueError("Accounts requires a node reference for trie access")
28
+
29
+ account_id: Optional[bytes] = self._trie.get(node, address)
30
+ if account_id is None:
31
+ return None
32
+
33
+ account = Account.from_atom(node, account_id)
34
+ self._cache[address] = account
35
+ return account
36
+
37
+ def set_account(self, address: bytes, account: Account) -> None:
38
+ self._cache[address] = account
@@ -88,17 +88,17 @@ def create_genesis_block(node: Any, validator_public_key: bytes, validator_secre
88
88
  # 2. Account trie with treasury, burn, and validator accounts.
89
89
  accounts_trie = PatriciaTrie()
90
90
 
91
- treasury_account = Account.create(balance=1, data=stake_root, nonce=0)
91
+ treasury_account = Account.create(balance=1, data=stake_root, counter=0)
92
92
  treasury_account_id, treasury_atoms = treasury_account.to_atom()
93
93
  _store_atoms(node, treasury_atoms)
94
94
  accounts_trie.put(node, TREASURY_ADDRESS, treasury_account_id)
95
95
 
96
- burn_account = Account.create(balance=0, data=b"", nonce=0)
96
+ burn_account = Account.create(balance=0, data=b"", counter=0)
97
97
  burn_account_id, burn_atoms = burn_account.to_atom()
98
98
  _store_atoms(node, burn_atoms)
99
99
  accounts_trie.put(node, BURN_ADDRESS, burn_account_id)
100
100
 
101
- validator_account = Account.create(balance=0, data=b"", nonce=0)
101
+ validator_account = Account.create(balance=0, data=b"", counter=0)
102
102
  validator_account_id, validator_atoms = validator_account.to_atom()
103
103
  _store_atoms(node, validator_atoms)
104
104
  accounts_trie.put(node, validator_pk, validator_account_id)
@@ -1,26 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Any, List, Optional, Tuple
4
+ from typing import Any, List, Tuple
5
5
 
6
6
  from .._storage.atom import Atom, ZERO32
7
- from .receipt import Receipt, STATUS_SUCCESS
8
-
9
-
10
- def _int_to_be_bytes(value: Optional[int]) -> bytes:
11
- if value is None:
12
- return b""
13
- value = int(value)
14
- if value == 0:
15
- return b"\x00"
16
- size = (value.bit_length() + 7) // 8
17
- return value.to_bytes(size, "big")
18
-
19
-
20
- def _be_bytes_to_int(data: Optional[bytes]) -> int:
21
- if not data:
22
- return 0
23
- return int.from_bytes(data, "big")
7
+ from ..utils.integer import bytes_to_int, int_to_bytes
8
+ from .account import Account
9
+ from .genesis import TREASURY_ADDRESS
10
+ from .receipt import STATUS_FAILED, Receipt, STATUS_SUCCESS
24
11
 
25
12
 
26
13
  def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
@@ -61,8 +48,8 @@ class Transaction:
61
48
  body_child_ids.append(atom.object_id())
62
49
  acc.append(atom)
63
50
 
64
- emit(_int_to_be_bytes(self.amount))
65
- emit(_int_to_be_bytes(self.counter))
51
+ emit(int_to_bytes(self.amount))
52
+ emit(int_to_bytes(self.counter))
66
53
  emit(bytes(self.data))
67
54
  emit(bytes(self.recipient))
68
55
  emit(bytes(self.sender))
@@ -160,8 +147,8 @@ class Transaction:
160
147
  signature_bytes = signature_atom.data if signature_atom is not None else b""
161
148
 
162
149
  return cls(
163
- amount=_be_bytes_to_int(amount_bytes),
164
- counter=_be_bytes_to_int(counter_bytes),
150
+ amount=bytes_to_int(amount_bytes),
151
+ counter=bytes_to_int(counter_bytes),
165
152
  data=data_bytes,
166
153
  recipient=recipient_bytes,
167
154
  sender=sender_bytes,
@@ -174,8 +161,49 @@ def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None
174
161
  """Apply transaction to the candidate block. Override downstream."""
175
162
  transaction = Transaction.from_atom(node, transaction_hash)
176
163
 
177
- if block.transactions is None:
178
- block.transactions = []
164
+ accounts = block.accounts
165
+
166
+ sender_account = accounts.get_account(address=transaction.sender, node=node)
167
+
168
+ if sender_account is None:
169
+ return
170
+
171
+ tx_cost = 1 + transaction.amount
172
+
173
+ if sender_account.balance < tx_cost:
174
+ low_sender_balance_receipt = Receipt(
175
+ transaction_hash=transaction_hash,
176
+ cost=0,
177
+ logs=b"low sender balance",
178
+ status=STATUS_FAILED
179
+ )
180
+ low_sender_balance_receipt.atomize()
181
+ block.receipts.append(receipt)
182
+ block.transactions.append(transaction)
183
+ return
184
+
185
+ recipient_account = accounts.get_account(address=transaction.recipient, node=node)
186
+
187
+ if recipient_account is None:
188
+ recipient_account = Account.create()
189
+
190
+ if transaction.recipient == TREASURY_ADDRESS:
191
+ stake_trie = recipient_account.data
192
+ existing_stake = stake_trie.get(node, transaction.sender)
193
+ current_stake = bytes_to_int(existing_stake)
194
+ new_stake = current_stake + transaction.amount
195
+ stake_trie.put(node, transaction.sender, int_to_bytes(new_stake))
196
+ recipient_account.data_hash = stake_trie.root_hash or ZERO32
197
+ recipient_account.balance += transaction.amount
198
+ else:
199
+ recipient_account.balance += transaction.amount
200
+
201
+ sender_account.balance -= tx_cost
202
+
203
+ block.accounts.set_account(address=sender_account)
204
+
205
+ block.accounts.set_account(address=recipient_account)
206
+
179
207
  block.transactions.append(transaction)
180
208
 
181
209
  receipt = Receipt(
@@ -185,8 +213,4 @@ def apply_transaction(node: Any, block: object, transaction_hash: bytes) -> None
185
213
  status=STATUS_SUCCESS,
186
214
  )
187
215
  receipt.atomize()
188
- if block.receipts is None:
189
- block.receipts = []
190
216
  block.receipts.append(receipt)
191
-
192
- # Downstream implementations can extend this to apply state changes.
@@ -103,9 +103,9 @@ class Block:
103
103
  stake_root = stake_trie.root_hash
104
104
 
105
105
  # 2. three 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
- burn_acct = Account.create(balance=0, data=b"", nonce=0)
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
109
 
110
110
  # 3. global Accounts structure
111
111
  accts = Accounts()
@@ -190,7 +190,7 @@ class Block:
190
190
 
191
191
  def _credit(addr: bytes, amt: int):
192
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.nonce()))
193
+ blk.accounts.set_account(addr, Account.create(acc.balance + amt, acc.data, acc.counter))
194
194
 
195
195
  if burn_amt:
196
196
  _credit(BURN, burn_amt)
@@ -280,17 +280,17 @@ class Block:
280
280
 
281
281
  sender_acct = self.accounts.get_account(sender_pk)
282
282
  if (sender_acct is None
283
- or sender_acct.nonce() != nonce
284
- or sender_acct.balance() < amount + fee):
283
+ or sender_acct.counter != nonce
284
+ or sender_acct.balance < amount + fee):
285
285
  raise ValueError("invalid or unaffordable transaction")
286
286
 
287
287
  # --- debit sender --------------------------------------------------
288
288
  self.accounts.set_account(
289
289
  sender_pk,
290
290
  Account.create(
291
- balance=sender_acct.balance() - amount - fee,
292
- data=sender_acct.data(),
293
- nonce=sender_acct.nonce() + 1,
291
+ balance=sender_acct.balance - amount - fee,
292
+ data=sender_acct.data,
293
+ counter=sender_acct.counter + 1,
294
294
  )
295
295
  )
296
296
 
@@ -298,14 +298,14 @@ class Block:
298
298
  if recip_pk == TREASURY:
299
299
  treasury = self.accounts.get_account(TREASURY)
300
300
 
301
- trie = PatriciaTrie(node_get=None, root_hash=treasury.data())
301
+ trie = PatriciaTrie(node_get=None, root_hash=treasury.data)
302
302
  stake_bytes = trie.get(sender_pk) or b""
303
303
  current_stake = int.from_bytes(stake_bytes, "big") if stake_bytes else 0
304
304
 
305
305
  if amount > 0:
306
306
  # stake **deposit**
307
307
  trie.put(sender_pk, (current_stake + amount).to_bytes(32, "big"))
308
- new_treas_bal = treasury.balance() + amount
308
+ new_treas_bal = treasury.balance + amount
309
309
  else:
310
310
  # stake **withdrawal**
311
311
  if current_stake == 0:
@@ -315,13 +315,13 @@ class Block:
315
315
  self.accounts.set_account(
316
316
  sender_pk,
317
317
  Account.create(
318
- balance=sender_after.balance() + current_stake,
319
- data=sender_after.data(),
320
- nonce=sender_after.nonce(),
318
+ balance=sender_after.balance + current_stake,
319
+ data=sender_after.data,
320
+ counter=sender_after.counter,
321
321
  )
322
322
  )
323
323
  trie.delete(sender_pk)
324
- new_treas_bal = treasury.balance() # treasury balance unchanged
324
+ new_treas_bal = treasury.balance # treasury balance unchanged
325
325
 
326
326
  # write back treasury with new trie root
327
327
  self.accounts.set_account(
@@ -329,7 +329,7 @@ class Block:
329
329
  Account.create(
330
330
  balance=new_treas_bal,
331
331
  data=trie.root_hash,
332
- nonce=treasury.nonce(),
332
+ counter=treasury.counter,
333
333
  )
334
334
  )
335
335
 
@@ -338,9 +338,9 @@ class Block:
338
338
  self.accounts.set_account(
339
339
  recip_pk,
340
340
  Account.create(
341
- balance=recip_acct.balance() + amount,
342
- data=recip_acct.data(),
343
- nonce=recip_acct.nonce(),
341
+ balance=recip_acct.balance + amount,
342
+ data=recip_acct.data,
343
+ counter=recip_acct.counter,
344
344
  )
345
345
  )
346
346
 
@@ -417,7 +417,7 @@ class Block:
417
417
  v_acct = dummy.accounts.get_account(self.validator_pk) or Account.create(0,b"",0)
418
418
  dummy.accounts.set_account(
419
419
  self.validator_pk,
420
- Account.create(v_acct.balance()+rew, v_acct.data(), v_acct.nonce())
420
+ Account.create(v_acct.balance+rew, v_acct.data, v_acct.counter)
421
421
  )
422
422
 
423
423
  if dummy.accounts.root_hash != self.accounts_hash:
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, Union
4
+
5
+ ByteLike = Union[bytes, bytearray, memoryview]
6
+
7
+
8
+ def int_to_bytes(value: Optional[int]) -> bytes:
9
+ """Convert an integer to a little-endian byte string with minimal length."""
10
+ if value is None:
11
+ return b""
12
+ value = int(value)
13
+ if value == 0:
14
+ return b"\x00"
15
+ length = (value.bit_length() + 7) // 8
16
+ return value.to_bytes(length, "little", signed=False)
17
+
18
+
19
+ def bytes_to_int(data: Optional[ByteLike]) -> int:
20
+ """Convert a little-endian byte string to an integer."""
21
+ if not data:
22
+ return 0
23
+ if isinstance(data, memoryview):
24
+ data = data.tobytes()
25
+ return int.from_bytes(bytes(data), "little", signed=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astreum
3
- Version: 0.2.41
3
+ Version: 0.2.42
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
@@ -53,4 +53,5 @@ src/astreum/models/merkle.py
53
53
  src/astreum/models/patricia.py
54
54
  src/astreum/storage/__init__.py
55
55
  src/astreum/storage/object.py
56
- src/astreum/storage/setup.py
56
+ src/astreum/storage/setup.py
57
+ src/astreum/utils/integer.py
@@ -1,170 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from typing import Any, Callable, List, Optional, Tuple
5
-
6
- from .._storage.atom import Atom, ZERO32
7
-
8
-
9
- def _int_to_be_bytes(value: int) -> bytes:
10
- value = int(value)
11
- if value < 0:
12
- raise ValueError("account integers must be non-negative")
13
- if value == 0:
14
- return b"\x00"
15
- size = (value.bit_length() + 7) // 8
16
- return value.to_bytes(size, "big")
17
-
18
-
19
- def _be_bytes_to_int(data: Optional[bytes]) -> int:
20
- if not data:
21
- return 0
22
- return int.from_bytes(data, "big")
23
-
24
-
25
- def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
26
- next_hash = ZERO32
27
- elements: List[Atom] = []
28
- for child_id in reversed(child_ids):
29
- elem = Atom.from_data(data=child_id, next_hash=next_hash)
30
- next_hash = elem.object_id()
31
- elements.append(elem)
32
- elements.reverse()
33
- value_atom = Atom.from_data(
34
- data=len(child_ids).to_bytes(8, "little"),
35
- next_hash=next_hash,
36
- )
37
- type_atom = Atom.from_data(data=b"list", next_hash=value_atom.object_id())
38
- atoms = elements + [value_atom, type_atom]
39
- return type_atom.object_id(), atoms
40
-
41
-
42
- def _resolve_storage_get(source: Any) -> Callable[[bytes], Optional[Atom]]:
43
- if callable(source):
44
- return source
45
- getter = getattr(source, "_local_get", None)
46
- if callable(getter):
47
- return getter
48
- raise TypeError("Account.from_atom needs a callable storage getter or node with '_local_get'")
49
-
50
-
51
- def _read_list_entries(
52
- storage_get: Callable[[bytes], Optional[Atom]],
53
- start: bytes,
54
- ) -> List[bytes]:
55
- entries: List[bytes] = []
56
- current = start if start and start != ZERO32 else b""
57
- while current:
58
- elem = storage_get(current)
59
- if elem is None:
60
- break
61
- entries.append(elem.data)
62
- nxt = elem.next
63
- current = nxt if nxt and nxt != ZERO32 else b""
64
- return entries
65
-
66
-
67
- @dataclass
68
- class Account:
69
- _balance: int
70
- _data: bytes
71
- _nonce: int
72
- hash: bytes = ZERO32
73
- atoms: List[Atom] = field(default_factory=list)
74
-
75
- @staticmethod
76
- def _encode(balance: int, data: bytes, nonce: int) -> Tuple[bytes, List[Atom]]:
77
- balance_atom = Atom.from_data(data=_int_to_be_bytes(balance))
78
- data_atom = Atom.from_data(data=bytes(data))
79
- nonce_atom = Atom.from_data(data=_int_to_be_bytes(nonce))
80
-
81
- field_atoms = [balance_atom, data_atom, nonce_atom]
82
- field_ids = [a.object_id() for a in field_atoms]
83
-
84
- body_id, body_atoms = _make_list(field_ids)
85
- type_atom = Atom.from_data(data=b"account", next_hash=body_id)
86
- top_id, top_atoms = _make_list([type_atom.object_id(), body_id])
87
-
88
- atoms = field_atoms + body_atoms + [type_atom] + top_atoms
89
- return top_id, atoms
90
-
91
- @classmethod
92
- def create(cls, balance: int, data: bytes, nonce: int) -> "Account":
93
- account_hash, atoms = cls._encode(balance, data, nonce)
94
- return cls(
95
- _balance=int(balance),
96
- _data=bytes(data),
97
- _nonce=int(nonce),
98
- hash=account_hash,
99
- atoms=atoms,
100
- )
101
-
102
- @classmethod
103
- def from_atom(cls, source: Any, account_id: bytes) -> "Account":
104
- storage_get = _resolve_storage_get(source)
105
-
106
- outer_list = storage_get(account_id)
107
- if outer_list is None or outer_list.data != b"list":
108
- raise ValueError("not an account (outer list missing)")
109
-
110
- outer_value = storage_get(outer_list.next)
111
- if outer_value is None:
112
- raise ValueError("malformed account (outer value missing)")
113
-
114
- entries = _read_list_entries(storage_get, outer_value.next)
115
- if len(entries) < 2:
116
- raise ValueError("malformed account (type/body missing)")
117
-
118
- type_atom_id, body_id = entries[0], entries[1]
119
- type_atom = storage_get(type_atom_id)
120
- if type_atom is None or type_atom.data != b"account":
121
- raise ValueError("not an account (type mismatch)")
122
-
123
- body_list = storage_get(body_id)
124
- if body_list is None or body_list.data != b"list":
125
- raise ValueError("malformed account body (type)")
126
-
127
- body_value = storage_get(body_list.next)
128
- if body_value is None:
129
- raise ValueError("malformed account body (value)")
130
-
131
- field_ids = _read_list_entries(storage_get, body_value.next)
132
- if len(field_ids) < 3:
133
- field_ids.extend([ZERO32] * (3 - len(field_ids)))
134
-
135
- def _read_field(field_id: bytes) -> bytes:
136
- if not field_id or field_id == ZERO32:
137
- return b""
138
- atom = storage_get(field_id)
139
- return atom.data if atom is not None else b""
140
-
141
- balance_bytes = _read_field(field_ids[0])
142
- data_bytes = _read_field(field_ids[1])
143
- nonce_bytes = _read_field(field_ids[2])
144
-
145
- account = cls.create(
146
- balance=_be_bytes_to_int(balance_bytes),
147
- data=data_bytes,
148
- nonce=_be_bytes_to_int(nonce_bytes),
149
- )
150
- if account.hash != account_id:
151
- raise ValueError("account hash mismatch while decoding")
152
- return account
153
-
154
- def balance(self) -> int:
155
- return self._balance
156
-
157
- def data(self) -> bytes:
158
- return self._data
159
-
160
- def nonce(self) -> int:
161
- return self._nonce
162
-
163
- def body_hash(self) -> bytes:
164
- return self.hash
165
-
166
- def to_atom(self) -> Tuple[bytes, List[Atom]]:
167
- account_hash, atoms = self._encode(self._balance, self._data, self._nonce)
168
- self.hash = account_hash
169
- self.atoms = atoms
170
- return account_hash, list(atoms)
@@ -1,67 +0,0 @@
1
- from __future__ import annotations
2
- from __future__ import annotations
3
-
4
- from typing import Any, Dict, Iterable, Optional, Tuple
5
-
6
- from .._storage.atom import Atom
7
- from .._storage.patricia import PatriciaTrie
8
- from .account import Account
9
-
10
-
11
- class Accounts:
12
- def __init__(
13
- self,
14
- root_hash: Optional[bytes] = None,
15
- ) -> None:
16
- self._trie = PatriciaTrie(root_hash=root_hash)
17
- self._cache: Dict[bytes, Account] = {}
18
- self._staged: Dict[bytes, Account] = {}
19
- self._staged_hashes: Dict[bytes, bytes] = {}
20
- self._staged_atoms: Dict[bytes, Iterable[Atom]] = {}
21
- self._node: Optional[Any] = None
22
-
23
- @property
24
- def root_hash(self) -> Optional[bytes]:
25
- return self._trie.root_hash
26
-
27
- def _resolve_node(self, node: Optional[Any]) -> Any:
28
- if node is not None:
29
- if self._node is None:
30
- self._node = node
31
- return node
32
- if self._node is None:
33
- raise ValueError("Accounts requires a node reference for trie access")
34
- return self._node
35
-
36
- def get_account(self, address: bytes, *, node: Optional[Any] = None) -> Optional[Account]:
37
- if address in self._staged:
38
- return self._staged[address]
39
-
40
- cached = self._cache.get(address)
41
- if cached is not None:
42
- return cached
43
-
44
- storage_node = self._resolve_node(node)
45
- account_id: Optional[bytes] = self._trie.get(storage_node, address)
46
- if account_id is None:
47
- return None
48
-
49
- account = Account.from_atom(storage_node, account_id)
50
- self._cache[address] = account
51
- return account
52
-
53
- def set_account(self, address: bytes, account: Account) -> None:
54
- account_hash, atoms = account.to_atom()
55
- self._staged[address] = account
56
- self._staged_hashes[address] = account_hash
57
- self._staged_atoms[address] = tuple(atoms)
58
- self._cache[address] = account
59
-
60
- def staged_items(self) -> Iterable[Tuple[bytes, Account]]:
61
- return self._staged.items()
62
-
63
- def staged_hashes(self) -> Dict[bytes, bytes]:
64
- return dict(self._staged_hashes)
65
-
66
- def staged_atoms(self) -> Dict[bytes, Iterable[Atom]]:
67
- return {addr: tuple(atoms) for addr, atoms in self._staged_atoms.items()}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes