unaiverse 0.1.6__cp313-cp313-win_amd64.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 unaiverse might be problematic. Click here for more details.
- unaiverse/__init__.py +19 -0
- unaiverse/agent.py +2008 -0
- unaiverse/agent_basics.py +1846 -0
- unaiverse/clock.py +191 -0
- unaiverse/dataprops.py +1209 -0
- unaiverse/hsm.py +1880 -0
- unaiverse/modules/__init__.py +18 -0
- unaiverse/modules/cnu/__init__.py +17 -0
- unaiverse/modules/cnu/cnus.py +536 -0
- unaiverse/modules/cnu/layers.py +261 -0
- unaiverse/modules/cnu/psi.py +60 -0
- unaiverse/modules/hl/__init__.py +15 -0
- unaiverse/modules/hl/hl_utils.py +411 -0
- unaiverse/modules/networks.py +1509 -0
- unaiverse/modules/utils.py +680 -0
- unaiverse/networking/__init__.py +16 -0
- unaiverse/networking/node/__init__.py +18 -0
- unaiverse/networking/node/connpool.py +1261 -0
- unaiverse/networking/node/node.py +2223 -0
- unaiverse/networking/node/profile.py +446 -0
- unaiverse/networking/node/tokens.py +79 -0
- unaiverse/networking/p2p/__init__.py +198 -0
- unaiverse/networking/p2p/go.mod +127 -0
- unaiverse/networking/p2p/go.sum +548 -0
- unaiverse/networking/p2p/golibp2p.py +18 -0
- unaiverse/networking/p2p/golibp2p.pyi +135 -0
- unaiverse/networking/p2p/lib.go +2714 -0
- unaiverse/networking/p2p/lib.go.sha256 +1 -0
- unaiverse/networking/p2p/lib_types.py +312 -0
- unaiverse/networking/p2p/message_pb2.py +63 -0
- unaiverse/networking/p2p/messages.py +265 -0
- unaiverse/networking/p2p/mylogger.py +77 -0
- unaiverse/networking/p2p/p2p.py +929 -0
- unaiverse/networking/p2p/proto-go/message.pb.go +616 -0
- unaiverse/networking/p2p/unailib.cp313-win_amd64.pyd +0 -0
- unaiverse/streamlib/__init__.py +15 -0
- unaiverse/streamlib/streamlib.py +210 -0
- unaiverse/streams.py +770 -0
- unaiverse/utils/__init__.py +16 -0
- unaiverse/utils/ask_lone_wolf.json +27 -0
- unaiverse/utils/lone_wolf.json +19 -0
- unaiverse/utils/misc.py +305 -0
- unaiverse/utils/sandbox.py +293 -0
- unaiverse/utils/server.py +435 -0
- unaiverse/world.py +175 -0
- unaiverse-0.1.6.dist-info/METADATA +365 -0
- unaiverse-0.1.6.dist-info/RECORD +50 -0
- unaiverse-0.1.6.dist-info/WHEEL +5 -0
- unaiverse-0.1.6.dist-info/licenses/LICENSE +43 -0
- unaiverse-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
█████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
|
|
3
|
+
░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
|
|
4
|
+
░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
|
|
5
|
+
░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
|
|
6
|
+
░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
|
|
7
|
+
░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
|
|
8
|
+
░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
|
|
9
|
+
░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
|
|
10
|
+
A Collectionless AI Project (https://collectionless.ai)
|
|
11
|
+
Registration/Login: https://unaiverse.io
|
|
12
|
+
Code Repositories: https://github.com/collectionlessai/
|
|
13
|
+
Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
|
|
14
|
+
"""
|
|
15
|
+
import math
|
|
16
|
+
from unaiverse.networking.p2p.messages import Msg
|
|
17
|
+
from unaiverse.networking.p2p.p2p import P2P, P2PError
|
|
18
|
+
from unaiverse.networking.node.tokens import TokenVerifier
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ConnectionPools:
|
|
22
|
+
DEBUG = True
|
|
23
|
+
|
|
24
|
+
def __init__(self, max_connections: int, pool_name_to_p2p_name_and_ratio: dict[str, [str, float]],
|
|
25
|
+
p2p_name_to_p2p: dict[str, P2P], public_key: str | None = None, token: str | None = None):
|
|
26
|
+
"""Initializes a new instance of the ConnectionPools class.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
max_connections: The maximum total number of connections allowed across all pools.
|
|
30
|
+
pool_name_to_p2p_name_and_ratio: A dictionary mapping pool names to a list containing the associated P2P network name and its connection ratio.
|
|
31
|
+
p2p_name_to_p2p: A dictionary mapping P2P network names to their corresponding P2P objects.
|
|
32
|
+
public_key: An optional public key for token verification.
|
|
33
|
+
token: An optional initial token for authentication.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
None.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
# Common terms: a "pool triple" is [pool_contents, max_connections_in_such_a_pool, p2p_object_of_the_pool
|
|
40
|
+
self.max_con = max_connections
|
|
41
|
+
self.pool_count = len(pool_name_to_p2p_name_and_ratio)
|
|
42
|
+
self.pool_names = list(pool_name_to_p2p_name_and_ratio.keys())
|
|
43
|
+
self.pool_ratios = [p2p_name_and_ratio[1] for p2p_name_and_ratio in pool_name_to_p2p_name_and_ratio.values()]
|
|
44
|
+
|
|
45
|
+
# Indices involving the P2P object or its name
|
|
46
|
+
self.p2p_name_to_p2p = p2p_name_to_p2p
|
|
47
|
+
self.p2p_name_and_pool_name_to_pool_triple = {}
|
|
48
|
+
self.p2p_to_pool_names = {}
|
|
49
|
+
|
|
50
|
+
# Indices rooted around the pool name
|
|
51
|
+
self.pool_name_to_pool_triple = {}
|
|
52
|
+
self.pool_name_to_added_in_last_update = {}
|
|
53
|
+
self.pool_name_to_removed_in_last_update = {}
|
|
54
|
+
self.pool_name_to_peer_infos = {p: {} for p in self.pool_names}
|
|
55
|
+
|
|
56
|
+
# Indices rooted around the peer ID
|
|
57
|
+
self.peer_id_to_pool_name = {}
|
|
58
|
+
self.peer_id_to_p2p = {}
|
|
59
|
+
self.peer_id_to_misc = {}
|
|
60
|
+
self.peer_id_to_token = {}
|
|
61
|
+
|
|
62
|
+
# Token-related stuff, super private
|
|
63
|
+
self.__token = token if token is not None else ""
|
|
64
|
+
self.__token_verifier = TokenVerifier(public_key) if public_key is not None else None
|
|
65
|
+
|
|
66
|
+
# Checking
|
|
67
|
+
for p2p_name_and_ratio in pool_name_to_p2p_name_and_ratio.values():
|
|
68
|
+
assert p2p_name_and_ratio[0] in self.p2p_name_to_p2p, f"Cannot find p2p named {p2p_name_and_ratio[0]} "
|
|
69
|
+
assert self.max_con >= len(self.pool_names), "Too small number of max connections"
|
|
70
|
+
assert sum([x for x in self.pool_ratios if x > 0]) == 1.0, "Pool ratios must sum to 1.0"
|
|
71
|
+
|
|
72
|
+
# Preparing the pool triples
|
|
73
|
+
self.pool_name_to_pool_triple = \
|
|
74
|
+
{k: [set(), 0, self.p2p_name_to_p2p[pool_name_to_p2p_name_and_ratio[k][0]]] for k in self.pool_names}
|
|
75
|
+
num_zero_ratio_pools = len([x for x in self.pool_ratios if x == 0])
|
|
76
|
+
assert num_zero_ratio_pools <= self.max_con, "Cannot create pools given the provided max connection count"
|
|
77
|
+
|
|
78
|
+
# Edit: to solve the teacher not engaging with more than two students.
|
|
79
|
+
pools_max_sizes = {k: max(math.floor(self.pool_ratios[i] * (self.max_con - num_zero_ratio_pools)),
|
|
80
|
+
1 if self.pool_ratios[i] >= 0. else 0)
|
|
81
|
+
for i, k in enumerate(self.pool_names)}
|
|
82
|
+
|
|
83
|
+
# Pools_max_sizes = {k: self.max_con for k in self.pool_names}
|
|
84
|
+
|
|
85
|
+
# Fixing sizes
|
|
86
|
+
tot = 0
|
|
87
|
+
for i, (k, v) in enumerate(pools_max_sizes.items()):
|
|
88
|
+
assert v > 0 or self.pool_ratios[i] < 0, "Cannot create pools given the provided max connection count"
|
|
89
|
+
|
|
90
|
+
# Edit: to solve the teacher not engaging with more than two students.
|
|
91
|
+
tot += v
|
|
92
|
+
assert tot <= self.max_con, \
|
|
93
|
+
"Cannot create pools given the provided max connection count"
|
|
94
|
+
pools_max_sizes[self.pool_names[-1]] += (self.max_con - tot)
|
|
95
|
+
|
|
96
|
+
# Storing fixed sizes in the previously created pool triples & building additional index
|
|
97
|
+
for pool_name, pool_contents_max_con_and_p2p in self.pool_name_to_pool_triple.items():
|
|
98
|
+
pool_contents_max_con_and_p2p[1] = pools_max_sizes[pool_name] # Fixing the second element of the triple
|
|
99
|
+
|
|
100
|
+
pool, _, p2p = pool_contents_max_con_and_p2p
|
|
101
|
+
p2p_name = None
|
|
102
|
+
for k, v in self.p2p_name_to_p2p.items():
|
|
103
|
+
if v == p2p:
|
|
104
|
+
p2p_name = k
|
|
105
|
+
break
|
|
106
|
+
if p2p_name not in self.p2p_name_and_pool_name_to_pool_triple:
|
|
107
|
+
self.p2p_name_and_pool_name_to_pool_triple[p2p_name] = {}
|
|
108
|
+
self.p2p_to_pool_names[p2p] = []
|
|
109
|
+
self.p2p_name_and_pool_name_to_pool_triple[p2p_name][pool_name] = (
|
|
110
|
+
pool_contents_max_con_and_p2p)
|
|
111
|
+
self.p2p_to_pool_names[p2p].append(pool_name)
|
|
112
|
+
|
|
113
|
+
def __str__(self):
|
|
114
|
+
"""Returns a human-readable string representation of the connection pools' status.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
None.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A formatted string showing the number of connections and peer IDs in each pool.
|
|
121
|
+
"""
|
|
122
|
+
max_len = max(len(s) for s in self.pool_names)
|
|
123
|
+
s = f"[ConnectionPool] max_con={self.max_con}, pool_count={self.pool_count}"
|
|
124
|
+
for k, (pool, max_con, _) in self.pool_name_to_pool_triple.items():
|
|
125
|
+
kk = k + ","
|
|
126
|
+
s += f"\n\tpool={kk.ljust(max_len + 1)}\tmax_con={max_con},\tcurrent_con={len(pool)},\tpeer_ids="
|
|
127
|
+
ss = "("
|
|
128
|
+
first = True
|
|
129
|
+
for c in pool:
|
|
130
|
+
if not first:
|
|
131
|
+
ss += ", "
|
|
132
|
+
ss += c
|
|
133
|
+
first = False
|
|
134
|
+
ss += ")"
|
|
135
|
+
s += ss
|
|
136
|
+
return s
|
|
137
|
+
|
|
138
|
+
def __getitem__(self, p2p_name):
|
|
139
|
+
"""Retrieves a P2P object by its name.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
p2p_name: The name of the P2P network.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
The P2P object.
|
|
146
|
+
"""
|
|
147
|
+
return self.p2p_name_to_p2p[p2p_name]
|
|
148
|
+
|
|
149
|
+
def conn_routing_fcn(self, connected_peer_infos: list, p2p: P2P):
|
|
150
|
+
"""A placeholder function that must be implemented to route connected peers to the correct pool.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
connected_peer_infos: A list of dictionaries containing information about connected peers.
|
|
154
|
+
p2p: The P2P network object from which the peers were connected.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A dictionary mapping pool names to a dictionary of peer IDs and their information.
|
|
158
|
+
"""
|
|
159
|
+
raise NotImplementedError("You must implement conn_routing_fcn!")
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def __connect(p2p: P2P, addresses: list[str]):
|
|
163
|
+
"""Establishes a connection to a peer via a P2P network.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
p2p: The P2P network object to use for the connection.
|
|
167
|
+
addresses: A list of addresses of the peer to connect to.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
A tuple containing the peer ID and a boolean indicating if the connection was established through a relay.
|
|
171
|
+
"""
|
|
172
|
+
if ConnectionPools.DEBUG:
|
|
173
|
+
print(f"[DEBUG CONNECTIONS-POOL] Connecting to {addresses}")
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
winning_addr_info_dict = p2p.connect_to(addresses)
|
|
177
|
+
peer_id = winning_addr_info_dict.get('ID')
|
|
178
|
+
connected_addr_str = winning_addr_info_dict.get('Addrs')[0]
|
|
179
|
+
through_relay = '/p2p-circuit/' in connected_addr_str
|
|
180
|
+
if ConnectionPools.DEBUG:
|
|
181
|
+
print(f"[DEBUG CONNECTIONS-POOL] Connected to peer {peer_id} via {connected_addr_str} "
|
|
182
|
+
f"(through relay: {through_relay})")
|
|
183
|
+
|
|
184
|
+
return peer_id, through_relay
|
|
185
|
+
except P2PError:
|
|
186
|
+
if ConnectionPools.DEBUG:
|
|
187
|
+
print(f"[DEBUG CONNECTIONS-POOL] Connection failed!")
|
|
188
|
+
return None, False
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def disconnect(p2p: P2P, peer_id: str):
|
|
192
|
+
"""Disconnects from a specific peer on a P2P network.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
p2p: The P2P network object to use for disconnection.
|
|
196
|
+
peer_id: The peer ID to disconnect from.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if the disconnection is successful, otherwise False.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
p2p.disconnect_from(peer_id)
|
|
203
|
+
except P2PError:
|
|
204
|
+
return False
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
def set_token(self, token: str):
|
|
208
|
+
"""Sets the authentication token for the connection pools.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
token: The new token string.
|
|
212
|
+
"""
|
|
213
|
+
self.__token = token
|
|
214
|
+
|
|
215
|
+
def verify_token(self, token: str, peer_id: str):
|
|
216
|
+
"""Verifies a received token using the provided public key.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
token: The token string to verify.
|
|
220
|
+
peer_id: The peer ID associated with the token.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
A tuple containing the node ID and CV hash if the token is valid, otherwise None.
|
|
224
|
+
"""
|
|
225
|
+
if self.__token_verifier is None:
|
|
226
|
+
return None
|
|
227
|
+
else:
|
|
228
|
+
node_id, cv_hash = self.__token_verifier.verify_token(token, p2p_peer=peer_id)
|
|
229
|
+
return node_id, cv_hash # If the verification fails, this is None, None
|
|
230
|
+
|
|
231
|
+
def connect(self, addresses: list[str], p2p_name: str):
|
|
232
|
+
"""Connects to a peer on a specified P2P network.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
addresses: A list of addresses of the peer to connect to.
|
|
236
|
+
p2p_name: The name of the P2P network to use.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
A tuple containing the peer ID of the connected peer and a boolean indicating if a relay was used.
|
|
240
|
+
"""
|
|
241
|
+
p2p = self.p2p_name_to_p2p[p2p_name]
|
|
242
|
+
|
|
243
|
+
# Connecting
|
|
244
|
+
peer_id, through_relay = ConnectionPools.__connect(p2p, addresses)
|
|
245
|
+
return peer_id, through_relay
|
|
246
|
+
|
|
247
|
+
def add(self, peer_info: dict, pool_name: str):
|
|
248
|
+
"""Adds a connected peer to a specified connection pool.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
peer_info: A dictionary containing information about the peer.
|
|
252
|
+
pool_name: The name of the pool to add the peer to.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if the peer is successfully added, otherwise False.
|
|
256
|
+
"""
|
|
257
|
+
peer_id = peer_info['id']
|
|
258
|
+
pool, max_size, p2p = self.pool_name_to_pool_triple[pool_name]
|
|
259
|
+
if len(pool) < max_size:
|
|
260
|
+
|
|
261
|
+
# "hoping" peer IDs are unique, and stopping duplicate cases
|
|
262
|
+
if peer_id in self.peer_id_to_pool_name and self.peer_id_to_pool_name[peer_id] != pool_name:
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
self.peer_id_to_pool_name[peer_id] = pool_name
|
|
266
|
+
self.peer_id_to_p2p[peer_id] = p2p
|
|
267
|
+
|
|
268
|
+
# Setting 'misc' field (default is 0, where 0 means public)
|
|
269
|
+
peer_info['misc'] = self.peer_id_to_misc.get(peer_id, 0)
|
|
270
|
+
|
|
271
|
+
# Storing (only)
|
|
272
|
+
pool.add(peer_id)
|
|
273
|
+
self.pool_name_to_peer_infos[pool_name][peer_id] = peer_info
|
|
274
|
+
return True
|
|
275
|
+
else:
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
def remove(self, peer_id: str):
|
|
279
|
+
"""Removes a peer from its connection pool and disconnects from it.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
peer_id: The peer ID to remove.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
True if the peer is successfully removed, otherwise False.
|
|
286
|
+
"""
|
|
287
|
+
if peer_id in self.peer_id_to_pool_name:
|
|
288
|
+
pool_name = self.peer_id_to_pool_name[peer_id]
|
|
289
|
+
pool, _, p2p = self.pool_name_to_pool_triple[pool_name]
|
|
290
|
+
|
|
291
|
+
# Disconnecting
|
|
292
|
+
disc = ConnectionPools.disconnect(p2p, peer_id)
|
|
293
|
+
pool.remove(peer_id)
|
|
294
|
+
del self.pool_name_to_peer_infos[pool_name][peer_id]
|
|
295
|
+
del self.peer_id_to_pool_name[peer_id]
|
|
296
|
+
del self.peer_id_to_p2p[peer_id]
|
|
297
|
+
if peer_id in self.peer_id_to_misc:
|
|
298
|
+
del self.peer_id_to_misc[peer_id]
|
|
299
|
+
if peer_id in self.peer_id_to_token:
|
|
300
|
+
del self.peer_id_to_token[peer_id]
|
|
301
|
+
return disc
|
|
302
|
+
else:
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
def get_all_connected_peer_infos(self, pool_name: str):
|
|
306
|
+
"""Retrieves a list of peer information dictionaries for a given pool.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
pool_name: The name of the pool to query.
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
A list of dictionaries, each containing information about a peer in the pool.
|
|
313
|
+
"""
|
|
314
|
+
return list(self.pool_name_to_peer_infos[pool_name].values())
|
|
315
|
+
|
|
316
|
+
def get_pool_status(self):
|
|
317
|
+
"""Returns a dictionary showing the set of peer IDs in each pool.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
None.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
A dictionary mapping pool names to the set of peer IDs in that pool.
|
|
324
|
+
"""
|
|
325
|
+
return {k: v[0] for k, v in self.pool_name_to_pool_triple.items()}
|
|
326
|
+
|
|
327
|
+
def get_all_connected_peer_ids(self):
|
|
328
|
+
"""Retrieves a list of all peer IDs currently connected across all pools.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
None.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
A list of all connected peer IDs.
|
|
335
|
+
"""
|
|
336
|
+
return list(self.peer_id_to_pool_name.keys())
|
|
337
|
+
|
|
338
|
+
def update(self):
|
|
339
|
+
"""Refreshes the connection pools by checking for new and lost connections.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
None.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
A tuple containing two dictionaries: one for newly added peers and one for removed peers, both keyed by
|
|
346
|
+
pool name.
|
|
347
|
+
"""
|
|
348
|
+
self.pool_name_to_added_in_last_update = {}
|
|
349
|
+
self.pool_name_to_removed_in_last_update = {}
|
|
350
|
+
|
|
351
|
+
for p2p_name, p2p in self.p2p_name_to_p2p.items():
|
|
352
|
+
connected_peer_infos = p2p.get_connected_peers_info()
|
|
353
|
+
|
|
354
|
+
if connected_peer_infos is not None:
|
|
355
|
+
|
|
356
|
+
# Routing to the right queue / filtering
|
|
357
|
+
pool_name_and_peer_ids_to_peer_info = self.conn_routing_fcn(connected_peer_infos, p2p)
|
|
358
|
+
|
|
359
|
+
# Parsing the generated index
|
|
360
|
+
for pool_name, connected_peer_ids_to_connected_peer_infos \
|
|
361
|
+
in pool_name_and_peer_ids_to_peer_info.items():
|
|
362
|
+
pool, _, pool_p2p = self.p2p_name_and_pool_name_to_pool_triple[p2p_name][pool_name]
|
|
363
|
+
connected_peer_ids = connected_peer_ids_to_connected_peer_infos.keys()
|
|
364
|
+
new_peer_ids = connected_peer_ids - pool
|
|
365
|
+
lost_peer_ids = pool - connected_peer_ids
|
|
366
|
+
|
|
367
|
+
# Clearing disconnected agents
|
|
368
|
+
for lost_peer_id in lost_peer_ids:
|
|
369
|
+
self.pool_name_to_removed_in_last_update.setdefault(pool_name, set()).add(lost_peer_id)
|
|
370
|
+
|
|
371
|
+
# Adding new agents
|
|
372
|
+
for new_peer_id in new_peer_ids:
|
|
373
|
+
peer_info = connected_peer_ids_to_connected_peer_infos[new_peer_id]
|
|
374
|
+
if not self.add(peer_info, pool_name=pool_name):
|
|
375
|
+
break
|
|
376
|
+
self.pool_name_to_added_in_last_update.setdefault(pool_name, set()).add(new_peer_id)
|
|
377
|
+
|
|
378
|
+
return self.pool_name_to_added_in_last_update, self.pool_name_to_removed_in_last_update
|
|
379
|
+
|
|
380
|
+
def get_messages(self, p2p_name: str, allowed_not_connected_peers: set | None = None):
|
|
381
|
+
"""Retrieves and verifies all messages from a specified P2P network.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
p2p_name: The name of the P2P network to fetch messages from.
|
|
385
|
+
allowed_not_connected_peers: An optional set of peer IDs to allow messages from, even if they are not in the pools.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
A list of verified and processed message objects.
|
|
389
|
+
"""
|
|
390
|
+
# Pop all messages
|
|
391
|
+
messages: list[Msg] = self[p2p_name].pop_messages() # Pop all messages (list of messages - list[Msg])
|
|
392
|
+
ret = []
|
|
393
|
+
for m in messages:
|
|
394
|
+
if (m.sender in self.peer_id_to_pool_name or # Check if expected sender
|
|
395
|
+
(allowed_not_connected_peers is not None and m.sender in allowed_not_connected_peers)):
|
|
396
|
+
try:
|
|
397
|
+
token_with_inspector_final_bit = m.piggyback
|
|
398
|
+
token = token_with_inspector_final_bit[0:-1]
|
|
399
|
+
inspector_mode = token_with_inspector_final_bit[-1]
|
|
400
|
+
node_id, _ = self.verify_token(token, m.sender)
|
|
401
|
+
if node_id is not None:
|
|
402
|
+
|
|
403
|
+
# Replacing piggyback with the node ID and the flag telling if it is inspector
|
|
404
|
+
m.piggyback = node_id + inspector_mode
|
|
405
|
+
ret.append(m)
|
|
406
|
+
if m.sender in self.peer_id_to_pool_name:
|
|
407
|
+
self.peer_id_to_token[m.sender] = token
|
|
408
|
+
else:
|
|
409
|
+
print("Received a message missing expected info in the token payload (discarding it)")
|
|
410
|
+
except Exception as e:
|
|
411
|
+
print(f"Received a message with an invalid piggyback token! (discarding it) [{e}]")
|
|
412
|
+
else:
|
|
413
|
+
if ConnectionPools.DEBUG:
|
|
414
|
+
print("Received a message from a unknown sender (discarding it)")
|
|
415
|
+
return ret
|
|
416
|
+
|
|
417
|
+
def get_added_after_updating(self, pool_name: str | None = None):
|
|
418
|
+
"""Retrieves the peers that were added in the last update cycle.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
pool_name: The name of a specific pool to query. If None, returns data for all pools.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
A set of added peer IDs for the specified pool, or a dictionary of sets for all pools.
|
|
425
|
+
"""
|
|
426
|
+
if pool_name is not None:
|
|
427
|
+
return self.pool_name_to_added_in_last_update[pool_name]
|
|
428
|
+
else:
|
|
429
|
+
return self.pool_name_to_added_in_last_update
|
|
430
|
+
|
|
431
|
+
def get_removed_after_updating(self, pool_name: str | None = None):
|
|
432
|
+
"""Retrieves the peers that were removed in the last update cycle.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
pool_name: The name of a specific pool to query. If None, returns data for all pools.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
A set of removed peer IDs for the specified pool, or a dictionary of sets for all pools.
|
|
439
|
+
"""
|
|
440
|
+
if pool_name is not None:
|
|
441
|
+
return self.pool_name_to_removed_in_last_update[pool_name]
|
|
442
|
+
else:
|
|
443
|
+
return self.pool_name_to_removed_in_last_update
|
|
444
|
+
|
|
445
|
+
def get_last_token(self, peer_id):
|
|
446
|
+
"""Retrieves the last known token for a given peer.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
peer_id: The peer ID to query.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
The token string if found, otherwise None.
|
|
453
|
+
"""
|
|
454
|
+
return self.peer_id_to_token[peer_id] if peer_id in self.peer_id_to_token else None
|
|
455
|
+
|
|
456
|
+
def is_connected(self, peer_id: str, pool_name: str | None = None):
|
|
457
|
+
"""Checks if a peer is currently connected, optionally in a specific pool.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
peer_id: The peer ID to check.
|
|
461
|
+
pool_name: An optional pool name to check within.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
True if the peer is connected, otherwise False.
|
|
465
|
+
"""
|
|
466
|
+
if pool_name is None:
|
|
467
|
+
return peer_id in self.peer_id_to_pool_name
|
|
468
|
+
else:
|
|
469
|
+
return peer_id in self.peer_id_to_pool_name and pool_name == self.peer_id_to_pool_name[peer_id]
|
|
470
|
+
|
|
471
|
+
def get_pool_of(self, peer_id: str):
|
|
472
|
+
"""Gets the pool name for a given connected peer.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
peer_id: The peer ID to query.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
The name of the pool the peer is in.
|
|
479
|
+
"""
|
|
480
|
+
return self.peer_id_to_pool_name[peer_id]
|
|
481
|
+
|
|
482
|
+
def size(self, pool_name: str | None = None):
|
|
483
|
+
"""Returns the number of connections in a specific pool or the total number across all pools.
|
|
484
|
+
|
|
485
|
+
Args:
|
|
486
|
+
pool_name: An optional pool name to get the size of. If None, returns the total size.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
The size of the pool or the total number of connections.
|
|
490
|
+
"""
|
|
491
|
+
if pool_name is not None:
|
|
492
|
+
return len(self.pool_name_to_pool_triple[pool_name])
|
|
493
|
+
else:
|
|
494
|
+
c = 0
|
|
495
|
+
for v in self.pool_name_to_pool_triple.values():
|
|
496
|
+
c += len(v)
|
|
497
|
+
return c
|
|
498
|
+
|
|
499
|
+
def send(self, peer_id: str, channel_trail: str | None,
|
|
500
|
+
content_type: str, content: bytes | dict | None = None, p2p: P2P | None = None):
|
|
501
|
+
"""Sends a direct message to a specific peer.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
peer_id: The peer ID to send the message to.
|
|
505
|
+
channel_trail: An optional string to append to the channel name.
|
|
506
|
+
content_type: The type of content in the message.
|
|
507
|
+
content: The message content.
|
|
508
|
+
p2p: An optional P2P object to use for sending. If None, it is derived from the peer_id.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
True if the message is sent successfully, otherwise False.
|
|
512
|
+
"""
|
|
513
|
+
# Getting the right p2p object
|
|
514
|
+
if p2p is None:
|
|
515
|
+
p2p = self.peer_id_to_p2p[peer_id] if peer_id in self.peer_id_to_p2p else None
|
|
516
|
+
if p2p is None:
|
|
517
|
+
if ConnectionPools.DEBUG:
|
|
518
|
+
print("[DEBUG CONNECTIONS-POOL] P2P non found for peer id: " + str(peer_id))
|
|
519
|
+
return False
|
|
520
|
+
|
|
521
|
+
# Defining channel
|
|
522
|
+
if channel_trail is not None and len(channel_trail) > 0:
|
|
523
|
+
channel = f"{p2p.peer_id}::dm:{peer_id}-{content_type}~{channel_trail}"
|
|
524
|
+
else:
|
|
525
|
+
channel = f"{p2p.peer_id}::dm:{peer_id}-{content_type}"
|
|
526
|
+
|
|
527
|
+
# Adding sender info here
|
|
528
|
+
msg = Msg(sender=p2p.peer_id,
|
|
529
|
+
content_type=content_type,
|
|
530
|
+
content=content,
|
|
531
|
+
channel=channel,
|
|
532
|
+
piggyback=self.__token + "0") # Adding inspector-mode bit (dummy bit here)
|
|
533
|
+
if ConnectionPools.DEBUG:
|
|
534
|
+
print("[DEBUG CONNECTIONS-POOL] Sending message: " + str(msg))
|
|
535
|
+
|
|
536
|
+
# Sending direct message
|
|
537
|
+
try:
|
|
538
|
+
p2p.send_message_to_peer(channel, msg)
|
|
539
|
+
|
|
540
|
+
# If the line above executes without raising an error, it was successful.
|
|
541
|
+
return True
|
|
542
|
+
except P2PError as e:
|
|
543
|
+
|
|
544
|
+
# If send_message_to_peer fails, it will raise a P2PError. We catch it here.
|
|
545
|
+
if ConnectionPools.DEBUG:
|
|
546
|
+
print("[DEBUG CONNECTIONS-POOL] Sending error is: " + str(e))
|
|
547
|
+
return False
|
|
548
|
+
|
|
549
|
+
def subscribe(self, peer_id: str, channel: str, default_p2p_name: str | None = None):
|
|
550
|
+
"""Subscribes to a topic/channel on a P2P network.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
peer_id: The peer ID associated with the topic/channel.
|
|
554
|
+
channel: The name of the channel to subscribe to.
|
|
555
|
+
default_p2p_name: An optional P2P network name to use if the peer's network is unknown.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
True if the subscription is successful, otherwise False.
|
|
559
|
+
"""
|
|
560
|
+
|
|
561
|
+
# Getting the right p2p object
|
|
562
|
+
p2p = None
|
|
563
|
+
for _p2p in self.p2p_to_pool_names.keys():
|
|
564
|
+
if _p2p.peer_id == peer_id:
|
|
565
|
+
p2p = _p2p
|
|
566
|
+
break
|
|
567
|
+
if p2p is None and peer_id in self.peer_id_to_p2p:
|
|
568
|
+
p2p = self.peer_id_to_p2p[peer_id]
|
|
569
|
+
if p2p is None:
|
|
570
|
+
if default_p2p_name is not None:
|
|
571
|
+
p2p = self.p2p_name_to_p2p[default_p2p_name]
|
|
572
|
+
else:
|
|
573
|
+
return False
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
p2p.subscribe_to_topic(channel)
|
|
577
|
+
except (P2PError, ValueError) as e:
|
|
578
|
+
return False
|
|
579
|
+
return True
|
|
580
|
+
|
|
581
|
+
def unsubscribe(self, peer_id: str, channel: str):
|
|
582
|
+
"""Unsubscribes from a topic/channel on a P2P network.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
peer_id: The peer ID associated with the topic/channel.
|
|
586
|
+
channel: The name of the channel to unsubscribe from.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
True if the unsubscription is successful, otherwise False.
|
|
590
|
+
"""
|
|
591
|
+
|
|
592
|
+
# Getting the right p2p object
|
|
593
|
+
p2p = None
|
|
594
|
+
for _p2p in self.p2p_to_pool_names.keys():
|
|
595
|
+
if _p2p.peer_id == peer_id:
|
|
596
|
+
p2p = _p2p
|
|
597
|
+
break
|
|
598
|
+
if p2p is None and peer_id in self.peer_id_to_p2p:
|
|
599
|
+
p2p = self.peer_id_to_p2p[peer_id]
|
|
600
|
+
if p2p is None:
|
|
601
|
+
return False
|
|
602
|
+
|
|
603
|
+
try:
|
|
604
|
+
p2p.unsubscribe_from_topic(channel)
|
|
605
|
+
except (P2PError, ValueError):
|
|
606
|
+
return False
|
|
607
|
+
return True
|
|
608
|
+
|
|
609
|
+
def publish(self, peer_id: str, channel: str,
|
|
610
|
+
content_type: str, content: bytes | dict | tuple | None = None):
|
|
611
|
+
"""Publishes a message to a topic/channel on a P2P network.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
peer_id: The peer ID associated with the topic/channel.
|
|
615
|
+
channel: The name of the channel to publish to.
|
|
616
|
+
content_type: The type of content in the message.
|
|
617
|
+
content: The message content.
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
True if the message is published successfully, otherwise False.
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
# Getting the right p2p object
|
|
624
|
+
p2p = None
|
|
625
|
+
for _p2p in self.p2p_to_pool_names.keys():
|
|
626
|
+
if _p2p.peer_id == peer_id:
|
|
627
|
+
p2p = _p2p
|
|
628
|
+
break
|
|
629
|
+
if p2p is None:
|
|
630
|
+
p2p = self.peer_id_to_p2p[peer_id]
|
|
631
|
+
if p2p is None:
|
|
632
|
+
return False
|
|
633
|
+
|
|
634
|
+
# Adding sender info here
|
|
635
|
+
msg = Msg(sender=p2p.peer_id,
|
|
636
|
+
content_type=content_type,
|
|
637
|
+
content=content,
|
|
638
|
+
channel=channel,
|
|
639
|
+
piggyback=self.__token + "0") # Adding inspector-mode bit (dummy bit here)
|
|
640
|
+
if ConnectionPools.DEBUG:
|
|
641
|
+
print("[DEBUG CONNECTIONS-POOL] Sending (publish) message: " + str(msg))
|
|
642
|
+
|
|
643
|
+
# Sending message via GossipSub
|
|
644
|
+
try:
|
|
645
|
+
p2p.broadcast_message(channel, msg)
|
|
646
|
+
|
|
647
|
+
# If the line above executes without raising an error, it was successful.
|
|
648
|
+
return True
|
|
649
|
+
except P2PError as e:
|
|
650
|
+
|
|
651
|
+
# If send_message_to_peer fails, it will raise a P2PError. We catch it here.
|
|
652
|
+
return False
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
class NodeConn(ConnectionPools):
|
|
656
|
+
|
|
657
|
+
# Basic name
|
|
658
|
+
__ALL_UNIVERSE = "all_universe"
|
|
659
|
+
__WORLD_AGENTS_ONLY = "world_agents"
|
|
660
|
+
__WORLD_NODE_ONLY = "world_node"
|
|
661
|
+
__WORLD_MASTERS_ONLY = "world_masters"
|
|
662
|
+
|
|
663
|
+
# Suffixes
|
|
664
|
+
__PUBLIC_NET = "_public"
|
|
665
|
+
__PRIVATE_NET = "_private"
|
|
666
|
+
|
|
667
|
+
# Prefixes
|
|
668
|
+
__INBOUND = "in_"
|
|
669
|
+
__OUTBOUND = "out_"
|
|
670
|
+
|
|
671
|
+
# P2p names
|
|
672
|
+
P2P_PUBLIC = "p2p_public"
|
|
673
|
+
P2P_WORLD = "p2p_world"
|
|
674
|
+
|
|
675
|
+
# All pools (prefix + basic name + suffix)
|
|
676
|
+
IN_PUBLIC = __INBOUND + __ALL_UNIVERSE + __PUBLIC_NET
|
|
677
|
+
OUT_PUBLIC = __OUTBOUND + __ALL_UNIVERSE + __PUBLIC_NET
|
|
678
|
+
IN_WORLD_AGENTS = __INBOUND + __WORLD_AGENTS_ONLY + __PRIVATE_NET
|
|
679
|
+
OUT_WORLD_AGENTS = __OUTBOUND + __WORLD_AGENTS_ONLY + __PRIVATE_NET
|
|
680
|
+
IN_WORLD_NODE = __INBOUND + __WORLD_NODE_ONLY + __PRIVATE_NET
|
|
681
|
+
OUT_WORLD_NODE = __OUTBOUND + __WORLD_NODE_ONLY + __PRIVATE_NET
|
|
682
|
+
IN_WORLD_MASTERS = __INBOUND + __WORLD_MASTERS_ONLY + __PRIVATE_NET
|
|
683
|
+
OUT_WORLD_MASTERS = __OUTBOUND + __WORLD_MASTERS_ONLY + __PRIVATE_NET
|
|
684
|
+
|
|
685
|
+
# Aggregated pools
|
|
686
|
+
PUBLIC = {IN_PUBLIC, OUT_PUBLIC}
|
|
687
|
+
WORLD_NODE = {IN_WORLD_NODE, OUT_WORLD_NODE}
|
|
688
|
+
WORLD_AGENTS = {IN_WORLD_AGENTS, OUT_WORLD_AGENTS}
|
|
689
|
+
WORLD_MASTERS = {IN_WORLD_MASTERS, OUT_WORLD_MASTERS}
|
|
690
|
+
WORLD = WORLD_NODE | WORLD_AGENTS | WORLD_MASTERS
|
|
691
|
+
ALL = PUBLIC | WORLD
|
|
692
|
+
OUTGOING = {OUT_PUBLIC, OUT_WORLD_NODE, OUT_WORLD_AGENTS, OUT_WORLD_MASTERS}
|
|
693
|
+
INCOMING = {IN_PUBLIC, IN_WORLD_NODE, IN_WORLD_AGENTS, IN_WORLD_MASTERS}
|
|
694
|
+
|
|
695
|
+
def __init__(self, max_connections: int, p2p_u: P2P, p2p_w: P2P,
|
|
696
|
+
is_world_node: bool, public_key: str, token: str):
|
|
697
|
+
"""Initializes a new instance of the NodeConn class.
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
max_connections: The total number of connections the node can handle.
|
|
701
|
+
p2p_u: The P2P object for the public network.
|
|
702
|
+
p2p_w: The P2P object for the world/private network.
|
|
703
|
+
is_world_node: A boolean flag indicating if this node is a world node.
|
|
704
|
+
public_key: The public key for token verification.
|
|
705
|
+
token: The node's authentication token.
|
|
706
|
+
"""
|
|
707
|
+
super().__init__(max_connections=max_connections,
|
|
708
|
+
p2p_name_to_p2p={
|
|
709
|
+
NodeConn.P2P_PUBLIC: p2p_u,
|
|
710
|
+
NodeConn.P2P_WORLD: p2p_w,
|
|
711
|
+
},
|
|
712
|
+
pool_name_to_p2p_name_and_ratio={
|
|
713
|
+
NodeConn.IN_PUBLIC: [NodeConn.P2P_PUBLIC, 0.25 / 2. if not is_world_node else 0.25 / 2.],
|
|
714
|
+
NodeConn.OUT_PUBLIC: [NodeConn.P2P_PUBLIC, 0.25 / 2. if not is_world_node else 0.25 / 2.],
|
|
715
|
+
NodeConn.IN_WORLD_AGENTS: [NodeConn.P2P_WORLD, .75 / 2 if not is_world_node else 0.5 / 2],
|
|
716
|
+
NodeConn.OUT_WORLD_AGENTS: [NodeConn.P2P_WORLD, .75 / 2 if not is_world_node else 0.5 / 2],
|
|
717
|
+
NodeConn.IN_WORLD_NODE: [NodeConn.P2P_WORLD, 0. if not is_world_node else -1.],
|
|
718
|
+
NodeConn.OUT_WORLD_NODE: [NodeConn.P2P_WORLD, 0. if not is_world_node else -1],
|
|
719
|
+
NodeConn.IN_WORLD_MASTERS: [NodeConn.P2P_WORLD, 0. if not is_world_node else 0.25 / 2.],
|
|
720
|
+
NodeConn.OUT_WORLD_MASTERS: [NodeConn.P2P_WORLD, 0. if not is_world_node else 0.25 / 2.]
|
|
721
|
+
},
|
|
722
|
+
public_key=public_key, token=token)
|
|
723
|
+
|
|
724
|
+
# Just for convenience
|
|
725
|
+
self.p2p_public = p2p_u
|
|
726
|
+
self.p2p_world = p2p_w
|
|
727
|
+
|
|
728
|
+
# These are the list of all the possible agents that might try to connect when we are in world
|
|
729
|
+
self.world_agents_list = set()
|
|
730
|
+
self.world_masters_list = set()
|
|
731
|
+
self.world_agents_and_world_masters_list = set()
|
|
732
|
+
self.world_node_peer_id = None
|
|
733
|
+
self.inspector_peer_id = None
|
|
734
|
+
self.role_to_peer_ids = {}
|
|
735
|
+
self.peer_id_to_addrs = {}
|
|
736
|
+
|
|
737
|
+
# Rendezvous
|
|
738
|
+
self.rendezvous_tag = -1
|
|
739
|
+
|
|
740
|
+
def reset_rendezvous_tag(self):
|
|
741
|
+
"""Resets the rendezvous tag to its initial state."""
|
|
742
|
+
self.rendezvous_tag = -1
|
|
743
|
+
|
|
744
|
+
def conn_routing_fcn(self, connected_peer_infos: list, p2p: P2P):
|
|
745
|
+
"""Routes connected peers to the correct connection pool based on their network and role.
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
connected_peer_infos: A list of dictionaries with information about connected peers.
|
|
749
|
+
p2p: The P2P network object where the connections were found.
|
|
750
|
+
|
|
751
|
+
Returns:
|
|
752
|
+
A dictionary mapping pool names to a dictionary of peer IDs and their information.
|
|
753
|
+
"""
|
|
754
|
+
pool_name_and_peer_id_to_peer_info = {k: {} for k in self.p2p_to_pool_names[p2p]}
|
|
755
|
+
public = p2p == self.p2p_public
|
|
756
|
+
|
|
757
|
+
for c in connected_peer_infos:
|
|
758
|
+
inbound = c['direction'] == "incoming"
|
|
759
|
+
outbound = c['direction'] == "outgoing"
|
|
760
|
+
peer_id = c['id'] # Other fields are: c['addrs'], c['connected_at']
|
|
761
|
+
|
|
762
|
+
if public:
|
|
763
|
+
if inbound:
|
|
764
|
+
pool_name_and_peer_id_to_peer_info[NodeConn.IN_PUBLIC][peer_id] = c
|
|
765
|
+
elif outbound:
|
|
766
|
+
pool_name_and_peer_id_to_peer_info[NodeConn.OUT_PUBLIC][peer_id] = c
|
|
767
|
+
else:
|
|
768
|
+
raise ValueError(f"Connection direction is undefined: {c['direction']}")
|
|
769
|
+
else:
|
|
770
|
+
is_world_agent = peer_id in self.world_agents_list
|
|
771
|
+
is_world_master = peer_id in self.world_masters_list
|
|
772
|
+
is_world_node = self.world_node_peer_id is not None and peer_id == self.world_node_peer_id
|
|
773
|
+
is_inspector = self.inspector_peer_id is not None and peer_id == self.inspector_peer_id
|
|
774
|
+
if not is_world_node and not is_world_master and not is_world_agent and not is_inspector:
|
|
775
|
+
if ConnectionPools.DEBUG:
|
|
776
|
+
print("[DEBUG CONNECTIONS-POOL] World agents list: " + str(self.world_agents_list))
|
|
777
|
+
print("[DEBUG CONNECTIONS-POOL] World masters list: " + str(self.world_masters_list))
|
|
778
|
+
print("[DEBUG CONNECTIONS-POOL] World node peer id: " + str(self.world_node_peer_id))
|
|
779
|
+
print("[DEBUG CONNECTIONS-POOL] Inspector peer id: " + str(self.inspector_peer_id))
|
|
780
|
+
print(f"[DEBUG CONNECTIONS-POOL] Unable to determine the peer type for {peer_id}: "
|
|
781
|
+
f"cannot say if world agent, master, world node, inspector (disconnecting it)")
|
|
782
|
+
ConnectionPools.disconnect(p2p, peer_id)
|
|
783
|
+
continue
|
|
784
|
+
|
|
785
|
+
if inbound:
|
|
786
|
+
pool_name_and_peer_id_to_peer_info[NodeConn.IN_WORLD_AGENTS if is_world_agent else (
|
|
787
|
+
NodeConn.IN_WORLD_NODE if is_world_node else
|
|
788
|
+
NodeConn.IN_WORLD_MASTERS)][peer_id] = c
|
|
789
|
+
elif outbound:
|
|
790
|
+
pool_name_and_peer_id_to_peer_info[NodeConn.OUT_WORLD_AGENTS if is_world_agent else (
|
|
791
|
+
NodeConn.OUT_WORLD_NODE if is_world_node else
|
|
792
|
+
NodeConn.OUT_WORLD_MASTERS)][peer_id] = c
|
|
793
|
+
else:
|
|
794
|
+
raise ValueError(f"Connection direction is undefined: {c}")
|
|
795
|
+
|
|
796
|
+
return pool_name_and_peer_id_to_peer_info
|
|
797
|
+
|
|
798
|
+
def set_world(self, world_peer_id: str | None):
|
|
799
|
+
"""Sets the peer ID of the world node.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
world_peer_id: The peer ID of the world node, or None to clear it.
|
|
803
|
+
"""
|
|
804
|
+
self.world_node_peer_id = world_peer_id
|
|
805
|
+
|
|
806
|
+
def set_inspector(self, inspector_peer_id: str | None):
|
|
807
|
+
"""Sets the peer ID of the inspector.
|
|
808
|
+
|
|
809
|
+
Args:
|
|
810
|
+
inspector_peer_id: The peer ID of the inspector node.
|
|
811
|
+
"""
|
|
812
|
+
self.inspector_peer_id = inspector_peer_id
|
|
813
|
+
|
|
814
|
+
def get_world_peer_id(self):
|
|
815
|
+
"""Returns the peer ID of the world node.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
None.
|
|
819
|
+
|
|
820
|
+
Returns:
|
|
821
|
+
The world node's peer ID.
|
|
822
|
+
"""
|
|
823
|
+
return self.world_node_peer_id
|
|
824
|
+
|
|
825
|
+
def set_addresses_in_peer_info(self, peer_id, addresses):
|
|
826
|
+
"""Updates the list of addresses for a given peer.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
peer_id: The peer ID to update.
|
|
830
|
+
addresses: A new list of addresses for the peer.
|
|
831
|
+
"""
|
|
832
|
+
if self.in_connection_queues(peer_id):
|
|
833
|
+
addrs = self.pool_name_to_peer_infos[self.get_pool_of(peer_id)][peer_id]['addrs']
|
|
834
|
+
addrs.clear() # Warning: do not allocate a new list, keep the current one (it is referenced by others)
|
|
835
|
+
for _addrs in addresses:
|
|
836
|
+
addrs.append(_addrs)
|
|
837
|
+
|
|
838
|
+
def set_role(self, peer_id, new_role: int):
|
|
839
|
+
"""Updates the role of a peer and its associated role-based lists.
|
|
840
|
+
|
|
841
|
+
Args:
|
|
842
|
+
peer_id: The peer ID to update.
|
|
843
|
+
new_role: The new role for the peer.
|
|
844
|
+
"""
|
|
845
|
+
cur_role = self.get_role(peer_id)
|
|
846
|
+
|
|
847
|
+
# Updating
|
|
848
|
+
self.peer_id_to_misc[peer_id] = new_role
|
|
849
|
+
|
|
850
|
+
if self.in_connection_queues(peer_id):
|
|
851
|
+
self.pool_name_to_peer_infos[self.get_pool_of(peer_id)][peer_id]['misc'] = new_role
|
|
852
|
+
|
|
853
|
+
# Updating
|
|
854
|
+
if cur_role in self.role_to_peer_ids:
|
|
855
|
+
if peer_id in self.role_to_peer_ids[cur_role]:
|
|
856
|
+
self.role_to_peer_ids[cur_role].remove(peer_id)
|
|
857
|
+
if len(self.role_to_peer_ids[cur_role]) == 0:
|
|
858
|
+
del self.role_to_peer_ids[cur_role]
|
|
859
|
+
if new_role not in self.role_to_peer_ids:
|
|
860
|
+
self.role_to_peer_ids[new_role] = set()
|
|
861
|
+
self.role_to_peer_ids[new_role].add(peer_id)
|
|
862
|
+
|
|
863
|
+
def set_world_agents_list(self, world_agents_list_peer_infos: list[dict] | None):
|
|
864
|
+
"""Sets the list of all world agents based on a provided list of peer information.
|
|
865
|
+
|
|
866
|
+
Args:
|
|
867
|
+
world_agents_list_peer_infos: A list of dictionaries containing peer information for world agents.
|
|
868
|
+
"""
|
|
869
|
+
|
|
870
|
+
# Clearing previous information
|
|
871
|
+
to_remove = []
|
|
872
|
+
for peer_id, misc in self.peer_id_to_misc.items():
|
|
873
|
+
if misc & 1 == 1 and misc & 2 == 0:
|
|
874
|
+
to_remove.append((peer_id, misc))
|
|
875
|
+
|
|
876
|
+
for peer_id, misc in to_remove:
|
|
877
|
+
del self.peer_id_to_misc[peer_id]
|
|
878
|
+
if peer_id in self.peer_id_to_addrs:
|
|
879
|
+
del self.peer_id_to_addrs[peer_id]
|
|
880
|
+
self.role_to_peer_ids[misc].discard(peer_id)
|
|
881
|
+
|
|
882
|
+
# Setting new information
|
|
883
|
+
if world_agents_list_peer_infos is not None and len(world_agents_list_peer_infos) > 0:
|
|
884
|
+
self.world_agents_list = {x['id'] for x in world_agents_list_peer_infos}
|
|
885
|
+
for x in world_agents_list_peer_infos:
|
|
886
|
+
self.peer_id_to_addrs[x['id']] = x['addrs']
|
|
887
|
+
self.set_role(x['id'], x['misc'])
|
|
888
|
+
else:
|
|
889
|
+
self.world_agents_list = set()
|
|
890
|
+
|
|
891
|
+
self.world_agents_and_world_masters_list = self.world_agents_list | self.world_masters_list
|
|
892
|
+
|
|
893
|
+
def set_world_masters_list(self, world_masters_list_peer_infos: list[dict] | None):
|
|
894
|
+
"""Sets the list of all world masters based on a provided list of peer information.
|
|
895
|
+
|
|
896
|
+
Args:
|
|
897
|
+
world_masters_list_peer_infos: A list of dictionaries containing peer information for world masters.
|
|
898
|
+
"""
|
|
899
|
+
|
|
900
|
+
# Clearing previous information
|
|
901
|
+
to_remove = []
|
|
902
|
+
for peer_id, misc in self.peer_id_to_misc.items():
|
|
903
|
+
if misc & 1 == 1 and misc & 2 == 2:
|
|
904
|
+
to_remove.append((peer_id, misc))
|
|
905
|
+
|
|
906
|
+
for peer_id, misc in to_remove:
|
|
907
|
+
del self.peer_id_to_misc[peer_id]
|
|
908
|
+
if peer_id in self.peer_id_to_addrs:
|
|
909
|
+
del self.peer_id_to_addrs[peer_id]
|
|
910
|
+
self.role_to_peer_ids[misc].discard(peer_id)
|
|
911
|
+
|
|
912
|
+
# Setting new information
|
|
913
|
+
if world_masters_list_peer_infos is not None and len(world_masters_list_peer_infos) > 0:
|
|
914
|
+
self.world_masters_list = {x['id'] for x in world_masters_list_peer_infos}
|
|
915
|
+
for x in world_masters_list_peer_infos:
|
|
916
|
+
self.peer_id_to_addrs[x['id']] = x['addrs']
|
|
917
|
+
self.set_role(x['id'], x['misc'])
|
|
918
|
+
else:
|
|
919
|
+
self.world_masters_list = set()
|
|
920
|
+
|
|
921
|
+
self.world_agents_and_world_masters_list = self.world_agents_list | self.world_masters_list
|
|
922
|
+
|
|
923
|
+
def add_to_world_agents_list(self, peer_id: str, addrs: list[str], role: int = -1):
|
|
924
|
+
"""Adds a new world agent to the list.
|
|
925
|
+
|
|
926
|
+
Args:
|
|
927
|
+
peer_id: The peer ID of the new agent.
|
|
928
|
+
addrs: A list of addresses for the new agent.
|
|
929
|
+
role: The role assigned to the agent.
|
|
930
|
+
"""
|
|
931
|
+
self.world_agents_list.add(peer_id)
|
|
932
|
+
|
|
933
|
+
# This assumes that the WORLD MASTER/AGENT BIT is the first one
|
|
934
|
+
assert role & 1 == 1, "Expecting the first bit of the role to be 1 for world agents"
|
|
935
|
+
assert role & 2 == 0, "Expecting the second bit of the role to be 0 for world agents"
|
|
936
|
+
self.peer_id_to_addrs[peer_id] = addrs
|
|
937
|
+
self.set_role(peer_id, role)
|
|
938
|
+
self.world_agents_and_world_masters_list = self.world_agents_list | self.world_masters_list
|
|
939
|
+
|
|
940
|
+
def add_to_world_masters_list(self, peer_id: str, addrs: list[str], role: int = -1):
|
|
941
|
+
"""Adds a new world master to the list.
|
|
942
|
+
|
|
943
|
+
Args:
|
|
944
|
+
peer_id: The peer ID of the new master.
|
|
945
|
+
addrs: A list of addresses for the new master.
|
|
946
|
+
role: The role assigned to the master.
|
|
947
|
+
"""
|
|
948
|
+
self.world_masters_list.add(peer_id)
|
|
949
|
+
|
|
950
|
+
# This assumes that the WORLD MASTER/AGENT BIT is the first one
|
|
951
|
+
assert role & 1 == 1, "Expecting the first bit of the role to be 1 for world masters"
|
|
952
|
+
assert role & 2 == 2, "Expecting the second bit of the role to be 1 for world masters"
|
|
953
|
+
self.peer_id_to_addrs[peer_id] = addrs
|
|
954
|
+
self.set_role(peer_id, role)
|
|
955
|
+
self.world_agents_and_world_masters_list = self.world_agents_list | self.world_masters_list
|
|
956
|
+
|
|
957
|
+
def get_added_after_updating(self, pool_names: list[str] | None = None):
|
|
958
|
+
"""Retrieves the set of peers added after the last update cycle for specified pools.
|
|
959
|
+
|
|
960
|
+
Args:
|
|
961
|
+
pool_names: A list of pool names to check. If None, checks all pools.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
A dictionary mapping pool names to sets of added peer IDs, or a single set if only one pool is specified.
|
|
965
|
+
"""
|
|
966
|
+
if pool_names is not None:
|
|
967
|
+
ret = {}
|
|
968
|
+
for p in pool_names:
|
|
969
|
+
ret[p] = super().get_added_after_updating(p)
|
|
970
|
+
return ret
|
|
971
|
+
else:
|
|
972
|
+
return super().get_added_after_updating()
|
|
973
|
+
|
|
974
|
+
def get_removed_after_updating(self, pool_names: list[str] | None = None):
|
|
975
|
+
"""Retrieves the set of peers removed after the last update cycle for specified pools.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
pool_names: A list of pool names to check. If None, checks all pools.
|
|
979
|
+
|
|
980
|
+
Returns:
|
|
981
|
+
A dictionary mapping pool names to sets of removed peer IDs, or a single set if only one pool is specified.
|
|
982
|
+
"""
|
|
983
|
+
if pool_names is not None:
|
|
984
|
+
ret = {}
|
|
985
|
+
for p in pool_names:
|
|
986
|
+
ret[p] = super().get_removed_after_updating(p)
|
|
987
|
+
return ret
|
|
988
|
+
else:
|
|
989
|
+
return super().get_removed_after_updating()
|
|
990
|
+
|
|
991
|
+
def size(self, pool_names: list[str] | None = None):
|
|
992
|
+
"""Returns the total number of connections across all specified pools.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
pool_names: A list of pool names to sum the size of. If None, returns the total size of all pools.
|
|
996
|
+
|
|
997
|
+
Returns:
|
|
998
|
+
The total number of connections.
|
|
999
|
+
"""
|
|
1000
|
+
if pool_names is not None:
|
|
1001
|
+
return super().size()
|
|
1002
|
+
else:
|
|
1003
|
+
c = 0
|
|
1004
|
+
for p in self.pool_names:
|
|
1005
|
+
c += super().size(p)
|
|
1006
|
+
return c
|
|
1007
|
+
|
|
1008
|
+
def is_connected(self, peer_id: str, pool_names: list[str] | None = None):
|
|
1009
|
+
"""Checks if a peer is connected in any of the specified pools.
|
|
1010
|
+
|
|
1011
|
+
Args:
|
|
1012
|
+
peer_id: The peer ID to check.
|
|
1013
|
+
pool_names: A list of pool names to search within. If None, searches all pools.
|
|
1014
|
+
|
|
1015
|
+
Returns:
|
|
1016
|
+
True if the peer is found in any of the pools, otherwise False.
|
|
1017
|
+
"""
|
|
1018
|
+
if pool_names is None:
|
|
1019
|
+
return super().is_connected(peer_id)
|
|
1020
|
+
else:
|
|
1021
|
+
for p in pool_names:
|
|
1022
|
+
if super().is_connected(peer_id, p):
|
|
1023
|
+
return True
|
|
1024
|
+
return False
|
|
1025
|
+
|
|
1026
|
+
def is_public(self, peer_id):
|
|
1027
|
+
"""Checks if a peer is connected via the public network.
|
|
1028
|
+
|
|
1029
|
+
Args:
|
|
1030
|
+
peer_id: The peer ID to check.
|
|
1031
|
+
|
|
1032
|
+
Returns:
|
|
1033
|
+
True if the peer is in a public pool, otherwise False.
|
|
1034
|
+
"""
|
|
1035
|
+
pool_name = self.get_pool_of(peer_id)
|
|
1036
|
+
return pool_name in NodeConn.PUBLIC
|
|
1037
|
+
|
|
1038
|
+
def is_world_master(self, peer_id):
|
|
1039
|
+
"""Checks if a peer is a world master.
|
|
1040
|
+
|
|
1041
|
+
Args:
|
|
1042
|
+
peer_id: The peer ID to check.
|
|
1043
|
+
|
|
1044
|
+
Returns:
|
|
1045
|
+
True if the peer is in a world master pool, otherwise False.
|
|
1046
|
+
"""
|
|
1047
|
+
pool_name = self.get_pool_of(peer_id)
|
|
1048
|
+
return pool_name in NodeConn.WORLD_MASTERS
|
|
1049
|
+
|
|
1050
|
+
def is_world_node(self, peer_id):
|
|
1051
|
+
"""Checks if a peer is the world node.
|
|
1052
|
+
|
|
1053
|
+
Args:
|
|
1054
|
+
peer_id: The peer ID to check.
|
|
1055
|
+
|
|
1056
|
+
Returns:
|
|
1057
|
+
True if the peer is in a world node pool, otherwise False.
|
|
1058
|
+
"""
|
|
1059
|
+
pool_name = self.get_pool_of(peer_id)
|
|
1060
|
+
return pool_name in NodeConn.WORLD_NODE
|
|
1061
|
+
|
|
1062
|
+
def is_in_world(self, peer_id):
|
|
1063
|
+
"""Checks if a peer is connected to the world network.
|
|
1064
|
+
|
|
1065
|
+
Args:
|
|
1066
|
+
peer_id: The peer ID to check.
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
True if the peer is in any world pool, otherwise False.
|
|
1070
|
+
"""
|
|
1071
|
+
pool_name = self.get_pool_of(peer_id)
|
|
1072
|
+
return pool_name in NodeConn.WORLD
|
|
1073
|
+
|
|
1074
|
+
def get_role(self, peer_id):
|
|
1075
|
+
"""Retrieves the role of a given peer.
|
|
1076
|
+
|
|
1077
|
+
Args:
|
|
1078
|
+
peer_id: The peer ID to query.
|
|
1079
|
+
|
|
1080
|
+
Returns:
|
|
1081
|
+
The integer role of the peer.
|
|
1082
|
+
"""
|
|
1083
|
+
role = self.peer_id_to_misc.get(peer_id, 0) # 0 means public
|
|
1084
|
+
assert role >= 0, "Expecting role to be >= 0"
|
|
1085
|
+
assert role & 1 != 0 or role == 0, "Expecting public role to be zero (all-zero-bits)"
|
|
1086
|
+
return role
|
|
1087
|
+
|
|
1088
|
+
def get_addrs(self, peer_id):
|
|
1089
|
+
"""Retrieves the list of addresses for a given peer.
|
|
1090
|
+
|
|
1091
|
+
Args:
|
|
1092
|
+
peer_id: The peer ID to query.
|
|
1093
|
+
|
|
1094
|
+
Returns:
|
|
1095
|
+
A list of addresses for the peer.
|
|
1096
|
+
"""
|
|
1097
|
+
return self.peer_id_to_addrs.get(peer_id)
|
|
1098
|
+
|
|
1099
|
+
def in_connection_queues(self, peer_id):
|
|
1100
|
+
"""Checks if a peer ID exists in any connection pool.
|
|
1101
|
+
|
|
1102
|
+
Args:
|
|
1103
|
+
peer_id: The peer ID to check.
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
True if the peer is found in any pool, otherwise False.
|
|
1107
|
+
"""
|
|
1108
|
+
return peer_id in self.peer_id_to_pool_name
|
|
1109
|
+
|
|
1110
|
+
def find_addrs_by_role(self, role, return_peer_ids_too: bool = False):
|
|
1111
|
+
"""Finds all addresses of peers with a specific role.
|
|
1112
|
+
|
|
1113
|
+
Args:
|
|
1114
|
+
role: The integer role to search for.
|
|
1115
|
+
return_peer_ids_too: A boolean to also return the peer IDs.
|
|
1116
|
+
|
|
1117
|
+
Returns:
|
|
1118
|
+
A list of lists of addresses, and optionally a list of peer IDs.
|
|
1119
|
+
"""
|
|
1120
|
+
if role in self.role_to_peer_ids:
|
|
1121
|
+
peer_ids = self.role_to_peer_ids[role]
|
|
1122
|
+
else:
|
|
1123
|
+
if not return_peer_ids_too:
|
|
1124
|
+
return []
|
|
1125
|
+
else:
|
|
1126
|
+
return [], []
|
|
1127
|
+
ret_addrs = []
|
|
1128
|
+
ret_peer_ids = []
|
|
1129
|
+
for peer_id in peer_ids:
|
|
1130
|
+
addrs = self.get_addrs(peer_id)
|
|
1131
|
+
if addrs is not None:
|
|
1132
|
+
ret_addrs.append(addrs)
|
|
1133
|
+
ret_peer_ids.append(peer_id)
|
|
1134
|
+
if not return_peer_ids_too:
|
|
1135
|
+
return ret_addrs
|
|
1136
|
+
else:
|
|
1137
|
+
return ret_addrs, ret_peer_ids
|
|
1138
|
+
|
|
1139
|
+
def count_by_role(self, role: int):
|
|
1140
|
+
"""Counts the number of peers with a specific role.
|
|
1141
|
+
|
|
1142
|
+
Args:
|
|
1143
|
+
role: The integer role to count.
|
|
1144
|
+
|
|
1145
|
+
Returns:
|
|
1146
|
+
The number of peers with that role.
|
|
1147
|
+
"""
|
|
1148
|
+
if role in self.role_to_peer_ids:
|
|
1149
|
+
return len(self.role_to_peer_ids[role])
|
|
1150
|
+
else:
|
|
1151
|
+
return 0
|
|
1152
|
+
|
|
1153
|
+
def get_all_connected_peer_infos(self, pool_names: list[str] | set[str]):
|
|
1154
|
+
"""Retrieves a list of all peer info dictionaries for the specified pools.
|
|
1155
|
+
|
|
1156
|
+
Args:
|
|
1157
|
+
pool_names: A list or set of pool names to query.
|
|
1158
|
+
|
|
1159
|
+
Returns:
|
|
1160
|
+
A list of dictionaries containing peer information.
|
|
1161
|
+
"""
|
|
1162
|
+
ret = []
|
|
1163
|
+
for p in pool_names:
|
|
1164
|
+
ret += super().get_all_connected_peer_infos(p)
|
|
1165
|
+
return ret
|
|
1166
|
+
|
|
1167
|
+
def set_world_agents_and_world_masters_lists_from_rendezvous(self):
|
|
1168
|
+
"""Updates the lists of world agents and masters using data from the rendezvous topic."""
|
|
1169
|
+
rendezvous_state = self.p2p_world.get_rendezvous_peers_info()
|
|
1170
|
+
|
|
1171
|
+
if rendezvous_state is not None:
|
|
1172
|
+
tag = rendezvous_state.get('update_count', -1)
|
|
1173
|
+
|
|
1174
|
+
if tag > self.rendezvous_tag:
|
|
1175
|
+
self.rendezvous_tag = tag
|
|
1176
|
+
rendezvous_peer_infos = rendezvous_state.get('peers', [])
|
|
1177
|
+
|
|
1178
|
+
world_agents_peer_infos = []
|
|
1179
|
+
world_masters_peer_infos = []
|
|
1180
|
+
|
|
1181
|
+
if ConnectionPools.DEBUG:
|
|
1182
|
+
print(f"[DEBUG CONNECTIONS-POOL] Rendezvous peer infos (tag: {tag}, peers: "
|
|
1183
|
+
f"{len(rendezvous_peer_infos)} peers)")
|
|
1184
|
+
|
|
1185
|
+
for c in rendezvous_peer_infos:
|
|
1186
|
+
if c['addrs'] is None or len(c['addrs']) == 0:
|
|
1187
|
+
print(f"[DEBUG CONNECTIONS-POOL] Skipping a peer with missing addrs: {c}")
|
|
1188
|
+
continue
|
|
1189
|
+
if (c['misc'] & 1) == 1 and (c['misc'] & 2) == 0:
|
|
1190
|
+
world_agents_peer_infos.append(c)
|
|
1191
|
+
elif (c['misc'] & 1) == 1 and (c['misc'] & 2) == 2:
|
|
1192
|
+
world_masters_peer_infos.append(c)
|
|
1193
|
+
else:
|
|
1194
|
+
raise ValueError("Unexpected value of the 'misc' field: " + str(c))
|
|
1195
|
+
|
|
1196
|
+
# Updating lists
|
|
1197
|
+
self.set_world_agents_list(world_agents_peer_infos)
|
|
1198
|
+
self.set_world_masters_list(world_masters_peer_infos)
|
|
1199
|
+
|
|
1200
|
+
def get_cv_hash_from_last_token(self, peer_id):
|
|
1201
|
+
"""Retrieves the CV hash from the last token received from a peer.
|
|
1202
|
+
|
|
1203
|
+
Args:
|
|
1204
|
+
peer_id: The peer ID to query.
|
|
1205
|
+
|
|
1206
|
+
Returns:
|
|
1207
|
+
The CV hash string, or None if not found.
|
|
1208
|
+
"""
|
|
1209
|
+
token = self.get_last_token(peer_id)
|
|
1210
|
+
if token is not None:
|
|
1211
|
+
_, cv_hash = self.verify_token(token, peer_id)
|
|
1212
|
+
return cv_hash
|
|
1213
|
+
else:
|
|
1214
|
+
return None
|
|
1215
|
+
|
|
1216
|
+
def remove(self, peer_id: str):
|
|
1217
|
+
"""Removes a peer and its associated information from all lists and pools.
|
|
1218
|
+
|
|
1219
|
+
Args:
|
|
1220
|
+
peer_id: The peer ID to remove.
|
|
1221
|
+
"""
|
|
1222
|
+
super().remove(peer_id)
|
|
1223
|
+
if peer_id in self.peer_id_to_addrs:
|
|
1224
|
+
del self.peer_id_to_addrs[peer_id]
|
|
1225
|
+
|
|
1226
|
+
def remove_all_world_agents(self):
|
|
1227
|
+
"""Removes all connected world agents from the pools and role lists."""
|
|
1228
|
+
peer_infos = self.get_all_connected_peer_infos(NodeConn.WORLD)
|
|
1229
|
+
for c in peer_infos:
|
|
1230
|
+
peer_id = c['id']
|
|
1231
|
+
self.remove(peer_id)
|
|
1232
|
+
for role, peer_ids in self.role_to_peer_ids.items():
|
|
1233
|
+
if role & 1 == NodeConn.WORLD:
|
|
1234
|
+
peer_ids.remove(peer_id)
|
|
1235
|
+
|
|
1236
|
+
def subscribe(self, peer_id: str, channel: str, default_p2p_name: str | None = None):
|
|
1237
|
+
"""Subscribes to a channel, defaulting to the world P2P network if a network is not specified.
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
peer_id: The peer ID associated with the channel.
|
|
1241
|
+
channel: The channel to subscribe to.
|
|
1242
|
+
default_p2p_name: An optional P2P name to use for the subscription.
|
|
1243
|
+
|
|
1244
|
+
Returns:
|
|
1245
|
+
True if successful, False otherwise.
|
|
1246
|
+
"""
|
|
1247
|
+
return super().subscribe(peer_id, channel,
|
|
1248
|
+
default_p2p_name=NodeConn.P2P_WORLD if default_p2p_name is None else default_p2p_name)
|
|
1249
|
+
|
|
1250
|
+
def get_messages(self, p2p_name: str, allowed_not_connected_peers: set | None = None):
|
|
1251
|
+
"""Retrieves messages, allowing for messages from known world agents and masters even if not in a connection pool.
|
|
1252
|
+
|
|
1253
|
+
Args:
|
|
1254
|
+
p2p_name: The name of the P2P network to get messages from.
|
|
1255
|
+
allowed_not_connected_peers: This parameter is ignored in this implementation.
|
|
1256
|
+
|
|
1257
|
+
Returns:
|
|
1258
|
+
A list of verified and processed message objects.
|
|
1259
|
+
"""
|
|
1260
|
+
assert allowed_not_connected_peers is None, "This param (allowed_not_connected_peers is ignored in NodeConn"
|
|
1261
|
+
return super().get_messages(p2p_name, allowed_not_connected_peers=self.world_agents_and_world_masters_list)
|