astreum 0.2.41__py3-none-any.whl → 0.2.43__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/_consensus/account.py +62 -137
- astreum/_consensus/accounts.py +7 -36
- astreum/_consensus/block.py +2 -2
- astreum/_consensus/genesis.py +3 -3
- astreum/_consensus/transaction.py +54 -30
- astreum/_consensus/workers/verify.py +1 -1
- astreum/_node.py +15 -3
- astreum/_storage/patricia.py +2 -2
- astreum/models/block.py +20 -20
- astreum/utils/integer.py +25 -0
- {astreum-0.2.41.dist-info → astreum-0.2.43.dist-info}/METADATA +1 -1
- {astreum-0.2.41.dist-info → astreum-0.2.43.dist-info}/RECORD +15 -14
- {astreum-0.2.41.dist-info → astreum-0.2.43.dist-info}/WHEEL +0 -0
- {astreum-0.2.41.dist-info → astreum-0.2.43.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.41.dist-info → astreum-0.2.43.dist-info}/top_level.txt +0 -0
astreum/_consensus/account.py
CHANGED
|
@@ -1,170 +1,95 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, List, Optional, Tuple
|
|
5
5
|
|
|
6
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
|
|
7
|
+
from .._storage.patricia import PatriciaTrie
|
|
8
|
+
from ..utils.integer import bytes_to_int, int_to_bytes
|
|
65
9
|
|
|
66
10
|
|
|
67
11
|
@dataclass
|
|
68
12
|
class Account:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
13
|
+
balance: int
|
|
14
|
+
code: bytes
|
|
15
|
+
counter: int
|
|
16
|
+
data_hash: bytes
|
|
17
|
+
data: PatriciaTrie
|
|
72
18
|
hash: bytes = ZERO32
|
|
19
|
+
body_hash: bytes = ZERO32
|
|
73
20
|
atoms: List[Atom] = field(default_factory=list)
|
|
74
21
|
|
|
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
22
|
@classmethod
|
|
92
|
-
def create(cls, balance: int,
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
atoms=atoms,
|
|
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)),
|
|
100
30
|
)
|
|
31
|
+
account.to_atom()
|
|
32
|
+
return account
|
|
101
33
|
|
|
102
34
|
@classmethod
|
|
103
|
-
def from_atom(cls,
|
|
104
|
-
storage_get =
|
|
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)")
|
|
35
|
+
def from_atom(cls, node: Any, account_id: bytes) -> "Account":
|
|
36
|
+
storage_get = node.storage_get
|
|
113
37
|
|
|
114
|
-
|
|
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)
|
|
38
|
+
type_atom = storage_get(account_id)
|
|
120
39
|
if type_atom is None or type_atom.data != b"account":
|
|
121
40
|
raise ValueError("not an account (type mismatch)")
|
|
122
41
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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)
|
|
126
46
|
|
|
127
|
-
|
|
128
|
-
if
|
|
129
|
-
raise ValueError("malformed account
|
|
47
|
+
balance_atom = _read_atom(type_atom.next)
|
|
48
|
+
if balance_atom is None:
|
|
49
|
+
raise ValueError("malformed account (balance missing)")
|
|
130
50
|
|
|
131
|
-
|
|
132
|
-
if
|
|
133
|
-
|
|
51
|
+
code_atom = _read_atom(balance_atom.next)
|
|
52
|
+
if code_atom is None:
|
|
53
|
+
raise ValueError("malformed account (code missing)")
|
|
134
54
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
atom = storage_get(field_id)
|
|
139
|
-
return atom.data if atom is not None else b""
|
|
55
|
+
counter_atom = _read_atom(code_atom.next)
|
|
56
|
+
if counter_atom is None:
|
|
57
|
+
raise ValueError("malformed account (counter missing)")
|
|
140
58
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
59
|
+
data_atom = _read_atom(counter_atom.next)
|
|
60
|
+
if data_atom is None:
|
|
61
|
+
raise ValueError("malformed account (data missing)")
|
|
144
62
|
|
|
145
63
|
account = cls.create(
|
|
146
|
-
balance=
|
|
147
|
-
data
|
|
148
|
-
|
|
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,
|
|
149
68
|
)
|
|
150
69
|
if account.hash != account_id:
|
|
151
70
|
raise ValueError("account hash mismatch while decoding")
|
|
152
71
|
return account
|
|
153
72
|
|
|
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
73
|
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
167
|
-
|
|
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()
|
|
168
92
|
self.hash = account_hash
|
|
93
|
+
self.body_hash = account_hash
|
|
169
94
|
self.atoms = atoms
|
|
170
95
|
return account_hash, list(atoms)
|
astreum/_consensus/accounts.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
from __future__ import annotations
|
|
3
2
|
|
|
4
|
-
from typing import Any, Dict,
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
5
4
|
|
|
6
|
-
from .._storage.atom import Atom
|
|
7
5
|
from .._storage.patricia import PatriciaTrie
|
|
8
6
|
from .account import Account
|
|
9
7
|
|
|
@@ -15,53 +13,26 @@ class Accounts:
|
|
|
15
13
|
) -> None:
|
|
16
14
|
self._trie = PatriciaTrie(root_hash=root_hash)
|
|
17
15
|
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
16
|
|
|
23
17
|
@property
|
|
24
18
|
def root_hash(self) -> Optional[bytes]:
|
|
25
19
|
return self._trie.root_hash
|
|
26
20
|
|
|
27
|
-
def
|
|
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
|
-
|
|
21
|
+
def get_account(self, address: bytes, node: Optional[Any] = None) -> Optional[Account]:
|
|
40
22
|
cached = self._cache.get(address)
|
|
41
23
|
if cached is not None:
|
|
42
24
|
return cached
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
|
|
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)
|
|
46
30
|
if account_id is None:
|
|
47
31
|
return None
|
|
48
32
|
|
|
49
|
-
account = Account.from_atom(
|
|
33
|
+
account = Account.from_atom(node, account_id)
|
|
50
34
|
self._cache[address] = account
|
|
51
35
|
return account
|
|
52
36
|
|
|
53
37
|
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
38
|
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()}
|
astreum/_consensus/block.py
CHANGED
|
@@ -186,9 +186,9 @@ class Block:
|
|
|
186
186
|
if callable(source):
|
|
187
187
|
storage_get = source
|
|
188
188
|
else:
|
|
189
|
-
storage_get =
|
|
189
|
+
storage_get = source.storage_get
|
|
190
190
|
if not callable(storage_get):
|
|
191
|
-
raise TypeError("Block.from_atom requires a node with '
|
|
191
|
+
raise TypeError("Block.from_atom requires a node with 'storage_get' or a callable storage getter")
|
|
192
192
|
# 1) Expect main list
|
|
193
193
|
main_typ = storage_get(block_id)
|
|
194
194
|
if main_typ is None or main_typ.data != b"list":
|
astreum/_consensus/genesis.py
CHANGED
|
@@ -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))
|
|
@@ -89,7 +76,7 @@ class Transaction:
|
|
|
89
76
|
node: Any,
|
|
90
77
|
transaction_id: bytes,
|
|
91
78
|
) -> Transaction:
|
|
92
|
-
storage_get = node.
|
|
79
|
+
storage_get = node.storage_get
|
|
93
80
|
if not callable(storage_get):
|
|
94
81
|
raise NotImplementedError("node does not expose a storage getter")
|
|
95
82
|
|
|
@@ -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,9 +161,50 @@ 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
|
-
|
|
179
|
-
|
|
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
|
+
|
|
207
|
+
block.transactions.append(transaction_hash)
|
|
180
208
|
|
|
181
209
|
receipt = Receipt(
|
|
182
210
|
transaction_hash=bytes(transaction_hash),
|
|
@@ -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.
|
|
@@ -17,7 +17,7 @@ def _process_peers_latest_block(
|
|
|
17
17
|
fk.head for fk in node.forks.values() if fk.head != latest_block_hash
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
new_fork.validate(storage_get=node.
|
|
20
|
+
new_fork.validate(storage_get=node.storage_get, stop_heads=current_fork_heads)
|
|
21
21
|
|
|
22
22
|
if new_fork.validated_upto and new_fork.validated_upto in node.forks:
|
|
23
23
|
ref = node.forks[new_fork.validated_upto]
|
astreum/_node.py
CHANGED
|
@@ -25,9 +25,9 @@ class Node:
|
|
|
25
25
|
communication_setup(node=self, config=config)
|
|
26
26
|
except Exception:
|
|
27
27
|
pass
|
|
28
|
-
try:
|
|
29
|
-
from astreum._consensus import consensus_setup # type: ignore
|
|
30
|
-
consensus_setup(node=self)
|
|
28
|
+
try:
|
|
29
|
+
from astreum._consensus import consensus_setup # type: ignore
|
|
30
|
+
consensus_setup(node=self)
|
|
31
31
|
except Exception:
|
|
32
32
|
pass
|
|
33
33
|
|
|
@@ -56,3 +56,15 @@ class Node:
|
|
|
56
56
|
def _local_set(self, key: bytes, value: Atom) -> None:
|
|
57
57
|
with self.in_memory_storage_lock:
|
|
58
58
|
self.in_memory_storage[key] = value
|
|
59
|
+
|
|
60
|
+
def _network_get(self, key: bytes) -> Optional[Atom]:
|
|
61
|
+
# locate storage provider
|
|
62
|
+
# query storage provider
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def storage_get(self, key: bytes) -> Optional[Atom]:
|
|
66
|
+
"""Retrieve an Atom by checking local storage first, then the network."""
|
|
67
|
+
atom = self._local_get(key)
|
|
68
|
+
if atom is not None:
|
|
69
|
+
return atom
|
|
70
|
+
return self._network_get(key)
|
astreum/_storage/patricia.py
CHANGED
|
@@ -87,7 +87,7 @@ class PatriciaNode:
|
|
|
87
87
|
hops = 0
|
|
88
88
|
|
|
89
89
|
while current != ZERO32 and hops < 4:
|
|
90
|
-
atom = node.
|
|
90
|
+
atom = node.storage_get(current)
|
|
91
91
|
if atom is None:
|
|
92
92
|
raise ValueError("missing atom while decoding Patricia node")
|
|
93
93
|
entries.append(atom.data)
|
|
@@ -163,7 +163,7 @@ class PatriciaTrie:
|
|
|
163
163
|
if cached is not None:
|
|
164
164
|
return cached
|
|
165
165
|
|
|
166
|
-
if storage_node.
|
|
166
|
+
if storage_node.storage_get(h) is None:
|
|
167
167
|
return None
|
|
168
168
|
|
|
169
169
|
pat_node = PatriciaNode.from_atoms(storage_node, h)
|
astreum/models/block.py
CHANGED
|
@@ -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:
|
astreum/utils/integer.py
ADDED
|
@@ -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.43
|
|
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,5 +1,5 @@
|
|
|
1
1
|
astreum/__init__.py,sha256=9tzA27B_eG5wRF1SAWJIV7xTmCcR1QFc123b_cvFOa4,345
|
|
2
|
-
astreum/_node.py,sha256=
|
|
2
|
+
astreum/_node.py,sha256=Nyb4NZWWDjfCcIgljWIDAGyX5QTMr1lwBirnWzg13hQ,2642
|
|
3
3
|
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
4
4
|
astreum/node.py,sha256=MmlK3jaANTMB3ZAxR8IaSc82OS9meJmVawYIVURADbg,39689
|
|
5
5
|
astreum/_communication/__init__.py,sha256=XJui0yOcfAur4HKt-8sSRlwB-MSU1rchkuOAY-nKDOE,207
|
|
@@ -10,19 +10,19 @@ astreum/_communication/route.py,sha256=fbVXY0xF3O-k7dY9TuCsr6_XxD3m7Cb9ugVacQZ6G
|
|
|
10
10
|
astreum/_communication/setup.py,sha256=TzXpc8dajV31BJhHQMMcb5UdaFrxb1xlYSj58vc_5mg,9072
|
|
11
11
|
astreum/_communication/util.py,sha256=bJ3td3naDzmCelAJQpLwiDMoRBkijQl9YLROjsWyOrI,1256
|
|
12
12
|
astreum/_consensus/__init__.py,sha256=gOCpvnIeO17CGjUGr0odaKNvGEggmDRXfT5IuyrYtcM,376
|
|
13
|
-
astreum/_consensus/account.py,sha256=
|
|
14
|
-
astreum/_consensus/accounts.py,sha256=
|
|
15
|
-
astreum/_consensus/block.py,sha256=
|
|
13
|
+
astreum/_consensus/account.py,sha256=ClMB1e07ky5Wf7BwR4yhEjTSaWK2wiCmpvzio9AsZAY,3295
|
|
14
|
+
astreum/_consensus/accounts.py,sha256=zGq2BCMJPtD_lzcj4jqMzB2Foc3ptxXSPhc9zypB1T0,1106
|
|
15
|
+
astreum/_consensus/block.py,sha256=HybdVcV1D9f1wUE9XeFRnCGF9HH8YI1bgo-y1h-RS5o,12342
|
|
16
16
|
astreum/_consensus/chain.py,sha256=WwUeLrdg7uj--ZsUxse6xFlzO2QeQQggyKSL49KhfU0,2768
|
|
17
17
|
astreum/_consensus/fork.py,sha256=dK5DtT9hWCj_rQs6MS1c1bcGBbkgVTBPIOudYbqS9vw,3825
|
|
18
|
-
astreum/_consensus/genesis.py,sha256=
|
|
18
|
+
astreum/_consensus/genesis.py,sha256=oED7AHw0fPJgMu0OmhbbikJcbItw_bxVBVU8RlB2rJM,4766
|
|
19
19
|
astreum/_consensus/receipt.py,sha256=GPLspqVJHnyBr1cKmBoClsJyeEUecYmg3_acz4L4rRo,6031
|
|
20
20
|
astreum/_consensus/setup.py,sha256=agwqfOemdLqE0Z1zrPV2XHVmZ9sAxp8Bh4k6e2u-t8w,2385
|
|
21
|
-
astreum/_consensus/transaction.py,sha256=
|
|
21
|
+
astreum/_consensus/transaction.py,sha256=q9r0nQzB_67dV-9cIThCzpiJkTojP--8QENPOKxN5yw,7481
|
|
22
22
|
astreum/_consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
|
|
23
23
|
astreum/_consensus/workers/discovery.py,sha256=X1yjKGjLSApMJ9mgWbnc7N21ALD09khDf-in-M45Mis,1683
|
|
24
24
|
astreum/_consensus/workers/validation.py,sha256=WcHLOs2NLMTrJpErghBXWeW2aqtX3-xGPo-HcySdEZ8,4814
|
|
25
|
-
astreum/_consensus/workers/verify.py,sha256=
|
|
25
|
+
astreum/_consensus/workers/verify.py,sha256=wNkfh5qMARew79B-yyGL2UiExaSoGpOIq0fXUkDzpdw,1925
|
|
26
26
|
astreum/_lispeum/__init__.py,sha256=LAy2Z-gBBQlByBHRGUKaaQrOB7QzFEEyGRtInmwTpfU,304
|
|
27
27
|
astreum/_lispeum/environment.py,sha256=pJ0rjp9GoQxHhDiPIVei0jP7dZ_Pznso2O_tpp94-Ik,328
|
|
28
28
|
astreum/_lispeum/expression.py,sha256=io8tbCer_1TJee77yRbcNI5q-DPFGa8xZiC80tGvRRQ,1063
|
|
@@ -33,21 +33,22 @@ astreum/_lispeum/parser.py,sha256=WOW3sSZWkIzPEy3fYTyl0lrtkMxHL9zRrNSYztPDemQ,22
|
|
|
33
33
|
astreum/_lispeum/tokenizer.py,sha256=P68uIj4aPKzjuCJ85jfzRi67QztpuXIOC1vvLQueBI4,552
|
|
34
34
|
astreum/_storage/__init__.py,sha256=EmKZNAZmo3UVE3ekOOuckwFnBVjpa0Sy8Oxg72Lgdxc,53
|
|
35
35
|
astreum/_storage/atom.py,sha256=YFjvfMhniInch13iaKGpw4CCxxgyWonniryb-Rfse4A,4177
|
|
36
|
-
astreum/_storage/patricia.py,sha256=
|
|
36
|
+
astreum/_storage/patricia.py,sha256=7kvhW8RJWZ_EOHjNgEHue2E60FeQfJaKWtLIbM-6G2E,14853
|
|
37
37
|
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
39
39
|
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
40
40
|
astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
|
|
41
41
|
astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
|
|
42
42
|
astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
-
astreum/models/block.py,sha256=
|
|
43
|
+
astreum/models/block.py,sha256=_dgas0zW9OLFV7C1Bo-laxa102klqpJrCAG3DnwQ51U,18092
|
|
44
44
|
astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
|
|
45
45
|
astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
|
|
46
46
|
astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
47
|
astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
|
|
48
48
|
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
49
|
-
astreum
|
|
50
|
-
astreum-0.2.
|
|
51
|
-
astreum-0.2.
|
|
52
|
-
astreum-0.2.
|
|
53
|
-
astreum-0.2.
|
|
49
|
+
astreum/utils/integer.py,sha256=iQt-klWOYVghu_NOT341MmHbOle4FDT3by4PNKNXscg,736
|
|
50
|
+
astreum-0.2.43.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
51
|
+
astreum-0.2.43.dist-info/METADATA,sha256=IjEYvHsaHI08xcD8hrZ5Pn9EZmwEPTNErSTqmv18t7Q,6181
|
|
52
|
+
astreum-0.2.43.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
astreum-0.2.43.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
54
|
+
astreum-0.2.43.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|