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.
Files changed (75) hide show
  1. astreum/__init__.py +16 -7
  2. astreum/{_communication → communication}/__init__.py +3 -3
  3. astreum/communication/handlers/handshake.py +83 -0
  4. astreum/communication/handlers/ping.py +48 -0
  5. astreum/communication/handlers/storage_request.py +81 -0
  6. astreum/communication/models/__init__.py +0 -0
  7. astreum/{_communication → communication/models}/route.py +5 -5
  8. astreum/communication/setup.py +205 -0
  9. astreum/communication/start.py +38 -0
  10. astreum/consensus/__init__.py +20 -0
  11. astreum/consensus/genesis.py +66 -0
  12. astreum/consensus/models/__init__.py +0 -0
  13. astreum/consensus/models/account.py +84 -0
  14. astreum/consensus/models/accounts.py +72 -0
  15. astreum/consensus/models/block.py +364 -0
  16. astreum/{_consensus → consensus/models}/chain.py +7 -7
  17. astreum/{_consensus → consensus/models}/fork.py +8 -8
  18. astreum/consensus/models/receipt.py +98 -0
  19. astreum/{_consensus → consensus/models}/transaction.py +76 -78
  20. astreum/{_consensus → consensus}/setup.py +18 -50
  21. astreum/consensus/start.py +68 -0
  22. astreum/consensus/validator.py +95 -0
  23. astreum/{_consensus → consensus}/workers/discovery.py +20 -1
  24. astreum/consensus/workers/validation.py +291 -0
  25. astreum/{_consensus → consensus}/workers/verify.py +31 -2
  26. astreum/machine/__init__.py +20 -0
  27. astreum/machine/evaluations/__init__.py +0 -0
  28. astreum/{_lispeum → machine/evaluations}/high_evaluation.py +16 -15
  29. astreum/machine/evaluations/low_evaluation.py +281 -0
  30. astreum/machine/evaluations/script_evaluation.py +27 -0
  31. astreum/machine/models/__init__.py +0 -0
  32. astreum/machine/models/environment.py +31 -0
  33. astreum/{_lispeum → machine/models}/expression.py +36 -8
  34. astreum/machine/tokenizer.py +90 -0
  35. astreum/node.py +73 -781
  36. astreum/storage/__init__.py +7 -0
  37. astreum/storage/actions/get.py +69 -0
  38. astreum/storage/actions/set.py +132 -0
  39. astreum/{_storage → storage/models}/atom.py +55 -57
  40. astreum/{_storage/patricia.py → storage/models/trie.py} +227 -203
  41. astreum/storage/setup.py +44 -15
  42. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/METADATA +25 -24
  43. astreum-0.3.1.dist-info/RECORD +62 -0
  44. astreum/_communication/setup.py +0 -322
  45. astreum/_consensus/__init__.py +0 -20
  46. astreum/_consensus/account.py +0 -95
  47. astreum/_consensus/accounts.py +0 -38
  48. astreum/_consensus/block.py +0 -311
  49. astreum/_consensus/genesis.py +0 -72
  50. astreum/_consensus/receipt.py +0 -136
  51. astreum/_consensus/workers/validation.py +0 -125
  52. astreum/_lispeum/__init__.py +0 -16
  53. astreum/_lispeum/environment.py +0 -13
  54. astreum/_lispeum/low_evaluation.py +0 -123
  55. astreum/_lispeum/tokenizer.py +0 -22
  56. astreum/_node.py +0 -198
  57. astreum/_storage/__init__.py +0 -7
  58. astreum/_storage/setup.py +0 -35
  59. astreum/format.py +0 -75
  60. astreum/models/block.py +0 -441
  61. astreum/models/merkle.py +0 -205
  62. astreum/models/patricia.py +0 -393
  63. astreum/storage/object.py +0 -68
  64. astreum-0.2.61.dist-info/RECORD +0 -57
  65. /astreum/{models → communication/handlers}/__init__.py +0 -0
  66. /astreum/{_communication → communication/models}/message.py +0 -0
  67. /astreum/{_communication → communication/models}/peer.py +0 -0
  68. /astreum/{_communication → communication/models}/ping.py +0 -0
  69. /astreum/{_communication → communication}/util.py +0 -0
  70. /astreum/{_consensus → consensus}/workers/__init__.py +0 -0
  71. /astreum/{_lispeum → machine/models}/meter.py +0 -0
  72. /astreum/{_lispeum → machine}/parser.py +0 -0
  73. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/WHEEL +0 -0
  74. {astreum-0.2.61.dist-info → astreum-0.3.1.dist-info}/licenses/LICENSE +0 -0
  75. {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.2.61
4
- Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
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 Lispeum virtual machine.
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
- | `machine-only` | bool | `True` | When **True** the node starts in *machine‑only* mode: no storage subsystem and no relay networking – only the Lispeum VM. Set to **False** to enable storage and relay features. |
34
- | `relay_secret_key` | hex string | Auto‑generated | Ed25519 private key that identifies the node on the network. If omitted, a fresh keypair is generated and kept in‑memory. |
35
- | `validation_secret_key` | hex string | `None` | X25519 private key that lets the node participate in the validation route. Leave unset for a non‑validator node. |
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 | Type | Default | Description |
44
- | --------------- | ----------------------- | ------- | ----------------------------------------------------------------------------------- |
45
- | `use_ipv6` | bool | `False` | Listen on IPv6 as well as IPv4. |
46
- | `incoming_port` | int | `7373` | UDP port the relay binds to. |
47
- | `bootstrap` | list\[tuple\[str, int]] | `[]` | Initial peers used to join the network, e.g. `[ ("bootstrap.astreum.org", 7373) ]`. |
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
- "storage_path": "./data/node1",
63
- "storage_get_relay_timeout": 5,
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
- ("bootstrap.astreum.org", 7373),
68
- ("127.0.0.1", 7374)
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
- The Lispeum virtual machine (VM) is embedded inside `astreum.Node`. You feed it Lispeum source text, and the node tokenizes, parses, and **evaluates** the resulting AST inside an isolated environment.
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, b"int.add", int_add_fn)
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, b"int.add")
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,,
@@ -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)))
@@ -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
- ]
@@ -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)
@@ -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