astreum 0.3.16__py3-none-any.whl → 0.3.48__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 (65) hide show
  1. astreum/__init__.py +1 -2
  2. astreum/communication/__init__.py +15 -11
  3. astreum/communication/difficulty.py +39 -0
  4. astreum/communication/disconnect.py +57 -0
  5. astreum/communication/handlers/handshake.py +105 -62
  6. astreum/communication/handlers/object_request.py +226 -138
  7. astreum/communication/handlers/object_response.py +118 -10
  8. astreum/communication/handlers/ping.py +9 -0
  9. astreum/communication/handlers/route_request.py +7 -1
  10. astreum/communication/handlers/route_response.py +7 -1
  11. astreum/communication/incoming_queue.py +96 -0
  12. astreum/communication/message_pow.py +36 -0
  13. astreum/communication/models/peer.py +4 -0
  14. astreum/communication/models/ping.py +27 -6
  15. astreum/communication/models/route.py +4 -0
  16. astreum/communication/{start.py → node.py} +10 -11
  17. astreum/communication/outgoing_queue.py +108 -0
  18. astreum/communication/processors/incoming.py +110 -37
  19. astreum/communication/processors/outgoing.py +35 -2
  20. astreum/communication/processors/peer.py +133 -58
  21. astreum/communication/setup.py +272 -113
  22. astreum/communication/util.py +14 -0
  23. astreum/machine/evaluations/low_evaluation.py +5 -5
  24. astreum/machine/models/expression.py +5 -5
  25. astreum/node.py +96 -87
  26. astreum/storage/actions/get.py +285 -183
  27. astreum/storage/actions/set.py +171 -156
  28. astreum/storage/models/atom.py +0 -14
  29. astreum/storage/models/trie.py +2 -2
  30. astreum/storage/providers.py +24 -0
  31. astreum/storage/requests.py +13 -10
  32. astreum/storage/setup.py +20 -15
  33. astreum/utils/config.py +260 -43
  34. astreum/utils/logging.py +1 -1
  35. astreum/{consensus → validation}/__init__.py +0 -4
  36. astreum/validation/constants.py +2 -0
  37. astreum/{consensus → validation}/genesis.py +4 -6
  38. astreum/{consensus → validation}/models/account.py +1 -1
  39. astreum/validation/models/block.py +544 -0
  40. astreum/validation/models/fork.py +511 -0
  41. astreum/{consensus → validation}/models/receipt.py +18 -5
  42. astreum/{consensus → validation}/models/transaction.py +50 -8
  43. astreum/validation/node.py +190 -0
  44. astreum/{consensus → validation}/validator.py +1 -1
  45. astreum/validation/workers/__init__.py +8 -0
  46. astreum/{consensus → validation}/workers/validation.py +360 -333
  47. astreum/verification/__init__.py +4 -0
  48. astreum/{consensus/workers/discovery.py → verification/discover.py} +1 -1
  49. astreum/verification/node.py +61 -0
  50. astreum/verification/worker.py +183 -0
  51. {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/METADATA +45 -9
  52. astreum-0.3.48.dist-info/RECORD +79 -0
  53. astreum/consensus/models/block.py +0 -364
  54. astreum/consensus/models/chain.py +0 -66
  55. astreum/consensus/models/fork.py +0 -100
  56. astreum/consensus/setup.py +0 -83
  57. astreum/consensus/start.py +0 -67
  58. astreum/consensus/workers/__init__.py +0 -9
  59. astreum/consensus/workers/verify.py +0 -90
  60. astreum-0.3.16.dist-info/RECORD +0 -72
  61. /astreum/{consensus → validation}/models/__init__.py +0 -0
  62. /astreum/{consensus → validation}/models/accounts.py +0 -0
  63. {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/WHEEL +0 -0
  64. {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/licenses/LICENSE +0 -0
  65. {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/top_level.txt +0 -0
@@ -1,176 +1,264 @@
1
- import logging
2
- import socket
1
+ import logging
2
+ import socket
3
3
  from enum import IntEnum
4
- from typing import TYPE_CHECKING, Tuple
4
+ from typing import Optional, TYPE_CHECKING, Tuple
5
5
 
6
- from .object_response import ObjectResponse, ObjectResponseType
6
+ from .object_response import (
7
+ ObjectResponse,
8
+ ObjectResponseType,
9
+ OBJECT_FOUND_ATOM_PAYLOAD,
10
+ OBJECT_FOUND_LIST_PAYLOAD,
11
+ encode_object_found_atom_payload,
12
+ encode_object_found_list_payload,
13
+ )
14
+ from ..outgoing_queue import enqueue_outgoing
7
15
  from ..models.message import Message, MessageTopic
8
16
  from ..util import xor_distance
9
-
10
- if TYPE_CHECKING:
11
- from .. import Node
12
- from ..models.peer import Peer
13
-
14
-
15
- class ObjectRequestType(IntEnum):
16
- OBJECT_GET = 0
17
- OBJECT_PUT = 1
18
-
19
-
17
+ from ...storage.providers import provider_id_for_payload, provider_payload_for_id
18
+
19
+ if TYPE_CHECKING:
20
+ from .. import Node
21
+ from ..models.peer import Peer
22
+
23
+
24
+ class ObjectRequestType(IntEnum):
25
+ OBJECT_GET = 0
26
+ OBJECT_PUT = 1
27
+
28
+
20
29
  class ObjectRequest:
21
30
  type: ObjectRequestType
22
31
  data: bytes
23
32
  atom_id: bytes
33
+ payload_type: Optional[int]
24
34
 
25
- def __init__(self, type: ObjectRequestType, data: bytes, atom_id: bytes = None):
35
+ def __init__(
36
+ self,
37
+ type: ObjectRequestType,
38
+ data: bytes = b"",
39
+ atom_id: bytes = None,
40
+ payload_type: Optional[int] = None,
41
+ ):
26
42
  self.type = type
27
43
  self.data = data
28
44
  self.atom_id = atom_id
45
+ self.payload_type = payload_type
29
46
 
30
47
  def to_bytes(self):
31
- return bytes([self.type.value]) + self.atom_id + self.data
48
+ if self.type == ObjectRequestType.OBJECT_GET and self.payload_type is not None:
49
+ payload = bytes([self.payload_type]) + self.data
50
+ else:
51
+ payload = self.data
52
+ return bytes([self.type.value]) + self.atom_id + payload
32
53
 
33
54
  @classmethod
34
55
  def from_bytes(cls, data: bytes) -> "ObjectRequest":
35
56
  # need at least 1 byte for type + 32 bytes for hash
36
- if len(data) < 1 + 32:
37
- raise ValueError(f"Too short for ObjectRequest ({len(data)} bytes)")
38
-
39
- type_val = data[0]
40
- try:
41
- req_type = ObjectRequestType(type_val)
42
- except ValueError:
43
- raise ValueError(f"Unknown ObjectRequestType: {type_val!r}")
44
-
57
+ if len(data) < 1 + 32:
58
+ raise ValueError(f"Too short for ObjectRequest ({len(data)} bytes)")
59
+
60
+ type_val = data[0]
61
+ try:
62
+ req_type = ObjectRequestType(type_val)
63
+ except ValueError:
64
+ raise ValueError(f"Unknown ObjectRequestType: {type_val!r}")
65
+
45
66
  atom_id_bytes = data[1:33]
46
- payload = data[33:]
67
+ payload = data[33:]
68
+ if req_type == ObjectRequestType.OBJECT_GET:
69
+ if payload:
70
+ payload_type = payload[0]
71
+ payload = payload[1:]
72
+ else:
73
+ payload_type = None
74
+ return cls(req_type, payload, atom_id_bytes, payload_type=payload_type)
47
75
  return cls(req_type, payload, atom_id_bytes)
48
-
49
-
50
- def encode_peer_contact_bytes(peer: "Peer") -> bytes:
51
- """Return a fixed-width peer contact payload (32-byte key + IPv4 + port)."""
52
- host, port = peer.address
53
- key_bytes = peer.public_key_bytes
54
- try:
55
- ip_bytes = socket.inet_aton(host)
56
- except OSError as exc: # pragma: no cover - inet_aton raises for invalid hosts
57
- raise ValueError(f"invalid IPv4 address: {host}") from exc
58
- if not (0 <= port <= 0xFFFF):
59
- raise ValueError(f"port out of range (0-65535): {port}")
60
- port_bytes = int(port).to_bytes(2, "big", signed=False)
61
- return key_bytes + ip_bytes + port_bytes
62
-
63
-
64
- def handle_object_request(node: "Node", peer: "Peer", message: Message) -> None:
65
- if message.content is None:
66
- node.logger.warning("OBJECT_REQUEST from %s missing content", peer.address)
67
- return
68
-
69
- try:
70
- object_request = ObjectRequest.from_bytes(message.content)
71
- except Exception as exc:
72
- node.logger.warning("Error decoding OBJECT_REQUEST from %s: %s", peer.address, exc)
73
- return
74
-
75
- match object_request.type:
76
+
77
+
78
+ def encode_peer_contact_bytes(peer: "Peer") -> bytes:
79
+ """Return a fixed-width peer contact payload (32-byte key + IPv4 + port)."""
80
+ host, port = peer.address
81
+ key_bytes = peer.public_key_bytes
82
+ try:
83
+ ip_bytes = socket.inet_aton(host)
84
+ except OSError as exc: # pragma: no cover - inet_aton raises for invalid hosts
85
+ raise ValueError(f"invalid IPv4 address: {host}") from exc
86
+ if not (0 <= port <= 0xFFFF):
87
+ raise ValueError(f"port out of range (0-65535): {port}")
88
+ port_bytes = int(port).to_bytes(2, "big", signed=False)
89
+ return key_bytes + ip_bytes + port_bytes
90
+
91
+
92
+ def handle_object_request(node: "Node", peer: "Peer", message: Message) -> None:
93
+ if message.content is None:
94
+ node.logger.warning("OBJECT_REQUEST from %s missing content", peer.address)
95
+ return
96
+
97
+ try:
98
+ object_request = ObjectRequest.from_bytes(message.content)
99
+ except Exception as exc:
100
+ node.logger.warning("Error decoding OBJECT_REQUEST from %s: %s", peer.address, exc)
101
+ return
102
+
103
+ match object_request.type:
76
104
  case ObjectRequestType.OBJECT_GET:
77
105
  atom_id = object_request.atom_id
78
106
  node.logger.debug("Handling OBJECT_GET for %s from %s", atom_id.hex(), peer.address)
107
+ payload_type = object_request.payload_type
108
+ if payload_type is None:
109
+ payload_type = OBJECT_FOUND_ATOM_PAYLOAD
79
110
 
80
- local_atom = node.local_get(atom_id)
81
- if local_atom is not None:
82
- node.logger.debug("Object %s found locally; returning to %s", atom_id.hex(), peer.address)
83
- resp = ObjectResponse(
84
- type=ObjectResponseType.OBJECT_FOUND,
85
- data=local_atom.to_bytes(),
86
- atom_id=atom_id
87
- )
88
- obj_res_msg = Message(
89
- topic=MessageTopic.OBJECT_RESPONSE,
90
- body=resp.to_bytes(),
91
- sender=node.relay_public_key,
92
- )
93
- obj_res_msg.encrypt(peer.shared_key_bytes)
94
- node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
95
- return
96
-
97
- if atom_id in node.storage_index:
98
- node.logger.debug("Known provider for %s; informing %s", atom_id.hex(), peer.address)
99
- provider_bytes = node.storage_index[atom_id]
100
- resp = ObjectResponse(
101
- type=ObjectResponseType.OBJECT_PROVIDER,
102
- data=provider_bytes,
103
- atom_id=atom_id
104
- )
105
- obj_res_msg = Message(
106
- topic=MessageTopic.OBJECT_RESPONSE,
107
- body=resp.to_bytes(),
108
- sender=node.relay_public_key,
109
- )
110
- obj_res_msg.encrypt(peer.shared_key_bytes)
111
- node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
112
- return
113
-
114
- nearest_peer = node.peer_route.closest_peer_for_hash(atom_id)
115
- if nearest_peer:
116
- node.logger.debug("Forwarding requester %s to nearest peer for %s", peer.address, atom_id.hex())
117
- peer_info = encode_peer_contact_bytes(nearest_peer)
118
- resp = ObjectResponse(
119
- type=ObjectResponseType.OBJECT_PROVIDER,
120
- # type=ObjectResponseType.OBJECT_NEAREST_PEER,
121
- data=peer_info,
122
- atom_id=atom_id
111
+ if payload_type == OBJECT_FOUND_ATOM_PAYLOAD:
112
+ local_atom = node.get_atom_from_local_storage(atom_id=atom_id)
113
+ if local_atom is not None:
114
+ node.logger.debug("Object %s found locally; returning to %s", atom_id.hex(), peer.address)
115
+ resp = ObjectResponse(
116
+ type=ObjectResponseType.OBJECT_FOUND,
117
+ data=encode_object_found_atom_payload(local_atom),
118
+ atom_id=atom_id
119
+ )
120
+ obj_res_msg = Message(
121
+ topic=MessageTopic.OBJECT_RESPONSE,
122
+ body=resp.to_bytes(),
123
+ sender=node.relay_public_key,
124
+ )
125
+ obj_res_msg.encrypt(peer.shared_key_bytes)
126
+ enqueue_outgoing(
127
+ node,
128
+ peer.address,
129
+ message=obj_res_msg,
130
+ difficulty=peer.difficulty,
131
+ )
132
+ return
133
+ elif payload_type == OBJECT_FOUND_LIST_PAYLOAD:
134
+ local_atoms = node.get_atom_list_from_local_storage(root_hash=atom_id)
135
+ if local_atoms is not None:
136
+ node.logger.debug("Object list %s found locally; returning to %s", atom_id.hex(), peer.address)
137
+ resp = ObjectResponse(
138
+ type=ObjectResponseType.OBJECT_FOUND,
139
+ data=encode_object_found_list_payload(local_atoms),
140
+ atom_id=atom_id
141
+ )
142
+ obj_res_msg = Message(
143
+ topic=MessageTopic.OBJECT_RESPONSE,
144
+ body=resp.to_bytes(),
145
+ sender=node.relay_public_key,
146
+ )
147
+ obj_res_msg.encrypt(peer.shared_key_bytes)
148
+ enqueue_outgoing(
149
+ node,
150
+ peer.address,
151
+ message=obj_res_msg,
152
+ difficulty=peer.difficulty,
153
+ )
154
+ return
155
+ else:
156
+ node.logger.warning(
157
+ "Unknown OBJECT_GET payload type %s for %s",
158
+ payload_type,
159
+ atom_id.hex(),
123
160
  )
161
+
162
+ if atom_id in node.storage_index:
163
+ provider_id = node.storage_index[atom_id]
164
+ provider_bytes = provider_payload_for_id(node, provider_id)
165
+ if provider_bytes is not None:
166
+ node.logger.debug("Known provider for %s; informing %s", atom_id.hex(), peer.address)
167
+ resp = ObjectResponse(
168
+ type=ObjectResponseType.OBJECT_PROVIDER,
169
+ data=provider_bytes,
170
+ atom_id=atom_id
171
+ )
172
+ obj_res_msg = Message(
173
+ topic=MessageTopic.OBJECT_RESPONSE,
174
+ body=resp.to_bytes(),
175
+ sender=node.relay_public_key,
176
+ )
177
+ obj_res_msg.encrypt(peer.shared_key_bytes)
178
+ enqueue_outgoing(
179
+ node,
180
+ peer.address,
181
+ message=obj_res_msg,
182
+ difficulty=peer.difficulty,
183
+ )
184
+ return
185
+ node.logger.warning(
186
+ "Unknown provider id %s for %s",
187
+ provider_id,
188
+ atom_id.hex(),
189
+ )
190
+
191
+ nearest_peer = node.peer_route.closest_peer_for_hash(atom_id)
192
+ if nearest_peer:
193
+ node.logger.debug("Forwarding requester %s to nearest peer for %s", peer.address, atom_id.hex())
194
+ peer_info = encode_peer_contact_bytes(nearest_peer)
195
+ resp = ObjectResponse(
196
+ type=ObjectResponseType.OBJECT_PROVIDER,
197
+ # type=ObjectResponseType.OBJECT_NEAREST_PEER,
198
+ data=peer_info,
199
+ atom_id=atom_id
200
+ )
124
201
  obj_res_msg = Message(
125
202
  topic=MessageTopic.OBJECT_RESPONSE,
126
203
  body=resp.to_bytes(),
127
204
  sender=node.relay_public_key,
128
205
  )
129
206
  obj_res_msg.encrypt(nearest_peer.shared_key_bytes)
130
- node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
131
-
132
- case ObjectRequestType.OBJECT_PUT:
133
- node.logger.debug("Handling OBJECT_PUT for %s from %s", object_request.atom_id.hex(), peer.address)
134
-
135
- nearest_peer = node.peer_route.closest_peer_for_hash(object_request.atom_id)
136
- is_self_closest = False
137
- if nearest_peer is None or nearest_peer.address is None:
138
- is_self_closest = True
139
- else:
140
- try:
141
- self_distance = xor_distance(object_request.atom_id, node.relay_public_key_bytes)
142
- peer_distance = xor_distance(object_request.atom_id, nearest_peer.public_key_bytes)
143
- except Exception as exc:
144
- node.logger.warning(
145
- "Failed distance comparison for OBJECT_PUT %s: %s",
146
- object_request.atom_id.hex(),
147
- exc,
148
- )
149
- is_self_closest = True
150
- else:
151
- is_self_closest = self_distance <= peer_distance
152
-
153
- if is_self_closest:
154
- node.logger.debug("Storing provider info for %s locally", object_request.atom_id.hex())
155
- node.storage_index[object_request.atom_id] = object_request.data
156
- else:
157
- node.logger.debug(
158
- "Forwarding OBJECT_PUT for %s to nearer peer %s",
159
- object_request.atom_id.hex(),
160
- nearest_peer.address,
161
- )
162
- fwd_req = ObjectRequest(
163
- type=ObjectRequestType.OBJECT_PUT,
164
- data=object_request.data,
165
- atom_id=object_request.atom_id,
207
+ enqueue_outgoing(
208
+ node,
209
+ peer.address,
210
+ message=obj_res_msg,
211
+ difficulty=peer.difficulty,
166
212
  )
213
+
214
+ case ObjectRequestType.OBJECT_PUT:
215
+ node.logger.debug("Handling OBJECT_PUT for %s from %s", object_request.atom_id.hex(), peer.address)
216
+
217
+ nearest_peer = node.peer_route.closest_peer_for_hash(object_request.atom_id)
218
+ is_self_closest = False
219
+ if nearest_peer is None or nearest_peer.address is None:
220
+ is_self_closest = True
221
+ else:
222
+ try:
223
+ self_distance = xor_distance(object_request.atom_id, node.relay_public_key_bytes)
224
+ peer_distance = xor_distance(object_request.atom_id, nearest_peer.public_key_bytes)
225
+ except Exception as exc:
226
+ node.logger.warning(
227
+ "Failed distance comparison for OBJECT_PUT %s: %s",
228
+ object_request.atom_id.hex(),
229
+ exc,
230
+ )
231
+ is_self_closest = True
232
+ else:
233
+ is_self_closest = self_distance <= peer_distance
234
+
235
+ if is_self_closest:
236
+ node.logger.debug("Storing provider info for %s locally", object_request.atom_id.hex())
237
+ provider_id = provider_id_for_payload(node, object_request.data)
238
+ node.storage_index[object_request.atom_id] = provider_id
239
+ else:
240
+ node.logger.debug(
241
+ "Forwarding OBJECT_PUT for %s to nearer peer %s",
242
+ object_request.atom_id.hex(),
243
+ nearest_peer.address,
244
+ )
245
+ fwd_req = ObjectRequest(
246
+ type=ObjectRequestType.OBJECT_PUT,
247
+ data=object_request.data,
248
+ atom_id=object_request.atom_id,
249
+ )
167
250
  obj_req_msg = Message(
168
251
  topic=MessageTopic.OBJECT_REQUEST,
169
252
  body=fwd_req.to_bytes(),
170
253
  sender=node.relay_public_key,
171
254
  )
172
255
  obj_req_msg.encrypt(nearest_peer.shared_key_bytes)
173
- node.outgoing_queue.put((obj_req_msg.to_bytes(), nearest_peer.address))
174
-
175
- case _:
176
- node.logger.warning("Unknown ObjectRequestType %s from %s", object_request.type, peer.address)
256
+ enqueue_outgoing(
257
+ node,
258
+ nearest_peer.address,
259
+ message=obj_req_msg,
260
+ difficulty=nearest_peer.difficulty,
261
+ )
262
+
263
+ case _:
264
+ node.logger.warning("Unknown ObjectRequestType %s from %s", object_request.type, peer.address)
@@ -1,9 +1,11 @@
1
1
  import socket
2
2
  from enum import IntEnum
3
- from typing import Tuple, TYPE_CHECKING
3
+ from typing import List, Tuple, TYPE_CHECKING
4
4
 
5
+ from ..outgoing_queue import enqueue_outgoing
5
6
  from ..models.message import Message, MessageTopic
6
7
  from ...storage.models.atom import Atom
8
+ from ...storage.requests import get_atom_req_payload
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  from .. import Node
@@ -16,6 +18,10 @@ class ObjectResponseType(IntEnum):
16
18
  OBJECT_NEAREST_PEER = 2
17
19
 
18
20
 
21
+ OBJECT_FOUND_ATOM_PAYLOAD = 1
22
+ OBJECT_FOUND_LIST_PAYLOAD = 2
23
+
24
+
19
25
  class ObjectResponse:
20
26
  type: ObjectResponseType
21
27
  data: bytes
@@ -46,6 +52,37 @@ class ObjectResponse:
46
52
  return cls(resp_type, payload, atom_id)
47
53
 
48
54
 
55
+ def encode_object_found_atom_payload(atom: Atom) -> bytes:
56
+ return bytes([OBJECT_FOUND_ATOM_PAYLOAD]) + atom.to_bytes()
57
+
58
+
59
+ def encode_object_found_list_payload(atoms: List[Atom]) -> bytes:
60
+ parts = [bytes([OBJECT_FOUND_LIST_PAYLOAD])]
61
+ for atom in atoms:
62
+ atom_bytes = atom.to_bytes()
63
+ parts.append(len(atom_bytes).to_bytes(4, "big", signed=False))
64
+ parts.append(atom_bytes)
65
+ return b"".join(parts)
66
+
67
+
68
+ def decode_object_found_list_payload(payload: bytes) -> List[Atom]:
69
+ atoms: List[Atom] = []
70
+ offset = 0
71
+ while offset < len(payload):
72
+ if len(payload) - offset < 4:
73
+ raise ValueError("truncated atom length")
74
+ atom_len = int.from_bytes(payload[offset : offset + 4], "big", signed=False)
75
+ offset += 4
76
+ if atom_len <= 0:
77
+ raise ValueError("invalid atom length")
78
+ end = offset + atom_len
79
+ if end > len(payload):
80
+ raise ValueError("truncated atom payload")
81
+ atoms.append(Atom.from_bytes(payload[offset:end]))
82
+ offset = end
83
+ return atoms
84
+
85
+
49
86
  def decode_object_provider(payload: bytes) -> Tuple[bytes, str, int]:
50
87
  expected_len = 32 + 4 + 2
51
88
  if len(payload) < expected_len:
@@ -76,17 +113,78 @@ def handle_object_response(node: "Node", peer: "Peer", message: Message) -> None
76
113
 
77
114
  match object_response.type:
78
115
  case ObjectResponseType.OBJECT_FOUND:
79
- atom = Atom.from_bytes(object_response.data)
80
- atom_id = atom.object_id()
81
- if object_response.atom_id == atom_id:
82
- node.pop_atom_req(atom_id)
83
- node._hot_storage_set(atom_id, atom)
84
- else:
116
+ payload = object_response.data
117
+ if not payload:
85
118
  node.logger.warning(
86
- "OBJECT_FOUND atom ID mismatch (expected=%s got=%s)",
119
+ "OBJECT_FOUND payload for %s missing content",
87
120
  object_response.atom_id.hex(),
88
- atom_id.hex(),
89
121
  )
122
+ return
123
+
124
+ payload_type = payload[0]
125
+ body = payload[1:]
126
+
127
+ if payload_type == OBJECT_FOUND_ATOM_PAYLOAD:
128
+ try:
129
+ atom = Atom.from_bytes(body)
130
+ except Exception as exc:
131
+ node.logger.warning(
132
+ "Invalid OBJECT_FOUND atom payload for %s: %s",
133
+ object_response.atom_id.hex(),
134
+ exc,
135
+ )
136
+ return
137
+
138
+ atom_id = atom.object_id()
139
+ if object_response.atom_id != atom_id:
140
+ node.logger.warning(
141
+ "OBJECT_FOUND atom ID mismatch (expected=%s got=%s)",
142
+ object_response.atom_id.hex(),
143
+ atom_id.hex(),
144
+ )
145
+ return
146
+
147
+ node.pop_atom_req(atom_id)
148
+ node._hot_storage_set(atom_id, atom)
149
+ return
150
+
151
+ if payload_type == OBJECT_FOUND_LIST_PAYLOAD:
152
+ try:
153
+ atoms = decode_object_found_list_payload(body)
154
+ except Exception as exc:
155
+ node.logger.warning(
156
+ "Invalid OBJECT_FOUND list payload for %s: %s",
157
+ object_response.atom_id.hex(),
158
+ exc,
159
+ )
160
+ return
161
+
162
+ if not atoms:
163
+ node.logger.warning(
164
+ "OBJECT_FOUND list payload for %s contained no atoms",
165
+ object_response.atom_id.hex(),
166
+ )
167
+ return
168
+
169
+ root_id = atoms[0].object_id()
170
+ if object_response.atom_id != root_id:
171
+ node.logger.warning(
172
+ "OBJECT_FOUND list root ID mismatch (expected=%s got=%s)",
173
+ object_response.atom_id.hex(),
174
+ root_id.hex(),
175
+ )
176
+ return
177
+
178
+ node.pop_atom_req(root_id)
179
+ for atom in atoms:
180
+ node._hot_storage_set(atom.object_id(), atom)
181
+ return
182
+
183
+ node.logger.warning(
184
+ "Unknown OBJECT_FOUND payload type %s for %s",
185
+ payload_type,
186
+ object_response.atom_id.hex(),
187
+ )
90
188
 
91
189
  case ObjectResponseType.OBJECT_PROVIDER:
92
190
  try:
@@ -97,10 +195,15 @@ def handle_object_response(node: "Node", peer: "Peer", message: Message) -> None
97
195
 
98
196
  from .object_request import ObjectRequest, ObjectRequestType
99
197
 
198
+ payload_type = get_atom_req_payload(node, object_response.atom_id)
199
+ if payload_type is None:
200
+ payload_type = OBJECT_FOUND_ATOM_PAYLOAD
201
+
100
202
  obj_req = ObjectRequest(
101
203
  type=ObjectRequestType.OBJECT_GET,
102
204
  data=b"",
103
205
  atom_id=object_response.atom_id,
206
+ payload_type=payload_type,
104
207
  )
105
208
  obj_req_bytes = obj_req.to_bytes()
106
209
  obj_req_msg = Message(
@@ -109,7 +212,12 @@ def handle_object_response(node: "Node", peer: "Peer", message: Message) -> None
109
212
  sender=node.relay_public_key,
110
213
  )
111
214
  obj_req_msg.encrypt(peer.shared_key_bytes)
112
- node.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_address, provider_port)))
215
+ enqueue_outgoing(
216
+ node,
217
+ (provider_address, provider_port),
218
+ message=obj_req_msg,
219
+ difficulty=1,
220
+ )
113
221
 
114
222
  case ObjectResponseType.OBJECT_NEAREST_PEER:
115
223
  node.logger.debug("Ignoring OBJECT_NEAREST_PEER response from %s", peer.address)
@@ -20,6 +20,15 @@ def handle_ping(node: "Node", peer: Peer, payload: bytes) -> None:
20
20
 
21
21
  peer.timestamp = datetime.now(timezone.utc)
22
22
  peer.latest_block = ping.latest_block
23
+ peer.difficulty = ping.difficulty
24
+ if peer.is_default_seed and ping.latest_block:
25
+ if getattr(node, "latest_block_hash", None) != ping.latest_block:
26
+ node.latest_block_hash = ping.latest_block
27
+ node.latest_block = None
28
+ node.logger.info(
29
+ "Updated latest block hash from default seed %s",
30
+ peer.address[0] if peer.address else "unknown",
31
+ )
23
32
 
24
33
  validation_route = node.validation_route
25
34
  if validation_route is None:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import socket
4
4
 
5
+ from ..outgoing_queue import enqueue_outgoing
5
6
  from ..models.message import Message, MessageTopic
6
7
  from ..util import xor_distance
7
8
 
@@ -73,4 +74,9 @@ def handle_route_request(node: "Node", peer: "Peer", message: Message) -> None:
73
74
  sender=node.relay_public_key,
74
75
  )
75
76
  response.encrypt(peer.shared_key_bytes)
76
- node.outgoing_queue.put((response.to_bytes(), peer.address))
77
+ enqueue_outgoing(
78
+ node,
79
+ peer.address,
80
+ message=response,
81
+ difficulty=peer.difficulty,
82
+ )
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import socket
4
4
 
5
+ from ..outgoing_queue import enqueue_outgoing
5
6
  from ..models.message import Message
6
7
 
7
8
  from typing import TYPE_CHECKING
@@ -50,4 +51,9 @@ def handle_route_response(node: "Node", peer: "Peer", message: Message) -> None:
50
51
  content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
51
52
  )
52
53
  for host, port in decoded_addresses:
53
- node.outgoing_queue.put((handshake_message.to_bytes(), (host, port)))
54
+ enqueue_outgoing(
55
+ node,
56
+ (host, port),
57
+ message=handshake_message,
58
+ difficulty=1,
59
+ )