astreum 0.3.5__tar.gz → 0.3.14__tar.gz
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-0.3.5/src/astreum.egg-info → astreum-0.3.14}/PKG-INFO +3 -3
- {astreum-0.3.5 → astreum-0.3.14}/README.md +7 -7
- {astreum-0.3.5 → astreum-0.3.14}/pyproject.toml +1 -1
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/__init__.py +4 -2
- astreum-0.3.14/src/astreum/communication/handlers/handshake.py +62 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/handlers/object_request.py +53 -30
- astreum-0.3.14/src/astreum/communication/handlers/object_response.py +115 -0
- astreum-0.3.14/src/astreum/communication/handlers/ping.py +34 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/handlers/route_request.py +19 -21
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/handlers/route_response.py +12 -11
- astreum-0.3.14/src/astreum/communication/models/message.py +124 -0
- astreum-0.3.14/src/astreum/communication/models/peer.py +51 -0
- astreum-0.3.14/src/astreum/communication/processors/incoming.py +98 -0
- astreum-0.3.14/src/astreum/communication/processors/outgoing.py +20 -0
- astreum-0.3.14/src/astreum/communication/processors/peer.py +59 -0
- astreum-0.3.14/src/astreum/communication/setup.py +168 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/start.py +9 -10
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/start.py +7 -8
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/validator.py +15 -8
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/workers/discovery.py +6 -7
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/workers/validation.py +334 -292
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/workers/verify.py +8 -10
- astreum-0.3.14/src/astreum/crypto/chacha20poly1305.py +74 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/evaluations/high_evaluation.py +237 -237
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/evaluations/low_evaluation.py +18 -18
- astreum-0.3.14/src/astreum/machine/models/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/node.py +21 -1
- astreum-0.3.14/src/astreum/storage/actions/get.py +183 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/storage/actions/set.py +55 -15
- astreum-0.3.14/src/astreum/storage/requests.py +28 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/utils/config.py +29 -1
- {astreum-0.3.5 → astreum-0.3.14/src/astreum.egg-info}/PKG-INFO +3 -3
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum.egg-info/SOURCES.txt +6 -0
- astreum-0.3.5/src/astreum/communication/handlers/handshake.py +0 -81
- astreum-0.3.5/src/astreum/communication/handlers/object_response.py +0 -37
- astreum-0.3.5/src/astreum/communication/handlers/ping.py +0 -48
- astreum-0.3.5/src/astreum/communication/models/message.py +0 -105
- astreum-0.3.5/src/astreum/communication/models/peer.py +0 -23
- astreum-0.3.5/src/astreum/communication/setup.py +0 -262
- astreum-0.3.5/src/astreum/storage/actions/get.py +0 -85
- {astreum-0.3.5 → astreum-0.3.14}/LICENSE +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/setup.cfg +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/handlers/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/models/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/models/ping.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/models/route.py +0 -0
- {astreum-0.3.5/src/astreum/consensus/models → astreum-0.3.14/src/astreum/communication/processors}/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/communication/util.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/genesis.py +0 -0
- {astreum-0.3.5/src/astreum/crypto → astreum-0.3.14/src/astreum/consensus/models}/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/account.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/accounts.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/block.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/chain.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/fork.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/receipt.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/models/transaction.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/setup.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/consensus/workers/__init__.py +0 -0
- {astreum-0.3.5/src/astreum/machine/evaluations → astreum-0.3.14/src/astreum/crypto}/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/crypto/quadratic_form.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/crypto/wesolowski.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/__init__.py +0 -0
- {astreum-0.3.5/src/astreum/machine/models → astreum-0.3.14/src/astreum/machine/evaluations}/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/evaluations/script_evaluation.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/models/environment.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/models/expression.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/models/meter.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/parser.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/machine/tokenizer.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/storage/__init__.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/storage/models/atom.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/storage/models/trie.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/storage/setup.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/utils/bytes.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/utils/integer.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum/utils/logging.py +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.3.5 → astreum-0.3.14}/src/astreum.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.14
|
|
4
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
6
|
Project-URL: Homepage, https://github.com/astreum/lib-py
|
|
@@ -113,11 +113,11 @@ node.env_set(env_id, "int.add", int_add_fn)
|
|
|
113
113
|
|
|
114
114
|
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
115
115
|
bound = node.env_get(env_id, "int.add")
|
|
116
|
-
call = Expr.ListExpr([Expr.
|
|
116
|
+
call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
|
|
117
117
|
res = node.high_eval(env_id, call)
|
|
118
118
|
|
|
119
119
|
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
120
|
-
print([b.value for b in res.elements]) # [3]
|
|
120
|
+
print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
### Handling errors
|
|
@@ -90,16 +90,16 @@ fn_body = Expr.ListExpr([
|
|
|
90
90
|
params = Expr.ListExpr([Expr.Symbol("a"), Expr.Symbol("b")])
|
|
91
91
|
int_add_fn = Expr.ListExpr([fn_body, params, Expr.Symbol("fn")])
|
|
92
92
|
|
|
93
|
-
# 4) Store under the name "int.add"
|
|
94
|
-
node.env_set(env_id, "int.add", int_add_fn)
|
|
95
|
-
|
|
96
|
-
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
97
|
-
bound = node.env_get(env_id, "int.add")
|
|
98
|
-
call = Expr.ListExpr([Expr.
|
|
93
|
+
# 4) Store under the name "int.add"
|
|
94
|
+
node.env_set(env_id, "int.add", int_add_fn)
|
|
95
|
+
|
|
96
|
+
# 5) Retrieve the function and call it with bytes 1 and 2
|
|
97
|
+
bound = node.env_get(env_id, "int.add")
|
|
98
|
+
call = Expr.ListExpr([Expr.Bytes(b"\x01"), Expr.Bytes(b"\x02"), bound])
|
|
99
99
|
res = node.high_eval(env_id, call)
|
|
100
100
|
|
|
101
101
|
# sk returns a list of bytes; for 1+2 expect a single byte with value 3
|
|
102
|
-
print([b.value for b in res.elements]) # [3]
|
|
102
|
+
print([int.from_bytes(b.value, 'big', signed=True) for b in res.elements]) # [3]
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
### Handling errors
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
from astreum.consensus import Account, Accounts, Block, Chain, Fork, Receipt, Transaction
|
|
3
|
-
from astreum.machine import Env, Expr
|
|
3
|
+
from astreum.machine import Env, Expr, parse, tokenize
|
|
4
4
|
from astreum.node import Node
|
|
5
5
|
|
|
6
6
|
|
|
@@ -15,4 +15,6 @@ __all__: list[str] = [
|
|
|
15
15
|
"Transaction",
|
|
16
16
|
"Account",
|
|
17
17
|
"Accounts",
|
|
18
|
+
"parse",
|
|
19
|
+
"tokenize",
|
|
18
20
|
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Sequence
|
|
4
|
+
|
|
5
|
+
from cryptography.hazmat.primitives import serialization
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
|
|
7
|
+
|
|
8
|
+
from ..models.peer import Peer
|
|
9
|
+
from ..models.message import Message
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from .... import Node
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_handshake(node: "Node", addr: Sequence[object], message: Message) -> bool:
|
|
16
|
+
"""Handle incoming handshake messages.
|
|
17
|
+
|
|
18
|
+
Returns True if the outer loop should `continue`, False otherwise.
|
|
19
|
+
"""
|
|
20
|
+
sender_public_key_bytes = message.sender_bytes
|
|
21
|
+
try:
|
|
22
|
+
sender_key = X25519PublicKey.from_public_bytes(sender_public_key_bytes)
|
|
23
|
+
except Exception as exc:
|
|
24
|
+
node.logger.warning("Error extracting sender key bytes: %s", exc)
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
host = addr[0]
|
|
29
|
+
port = int.from_bytes(message.content[:2], "big", signed=False)
|
|
30
|
+
except Exception:
|
|
31
|
+
return True
|
|
32
|
+
peer_address = (host, port)
|
|
33
|
+
|
|
34
|
+
existing_peer = node.get_peer(sender_public_key_bytes)
|
|
35
|
+
if existing_peer is not None:
|
|
36
|
+
existing_peer.address = peer_address
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
peer = Peer(
|
|
41
|
+
node_secret_key=node.relay_secret_key,
|
|
42
|
+
peer_public_key=sender_key,
|
|
43
|
+
address=peer_address,
|
|
44
|
+
)
|
|
45
|
+
except Exception:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
node.add_peer(sender_public_key_bytes, peer)
|
|
49
|
+
node.peer_route.add_peer(sender_public_key_bytes, peer)
|
|
50
|
+
|
|
51
|
+
node.logger.info(
|
|
52
|
+
"Handshake accepted from %s:%s; peer added",
|
|
53
|
+
peer_address[0],
|
|
54
|
+
peer_address[1],
|
|
55
|
+
)
|
|
56
|
+
response = Message(
|
|
57
|
+
handshake=True,
|
|
58
|
+
sender=node.relay_public_key,
|
|
59
|
+
content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
|
|
60
|
+
)
|
|
61
|
+
node.outgoing_queue.put((response.to_bytes(), peer_address))
|
|
62
|
+
return True
|
|
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Tuple
|
|
|
5
5
|
|
|
6
6
|
from .object_response import ObjectResponse, ObjectResponseType
|
|
7
7
|
from ..models.message import Message, MessageTopic
|
|
8
|
+
from ..util import xor_distance
|
|
8
9
|
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from .. import Node
|
|
@@ -27,7 +28,7 @@ class ObjectRequest:
|
|
|
27
28
|
self.atom_id = atom_id
|
|
28
29
|
|
|
29
30
|
def to_bytes(self):
|
|
30
|
-
return [self.type.value] + self.atom_id + self.data
|
|
31
|
+
return bytes([self.type.value]) + self.atom_id + self.data
|
|
31
32
|
|
|
32
33
|
@classmethod
|
|
33
34
|
def from_bytes(cls, data: bytes) -> "ObjectRequest":
|
|
@@ -48,8 +49,6 @@ class ObjectRequest:
|
|
|
48
49
|
|
|
49
50
|
def encode_peer_contact_bytes(peer: "Peer") -> bytes:
|
|
50
51
|
"""Return a fixed-width peer contact payload (32-byte key + IPv4 + port)."""
|
|
51
|
-
if not peer.address:
|
|
52
|
-
raise ValueError("peer address is required for encoding peer info")
|
|
53
52
|
host, port = peer.address
|
|
54
53
|
key_bytes = peer.public_key_bytes
|
|
55
54
|
try:
|
|
@@ -62,22 +61,25 @@ def encode_peer_contact_bytes(peer: "Peer") -> bytes:
|
|
|
62
61
|
return key_bytes + ip_bytes + port_bytes
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
def handle_object_request(node: "Node",
|
|
66
|
-
|
|
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
|
+
|
|
67
69
|
try:
|
|
68
|
-
object_request = ObjectRequest.from_bytes(message.
|
|
70
|
+
object_request = ObjectRequest.from_bytes(message.content)
|
|
69
71
|
except Exception as exc:
|
|
70
|
-
|
|
72
|
+
node.logger.warning("Error decoding OBJECT_REQUEST from %s: %s", peer.address, exc)
|
|
71
73
|
return
|
|
72
74
|
|
|
73
75
|
match object_request.type:
|
|
74
76
|
case ObjectRequestType.OBJECT_GET:
|
|
75
77
|
atom_id = object_request.atom_id
|
|
76
|
-
|
|
78
|
+
node.logger.debug("Handling OBJECT_GET for %s from %s", atom_id.hex(), peer.address)
|
|
77
79
|
|
|
78
80
|
local_atom = node.local_get(atom_id)
|
|
79
81
|
if local_atom is not None:
|
|
80
|
-
|
|
82
|
+
node.logger.debug("Object %s found locally; returning to %s", atom_id.hex(), peer.address)
|
|
81
83
|
resp = ObjectResponse(
|
|
82
84
|
type=ObjectResponseType.OBJECT_FOUND,
|
|
83
85
|
data=local_atom.to_bytes(),
|
|
@@ -88,13 +90,13 @@ def handle_object_request(node: "Node", addr: Tuple[str, int], message: Message)
|
|
|
88
90
|
body=resp.to_bytes(),
|
|
89
91
|
sender=node.relay_public_key,
|
|
90
92
|
)
|
|
91
|
-
|
|
93
|
+
obj_res_msg.encrypt(peer.shared_key_bytes)
|
|
94
|
+
node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
|
|
92
95
|
return
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
provider_bytes = storage_index[atom_id]
|
|
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]
|
|
98
100
|
resp = ObjectResponse(
|
|
99
101
|
type=ObjectResponseType.OBJECT_PROVIDER,
|
|
100
102
|
data=provider_bytes,
|
|
@@ -105,12 +107,13 @@ def handle_object_request(node: "Node", addr: Tuple[str, int], message: Message)
|
|
|
105
107
|
body=resp.to_bytes(),
|
|
106
108
|
sender=node.relay_public_key,
|
|
107
109
|
)
|
|
108
|
-
|
|
110
|
+
obj_res_msg.encrypt(peer.shared_key_bytes)
|
|
111
|
+
node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
|
|
109
112
|
return
|
|
110
113
|
|
|
111
114
|
nearest_peer = node.peer_route.closest_peer_for_hash(atom_id)
|
|
112
115
|
if nearest_peer:
|
|
113
|
-
|
|
116
|
+
node.logger.debug("Forwarding requester %s to nearest peer for %s", peer.address, atom_id.hex())
|
|
114
117
|
peer_info = encode_peer_contact_bytes(nearest_peer)
|
|
115
118
|
resp = ObjectResponse(
|
|
116
119
|
type=ObjectResponseType.OBJECT_PROVIDER,
|
|
@@ -123,31 +126,51 @@ def handle_object_request(node: "Node", addr: Tuple[str, int], message: Message)
|
|
|
123
126
|
body=resp.to_bytes(),
|
|
124
127
|
sender=node.relay_public_key,
|
|
125
128
|
)
|
|
126
|
-
|
|
129
|
+
obj_res_msg.encrypt(nearest_peer.shared_key_bytes)
|
|
130
|
+
node.outgoing_queue.put((obj_res_msg.to_bytes(), peer.address))
|
|
127
131
|
|
|
128
132
|
case ObjectRequestType.OBJECT_PUT:
|
|
129
|
-
|
|
130
|
-
node_logger.debug("Handling OBJECT_PUT for %s from %s", atom_hash.hex(), addr)
|
|
133
|
+
node.logger.debug("Handling OBJECT_PUT for %s from %s", object_request.atom_id.hex(), peer.address)
|
|
131
134
|
|
|
132
|
-
nearest_peer = node.peer_route.closest_peer_for_hash(
|
|
133
|
-
|
|
134
|
-
if
|
|
135
|
-
|
|
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
|
+
)
|
|
136
162
|
fwd_req = ObjectRequest(
|
|
137
163
|
type=ObjectRequestType.OBJECT_PUT,
|
|
138
164
|
data=object_request.data,
|
|
165
|
+
atom_id=object_request.atom_id,
|
|
139
166
|
)
|
|
140
167
|
obj_req_msg = Message(
|
|
141
168
|
topic=MessageTopic.OBJECT_REQUEST,
|
|
142
169
|
body=fwd_req.to_bytes(),
|
|
143
170
|
sender=node.relay_public_key,
|
|
144
171
|
)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
node_logger.debug("Storing provider info for %s locally", atom_hash.hex())
|
|
148
|
-
if not hasattr(node, "storage_index") or not isinstance(node.storage_index, dict):
|
|
149
|
-
node.storage_index = {}
|
|
150
|
-
node.storage_index[atom_hash] = object_request.data[32:]
|
|
172
|
+
obj_req_msg.encrypt(nearest_peer.shared_key_bytes)
|
|
173
|
+
node.outgoing_queue.put((obj_req_msg.to_bytes(), nearest_peer.address))
|
|
151
174
|
|
|
152
175
|
case _:
|
|
153
|
-
|
|
176
|
+
node.logger.warning("Unknown ObjectRequestType %s from %s", object_request.type, peer.address)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
from typing import Tuple, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from ..models.message import Message, MessageTopic
|
|
6
|
+
from ...storage.models.atom import Atom
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .. import Node
|
|
10
|
+
from ..models.peer import Peer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ObjectResponseType(IntEnum):
|
|
14
|
+
OBJECT_FOUND = 0
|
|
15
|
+
OBJECT_PROVIDER = 1
|
|
16
|
+
OBJECT_NEAREST_PEER = 2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ObjectResponse:
|
|
20
|
+
type: ObjectResponseType
|
|
21
|
+
data: bytes
|
|
22
|
+
atom_id: bytes
|
|
23
|
+
|
|
24
|
+
def __init__(self, type: ObjectResponseType, data: bytes, atom_id: bytes = None):
|
|
25
|
+
self.type = type
|
|
26
|
+
self.data = data
|
|
27
|
+
self.atom_id = atom_id
|
|
28
|
+
|
|
29
|
+
def to_bytes(self):
|
|
30
|
+
return bytes([self.type.value]) + self.atom_id + self.data
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_bytes(cls, data: bytes) -> "ObjectResponse":
|
|
34
|
+
# need at least 1 byte for type + 32 bytes for atom id
|
|
35
|
+
if len(data) < 1 + 32:
|
|
36
|
+
raise ValueError(f"Too short to be a valid ObjectResponse ({len(data)} bytes)")
|
|
37
|
+
|
|
38
|
+
type_val = data[0]
|
|
39
|
+
try:
|
|
40
|
+
resp_type = ObjectResponseType(type_val)
|
|
41
|
+
except ValueError:
|
|
42
|
+
raise ValueError(f"Unknown ObjectResponseType: {type_val}")
|
|
43
|
+
|
|
44
|
+
atom_id = data[1:33]
|
|
45
|
+
payload = data[33:]
|
|
46
|
+
return cls(resp_type, payload, atom_id)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def decode_object_provider(payload: bytes) -> Tuple[bytes, str, int]:
|
|
50
|
+
expected_len = 32 + 4 + 2
|
|
51
|
+
if len(payload) < expected_len:
|
|
52
|
+
raise ValueError("provider payload too short")
|
|
53
|
+
|
|
54
|
+
provider_public_key = payload[:32]
|
|
55
|
+
provider_ip_bytes = payload[32:36]
|
|
56
|
+
provider_port_bytes = payload[36:38]
|
|
57
|
+
|
|
58
|
+
provider_address = socket.inet_ntoa(provider_ip_bytes)
|
|
59
|
+
provider_port = int.from_bytes(provider_port_bytes, byteorder="big", signed=False)
|
|
60
|
+
return provider_public_key, provider_address, provider_port
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def handle_object_response(node: "Node", peer: "Peer", message: Message) -> None:
|
|
64
|
+
if message.content is None:
|
|
65
|
+
node.logger.warning("OBJECT_RESPONSE from %s missing content", peer.address)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
object_response = ObjectResponse.from_bytes(message.content)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
node.logger.warning("Error decoding OBJECT_RESPONSE from %s: %s", peer.address, exc)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if not node.has_atom_req(object_response.atom_id):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
match object_response.type:
|
|
78
|
+
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:
|
|
85
|
+
node.logger.warning(
|
|
86
|
+
"OBJECT_FOUND atom ID mismatch (expected=%s got=%s)",
|
|
87
|
+
object_response.atom_id.hex(),
|
|
88
|
+
atom_id.hex(),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
case ObjectResponseType.OBJECT_PROVIDER:
|
|
92
|
+
try:
|
|
93
|
+
_, provider_address, provider_port = decode_object_provider(object_response.data)
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
node.logger.warning("Invalid OBJECT_PROVIDER payload from %s: %s", peer.address, exc)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
from .object_request import ObjectRequest, ObjectRequestType
|
|
99
|
+
|
|
100
|
+
obj_req = ObjectRequest(
|
|
101
|
+
type=ObjectRequestType.OBJECT_GET,
|
|
102
|
+
data=b"",
|
|
103
|
+
atom_id=object_response.atom_id,
|
|
104
|
+
)
|
|
105
|
+
obj_req_bytes = obj_req.to_bytes()
|
|
106
|
+
obj_req_msg = Message(
|
|
107
|
+
topic=MessageTopic.OBJECT_REQUEST,
|
|
108
|
+
body=obj_req_bytes,
|
|
109
|
+
sender=node.relay_public_key,
|
|
110
|
+
)
|
|
111
|
+
obj_req_msg.encrypt(peer.shared_key_bytes)
|
|
112
|
+
node.outgoing_queue.put((obj_req_msg.to_bytes(), (provider_address, provider_port)))
|
|
113
|
+
|
|
114
|
+
case ObjectResponseType.OBJECT_NEAREST_PEER:
|
|
115
|
+
node.logger.debug("Ignoring OBJECT_NEAREST_PEER response from %s", peer.address)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from ..models.ping import Ping
|
|
7
|
+
from ..models.peer import Peer
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .... import Node
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def handle_ping(node: "Node", peer: Peer, payload: bytes) -> None:
|
|
14
|
+
"""Update peer and validation state based on an incoming ping message."""
|
|
15
|
+
try:
|
|
16
|
+
ping = Ping.from_bytes(payload)
|
|
17
|
+
except Exception as exc:
|
|
18
|
+
node.logger.warning("Error decoding ping: %s", exc)
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
peer.timestamp = datetime.now(timezone.utc)
|
|
22
|
+
peer.latest_block = ping.latest_block
|
|
23
|
+
|
|
24
|
+
validation_route = node.validation_route
|
|
25
|
+
if validation_route is None:
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
if ping.is_validator:
|
|
30
|
+
validation_route.add_peer(peer.public_key_bytes)
|
|
31
|
+
else:
|
|
32
|
+
validation_route.remove_peer(peer.public_key_bytes)
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Sequence
|
|
4
|
-
|
|
5
3
|
import socket
|
|
6
4
|
|
|
7
5
|
from ..models.message import Message, MessageTopic
|
|
@@ -10,19 +8,17 @@ from ..util import xor_distance
|
|
|
10
8
|
from typing import TYPE_CHECKING
|
|
11
9
|
if TYPE_CHECKING:
|
|
12
10
|
from .... import Node
|
|
11
|
+
from ..models.peer import Peer
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
def handle_route_request(node: "Node",
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
request_addr = (addr[0], int(addr[1])) if len(addr) >= 2 else addr
|
|
19
|
-
sender_public_key = node.addresses.get(request_addr)
|
|
14
|
+
def handle_route_request(node: "Node", peer: "Peer", message: Message) -> None:
|
|
15
|
+
sender_public_key = getattr(peer, "public_key_bytes", None)
|
|
20
16
|
if not sender_public_key:
|
|
21
|
-
logger.warning("Unknown sender for ROUTE_REQUEST from %s",
|
|
17
|
+
node.logger.warning("Unknown sender for ROUTE_REQUEST from %s", peer.address)
|
|
22
18
|
return
|
|
23
19
|
|
|
24
20
|
if not message.content:
|
|
25
|
-
logger.warning("ROUTE_REQUEST missing route id from %s",
|
|
21
|
+
node.logger.warning("ROUTE_REQUEST missing route id from %s", peer.address)
|
|
26
22
|
return
|
|
27
23
|
route_id = message.content[0]
|
|
28
24
|
if route_id == 0:
|
|
@@ -30,38 +26,44 @@ def handle_route_request(node: "Node", addr: Sequence[object], message: Message)
|
|
|
30
26
|
elif route_id == 1:
|
|
31
27
|
route = node.validation_route
|
|
32
28
|
if route is None:
|
|
33
|
-
logger.warning("Validation route not initialized for %s",
|
|
29
|
+
node.logger.warning("Validation route not initialized for %s", peer.address)
|
|
34
30
|
return
|
|
35
31
|
else:
|
|
36
|
-
logger.warning("Unknown route id %s in ROUTE_REQUEST from %s", route_id,
|
|
32
|
+
node.logger.warning("Unknown route id %s in ROUTE_REQUEST from %s", route_id, peer.address)
|
|
37
33
|
return
|
|
38
34
|
|
|
39
35
|
payload_parts = []
|
|
40
36
|
for bucket in route.buckets.values():
|
|
41
37
|
closest_key = None
|
|
42
38
|
closest_distance = None
|
|
39
|
+
|
|
43
40
|
for peer_key in bucket:
|
|
44
41
|
try:
|
|
45
42
|
distance = xor_distance(sender_public_key, peer_key)
|
|
46
43
|
except ValueError:
|
|
47
44
|
continue
|
|
45
|
+
|
|
48
46
|
if closest_distance is None or distance < closest_distance:
|
|
49
47
|
closest_distance = distance
|
|
50
48
|
closest_key = peer_key
|
|
49
|
+
|
|
51
50
|
if closest_key is None:
|
|
52
51
|
continue
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
|
|
53
|
+
bucket_peer = node.get_peer(closest_key)
|
|
54
|
+
if bucket_peer is None or bucket_peer.address is None:
|
|
55
55
|
continue
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
host, port = bucket_peer.address
|
|
57
58
|
try:
|
|
58
59
|
address_bytes = socket.inet_pton(socket.AF_INET, host)
|
|
59
60
|
except OSError:
|
|
60
61
|
try:
|
|
61
62
|
address_bytes = socket.inet_pton(socket.AF_INET6, host)
|
|
62
63
|
except OSError as exc:
|
|
63
|
-
logger.warning("Invalid peer address %s: %s",
|
|
64
|
+
node.logger.warning("Invalid peer address %s: %s", bucket_peer.address, exc)
|
|
64
65
|
continue
|
|
66
|
+
|
|
65
67
|
port_bytes = int(port).to_bytes(2, "big", signed=False)
|
|
66
68
|
payload_parts.append(address_bytes + port_bytes)
|
|
67
69
|
|
|
@@ -70,9 +72,5 @@ def handle_route_request(node: "Node", addr: Sequence[object], message: Message)
|
|
|
70
72
|
content=b"".join(payload_parts),
|
|
71
73
|
sender=node.relay_public_key,
|
|
72
74
|
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
except Exception:
|
|
76
|
-
logger.warning("Invalid requester address %s", addr)
|
|
77
|
-
return
|
|
78
|
-
node.outgoing_queue.put((response.to_bytes(), (request_host, request_port)))
|
|
75
|
+
response.encrypt(peer.shared_key_bytes)
|
|
76
|
+
node.outgoing_queue.put((response.to_bytes(), peer.address))
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Sequence
|
|
4
|
-
|
|
5
3
|
import socket
|
|
6
4
|
|
|
7
5
|
from ..models.message import Message
|
|
@@ -9,21 +7,20 @@ from ..models.message import Message
|
|
|
9
7
|
from typing import TYPE_CHECKING
|
|
10
8
|
if TYPE_CHECKING:
|
|
11
9
|
from .... import Node
|
|
10
|
+
from ..models.peer import Peer
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
def handle_route_response(node: "Node",
|
|
15
|
-
logger = node.logger
|
|
16
|
-
|
|
13
|
+
def handle_route_response(node: "Node", peer: "Peer", message: Message) -> None:
|
|
17
14
|
payload = message.content
|
|
18
15
|
if not payload:
|
|
19
16
|
return
|
|
20
17
|
host_len = 16 if node.use_ipv6 else 4
|
|
21
18
|
chunk_size = host_len + 2
|
|
22
19
|
if len(payload) % chunk_size != 0:
|
|
23
|
-
logger.warning(
|
|
20
|
+
node.logger.warning(
|
|
24
21
|
"ROUTE_RESPONSE payload size mismatch (%s bytes) from %s",
|
|
25
22
|
len(payload),
|
|
26
|
-
|
|
23
|
+
peer.address,
|
|
27
24
|
)
|
|
28
25
|
return
|
|
29
26
|
|
|
@@ -35,9 +32,9 @@ def handle_route_response(node: "Node", addr: Sequence[object], message: Message
|
|
|
35
32
|
try:
|
|
36
33
|
host = socket.inet_ntop(family, host_bytes)
|
|
37
34
|
except OSError as exc:
|
|
38
|
-
logger.warning(
|
|
35
|
+
node.logger.warning(
|
|
39
36
|
"Invalid host bytes in ROUTE_RESPONSE from %s: %s",
|
|
40
|
-
|
|
37
|
+
peer.address,
|
|
41
38
|
exc,
|
|
42
39
|
)
|
|
43
40
|
continue
|
|
@@ -45,8 +42,12 @@ def handle_route_response(node: "Node", addr: Sequence[object], message: Message
|
|
|
45
42
|
decoded_addresses.append((host, port))
|
|
46
43
|
if not decoded_addresses:
|
|
47
44
|
return
|
|
48
|
-
logger.debug("Decoded %s addresses from ROUTE_RESPONSE", len(decoded_addresses))
|
|
45
|
+
node.logger.debug("Decoded %s addresses from ROUTE_RESPONSE", len(decoded_addresses))
|
|
49
46
|
|
|
50
|
-
handshake_message = Message(
|
|
47
|
+
handshake_message = Message(
|
|
48
|
+
handshake=True,
|
|
49
|
+
sender=node.relay_public_key,
|
|
50
|
+
content=int(node.config["incoming_port"]).to_bytes(2, "big", signed=False),
|
|
51
|
+
)
|
|
51
52
|
for host, port in decoded_addresses:
|
|
52
53
|
node.outgoing_queue.put((handshake_message.to_bytes(), (host, port)))
|