astreum 0.2.41__py3-none-any.whl → 0.3.1__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.
- astreum/__init__.py +16 -7
- astreum/{_communication → communication}/__init__.py +3 -3
- astreum/communication/handlers/handshake.py +83 -0
- astreum/communication/handlers/ping.py +48 -0
- astreum/communication/handlers/storage_request.py +81 -0
- astreum/communication/models/__init__.py +0 -0
- astreum/{_communication → communication/models}/message.py +1 -0
- astreum/communication/models/peer.py +23 -0
- astreum/{_communication → communication/models}/route.py +45 -8
- astreum/{_communication → communication}/setup.py +46 -95
- astreum/communication/start.py +38 -0
- astreum/consensus/__init__.py +20 -0
- astreum/consensus/genesis.py +66 -0
- astreum/consensus/models/__init__.py +0 -0
- astreum/consensus/models/account.py +84 -0
- astreum/consensus/models/accounts.py +72 -0
- astreum/consensus/models/block.py +364 -0
- astreum/{_consensus → consensus/models}/chain.py +7 -7
- astreum/{_consensus → consensus/models}/fork.py +8 -8
- astreum/consensus/models/receipt.py +98 -0
- astreum/consensus/models/transaction.py +213 -0
- astreum/{_consensus → consensus}/setup.py +26 -11
- astreum/consensus/start.py +68 -0
- astreum/consensus/validator.py +95 -0
- astreum/{_consensus → consensus}/workers/discovery.py +20 -1
- astreum/consensus/workers/validation.py +291 -0
- astreum/{_consensus → consensus}/workers/verify.py +32 -3
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/machine/evaluations/high_evaluation.py +237 -0
- astreum/machine/evaluations/low_evaluation.py +281 -0
- astreum/machine/evaluations/script_evaluation.py +27 -0
- astreum/machine/models/__init__.py +0 -0
- astreum/machine/models/environment.py +31 -0
- astreum/machine/models/expression.py +218 -0
- astreum/{_lispeum → machine}/parser.py +26 -31
- astreum/machine/tokenizer.py +90 -0
- astreum/node.py +73 -781
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +69 -0
- astreum/storage/actions/set.py +132 -0
- astreum/storage/models/atom.py +107 -0
- astreum/{_storage/patricia.py → storage/models/trie.py} +236 -177
- astreum/storage/setup.py +44 -15
- astreum/utils/bytes.py +24 -0
- astreum/utils/integer.py +25 -0
- astreum/utils/logging.py +219 -0
- astreum-0.3.1.dist-info/METADATA +160 -0
- astreum-0.3.1.dist-info/RECORD +62 -0
- astreum/_communication/peer.py +0 -11
- astreum/_consensus/__init__.py +0 -20
- astreum/_consensus/account.py +0 -170
- astreum/_consensus/accounts.py +0 -67
- astreum/_consensus/block.py +0 -328
- astreum/_consensus/genesis.py +0 -141
- astreum/_consensus/receipt.py +0 -177
- astreum/_consensus/transaction.py +0 -192
- astreum/_consensus/workers/validation.py +0 -122
- astreum/_lispeum/__init__.py +0 -16
- astreum/_lispeum/environment.py +0 -13
- astreum/_lispeum/expression.py +0 -37
- astreum/_lispeum/high_evaluation.py +0 -177
- astreum/_lispeum/low_evaluation.py +0 -123
- astreum/_lispeum/tokenizer.py +0 -22
- astreum/_node.py +0 -58
- astreum/_storage/__init__.py +0 -5
- astreum/_storage/atom.py +0 -117
- astreum/format.py +0 -75
- astreum/models/block.py +0 -441
- astreum/models/merkle.py +0 -205
- astreum/models/patricia.py +0 -393
- astreum/storage/object.py +0 -68
- astreum-0.2.41.dist-info/METADATA +0 -146
- astreum-0.2.41.dist-info/RECORD +0 -53
- /astreum/{models → communication/handlers}/__init__.py +0 -0
- /astreum/{_communication → communication/models}/ping.py +0 -0
- /astreum/{_communication → communication}/util.py +0 -0
- /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
- /astreum/{_lispeum → machine/models}/meter.py +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.41.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
astreum/_consensus/accounts.py
DELETED
|
@@ -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()}
|
astreum/_consensus/block.py
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import Any, Callable, List, Optional, Tuple, TYPE_CHECKING
|
|
3
|
-
|
|
4
|
-
from .._storage.atom import Atom, ZERO32
|
|
5
|
-
|
|
6
|
-
if TYPE_CHECKING:
|
|
7
|
-
from .._storage.patricia import PatriciaTrie
|
|
8
|
-
from .transaction import Transaction
|
|
9
|
-
from .receipt import Receipt
|
|
10
|
-
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
11
|
-
from cryptography.exceptions import InvalidSignature
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _int_to_be_bytes(n: Optional[int]) -> bytes:
|
|
15
|
-
if n is None:
|
|
16
|
-
return b""
|
|
17
|
-
n = int(n)
|
|
18
|
-
if n == 0:
|
|
19
|
-
return b"\x00"
|
|
20
|
-
size = (n.bit_length() + 7) // 8
|
|
21
|
-
return n.to_bytes(size, "big")
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _be_bytes_to_int(b: Optional[bytes]) -> int:
|
|
25
|
-
if not b:
|
|
26
|
-
return 0
|
|
27
|
-
return int.from_bytes(b, "big")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
|
|
31
|
-
"""Create a typed 'list' atom for child object ids.
|
|
32
|
-
|
|
33
|
-
Encodes elements as a linked chain of element-atoms with data=child_id and
|
|
34
|
-
next pointing to the next element's object id. The list value atom contains
|
|
35
|
-
the element count and points to the head of the element chain. The type atom
|
|
36
|
-
identifies the structure as a list.
|
|
37
|
-
"""
|
|
38
|
-
acc: List[Atom] = []
|
|
39
|
-
next_hash = ZERO32
|
|
40
|
-
elem_atoms: List[Atom] = []
|
|
41
|
-
# Build element chain in reverse, then flip to maintain forward order
|
|
42
|
-
for h in reversed(child_ids):
|
|
43
|
-
a = Atom.from_data(data=h, next_hash=next_hash)
|
|
44
|
-
next_hash = a.object_id()
|
|
45
|
-
elem_atoms.append(a)
|
|
46
|
-
elem_atoms.reverse()
|
|
47
|
-
head = next_hash
|
|
48
|
-
val = Atom.from_data(data=(len(child_ids)).to_bytes(8, "little"), next_hash=head)
|
|
49
|
-
typ = Atom.from_data(data=b"list", next_hash=val.object_id())
|
|
50
|
-
return typ.object_id(), acc + elem_atoms + [val, typ]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Block:
|
|
54
|
-
"""Validation Block representation using Atom storage.
|
|
55
|
-
|
|
56
|
-
Top-level encoding:
|
|
57
|
-
block_id = list([ type_atom, body_list, signature_atom ])
|
|
58
|
-
where: type_atom = Atom(data=b"block", next=body_list_id)
|
|
59
|
-
body_list = list([...details...])
|
|
60
|
-
signature_atom = Atom(data=<signature-bytes>)
|
|
61
|
-
|
|
62
|
-
Details order in body_list:
|
|
63
|
-
0: previous_block_hash (bytes)
|
|
64
|
-
1: number (int → big-endian bytes)
|
|
65
|
-
2: timestamp (int → big-endian bytes)
|
|
66
|
-
3: accounts_hash (bytes)
|
|
67
|
-
4: transactions_total_fees (int → big-endian bytes)
|
|
68
|
-
5: transactions_hash (bytes)
|
|
69
|
-
6: receipts_hash (bytes)
|
|
70
|
-
7: delay_difficulty (int → big-endian bytes)
|
|
71
|
-
8: delay_output (bytes)
|
|
72
|
-
9: validator_public_key (bytes)
|
|
73
|
-
|
|
74
|
-
Notes:
|
|
75
|
-
- "body tree" is represented here by the body_list id (self.body_hash), not
|
|
76
|
-
embedded again as a field to avoid circular references.
|
|
77
|
-
- "signature" is a field on the class but is not required for validation
|
|
78
|
-
navigation; include it in the instance but it is not encoded in atoms
|
|
79
|
-
unless explicitly provided via details extension in the future.
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
# essential identifiers
|
|
83
|
-
hash: bytes
|
|
84
|
-
previous_block_hash: bytes
|
|
85
|
-
previous_block: Optional["Block"]
|
|
86
|
-
|
|
87
|
-
# block details
|
|
88
|
-
number: Optional[int]
|
|
89
|
-
timestamp: Optional[int]
|
|
90
|
-
accounts_hash: Optional[bytes]
|
|
91
|
-
transactions_total_fees: Optional[int]
|
|
92
|
-
transactions_hash: Optional[bytes]
|
|
93
|
-
receipts_hash: Optional[bytes]
|
|
94
|
-
delay_difficulty: Optional[int]
|
|
95
|
-
delay_output: Optional[bytes]
|
|
96
|
-
validator_public_key: Optional[bytes]
|
|
97
|
-
|
|
98
|
-
# additional
|
|
99
|
-
body_hash: Optional[bytes]
|
|
100
|
-
signature: Optional[bytes]
|
|
101
|
-
|
|
102
|
-
# structures
|
|
103
|
-
accounts: Optional["PatriciaTrie"]
|
|
104
|
-
transactions: Optional[List["Transaction"]]
|
|
105
|
-
receipts: Optional[List["Receipt"]]
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def __init__(self) -> None:
|
|
110
|
-
# defaults for safety
|
|
111
|
-
self.hash = b""
|
|
112
|
-
self.previous_block_hash = ZERO32
|
|
113
|
-
self.previous_block = None
|
|
114
|
-
self.number = None
|
|
115
|
-
self.timestamp = None
|
|
116
|
-
self.accounts_hash = None
|
|
117
|
-
self.transactions_total_fees = None
|
|
118
|
-
self.transactions_hash = None
|
|
119
|
-
self.receipts_hash = None
|
|
120
|
-
self.delay_difficulty = None
|
|
121
|
-
self.delay_output = None
|
|
122
|
-
self.validator_public_key = None
|
|
123
|
-
self.body_hash = None
|
|
124
|
-
self.signature = None
|
|
125
|
-
self.accounts = None
|
|
126
|
-
self.transactions = None
|
|
127
|
-
self.receipts = None
|
|
128
|
-
|
|
129
|
-
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
130
|
-
# Build body details as direct byte atoms, in defined order
|
|
131
|
-
details_ids: List[bytes] = []
|
|
132
|
-
atoms_acc: List[Atom] = []
|
|
133
|
-
|
|
134
|
-
def _emit(detail_bytes: bytes) -> None:
|
|
135
|
-
atom = Atom.from_data(data=detail_bytes)
|
|
136
|
-
details_ids.append(atom.object_id())
|
|
137
|
-
atoms_acc.append(atom)
|
|
138
|
-
|
|
139
|
-
# 0: previous_block_hash
|
|
140
|
-
prev_hash = self.previous_block_hash or (self.previous_block.hash if self.previous_block else b"")
|
|
141
|
-
prev_hash = prev_hash or ZERO32
|
|
142
|
-
self.previous_block_hash = prev_hash
|
|
143
|
-
_emit(prev_hash)
|
|
144
|
-
# 1: number
|
|
145
|
-
_emit(_int_to_be_bytes(self.number))
|
|
146
|
-
# 2: timestamp
|
|
147
|
-
_emit(_int_to_be_bytes(self.timestamp))
|
|
148
|
-
# 3: accounts_hash
|
|
149
|
-
_emit(self.accounts_hash or b"")
|
|
150
|
-
# 4: transactions_total_fees
|
|
151
|
-
_emit(_int_to_be_bytes(self.transactions_total_fees))
|
|
152
|
-
# 5: transactions_hash
|
|
153
|
-
_emit(self.transactions_hash or b"")
|
|
154
|
-
# 6: receipts_hash
|
|
155
|
-
_emit(self.receipts_hash or b"")
|
|
156
|
-
# 7: delay_difficulty
|
|
157
|
-
_emit(_int_to_be_bytes(self.delay_difficulty))
|
|
158
|
-
# 8: delay_output
|
|
159
|
-
_emit(self.delay_output or b"")
|
|
160
|
-
# 9: validator_public_key
|
|
161
|
-
_emit(self.validator_public_key or b"")
|
|
162
|
-
|
|
163
|
-
# Build body list
|
|
164
|
-
body_id, body_atoms = _make_list(details_ids)
|
|
165
|
-
atoms_acc.extend(body_atoms)
|
|
166
|
-
self.body_hash = body_id
|
|
167
|
-
|
|
168
|
-
# Type atom points to body list
|
|
169
|
-
type_atom = Atom.from_data(data=b"block", next_hash=body_id)
|
|
170
|
-
|
|
171
|
-
# Signature atom (raw byte payload)
|
|
172
|
-
sig_atom = Atom.from_data(data=self.signature or b"", next_hash=ZERO32)
|
|
173
|
-
|
|
174
|
-
# Main block list: [type_atom, body_list, signature]
|
|
175
|
-
main_id, main_atoms = _make_list([type_atom.object_id(), body_id, sig_atom.object_id()])
|
|
176
|
-
atoms_acc.append(type_atom)
|
|
177
|
-
atoms_acc.append(sig_atom)
|
|
178
|
-
atoms_acc.extend(main_atoms)
|
|
179
|
-
|
|
180
|
-
self.hash = main_id
|
|
181
|
-
return self.hash, atoms_acc
|
|
182
|
-
|
|
183
|
-
@classmethod
|
|
184
|
-
def from_atom(cls, source: Any, block_id: bytes) -> "Block":
|
|
185
|
-
storage_get: Optional[Callable[[bytes], Optional[Atom]]]
|
|
186
|
-
if callable(source):
|
|
187
|
-
storage_get = source
|
|
188
|
-
else:
|
|
189
|
-
storage_get = getattr(source, "_local_get", None)
|
|
190
|
-
if not callable(storage_get):
|
|
191
|
-
raise TypeError("Block.from_atom requires a node with '_local_get' or a callable storage getter")
|
|
192
|
-
# 1) Expect main list
|
|
193
|
-
main_typ = storage_get(block_id)
|
|
194
|
-
if main_typ is None or main_typ.data != b"list":
|
|
195
|
-
raise ValueError("not a block (main list missing)")
|
|
196
|
-
main_val = storage_get(main_typ.next)
|
|
197
|
-
if main_val is None:
|
|
198
|
-
raise ValueError("malformed block list (missing value)")
|
|
199
|
-
# length is little-endian u64 per storage format
|
|
200
|
-
if len(main_val.data) < 1:
|
|
201
|
-
raise ValueError("malformed block list (length)")
|
|
202
|
-
head = main_val.next
|
|
203
|
-
|
|
204
|
-
# read first 2 elements: [type_atom_id, body_list_id]
|
|
205
|
-
first_elem = storage_get(head)
|
|
206
|
-
if first_elem is None:
|
|
207
|
-
raise ValueError("malformed block list (head element)")
|
|
208
|
-
type_atom_id = first_elem.data
|
|
209
|
-
second_elem = storage_get(first_elem.next)
|
|
210
|
-
if second_elem is None:
|
|
211
|
-
raise ValueError("malformed block list (second element)")
|
|
212
|
-
body_list_id = second_elem.data
|
|
213
|
-
# optional 3rd element: signature atom id
|
|
214
|
-
third_elem = storage_get(second_elem.next) if second_elem.next else None
|
|
215
|
-
sig_atom_id: Optional[bytes] = third_elem.data if third_elem is not None else None
|
|
216
|
-
|
|
217
|
-
# 2) Validate type atom and linkage to body
|
|
218
|
-
type_atom = storage_get(type_atom_id)
|
|
219
|
-
if type_atom is None or type_atom.data != b"block" or type_atom.next != body_list_id:
|
|
220
|
-
raise ValueError("not a block (type atom)")
|
|
221
|
-
|
|
222
|
-
# 3) Parse body list of details
|
|
223
|
-
body_typ = storage_get(body_list_id)
|
|
224
|
-
if body_typ is None or body_typ.data != b"list":
|
|
225
|
-
raise ValueError("malformed body (type)")
|
|
226
|
-
body_val = storage_get(body_typ.next)
|
|
227
|
-
if body_val is None:
|
|
228
|
-
raise ValueError("malformed body (value)")
|
|
229
|
-
cur_elem_id = body_val.next
|
|
230
|
-
|
|
231
|
-
def _read_detail_bytes(elem_id: bytes) -> bytes:
|
|
232
|
-
elem = storage_get(elem_id)
|
|
233
|
-
if elem is None:
|
|
234
|
-
return b""
|
|
235
|
-
child_id = elem.data
|
|
236
|
-
detail = storage_get(child_id)
|
|
237
|
-
return detail.data if detail is not None else b""
|
|
238
|
-
|
|
239
|
-
details: List[bytes] = []
|
|
240
|
-
# We read up to 10 fields if present
|
|
241
|
-
for _ in range(10):
|
|
242
|
-
if not cur_elem_id:
|
|
243
|
-
break
|
|
244
|
-
b = _read_detail_bytes(cur_elem_id)
|
|
245
|
-
details.append(b)
|
|
246
|
-
nxt = storage_get(cur_elem_id)
|
|
247
|
-
cur_elem_id = nxt.next if nxt is not None else b""
|
|
248
|
-
|
|
249
|
-
b = cls()
|
|
250
|
-
b.hash = block_id
|
|
251
|
-
b.body_hash = body_list_id
|
|
252
|
-
|
|
253
|
-
# Map details back per the defined order
|
|
254
|
-
get = lambda i: details[i] if i < len(details) else b""
|
|
255
|
-
b.previous_block_hash = get(0) or ZERO32
|
|
256
|
-
b.previous_block = None
|
|
257
|
-
b.number = _be_bytes_to_int(get(1))
|
|
258
|
-
b.timestamp = _be_bytes_to_int(get(2))
|
|
259
|
-
b.accounts_hash = get(3) or None
|
|
260
|
-
b.transactions_total_fees = _be_bytes_to_int(get(4))
|
|
261
|
-
b.transactions_hash = get(5) or None
|
|
262
|
-
b.receipts_hash = get(6) or None
|
|
263
|
-
b.delay_difficulty = _be_bytes_to_int(get(7))
|
|
264
|
-
b.delay_output = get(8) or None
|
|
265
|
-
b.validator_public_key = get(9) or None
|
|
266
|
-
|
|
267
|
-
# 4) Parse signature if present (supports raw or typed 'bytes' atom)
|
|
268
|
-
if sig_atom_id is not None:
|
|
269
|
-
sa = storage_get(sig_atom_id)
|
|
270
|
-
if sa is not None:
|
|
271
|
-
if sa.data == b"bytes":
|
|
272
|
-
sval = storage_get(sa.next)
|
|
273
|
-
b.signature = sval.data if sval is not None else b""
|
|
274
|
-
else:
|
|
275
|
-
b.signature = sa.data
|
|
276
|
-
|
|
277
|
-
return b
|
|
278
|
-
|
|
279
|
-
def validate(self, storage_get: Callable[[bytes], Optional[Atom]]) -> bool:
|
|
280
|
-
"""Validate this block against storage.
|
|
281
|
-
|
|
282
|
-
Checks:
|
|
283
|
-
- Signature: signature must verify over the body list id using the
|
|
284
|
-
validator's public key.
|
|
285
|
-
- Timestamp monotonicity: if previous block exists (not ZERO32), this
|
|
286
|
-
block's timestamp must be >= previous.timestamp + 1.
|
|
287
|
-
"""
|
|
288
|
-
# Unverifiable if critical fields are missing
|
|
289
|
-
if not self.body_hash:
|
|
290
|
-
return False
|
|
291
|
-
if not self.signature:
|
|
292
|
-
return False
|
|
293
|
-
if not self.validator_public_key:
|
|
294
|
-
return False
|
|
295
|
-
if self.timestamp is None:
|
|
296
|
-
return False
|
|
297
|
-
|
|
298
|
-
# 1) Signature check over body hash
|
|
299
|
-
try:
|
|
300
|
-
pub = Ed25519PublicKey.from_public_bytes(bytes(self.validator_public_key))
|
|
301
|
-
pub.verify(self.signature, self.body_hash)
|
|
302
|
-
except InvalidSignature as e:
|
|
303
|
-
raise ValueError("invalid signature") from e
|
|
304
|
-
|
|
305
|
-
# 2) Timestamp monotonicity against previous block
|
|
306
|
-
prev_ts: Optional[int] = None
|
|
307
|
-
prev_hash = self.previous_block_hash or ZERO32
|
|
308
|
-
|
|
309
|
-
if self.previous_block is not None:
|
|
310
|
-
prev_ts = int(self.previous_block.timestamp or 0)
|
|
311
|
-
prev_hash = self.previous_block.hash or prev_hash or ZERO32
|
|
312
|
-
|
|
313
|
-
if prev_hash and prev_hash != ZERO32 and prev_ts is None:
|
|
314
|
-
# If previous block cannot be loaded, treat as unverifiable, not malicious
|
|
315
|
-
try:
|
|
316
|
-
prev = Block.from_atom(storage_get, prev_hash)
|
|
317
|
-
except Exception:
|
|
318
|
-
return False
|
|
319
|
-
prev_ts = int(prev.timestamp or 0)
|
|
320
|
-
|
|
321
|
-
if prev_hash and prev_hash != ZERO32:
|
|
322
|
-
if prev_ts is None:
|
|
323
|
-
return False
|
|
324
|
-
cur_ts = int(self.timestamp or 0)
|
|
325
|
-
if cur_ts < prev_ts + 1:
|
|
326
|
-
raise ValueError("timestamp must be at least prev+1")
|
|
327
|
-
|
|
328
|
-
return True
|
astreum/_consensus/genesis.py
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from typing import Any, Iterable, List, Optional, Tuple
|
|
5
|
-
|
|
6
|
-
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
7
|
-
|
|
8
|
-
from .account import Account
|
|
9
|
-
from .block import Block
|
|
10
|
-
from .._storage.atom import Atom, ZERO32
|
|
11
|
-
from .._storage.patricia import PatriciaTrie, PatriciaNode
|
|
12
|
-
|
|
13
|
-
TREASURY_ADDRESS = b"\x01" * 32
|
|
14
|
-
BURN_ADDRESS = b"\x00" * 32
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _int_to_be_bytes(value: int) -> bytes:
|
|
18
|
-
if value < 0:
|
|
19
|
-
raise ValueError("integer fields in genesis must be non-negative")
|
|
20
|
-
if value == 0:
|
|
21
|
-
return b"\x00"
|
|
22
|
-
length = (value.bit_length() + 7) // 8
|
|
23
|
-
return value.to_bytes(length, "big")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _make_list(child_ids: List[bytes]) -> Tuple[bytes, List[Atom]]:
|
|
27
|
-
next_hash = ZERO32
|
|
28
|
-
chain: List[Atom] = []
|
|
29
|
-
for child_id in reversed(child_ids):
|
|
30
|
-
elem = Atom.from_data(data=child_id, next_hash=next_hash)
|
|
31
|
-
next_hash = elem.object_id()
|
|
32
|
-
chain.append(elem)
|
|
33
|
-
chain.reverse()
|
|
34
|
-
|
|
35
|
-
value_atom = Atom.from_data(
|
|
36
|
-
data=len(child_ids).to_bytes(8, "little"),
|
|
37
|
-
next_hash=next_hash,
|
|
38
|
-
)
|
|
39
|
-
type_atom = Atom.from_data(data=b"list", next_hash=value_atom.object_id())
|
|
40
|
-
atoms = chain + [value_atom, type_atom]
|
|
41
|
-
return type_atom.object_id(), atoms
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _store_atoms(node: Any, atoms: Iterable[Atom]) -> None:
|
|
45
|
-
setter = getattr(node, "_local_set", None)
|
|
46
|
-
if not callable(setter):
|
|
47
|
-
raise TypeError("node must expose '_local_set(object_id, atom)'")
|
|
48
|
-
for atom in atoms:
|
|
49
|
-
setter(atom.object_id(), atom)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _persist_trie(trie: PatriciaTrie, node: Any) -> None:
|
|
53
|
-
for patricia_node in trie.nodes.values():
|
|
54
|
-
_, atoms = patricia_node.to_atoms()
|
|
55
|
-
_store_atoms(node, atoms)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if not hasattr(PatriciaNode, "to_bytes"):
|
|
59
|
-
def _patricia_node_to_bytes(self: PatriciaNode) -> bytes: # type: ignore[no-redef]
|
|
60
|
-
fields = [
|
|
61
|
-
bytes([self.key_len]) + self.key,
|
|
62
|
-
self.child_0 or ZERO32,
|
|
63
|
-
self.child_1 or ZERO32,
|
|
64
|
-
self.value or b"",
|
|
65
|
-
]
|
|
66
|
-
encoded: List[bytes] = []
|
|
67
|
-
for field in fields:
|
|
68
|
-
encoded.append(len(field).to_bytes(4, "big"))
|
|
69
|
-
encoded.append(field)
|
|
70
|
-
return b"".join(encoded)
|
|
71
|
-
|
|
72
|
-
PatriciaNode.to_bytes = _patricia_node_to_bytes # type: ignore[attr-defined]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def create_genesis_block(node: Any, validator_public_key: bytes, validator_secret_key: bytes) -> Block:
|
|
76
|
-
validator_pk = bytes(validator_public_key)
|
|
77
|
-
|
|
78
|
-
if len(validator_pk) != 32:
|
|
79
|
-
raise ValueError("validator_public_key must be 32 bytes")
|
|
80
|
-
|
|
81
|
-
# 1. Stake trie with single validator stake of 1 (encoded on 32 bytes).
|
|
82
|
-
stake_trie = PatriciaTrie()
|
|
83
|
-
stake_amount = (1).to_bytes(32, "big")
|
|
84
|
-
stake_trie.put(node, validator_pk, stake_amount)
|
|
85
|
-
_persist_trie(stake_trie, node)
|
|
86
|
-
stake_root = stake_trie.root_hash or ZERO32
|
|
87
|
-
|
|
88
|
-
# 2. Account trie with treasury, burn, and validator accounts.
|
|
89
|
-
accounts_trie = PatriciaTrie()
|
|
90
|
-
|
|
91
|
-
treasury_account = Account.create(balance=1, data=stake_root, nonce=0)
|
|
92
|
-
treasury_account_id, treasury_atoms = treasury_account.to_atom()
|
|
93
|
-
_store_atoms(node, treasury_atoms)
|
|
94
|
-
accounts_trie.put(node, TREASURY_ADDRESS, treasury_account_id)
|
|
95
|
-
|
|
96
|
-
burn_account = Account.create(balance=0, data=b"", nonce=0)
|
|
97
|
-
burn_account_id, burn_atoms = burn_account.to_atom()
|
|
98
|
-
_store_atoms(node, burn_atoms)
|
|
99
|
-
accounts_trie.put(node, BURN_ADDRESS, burn_account_id)
|
|
100
|
-
|
|
101
|
-
validator_account = Account.create(balance=0, data=b"", nonce=0)
|
|
102
|
-
validator_account_id, validator_atoms = validator_account.to_atom()
|
|
103
|
-
_store_atoms(node, validator_atoms)
|
|
104
|
-
accounts_trie.put(node, validator_pk, validator_account_id)
|
|
105
|
-
|
|
106
|
-
_persist_trie(accounts_trie, node)
|
|
107
|
-
|
|
108
|
-
accounts_root = accounts_trie.root_hash
|
|
109
|
-
if accounts_root is None:
|
|
110
|
-
raise ValueError("genesis accounts trie is empty")
|
|
111
|
-
|
|
112
|
-
# 3. Assemble block metadata.
|
|
113
|
-
block = Block()
|
|
114
|
-
block.previous_block_hash = ZERO32
|
|
115
|
-
block.number = 0
|
|
116
|
-
block.timestamp = 0
|
|
117
|
-
block.accounts_hash = accounts_root
|
|
118
|
-
block.accounts = accounts_trie
|
|
119
|
-
block.transactions_total_fees = 0
|
|
120
|
-
block.transactions_hash = ZERO32
|
|
121
|
-
block.receipts_hash = ZERO32
|
|
122
|
-
block.delay_difficulty = 0
|
|
123
|
-
block.delay_output = b""
|
|
124
|
-
block.validator_public_key = validator_pk
|
|
125
|
-
block.transactions = []
|
|
126
|
-
block.receipts = []
|
|
127
|
-
|
|
128
|
-
# 4. Sign the block body with the validator secret key.
|
|
129
|
-
block.signature = b""
|
|
130
|
-
block.to_atom()
|
|
131
|
-
|
|
132
|
-
if block.body_hash is None:
|
|
133
|
-
raise ValueError("failed to materialise genesis block body")
|
|
134
|
-
|
|
135
|
-
secret = Ed25519PrivateKey.from_private_bytes(validator_secret_key)
|
|
136
|
-
block.signature = secret.sign(block.body_hash)
|
|
137
|
-
block_hash, block_atoms = block.to_atom()
|
|
138
|
-
_store_atoms(node, block_atoms)
|
|
139
|
-
|
|
140
|
-
block.hash = block_hash
|
|
141
|
-
return block
|