astreum 0.2.39__py3-none-any.whl → 0.2.40__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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- astreum/_communication/__init__.py +2 -0
- astreum/{models → _communication}/message.py +100 -64
- astreum/_communication/ping.py +33 -0
- astreum/_communication/route.py +53 -20
- astreum/_communication/setup.py +240 -99
- astreum/_communication/util.py +42 -0
- astreum/_consensus/__init__.py +2 -0
- astreum/_consensus/block.py +76 -51
- astreum/_consensus/chain.py +5 -2
- astreum/_consensus/fork.py +3 -1
- astreum/_consensus/receipt.py +167 -0
- astreum/_consensus/setup.py +11 -15
- astreum/_consensus/transaction.py +18 -22
- astreum/_storage/patricia.py +443 -0
- astreum/models/block.py +8 -8
- {astreum-0.2.39.dist-info → astreum-0.2.40.dist-info}/METADATA +1 -1
- {astreum-0.2.39.dist-info → astreum-0.2.40.dist-info}/RECORD +20 -16
- {astreum-0.2.39.dist-info → astreum-0.2.40.dist-info}/WHEEL +0 -0
- {astreum-0.2.39.dist-info → astreum-0.2.40.dist-info}/licenses/LICENSE +0 -0
- {astreum-0.2.39.dist-info → astreum-0.2.40.dist-info}/top_level.txt +0 -0
|
@@ -1,64 +1,100 @@
|
|
|
1
|
-
from enum import IntEnum
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
raise ValueError("
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from cryptography.hazmat.primitives import serialization
|
|
4
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
|
|
5
|
+
|
|
6
|
+
class MessageTopic(IntEnum):
|
|
7
|
+
PING = 0
|
|
8
|
+
OBJECT_REQUEST = 1
|
|
9
|
+
OBJECT_RESPONSE = 2
|
|
10
|
+
ROUTE_REQUEST = 3
|
|
11
|
+
ROUTE_RESPONSE = 4
|
|
12
|
+
TRANSACTION = 5
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Message:
|
|
16
|
+
handshake: bool
|
|
17
|
+
sender: Optional[X25519PublicKey]
|
|
18
|
+
|
|
19
|
+
topic: Optional[MessageTopic]
|
|
20
|
+
content: bytes
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
handshake: bool = False,
|
|
26
|
+
sender: Optional[X25519PublicKey] = None,
|
|
27
|
+
topic: Optional[MessageTopic] = None,
|
|
28
|
+
content: bytes = b"",
|
|
29
|
+
body: Optional[bytes] = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
if body is not None:
|
|
32
|
+
if content and content != b"":
|
|
33
|
+
raise ValueError("specify only one of 'content' or 'body'")
|
|
34
|
+
content = body
|
|
35
|
+
|
|
36
|
+
self.handshake = handshake
|
|
37
|
+
self.sender = sender
|
|
38
|
+
self.topic = topic
|
|
39
|
+
self.content = content or b""
|
|
40
|
+
|
|
41
|
+
if self.handshake:
|
|
42
|
+
if self.sender is None:
|
|
43
|
+
raise ValueError("handshake Message requires a sender public key")
|
|
44
|
+
self.topic = None
|
|
45
|
+
self.content = b""
|
|
46
|
+
else:
|
|
47
|
+
if self.topic is None:
|
|
48
|
+
raise ValueError("non-handshake Message requires a topic")
|
|
49
|
+
|
|
50
|
+
def to_bytes(self):
|
|
51
|
+
if self.handshake:
|
|
52
|
+
# handshake byte (1) + raw public key bytes
|
|
53
|
+
return bytes([1]) + self.sender.public_bytes(
|
|
54
|
+
encoding=serialization.Encoding.Raw,
|
|
55
|
+
format=serialization.PublicFormat.Raw
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
# normal message: 0 + topic + content
|
|
59
|
+
return bytes([0, self.topic.value]) + self.content
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def from_bytes(cls, data: bytes) -> "Message":
|
|
63
|
+
if len(data) < 1:
|
|
64
|
+
raise ValueError("Cannot parse Message: no data")
|
|
65
|
+
flag = data[0]
|
|
66
|
+
# create empty instance
|
|
67
|
+
msg = cls.__new__(cls)
|
|
68
|
+
|
|
69
|
+
if flag == 1:
|
|
70
|
+
# handshake message: the rest is the peer’s public key
|
|
71
|
+
key_bytes = data[1:]
|
|
72
|
+
if not key_bytes:
|
|
73
|
+
raise ValueError("Handshake message missing sender public key bytes")
|
|
74
|
+
try:
|
|
75
|
+
sender = X25519PublicKey.from_public_bytes(key_bytes)
|
|
76
|
+
except ValueError:
|
|
77
|
+
raise ValueError("Invalid public key bytes")
|
|
78
|
+
if sender is None:
|
|
79
|
+
raise ValueError("Handshake message missing sender public key")
|
|
80
|
+
msg.handshake = True
|
|
81
|
+
msg.sender = sender
|
|
82
|
+
msg.topic = None
|
|
83
|
+
msg.content = b''
|
|
84
|
+
elif flag == 0:
|
|
85
|
+
# normal message: next byte is topic, rest is content
|
|
86
|
+
if len(data) < 2:
|
|
87
|
+
raise ValueError("Cannot parse Message: missing topic byte")
|
|
88
|
+
topic_val = data[1]
|
|
89
|
+
try:
|
|
90
|
+
topic = MessageTopic(topic_val)
|
|
91
|
+
except ValueError:
|
|
92
|
+
raise ValueError(f"Unknown MessageTopic: {topic_val}")
|
|
93
|
+
msg.handshake = False
|
|
94
|
+
msg.sender = None
|
|
95
|
+
msg.topic = topic
|
|
96
|
+
msg.content = data[2:]
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Invalid handshake flag: {flag}")
|
|
99
|
+
|
|
100
|
+
return msg
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PingFormatError(ValueError):
|
|
7
|
+
"""Raised when ping payload bytes are invalid."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Ping:
|
|
12
|
+
is_validator: bool
|
|
13
|
+
latest_block: bytes
|
|
14
|
+
|
|
15
|
+
PAYLOAD_SIZE = 33
|
|
16
|
+
|
|
17
|
+
def __post_init__(self) -> None:
|
|
18
|
+
lb = bytes(self.latest_block or b"")
|
|
19
|
+
if len(lb) != 32:
|
|
20
|
+
raise ValueError("latest_block must be exactly 32 bytes")
|
|
21
|
+
self.latest_block = lb
|
|
22
|
+
|
|
23
|
+
def to_bytes(self) -> bytes:
|
|
24
|
+
return (b"\x01" if self.is_validator else b"\x00") + self.latest_block
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_bytes(cls, data: bytes) -> "Ping":
|
|
28
|
+
if len(data) != cls.PAYLOAD_SIZE:
|
|
29
|
+
raise PingFormatError("ping payload must be exactly 33 bytes")
|
|
30
|
+
flag = data[0]
|
|
31
|
+
if flag not in (0, 1):
|
|
32
|
+
raise PingFormatError("ping validator flag must be 0 or 1")
|
|
33
|
+
return cls(is_validator=bool(flag), latest_block=data[1:])
|
astreum/_communication/route.py
CHANGED
|
@@ -1,25 +1,58 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
2
|
-
from cryptography.hazmat.primitives
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
from typing import Dict, List, Union
|
|
2
|
+
from cryptography.hazmat.primitives import serialization
|
|
3
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey
|
|
4
|
+
|
|
5
|
+
PeerKey = Union[X25519PublicKey, bytes, bytearray]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Route:
|
|
9
|
+
def __init__(self, relay_public_key: X25519PublicKey, bucket_size: int = 16):
|
|
10
|
+
self.relay_public_key_bytes = relay_public_key.public_bytes(
|
|
11
|
+
encoding=serialization.Encoding.Raw,
|
|
12
|
+
format=serialization.PublicFormat.Raw,
|
|
13
|
+
)
|
|
14
|
+
self.bucket_size = bucket_size
|
|
15
|
+
self.buckets: Dict[int, List[bytes]] = {
|
|
16
|
+
i: [] for i in range(len(self.relay_public_key_bytes) * 8)
|
|
17
|
+
}
|
|
18
|
+
self.peers = {}
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
14
21
|
def _matching_leading_bits(a: bytes, b: bytes) -> int:
|
|
15
22
|
for byte_index, (ba, bb) in enumerate(zip(a, b)):
|
|
16
23
|
diff = ba ^ bb
|
|
17
24
|
if diff:
|
|
18
25
|
return byte_index * 8 + (8 - diff.bit_length())
|
|
19
|
-
return len(a) * 8
|
|
20
|
-
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
return len(a) * 8
|
|
27
|
+
|
|
28
|
+
def _normalize_peer_key(self, peer_public_key: PeerKey) -> bytes:
|
|
29
|
+
if isinstance(peer_public_key, X25519PublicKey):
|
|
30
|
+
return peer_public_key.public_bytes(
|
|
31
|
+
encoding=serialization.Encoding.Raw,
|
|
32
|
+
format=serialization.PublicFormat.Raw,
|
|
33
|
+
)
|
|
34
|
+
if isinstance(peer_public_key, (bytes, bytearray)):
|
|
35
|
+
key_bytes = bytes(peer_public_key)
|
|
36
|
+
if len(key_bytes) != len(self.relay_public_key_bytes):
|
|
37
|
+
raise ValueError("peer key must be raw 32-byte public key")
|
|
38
|
+
return key_bytes
|
|
39
|
+
raise TypeError("peer_public_key must be raw bytes or X25519PublicKey")
|
|
40
|
+
|
|
41
|
+
def add_peer(self, peer_public_key: PeerKey):
|
|
42
|
+
peer_public_key_bytes = self._normalize_peer_key(peer_public_key)
|
|
43
|
+
bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
|
|
44
|
+
if len(self.buckets[bucket_idx]) < self.bucket_size:
|
|
45
|
+
bucket = self.buckets[bucket_idx]
|
|
46
|
+
if peer_public_key_bytes not in bucket:
|
|
47
|
+
bucket.append(peer_public_key_bytes)
|
|
48
|
+
|
|
49
|
+
def remove_peer(self, peer_public_key: PeerKey):
|
|
50
|
+
peer_public_key_bytes = self._normalize_peer_key(peer_public_key)
|
|
51
|
+
bucket_idx = self._matching_leading_bits(self.relay_public_key_bytes, peer_public_key_bytes)
|
|
52
|
+
bucket = self.buckets.get(bucket_idx)
|
|
53
|
+
if not bucket:
|
|
54
|
+
return
|
|
55
|
+
try:
|
|
56
|
+
bucket.remove(peer_public_key_bytes)
|
|
57
|
+
except ValueError:
|
|
58
|
+
pass
|
astreum/_communication/setup.py
CHANGED
|
@@ -1,73 +1,188 @@
|
|
|
1
|
-
import socket, threading
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
import socket, threading
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from queue import Queue
|
|
4
|
+
from typing import Tuple, Optional
|
|
4
5
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
5
6
|
from cryptography.hazmat.primitives import serialization
|
|
6
|
-
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|
7
|
-
X25519PrivateKey,
|
|
8
|
-
X25519PublicKey,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
from typing import TYPE_CHECKING
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .. import Node
|
|
14
|
-
|
|
15
|
-
from .import Route
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def
|
|
22
|
-
"""
|
|
23
|
-
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
sock
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
) ->
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
|
8
|
+
X25519PrivateKey,
|
|
9
|
+
X25519PublicKey,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .. import Node
|
|
15
|
+
|
|
16
|
+
from . import Route, Message
|
|
17
|
+
from .message import MessageTopic
|
|
18
|
+
from .peer import Peer
|
|
19
|
+
from .ping import Ping
|
|
20
|
+
from .util import address_str_to_host_and_port
|
|
21
|
+
|
|
22
|
+
def load_x25519(hex_key: Optional[str]) -> X25519PrivateKey:
|
|
23
|
+
"""DH key for relaying (always X25519)."""
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
def load_ed25519(hex_key: Optional[str]) -> Optional[ed25519.Ed25519PrivateKey]:
|
|
27
|
+
"""Signing key for validation (Ed25519), or None if absent."""
|
|
28
|
+
return ed25519.Ed25519PrivateKey.from_private_bytes(bytes.fromhex(hex_key)) \
|
|
29
|
+
if hex_key else None
|
|
30
|
+
|
|
31
|
+
def make_routes(
|
|
32
|
+
relay_pk: X25519PublicKey,
|
|
33
|
+
val_sk: Optional[ed25519.Ed25519PrivateKey]
|
|
34
|
+
) -> Tuple[Route, Optional[Route]]:
|
|
35
|
+
"""Peer route (DH pubkey) + optional validation route (ed pubkey)."""
|
|
36
|
+
peer_rt = Route(relay_pk)
|
|
37
|
+
val_rt = Route(val_sk.public_key()) if val_sk else None
|
|
38
|
+
return peer_rt, val_rt
|
|
39
|
+
|
|
40
|
+
def setup_outgoing(
|
|
41
|
+
use_ipv6: bool
|
|
42
|
+
) -> Tuple[socket.socket, Queue, threading.Thread]:
|
|
43
|
+
fam = socket.AF_INET6 if use_ipv6 else socket.AF_INET
|
|
44
|
+
sock = socket.socket(fam, socket.SOCK_DGRAM)
|
|
45
|
+
q = Queue()
|
|
46
|
+
thr = threading.Thread(target=lambda: None, daemon=True)
|
|
47
|
+
thr.start()
|
|
48
|
+
return sock, q, thr
|
|
49
|
+
|
|
50
|
+
def make_maps():
|
|
51
|
+
"""Empty lookup maps: peers and addresses."""
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def process_incoming_messages(node: "Node") -> None:
|
|
56
|
+
"""Process incoming messages (placeholder)."""
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
data, addr = node.incoming_queue.get()
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
print(f"Error taking from incoming queue: {exc}")
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
message = Message.from_bytes(data)
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
print(f"Error decoding message: {exc}")
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
if message.handshake:
|
|
71
|
+
sender_key = message.sender
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
sender_public_key_bytes = sender_key.public_bytes(
|
|
75
|
+
encoding=serialization.Encoding.Raw,
|
|
76
|
+
format=serialization.PublicFormat.Raw,
|
|
77
|
+
)
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
print(f"Error extracting sender key bytes: {exc}")
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Normalize remote address (IPv6 tuples may be 4 elements)
|
|
83
|
+
try:
|
|
84
|
+
host, port = addr[0], int(addr[1])
|
|
85
|
+
except Exception:
|
|
86
|
+
continue
|
|
87
|
+
address_key = (host, port)
|
|
88
|
+
|
|
89
|
+
old_key_bytes = node.addresses.get(address_key)
|
|
90
|
+
node.addresses[address_key] = sender_public_key_bytes
|
|
91
|
+
|
|
92
|
+
if old_key_bytes is None:
|
|
93
|
+
# brand-new address -> brand-new peer
|
|
94
|
+
try:
|
|
95
|
+
peer = Peer(node.relay_secret_key, sender_key)
|
|
96
|
+
except Exception:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
node.peers[sender_public_key_bytes] = peer
|
|
100
|
+
node.peer_route.add_peer(sender_public_key_bytes)
|
|
101
|
+
|
|
102
|
+
response = Message(handshake=True, sender=node.relay_public_key)
|
|
103
|
+
node.outgoing_queue.put((response.to_bytes(), address_key))
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
elif old_key_bytes == sender_public_key_bytes:
|
|
107
|
+
# existing mapping with same key -> nothing to change
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
# address reused with a different key -> replace peer
|
|
112
|
+
node.peers.pop(old_key_bytes, None)
|
|
113
|
+
try:
|
|
114
|
+
peer = Peer(node.relay_secret_key, sender_key)
|
|
115
|
+
except Exception:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
node.peers[sender_public_key_bytes] = peer
|
|
119
|
+
|
|
120
|
+
match message.topic:
|
|
121
|
+
case MessageTopic.PING:
|
|
122
|
+
try:
|
|
123
|
+
host, port = addr[0], int(addr[1])
|
|
124
|
+
except Exception:
|
|
125
|
+
continue
|
|
126
|
+
address_key = (host, port)
|
|
127
|
+
sender_public_key_bytes = node.addresses.get(address_key)
|
|
128
|
+
if sender_public_key_bytes is None:
|
|
129
|
+
continue
|
|
130
|
+
peer = node.peers.get(sender_public_key_bytes)
|
|
131
|
+
if peer is None:
|
|
132
|
+
continue
|
|
133
|
+
try:
|
|
134
|
+
ping = Ping.from_bytes(message.content)
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
print(f"Error decoding ping: {exc}")
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
peer.timestamp = datetime.now(timezone.utc)
|
|
140
|
+
peer.latest_block = ping.latest_block
|
|
141
|
+
|
|
142
|
+
validation_route = node.validation_route
|
|
143
|
+
if validation_route is None:
|
|
144
|
+
continue
|
|
145
|
+
if ping.is_validator:
|
|
146
|
+
try:
|
|
147
|
+
validation_route.add_peer(sender_public_key_bytes)
|
|
148
|
+
except Exception:
|
|
149
|
+
pass
|
|
150
|
+
else:
|
|
151
|
+
try:
|
|
152
|
+
validation_route.remove_peer(sender_public_key_bytes)
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
case MessageTopic.OBJECT_REQUEST:
|
|
156
|
+
pass
|
|
157
|
+
case MessageTopic.OBJECT_RESPONSE:
|
|
158
|
+
pass
|
|
159
|
+
case MessageTopic.ROUTE_REQUEST:
|
|
160
|
+
pass
|
|
161
|
+
case MessageTopic.ROUTE_RESPONSE:
|
|
162
|
+
pass
|
|
163
|
+
case MessageTopic.TRANSACTION:
|
|
164
|
+
if node.validation_secret_key is None:
|
|
165
|
+
continue
|
|
166
|
+
node._validation_transaction_queue.put(message.content)
|
|
167
|
+
|
|
168
|
+
case _:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def populate_incoming_messages(node: "Node") -> None:
|
|
173
|
+
"""Receive UDP packets and feed the incoming queue (placeholder)."""
|
|
174
|
+
while True:
|
|
175
|
+
try:
|
|
176
|
+
data, addr = node.incoming_socket.recvfrom(4096)
|
|
177
|
+
node.incoming_queue.put((data, addr))
|
|
178
|
+
except Exception as exc:
|
|
179
|
+
print(f"Error populating incoming queue: {exc}")
|
|
180
|
+
|
|
181
|
+
def communication_setup(node: "Node", config: dict):
|
|
182
|
+
node.use_ipv6 = config.get('use_ipv6', False)
|
|
183
|
+
|
|
184
|
+
# key loading
|
|
185
|
+
node.relay_secret_key = load_x25519(config.get('relay_secret_key'))
|
|
71
186
|
node.validation_secret_key = load_ed25519(config.get('validation_secret_key'))
|
|
72
187
|
|
|
73
188
|
# derive pubs + routes
|
|
@@ -80,34 +195,60 @@ def communication_setup(node: "Node", config: dict):
|
|
|
80
195
|
if node.validation_secret_key
|
|
81
196
|
else None
|
|
82
197
|
)
|
|
83
|
-
node.peer_route, node.validation_route = make_routes(
|
|
84
|
-
node.relay_public_key,
|
|
85
|
-
node.validation_secret_key
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
# sockets + queues + threads
|
|
89
|
-
(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
node.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
node.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
daemon=True
|
|
106
|
-
)
|
|
107
|
-
node.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
198
|
+
node.peer_route, node.validation_route = make_routes(
|
|
199
|
+
node.relay_public_key,
|
|
200
|
+
node.validation_secret_key
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# sockets + queues + threads
|
|
204
|
+
incoming_port = config.get('incoming_port', 7373)
|
|
205
|
+
fam = socket.AF_INET6 if node.use_ipv6 else socket.AF_INET
|
|
206
|
+
node.incoming_socket = socket.socket(fam, socket.SOCK_DGRAM)
|
|
207
|
+
if node.use_ipv6:
|
|
208
|
+
node.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
209
|
+
node.incoming_socket.bind(("::" if node.use_ipv6 else "0.0.0.0", incoming_port or 0))
|
|
210
|
+
node.incoming_port = node.incoming_socket.getsockname()[1]
|
|
211
|
+
node.incoming_queue = Queue()
|
|
212
|
+
node.incoming_populate_thread = threading.Thread(
|
|
213
|
+
target=populate_incoming_messages,
|
|
214
|
+
args=(node,),
|
|
215
|
+
daemon=True,
|
|
216
|
+
)
|
|
217
|
+
node.incoming_process_thread = threading.Thread(
|
|
218
|
+
target=process_incoming_messages,
|
|
219
|
+
args=(node,),
|
|
220
|
+
daemon=True,
|
|
221
|
+
)
|
|
222
|
+
node.incoming_populate_thread.start()
|
|
223
|
+
node.incoming_process_thread.start()
|
|
224
|
+
|
|
225
|
+
(node.outgoing_socket,
|
|
226
|
+
node.outgoing_queue,
|
|
227
|
+
node.outgoing_thread
|
|
228
|
+
) = setup_outgoing(node.use_ipv6)
|
|
229
|
+
|
|
230
|
+
# other workers & maps
|
|
231
|
+
node.object_request_queue = Queue()
|
|
232
|
+
node.peer_manager_thread = threading.Thread(
|
|
233
|
+
target=node._relay_peer_manager,
|
|
234
|
+
daemon=True
|
|
235
|
+
)
|
|
236
|
+
node.peer_manager_thread.start()
|
|
237
|
+
|
|
238
|
+
node.peers, node.addresses = {}, {} # peers: Dict[bytes,Peer], addresses: Dict[(str,int),bytes]
|
|
239
|
+
latest_hash = getattr(node, "latest_block_hash", None)
|
|
240
|
+
if not isinstance(latest_hash, (bytes, bytearray)) or len(latest_hash) != 32:
|
|
241
|
+
node.latest_block_hash = bytes(32)
|
|
242
|
+
else:
|
|
243
|
+
node.latest_block_hash = bytes(latest_hash)
|
|
244
|
+
|
|
245
|
+
# bootstrap pings
|
|
246
|
+
for addr in config.get('bootstrap', []):
|
|
247
|
+
try:
|
|
248
|
+
host, port = address_str_to_host_and_port(addr) # type: ignore[arg-type]
|
|
249
|
+
except Exception:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
handshake_message = Message(handshake=True, sender=node.relay_public_key)
|
|
253
|
+
|
|
254
|
+
node.outgoing_queue.put((handshake_message.to_bytes(), (host, port)))
|