astreum 0.2.4__tar.gz → 0.2.6__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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- {astreum-0.2.4/src/astreum.egg-info → astreum-0.2.6}/PKG-INFO +1 -1
- {astreum-0.2.4 → astreum-0.2.6}/pyproject.toml +1 -1
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/node.py +1 -1
- {astreum-0.2.4 → astreum-0.2.6/src/astreum.egg-info}/PKG-INFO +1 -1
- astreum-0.2.6/src/astreum.egg-info/SOURCES.txt +22 -0
- astreum-0.2.4/src/astreum/_node/relay/__init__.py +0 -371
- astreum-0.2.4/src/astreum/_node/relay/bucket.py +0 -90
- astreum-0.2.4/src/astreum/_node/relay/envelope.py +0 -280
- astreum-0.2.4/src/astreum/_node/relay/message.py +0 -110
- astreum-0.2.4/src/astreum/_node/relay/peer.py +0 -174
- astreum-0.2.4/src/astreum/_node/relay/route.py +0 -161
- astreum-0.2.4/src/astreum/_node/storage/storage.py +0 -253
- astreum-0.2.4/src/astreum/_node/storage/utils.py +0 -137
- astreum-0.2.4/src/astreum/_node/utils.py +0 -34
- astreum-0.2.4/src/astreum/_node/validation/_block/__init__.py +0 -0
- astreum-0.2.4/src/astreum/_node/validation/_block/create.py +0 -98
- astreum-0.2.4/src/astreum/_node/validation/_block/model.py +0 -81
- astreum-0.2.4/src/astreum/_node/validation/_block/validate.py +0 -196
- astreum-0.2.4/src/astreum/_node/validation/account.py +0 -99
- astreum-0.2.4/src/astreum/_node/validation/block.py +0 -21
- astreum-0.2.4/src/astreum/_node/validation/constants.py +0 -15
- astreum-0.2.4/src/astreum/_node/validation/stake.py +0 -229
- astreum-0.2.4/src/astreum/_node/validation/transaction.py +0 -146
- astreum-0.2.4/src/astreum/_node/validation/vdf.py +0 -80
- astreum-0.2.4/src/astreum/crypto/__init__.py +0 -0
- astreum-0.2.4/src/astreum/lispeum/expression.py +0 -95
- astreum-0.2.4/src/astreum/lispeum/special/__init__.py +0 -0
- astreum-0.2.4/src/astreum/lispeum/special/definition.py +0 -27
- astreum-0.2.4/src/astreum/lispeum/special/list/__init__.py +0 -0
- astreum-0.2.4/src/astreum/lispeum/special/list/all.py +0 -32
- astreum-0.2.4/src/astreum/lispeum/special/list/any.py +0 -32
- astreum-0.2.4/src/astreum/lispeum/special/list/fold.py +0 -29
- astreum-0.2.4/src/astreum/lispeum/special/list/get.py +0 -20
- astreum-0.2.4/src/astreum/lispeum/special/list/insert.py +0 -23
- astreum-0.2.4/src/astreum/lispeum/special/list/map.py +0 -30
- astreum-0.2.4/src/astreum/lispeum/special/list/position.py +0 -33
- astreum-0.2.4/src/astreum/lispeum/special/list/remove.py +0 -22
- astreum-0.2.4/src/astreum/lispeum/special/number/__init__.py +0 -0
- astreum-0.2.4/src/astreum/lispeum/special/number/addition.py +0 -0
- astreum-0.2.4/src/astreum/lispeum/storage.py +0 -410
- astreum-0.2.4/src/astreum/machine/__init__.py +0 -352
- astreum-0.2.4/src/astreum/machine/environment.py +0 -4
- astreum-0.2.4/src/astreum/machine/error.py +0 -0
- astreum-0.2.4/src/astreum.egg-info/SOURCES.txt +0 -60
- {astreum-0.2.4 → astreum-0.2.6}/LICENSE +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/README.md +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/setup.cfg +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/__init__.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/_node/__init__.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/_node/storage/__init__.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/_node/storage/merkle.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/_node/storage/patricia.py +0 -0
- {astreum-0.2.4/src/astreum/_node/validation → astreum-0.2.6/src/astreum/crypto}/__init__.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/format.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/src/astreum.egg-info/top_level.txt +0 -0
- {astreum-0.2.4 → astreum-0.2.6}/tests/test_node_machine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/astreum/__init__.py
|
|
5
|
+
src/astreum/format.py
|
|
6
|
+
src/astreum/node.py
|
|
7
|
+
src/astreum.egg-info/PKG-INFO
|
|
8
|
+
src/astreum.egg-info/SOURCES.txt
|
|
9
|
+
src/astreum.egg-info/dependency_links.txt
|
|
10
|
+
src/astreum.egg-info/requires.txt
|
|
11
|
+
src/astreum.egg-info/top_level.txt
|
|
12
|
+
src/astreum/_node/__init__.py
|
|
13
|
+
src/astreum/_node/storage/__init__.py
|
|
14
|
+
src/astreum/_node/storage/merkle.py
|
|
15
|
+
src/astreum/_node/storage/patricia.py
|
|
16
|
+
src/astreum/crypto/__init__.py
|
|
17
|
+
src/astreum/crypto/ed25519.py
|
|
18
|
+
src/astreum/crypto/x25519.py
|
|
19
|
+
src/astreum/lispeum/__init__.py
|
|
20
|
+
src/astreum/lispeum/parser.py
|
|
21
|
+
src/astreum/lispeum/tokenizer.py
|
|
22
|
+
tests/test_node_machine.py
|
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Relay module for handling network communication in the Astreum node.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import socket
|
|
6
|
-
import threading
|
|
7
|
-
import random
|
|
8
|
-
import time
|
|
9
|
-
from queue import Queue
|
|
10
|
-
from typing import Tuple, Callable, Dict, Set, Optional, List
|
|
11
|
-
from .message import Message, Topic
|
|
12
|
-
from .envelope import Envelope
|
|
13
|
-
from .bucket import KBucket
|
|
14
|
-
from .peer import Peer, PeerManager
|
|
15
|
-
from .route import RouteTable
|
|
16
|
-
import json
|
|
17
|
-
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
18
|
-
|
|
19
|
-
class Relay:
|
|
20
|
-
def __init__(self, config: dict):
|
|
21
|
-
"""Initialize relay with configuration."""
|
|
22
|
-
self.config = config
|
|
23
|
-
self.use_ipv6 = config.get('use_ipv6', False)
|
|
24
|
-
incoming_port = config.get('incoming_port', 7373)
|
|
25
|
-
self.max_message_size = config.get('max_message_size', 65536) # Max UDP datagram size
|
|
26
|
-
self.num_workers = config.get('num_workers', 4)
|
|
27
|
-
|
|
28
|
-
# Generate Ed25519 keypair for this node
|
|
29
|
-
if 'relay_private_key' in config:
|
|
30
|
-
# Load existing private key if provided
|
|
31
|
-
try:
|
|
32
|
-
private_key_bytes = bytes.fromhex(config['relay_private_key'])
|
|
33
|
-
self.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(private_key_bytes)
|
|
34
|
-
except Exception as e:
|
|
35
|
-
print(f"Error loading private key: {e}, generating new one")
|
|
36
|
-
self.private_key = ed25519.Ed25519PrivateKey.generate()
|
|
37
|
-
else:
|
|
38
|
-
# Generate new keypair
|
|
39
|
-
self.private_key = ed25519.Ed25519PrivateKey.generate()
|
|
40
|
-
|
|
41
|
-
# Use public key as node ID
|
|
42
|
-
self.public_key = self.private_key.public_key()
|
|
43
|
-
self.node_id = self.public_key.public_bytes_raw()
|
|
44
|
-
|
|
45
|
-
# Save private key bytes for config persistence
|
|
46
|
-
self.private_key_bytes = self.private_key.private_bytes_raw()
|
|
47
|
-
|
|
48
|
-
# Routes that this node participates in
|
|
49
|
-
# 0 = peer route, 1 = validation route
|
|
50
|
-
# All routes are tracked by default, but we only join some
|
|
51
|
-
self.routes: List[int] = []
|
|
52
|
-
self.tracked_routes: List[int] = [0, 1] # Track all routes
|
|
53
|
-
|
|
54
|
-
# Always join peer route
|
|
55
|
-
self.routes.append(0) # Peer route
|
|
56
|
-
|
|
57
|
-
# Check if this node should join validation route
|
|
58
|
-
if config.get('validation_route', False):
|
|
59
|
-
self.routes.append(1) # Validation route
|
|
60
|
-
|
|
61
|
-
# Choose address family based on IPv4 or IPv6
|
|
62
|
-
family = socket.AF_INET6 if self.use_ipv6 else socket.AF_INET
|
|
63
|
-
|
|
64
|
-
# Create a UDP socket
|
|
65
|
-
self.incoming_socket = socket.socket(family, socket.SOCK_DGRAM)
|
|
66
|
-
|
|
67
|
-
# Allow dual-stack support (IPv4-mapped addresses on IPv6)
|
|
68
|
-
if self.use_ipv6:
|
|
69
|
-
self.incoming_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
|
70
|
-
|
|
71
|
-
# Bind to an address (IPv6 "::" or IPv4 "0.0.0.0") and port
|
|
72
|
-
bind_address = "::" if self.use_ipv6 else "0.0.0.0"
|
|
73
|
-
self.incoming_socket.bind((bind_address, incoming_port or 0))
|
|
74
|
-
|
|
75
|
-
# Get the actual port assigned
|
|
76
|
-
self.incoming_port = self.incoming_socket.getsockname()[1]
|
|
77
|
-
|
|
78
|
-
# Create a UDP socket for sending messages
|
|
79
|
-
self.outgoing_socket = socket.socket(family, socket.SOCK_DGRAM)
|
|
80
|
-
|
|
81
|
-
# Message queues
|
|
82
|
-
self.incoming_queue = Queue()
|
|
83
|
-
self.outgoing_queue = Queue()
|
|
84
|
-
|
|
85
|
-
# Message handling
|
|
86
|
-
self.message_handlers: Dict[Topic, Callable] = {
|
|
87
|
-
Topic.PEER_ROUTE: None, # set by Node later
|
|
88
|
-
Topic.OBJECT_REQUEST: None, # set by Node later
|
|
89
|
-
Topic.OBJECT_RESPONSE: None, # set by Node later
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# Route buckets (peers for each route)
|
|
93
|
-
self.peer_route_bucket = KBucket(k=20) # Bucket for peer route
|
|
94
|
-
self.validation_route_bucket = KBucket(k=20) # Bucket for validation route
|
|
95
|
-
|
|
96
|
-
# Initialize route table with our node ID
|
|
97
|
-
self.route_table = RouteTable(self)
|
|
98
|
-
|
|
99
|
-
# Initialize storage index
|
|
100
|
-
self.storage_index: Dict[bytes, bytes] = {}
|
|
101
|
-
|
|
102
|
-
# Start worker threads
|
|
103
|
-
self._start_workers()
|
|
104
|
-
|
|
105
|
-
def is_in_peer_route(self) -> bool:
|
|
106
|
-
"""Check if this node is part of the peer route."""
|
|
107
|
-
return 0 in self.routes
|
|
108
|
-
|
|
109
|
-
def is_in_validation_route(self) -> bool:
|
|
110
|
-
"""Check if this node is part of the validation route."""
|
|
111
|
-
return 1 in self.routes
|
|
112
|
-
|
|
113
|
-
def is_tracking_route(self, route_type: int) -> bool:
|
|
114
|
-
"""Check if this node is tracking a specific route."""
|
|
115
|
-
return route_type in self.tracked_routes
|
|
116
|
-
|
|
117
|
-
def get_random_peers_from_route(self, route_type: int, count: int = 3) -> List[Peer]:
|
|
118
|
-
"""
|
|
119
|
-
Get a list of random peers from different buckets in the specified route.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
route_type (int): Route type (0 for peer, 1 for validation)
|
|
123
|
-
count (int): Number of random peers to select (one from each bucket)
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
List[Peer]: List of randomly selected peers from different buckets
|
|
127
|
-
"""
|
|
128
|
-
result = []
|
|
129
|
-
route_id = self._get_route_id(route_type)
|
|
130
|
-
|
|
131
|
-
# Get all buckets that have peers for this route
|
|
132
|
-
buckets_with_peers = []
|
|
133
|
-
for i, bucket in enumerate(self.routing_table):
|
|
134
|
-
# For each bucket, collect peers that are in this route
|
|
135
|
-
route_peers_in_bucket = [peer for peer in bucket.values()
|
|
136
|
-
if peer.routes and route_id in peer.routes]
|
|
137
|
-
if route_peers_in_bucket:
|
|
138
|
-
buckets_with_peers.append((i, route_peers_in_bucket))
|
|
139
|
-
|
|
140
|
-
# If we don't have any buckets with peers, return empty list
|
|
141
|
-
if not buckets_with_peers:
|
|
142
|
-
return []
|
|
143
|
-
|
|
144
|
-
# If we have fewer buckets than requested count, adjust count
|
|
145
|
-
sample_count = min(count, len(buckets_with_peers))
|
|
146
|
-
|
|
147
|
-
# Sample random buckets
|
|
148
|
-
selected_buckets = random.sample(buckets_with_peers, sample_count)
|
|
149
|
-
|
|
150
|
-
# For each selected bucket, pick one random peer
|
|
151
|
-
for bucket_idx, peers in selected_buckets:
|
|
152
|
-
# Select one random peer from this bucket
|
|
153
|
-
selected_peer = random.choice(peers)
|
|
154
|
-
result.append(selected_peer)
|
|
155
|
-
|
|
156
|
-
return result
|
|
157
|
-
|
|
158
|
-
def get_peers_in_route(self, route_type: int) -> List[Peer]:
|
|
159
|
-
"""
|
|
160
|
-
Get all peers in a specific route.
|
|
161
|
-
|
|
162
|
-
Args:
|
|
163
|
-
route_type (int): Route type (0 for peer, 1 for validation)
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
List[Peer]: List of peers in the route
|
|
167
|
-
"""
|
|
168
|
-
if route_type == 0: # Peer route
|
|
169
|
-
return self.peer_route_bucket.get_peers()
|
|
170
|
-
elif route_type == 1: # Validation route
|
|
171
|
-
return self.validation_route_bucket.get_peers()
|
|
172
|
-
return []
|
|
173
|
-
|
|
174
|
-
def add_peer_to_route(self, peer: Peer, route_types: List[int]):
|
|
175
|
-
"""
|
|
176
|
-
Add a peer to specified routes.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
peer (Peer): The peer to add
|
|
180
|
-
route_types (List[int]): List of route types to add the peer to (0 = peer, 1 = validation)
|
|
181
|
-
"""
|
|
182
|
-
for route_type in route_types:
|
|
183
|
-
if route_type == 0: # Peer route
|
|
184
|
-
# Add to top of bucket, eject last if at capacity
|
|
185
|
-
self.peer_route_bucket.add(peer, to_front=True)
|
|
186
|
-
elif route_type == 1: # Validation route
|
|
187
|
-
# Add to top of bucket, eject last if at capacity
|
|
188
|
-
self.validation_route_bucket.add(peer, to_front=True)
|
|
189
|
-
|
|
190
|
-
def register_message_handler(self, topic: Topic, handler_func):
|
|
191
|
-
"""Register a handler function for a specific message topic."""
|
|
192
|
-
self.message_handlers[topic] = handler_func
|
|
193
|
-
|
|
194
|
-
def _start_workers(self):
|
|
195
|
-
"""Start worker threads for processing incoming and outgoing messages."""
|
|
196
|
-
self.running = True
|
|
197
|
-
|
|
198
|
-
# Start receiver thread
|
|
199
|
-
self.receiver_thread = threading.Thread(target=self._receive_messages)
|
|
200
|
-
self.receiver_thread.daemon = True
|
|
201
|
-
self.receiver_thread.start()
|
|
202
|
-
|
|
203
|
-
# Start sender thread
|
|
204
|
-
self.sender_thread = threading.Thread(target=self._send_messages)
|
|
205
|
-
self.sender_thread.daemon = True
|
|
206
|
-
self.sender_thread.start()
|
|
207
|
-
|
|
208
|
-
# Start worker threads for processing incoming messages
|
|
209
|
-
self.worker_threads = []
|
|
210
|
-
for _ in range(self.num_workers):
|
|
211
|
-
thread = threading.Thread(target=self._process_messages)
|
|
212
|
-
thread.daemon = True
|
|
213
|
-
thread.start()
|
|
214
|
-
self.worker_threads.append(thread)
|
|
215
|
-
|
|
216
|
-
def _receive_messages(self):
|
|
217
|
-
"""Continuously receive messages and add them to the incoming queue."""
|
|
218
|
-
while self.running:
|
|
219
|
-
try:
|
|
220
|
-
data, addr = self.incoming_socket.recvfrom(self.max_message_size)
|
|
221
|
-
self.incoming_queue.put((data, addr))
|
|
222
|
-
except Exception as e:
|
|
223
|
-
# Log error but continue running
|
|
224
|
-
print(f"Error receiving message: {e}")
|
|
225
|
-
|
|
226
|
-
def _send_messages(self):
|
|
227
|
-
"""Continuously send messages from the outgoing queue."""
|
|
228
|
-
while self.running:
|
|
229
|
-
try:
|
|
230
|
-
data, addr = self.outgoing_queue.get()
|
|
231
|
-
self.outgoing_socket.sendto(data, addr)
|
|
232
|
-
self.outgoing_queue.task_done()
|
|
233
|
-
except Exception as e:
|
|
234
|
-
# Log error but continue running
|
|
235
|
-
print(f"Error sending message: {e}")
|
|
236
|
-
|
|
237
|
-
def _process_messages(self):
|
|
238
|
-
"""Process messages from the incoming queue."""
|
|
239
|
-
while self.running:
|
|
240
|
-
try:
|
|
241
|
-
data, addr = self.incoming_queue.get()
|
|
242
|
-
self._handle_message(data, addr)
|
|
243
|
-
self.incoming_queue.task_done()
|
|
244
|
-
except Exception as e:
|
|
245
|
-
# Log error but continue running
|
|
246
|
-
print(f"Error processing message: {e}")
|
|
247
|
-
|
|
248
|
-
def _handle_message(self, data: bytes, addr: Tuple[str, int]):
|
|
249
|
-
"""Handle an incoming message."""
|
|
250
|
-
envelope = Envelope.from_bytes(data)
|
|
251
|
-
if envelope and envelope.message.topic in self.message_handlers:
|
|
252
|
-
# For transaction messages, only process if we're in validation route
|
|
253
|
-
if envelope.message.topic == Topic.TRANSACTION:
|
|
254
|
-
if self.is_in_validation_route():
|
|
255
|
-
self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
|
|
256
|
-
# For block messages, only process if we're in validation route
|
|
257
|
-
elif envelope.message.topic == Topic.BLOCK:
|
|
258
|
-
if self.is_in_validation_route():
|
|
259
|
-
self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
|
|
260
|
-
# Latest block and latest block requests can be handled by any node tracking the routes
|
|
261
|
-
elif envelope.message.topic in (Topic.LATEST_BLOCK, Topic.LATEST_BLOCK_REQUEST):
|
|
262
|
-
self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
|
|
263
|
-
# For other message types, always process
|
|
264
|
-
else:
|
|
265
|
-
self.message_handlers[envelope.message.topic](envelope.message.body, addr, envelope)
|
|
266
|
-
|
|
267
|
-
def send(self, data: bytes, addr: Tuple[str, int]):
|
|
268
|
-
"""Send raw data to a specific address."""
|
|
269
|
-
self.outgoing_queue.put((data, addr))
|
|
270
|
-
|
|
271
|
-
def get_address(self) -> Tuple[str, int]:
|
|
272
|
-
"""
|
|
273
|
-
Get the local address of this relay node.
|
|
274
|
-
|
|
275
|
-
Returns:
|
|
276
|
-
Tuple[str, int]: The local address (host, port)
|
|
277
|
-
"""
|
|
278
|
-
# This is a simplification - in a real implementation this would determine the
|
|
279
|
-
# actual public-facing IP address, which may be different from the binding address
|
|
280
|
-
return ("localhost", self.incoming_port)
|
|
281
|
-
|
|
282
|
-
def get_routes(self) -> bytes:
|
|
283
|
-
"""
|
|
284
|
-
Get the routes this node is part of as a bytes object.
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
bytes: List of route types (0 for peer, 1 for validation)
|
|
288
|
-
"""
|
|
289
|
-
return bytes(self.routes)
|
|
290
|
-
|
|
291
|
-
def send_message(self, body: bytes, topic: Topic, addr: Tuple[str, int], encrypted: bool = False, difficulty: int = 1):
|
|
292
|
-
"""
|
|
293
|
-
Create and send a message to a specific address.
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
body (bytes): The message body
|
|
297
|
-
topic (Topic): The message topic
|
|
298
|
-
addr (Tuple[str, int]): The recipient's address (host, port)
|
|
299
|
-
encrypted (bool): Whether the message is encrypted
|
|
300
|
-
difficulty (int): Number of leading zero bits required in the nonce hash
|
|
301
|
-
"""
|
|
302
|
-
envelope = Envelope.create(body, topic, encrypted, difficulty)
|
|
303
|
-
encoded_data = envelope.to_bytes()
|
|
304
|
-
self.send(encoded_data, addr)
|
|
305
|
-
|
|
306
|
-
def send_message_to_addr(self, addr: tuple, topic: Topic, body: bytes):
|
|
307
|
-
"""
|
|
308
|
-
Send a message to a specific address.
|
|
309
|
-
|
|
310
|
-
Args:
|
|
311
|
-
addr: Tuple of (ip, port) to send to
|
|
312
|
-
topic: Message topic
|
|
313
|
-
body: Message body
|
|
314
|
-
"""
|
|
315
|
-
try:
|
|
316
|
-
# Create an envelope with our node id and the message
|
|
317
|
-
message = Message(self.node_id, topic, body)
|
|
318
|
-
envelope = Envelope(message)
|
|
319
|
-
|
|
320
|
-
# Serialize and send
|
|
321
|
-
self.outgoing_socket.sendto(envelope.to_bytes(), addr)
|
|
322
|
-
except Exception as e:
|
|
323
|
-
print(f"Error sending message to {addr}: {e}")
|
|
324
|
-
|
|
325
|
-
def send_message_to_peer(self, peer: Peer, topic: Topic, body):
|
|
326
|
-
"""
|
|
327
|
-
Send a message to a specific peer.
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
peer: Peer to send to
|
|
331
|
-
topic: Message topic
|
|
332
|
-
body: Message body (bytes or JSON serializable)
|
|
333
|
-
"""
|
|
334
|
-
# Convert body to bytes if it's not already
|
|
335
|
-
if not isinstance(body, bytes):
|
|
336
|
-
if isinstance(body, dict) or isinstance(body, list):
|
|
337
|
-
body = json.dumps(body).encode('utf-8')
|
|
338
|
-
else:
|
|
339
|
-
body = str(body).encode('utf-8')
|
|
340
|
-
|
|
341
|
-
# Send to the peer's address
|
|
342
|
-
self.send_message_to_addr(peer.address, topic, body)
|
|
343
|
-
|
|
344
|
-
def stop(self):
|
|
345
|
-
"""Stop all worker threads."""
|
|
346
|
-
self.running = False
|
|
347
|
-
# Wait for queues to be processed
|
|
348
|
-
self.incoming_queue.join()
|
|
349
|
-
self.outgoing_queue.join()
|
|
350
|
-
|
|
351
|
-
# RouteTable wrapper methods
|
|
352
|
-
def add_peer(self, addr, public_key, difficulty):
|
|
353
|
-
"""Add a peer to the routing table."""
|
|
354
|
-
return self.route_table.update_peer(addr, public_key, difficulty)
|
|
355
|
-
|
|
356
|
-
def get_closest_peers(self, target_id, count=3):
|
|
357
|
-
"""Get the closest peers to the target ID."""
|
|
358
|
-
return self.route_table.get_closest_peers(target_id, count=count)
|
|
359
|
-
|
|
360
|
-
@property
|
|
361
|
-
def num_buckets(self):
|
|
362
|
-
"""Get the number of buckets in the routing table."""
|
|
363
|
-
return self.route_table.num_buckets
|
|
364
|
-
|
|
365
|
-
def get_bucket_peers(self, bucket_index):
|
|
366
|
-
"""Get peers from a specific bucket."""
|
|
367
|
-
return self.route_table.get_bucket_peers(bucket_index)
|
|
368
|
-
|
|
369
|
-
def has_peer(self, addr):
|
|
370
|
-
"""Check if a peer with the given address exists in the routing table."""
|
|
371
|
-
return self.route_table.has_peer(addr)
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
K-bucket implementation for Kademlia-style routing in Astreum node.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import List, Set
|
|
6
|
-
from .peer import Peer
|
|
7
|
-
|
|
8
|
-
class KBucket:
|
|
9
|
-
"""
|
|
10
|
-
A Kademlia k-bucket that stores peers.
|
|
11
|
-
|
|
12
|
-
K-buckets are used to store contact information for nodes in the DHT.
|
|
13
|
-
When a new node is added, it's placed at the tail of the list.
|
|
14
|
-
If a node is already in the list, it is moved to the tail.
|
|
15
|
-
This creates a least-recently seen eviction policy.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, k: int = 20):
|
|
19
|
-
"""
|
|
20
|
-
Initialize a k-bucket with a fixed size.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
k (int): Maximum number of peers in the bucket
|
|
24
|
-
"""
|
|
25
|
-
self.k = k
|
|
26
|
-
self.peers: List[Peer] = []
|
|
27
|
-
self._peer_ids: Set[bytes] = set() # Track peer IDs for quick lookup
|
|
28
|
-
|
|
29
|
-
def add(self, peer: Peer) -> bool:
|
|
30
|
-
"""
|
|
31
|
-
Add peer to bucket if not full or if peer exists.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
peer (Peer): Peer to add to the bucket
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
bool: True if added/exists, False if bucket full and peer not in bucket
|
|
38
|
-
"""
|
|
39
|
-
# If peer already in bucket, move to end (most recently seen)
|
|
40
|
-
if peer.public_key in self._peer_ids:
|
|
41
|
-
# Find and remove the peer
|
|
42
|
-
for i, existing_peer in enumerate(self.peers):
|
|
43
|
-
if existing_peer.public_key == peer.public_key:
|
|
44
|
-
del self.peers[i]
|
|
45
|
-
break
|
|
46
|
-
|
|
47
|
-
# Add back at the end (most recently seen)
|
|
48
|
-
self.peers.append(peer)
|
|
49
|
-
peer.update_last_seen()
|
|
50
|
-
return True
|
|
51
|
-
|
|
52
|
-
# If bucket not full, add peer
|
|
53
|
-
if len(self.peers) < self.k:
|
|
54
|
-
self.peers.append(peer)
|
|
55
|
-
self._peer_ids.add(peer.public_key)
|
|
56
|
-
peer.update_last_seen()
|
|
57
|
-
return True
|
|
58
|
-
|
|
59
|
-
# Bucket full and peer not in bucket
|
|
60
|
-
return False
|
|
61
|
-
|
|
62
|
-
def remove(self, peer: Peer) -> bool:
|
|
63
|
-
"""
|
|
64
|
-
Remove peer from bucket.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
peer (Peer): Peer to remove
|
|
68
|
-
|
|
69
|
-
Returns:
|
|
70
|
-
bool: True if removed, False if not in bucket
|
|
71
|
-
"""
|
|
72
|
-
if peer.public_key in self._peer_ids:
|
|
73
|
-
for i, existing_peer in enumerate(self.peers):
|
|
74
|
-
if existing_peer.public_key == peer.public_key:
|
|
75
|
-
del self.peers[i]
|
|
76
|
-
self._peer_ids.remove(peer.public_key)
|
|
77
|
-
return True
|
|
78
|
-
return False
|
|
79
|
-
|
|
80
|
-
def get_peers(self) -> List[Peer]:
|
|
81
|
-
"""Get all peers in the bucket."""
|
|
82
|
-
return self.peers.copy()
|
|
83
|
-
|
|
84
|
-
def contains(self, peer_id: bytes) -> bool:
|
|
85
|
-
"""Check if a peer ID is in the bucket."""
|
|
86
|
-
return peer_id in self._peer_ids
|
|
87
|
-
|
|
88
|
-
def __len__(self) -> int:
|
|
89
|
-
"""Get the number of peers in the bucket."""
|
|
90
|
-
return len(self.peers)
|