astreum 0.2.40__py3-none-any.whl → 0.2.42__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/__init__.py +4 -0
- astreum/_consensus/account.py +95 -0
- astreum/_consensus/accounts.py +38 -0
- astreum/_consensus/block.py +9 -2
- astreum/_consensus/chain.py +65 -65
- astreum/_consensus/fork.py +99 -99
- astreum/_consensus/genesis.py +141 -0
- astreum/_consensus/receipt.py +11 -1
- astreum/_consensus/setup.py +15 -152
- astreum/_consensus/transaction.py +71 -23
- astreum/_consensus/workers/__init__.py +9 -0
- astreum/_consensus/workers/discovery.py +48 -0
- astreum/_consensus/workers/validation.py +122 -0
- astreum/_consensus/workers/verify.py +63 -0
- astreum/_storage/atom.py +24 -7
- astreum/models/block.py +22 -22
- astreum/node.py +755 -753
- astreum/utils/integer.py +25 -0
- {astreum-0.2.40.dist-info → astreum-0.2.42.dist-info}/METADATA +1 -1
- {astreum-0.2.40.dist-info → astreum-0.2.42.dist-info}/RECORD +23 -28
- astreum/lispeum/__init__.py +0 -0
- astreum/lispeum/environment.py +0 -40
- astreum/lispeum/expression.py +0 -86
- astreum/lispeum/parser.py +0 -41
- astreum/lispeum/tokenizer.py +0 -52
- astreum/models/account.py +0 -91
- astreum/models/accounts.py +0 -34
- astreum/models/transaction.py +0 -106
- astreum/relay/__init__.py +0 -0
- astreum/relay/peer.py +0 -9
- astreum/relay/route.py +0 -25
- astreum/relay/setup.py +0 -58
- {astreum-0.2.40.dist-info → astreum-0.2.42.dist-info}/WHEEL +0 -0
- {astreum-0.2.40.dist-info → astreum-0.2.42.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.40.dist-info → astreum-0.2.42.dist-info}/top_level.txt +0 -0
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.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,7 +1,7 @@
|
|
|
1
1
|
astreum/__init__.py,sha256=9tzA27B_eG5wRF1SAWJIV7xTmCcR1QFc123b_cvFOa4,345
|
|
2
2
|
astreum/_node.py,sha256=3fpfULVs3MrPBR-ymlvPyuZMi8lv0a8JBnxPb2oFOTU,2214
|
|
3
3
|
astreum/format.py,sha256=X4tG5GGPweNCE54bHYkLFiuLTbmpy5upO_s1Cef-MGA,2711
|
|
4
|
-
astreum/node.py,sha256=
|
|
4
|
+
astreum/node.py,sha256=MmlK3jaANTMB3ZAxR8IaSc82OS9meJmVawYIVURADbg,39689
|
|
5
5
|
astreum/_communication/__init__.py,sha256=XJui0yOcfAur4HKt-8sSRlwB-MSU1rchkuOAY-nKDOE,207
|
|
6
6
|
astreum/_communication/message.py,sha256=zNjUnt1roJRHMEoiLkcA7VK_TsHXok8PYEF7KTCqKnQ,3303
|
|
7
7
|
astreum/_communication/peer.py,sha256=DT2vJOzeLyyOj7vTDQ1u1lF5Vc7Epj0Hhie-YfDrzfA,425
|
|
@@ -9,14 +9,20 @@ astreum/_communication/ping.py,sha256=u_DQTZJsbMdYiDDqjdZDsLaN5na2m9WZjVeEM3zq9_
|
|
|
9
9
|
astreum/_communication/route.py,sha256=fbVXY0xF3O-k7dY9TuCsr6_XxD3m7Cb9ugVacQZ6GSk,2490
|
|
10
10
|
astreum/_communication/setup.py,sha256=TzXpc8dajV31BJhHQMMcb5UdaFrxb1xlYSj58vc_5mg,9072
|
|
11
11
|
astreum/_communication/util.py,sha256=bJ3td3naDzmCelAJQpLwiDMoRBkijQl9YLROjsWyOrI,1256
|
|
12
|
-
astreum/_consensus/__init__.py,sha256=
|
|
13
|
-
astreum/_consensus/
|
|
14
|
-
astreum/_consensus/
|
|
15
|
-
astreum/_consensus/
|
|
16
|
-
astreum/_consensus/
|
|
17
|
-
astreum/_consensus/
|
|
18
|
-
astreum/_consensus/
|
|
19
|
-
astreum/_consensus/
|
|
12
|
+
astreum/_consensus/__init__.py,sha256=gOCpvnIeO17CGjUGr0odaKNvGEggmDRXfT5IuyrYtcM,376
|
|
13
|
+
astreum/_consensus/account.py,sha256=XCBWRapmFMrLCzz0wLtuuIaDRyNN9IKyOGc4dEl8W0s,3294
|
|
14
|
+
astreum/_consensus/accounts.py,sha256=zGq2BCMJPtD_lzcj4jqMzB2Foc3ptxXSPhc9zypB1T0,1106
|
|
15
|
+
astreum/_consensus/block.py,sha256=v6U2y8br9PVKGn0Q8fLVOtEODlXayPtEd_Wqprem_TU,12358
|
|
16
|
+
astreum/_consensus/chain.py,sha256=WwUeLrdg7uj--ZsUxse6xFlzO2QeQQggyKSL49KhfU0,2768
|
|
17
|
+
astreum/_consensus/fork.py,sha256=dK5DtT9hWCj_rQs6MS1c1bcGBbkgVTBPIOudYbqS9vw,3825
|
|
18
|
+
astreum/_consensus/genesis.py,sha256=oED7AHw0fPJgMu0OmhbbikJcbItw_bxVBVU8RlB2rJM,4766
|
|
19
|
+
astreum/_consensus/receipt.py,sha256=GPLspqVJHnyBr1cKmBoClsJyeEUecYmg3_acz4L4rRo,6031
|
|
20
|
+
astreum/_consensus/setup.py,sha256=agwqfOemdLqE0Z1zrPV2XHVmZ9sAxp8Bh4k6e2u-t8w,2385
|
|
21
|
+
astreum/_consensus/transaction.py,sha256=VRp06ExbjAebiPcTZEX7PH-zwjzy4CLcf5kclyooL1k,7475
|
|
22
|
+
astreum/_consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
|
|
23
|
+
astreum/_consensus/workers/discovery.py,sha256=X1yjKGjLSApMJ9mgWbnc7N21ALD09khDf-in-M45Mis,1683
|
|
24
|
+
astreum/_consensus/workers/validation.py,sha256=WcHLOs2NLMTrJpErghBXWeW2aqtX3-xGPo-HcySdEZ8,4814
|
|
25
|
+
astreum/_consensus/workers/verify.py,sha256=uqmf2UICj_eBFeGDpGyzKQLtz9qeCbOtz5euwNqkZmw,1924
|
|
20
26
|
astreum/_lispeum/__init__.py,sha256=LAy2Z-gBBQlByBHRGUKaaQrOB7QzFEEyGRtInmwTpfU,304
|
|
21
27
|
astreum/_lispeum/environment.py,sha256=pJ0rjp9GoQxHhDiPIVei0jP7dZ_Pznso2O_tpp94-Ik,328
|
|
22
28
|
astreum/_lispeum/expression.py,sha256=io8tbCer_1TJee77yRbcNI5q-DPFGa8xZiC80tGvRRQ,1063
|
|
@@ -26,34 +32,23 @@ astreum/_lispeum/meter.py,sha256=5q2PFW7_jmgKVM1-vwE4RRjMfPEthUA4iu1CwR-Axws,505
|
|
|
26
32
|
astreum/_lispeum/parser.py,sha256=WOW3sSZWkIzPEy3fYTyl0lrtkMxHL9zRrNSYztPDemQ,2271
|
|
27
33
|
astreum/_lispeum/tokenizer.py,sha256=P68uIj4aPKzjuCJ85jfzRi67QztpuXIOC1vvLQueBI4,552
|
|
28
34
|
astreum/_storage/__init__.py,sha256=EmKZNAZmo3UVE3ekOOuckwFnBVjpa0Sy8Oxg72Lgdxc,53
|
|
29
|
-
astreum/_storage/atom.py,sha256=
|
|
35
|
+
astreum/_storage/atom.py,sha256=YFjvfMhniInch13iaKGpw4CCxxgyWonniryb-Rfse4A,4177
|
|
30
36
|
astreum/_storage/patricia.py,sha256=Eh8AO2_J8WCai4kyuML_0Jb_zntgGlq-VPljnSjss10,14851
|
|
31
37
|
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
38
|
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
33
39
|
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
34
40
|
astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
|
|
35
41
|
astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
|
|
36
|
-
astreum/lispeum/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
astreum/lispeum/environment.py,sha256=wolwt9psDl62scgjaVG0G59xlBs1AM4NPgryUbxzG_4,1220
|
|
38
|
-
astreum/lispeum/expression.py,sha256=K8gFifDaHu394bs9qnpvP8tjeiymFGQpnDC_iW9nU4E,2379
|
|
39
|
-
astreum/lispeum/parser.py,sha256=jQRzZYvBuSg8t_bxsbt1-WcHaR_LPveHNX7Qlxhaw-M,1165
|
|
40
|
-
astreum/lispeum/tokenizer.py,sha256=J-I7MEd0r2ZoVqxvRPlu-Afe2ZdM0tKXXhf1R4SxYTo,1429
|
|
41
42
|
astreum/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
-
astreum/models/
|
|
43
|
-
astreum/models/accounts.py,sha256=aFSEWlq6zRf65-KGAdNGqEJyNVY3fpKhx8y1vU6sgSc,1164
|
|
44
|
-
astreum/models/block.py,sha256=XNuzLaYKGX9Mv_naqWATfPskpWmeBh6FYzo4TGimLwg,18120
|
|
43
|
+
astreum/models/block.py,sha256=_dgas0zW9OLFV7C1Bo-laxa102klqpJrCAG3DnwQ51U,18092
|
|
45
44
|
astreum/models/merkle.py,sha256=lvWJa9nmrBL0n_2h_uNqpB_9a5s5Hn1FceRLx0IZIVQ,6778
|
|
46
45
|
astreum/models/patricia.py,sha256=ohmXrcaz7Ae561tyC4u4iPOkQPkKr8N0IWJek4upFIg,13392
|
|
47
|
-
astreum/models/transaction.py,sha256=MkLL5YX18kIf9-O4LBaZ4eWjkXDAaYIrDcDehbDZoqg,3038
|
|
48
|
-
astreum/relay/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
49
|
-
astreum/relay/peer.py,sha256=94rNkHfsvYfq-ijLDR9QQkEJZ1meMr17HsaNYDBB8kg,398
|
|
50
|
-
astreum/relay/route.py,sha256=enWT_1260LJq-L-zK-jtacQ8LbZGquNO9yj-9IglSXE,1232
|
|
51
|
-
astreum/relay/setup.py,sha256=ynvGaJdlDtw_f5LLiow2Wo7IRzUjvgk8eSr1Sv4_zTg,2090
|
|
52
46
|
astreum/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
47
|
astreum/storage/object.py,sha256=knFlvw_tpcC4twSu1DGNpHX31wlANN8E5dgEqIfU--Q,2041
|
|
54
48
|
astreum/storage/setup.py,sha256=1-9ztEFI_BvRDvAA0lAn4mFya8iq65THTArlj--M3Hg,626
|
|
55
|
-
astreum
|
|
56
|
-
astreum-0.2.
|
|
57
|
-
astreum-0.2.
|
|
58
|
-
astreum-0.2.
|
|
59
|
-
astreum-0.2.
|
|
49
|
+
astreum/utils/integer.py,sha256=iQt-klWOYVghu_NOT341MmHbOle4FDT3by4PNKNXscg,736
|
|
50
|
+
astreum-0.2.42.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
51
|
+
astreum-0.2.42.dist-info/METADATA,sha256=xb_Bi75R1izIliHmHZQiJ93TxWPn6l9Z9EiN4IqVTAc,6181
|
|
52
|
+
astreum-0.2.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
astreum-0.2.42.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
54
|
+
astreum-0.2.42.dist-info/RECORD,,
|
astreum/lispeum/__init__.py
DELETED
|
File without changes
|
astreum/lispeum/environment.py
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Optional
|
|
2
|
-
import uuid
|
|
3
|
-
|
|
4
|
-
from astreum.lispeum.expression import Expr
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Env:
|
|
8
|
-
def __init__(
|
|
9
|
-
self,
|
|
10
|
-
data: Optional[Dict[str, Expr]] = None,
|
|
11
|
-
parent_id: Optional[uuid.UUID] = None,
|
|
12
|
-
max_exprs: Optional[int] = 8,
|
|
13
|
-
):
|
|
14
|
-
self.data: Dict[str, Expr] = data if data is not None else {}
|
|
15
|
-
self.parent_id: Optional[uuid.UUID] = parent_id
|
|
16
|
-
self.max_exprs: Optional[int] = max_exprs
|
|
17
|
-
|
|
18
|
-
def put(self, name: str, value: Expr) -> None:
|
|
19
|
-
if (
|
|
20
|
-
self.max_exprs is not None
|
|
21
|
-
and name not in self.data
|
|
22
|
-
and len(self.data) >= self.max_exprs
|
|
23
|
-
):
|
|
24
|
-
raise RuntimeError(
|
|
25
|
-
f"environment full: {len(self.data)} ≥ max_exprs={self.max_exprs}"
|
|
26
|
-
)
|
|
27
|
-
self.data[name] = value
|
|
28
|
-
|
|
29
|
-
def get(self, name: str) -> Optional[Expr]:
|
|
30
|
-
return self.data.get(name)
|
|
31
|
-
|
|
32
|
-
def pop(self, name: str) -> Optional[Expr]:
|
|
33
|
-
return self.data.pop(name, None)
|
|
34
|
-
|
|
35
|
-
def __repr__(self) -> str:
|
|
36
|
-
return (
|
|
37
|
-
f"Env(size={len(self.data)}, "
|
|
38
|
-
f"max_exprs={self.max_exprs}, "
|
|
39
|
-
f"parent_id={self.parent_id})"
|
|
40
|
-
)
|
astreum/lispeum/expression.py
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
from typing import List, Optional, Union
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Expr:
|
|
6
|
-
class ListExpr:
|
|
7
|
-
def __init__(self, elements: List['Expr']):
|
|
8
|
-
self.elements = elements
|
|
9
|
-
|
|
10
|
-
def __eq__(self, other):
|
|
11
|
-
if not isinstance(other, Expr.ListExpr):
|
|
12
|
-
return NotImplemented
|
|
13
|
-
return self.elements == other.elements
|
|
14
|
-
|
|
15
|
-
def __ne__(self, other):
|
|
16
|
-
return not self.__eq__(other)
|
|
17
|
-
|
|
18
|
-
@property
|
|
19
|
-
def value(self):
|
|
20
|
-
inner = " ".join(str(e) for e in self.elements)
|
|
21
|
-
return f"({inner})"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def __repr__(self):
|
|
25
|
-
if not self.elements:
|
|
26
|
-
return "()"
|
|
27
|
-
|
|
28
|
-
inner = " ".join(str(e) for e in self.elements)
|
|
29
|
-
return f"({inner})"
|
|
30
|
-
|
|
31
|
-
def __iter__(self):
|
|
32
|
-
return iter(self.elements)
|
|
33
|
-
|
|
34
|
-
def __getitem__(self, index: Union[int, slice]):
|
|
35
|
-
return self.elements[index]
|
|
36
|
-
|
|
37
|
-
def __len__(self):
|
|
38
|
-
return len(self.elements)
|
|
39
|
-
|
|
40
|
-
class Symbol:
|
|
41
|
-
def __init__(self, value: str):
|
|
42
|
-
self.value = value
|
|
43
|
-
|
|
44
|
-
def __repr__(self):
|
|
45
|
-
return self.value
|
|
46
|
-
|
|
47
|
-
class Integer:
|
|
48
|
-
def __init__(self, value: int):
|
|
49
|
-
self.value = value
|
|
50
|
-
|
|
51
|
-
def __repr__(self):
|
|
52
|
-
return str(self.value)
|
|
53
|
-
|
|
54
|
-
class String:
|
|
55
|
-
def __init__(self, value: str):
|
|
56
|
-
self.value = value
|
|
57
|
-
|
|
58
|
-
def __repr__(self):
|
|
59
|
-
return f'"{self.value}"'
|
|
60
|
-
|
|
61
|
-
class Boolean:
|
|
62
|
-
def __init__(self, value: bool):
|
|
63
|
-
self.value = value
|
|
64
|
-
|
|
65
|
-
def __repr__(self):
|
|
66
|
-
return "true" if self.value else "false"
|
|
67
|
-
|
|
68
|
-
class Function:
|
|
69
|
-
def __init__(self, params: List[str], body: 'Expr'):
|
|
70
|
-
self.params = params
|
|
71
|
-
self.body = body
|
|
72
|
-
|
|
73
|
-
def __repr__(self):
|
|
74
|
-
params_str = " ".join(self.params)
|
|
75
|
-
body_str = str(self.body)
|
|
76
|
-
return f"(fn ({params_str}) {body_str})"
|
|
77
|
-
|
|
78
|
-
class Error:
|
|
79
|
-
def __init__(self, message: str, origin: Optional['Expr'] = None):
|
|
80
|
-
self.message = message
|
|
81
|
-
self.origin = origin
|
|
82
|
-
|
|
83
|
-
def __repr__(self):
|
|
84
|
-
if self.origin is None:
|
|
85
|
-
return f'(error "{self.message}")'
|
|
86
|
-
return f'(error "{self.message}" in {self.origin})'
|
astreum/lispeum/parser.py
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from typing import List, Tuple
|
|
2
|
-
from ..node import Expr
|
|
3
|
-
|
|
4
|
-
class ParseError(Exception):
|
|
5
|
-
pass
|
|
6
|
-
|
|
7
|
-
def parse(tokens: List[str]) -> Tuple[Expr, List[str]]:
|
|
8
|
-
if not tokens:
|
|
9
|
-
raise ParseError("Unexpected end of input")
|
|
10
|
-
|
|
11
|
-
first_token, *rest = tokens
|
|
12
|
-
|
|
13
|
-
if first_token == '(':
|
|
14
|
-
if not rest:
|
|
15
|
-
raise ParseError("Expected token after '('")
|
|
16
|
-
|
|
17
|
-
list_items = []
|
|
18
|
-
inner_tokens = rest
|
|
19
|
-
|
|
20
|
-
while inner_tokens:
|
|
21
|
-
if inner_tokens[0] == ')':
|
|
22
|
-
return Expr.ListExpr(list_items), inner_tokens[1:]
|
|
23
|
-
|
|
24
|
-
expr, inner_tokens = parse(inner_tokens)
|
|
25
|
-
list_items.append(expr)
|
|
26
|
-
|
|
27
|
-
raise ParseError("Expected closing ')'")
|
|
28
|
-
|
|
29
|
-
elif first_token == ')':
|
|
30
|
-
raise ParseError("Unexpected closing parenthesis")
|
|
31
|
-
|
|
32
|
-
elif first_token.startswith('"') and first_token.endswith('"'):
|
|
33
|
-
string_content = first_token[1:-1]
|
|
34
|
-
return Expr.String(string_content), rest
|
|
35
|
-
|
|
36
|
-
else:
|
|
37
|
-
try:
|
|
38
|
-
number = int(first_token)
|
|
39
|
-
return Expr.Integer(number), rest
|
|
40
|
-
except ValueError:
|
|
41
|
-
return Expr.Symbol(first_token), rest
|
astreum/lispeum/tokenizer.py
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# Tokenizer function
|
|
4
|
-
from typing import List
|
|
5
|
-
|
|
6
|
-
class ParseError(Exception):
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
def tokenize(input: str) -> List[str]:
|
|
10
|
-
tokens = []
|
|
11
|
-
current_token = ""
|
|
12
|
-
in_string = False
|
|
13
|
-
escape = False
|
|
14
|
-
|
|
15
|
-
for char in input:
|
|
16
|
-
if in_string:
|
|
17
|
-
if escape:
|
|
18
|
-
current_token += char
|
|
19
|
-
escape = False
|
|
20
|
-
elif char == '\\':
|
|
21
|
-
escape = True
|
|
22
|
-
elif char == '"':
|
|
23
|
-
in_string = False
|
|
24
|
-
tokens.append(f'"{current_token}"')
|
|
25
|
-
current_token = ""
|
|
26
|
-
else:
|
|
27
|
-
current_token += char
|
|
28
|
-
else:
|
|
29
|
-
if char.isspace():
|
|
30
|
-
if current_token:
|
|
31
|
-
tokens.append(current_token)
|
|
32
|
-
current_token = ""
|
|
33
|
-
elif char == '(' or char == ')':
|
|
34
|
-
if current_token:
|
|
35
|
-
tokens.append(current_token)
|
|
36
|
-
current_token = ""
|
|
37
|
-
tokens.append(char)
|
|
38
|
-
elif char == '"':
|
|
39
|
-
if current_token:
|
|
40
|
-
tokens.append(current_token)
|
|
41
|
-
current_token = ""
|
|
42
|
-
in_string = True
|
|
43
|
-
else:
|
|
44
|
-
current_token += char
|
|
45
|
-
|
|
46
|
-
if in_string:
|
|
47
|
-
raise ParseError("Unterminated string literal")
|
|
48
|
-
|
|
49
|
-
if current_token:
|
|
50
|
-
tokens.append(current_token)
|
|
51
|
-
|
|
52
|
-
return tokens
|
astreum/models/account.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Optional, Callable
|
|
4
|
-
|
|
5
|
-
from .merkle import MerkleTree
|
|
6
|
-
|
|
7
|
-
_FIELD_ORDER = ["balance", "data", "nonce"]
|
|
8
|
-
_INT_FIELDS = {"balance", "nonce"}
|
|
9
|
-
|
|
10
|
-
def _int_to_min_bytes(i: int) -> bytes:
|
|
11
|
-
length = (i.bit_length() + 7) // 8 or 1
|
|
12
|
-
return i.to_bytes(length, "big")
|
|
13
|
-
|
|
14
|
-
class Account:
|
|
15
|
-
def __init__(
|
|
16
|
-
self,
|
|
17
|
-
body_hash: bytes,
|
|
18
|
-
*,
|
|
19
|
-
body_tree: Optional[MerkleTree] = None,
|
|
20
|
-
get_node_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
21
|
-
) -> None:
|
|
22
|
-
self._body_hash = body_hash
|
|
23
|
-
self._body_tree = body_tree
|
|
24
|
-
self._balance: Optional[int] = None
|
|
25
|
-
self._data: Optional[bytes] = None
|
|
26
|
-
self._nonce: Optional[int] = None
|
|
27
|
-
|
|
28
|
-
if self._body_tree and get_node_fn:
|
|
29
|
-
self._body_tree._node_get = get_node_fn
|
|
30
|
-
|
|
31
|
-
@classmethod
|
|
32
|
-
def create(
|
|
33
|
-
cls,
|
|
34
|
-
balance: int,
|
|
35
|
-
data: bytes,
|
|
36
|
-
nonce: int,
|
|
37
|
-
) -> Account:
|
|
38
|
-
"""Build an Account body from explicit fields in alphabetical order."""
|
|
39
|
-
# prepare values dict
|
|
40
|
-
values = {"balance": balance, "data": data, "nonce": nonce}
|
|
41
|
-
|
|
42
|
-
# build leaves in alphabetical order
|
|
43
|
-
leaves: list[bytes] = []
|
|
44
|
-
for name in _FIELD_ORDER:
|
|
45
|
-
v = values[name]
|
|
46
|
-
if name in _INT_FIELDS:
|
|
47
|
-
leaves.append(_int_to_min_bytes(v)) # type: ignore[arg-type]
|
|
48
|
-
else:
|
|
49
|
-
leaves.append(v)
|
|
50
|
-
|
|
51
|
-
tree = MerkleTree.from_leaves(leaves)
|
|
52
|
-
return cls(tree.root_hash, body_tree=tree)
|
|
53
|
-
|
|
54
|
-
def body_hash(self) -> bytes:
|
|
55
|
-
"""Return the Merkle root of the account body."""
|
|
56
|
-
return self._body_hash
|
|
57
|
-
|
|
58
|
-
def _require_tree(self) -> MerkleTree:
|
|
59
|
-
if not self._body_tree:
|
|
60
|
-
raise ValueError("Body tree unavailable for this Account")
|
|
61
|
-
return self._body_tree
|
|
62
|
-
|
|
63
|
-
def balance(self) -> int:
|
|
64
|
-
"""Fetch & cache the `balance` field (leaf 0)."""
|
|
65
|
-
if self._balance is not None:
|
|
66
|
-
return self._balance
|
|
67
|
-
raw = self._require_tree().get(0)
|
|
68
|
-
if raw is None:
|
|
69
|
-
raise ValueError("Merkle leaf 0 (balance) missing")
|
|
70
|
-
self._balance = int.from_bytes(raw, "big")
|
|
71
|
-
return self._balance
|
|
72
|
-
|
|
73
|
-
def data(self) -> bytes:
|
|
74
|
-
"""Fetch & cache the `data` field (leaf 1)."""
|
|
75
|
-
if self._data is not None:
|
|
76
|
-
return self._data
|
|
77
|
-
raw = self._require_tree().get(1)
|
|
78
|
-
if raw is None:
|
|
79
|
-
raise ValueError("Merkle leaf 1 (data) missing")
|
|
80
|
-
self._data = raw
|
|
81
|
-
return self._data
|
|
82
|
-
|
|
83
|
-
def nonce(self) -> int:
|
|
84
|
-
"""Fetch & cache the `nonce` field (leaf 2)."""
|
|
85
|
-
if self._nonce is not None:
|
|
86
|
-
return self._nonce
|
|
87
|
-
raw = self._require_tree().get(2)
|
|
88
|
-
if raw is None:
|
|
89
|
-
raise ValueError("Merkle leaf 2 (nonce) missing")
|
|
90
|
-
self._nonce = int.from_bytes(raw, "big")
|
|
91
|
-
return self._nonce
|
astreum/models/accounts.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from typing import Dict, Optional, Callable
|
|
3
|
-
from .patricia import PatriciaTrie
|
|
4
|
-
from .account import Account
|
|
5
|
-
|
|
6
|
-
class Accounts:
|
|
7
|
-
def __init__(
|
|
8
|
-
self,
|
|
9
|
-
root_hash: Optional[bytes] = None,
|
|
10
|
-
global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
11
|
-
) -> None:
|
|
12
|
-
self._global_get_fn = global_get_fn
|
|
13
|
-
self._trie = PatriciaTrie(node_get=global_get_fn, root_hash=root_hash)
|
|
14
|
-
self._cache: Dict[bytes, Account] = {}
|
|
15
|
-
|
|
16
|
-
@property
|
|
17
|
-
def root_hash(self) -> Optional[bytes]:
|
|
18
|
-
return self._trie.root_hash
|
|
19
|
-
|
|
20
|
-
def get_account(self, address: bytes) -> Optional[Account]:
|
|
21
|
-
if address in self._cache:
|
|
22
|
-
return self._cache[address]
|
|
23
|
-
|
|
24
|
-
body_hash: Optional[bytes] = self._trie.get(address)
|
|
25
|
-
if body_hash is None:
|
|
26
|
-
return None
|
|
27
|
-
|
|
28
|
-
acc = Account(body_hash, get_node_fn=self._global_get_fn)
|
|
29
|
-
self._cache[address] = acc
|
|
30
|
-
return acc
|
|
31
|
-
|
|
32
|
-
def set_account(self, address: bytes, account: Account) -> None:
|
|
33
|
-
self._cache[address] = account
|
|
34
|
-
self._trie.put(address, account.body_hash())
|
astreum/models/transaction.py
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Dict, List, Optional, Union, Any, Callable
|
|
4
|
-
|
|
5
|
-
from .merkle import MerkleTree
|
|
6
|
-
from ..crypto import ed25519
|
|
7
|
-
|
|
8
|
-
_FIELD_ORDER: List[str] = [
|
|
9
|
-
"amount",
|
|
10
|
-
"balance",
|
|
11
|
-
"fee",
|
|
12
|
-
"nonce",
|
|
13
|
-
"recipient_pk",
|
|
14
|
-
"sender_pk",
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
_INT_FIELDS = {"amount", "balance", "fee", "nonce"}
|
|
18
|
-
|
|
19
|
-
def _int_to_min_bytes(i: int) -> bytes:
|
|
20
|
-
length = (i.bit_length() + 7) // 8 or 1
|
|
21
|
-
return i.to_bytes(length, "big")
|
|
22
|
-
|
|
23
|
-
class Transaction:
|
|
24
|
-
# init
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
tx_hash: bytes,
|
|
28
|
-
*,
|
|
29
|
-
tree: Optional[MerkleTree] = None,
|
|
30
|
-
global_get_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
31
|
-
) -> None:
|
|
32
|
-
self._hash = tx_hash
|
|
33
|
-
self._tree = tree
|
|
34
|
-
self._field_cache: Dict[str, Union[int, bytes]] = {}
|
|
35
|
-
|
|
36
|
-
if self._tree and global_get_fn:
|
|
37
|
-
self._tree.global_get_fn = global_get_fn
|
|
38
|
-
|
|
39
|
-
@classmethod
|
|
40
|
-
def create(
|
|
41
|
-
cls,
|
|
42
|
-
*,
|
|
43
|
-
amount: int,
|
|
44
|
-
balance: int,
|
|
45
|
-
fee: int,
|
|
46
|
-
nonce: int,
|
|
47
|
-
recipient_pk: bytes,
|
|
48
|
-
sender_pk: bytes,
|
|
49
|
-
) -> "Transaction":
|
|
50
|
-
vals: Dict[str, Any] = locals().copy()
|
|
51
|
-
leaves = [
|
|
52
|
-
vals[name] if isinstance(vals[name], bytes) else _int_to_min_bytes(vals[name])
|
|
53
|
-
for name in _FIELD_ORDER
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
tree = MerkleTree.from_leaves(leaves)
|
|
57
|
-
return cls(tx_hash=tree.root_hash, tree=tree)
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def hash(self) -> bytes:
|
|
61
|
-
return self._hash
|
|
62
|
-
|
|
63
|
-
def _require_tree(self) -> MerkleTree:
|
|
64
|
-
if not self._tree:
|
|
65
|
-
raise ValueError("Merkle tree unavailable for this Transaction")
|
|
66
|
-
return self._tree
|
|
67
|
-
|
|
68
|
-
def _field(self, idx: int, name: str) -> Union[int, bytes]:
|
|
69
|
-
if name in self._field_cache:
|
|
70
|
-
return self._field_cache[name]
|
|
71
|
-
|
|
72
|
-
raw = self._require_tree().get(idx)
|
|
73
|
-
if raw is None:
|
|
74
|
-
raise ValueError(f"Leaf {idx} (‘{name}’) missing from Merkle tree")
|
|
75
|
-
|
|
76
|
-
value = int.from_bytes(raw, "big") if name in _INT_FIELDS else raw
|
|
77
|
-
self._field_cache[name] = value
|
|
78
|
-
return value
|
|
79
|
-
|
|
80
|
-
def get_amount(self) -> int:
|
|
81
|
-
return self._field(0, "amount")
|
|
82
|
-
|
|
83
|
-
def get_balance(self) -> int:
|
|
84
|
-
return self._field(1, "balance")
|
|
85
|
-
|
|
86
|
-
def get_fee(self) -> int:
|
|
87
|
-
return self._field(2, "fee")
|
|
88
|
-
|
|
89
|
-
def get_nonce(self) -> int:
|
|
90
|
-
return self._field(3, "nonce")
|
|
91
|
-
|
|
92
|
-
def get_recipient_pk(self) -> bytes:
|
|
93
|
-
return self._field(4, "recipient_pk")
|
|
94
|
-
|
|
95
|
-
def get_sender_pk(self) -> bytes:
|
|
96
|
-
return self._field(5, "sender_pk")
|
|
97
|
-
|
|
98
|
-
def sign(self, priv: ed25519.Ed25519PrivateKey) -> bytes:
|
|
99
|
-
return priv.sign(self.hash)
|
|
100
|
-
|
|
101
|
-
def verify_signature(self, sig: bytes, sender_pk: bytes) -> bool:
|
|
102
|
-
try:
|
|
103
|
-
ed25519.Ed25519PublicKey.from_public_bytes(sender_pk).verify(sig, self.hash)
|
|
104
|
-
return True
|
|
105
|
-
except Exception:
|
|
106
|
-
return False
|
astreum/relay/__init__.py
DELETED
|
File without changes
|
astreum/relay/peer.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
|
|
4
|
-
class Peer:
|
|
5
|
-
shared_key: bytes
|
|
6
|
-
timestamp: datetime
|
|
7
|
-
def __init__(self, my_sec_key: X25519PrivateKey, peer_pub_key: X25519PublicKey):
|
|
8
|
-
self.shared_key = my_sec_key.exchange(peer_pub_key)
|
|
9
|
-
self.timestamp = datetime.now(timezone.utc)
|
astreum/relay/route.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
|
|
3
|
-
|
|
4
|
-
class Route:
|
|
5
|
-
def __init__(self, relay_public_key: X25519PublicKey, bucket_size: int = 16):
|
|
6
|
-
self.relay_public_key_bytes = relay_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
|
|
7
|
-
self.bucket_size = bucket_size
|
|
8
|
-
self.buckets: Dict[int, List[X25519PublicKey]] = {
|
|
9
|
-
i: [] for i in range(len(self.relay_public_key_bytes) * 8)
|
|
10
|
-
}
|
|
11
|
-
self.peers = {}
|
|
12
|
-
|
|
13
|
-
@staticmethod
|
|
14
|
-
def _matching_leading_bits(a: bytes, b: bytes) -> int:
|
|
15
|
-
for byte_index, (ba, bb) in enumerate(zip(a, b)):
|
|
16
|
-
diff = ba ^ bb
|
|
17
|
-
if diff:
|
|
18
|
-
return byte_index * 8 + (8 - diff.bit_length())
|
|
19
|
-
return len(a) * 8
|
|
20
|
-
|
|
21
|
-
def add_peer(self, peer_public_key: X25519PublicKey):
|
|
22
|
-
peer_public_key_bytes = peer_public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
|
|
23
|
-
bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
|
|
24
|
-
if len(self.buckets[bucket_idx]) < self.bucket_size:
|
|
25
|
-
self.buckets[bucket_idx].append(peer_public_key)
|
astreum/relay/setup.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import socket, threading
|
|
2
|
-
from queue import Queue
|
|
3
|
-
from typing import Tuple, Optional
|
|
4
|
-
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
5
|
-
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|
6
|
-
X25519PrivateKey,
|
|
7
|
-
X25519PublicKey,
|
|
8
|
-
)
|
|
9
|
-
from yourproject.routes import Route
|
|
10
|
-
|
|
11
|
-
def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
|
|
12
|
-
"""DH key for relaying (always X25519)."""
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
|
|
16
|
-
"""Signing key for validation (Ed25519), or None if absent."""
|
|
17
|
-
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
|
|
18
|
-
if hex_key else None
|
|
19
|
-
|
|
20
|
-
def make_routes(
|
|
21
|
-
relay_pk: X25519PublicKey,
|
|
22
|
-
val_sk: Optional[ed25519.Ed25519PrivateKey]
|
|
23
|
-
) -> Tuple[Route, Optional[Route]]:
|
|
24
|
-
"""Peer route (DH pubkey) + optional validation route (ed pubkey)."""
|
|
25
|
-
peer_rt = Route(relay_pk)
|
|
26
|
-
val_rt = Route(val_sk.public_key()) if val_sk else None
|
|
27
|
-
return peer_rt, val_rt
|
|
28
|
-
|
|
29
|
-
def setup_udp(
|
|
30
|
-
bind_port: int,
|
|
31
|
-
use_ipv6: bool
|
|
32
|
-
) -> Tuple[socket.socket, int, Queue, threading.Thread, threading.Thread]:
|
|
33
|
-
fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
|
|
34
|
-
sock = socket.socket(fam, socket.SOCK_DGRAM)
|
|
35
|
-
if use_ipv6:
|
|
36
|
-
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
37
|
-
sock.bind(("::" if use_ipv6 else "0.0.0.0", bind_port or 0))
|
|
38
|
-
port = sock.getsockname()[1]
|
|
39
|
-
|
|
40
|
-
q = Queue()
|
|
41
|
-
pop = threading.Thread(target=lambda: None, daemon=True)
|
|
42
|
-
proc = threading.Thread(target=lambda: None, daemon=True)
|
|
43
|
-
pop.start(); proc.start()
|
|
44
|
-
return sock, port, q, pop, proc
|
|
45
|
-
|
|
46
|
-
def setup_outgoing(
|
|
47
|
-
use_ipv6: bool
|
|
48
|
-
) -> Tuple[socket.socket, Queue, threading.Thread]:
|
|
49
|
-
fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
|
|
50
|
-
sock = socket.socket(fam, socket.SOCK_DGRAM)
|
|
51
|
-
q = Queue()
|
|
52
|
-
thr = threading.Thread(target=lambda: None, daemon=True)
|
|
53
|
-
thr.start()
|
|
54
|
-
return sock, q, thr
|
|
55
|
-
|
|
56
|
-
def make_maps():
|
|
57
|
-
"""Empty lookup maps: peers and addresses."""
|
|
58
|
-
return
|
|
File without changes
|
|
File without changes
|