astreum 0.3.9__py3-none-any.whl → 0.3.46__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 +5 -4
- astreum/communication/__init__.py +15 -11
- astreum/communication/difficulty.py +39 -0
- astreum/communication/disconnect.py +57 -0
- astreum/communication/handlers/handshake.py +105 -89
- astreum/communication/handlers/object_request.py +179 -149
- astreum/communication/handlers/object_response.py +7 -1
- astreum/communication/handlers/ping.py +9 -0
- astreum/communication/handlers/route_request.py +7 -1
- astreum/communication/handlers/route_response.py +7 -1
- astreum/communication/incoming_queue.py +96 -0
- astreum/communication/message_pow.py +36 -0
- astreum/communication/models/peer.py +4 -0
- astreum/communication/models/ping.py +27 -6
- astreum/communication/models/route.py +4 -0
- astreum/communication/{start.py → node.py} +10 -11
- astreum/communication/outgoing_queue.py +108 -0
- astreum/communication/processors/incoming.py +110 -37
- astreum/communication/processors/outgoing.py +35 -2
- astreum/communication/processors/peer.py +134 -0
- astreum/communication/setup.py +273 -112
- astreum/communication/util.py +14 -0
- astreum/node.py +99 -89
- astreum/storage/actions/get.py +79 -48
- astreum/storage/actions/set.py +171 -156
- astreum/storage/providers.py +24 -0
- astreum/storage/setup.py +23 -22
- astreum/utils/config.py +247 -30
- astreum/utils/logging.py +1 -1
- astreum/{consensus → validation}/__init__.py +0 -4
- astreum/validation/constants.py +2 -0
- astreum/{consensus → validation}/genesis.py +4 -6
- astreum/validation/models/block.py +544 -0
- astreum/validation/models/fork.py +511 -0
- astreum/{consensus → validation}/models/receipt.py +17 -4
- astreum/{consensus → validation}/models/transaction.py +45 -3
- astreum/validation/node.py +190 -0
- astreum/{consensus → validation}/validator.py +18 -9
- astreum/validation/workers/__init__.py +8 -0
- astreum/{consensus → validation}/workers/validation.py +361 -307
- astreum/verification/__init__.py +4 -0
- astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
- astreum/verification/node.py +61 -0
- astreum/verification/worker.py +183 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/METADATA +43 -9
- astreum-0.3.46.dist-info/RECORD +79 -0
- astreum/consensus/models/block.py +0 -364
- astreum/consensus/models/chain.py +0 -66
- astreum/consensus/models/fork.py +0 -100
- astreum/consensus/setup.py +0 -83
- astreum/consensus/start.py +0 -67
- astreum/consensus/workers/__init__.py +0 -9
- astreum/consensus/workers/verify.py +0 -90
- astreum-0.3.9.dist-info/RECORD +0 -71
- /astreum/{consensus → validation}/models/__init__.py +0 -0
- /astreum/{consensus → validation}/models/account.py +0 -0
- /astreum/{consensus → validation}/models/accounts.py +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/WHEEL +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.3.9.dist-info → astreum-0.3.46.dist-info}/top_level.txt +0 -0
astreum/communication/setup.py
CHANGED
|
@@ -1,148 +1,303 @@
|
|
|
1
|
-
import socket, threading
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from cryptography.hazmat.primitives
|
|
6
|
-
from cryptography.hazmat.primitives.asymmetric
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import heapq, os, socket, threading, time
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from queue import Queue
|
|
4
|
+
from typing import Tuple, Optional, Set
|
|
5
|
+
from cryptography.hazmat.primitives import serialization
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
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
|
|
16
17
|
from .processors.incoming import (
|
|
17
18
|
process_incoming_messages,
|
|
18
19
|
populate_incoming_messages,
|
|
19
20
|
)
|
|
20
21
|
from .processors.outgoing import process_outgoing_messages
|
|
22
|
+
from .processors.peer import manage_peer
|
|
23
|
+
from .outgoing_queue import enqueue_outgoing
|
|
21
24
|
from .util import address_str_to_host_and_port
|
|
22
25
|
from ..utils.bytes import hex_to_bytes
|
|
23
|
-
|
|
24
|
-
def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
|
|
25
|
-
"""DH key for relaying (always X25519)."""
|
|
26
|
-
if hex_key:
|
|
27
|
-
return X25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key))
|
|
28
|
-
return X25519PrivateKey.generate()
|
|
29
|
-
|
|
30
|
-
def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
|
|
31
|
-
"""Signing key for validation (Ed25519), or None if absent."""
|
|
32
|
-
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
|
|
33
|
-
if hex_key else None
|
|
34
|
-
|
|
35
|
-
def make_routes(
|
|
36
|
-
relay_pk: X25519PublicKey,
|
|
37
|
-
val_sk: Optional[ed25519.Ed25519PrivateKey]
|
|
38
|
-
) -> Tuple[Route, Optional[Route]]:
|
|
39
|
-
"""Peer route (DH pubkey) + optional validation route (ed pubkey)."""
|
|
40
|
-
peer_rt = Route(relay_pk)
|
|
41
|
-
val_rt = Route(val_sk.public_key()) if val_sk else None
|
|
42
|
-
return peer_rt, val_rt
|
|
43
|
-
|
|
26
|
+
|
|
27
|
+
def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
|
|
28
|
+
"""DH key for relaying (always X25519)."""
|
|
29
|
+
if hex_key:
|
|
30
|
+
return X25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key))
|
|
31
|
+
return X25519PrivateKey.generate()
|
|
32
|
+
|
|
33
|
+
def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
|
|
34
|
+
"""Signing key for validation (Ed25519), or None if absent."""
|
|
35
|
+
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
|
|
36
|
+
if hex_key else None
|
|
37
|
+
|
|
38
|
+
def make_routes(
|
|
39
|
+
relay_pk: X25519PublicKey,
|
|
40
|
+
val_sk: Optional[ed25519.Ed25519PrivateKey]
|
|
41
|
+
) -> Tuple[Route, Optional[Route]]:
|
|
42
|
+
"""Peer route (DH pubkey) + optional validation route (ed pubkey)."""
|
|
43
|
+
peer_rt = Route(relay_pk)
|
|
44
|
+
val_rt = Route(val_sk.public_key()) if val_sk else None
|
|
45
|
+
return peer_rt, val_rt
|
|
46
|
+
|
|
44
47
|
def make_maps():
|
|
45
48
|
"""Empty lookup maps: peers and addresses."""
|
|
46
49
|
return
|
|
47
50
|
|
|
48
51
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
def _resolve_default_seed_ips(node: "Node", default_seed: Optional[str]) -> Set[str]:
|
|
53
|
+
if default_seed is None:
|
|
54
|
+
return set()
|
|
55
|
+
try:
|
|
56
|
+
host, port = address_str_to_host_and_port(default_seed)
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
node.logger.warning("Invalid default seed %s: %s", default_seed, exc)
|
|
59
|
+
return set()
|
|
60
|
+
try:
|
|
61
|
+
infos = socket.getaddrinfo(host, port, type=socket.SOCK_DGRAM)
|
|
62
|
+
except Exception as exc:
|
|
63
|
+
node.logger.warning("Failed resolving default seed %s:%s: %s", host, port, exc)
|
|
64
|
+
return set()
|
|
65
|
+
resolved = {info[4][0] for info in infos if info[4]}
|
|
66
|
+
if resolved:
|
|
67
|
+
resolved_list = ", ".join(sorted(resolved))
|
|
68
|
+
node.logger.info("Default seed resolved to %s", resolved_list)
|
|
69
|
+
else:
|
|
70
|
+
node.logger.warning("No IPs resolved for default seed %s:%s", host, port)
|
|
71
|
+
return resolved
|
|
53
72
|
|
|
54
|
-
# key loading
|
|
55
|
-
node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
|
|
56
|
-
node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
|
|
57
73
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
def advertise_cold_storage(node: "Node") -> None:
|
|
75
|
+
"""Advertise all cold storage atom ids to the closest known peer."""
|
|
76
|
+
node_logger = node.logger
|
|
77
|
+
cold_path = node.config.get("cold_storage_path")
|
|
78
|
+
if not cold_path:
|
|
79
|
+
node_logger.debug("Cold storage disabled; skipping cold atom advertisement")
|
|
80
|
+
return
|
|
81
|
+
advertise_limit = node.config.get("cold_storage_advertise_limit", 1000)
|
|
82
|
+
if advertise_limit == 0:
|
|
83
|
+
node_logger.debug(
|
|
84
|
+
"Cold storage advertisement disabled; skipping cold atom advertisement"
|
|
68
85
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
directory = Path(cold_path)
|
|
89
|
+
if not directory.exists():
|
|
90
|
+
node_logger.warning("Cold storage path %s missing; cannot advertise atoms", directory)
|
|
91
|
+
return
|
|
92
|
+
if not directory.is_dir():
|
|
93
|
+
node_logger.warning("Cold storage path %s is not a directory; skipping", directory)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
advertised = 0
|
|
97
|
+
skipped = 0
|
|
98
|
+
if advertise_limit < 0:
|
|
99
|
+
for file_path in directory.glob("*.bin"):
|
|
100
|
+
if not file_path.is_file():
|
|
101
|
+
skipped += 1
|
|
102
|
+
continue
|
|
103
|
+
atom_hex = file_path.stem
|
|
104
|
+
if len(atom_hex) != 64:
|
|
105
|
+
skipped += 1
|
|
106
|
+
continue
|
|
107
|
+
try:
|
|
108
|
+
atom_id = bytes.fromhex(atom_hex)
|
|
109
|
+
except ValueError:
|
|
110
|
+
skipped += 1
|
|
111
|
+
continue
|
|
112
|
+
if len(atom_id) != 32:
|
|
113
|
+
skipped += 1
|
|
114
|
+
continue
|
|
115
|
+
node._network_set(atom_id)
|
|
116
|
+
advertised += 1
|
|
117
|
+
else:
|
|
118
|
+
heap = []
|
|
119
|
+
for entry in os.scandir(directory):
|
|
120
|
+
name = entry.name
|
|
121
|
+
if not name.endswith(".bin"):
|
|
122
|
+
continue
|
|
123
|
+
if not entry.is_file():
|
|
124
|
+
skipped += 1
|
|
125
|
+
continue
|
|
126
|
+
atom_hex = name[:-4]
|
|
127
|
+
if len(atom_hex) != 64:
|
|
128
|
+
skipped += 1
|
|
129
|
+
continue
|
|
130
|
+
try:
|
|
131
|
+
atom_id = bytes.fromhex(atom_hex)
|
|
132
|
+
except ValueError:
|
|
133
|
+
skipped += 1
|
|
134
|
+
continue
|
|
135
|
+
if len(atom_id) != 32:
|
|
136
|
+
skipped += 1
|
|
137
|
+
continue
|
|
138
|
+
try:
|
|
139
|
+
mtime = entry.stat().st_mtime
|
|
140
|
+
except OSError:
|
|
141
|
+
skipped += 1
|
|
142
|
+
continue
|
|
143
|
+
if len(heap) < advertise_limit:
|
|
144
|
+
heapq.heappush(heap, (mtime, atom_id))
|
|
145
|
+
else:
|
|
146
|
+
if mtime > heap[0][0]:
|
|
147
|
+
heapq.heapreplace(heap, (mtime, atom_id))
|
|
148
|
+
for _, atom_id in sorted(heap, key=lambda item: item[0], reverse=True):
|
|
149
|
+
node._network_set(atom_id)
|
|
150
|
+
advertised += 1
|
|
151
|
+
|
|
152
|
+
node_logger.info(
|
|
153
|
+
"Cold storage advertisement complete (advertised=%s, skipped=%s)",
|
|
154
|
+
advertised,
|
|
155
|
+
skipped,
|
|
75
156
|
)
|
|
76
157
|
|
|
77
|
-
# connection state & atom request tracking
|
|
78
|
-
node.is_connected = False
|
|
79
|
-
node.atom_requests = set()
|
|
80
|
-
node.atom_requests_lock = threading.RLock()
|
|
81
158
|
|
|
82
|
-
|
|
83
|
-
|
|
159
|
+
def manage_storage_index(node: "Node") -> None:
|
|
160
|
+
interval = node.config.get("storage_index_interval", 0)
|
|
161
|
+
if not interval:
|
|
162
|
+
node.logger.info("Storage index advertiser disabled")
|
|
163
|
+
return
|
|
164
|
+
node.logger.info("Storage index advertiser started (interval=%ss)", interval)
|
|
165
|
+
stop = getattr(node, "communication_stop_event", None)
|
|
166
|
+
while stop is None or not stop.is_set():
|
|
167
|
+
if stop is not None and stop.wait(interval):
|
|
168
|
+
break
|
|
169
|
+
try:
|
|
170
|
+
advertise_cold_storage(node)
|
|
171
|
+
except Exception:
|
|
172
|
+
node.logger.exception("Storage index advertisement failed")
|
|
173
|
+
node.logger.info("Storage index advertiser stopped")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def communication_setup(node: "Node", config: dict):
|
|
177
|
+
node.logger.info("Setting up node communication")
|
|
178
|
+
node.use_ipv6 = config.get('use_ipv6', False)
|
|
179
|
+
node.peers_lock = threading.RLock()
|
|
180
|
+
node.communication_stop_event = threading.Event()
|
|
181
|
+
default_seed = config.get("default_seed")
|
|
182
|
+
node.default_seed_ips = _resolve_default_seed_ips(node, default_seed)
|
|
183
|
+
|
|
184
|
+
# key loading
|
|
185
|
+
node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
|
|
186
|
+
node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
|
|
187
|
+
|
|
188
|
+
# derive pubs + routes
|
|
189
|
+
node.relay_public_key = node.relay_secret_key.public_key()
|
|
190
|
+
node.relay_public_key_bytes = node.relay_public_key.public_bytes(
|
|
191
|
+
encoding=serialization.Encoding.Raw,
|
|
192
|
+
format=serialization.PublicFormat.Raw,
|
|
193
|
+
)
|
|
194
|
+
node.validation_public_key = (
|
|
195
|
+
node.validation_secret_key.public_key().public_bytes(
|
|
196
|
+
encoding=serialization.Encoding.Raw,
|
|
197
|
+
format=serialization.PublicFormat.Raw,
|
|
198
|
+
)
|
|
199
|
+
if node.validation_secret_key
|
|
200
|
+
else None
|
|
201
|
+
)
|
|
202
|
+
node.peer_route, node.validation_route = make_routes(
|
|
203
|
+
node.relay_public_key,
|
|
204
|
+
node.validation_secret_key
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# connection state & atom request tracking
|
|
208
|
+
node.is_connected = False
|
|
209
|
+
node.atom_requests = set()
|
|
210
|
+
node.atom_requests_lock = threading.RLock()
|
|
211
|
+
|
|
212
|
+
# sockets + queues + threads
|
|
213
|
+
incoming_port = config.get("incoming_port")
|
|
214
|
+
if incoming_port is None:
|
|
215
|
+
raise ValueError("incoming_port must be configured before communication setup")
|
|
84
216
|
fam = socket.AF_INET6 if node.use_ipv6 else socket.AF_INET
|
|
85
217
|
node.incoming_socket = socket.socket(fam, socket.SOCK_DGRAM)
|
|
86
218
|
if node.use_ipv6:
|
|
87
219
|
node.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
88
|
-
node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port
|
|
89
|
-
|
|
220
|
+
node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port))
|
|
221
|
+
bound_port = node.incoming_socket.getsockname()[1]
|
|
222
|
+
if incoming_port != 0 and bound_port != incoming_port:
|
|
223
|
+
raise OSError(
|
|
224
|
+
f"incoming_port mismatch: requested {incoming_port}, got {bound_port}"
|
|
225
|
+
)
|
|
226
|
+
node.config["incoming_port"] = bound_port if incoming_port == 0 else incoming_port
|
|
227
|
+
node.incoming_socket.settimeout(0.5)
|
|
90
228
|
node.logger.info(
|
|
91
229
|
"Incoming UDP socket bound to %s:%s",
|
|
92
230
|
"::" if node.use_ipv6 else "0.0.0.0",
|
|
93
|
-
node.incoming_port,
|
|
231
|
+
node.config["incoming_port"],
|
|
94
232
|
)
|
|
95
233
|
node.incoming_queue = Queue()
|
|
234
|
+
node.incoming_queue_size = 0
|
|
235
|
+
node.incoming_queue_size_lock = threading.RLock()
|
|
236
|
+
node.incoming_queue_size_limit = node.config.get("incoming_queue_size_limit", 0)
|
|
237
|
+
node.incoming_queue_timeout = node.config.get("incoming_queue_timeout", 0)
|
|
96
238
|
node.incoming_populate_thread = threading.Thread(
|
|
97
239
|
target=populate_incoming_messages,
|
|
98
240
|
args=(node,),
|
|
99
241
|
daemon=True,
|
|
100
242
|
)
|
|
101
|
-
node.incoming_process_thread = threading.Thread(
|
|
102
|
-
target=process_incoming_messages,
|
|
103
|
-
args=(node,),
|
|
104
|
-
daemon=True,
|
|
105
|
-
)
|
|
106
|
-
node.incoming_populate_thread.start()
|
|
107
|
-
node.incoming_process_thread.start()
|
|
108
|
-
|
|
109
|
-
node.outgoing_socket = socket.socket(
|
|
110
|
-
socket.AF_INET6 if node.use_ipv6 else socket.AF_INET,
|
|
111
|
-
socket.SOCK_DGRAM,
|
|
112
|
-
)
|
|
243
|
+
node.incoming_process_thread = threading.Thread(
|
|
244
|
+
target=process_incoming_messages,
|
|
245
|
+
args=(node,),
|
|
246
|
+
daemon=True,
|
|
247
|
+
)
|
|
248
|
+
node.incoming_populate_thread.start()
|
|
249
|
+
node.incoming_process_thread.start()
|
|
250
|
+
|
|
251
|
+
node.outgoing_socket = socket.socket(
|
|
252
|
+
socket.AF_INET6 if node.use_ipv6 else socket.AF_INET,
|
|
253
|
+
socket.SOCK_DGRAM,
|
|
254
|
+
)
|
|
255
|
+
node.outgoing_socket.settimeout(0.5)
|
|
113
256
|
node.outgoing_queue = Queue()
|
|
257
|
+
node.outgoing_queue_size = 0
|
|
258
|
+
node.outgoing_queue_size_lock = threading.RLock()
|
|
259
|
+
node.outgoing_queue_size_limit = node.config.get("outgoing_queue_size_limit", 0)
|
|
260
|
+
node.outgoing_queue_timeout = node.config.get("outgoing_queue_timeout", 0)
|
|
114
261
|
|
|
115
262
|
node.outgoing_thread = threading.Thread(
|
|
116
263
|
target=process_outgoing_messages,
|
|
117
264
|
args=(node,),
|
|
118
265
|
daemon=True,
|
|
119
266
|
)
|
|
120
|
-
node.outgoing_thread.start()
|
|
121
|
-
|
|
122
|
-
# other workers & maps
|
|
123
|
-
# track atom requests we initiated; guarded by atom_requests_lock on the node
|
|
124
|
-
node.peer_manager_thread = threading.Thread(
|
|
125
|
-
target=
|
|
126
|
-
|
|
267
|
+
node.outgoing_thread.start()
|
|
268
|
+
|
|
269
|
+
# other workers & maps
|
|
270
|
+
# track atom requests we initiated; guarded by atom_requests_lock on the node
|
|
271
|
+
node.peer_manager_thread = threading.Thread(
|
|
272
|
+
target=manage_peer,
|
|
273
|
+
args=(node,),
|
|
274
|
+
daemon=True
|
|
275
|
+
)
|
|
276
|
+
node.peer_manager_thread.start()
|
|
277
|
+
|
|
278
|
+
with node.peers_lock:
|
|
279
|
+
node.peers = {} # Dict[bytes,Peer]
|
|
280
|
+
|
|
281
|
+
latest_block_hex = config.get("latest_block_hash")
|
|
282
|
+
if latest_block_hex:
|
|
283
|
+
try:
|
|
284
|
+
node.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
|
|
285
|
+
except Exception as exc:
|
|
286
|
+
node.logger.warning("Invalid latest_block_hash in config: %s", exc)
|
|
287
|
+
node.latest_block_hash = None
|
|
288
|
+
else:
|
|
289
|
+
node.latest_block_hash = None
|
|
290
|
+
|
|
291
|
+
node.logger.info(
|
|
292
|
+
"Communication ready (incoming_port=%s, outgoing_socket_initialized=%s, bootstrap_count=%s)",
|
|
293
|
+
node.config["incoming_port"],
|
|
294
|
+
node.outgoing_socket is not None,
|
|
295
|
+
len(node.bootstrap_peers),
|
|
127
296
|
)
|
|
128
|
-
node.
|
|
129
|
-
|
|
130
|
-
with node.peers_lock:
|
|
131
|
-
node.peers, node.addresses = {}, {} # peers: Dict[bytes,Peer], addresses: Dict[(str,int),bytes]
|
|
132
|
-
|
|
133
|
-
latest_block_hex = config.get("latest_block_hash")
|
|
134
|
-
if latest_block_hex:
|
|
135
|
-
try:
|
|
136
|
-
node.latest_block_hash = hex_to_bytes(latest_block_hex, expected_length=32)
|
|
137
|
-
except Exception as exc:
|
|
138
|
-
node.logger.warning("Invalid latest_block_hash in config: %s", exc)
|
|
139
|
-
node.latest_block_hash = None
|
|
140
|
-
else:
|
|
141
|
-
node.latest_block_hash = None
|
|
297
|
+
node.is_connected = True
|
|
142
298
|
|
|
143
|
-
# bootstrap pings
|
|
144
|
-
|
|
145
|
-
for addr in bootstrap_peers:
|
|
299
|
+
# bootstrap pings (requires connected state for enqueue_outgoing)
|
|
300
|
+
for addr in node.bootstrap_peers:
|
|
146
301
|
try:
|
|
147
302
|
host, port = address_str_to_host_and_port(addr) # type: ignore[arg-type]
|
|
148
303
|
except Exception as exc:
|
|
@@ -154,13 +309,19 @@ def communication_setup(node: "Node", config: dict):
|
|
|
154
309
|
sender=node.relay_public_key,
|
|
155
310
|
content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
|
|
156
311
|
)
|
|
157
|
-
|
|
312
|
+
enqueue_outgoing(
|
|
313
|
+
node,
|
|
314
|
+
(host, port),
|
|
315
|
+
message=handshake_message,
|
|
316
|
+
difficulty=1,
|
|
317
|
+
)
|
|
158
318
|
node.logger.info("Sent bootstrap handshake to %s:%s", host, port)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
319
|
+
if node.bootstrap_peers:
|
|
320
|
+
node._bootstrap_last_attempt = time.time()
|
|
321
|
+
advertise_cold_storage(node)
|
|
322
|
+
node.storage_index_thread = threading.Thread(
|
|
323
|
+
target=manage_storage_index,
|
|
324
|
+
args=(node,),
|
|
325
|
+
daemon=True,
|
|
165
326
|
)
|
|
166
|
-
node.
|
|
327
|
+
node.storage_index_thread.start()
|
astreum/communication/util.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
from typing import Tuple
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from .. import Node
|
|
6
|
+
|
|
3
7
|
|
|
4
8
|
def address_str_to_host_and_port(address: str) -> Tuple[str, int]:
|
|
5
9
|
"""Parse `host:port` (or `[ipv6]:port`) into a tuple."""
|
|
@@ -47,3 +51,13 @@ def xor_distance(a: bytes, b: bytes) -> int:
|
|
|
47
51
|
if len(a) != len(b):
|
|
48
52
|
raise ValueError("xor distance requires operands of equal length")
|
|
49
53
|
return int.from_bytes(bytes(x ^ y for x, y in zip(a, b)), "big", signed=False)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_bootstrap_peers(node: "Node") -> list[str]:
|
|
57
|
+
default_seed = node.config["default_seed"]
|
|
58
|
+
additional_seeds = node.config["additional_seeds"]
|
|
59
|
+
peers = []
|
|
60
|
+
if default_seed is not None:
|
|
61
|
+
peers.append(default_seed)
|
|
62
|
+
peers.extend(additional_seeds)
|
|
63
|
+
return peers
|
astreum/node.py
CHANGED
|
@@ -1,92 +1,102 @@
|
|
|
1
|
-
"""Core Astreum Node implementation."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import threading
|
|
6
|
-
import uuid
|
|
7
|
-
from typing import Dict
|
|
8
|
-
|
|
9
|
-
from astreum.communication.
|
|
10
|
-
from astreum.communication.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from astreum.
|
|
19
|
-
from astreum.
|
|
20
|
-
from astreum.
|
|
21
|
-
from astreum.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
from astreum.
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
"""Core Astreum Node implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
import uuid
|
|
7
|
+
from typing import Dict
|
|
8
|
+
|
|
9
|
+
from astreum.communication.node import connect_node
|
|
10
|
+
from astreum.communication.util import get_bootstrap_peers
|
|
11
|
+
from astreum.communication.disconnect import disconnect_node
|
|
12
|
+
from astreum.communication.models.peer import (
|
|
13
|
+
add_peer as peers_add_peer,
|
|
14
|
+
replace_peer as peers_replace_peer,
|
|
15
|
+
get_peer as peers_get_peer,
|
|
16
|
+
remove_peer as peers_remove_peer,
|
|
17
|
+
)
|
|
18
|
+
from astreum.validation.node import validate_blockchain
|
|
19
|
+
from astreum.verification.node import verify_blockchain
|
|
20
|
+
from astreum.machine import Expr, high_eval, low_eval, script_eval
|
|
21
|
+
from astreum.machine.models.environment import Env, env_get, env_set
|
|
22
|
+
from astreum.machine.models.expression import get_expr_list_from_storage
|
|
23
|
+
from astreum.storage.models.atom import get_atom_list_from_storage
|
|
24
|
+
from astreum.storage.actions.get import (
|
|
25
|
+
_hot_storage_get,
|
|
26
|
+
_cold_storage_get,
|
|
27
|
+
_network_get,
|
|
28
|
+
storage_get,
|
|
29
|
+
local_get,
|
|
30
|
+
)
|
|
31
|
+
from astreum.storage.actions.set import (
|
|
32
|
+
_hot_storage_set,
|
|
33
|
+
_cold_storage_set,
|
|
34
|
+
_network_set,
|
|
35
|
+
)
|
|
36
|
+
from astreum.storage.requests import add_atom_req, has_atom_req, pop_atom_req
|
|
37
|
+
from astreum.storage.setup import storage_setup
|
|
38
|
+
from astreum.utils.config import config_setup
|
|
39
|
+
from astreum.utils.logging import logging_setup
|
|
40
|
+
|
|
41
|
+
|
|
39
42
|
class Node:
|
|
40
43
|
def __init__(self, config: dict = {}):
|
|
41
44
|
self.config = config_setup(config=config)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
self.
|
|
56
|
-
self.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
45
|
+
self.bootstrap_peers = get_bootstrap_peers(self)
|
|
46
|
+
|
|
47
|
+
self.logger = logging_setup(self.config)
|
|
48
|
+
|
|
49
|
+
self.logger.info("Starting Astreum Node")
|
|
50
|
+
|
|
51
|
+
# Chain Configuration
|
|
52
|
+
self.logger.info(f"Chain configured as: {self.config["chain"]} ({self.config["chain_id"]})")
|
|
53
|
+
|
|
54
|
+
# Storage Setup
|
|
55
|
+
storage_setup(self, config=self.config)
|
|
56
|
+
|
|
57
|
+
# Machine Setup
|
|
58
|
+
self.environments: Dict[uuid.UUID, Env] = {}
|
|
59
|
+
self.machine_environments_lock = threading.RLock()
|
|
60
|
+
self.is_connected = False
|
|
61
|
+
self.latest_block_hash = None
|
|
62
|
+
self.latest_block = None
|
|
63
|
+
|
|
64
|
+
connect = connect_node
|
|
65
|
+
disconnect = disconnect_node
|
|
66
|
+
|
|
67
|
+
verify = verify_blockchain
|
|
68
|
+
|
|
69
|
+
validate = validate_blockchain
|
|
70
|
+
|
|
71
|
+
low_eval = low_eval
|
|
72
|
+
high_eval = high_eval
|
|
73
|
+
script_eval = script_eval
|
|
74
|
+
|
|
75
|
+
env_get = env_get
|
|
76
|
+
env_set = env_set
|
|
77
|
+
|
|
78
|
+
# Storage
|
|
79
|
+
## Get
|
|
80
|
+
_hot_storage_get = _hot_storage_get
|
|
81
|
+
_cold_storage_get = _cold_storage_get
|
|
82
|
+
_network_get = _network_get
|
|
83
|
+
|
|
84
|
+
## Set
|
|
85
|
+
_hot_storage_set = _hot_storage_set
|
|
86
|
+
_cold_storage_set = _cold_storage_set
|
|
87
|
+
_network_set = _network_set
|
|
88
|
+
|
|
89
|
+
storage_get = storage_get
|
|
90
|
+
local_get = local_get
|
|
91
|
+
|
|
92
|
+
get_expr_list_from_storage = get_expr_list_from_storage
|
|
93
|
+
get_atom_list_from_storage = get_atom_list_from_storage
|
|
94
|
+
|
|
95
|
+
add_atom_req = add_atom_req
|
|
96
|
+
has_atom_req = has_atom_req
|
|
97
|
+
pop_atom_req = pop_atom_req
|
|
98
|
+
|
|
99
|
+
add_peer = peers_add_peer
|
|
100
|
+
replace_peer = peers_replace_peer
|
|
101
|
+
get_peer = peers_get_peer
|
|
102
|
+
remove_peer = peers_remove_peer
|