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.
- astreum/__init__.py +1 -2
- astreum/communication/__init__.py +15 -11
- astreum/communication/difficulty.py +39 -0
- astreum/communication/disconnect.py +57 -0
- astreum/communication/handlers/handshake.py +105 -62
- astreum/communication/handlers/object_request.py +226 -138
- astreum/communication/handlers/object_response.py +118 -10
- 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 +133 -58
- astreum/communication/setup.py +272 -113
- astreum/communication/util.py +14 -0
- astreum/machine/evaluations/low_evaluation.py +5 -5
- astreum/machine/models/expression.py +5 -5
- astreum/node.py +96 -87
- astreum/storage/actions/get.py +285 -183
- astreum/storage/actions/set.py +171 -156
- astreum/storage/models/atom.py +0 -14
- astreum/storage/models/trie.py +2 -2
- astreum/storage/providers.py +24 -0
- astreum/storage/requests.py +13 -10
- astreum/storage/setup.py +20 -15
- astreum/utils/config.py +260 -43
- 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/{consensus → validation}/models/account.py +1 -1
- astreum/validation/models/block.py +544 -0
- astreum/validation/models/fork.py +511 -0
- astreum/{consensus → validation}/models/receipt.py +18 -5
- astreum/{consensus → validation}/models/transaction.py +50 -8
- astreum/validation/node.py +190 -0
- astreum/{consensus → validation}/validator.py +1 -1
- astreum/validation/workers/__init__.py +8 -0
- astreum/{consensus → validation}/workers/validation.py +360 -333
- 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.16.dist-info → astreum-0.3.48.dist-info}/METADATA +45 -9
- astreum-0.3.48.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.16.dist-info/RECORD +0 -72
- /astreum/{consensus → validation}/models/__init__.py +0 -0
- /astreum/{consensus → validation}/models/accounts.py +0 -0
- {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/WHEEL +0 -0
- {astreum-0.3.16.dist-info → astreum-0.3.48.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
from ..
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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__(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
+
enqueue_outgoing(
|
|
55
|
+
node,
|
|
56
|
+
(host, port),
|
|
57
|
+
message=handshake_message,
|
|
58
|
+
difficulty=1,
|
|
59
|
+
)
|