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.
- {astreum-0.2.41/src/astreum.egg-info → astreum-0.2.42}/PKG-INFO +1 -1
- {astreum-0.2.41 → astreum-0.2.42}/pyproject.toml +1 -1
- astreum-0.2.42/src/astreum/_consensus/account.py +95 -0
- astreum-0.2.42/src/astreum/_consensus/accounts.py +38 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/genesis.py +3 -3
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/transaction.py +52 -28
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/block.py +20 -20
- astreum-0.2.42/src/astreum/utils/integer.py +25 -0
- {astreum-0.2.41 → astreum-0.2.42/src/astreum.egg-info}/PKG-INFO +1 -1
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/SOURCES.txt +2 -1
- astreum-0.2.41/src/astreum/_consensus/account.py +0 -170
- astreum-0.2.41/src/astreum/_consensus/accounts.py +0 -67
- {astreum-0.2.41 → astreum-0.2.42}/LICENSE +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/README.md +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/setup.cfg +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/message.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/peer.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/ping.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/route.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/setup.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_communication/util.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/block.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/chain.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/fork.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/receipt.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/setup.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/discovery.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/validation.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_consensus/workers/verify.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/environment.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/expression.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/high_evaluation.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/low_evaluation.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/meter.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/parser.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_lispeum/tokenizer.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_node.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/atom.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/_storage/patricia.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/quadratic_form.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/wesolowski.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/format.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/merkle.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/models/patricia.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/node.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/__init__.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/object.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum/storage/setup.py +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.41 → astreum-0.2.42}/src/astreum.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
|
@@ -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,
|
|
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"",
|
|
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"",
|
|
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,
|
|
4
|
+
from typing import Any, List, Tuple
|
|
5
5
|
|
|
6
6
|
from .._storage.atom import Atom, ZERO32
|
|
7
|
-
from .
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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(
|
|
65
|
-
emit(
|
|
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=
|
|
164
|
-
counter=
|
|
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
|
-
|
|
178
|
-
|
|
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"",
|
|
107
|
-
treasury_acct = Account.create(balance=1, data=stake_root,
|
|
108
|
-
burn_acct = Account.create(balance=0, data=b"",
|
|
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
|
|
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.
|
|
284
|
-
or sender_acct.balance
|
|
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
|
|
292
|
-
data=sender_acct.data
|
|
293
|
-
|
|
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
|
|
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
|
|
319
|
-
data=sender_after.data
|
|
320
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
342
|
-
data=recip_acct.data
|
|
343
|
-
|
|
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
|
|
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.
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|