astreum 0.2.61__py3-none-any.whl → 0.3.9__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 +89 -0
- astreum/communication/handlers/object_request.py +176 -0
- astreum/communication/handlers/object_response.py +115 -0
- astreum/communication/handlers/ping.py +34 -0
- astreum/communication/handlers/route_request.py +76 -0
- astreum/communication/handlers/route_response.py +53 -0
- astreum/communication/models/__init__.py +0 -0
- astreum/communication/models/message.py +124 -0
- astreum/communication/models/peer.py +51 -0
- astreum/{_communication → communication/models}/route.py +7 -12
- astreum/communication/processors/__init__.py +0 -0
- astreum/communication/processors/incoming.py +98 -0
- astreum/communication/processors/outgoing.py +20 -0
- astreum/communication/setup.py +166 -0
- astreum/communication/start.py +37 -0
- astreum/{_communication → communication}/util.py +7 -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 → consensus/models}/transaction.py +76 -78
- astreum/{_consensus → consensus}/setup.py +18 -50
- astreum/consensus/start.py +67 -0
- astreum/consensus/validator.py +95 -0
- astreum/{_consensus → consensus}/workers/discovery.py +19 -1
- astreum/consensus/workers/validation.py +307 -0
- astreum/{_consensus → consensus}/workers/verify.py +29 -2
- astreum/crypto/chacha20poly1305.py +74 -0
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/{_lispeum → machine/evaluations}/high_evaluation.py +237 -236
- 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/{_lispeum → machine/models}/expression.py +36 -8
- astreum/machine/tokenizer.py +90 -0
- astreum/node.py +78 -767
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +183 -0
- astreum/storage/actions/set.py +178 -0
- astreum/{_storage → storage/models}/atom.py +55 -57
- astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
- astreum/storage/requests.py +28 -0
- astreum/storage/setup.py +22 -15
- astreum/utils/config.py +48 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/METADATA +27 -26
- astreum-0.3.9.dist-info/RECORD +71 -0
- astreum/_communication/message.py +0 -101
- astreum/_communication/peer.py +0 -23
- astreum/_communication/setup.py +0 -322
- astreum/_consensus/__init__.py +0 -20
- astreum/_consensus/account.py +0 -95
- astreum/_consensus/accounts.py +0 -38
- astreum/_consensus/block.py +0 -311
- astreum/_consensus/genesis.py +0 -72
- astreum/_consensus/receipt.py +0 -136
- astreum/_consensus/workers/validation.py +0 -125
- astreum/_lispeum/__init__.py +0 -16
- astreum/_lispeum/environment.py +0 -13
- astreum/_lispeum/low_evaluation.py +0 -123
- astreum/_lispeum/tokenizer.py +0 -22
- astreum/_node.py +0 -198
- astreum/_storage/__init__.py +0 -7
- astreum/_storage/setup.py +0 -35
- 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.61.dist-info/RECORD +0 -57
- /astreum/{models → communication/handlers}/__init__.py +0 -0
- /astreum/{_communication → communication/models}/ping.py +0 -0
- /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
- /astreum/{_lispeum → machine/models}/meter.py +0 -0
- /astreum/{_lispeum → machine}/parser.py +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/WHEEL +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.9.dist-info}/top_level.txt +0 -0
astreum/_consensus/block.py
DELETED
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import Any, Callable, List, Optional, Tuple, TYPE_CHECKING
|
|
3
|
-
|
|
4
|
-
from .._storage.atom import Atom, AtomKind, 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
|
-
class Block:
|
|
31
|
-
"""Validation Block representation using Atom storage.
|
|
32
|
-
|
|
33
|
-
Top-level encoding:
|
|
34
|
-
block_id = type_atom.object_id()
|
|
35
|
-
chain: type_atom --next--> signature_atom --next--> body_list_atom --next--> ZERO32
|
|
36
|
-
where: type_atom = Atom(kind=AtomKind.SYMBOL, data=b"block")
|
|
37
|
-
signature_atom = Atom(kind=AtomKind.BYTES, data=<signature-bytes>)
|
|
38
|
-
body_list_atom = Atom(kind=AtomKind.LIST, data=<body_head_id>)
|
|
39
|
-
|
|
40
|
-
Details order in body_list:
|
|
41
|
-
0: previous_block_hash (bytes)
|
|
42
|
-
1: number (int -> big-endian bytes)
|
|
43
|
-
2: timestamp (int -> big-endian bytes)
|
|
44
|
-
3: accounts_hash (bytes)
|
|
45
|
-
4: transactions_total_fees (int -> big-endian bytes)
|
|
46
|
-
5: transactions_hash (bytes)
|
|
47
|
-
6: receipts_hash (bytes)
|
|
48
|
-
7: delay_difficulty (int -> big-endian bytes)
|
|
49
|
-
8: delay_output (bytes)
|
|
50
|
-
9: validator_public_key (bytes)
|
|
51
|
-
|
|
52
|
-
Notes:
|
|
53
|
-
- "body tree" is represented here by the body_list id (self.body_hash), not
|
|
54
|
-
embedded again as a field to avoid circular references.
|
|
55
|
-
- "signature" is a field on the class but is not required for validation
|
|
56
|
-
navigation; include it in the instance but it is not encoded in atoms
|
|
57
|
-
unless explicitly provided via details extension in the future.
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
# essential identifiers
|
|
61
|
-
hash: bytes
|
|
62
|
-
previous_block_hash: bytes
|
|
63
|
-
previous_block: Optional["Block"]
|
|
64
|
-
|
|
65
|
-
# block details
|
|
66
|
-
number: Optional[int]
|
|
67
|
-
timestamp: Optional[int]
|
|
68
|
-
accounts_hash: Optional[bytes]
|
|
69
|
-
transactions_total_fees: Optional[int]
|
|
70
|
-
transactions_hash: Optional[bytes]
|
|
71
|
-
receipts_hash: Optional[bytes]
|
|
72
|
-
delay_difficulty: Optional[int]
|
|
73
|
-
delay_output: Optional[bytes]
|
|
74
|
-
validator_public_key: Optional[bytes]
|
|
75
|
-
|
|
76
|
-
# additional
|
|
77
|
-
body_hash: Optional[bytes]
|
|
78
|
-
signature: Optional[bytes]
|
|
79
|
-
|
|
80
|
-
# structures
|
|
81
|
-
accounts: Optional["PatriciaTrie"]
|
|
82
|
-
transactions: Optional[List["Transaction"]]
|
|
83
|
-
receipts: Optional[List["Receipt"]]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def __init__(self) -> None:
|
|
88
|
-
# defaults for safety
|
|
89
|
-
self.hash = b""
|
|
90
|
-
self.previous_block_hash = ZERO32
|
|
91
|
-
self.previous_block = None
|
|
92
|
-
self.number = None
|
|
93
|
-
self.timestamp = None
|
|
94
|
-
self.accounts_hash = None
|
|
95
|
-
self.transactions_total_fees = None
|
|
96
|
-
self.transactions_hash = None
|
|
97
|
-
self.receipts_hash = None
|
|
98
|
-
self.delay_difficulty = None
|
|
99
|
-
self.delay_output = None
|
|
100
|
-
self.validator_public_key = None
|
|
101
|
-
self.body_hash = None
|
|
102
|
-
self.signature = None
|
|
103
|
-
self.accounts = None
|
|
104
|
-
self.transactions = None
|
|
105
|
-
self.receipts = None
|
|
106
|
-
|
|
107
|
-
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
108
|
-
# Build body details as direct byte atoms, in defined order
|
|
109
|
-
details_ids: List[bytes] = []
|
|
110
|
-
block_atoms: List[Atom] = []
|
|
111
|
-
|
|
112
|
-
def _emit(detail_bytes: bytes) -> None:
|
|
113
|
-
atom = Atom.from_data(data=detail_bytes, kind=AtomKind.BYTES)
|
|
114
|
-
details_ids.append(atom.object_id())
|
|
115
|
-
block_atoms.append(atom)
|
|
116
|
-
|
|
117
|
-
# 0: previous_block_hash
|
|
118
|
-
_emit(self.previous_block_hash)
|
|
119
|
-
# 1: number
|
|
120
|
-
_emit(_int_to_be_bytes(self.number))
|
|
121
|
-
# 2: timestamp
|
|
122
|
-
_emit(_int_to_be_bytes(self.timestamp))
|
|
123
|
-
# 3: accounts_hash
|
|
124
|
-
_emit(self.accounts_hash or b"")
|
|
125
|
-
# 4: transactions_total_fees
|
|
126
|
-
_emit(_int_to_be_bytes(self.transactions_total_fees))
|
|
127
|
-
# 5: transactions_hash
|
|
128
|
-
_emit(self.transactions_hash or b"")
|
|
129
|
-
# 6: receipts_hash
|
|
130
|
-
_emit(self.receipts_hash or b"")
|
|
131
|
-
# 7: delay_difficulty
|
|
132
|
-
_emit(_int_to_be_bytes(self.delay_difficulty))
|
|
133
|
-
# 8: delay_output
|
|
134
|
-
_emit(self.delay_output or b"")
|
|
135
|
-
# 9: validator_public_key
|
|
136
|
-
_emit(self.validator_public_key or b"")
|
|
137
|
-
|
|
138
|
-
# Build body list chain (head points to the first detail atom id)
|
|
139
|
-
body_atoms: List[Atom] = []
|
|
140
|
-
body_head = ZERO32
|
|
141
|
-
for child_id in reversed(details_ids):
|
|
142
|
-
node = Atom.from_data(data=child_id, next_hash=body_head, kind=AtomKind.BYTES)
|
|
143
|
-
body_head = node.object_id()
|
|
144
|
-
body_atoms.append(node)
|
|
145
|
-
body_atoms.reverse()
|
|
146
|
-
|
|
147
|
-
block_atoms.extend(body_atoms)
|
|
148
|
-
|
|
149
|
-
body_list_atom = Atom.from_data(data=body_head, kind=AtomKind.LIST)
|
|
150
|
-
self.body_hash = body_list_atom.object_id()
|
|
151
|
-
|
|
152
|
-
# Signature atom links to body list atom; type atom links to signature atom
|
|
153
|
-
sig_atom = Atom.from_data(data=self.signature, next_hash=self.body_hash, kind=AtomKind.BYTES)
|
|
154
|
-
type_atom = Atom.from_data(data=b"block", next_hash=sig_atom.object_id(), kind=AtomKind.SYMBOL)
|
|
155
|
-
|
|
156
|
-
block_atoms.append(body_list_atom)
|
|
157
|
-
block_atoms.append(sig_atom)
|
|
158
|
-
block_atoms.append(type_atom)
|
|
159
|
-
|
|
160
|
-
self.hash = type_atom.object_id()
|
|
161
|
-
return self.hash, block_atoms
|
|
162
|
-
|
|
163
|
-
@classmethod
|
|
164
|
-
def from_atom(cls, source: Any, block_id: bytes) -> "Block":
|
|
165
|
-
storage_get: Optional[Callable[[bytes], Optional[Atom]]]
|
|
166
|
-
if callable(source):
|
|
167
|
-
storage_get = source
|
|
168
|
-
else:
|
|
169
|
-
storage_get = getattr(source, "storage_get", None)
|
|
170
|
-
if not callable(storage_get):
|
|
171
|
-
raise TypeError(
|
|
172
|
-
"Block.from_atom requires a node with 'storage_get' or a callable storage getter"
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
def _atom_kind(atom: Optional[Atom]) -> Optional[AtomKind]:
|
|
176
|
-
kind_value = getattr(atom, "kind", None)
|
|
177
|
-
if isinstance(kind_value, AtomKind):
|
|
178
|
-
return kind_value
|
|
179
|
-
if isinstance(kind_value, int):
|
|
180
|
-
try:
|
|
181
|
-
return AtomKind(kind_value)
|
|
182
|
-
except ValueError:
|
|
183
|
-
return None
|
|
184
|
-
return None
|
|
185
|
-
|
|
186
|
-
def _require_atom(atom_id: Optional[bytes], context: str, expected_kind: Optional[AtomKind] = None) -> Atom:
|
|
187
|
-
if not atom_id or atom_id == ZERO32:
|
|
188
|
-
raise ValueError(f"missing {context}")
|
|
189
|
-
atom = storage_get(atom_id)
|
|
190
|
-
if atom is None:
|
|
191
|
-
raise ValueError(f"missing {context}")
|
|
192
|
-
if expected_kind is not None:
|
|
193
|
-
kind = _atom_kind(atom)
|
|
194
|
-
if kind is not expected_kind:
|
|
195
|
-
raise ValueError(f"malformed {context}")
|
|
196
|
-
return atom
|
|
197
|
-
|
|
198
|
-
def _read_list(head_id: Optional[bytes], context: str) -> List[bytes]:
|
|
199
|
-
entries: List[bytes] = []
|
|
200
|
-
current = head_id
|
|
201
|
-
if not current or current == ZERO32:
|
|
202
|
-
return entries
|
|
203
|
-
while current and current != ZERO32:
|
|
204
|
-
node = storage_get(current)
|
|
205
|
-
if node is None:
|
|
206
|
-
raise ValueError(f"missing list node while decoding {context}")
|
|
207
|
-
node_kind = _atom_kind(node)
|
|
208
|
-
if node_kind is not AtomKind.BYTES:
|
|
209
|
-
raise ValueError(f"list element must be bytes while decoding {context}")
|
|
210
|
-
if len(node.data) != len(ZERO32):
|
|
211
|
-
raise ValueError(f"list element payload has unexpected length while decoding {context}")
|
|
212
|
-
entries.append(node.data)
|
|
213
|
-
current = node.next
|
|
214
|
-
return entries
|
|
215
|
-
|
|
216
|
-
type_atom = _require_atom(block_id, "block type atom", AtomKind.SYMBOL)
|
|
217
|
-
if type_atom.data != b"block":
|
|
218
|
-
raise ValueError("not a block (type atom payload)")
|
|
219
|
-
|
|
220
|
-
sig_atom = _require_atom(type_atom.next, "block signature atom", AtomKind.BYTES)
|
|
221
|
-
body_list_id = sig_atom.next
|
|
222
|
-
body_list_atom = _require_atom(body_list_id, "block body list atom", AtomKind.LIST)
|
|
223
|
-
if body_list_atom.next and body_list_atom.next != ZERO32:
|
|
224
|
-
raise ValueError("malformed block (body list tail)")
|
|
225
|
-
|
|
226
|
-
body_child_ids = _read_list(body_list_atom.data, "block body")
|
|
227
|
-
|
|
228
|
-
details: List[bytes] = []
|
|
229
|
-
for idx, child_id in enumerate(body_child_ids):
|
|
230
|
-
if idx >= 10:
|
|
231
|
-
break
|
|
232
|
-
if not child_id or child_id == ZERO32:
|
|
233
|
-
details.append(b"")
|
|
234
|
-
continue
|
|
235
|
-
detail_atom = storage_get(child_id)
|
|
236
|
-
details.append(detail_atom.data if detail_atom is not None else b"")
|
|
237
|
-
|
|
238
|
-
if len(details) < 10:
|
|
239
|
-
details.extend([b""] * (10 - len(details)))
|
|
240
|
-
|
|
241
|
-
b = cls()
|
|
242
|
-
b.hash = block_id
|
|
243
|
-
b.body_hash = body_list_id
|
|
244
|
-
|
|
245
|
-
get = lambda i: details[i] if i < len(details) else b""
|
|
246
|
-
b.previous_block_hash = get(0) or ZERO32
|
|
247
|
-
b.previous_block = None
|
|
248
|
-
b.number = _be_bytes_to_int(get(1))
|
|
249
|
-
b.timestamp = _be_bytes_to_int(get(2))
|
|
250
|
-
b.accounts_hash = get(3) or None
|
|
251
|
-
b.transactions_total_fees = _be_bytes_to_int(get(4))
|
|
252
|
-
b.transactions_hash = get(5) or None
|
|
253
|
-
b.receipts_hash = get(6) or None
|
|
254
|
-
b.delay_difficulty = _be_bytes_to_int(get(7))
|
|
255
|
-
b.delay_output = get(8) or None
|
|
256
|
-
b.validator_public_key = get(9) or None
|
|
257
|
-
|
|
258
|
-
b.signature = sig_atom.data if sig_atom is not None else None
|
|
259
|
-
|
|
260
|
-
return b
|
|
261
|
-
|
|
262
|
-
def validate(self, storage_get: Callable[[bytes], Optional[Atom]]) -> bool:
|
|
263
|
-
"""Validate this block against storage.
|
|
264
|
-
|
|
265
|
-
Checks:
|
|
266
|
-
- Signature: signature must verify over the body list id using the
|
|
267
|
-
validator's public key.
|
|
268
|
-
- Timestamp monotonicity: if previous block exists (not ZERO32), this
|
|
269
|
-
block's timestamp must be >= previous.timestamp + 1.
|
|
270
|
-
"""
|
|
271
|
-
# Unverifiable if critical fields are missing
|
|
272
|
-
if not self.body_hash:
|
|
273
|
-
return False
|
|
274
|
-
if not self.signature:
|
|
275
|
-
return False
|
|
276
|
-
if not self.validator_public_key:
|
|
277
|
-
return False
|
|
278
|
-
if self.timestamp is None:
|
|
279
|
-
return False
|
|
280
|
-
|
|
281
|
-
# 1) Signature check over body hash
|
|
282
|
-
try:
|
|
283
|
-
pub = Ed25519PublicKey.from_public_bytes(bytes(self.validator_public_key))
|
|
284
|
-
pub.verify(self.signature, self.body_hash)
|
|
285
|
-
except InvalidSignature as e:
|
|
286
|
-
raise ValueError("invalid signature") from e
|
|
287
|
-
|
|
288
|
-
# 2) Timestamp monotonicity against previous block
|
|
289
|
-
prev_ts: Optional[int] = None
|
|
290
|
-
prev_hash = self.previous_block_hash or ZERO32
|
|
291
|
-
|
|
292
|
-
if self.previous_block is not None:
|
|
293
|
-
prev_ts = int(self.previous_block.timestamp or 0)
|
|
294
|
-
prev_hash = self.previous_block.hash or prev_hash or ZERO32
|
|
295
|
-
|
|
296
|
-
if prev_hash and prev_hash != ZERO32 and prev_ts is None:
|
|
297
|
-
# If previous block cannot be loaded, treat as unverifiable, not malicious
|
|
298
|
-
try:
|
|
299
|
-
prev = Block.from_atom(storage_get, prev_hash)
|
|
300
|
-
except Exception:
|
|
301
|
-
return False
|
|
302
|
-
prev_ts = int(prev.timestamp or 0)
|
|
303
|
-
|
|
304
|
-
if prev_hash and prev_hash != ZERO32:
|
|
305
|
-
if prev_ts is None:
|
|
306
|
-
return False
|
|
307
|
-
cur_ts = int(self.timestamp or 0)
|
|
308
|
-
if cur_ts < prev_ts + 1:
|
|
309
|
-
raise ValueError("timestamp must be at least prev+1")
|
|
310
|
-
|
|
311
|
-
return True
|
astreum/_consensus/genesis.py
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from typing import Any, List
|
|
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 ZERO32
|
|
11
|
-
from .._storage.patricia import PatriciaTrie
|
|
12
|
-
from ..utils.integer import int_to_bytes
|
|
13
|
-
|
|
14
|
-
TREASURY_ADDRESS = b"\x01" * 32
|
|
15
|
-
BURN_ADDRESS = b"\x00" * 32
|
|
16
|
-
def create_genesis_block(node: Any, validator_public_key: bytes, validator_secret_key: bytes) -> Block:
|
|
17
|
-
validator_pk = bytes(validator_public_key)
|
|
18
|
-
|
|
19
|
-
if len(validator_pk) != 32:
|
|
20
|
-
raise ValueError("validator_public_key must be 32 bytes")
|
|
21
|
-
|
|
22
|
-
# 1. Stake trie with single validator stake of 1 (encoded on 32 bytes).
|
|
23
|
-
stake_trie = PatriciaTrie()
|
|
24
|
-
stake_amount = int_to_bytes(1)
|
|
25
|
-
stake_trie.put(storage_node=node, key=validator_pk, value=stake_amount)
|
|
26
|
-
stake_root = stake_trie.root_hash
|
|
27
|
-
|
|
28
|
-
# 2. Account trie with treasury, burn, and validator accounts.
|
|
29
|
-
accounts_trie = PatriciaTrie()
|
|
30
|
-
|
|
31
|
-
treasury_account = Account.create(balance=1, data=stake_root, counter=0)
|
|
32
|
-
accounts_trie.put(storage_node=node, key=TREASURY_ADDRESS, value=treasury_account.hash)
|
|
33
|
-
|
|
34
|
-
burn_account = Account.create(balance=0, data=b"", counter=0)
|
|
35
|
-
accounts_trie.put(storage_node=node, key=BURN_ADDRESS, value=burn_account.hash)
|
|
36
|
-
|
|
37
|
-
validator_account = Account.create(balance=0, data=b"", counter=0)
|
|
38
|
-
accounts_trie.put(storage_node=node, key=validator_pk, value=validator_account.hash)
|
|
39
|
-
|
|
40
|
-
accounts_root = accounts_trie.root_hash
|
|
41
|
-
if accounts_root is None:
|
|
42
|
-
raise ValueError("genesis accounts trie is empty")
|
|
43
|
-
|
|
44
|
-
# 3. Assemble block metadata.
|
|
45
|
-
block = Block()
|
|
46
|
-
block.previous_block_hash = ZERO32
|
|
47
|
-
block.number = 0
|
|
48
|
-
block.timestamp = 0
|
|
49
|
-
block.accounts_hash = accounts_root
|
|
50
|
-
block.accounts = accounts_trie
|
|
51
|
-
block.transactions_total_fees = 0
|
|
52
|
-
block.transactions_hash = ZERO32
|
|
53
|
-
block.receipts_hash = ZERO32
|
|
54
|
-
block.delay_difficulty = 0
|
|
55
|
-
block.delay_output = b""
|
|
56
|
-
block.validator_public_key = validator_pk
|
|
57
|
-
block.transactions = []
|
|
58
|
-
block.receipts = []
|
|
59
|
-
|
|
60
|
-
# 4. Sign the block body with the validator secret key.
|
|
61
|
-
block.signature = b""
|
|
62
|
-
block.to_atom()
|
|
63
|
-
|
|
64
|
-
if block.body_hash is None:
|
|
65
|
-
raise ValueError("failed to materialise genesis block body")
|
|
66
|
-
|
|
67
|
-
secret = Ed25519PrivateKey.from_private_bytes(validator_secret_key)
|
|
68
|
-
block.signature = secret.sign(block.body_hash)
|
|
69
|
-
block_hash, _ = block.to_atom()
|
|
70
|
-
|
|
71
|
-
block.hash = block_hash
|
|
72
|
-
return block
|
astreum/_consensus/receipt.py
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Callable, List, Optional, Tuple
|
|
5
|
-
|
|
6
|
-
from .._storage.atom import Atom, AtomKind, ZERO32
|
|
7
|
-
|
|
8
|
-
STATUS_SUCCESS = 0
|
|
9
|
-
STATUS_FAILED = 1
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _int_to_be_bytes(value: Optional[int]) -> bytes:
|
|
13
|
-
if value is None:
|
|
14
|
-
return b""
|
|
15
|
-
value = int(value)
|
|
16
|
-
if value == 0:
|
|
17
|
-
return b"\x00"
|
|
18
|
-
size = (value.bit_length() + 7) // 8
|
|
19
|
-
return value.to_bytes(size, "big")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _be_bytes_to_int(data: Optional[bytes]) -> int:
|
|
23
|
-
if not data:
|
|
24
|
-
return 0
|
|
25
|
-
return int.from_bytes(data, "big")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass
|
|
29
|
-
class Receipt:
|
|
30
|
-
transaction_hash: bytes = ZERO32
|
|
31
|
-
cost: int = 0
|
|
32
|
-
logs: bytes = b""
|
|
33
|
-
status: int = 0
|
|
34
|
-
hash: bytes = ZERO32
|
|
35
|
-
atoms: List[Atom] = field(default_factory=list)
|
|
36
|
-
|
|
37
|
-
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
38
|
-
"""Serialise the receipt into Atom storage."""
|
|
39
|
-
if self.status not in (STATUS_SUCCESS, STATUS_FAILED):
|
|
40
|
-
raise ValueError("unsupported receipt status")
|
|
41
|
-
|
|
42
|
-
detail_specs = [
|
|
43
|
-
(bytes(self.transaction_hash), AtomKind.LIST),
|
|
44
|
-
(_int_to_be_bytes(self.status), AtomKind.BYTES),
|
|
45
|
-
(_int_to_be_bytes(self.cost), AtomKind.BYTES),
|
|
46
|
-
(bytes(self.logs), AtomKind.BYTES),
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
detail_atoms: List[Atom] = []
|
|
50
|
-
next_hash = ZERO32
|
|
51
|
-
for payload, kind in reversed(detail_specs):
|
|
52
|
-
atom = Atom.from_data(data=payload, next_hash=next_hash, kind=kind)
|
|
53
|
-
detail_atoms.append(atom)
|
|
54
|
-
next_hash = atom.object_id()
|
|
55
|
-
detail_atoms.reverse()
|
|
56
|
-
|
|
57
|
-
type_atom = Atom.from_data(
|
|
58
|
-
data=b"receipt",
|
|
59
|
-
next_hash=next_hash,
|
|
60
|
-
kind=AtomKind.SYMBOL,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
self.hash = type_atom.object_id()
|
|
64
|
-
atoms = detail_atoms + [type_atom]
|
|
65
|
-
return self.hash, atoms
|
|
66
|
-
|
|
67
|
-
def atomize(self) -> Tuple[bytes, List[Atom]]:
|
|
68
|
-
"""Generate atoms for this receipt and cache them."""
|
|
69
|
-
receipt_id, atoms = self.to_atom()
|
|
70
|
-
self.hash = receipt_id
|
|
71
|
-
self.atoms = atoms
|
|
72
|
-
return receipt_id, atoms
|
|
73
|
-
|
|
74
|
-
@classmethod
|
|
75
|
-
def from_atom(
|
|
76
|
-
cls,
|
|
77
|
-
storage_get: Callable[[bytes], Optional[Atom]],
|
|
78
|
-
receipt_id: bytes,
|
|
79
|
-
) -> Receipt:
|
|
80
|
-
"""Materialise a Receipt from Atom storage."""
|
|
81
|
-
def _atom_kind(atom: Optional[Atom]) -> Optional[AtomKind]:
|
|
82
|
-
kind_value = getattr(atom, "kind", None)
|
|
83
|
-
if isinstance(kind_value, AtomKind):
|
|
84
|
-
return kind_value
|
|
85
|
-
if isinstance(kind_value, int):
|
|
86
|
-
try:
|
|
87
|
-
return AtomKind(kind_value)
|
|
88
|
-
except ValueError:
|
|
89
|
-
return None
|
|
90
|
-
return None
|
|
91
|
-
|
|
92
|
-
type_atom = storage_get(receipt_id)
|
|
93
|
-
if type_atom is None:
|
|
94
|
-
raise ValueError("missing receipt type atom")
|
|
95
|
-
if _atom_kind(type_atom) is not AtomKind.SYMBOL:
|
|
96
|
-
raise ValueError("malformed receipt (type kind)")
|
|
97
|
-
if type_atom.data != b"receipt":
|
|
98
|
-
raise ValueError("not a receipt (type payload)")
|
|
99
|
-
|
|
100
|
-
details: List[Atom] = []
|
|
101
|
-
current = type_atom.next
|
|
102
|
-
while current and current != ZERO32 and len(details) < 4:
|
|
103
|
-
atom = storage_get(current)
|
|
104
|
-
if atom is None:
|
|
105
|
-
raise ValueError("missing receipt detail atom")
|
|
106
|
-
details.append(atom)
|
|
107
|
-
current = atom.next
|
|
108
|
-
|
|
109
|
-
if current and current != ZERO32:
|
|
110
|
-
raise ValueError("too many receipt fields")
|
|
111
|
-
if len(details) != 4:
|
|
112
|
-
raise ValueError("incomplete receipt fields")
|
|
113
|
-
|
|
114
|
-
tx_atom, status_atom, cost_atom, logs_atom = details
|
|
115
|
-
|
|
116
|
-
if _atom_kind(tx_atom) is not AtomKind.LIST:
|
|
117
|
-
raise ValueError("receipt transaction hash must be list-kind")
|
|
118
|
-
if any(_atom_kind(atom) is not AtomKind.BYTES for atom in [status_atom, cost_atom, logs_atom]):
|
|
119
|
-
raise ValueError("receipt detail atoms must be bytes-kind")
|
|
120
|
-
|
|
121
|
-
transaction_hash_bytes = tx_atom.data or ZERO32
|
|
122
|
-
status_bytes = status_atom.data
|
|
123
|
-
cost_bytes = cost_atom.data
|
|
124
|
-
logs_bytes = logs_atom.data
|
|
125
|
-
|
|
126
|
-
status_value = _be_bytes_to_int(status_bytes)
|
|
127
|
-
if status_value not in (STATUS_SUCCESS, STATUS_FAILED):
|
|
128
|
-
raise ValueError("unsupported receipt status")
|
|
129
|
-
|
|
130
|
-
return cls(
|
|
131
|
-
transaction_hash=transaction_hash_bytes or ZERO32,
|
|
132
|
-
cost=_be_bytes_to_int(cost_bytes),
|
|
133
|
-
logs=logs_bytes,
|
|
134
|
-
status=status_value,
|
|
135
|
-
hash=bytes(receipt_id),
|
|
136
|
-
)
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
from queue import Empty
|
|
5
|
-
from typing import Any, Callable
|
|
6
|
-
|
|
7
|
-
from ..block import Block
|
|
8
|
-
from ..transaction import apply_transaction
|
|
9
|
-
from ..._storage.atom import bytes_list_to_atoms
|
|
10
|
-
from ..._storage.patricia import PatriciaTrie
|
|
11
|
-
from ..._communication.message import Message, MessageTopic
|
|
12
|
-
from ..._communication.ping import Ping
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def make_validation_worker(
|
|
16
|
-
node: Any,
|
|
17
|
-
*,
|
|
18
|
-
current_validator: Callable[[Any], bytes],
|
|
19
|
-
) -> Callable[[], None]:
|
|
20
|
-
"""Build the validation worker bound to the given node."""
|
|
21
|
-
|
|
22
|
-
def _validation_worker() -> None:
|
|
23
|
-
stop = node._validation_stop_event
|
|
24
|
-
while not stop.is_set():
|
|
25
|
-
validation_public_key = getattr(node, "validation_public_key", None)
|
|
26
|
-
if not validation_public_key:
|
|
27
|
-
time.sleep(0.5)
|
|
28
|
-
continue
|
|
29
|
-
|
|
30
|
-
scheduled_validator = current_validator(node)
|
|
31
|
-
|
|
32
|
-
if scheduled_validator != validation_public_key:
|
|
33
|
-
time.sleep(0.5)
|
|
34
|
-
continue
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
current_hash = node._validation_transaction_queue.get_nowait()
|
|
38
|
-
except Empty:
|
|
39
|
-
time.sleep(0.1)
|
|
40
|
-
continue
|
|
41
|
-
|
|
42
|
-
# create thread to perform vdf
|
|
43
|
-
|
|
44
|
-
new_block = Block()
|
|
45
|
-
new_block.validator_public_key = validation_public_key
|
|
46
|
-
new_block.previous_block_hash = node.latest_block_hash
|
|
47
|
-
try:
|
|
48
|
-
new_block.previous_block = Block.from_atom(node, new_block.previous_block_hash)
|
|
49
|
-
except Exception:
|
|
50
|
-
continue
|
|
51
|
-
new_block.accounts = PatriciaTrie(root_hash=new_block.previous_block.accounts_hash)
|
|
52
|
-
|
|
53
|
-
# we may want to add a timer to process part of the txs only on a slow computer
|
|
54
|
-
while True:
|
|
55
|
-
try:
|
|
56
|
-
apply_transaction(node, new_block, current_hash)
|
|
57
|
-
except NotImplementedError:
|
|
58
|
-
node._validation_transaction_queue.put(current_hash)
|
|
59
|
-
time.sleep(0.5)
|
|
60
|
-
break
|
|
61
|
-
except Exception:
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
current_hash = node._validation_transaction_queue.get_nowait()
|
|
66
|
-
except Empty:
|
|
67
|
-
break
|
|
68
|
-
|
|
69
|
-
# create an atom list of transactions, save the list head hash as the block's transactions_hash
|
|
70
|
-
transactions = new_block.transactions or []
|
|
71
|
-
tx_hashes = [bytes(tx.hash) for tx in transactions if tx.hash]
|
|
72
|
-
head_hash, _ = bytes_list_to_atoms(tx_hashes)
|
|
73
|
-
new_block.transactions_hash = head_hash
|
|
74
|
-
|
|
75
|
-
receipts = new_block.receipts or []
|
|
76
|
-
receipt_hashes = [bytes(rcpt.hash) for rcpt in receipts if rcpt.hash]
|
|
77
|
-
receipts_head, _ = bytes_list_to_atoms(receipt_hashes)
|
|
78
|
-
new_block.receipts_hash = receipts_head
|
|
79
|
-
|
|
80
|
-
# get vdf result, default to 0 for now
|
|
81
|
-
|
|
82
|
-
# get timestamp or wait for a the next second from the previous block, rule is the next block must be atleast 1 second after the previous
|
|
83
|
-
now = time.time()
|
|
84
|
-
min_allowed = new_block.previous_block.timestamp + 1
|
|
85
|
-
if now < min_allowed:
|
|
86
|
-
time.sleep(max(0.0, min_allowed - now))
|
|
87
|
-
now = time.time()
|
|
88
|
-
new_block.timestamp = max(int(now), min_allowed)
|
|
89
|
-
|
|
90
|
-
# atomize block
|
|
91
|
-
new_block_hash, new_block_atoms = new_block.to_atom()
|
|
92
|
-
# put as own latest block hash
|
|
93
|
-
node.latest_block_hash = new_block_hash
|
|
94
|
-
|
|
95
|
-
# ping peers in the validation route to update there records
|
|
96
|
-
if node.validation_route and node.outgoing_queue and node.addresses:
|
|
97
|
-
route_peers = {
|
|
98
|
-
peer_key
|
|
99
|
-
for bucket in getattr(node.validation_route, "buckets", {}).values()
|
|
100
|
-
for peer_key in bucket
|
|
101
|
-
}
|
|
102
|
-
if route_peers:
|
|
103
|
-
ping_payload = Ping(
|
|
104
|
-
is_validator=True,
|
|
105
|
-
latest_block=new_block_hash,
|
|
106
|
-
).to_bytes()
|
|
107
|
-
|
|
108
|
-
message_bytes = Message(
|
|
109
|
-
topic=MessageTopic.PING,
|
|
110
|
-
content=ping_payload,
|
|
111
|
-
).to_bytes()
|
|
112
|
-
|
|
113
|
-
for address, peer_key in node.addresses.items():
|
|
114
|
-
if peer_key in route_peers:
|
|
115
|
-
try:
|
|
116
|
-
node.outgoing_queue.put((message_bytes, address))
|
|
117
|
-
except Exception:
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
# upload block atoms
|
|
121
|
-
|
|
122
|
-
# upload receipt atoms
|
|
123
|
-
# upload account atoms
|
|
124
|
-
|
|
125
|
-
return _validation_worker
|
astreum/_lispeum/__init__.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from .expression import Expr
|
|
2
|
-
from .environment import Env
|
|
3
|
-
from .low_evaluation import low_eval
|
|
4
|
-
from .meter import Meter
|
|
5
|
-
from .parser import parse, ParseError
|
|
6
|
-
from .tokenizer import tokenize
|
|
7
|
-
|
|
8
|
-
__all__ = [
|
|
9
|
-
"Env",
|
|
10
|
-
"Expr",
|
|
11
|
-
"low_eval",
|
|
12
|
-
"Meter",
|
|
13
|
-
"parse",
|
|
14
|
-
"tokenize",
|
|
15
|
-
"ParseError",
|
|
16
|
-
]
|
astreum/_lispeum/environment.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from ast import Expr
|
|
2
|
-
from typing import Dict, Optional
|
|
3
|
-
import uuid
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Env:
|
|
7
|
-
def __init__(
|
|
8
|
-
self,
|
|
9
|
-
data: Optional[Dict[str, Expr]] = None,
|
|
10
|
-
parent_id: Optional[uuid.UUID] = None,
|
|
11
|
-
):
|
|
12
|
-
self.data: Dict[bytes, Expr] = {} if data is None else data
|
|
13
|
-
self.parent_id = parent_id
|