astreum 0.2.61__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}/route.py +5 -5
- astreum/communication/setup.py +205 -0
- 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 → consensus/models}/transaction.py +76 -78
- astreum/{_consensus → consensus}/setup.py +18 -50
- 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 +31 -2
- astreum/machine/__init__.py +20 -0
- astreum/machine/evaluations/__init__.py +0 -0
- astreum/{_lispeum → machine/evaluations}/high_evaluation.py +16 -15
- 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 +73 -781
- astreum/storage/__init__.py +7 -0
- astreum/storage/actions/get.py +69 -0
- astreum/storage/actions/set.py +132 -0
- astreum/{_storage → storage/models}/atom.py +55 -57
- astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
- astreum/storage/setup.py +44 -15
- {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/METADATA +25 -24
- astreum-0.3.1.dist-info/RECORD +62 -0
- 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}/message.py +0 -0
- /astreum/{_communication → communication/models}/peer.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/{_lispeum → machine}/parser.py +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Python library to interact with the Astreum blockchain and its
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Python library to interact with the Astreum blockchain and its virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
|
-
Project-URL: Homepage, https://github.com/astreum/lib
|
|
7
|
-
Project-URL: Issues, https://github.com/astreum/lib/issues
|
|
6
|
+
Project-URL: Homepage, https://github.com/astreum/lib-py
|
|
7
|
+
Project-URL: Issues, https://github.com/astreum/lib-py/issues
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -18,7 +18,7 @@ Dynamic: license-file
|
|
|
18
18
|
|
|
19
19
|
# lib
|
|
20
20
|
|
|
21
|
-
Python library to interact with the Astreum blockchain and its
|
|
21
|
+
Python library to interact with the Astreum blockchain and its virtual machine.
|
|
22
22
|
|
|
23
23
|
[View on PyPI](https://pypi.org/project/astreum/)
|
|
24
24
|
|
|
@@ -30,21 +30,21 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
|
|
|
30
30
|
|
|
31
31
|
| Parameter | Type | Default | Description |
|
|
32
32
|
| --------------------------- | ---------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `storage_path` | string | `None` | Directory where objects are persisted. If *None*, the node uses an in‑memory store. |
|
|
37
|
-
| `storage_get_relay_timeout` | float | `5` | Seconds to wait for an object requested from peers before timing‑out. |
|
|
33
|
+
| `hot_storage_limit` | int | `1073741824` | Maximum bytes kept in the hot cache before new atoms are skipped (1 GiB). |
|
|
34
|
+
| `cold_storage_limit` | int | `10737418240` | Cold storage write threshold (10 GiB by default); set to `0` to skip the limit. |
|
|
35
|
+
| `cold_storage_path` | string | `None` | Directory where persisted atoms live; Astreum creates it on startup and skips cold storage when unset. |
|
|
38
36
|
| `logging_retention` | int | `90` | Number of days to keep rotated log files (daily gzip). |
|
|
39
37
|
| `verbose` | bool | `False` | When **True**, also mirror JSON logs to stdout with a human-readable format. |
|
|
40
38
|
|
|
41
39
|
### Networking
|
|
42
40
|
|
|
43
|
-
| Parameter
|
|
44
|
-
|
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
41
|
+
| Parameter | Type | Default | Description |
|
|
42
|
+
| ------------------------ | ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `relay_secret_key` | hex string | Auto-generated | X25519 private key used for the relay route; a new keypair is created when this field is omitted. |
|
|
44
|
+
| `validation_secret_key` | hex string | `None` | Optional Ed25519 key that lets the node join the validation route; leave blank to opt out of validation. |
|
|
45
|
+
| `use_ipv6` | bool | `False` | Bind the incoming/outgoing sockets on IPv6 (the OS still listens on IPv4 if a peer speaks both). |
|
|
46
|
+
| `incoming_port` | int | `7373` | UDP port the relay binds to; pass `0` or omit to let the OS pick an ephemeral port. |
|
|
47
|
+
| `bootstrap` | list\[str\] | `[]` | Addresses to ping with a handshake before joining; each must look like `host:port` or `[ipv6]:port`. |
|
|
48
48
|
|
|
49
49
|
> **Note**
|
|
50
50
|
> The peer‑to‑peer *route* used for object discovery is always enabled.
|
|
@@ -56,16 +56,16 @@ When initializing an `astreum.Node`, pass a dictionary with any of the options b
|
|
|
56
56
|
from astreum.node import Node
|
|
57
57
|
|
|
58
58
|
config = {
|
|
59
|
-
"machine-only": False, # run full node
|
|
60
59
|
"relay_secret_key": "ab…cd", # optional – hex encoded
|
|
61
60
|
"validation_secret_key": "12…34", # optional – validator
|
|
62
|
-
"
|
|
63
|
-
"
|
|
61
|
+
"hot_storage_limit": 1073741824, # cap hot cache at 1 GiB
|
|
62
|
+
"cold_storage_limit": 10737418240, # cap cold storage at 10 GiB
|
|
63
|
+
"cold_storage_path": "./data/node1",
|
|
64
64
|
"incoming_port": 7373,
|
|
65
65
|
"use_ipv6": False,
|
|
66
66
|
"bootstrap": [
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
"bootstrap.astreum.org:7373",
|
|
68
|
+
"127.0.0.1:7374"
|
|
69
69
|
]
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -73,9 +73,10 @@ node = Node(config)
|
|
|
73
73
|
# … your code …
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
## Lispeum Machine Quickstart
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
## Astreum Machine Quickstart
|
|
78
|
+
|
|
79
|
+
The Astreum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Astreum script, and the node tokenizes, parses, and evaluates.
|
|
79
80
|
|
|
80
81
|
```python
|
|
81
82
|
# Define a named function int.add (stack body) and call it with bytes 1 and 2
|
|
@@ -108,10 +109,10 @@ params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
|
108
109
|
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
109
110
|
|
|
110
111
|
# 4) Store under the name "int.add"
|
|
111
|
-
node.env_set(env_id,
|
|
112
|
+
node.env_set(env_id, "int.add", int_add_fn)
|
|
112
113
|
|
|
113
114
|
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
114
|
-
bound = node.env_get(env_id,
|
|
115
|
+
bound = node.env_get(env_id, "int.add")
|
|
115
116
|
call = Expr.ListExpr([Expr.Byte(1), Expr.Byte(2), bound])
|
|
116
117
|
res = node.high_eval(env_id, call)
|
|
117
118
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
astreum/__init__.py,sha256=GkEW_ReYore8_0nEOvPnZLUa3lO7CgMWu6LeEjrGXEk,325
|
|
2
|
+
astreum/node.py,sha256=cHZyq9ImhCB9PSROKR5lFsUau6VLCjRIfiJSZhCPFzI,2103
|
|
3
|
+
astreum/communication/__init__.py,sha256=wNxzsAk8Fol9cGMPuVvY4etrrMqn3SjZq1dE82kFrxw,228
|
|
4
|
+
astreum/communication/setup.py,sha256=qliXCj2uHvzullCPSVtUuEG9zdqHewKgYfQLfsM8tao,7236
|
|
5
|
+
astreum/communication/start.py,sha256=lfud8VvLeKFbkF_TwHFODg20RVpClUa4a_zsHB7ynxk,1853
|
|
6
|
+
astreum/communication/util.py,sha256=bJ3td3naDzmCelAJQpLwiDMoRBkijQl9YLROjsWyOrI,1256
|
|
7
|
+
astreum/communication/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
astreum/communication/handlers/handshake.py,sha256=twd18nnfYcyC8hLXZ0EDwUw-2mPQGRf1RYdW21x9CHM,2378
|
|
9
|
+
astreum/communication/handlers/ping.py,sha256=xY-QT0IoeNPKR1hyruRwJa2N8_op7aPOCZUk9X-kZWk,1258
|
|
10
|
+
astreum/communication/handlers/storage_request.py,sha256=rUWhoeOxVZHcvEMxi74hN9XF9SFHe9Uw-9q4pBP-KwE,2406
|
|
11
|
+
astreum/communication/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
astreum/communication/models/message.py,sha256=Wl1IITj7eY9_q0IOT4J7c5gsjS1bF51CH7GcSSuu5OM,3327
|
|
13
|
+
astreum/communication/models/peer.py,sha256=CbqkyCwhFCiC2spd1-KjNdeVGNjjt2ECVs8uHot-ETI,875
|
|
14
|
+
astreum/communication/models/ping.py,sha256=u_DQTZJsbMdYiDDqjdZDsLaN5na2m9WZjVeEM3zq9_Y,955
|
|
15
|
+
astreum/communication/models/route.py,sha256=LRHx0R1MSIln92GQbyDrZpE_hfiHDiSG_3z1Ssq_1n4,4032
|
|
16
|
+
astreum/consensus/__init__.py,sha256=VZR_NyGSD5VvZp3toD2zpdYwFDLBIcckeVZXFPlruuU,425
|
|
17
|
+
astreum/consensus/genesis.py,sha256=RI9AzQFmDTgNFuiiTmW2dDiGcURIUGmThdRpxWrUOBk,1962
|
|
18
|
+
astreum/consensus/setup.py,sha256=lrEapfpJXKqw4iwST11-tqPAI2VW2h3H6Ue4JDAtrP4,3142
|
|
19
|
+
astreum/consensus/start.py,sha256=ZUa77cINmj5AzGR8dnZ1KS0OeDIyesSmrEOx0zo4HBI,2581
|
|
20
|
+
astreum/consensus/validator.py,sha256=cqcmw1WEB8DkznNX_Mn8tmE956rVSNCPv1FicdL8EAQ,3647
|
|
21
|
+
astreum/consensus/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
astreum/consensus/models/account.py,sha256=3QcT59QUZynysLSbiywidFYVzYJ3LR6qia7JwXOwn4I,2690
|
|
23
|
+
astreum/consensus/models/accounts.py,sha256=iUMs6LvmMea-gxd6-ujkFjqhWmuW1cl9XTWGXQkpLys,2388
|
|
24
|
+
astreum/consensus/models/block.py,sha256=nNtw9TbEAF1mIEfgJr1fuswcZ0B63SVnuBANqJ5Zaac,13531
|
|
25
|
+
astreum/consensus/models/chain.py,sha256=SIIDFSYbag76kTUNwnuJ2_zyuhFsvT7n5HgrVTxBrvE,2797
|
|
26
|
+
astreum/consensus/models/fork.py,sha256=IbXRB93bUg2k3q3oQ9dOPzozV-rY-TEDFjYrw-WBymE,3859
|
|
27
|
+
astreum/consensus/models/receipt.py,sha256=KjKKjYp_LnP2zkX1FLIwD_4hqKV1b2TPfp43tY701q4,3336
|
|
28
|
+
astreum/consensus/models/transaction.py,sha256=AYa1Q-BaYW3mkOv1e3WbvDFEsYamKMiFrja-eO2zU_Y,7475
|
|
29
|
+
astreum/consensus/workers/__init__.py,sha256=bS5FjbevbIR5FHbVGnT4Jli17VIld_5auemRw4CaHFU,278
|
|
30
|
+
astreum/consensus/workers/discovery.py,sha256=ckko9286WaK0qAaUpk_pHmQe_N3F87iGZu67OhCdtY8,2487
|
|
31
|
+
astreum/consensus/workers/validation.py,sha256=1jwFUL1zztuzLiYAmi92-KTUq97yraFAhuvhNhFJeLs,12223
|
|
32
|
+
astreum/consensus/workers/verify.py,sha256=eadF27iXOnMife_Pwz65lVwUyTEU8LGIcdGkCT_nzo0,3487
|
|
33
|
+
astreum/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
astreum/crypto/ed25519.py,sha256=FRnvlN0kZlxn4j-sJKl-C9tqiz_0z4LZyXLj3KIj1TQ,1760
|
|
35
|
+
astreum/crypto/quadratic_form.py,sha256=pJgbORey2NTWbQNhdyvrjy_6yjORudQ67jBz2ScHptg,4037
|
|
36
|
+
astreum/crypto/wesolowski.py,sha256=SUgGXW3Id07dJtWzDcs4dluIhjqbRWQ8YWjn_mK78AQ,4092
|
|
37
|
+
astreum/crypto/x25519.py,sha256=i29v4BmwKRcbz9E7NKqFDQyxzFtJUqN0St9jd7GS1uA,1137
|
|
38
|
+
astreum/machine/__init__.py,sha256=TjWf9RlGuOGbCqdjJKidh8W4pCzUoLpi3FgutssEGoQ,479
|
|
39
|
+
astreum/machine/parser.py,sha256=Z_Y0Sax0rPh8JcIo19-iNDQoc5GTdGQkmfFyLpCB4bw,1757
|
|
40
|
+
astreum/machine/tokenizer.py,sha256=6wPqR_D3h5BEvR78XKtD45ouy77RZBbz4Yh4jHSmN4o,2394
|
|
41
|
+
astreum/machine/evaluations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
astreum/machine/evaluations/high_evaluation.py,sha256=0tKOvW8T7EEHrL5pZtMUSnUszYTPWSP2xnEocr1eIOk,9778
|
|
43
|
+
astreum/machine/evaluations/low_evaluation.py,sha256=n3LwHDD889PAoj1XW7D2Eu4WCalx5nl0mKoLrgdoLpo,10337
|
|
44
|
+
astreum/machine/evaluations/script_evaluation.py,sha256=eWouYUwTYzaqUyXqEe-lAJFIluW0gMeCDdXqle88oWw,864
|
|
45
|
+
astreum/machine/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
astreum/machine/models/environment.py,sha256=WjP6GRX_8e0-BAhzRLvQ6fYtKQEVR0LZi7DZNZS0TSE,1019
|
|
47
|
+
astreum/machine/models/expression.py,sha256=yYr9ktk-NWPL4EXwHz7ePvr9eNvfSBQe3yzRUz06yas,7675
|
|
48
|
+
astreum/machine/models/meter.py,sha256=5q2PFW7_jmgKVM1-vwE4RRjMfPEthUA4iu1CwR-Axws,505
|
|
49
|
+
astreum/storage/__init__.py,sha256=Flk6WXT2xGFHWWJiZHK3O5OpjoLTOFMqqIiJTtD58kY,111
|
|
50
|
+
astreum/storage/setup.py,sha256=udwLpSCFpneKH9DlxUB40EVjmhjqQQ2hS4dePwQKkL8,1508
|
|
51
|
+
astreum/storage/actions/get.py,sha256=XRNOUzD3OjMpfFPyhQQt2rE5dpS_Hdp9Yf5SYELjm30,2572
|
|
52
|
+
astreum/storage/actions/set.py,sha256=-eyHJW5xPRbkDV8YvPQsp_SEFkCt4HEQ0VK2soYRXvg,4210
|
|
53
|
+
astreum/storage/models/atom.py,sha256=FY_bgtoju59Yo7TL1DTFTr9_pRMNBuH6-u59D6bz2fc,3163
|
|
54
|
+
astreum/storage/models/trie.py,sha256=Bn3ssPGI7YGS4iUH5ESvpG1NE6Ljx2Xo7wkEpQhjKUY,17587
|
|
55
|
+
astreum/utils/bytes.py,sha256=9QTWC2JCdwWLB5R2mPtmjPro0IUzE58DL3uEul4AheE,846
|
|
56
|
+
astreum/utils/integer.py,sha256=iQt-klWOYVghu_NOT341MmHbOle4FDT3by4PNKNXscg,736
|
|
57
|
+
astreum/utils/logging.py,sha256=mRDtWSCj8vKt58WGKLNSkK9Oa0graNVSoS8URby4Q9g,6684
|
|
58
|
+
astreum-0.3.1.dist-info/licenses/LICENSE,sha256=gYBvRDP-cPLmTyJhvZ346QkrYW_eleke4Z2Yyyu43eQ,1089
|
|
59
|
+
astreum-0.3.1.dist-info/METADATA,sha256=_fPZGHAf0_YTfkErbQwTEovrBE0x_MbUPp8uXJ04JUE,7716
|
|
60
|
+
astreum-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
61
|
+
astreum-0.3.1.dist-info/top_level.txt,sha256=1EG1GmkOk3NPmUA98FZNdKouhRyget-KiFiMk0i2Uz0,8
|
|
62
|
+
astreum-0.3.1.dist-info/RECORD,,
|
astreum/_communication/setup.py
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import socket, threading
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
from queue import Queue
|
|
4
|
-
from typing import Tuple, Optional
|
|
5
|
-
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
6
|
-
from cryptography.hazmat.primitives import serialization
|
|
7
|
-
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|
8
|
-
X25519PrivateKey,
|
|
9
|
-
X25519PublicKey,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from .. import Node
|
|
15
|
-
|
|
16
|
-
from . import Route, Message
|
|
17
|
-
from .message import MessageTopic
|
|
18
|
-
from .peer import Peer
|
|
19
|
-
from .ping import Ping
|
|
20
|
-
from .util import address_str_to_host_and_port
|
|
21
|
-
|
|
22
|
-
def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
|
|
23
|
-
"""DH key for relaying (always X25519)."""
|
|
24
|
-
return
|
|
25
|
-
|
|
26
|
-
def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
|
|
27
|
-
"""Signing key for validation (Ed25519), or None if absent."""
|
|
28
|
-
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
|
|
29
|
-
if hex_key else None
|
|
30
|
-
|
|
31
|
-
def make_routes(
|
|
32
|
-
relay_pk: X25519PublicKey,
|
|
33
|
-
val_sk: Optional[ed25519.Ed25519PrivateKey]
|
|
34
|
-
) -> Tuple[Route, Optional[Route]]:
|
|
35
|
-
"""Peer route (DH pubkey) + optional validation route (ed pubkey)."""
|
|
36
|
-
peer_rt = Route(relay_pk)
|
|
37
|
-
val_rt = Route(val_sk.public_key()) if val_sk else None
|
|
38
|
-
return peer_rt, val_rt
|
|
39
|
-
|
|
40
|
-
def setup_outgoing(
|
|
41
|
-
use_ipv6: bool
|
|
42
|
-
) -> Tuple[socket.socket, Queue, threading.Thread]:
|
|
43
|
-
fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
|
|
44
|
-
sock = socket.socket(fam, socket.SOCK_DGRAM)
|
|
45
|
-
q = Queue()
|
|
46
|
-
thr = threading.Thread(target=lambda: None, daemon=True)
|
|
47
|
-
thr.start()
|
|
48
|
-
return sock, q, thr
|
|
49
|
-
|
|
50
|
-
def make_maps():
|
|
51
|
-
"""Empty lookup maps: peers and addresses."""
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def process_incoming_messages(node: "Node") -> None:
|
|
56
|
-
"""Process incoming messages (placeholder)."""
|
|
57
|
-
while True:
|
|
58
|
-
try:
|
|
59
|
-
data, addr = node.incoming_queue.get()
|
|
60
|
-
except Exception as exc:
|
|
61
|
-
print(f"Error taking from incoming queue: {exc}")
|
|
62
|
-
continue
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
message = Message.from_bytes(data)
|
|
66
|
-
except Exception as exc:
|
|
67
|
-
print(f"Error decoding message: {exc}")
|
|
68
|
-
continue
|
|
69
|
-
|
|
70
|
-
if message.handshake:
|
|
71
|
-
sender_key = message.sender
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
sender_public_key_bytes = sender_key.public_bytes(
|
|
75
|
-
encoding=serialization.Encoding.Raw,
|
|
76
|
-
format=serialization.PublicFormat.Raw,
|
|
77
|
-
)
|
|
78
|
-
except Exception as exc:
|
|
79
|
-
print(f"Error extracting sender key bytes: {exc}")
|
|
80
|
-
continue
|
|
81
|
-
|
|
82
|
-
# Normalize remote address (IPv6 tuples may be 4 elements)
|
|
83
|
-
try:
|
|
84
|
-
host, port = addr[0], int(addr[1])
|
|
85
|
-
except Exception:
|
|
86
|
-
continue
|
|
87
|
-
address_key = (host, port)
|
|
88
|
-
|
|
89
|
-
old_key_bytes = node.addresses.get(address_key)
|
|
90
|
-
node.addresses[address_key] = sender_public_key_bytes
|
|
91
|
-
|
|
92
|
-
if old_key_bytes is None:
|
|
93
|
-
# brand-new address -> brand-new peer
|
|
94
|
-
try:
|
|
95
|
-
peer = Peer(node.relay_secret_key, sender_key)
|
|
96
|
-
except Exception:
|
|
97
|
-
continue
|
|
98
|
-
peer.address = address_key
|
|
99
|
-
|
|
100
|
-
node.peers[sender_public_key_bytes] = peer
|
|
101
|
-
node.peer_route.add_peer(sender_public_key_bytes, peer)
|
|
102
|
-
|
|
103
|
-
response = Message(handshake=True, sender=node.relay_public_key)
|
|
104
|
-
node.outgoing_queue.put((response.to_bytes(), address_key))
|
|
105
|
-
continue
|
|
106
|
-
|
|
107
|
-
elif old_key_bytes == sender_public_key_bytes:
|
|
108
|
-
# existing mapping with same key -> nothing to change
|
|
109
|
-
peer = node.peers.get(sender_public_key_bytes)
|
|
110
|
-
if peer is not None:
|
|
111
|
-
peer.address = address_key
|
|
112
|
-
|
|
113
|
-
else:
|
|
114
|
-
# address reused with a different key -> replace peer
|
|
115
|
-
node.peers.pop(old_key_bytes, None)
|
|
116
|
-
try:
|
|
117
|
-
node.peer_route.remove_peer(old_key_bytes)
|
|
118
|
-
except Exception:
|
|
119
|
-
pass
|
|
120
|
-
try:
|
|
121
|
-
peer = Peer(node.relay_secret_key, sender_key)
|
|
122
|
-
except Exception:
|
|
123
|
-
continue
|
|
124
|
-
peer.address = address_key
|
|
125
|
-
|
|
126
|
-
node.peers[sender_public_key_bytes] = peer
|
|
127
|
-
node.peer_route.add_peer(sender_public_key_bytes, peer)
|
|
128
|
-
|
|
129
|
-
match message.topic:
|
|
130
|
-
case MessageTopic.PING:
|
|
131
|
-
try:
|
|
132
|
-
host, port = addr[0], int(addr[1])
|
|
133
|
-
except Exception:
|
|
134
|
-
continue
|
|
135
|
-
address_key = (host, port)
|
|
136
|
-
sender_public_key_bytes = node.addresses.get(address_key)
|
|
137
|
-
if sender_public_key_bytes is None:
|
|
138
|
-
continue
|
|
139
|
-
peer = node.peers.get(sender_public_key_bytes)
|
|
140
|
-
if peer is None:
|
|
141
|
-
continue
|
|
142
|
-
try:
|
|
143
|
-
ping = Ping.from_bytes(message.content)
|
|
144
|
-
except Exception as exc:
|
|
145
|
-
print(f"Error decoding ping: {exc}")
|
|
146
|
-
continue
|
|
147
|
-
|
|
148
|
-
peer.timestamp = datetime.now(timezone.utc)
|
|
149
|
-
peer.latest_block = ping.latest_block
|
|
150
|
-
|
|
151
|
-
validation_route = node.validation_route
|
|
152
|
-
if validation_route is None:
|
|
153
|
-
continue
|
|
154
|
-
if ping.is_validator:
|
|
155
|
-
try:
|
|
156
|
-
validation_route.add_peer(sender_public_key_bytes)
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
159
|
-
else:
|
|
160
|
-
try:
|
|
161
|
-
validation_route.remove_peer(sender_public_key_bytes)
|
|
162
|
-
except Exception:
|
|
163
|
-
pass
|
|
164
|
-
case MessageTopic.OBJECT_REQUEST:
|
|
165
|
-
pass
|
|
166
|
-
case MessageTopic.OBJECT_RESPONSE:
|
|
167
|
-
pass
|
|
168
|
-
case MessageTopic.ROUTE_REQUEST:
|
|
169
|
-
pass
|
|
170
|
-
case MessageTopic.ROUTE_RESPONSE:
|
|
171
|
-
pass
|
|
172
|
-
case MessageTopic.TRANSACTION:
|
|
173
|
-
if node.validation_secret_key is None:
|
|
174
|
-
continue
|
|
175
|
-
node._validation_transaction_queue.put(message.content)
|
|
176
|
-
|
|
177
|
-
case MessageTopic.STORAGE_REQUEST:
|
|
178
|
-
payload = message.content
|
|
179
|
-
if len(payload) < 32:
|
|
180
|
-
continue
|
|
181
|
-
|
|
182
|
-
atom_id = payload[:32]
|
|
183
|
-
provider_bytes = payload[32:]
|
|
184
|
-
if not provider_bytes:
|
|
185
|
-
continue
|
|
186
|
-
|
|
187
|
-
try:
|
|
188
|
-
provider_str = provider_bytes.decode("utf-8")
|
|
189
|
-
except UnicodeDecodeError:
|
|
190
|
-
continue
|
|
191
|
-
|
|
192
|
-
try:
|
|
193
|
-
host, port = addr[0], int(addr[1])
|
|
194
|
-
except Exception:
|
|
195
|
-
continue
|
|
196
|
-
address_key = (host, port)
|
|
197
|
-
sender_key_bytes = node.addresses.get(address_key)
|
|
198
|
-
if sender_key_bytes is None:
|
|
199
|
-
continue
|
|
200
|
-
|
|
201
|
-
try:
|
|
202
|
-
local_key_bytes = node.relay_public_key.public_bytes(
|
|
203
|
-
encoding=serialization.Encoding.Raw,
|
|
204
|
-
format=serialization.PublicFormat.Raw,
|
|
205
|
-
)
|
|
206
|
-
except Exception:
|
|
207
|
-
continue
|
|
208
|
-
|
|
209
|
-
def xor_distance(target: bytes, key: bytes) -> int:
|
|
210
|
-
return int.from_bytes(
|
|
211
|
-
bytes(a ^ b for a, b in zip(target, key)),
|
|
212
|
-
byteorder="big",
|
|
213
|
-
signed=False,
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
self_distance = xor_distance(atom_id, local_key_bytes)
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
closest_peer = node.peer_route.closest_peer_for_hash(atom_id)
|
|
220
|
-
except Exception:
|
|
221
|
-
closest_peer = None
|
|
222
|
-
|
|
223
|
-
if (
|
|
224
|
-
closest_peer is not None
|
|
225
|
-
and closest_peer.public_key_bytes != sender_key_bytes
|
|
226
|
-
):
|
|
227
|
-
closest_distance = xor_distance(atom_id, closest_peer.public_key_bytes)
|
|
228
|
-
if closest_distance < self_distance:
|
|
229
|
-
target_addr = closest_peer.address
|
|
230
|
-
if target_addr is not None and target_addr != addr:
|
|
231
|
-
node.outgoing_queue.put((message.to_bytes(), target_addr))
|
|
232
|
-
continue
|
|
233
|
-
|
|
234
|
-
node.storage_index[atom_id] = provider_str.strip()
|
|
235
|
-
|
|
236
|
-
case _:
|
|
237
|
-
continue
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def populate_incoming_messages(node: "Node") -> None:
|
|
241
|
-
"""Receive UDP packets and feed the incoming queue (placeholder)."""
|
|
242
|
-
while True:
|
|
243
|
-
try:
|
|
244
|
-
data, addr = node.incoming_socket.recvfrom(4096)
|
|
245
|
-
node.incoming_queue.put((data, addr))
|
|
246
|
-
except Exception as exc:
|
|
247
|
-
print(f"Error populating incoming queue: {exc}")
|
|
248
|
-
|
|
249
|
-
def communication_setup(node: "Node", config: dict):
|
|
250
|
-
node.use_ipv6 = config.get('use_ipv6', False)
|
|
251
|
-
|
|
252
|
-
# key loading
|
|
253
|
-
node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
|
|
254
|
-
node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
|
|
255
|
-
|
|
256
|
-
# derive pubs + routes
|
|
257
|
-
node.relay_public_key = node.relay_secret_key.public_key()
|
|
258
|
-
node.validation_public_key = (
|
|
259
|
-
node.validation_secret_key.public_key().public_bytes(
|
|
260
|
-
encoding=serialization.Encoding.Raw,
|
|
261
|
-
format=serialization.PublicFormat.Raw,
|
|
262
|
-
)
|
|
263
|
-
if node.validation_secret_key
|
|
264
|
-
else None
|
|
265
|
-
)
|
|
266
|
-
node.peer_route, node.validation_route = make_routes(
|
|
267
|
-
node.relay_public_key,
|
|
268
|
-
node.validation_secret_key
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
# sockets + queues + threads
|
|
272
|
-
incoming_port = config.get('incoming_port', 7373)
|
|
273
|
-
fam = socket.AF_INET6 if node.use_ipv6 else socket.AF_INET
|
|
274
|
-
node.incoming_socket = socket.socket(fam, socket.SOCK_DGRAM)
|
|
275
|
-
if node.use_ipv6:
|
|
276
|
-
node.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
277
|
-
node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port or 0))
|
|
278
|
-
node.incoming_port = node.incoming_socket.getsockname()[1]
|
|
279
|
-
node.incoming_queue = Queue()
|
|
280
|
-
node.incoming_populate_thread = threading.Thread(
|
|
281
|
-
target=populate_incoming_messages,
|
|
282
|
-
args=(node,),
|
|
283
|
-
daemon=True,
|
|
284
|
-
)
|
|
285
|
-
node.incoming_process_thread = threading.Thread(
|
|
286
|
-
target=process_incoming_messages,
|
|
287
|
-
args=(node,),
|
|
288
|
-
daemon=True,
|
|
289
|
-
)
|
|
290
|
-
node.incoming_populate_thread.start()
|
|
291
|
-
node.incoming_process_thread.start()
|
|
292
|
-
|
|
293
|
-
(node.outgoing_socket,
|
|
294
|
-
node.outgoing_queue,
|
|
295
|
-
node.outgoing_thread
|
|
296
|
-
) = setup_outgoing(node.use_ipv6)
|
|
297
|
-
|
|
298
|
-
# other workers & maps
|
|
299
|
-
node.object_request_queue = Queue()
|
|
300
|
-
node.peer_manager_thread = threading.Thread(
|
|
301
|
-
target=node._relay_peer_manager,
|
|
302
|
-
daemon=True
|
|
303
|
-
)
|
|
304
|
-
node.peer_manager_thread.start()
|
|
305
|
-
|
|
306
|
-
node.peers, node.addresses = {}, {} # peers: Dict[bytes,Peer], addresses: Dict[(str,int),bytes]
|
|
307
|
-
latest_hash = getattr(node, "latest_block_hash", None)
|
|
308
|
-
if not isinstance(latest_hash, (bytes, bytearray)) or len(latest_hash) != 32:
|
|
309
|
-
node.latest_block_hash = bytes(32)
|
|
310
|
-
else:
|
|
311
|
-
node.latest_block_hash = bytes(latest_hash)
|
|
312
|
-
|
|
313
|
-
# bootstrap pings
|
|
314
|
-
for addr in config.get('bootstrap', []):
|
|
315
|
-
try:
|
|
316
|
-
host, port = address_str_to_host_and_port(addr) # type: ignore[arg-type]
|
|
317
|
-
except Exception:
|
|
318
|
-
continue
|
|
319
|
-
|
|
320
|
-
handshake_message = Message(handshake=True, sender=node.relay_public_key)
|
|
321
|
-
|
|
322
|
-
node.outgoing_queue.put((handshake_message.to_bytes(), (host, port)))
|
astreum/_consensus/__init__.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from .account import Account
|
|
2
|
-
from .accounts import Accounts
|
|
3
|
-
from .block import Block
|
|
4
|
-
from .chain import Chain
|
|
5
|
-
from .fork import Fork
|
|
6
|
-
from .receipt import Receipt
|
|
7
|
-
from .transaction import Transaction
|
|
8
|
-
from .setup import consensus_setup
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
__all__ = [
|
|
12
|
-
"Block",
|
|
13
|
-
"Chain",
|
|
14
|
-
"Fork",
|
|
15
|
-
"Receipt",
|
|
16
|
-
"Transaction",
|
|
17
|
-
"Account",
|
|
18
|
-
"Accounts",
|
|
19
|
-
"consensus_setup",
|
|
20
|
-
]
|
astreum/_consensus/account.py
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, List, Optional, Tuple
|
|
5
|
-
|
|
6
|
-
from .._storage.atom import Atom, ZERO32
|
|
7
|
-
from .._storage.patricia import PatriciaTrie
|
|
8
|
-
from ..utils.integer import bytes_to_int, int_to_bytes
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class Account:
|
|
13
|
-
balance: int
|
|
14
|
-
code: bytes
|
|
15
|
-
counter: int
|
|
16
|
-
data_hash: bytes
|
|
17
|
-
data: PatriciaTrie
|
|
18
|
-
hash: bytes = ZERO32
|
|
19
|
-
body_hash: bytes = ZERO32
|
|
20
|
-
atoms: List[Atom] = field(default_factory=list)
|
|
21
|
-
|
|
22
|
-
@classmethod
|
|
23
|
-
def create(cls, balance: int = 0, data_hash: bytes = ZERO32, code: bytes = ZERO32, counter: int = 0) -> "Account":
|
|
24
|
-
account = cls(
|
|
25
|
-
balance=int(balance),
|
|
26
|
-
code=bytes(code),
|
|
27
|
-
counter=int(counter),
|
|
28
|
-
data_hash=bytes(data_hash),
|
|
29
|
-
data=PatriciaTrie(root_hash=bytes(data_hash)),
|
|
30
|
-
)
|
|
31
|
-
account.to_atom()
|
|
32
|
-
return account
|
|
33
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def from_atom(cls, node: Any, account_id: bytes) -> "Account":
|
|
36
|
-
storage_get = node.storage_get
|
|
37
|
-
|
|
38
|
-
type_atom = storage_get(account_id)
|
|
39
|
-
if type_atom is None or type_atom.data != b"account":
|
|
40
|
-
raise ValueError("not an account (type mismatch)")
|
|
41
|
-
|
|
42
|
-
def _read_atom(atom_id: Optional[bytes]) -> Optional[Atom]:
|
|
43
|
-
if not atom_id or atom_id == ZERO32:
|
|
44
|
-
return None
|
|
45
|
-
return storage_get(atom_id)
|
|
46
|
-
|
|
47
|
-
balance_atom = _read_atom(type_atom.next)
|
|
48
|
-
if balance_atom is None:
|
|
49
|
-
raise ValueError("malformed account (balance missing)")
|
|
50
|
-
|
|
51
|
-
code_atom = _read_atom(balance_atom.next)
|
|
52
|
-
if code_atom is None:
|
|
53
|
-
raise ValueError("malformed account (code missing)")
|
|
54
|
-
|
|
55
|
-
counter_atom = _read_atom(code_atom.next)
|
|
56
|
-
if counter_atom is None:
|
|
57
|
-
raise ValueError("malformed account (counter missing)")
|
|
58
|
-
|
|
59
|
-
data_atom = _read_atom(counter_atom.next)
|
|
60
|
-
if data_atom is None:
|
|
61
|
-
raise ValueError("malformed account (data missing)")
|
|
62
|
-
|
|
63
|
-
account = cls.create(
|
|
64
|
-
balance=bytes_to_int(balance_atom.data),
|
|
65
|
-
data_hash=data_atom.data,
|
|
66
|
-
counter=bytes_to_int(counter_atom.data),
|
|
67
|
-
code=code_atom.data,
|
|
68
|
-
)
|
|
69
|
-
if account.hash != account_id:
|
|
70
|
-
raise ValueError("account hash mismatch while decoding")
|
|
71
|
-
return account
|
|
72
|
-
|
|
73
|
-
def to_atom(self) -> Tuple[bytes, List[Atom]]:
|
|
74
|
-
# Build a single forward chain: account -> balance -> code -> counter -> data.
|
|
75
|
-
data_atom = Atom.from_data(data=bytes(self.data_hash))
|
|
76
|
-
counter_atom = Atom.from_data(
|
|
77
|
-
data=int_to_bytes(self.counter),
|
|
78
|
-
next_hash=data_atom.object_id(),
|
|
79
|
-
)
|
|
80
|
-
code_atom = Atom.from_data(
|
|
81
|
-
data=bytes(self.code),
|
|
82
|
-
next_hash=counter_atom.object_id(),
|
|
83
|
-
)
|
|
84
|
-
balance_atom = Atom.from_data(
|
|
85
|
-
data=int_to_bytes(self.balance),
|
|
86
|
-
next_hash=code_atom.object_id(),
|
|
87
|
-
)
|
|
88
|
-
type_atom = Atom.from_data(data=b"account", next_hash=balance_atom.object_id())
|
|
89
|
-
|
|
90
|
-
atoms = [data_atom, counter_atom, code_atom, balance_atom, type_atom]
|
|
91
|
-
account_hash = type_atom.object_id()
|
|
92
|
-
self.hash = account_hash
|
|
93
|
-
self.body_hash = account_hash
|
|
94
|
-
self.atoms = atoms
|
|
95
|
-
return account_hash, list(atoms)
|
astreum/_consensus/accounts.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
4
|
-
|
|
5
|
-
from .._storage.patricia import PatriciaTrie
|
|
6
|
-
from .account import Account
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Accounts:
|
|
10
|
-
def __init__(
|
|
11
|
-
self,
|
|
12
|
-
root_hash: Optional[bytes] = None,
|
|
13
|
-
) -> None:
|
|
14
|
-
self._trie = PatriciaTrie(root_hash=root_hash)
|
|
15
|
-
self._cache: Dict[bytes, Account] = {}
|
|
16
|
-
|
|
17
|
-
@property
|
|
18
|
-
def root_hash(self) -> Optional[bytes]:
|
|
19
|
-
return self._trie.root_hash
|
|
20
|
-
|
|
21
|
-
def get_account(self, address: bytes, node: Optional[Any] = None) -> Optional[Account]:
|
|
22
|
-
cached = self._cache.get(address)
|
|
23
|
-
if cached is not None:
|
|
24
|
-
return cached
|
|
25
|
-
|
|
26
|
-
if node is None:
|
|
27
|
-
raise ValueError("Accounts requires a node reference for trie access")
|
|
28
|
-
|
|
29
|
-
account_id: Optional[bytes] = self._trie.get(node, address)
|
|
30
|
-
if account_id is None:
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
account = Account.from_atom(node, account_id)
|
|
34
|
-
self._cache[address] = account
|
|
35
|
-
return account
|
|
36
|
-
|
|
37
|
-
def set_account(self, address: bytes, account: Account) -> None:
|
|
38
|
-
self._cache[address] = account
|